├── .clang-tidy ├── .github └── FUNDING.yml ├── .gitignore ├── .gitmodules ├── .lgtm.yml ├── .lgtm └── cpp-queries │ ├── const-cast.ql │ └── reinterpret-cast.ql ├── CODE_OF_CONDUCT.md ├── LICENSE ├── LICENSE.hardware ├── README.md ├── common ├── include │ └── usbProtocol.hxx └── scripts │ └── get_fw_size.py ├── cross-files ├── arm-none-eabi-cxc.meson ├── arm-none-eabi-system.meson └── arm-none-eabi.meson ├── deps ├── dragonTI.wrap ├── dragonUSB.wrap ├── fmt.wrap ├── packagefiles │ └── fmt-9.1.0 │ │ ├── LICENSE.build │ │ └── meson.build └── substrate.wrap ├── firmware ├── constants.hxx ├── flash.cxx ├── flash.hxx ├── indexedIterator.hxx ├── led.cxx ├── led.hxx ├── meson.build ├── osc.cxx ├── osc.hxx ├── platform.hxx ├── sfdp.cxx ├── sfdp.hxx ├── spi.cxx ├── spi.hxx ├── spiFlashProgrammer.cxx ├── startup.cxx ├── timer.cxx ├── timer.hxx ├── tm4c123gh6pm │ └── memoryLayout.ld └── usb │ ├── descriptors.cxx │ ├── flashProto.cxx │ └── flashProto.hxx ├── hardware ├── .gitignore ├── Connectors.kicad_sch ├── Onboard_Flash.kicad_sch ├── Power_Supply.kicad_sch ├── Processor.kicad_sch ├── SPIFlashProgrammer.kicad_pcb ├── SPIFlashProgrammer.kicad_pro ├── SPIFlashProgrammer.kicad_sch └── gerber │ ├── .gitignore │ └── SPIFlashProgrammer.gvp ├── meson.build ├── runClangTidy.py └── software ├── flashprog.cxx ├── include ├── help.hxx ├── meson.build ├── progress.hxx ├── usbContext.hxx ├── usbDevice.hxx ├── usbDeviceList.hxx └── version.hxx.in ├── meson.build ├── options.hxx ├── progress.cxx ├── sfdp.cxx ├── sfdp.hxx ├── sfdpInternal.hxx └── utils ├── iterator.hxx ├── span.hxx └── units.hxx /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: 'clang-diagnostic-*,clang-analyzer-*,bugprone-*,cert-*,cppcoreguidelines-*,modernize-*,performance-*,readability-*,portability-*,clang-analyzer-unix,clang-analyzer-security,clang-analyzer-deadcode,clang-analyzer-core,clang-analyzer-cplusplus,clang-analyzer-optin,-modernize-use-trailing-return-type,-cppcoreguidelines-avoid-magic-numbers,-readability-redundant-member-init,-readability-else-after-return,-readability-implicit-bool-conversion,-readability-named-parameter,-cppcoreguidelines-pro-bounds-pointer-arithmetic,llvm-namespace-comment,-readability-magic-numbers,-cppcoreguidelines-pro-bounds-constant-array-index,-bugprone-easily-swappable-parameters' 3 | WarningsAsErrors: '' 4 | HeaderFilterRegex: '((common|software|firmware|deps/substrate/substrate]/.+)' 5 | FormatStyle: 'none' 6 | CheckOptions: 7 | - key: cert-dcl16-c.NewSuffixes 8 | value: 'L;LL;LU;LLU' 9 | - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField 10 | value: '0' 11 | - key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors 12 | value: '0' 13 | - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic 14 | value: '1' 15 | - key: modernize-loop-convert.MaxCopySize 16 | value: '16' 17 | - key: modernize-loop-convert.MinConfidence 18 | value: reasonable 19 | - key: modernize-loop-convert.NamingStyle 20 | value: camelBack 21 | - key: modernize-pass-by-value.IncludeStyle 22 | value: llvm 23 | - key: modernize-replace-auto-ptr.IncludeStyle 24 | value: llvm 25 | - key: modernize-use-nullptr.NullMacros 26 | value: 'NULL' 27 | - key: readability-braces-around-statements.ShortStatementLines 28 | value: '4' 29 | - key: llvm-namespace-comment.ShortNamespaceLines 30 | value: '20' 31 | - key: clang-analyzer-optin.performance.Padding 32 | value: true 33 | ... 34 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dragonmux 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /.vscode/ 3 | /deps/* 4 | !/deps/*.wrap 5 | !/deps/packagefiles 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bad-alloc-heavy-industries/SPIFlashProgrammer/e7a2dc2926df3248fc3a3f591baa5c457f24abfa/.gitmodules -------------------------------------------------------------------------------- /.lgtm.yml: -------------------------------------------------------------------------------- 1 | extraction: 2 | cpp: 3 | after_prepare: 4 | - PATH="$HOME/.local/bin:$PATH" 5 | - pip3 install --upgrade setuptools wheel 6 | - pip3 install --upgrade meson 7 | configure: 8 | command: 9 | - meson build --cross-file cross-files/arm-none-eabi-system.meson 10 | index: 11 | build_command: 12 | - ninja -C build 13 | queries: 14 | - include: "*" 15 | -------------------------------------------------------------------------------- /.lgtm/cpp-queries/const-cast.ql: -------------------------------------------------------------------------------- 1 | /** 2 | * @name const_cast<>() removing constness 3 | * @description Using const_cast<>() to remove constness is extremely discouraged 4 | * @kind problem 5 | * @problem.severity warning 6 | * @precision medium 7 | * @id cpp/const-cast 8 | * @tags reliability 9 | * correctness 10 | * security 11 | * external/cppcoreguidelines/pro-type-constcast 12 | */ 13 | 14 | import cpp 15 | 16 | from ConstCast cast_ 17 | where 18 | not cast_.isCompilerGenerated() and 19 | not (cast_.getUnderlyingType().isConst() or cast_.getUnderlyingType().isDeeplyConstBelow()) 20 | select cast_, "Casting away constness - const_cast<>()" 21 | -------------------------------------------------------------------------------- /.lgtm/cpp-queries/reinterpret-cast.ql: -------------------------------------------------------------------------------- 1 | /** 2 | * @name Potential UB - reinterpret_cast<>() 3 | * @description Using reinterpret_cast<>() is, except in rare circumstance, UB-invoking 4 | * Please do not use reinterpret_cast<>() 5 | * @kind problem 6 | * @problem.severity warning 7 | * @precision medium 8 | * @id cpp/reinterpret-cast 9 | * @tags reliability 10 | * correctness 11 | * security 12 | * external/cppcoreguidelines/pro-type-reinterpretcast 13 | */ 14 | 15 | import cpp 16 | 17 | from ReinterpretCast cast_ 18 | where 19 | not cast_.isCompilerGenerated() 20 | select cast_, "Potential UB - reinterpret_cast<>()" 21 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 121 | 122 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 123 | enforcement ladder](https://github.com/mozilla/diversity). 124 | 125 | [homepage]: https://www.contributor-covenant.org 126 | 127 | For answers to common questions about this code of conduct, see the FAQ at 128 | https://www.contributor-covenant.org/faq. Translations are available at 129 | https://www.contributor-covenant.org/translations. 130 | 131 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Rachel Mant 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /LICENSE.hardware: -------------------------------------------------------------------------------- 1 | CERN Open Hardware Licence Version 2 - Strongly Reciprocal 2 | 3 | 4 | Preamble 5 | 6 | CERN has developed this licence to promote collaboration among 7 | hardware designers and to provide a legal tool which supports the 8 | freedom to use, study, modify, share and distribute hardware designs 9 | and products based on those designs. Version 2 of the CERN Open 10 | Hardware Licence comes in three variants: CERN-OHL-P (permissive); and 11 | two reciprocal licences: CERN-OHL-W (weakly reciprocal) and this 12 | licence, CERN-OHL-S (strongly reciprocal). 13 | 14 | The CERN-OHL-S is copyright CERN 2020. Anyone is welcome to use it, in 15 | unmodified form only. 16 | 17 | Use of this Licence does not imply any endorsement by CERN of any 18 | Licensor or their designs nor does it imply any involvement by CERN in 19 | their development. 20 | 21 | 22 | 1 Definitions 23 | 24 | 1.1 'Licence' means this CERN-OHL-S. 25 | 26 | 1.2 'Compatible Licence' means 27 | 28 | a) any earlier version of the CERN Open Hardware licence, or 29 | 30 | b) any version of the CERN-OHL-S, or 31 | 32 | c) any licence which permits You to treat the Source to which 33 | it applies as licensed under CERN-OHL-S provided that on 34 | Conveyance of any such Source, or any associated Product You 35 | treat the Source in question as being licensed under 36 | CERN-OHL-S. 37 | 38 | 1.3 'Source' means information such as design materials or digital 39 | code which can be applied to Make or test a Product or to 40 | prepare a Product for use, Conveyance or sale, regardless of its 41 | medium or how it is expressed. It may include Notices. 42 | 43 | 1.4 'Covered Source' means Source that is explicitly made available 44 | under this Licence. 45 | 46 | 1.5 'Product' means any device, component, work or physical object, 47 | whether in finished or intermediate form, arising from the use, 48 | application or processing of Covered Source. 49 | 50 | 1.6 'Make' means to create or configure something, whether by 51 | manufacture, assembly, compiling, loading or applying Covered 52 | Source or another Product or otherwise. 53 | 54 | 1.7 'Available Component' means any part, sub-assembly, library or 55 | code which: 56 | 57 | a) is licensed to You as Complete Source under a Compatible 58 | Licence; or 59 | 60 | b) is available, at the time a Product or the Source containing 61 | it is first Conveyed, to You and any other prospective 62 | licensees 63 | 64 | i) as a physical part with sufficient rights and 65 | information (including any configuration and 66 | programming files and information about its 67 | characteristics and interfaces) to enable it either to 68 | be Made itself, or to be sourced and used to Make the 69 | Product; or 70 | ii) as part of the normal distribution of a tool used to 71 | design or Make the Product. 72 | 73 | 1.8 'Complete Source' means the set of all Source necessary to Make 74 | a Product, in the preferred form for making modifications, 75 | including necessary installation and interfacing information 76 | both for the Product, and for any included Available Components. 77 | If the format is proprietary, it must also be made available in 78 | a format (if the proprietary tool can create it) which is 79 | viewable with a tool available to potential licensees and 80 | licensed under a licence approved by the Free Software 81 | Foundation or the Open Source Initiative. Complete Source need 82 | not include the Source of any Available Component, provided that 83 | You include in the Complete Source sufficient information to 84 | enable a recipient to Make or source and use the Available 85 | Component to Make the Product. 86 | 87 | 1.9 'Source Location' means a location where a Licensor has placed 88 | Covered Source, and which that Licensor reasonably believes will 89 | remain easily accessible for at least three years for anyone to 90 | obtain a digital copy. 91 | 92 | 1.10 'Notice' means copyright, acknowledgement and trademark notices, 93 | Source Location references, modification notices (subsection 94 | 3.3(b)) and all notices that refer to this Licence and to the 95 | disclaimer of warranties that are included in the Covered 96 | Source. 97 | 98 | 1.11 'Licensee' or 'You' means any person exercising rights under 99 | this Licence. 100 | 101 | 1.12 'Licensor' means a natural or legal person who creates or 102 | modifies Covered Source. A person may be a Licensee and a 103 | Licensor at the same time. 104 | 105 | 1.13 'Convey' means to communicate to the public or distribute. 106 | 107 | 108 | 2 Applicability 109 | 110 | 2.1 This Licence governs the use, copying, modification, Conveying 111 | of Covered Source and Products, and the Making of Products. By 112 | exercising any right granted under this Licence, You irrevocably 113 | accept these terms and conditions. 114 | 115 | 2.2 This Licence is granted by the Licensor directly to You, and 116 | shall apply worldwide and without limitation in time. 117 | 118 | 2.3 You shall not attempt to restrict by contract or otherwise the 119 | rights granted under this Licence to other Licensees. 120 | 121 | 2.4 This Licence is not intended to restrict fair use, fair dealing, 122 | or any other similar right. 123 | 124 | 125 | 3 Copying, Modifying and Conveying Covered Source 126 | 127 | 3.1 You may copy and Convey verbatim copies of Covered Source, in 128 | any medium, provided You retain all Notices. 129 | 130 | 3.2 You may modify Covered Source, other than Notices, provided that 131 | You irrevocably undertake to make that modified Covered Source 132 | available from a Source Location should You Convey a Product in 133 | circumstances where the recipient does not otherwise receive a 134 | copy of the modified Covered Source. In each case subsection 3.3 135 | shall apply. 136 | 137 | You may only delete Notices if they are no longer applicable to 138 | the corresponding Covered Source as modified by You and You may 139 | add additional Notices applicable to Your modifications. 140 | Including Covered Source in a larger work is modifying the 141 | Covered Source, and the larger work becomes modified Covered 142 | Source. 143 | 144 | 3.3 You may Convey modified Covered Source (with the effect that You 145 | shall also become a Licensor) provided that You: 146 | 147 | a) retain Notices as required in subsection 3.2; 148 | 149 | b) add a Notice to the modified Covered Source stating that You 150 | have modified it, with the date and brief description of how 151 | You have modified it; 152 | 153 | c) add a Source Location Notice for the modified Covered Source 154 | if You Convey in circumstances where the recipient does not 155 | otherwise receive a copy of the modified Covered Source; and 156 | 157 | d) license the modified Covered Source under the terms and 158 | conditions of this Licence (or, as set out in subsection 159 | 8.3, a later version, if permitted by the licence of the 160 | original Covered Source). Such modified Covered Source must 161 | be licensed as a whole, but excluding Available Components 162 | contained in it, which remain licensed under their own 163 | applicable licences. 164 | 165 | 166 | 4 Making and Conveying Products 167 | 168 | You may Make Products, and/or Convey them, provided that You either 169 | provide each recipient with a copy of the Complete Source or ensure 170 | that each recipient is notified of the Source Location of the Complete 171 | Source. That Complete Source is Covered Source, and You must 172 | accordingly satisfy Your obligations set out in subsection 3.3. If 173 | specified in a Notice, the Product must visibly and securely display 174 | the Source Location on it or its packaging or documentation in the 175 | manner specified in that Notice. 176 | 177 | 178 | 5 Research and Development 179 | 180 | You may Convey Covered Source, modified Covered Source or Products to 181 | a legal entity carrying out development, testing or quality assurance 182 | work on Your behalf provided that the work is performed on terms which 183 | prevent the entity from both using the Source or Products for its own 184 | internal purposes and Conveying the Source or Products or any 185 | modifications to them to any person other than You. Any modifications 186 | made by the entity shall be deemed to be made by You pursuant to 187 | subsection 3.2. 188 | 189 | 190 | 6 DISCLAIMER AND LIABILITY 191 | 192 | 6.1 DISCLAIMER OF WARRANTY -- The Covered Source and any Products 193 | are provided 'as is' and any express or implied warranties, 194 | including, but not limited to, implied warranties of 195 | merchantability, of satisfactory quality, non-infringement of 196 | third party rights, and fitness for a particular purpose or use 197 | are disclaimed in respect of any Source or Product to the 198 | maximum extent permitted by law. The Licensor makes no 199 | representation that any Source or Product does not or will not 200 | infringe any patent, copyright, trade secret or other 201 | proprietary right. The entire risk as to the use, quality, and 202 | performance of any Source or Product shall be with You and not 203 | the Licensor. This disclaimer of warranty is an essential part 204 | of this Licence and a condition for the grant of any rights 205 | granted under this Licence. 206 | 207 | 6.2 EXCLUSION AND LIMITATION OF LIABILITY -- The Licensor shall, to 208 | the maximum extent permitted by law, have no liability for 209 | direct, indirect, special, incidental, consequential, exemplary, 210 | punitive or other damages of any character including, without 211 | limitation, procurement of substitute goods or services, loss of 212 | use, data or profits, or business interruption, however caused 213 | and on any theory of contract, warranty, tort (including 214 | negligence), product liability or otherwise, arising in any way 215 | in relation to the Covered Source, modified Covered Source 216 | and/or the Making or Conveyance of a Product, even if advised of 217 | the possibility of such damages, and You shall hold the 218 | Licensor(s) free and harmless from any liability, costs, 219 | damages, fees and expenses, including claims by third parties, 220 | in relation to such use. 221 | 222 | 223 | 7 Patents 224 | 225 | 7.1 Subject to the terms and conditions of this Licence, each 226 | Licensor hereby grants to You a perpetual, worldwide, 227 | non-exclusive, no-charge, royalty-free, irrevocable (except as 228 | stated in subsections 7.2 and 8.4) patent license to Make, have 229 | Made, use, offer to sell, sell, import, and otherwise transfer 230 | the Covered Source and Products, where such licence applies only 231 | to those patent claims licensable by such Licensor that are 232 | necessarily infringed by exercising rights under the Covered 233 | Source as Conveyed by that Licensor. 234 | 235 | 7.2 If You institute patent litigation against any entity (including 236 | a cross-claim or counterclaim in a lawsuit) alleging that the 237 | Covered Source or a Product constitutes direct or contributory 238 | patent infringement, or You seek any declaration that a patent 239 | licensed to You under this Licence is invalid or unenforceable 240 | then any rights granted to You under this Licence shall 241 | terminate as of the date such process is initiated. 242 | 243 | 244 | 8 General 245 | 246 | 8.1 If any provisions of this Licence are or subsequently become 247 | invalid or unenforceable for any reason, the remaining 248 | provisions shall remain effective. 249 | 250 | 8.2 You shall not use any of the name (including acronyms and 251 | abbreviations), image, or logo by which the Licensor or CERN is 252 | known, except where needed to comply with section 3, or where 253 | the use is otherwise allowed by law. Any such permitted use 254 | shall be factual and shall not be made so as to suggest any kind 255 | of endorsement or implication of involvement by the Licensor or 256 | its personnel. 257 | 258 | 8.3 CERN may publish updated versions and variants of this Licence 259 | which it considers to be in the spirit of this version, but may 260 | differ in detail to address new problems or concerns. New 261 | versions will be published with a unique version number and a 262 | variant identifier specifying the variant. If the Licensor has 263 | specified that a given variant applies to the Covered Source 264 | without specifying a version, You may treat that Covered Source 265 | as being released under any version of the CERN-OHL with that 266 | variant. If no variant is specified, the Covered Source shall be 267 | treated as being released under CERN-OHL-S. The Licensor may 268 | also specify that the Covered Source is subject to a specific 269 | version of the CERN-OHL or any later version in which case You 270 | may apply this or any later version of CERN-OHL with the same 271 | variant identifier published by CERN. 272 | 273 | 8.4 This Licence shall terminate with immediate effect if You fail 274 | to comply with any of its terms and conditions. 275 | 276 | 8.5 However, if You cease all breaches of this Licence, then Your 277 | Licence from any Licensor is reinstated unless such Licensor has 278 | terminated this Licence by giving You, while You remain in 279 | breach, a notice specifying the breach and requiring You to cure 280 | it within 30 days, and You have failed to come into compliance 281 | in all material respects by the end of the 30 day period. Should 282 | You repeat the breach after receipt of a cure notice and 283 | subsequent reinstatement, this Licence will terminate 284 | immediately and permanently. Section 6 shall continue to apply 285 | after any termination. 286 | 287 | 8.6 This Licence shall not be enforceable except by a Licensor 288 | acting as such, and third party beneficiary rights are 289 | specifically excluded. 290 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SPI Flash Programmer 2 | 3 | After a complete rewrite and re-hash of the original ideal having had it proove itself, a PCB and tightly integrated yet simple to use firmware is the name of the game. 4 | 5 | SPIFlashProgrammer is a small but capable board housing a Texas Instruments Tiva-C series ARM microprocessor, two Flash devices for use in a production environment to allow one board to program up to two different target devices on a production line, and USB for power and connecting to a host machine. 6 | 7 | When connected to a host, the USB connectivity and `flashprog` utility come together to form a sleak programming experience, allowing the tool to be used in a "set and forget" manner for test and debug of bitstreams and firmware. 8 | 9 | ## Flash chip support 10 | 11 | The firmware targets 25 series memory devices such as the M25P16, W25Q80BV, and AT25SF641. 12 | 13 | ## Build and installation 14 | 15 | Building requires a small number of prerequsites and dependencies: 16 | 17 | * [Meson](https://meson.build) 18 | * [Ninja](https://ninja-build.org) 19 | * A modern arm-none-eabi GCC such as the [GNU ARM Embedded Toolchain](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm) 20 | or any distro or custom build GCC capable of targeting ARMv7 via C++17 21 | 22 | To build the firmware and host software, run 23 | 24 | ```Bash 25 | meson build --cross-file=cross-files/arm-none-eabi.meson 26 | ninja -C build 27 | ``` 28 | 29 | You may then use the results in-place, or install the host software with `ninja -C build install`. 30 | 31 | Programming the firmware onto a newly minted board can be achived via any JTAG adapter that can talk TI ICDI. 32 | We use the awesome [Black Magic Probe](https://github.com/blacksphere/blackmagic) with the toolchain GDB. 33 | -------------------------------------------------------------------------------- /common/include/usbProtocol.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef USB_PROTOCOL_HXX 3 | #define USB_PROTOCOL_HXX 4 | 5 | #include 6 | #include 7 | 8 | #ifdef __arm__ 9 | #include 10 | #include 11 | #else 12 | #include 13 | #include "usbContext.hxx" 14 | #endif 15 | 16 | namespace flashProto 17 | { 18 | enum class messages_t : uint8_t 19 | { 20 | deviceCount, 21 | listDevice, 22 | targetDevice, 23 | erase, 24 | read, 25 | write, 26 | verifiedWrite, 27 | resetTarget, 28 | status, 29 | abort, 30 | sfdp 31 | }; 32 | 33 | enum class flashBus_t : uint8_t 34 | { 35 | internal, 36 | external, 37 | unknown 38 | }; 39 | 40 | enum class eraseOperation_t : uint8_t 41 | { 42 | all, 43 | page, 44 | pageRange, 45 | idle 46 | }; 47 | 48 | struct page_t final 49 | { 50 | private: 51 | std::array value{}; 52 | 53 | public: 54 | constexpr page_t() noexcept = default; 55 | constexpr page_t(const uint32_t page) noexcept : 56 | value{uint8_t(page), uint8_t(page >> 8U), uint8_t(page >> 16U)} { } 57 | 58 | constexpr operator uint32_t() const noexcept 59 | { return value[0] | (uint32_t(value[1]) << 8U) | (uint32_t(value[2] << 16U)); } 60 | 61 | constexpr page_t &operator ++() noexcept 62 | { 63 | const uint32_t page{*this}; 64 | *this = {page + 1}; 65 | return *this; 66 | } 67 | }; 68 | 69 | struct bool_t final 70 | { 71 | private: 72 | uint8_t value{}; 73 | 74 | public: 75 | constexpr bool_t() noexcept = default; 76 | constexpr bool_t(const bool value_) noexcept : value{uint8_t(value_ ? 1U : 0U)} { } 77 | constexpr operator bool() const noexcept { return value; } 78 | }; 79 | 80 | namespace responses 81 | { 82 | #ifndef __arm__ 83 | struct usbError_t final : std::exception 84 | { 85 | [[nodiscard]] const char *what() const noexcept final 86 | { return "Failure during reading data from the device"; } 87 | }; 88 | #endif 89 | 90 | struct deviceCount_t final 91 | { 92 | messages_t type{messages_t::deviceCount}; 93 | uint8_t internalCount{0}; 94 | uint8_t externalCount{0}; 95 | 96 | constexpr deviceCount_t() noexcept = default; 97 | 98 | #ifndef __arm__ 99 | deviceCount_t(const usbDeviceHandle_t &device, uint8_t endpoint) : deviceCount_t{} 100 | { 101 | if (!read(device, endpoint)) 102 | throw usbError_t{}; 103 | } 104 | 105 | [[nodiscard]] bool read(const usbDeviceHandle_t &device, uint8_t endpoint) noexcept 106 | { return device.readInterrupt(endpoint, &type, sizeof(deviceCount_t)); } 107 | #endif 108 | }; 109 | 110 | struct listDevice_t final 111 | { 112 | messages_t type{messages_t::listDevice}; 113 | uint8_t manufacturer{0}; 114 | uint8_t deviceType{0}; 115 | uint32_t deviceSize{0}; 116 | page_t pageSize{}; 117 | page_t eraseSize{}; 118 | 119 | constexpr listDevice_t() noexcept = default; 120 | 121 | #ifndef __arm__ 122 | listDevice_t(const usbDeviceHandle_t &device, uint8_t endpoint) : listDevice_t{} 123 | { 124 | if (!read(device, endpoint)) 125 | throw usbError_t{}; 126 | } 127 | 128 | [[nodiscard]] bool read(const usbDeviceHandle_t &device, uint8_t endpoint) noexcept 129 | { return device.readInterrupt(endpoint, &type, sizeof(listDevice_t)); } 130 | #endif 131 | }; 132 | 133 | struct erase_t final 134 | { 135 | messages_t type{messages_t::erase}; 136 | uint8_t complete{0}; 137 | page_t currentPage{}; 138 | 139 | constexpr erase_t() noexcept = default; 140 | 141 | #ifndef __arm__ 142 | erase_t(const usbDeviceHandle_t &device, uint8_t endpoint) : erase_t{} 143 | { 144 | if (!read(device, endpoint)) 145 | throw usbError_t{}; 146 | } 147 | 148 | [[nodiscard]] bool read(const usbDeviceHandle_t &device, uint8_t endpoint) noexcept 149 | { return device.readInterrupt(endpoint, &type, sizeof(erase_t)); } 150 | #endif 151 | }; 152 | 153 | struct write_t final 154 | { 155 | messages_t type{messages_t::write}; 156 | 157 | constexpr write_t() noexcept = default; 158 | 159 | #ifndef __arm__ 160 | write_t(const usbDeviceHandle_t &device, uint8_t endpoint) : write_t{} 161 | { 162 | if (!read(device, endpoint)) 163 | throw usbError_t{}; 164 | } 165 | 166 | [[nodiscard]] bool read(const usbDeviceHandle_t &device, uint8_t endpoint) noexcept 167 | { return device.readInterrupt(endpoint, &type, sizeof(write_t)); } 168 | #endif 169 | }; 170 | 171 | struct status_t final 172 | { 173 | uint8_t eraseComplete{}; 174 | page_t erasePage{}; 175 | bool writeOK{}; 176 | }; 177 | 178 | static_assert(sizeof(deviceCount_t) == 3); 179 | static_assert(sizeof(listDevice_t) == 16); 180 | static_assert(sizeof(erase_t) == 5); 181 | static_assert(sizeof(write_t) == 1); 182 | static_assert(sizeof(status_t) == 5); 183 | } // namespace responses 184 | 185 | namespace requests 186 | { 187 | namespace impl 188 | { 189 | struct address_t final 190 | { 191 | uint8_t addrL; 192 | uint8_t addrH; 193 | }; 194 | 195 | static_assert(sizeof(address_t) == sizeof(uint16_t)); 196 | } // namespace impl 197 | 198 | #ifndef __arm__ 199 | struct usbError_t final : std::exception 200 | { 201 | [[nodiscard]] const char *what() const noexcept final 202 | { return "Failure during writing data to the device"; } 203 | }; 204 | #endif 205 | 206 | struct deviceCount_t final 207 | { 208 | constexpr deviceCount_t() noexcept = default; 209 | 210 | #ifndef __arm__ 211 | [[nodiscard]] bool read(const usbDeviceHandle_t &device, uint8_t interface, 212 | responses::deviceCount_t &count) const noexcept 213 | { 214 | return device.readControl({recipient_t::interface, request_t::typeClass}, 215 | static_cast(messages_t::deviceCount), 0, interface, count); 216 | } 217 | #endif 218 | }; 219 | 220 | struct listDevice_t final 221 | { 222 | public: 223 | uint8_t deviceNumber{0}; 224 | flashBus_t deviceType{flashBus_t::unknown}; 225 | 226 | constexpr listDevice_t() noexcept = default; 227 | constexpr listDevice_t(const uint8_t number, const flashBus_t type) noexcept : 228 | deviceNumber{number}, deviceType{type} { } 229 | 230 | #ifndef __arm__ 231 | [[nodiscard]] bool read(const usbDeviceHandle_t &device, uint8_t interface, 232 | responses::listDevice_t &listing) const noexcept 233 | { 234 | impl::address_t address{deviceNumber, static_cast(deviceType)}; 235 | uint16_t index{}; 236 | std::memcpy(&index, &address, sizeof(uint16_t)); 237 | return device.readControl({recipient_t::interface, request_t::typeClass}, 238 | static_cast(messages_t::listDevice), index, interface, listing); 239 | } 240 | #endif 241 | 242 | private: 243 | }; 244 | 245 | struct targetDevice_t final 246 | { 247 | uint8_t deviceNumber{0}; 248 | flashBus_t deviceType{flashBus_t::unknown}; 249 | 250 | constexpr targetDevice_t() noexcept = default; 251 | constexpr targetDevice_t(const uint8_t number, const flashBus_t type) noexcept : 252 | deviceNumber{number}, deviceType{type} { } 253 | 254 | #ifndef __arm__ 255 | [[nodiscard]] bool write(const usbDeviceHandle_t &device, uint8_t interface) const noexcept 256 | { 257 | impl::address_t address{deviceNumber, static_cast(deviceType)}; 258 | uint16_t index{}; 259 | std::memcpy(&index, &address, sizeof(uint16_t)); 260 | return device.writeControl({recipient_t::interface, request_t::typeClass}, 261 | static_cast(messages_t::targetDevice), index, interface, nullptr); 262 | } 263 | #endif 264 | }; 265 | 266 | struct erase_t final 267 | { 268 | // Only valid for eraseOperation_t::page{,Range} 269 | page_t beginPage{}; 270 | // Only valid for eraseOperation_t::pageRange 271 | page_t endPage{}; 272 | 273 | constexpr erase_t() noexcept = default; 274 | constexpr erase_t(const page_t begin, const page_t end) noexcept : 275 | beginPage{begin}, endPage{end} { } 276 | 277 | #ifndef __arm__ 278 | [[nodiscard]] bool write(const usbDeviceHandle_t &device, uint8_t interface, 279 | const eraseOperation_t oper) const noexcept 280 | { 281 | uint16_t index{}; 282 | static_assert(sizeof(eraseOperation_t) == sizeof(uint8_t)); 283 | std::memcpy(&index, &oper, sizeof(uint8_t)); 284 | return device.writeControl({recipient_t::interface, request_t::typeClass}, 285 | static_cast(messages_t::erase), index, interface, *this); 286 | } 287 | #endif 288 | }; 289 | 290 | struct read_t final 291 | { 292 | page_t page{}; 293 | 294 | constexpr read_t() noexcept = default; 295 | constexpr read_t(const page_t pageNumber) noexcept : page{pageNumber} { } 296 | 297 | #ifndef __arm__ 298 | [[nodiscard]] bool write(const usbDeviceHandle_t &device, uint8_t interface, 299 | const uint16_t readCount = 0) const noexcept 300 | { 301 | return device.writeControl({recipient_t::interface, request_t::typeClass}, 302 | static_cast(messages_t::read), readCount, interface, page); 303 | } 304 | #endif 305 | }; 306 | 307 | // This write_t is then followed by 64-byte blocks of data 308 | // Which contsitute the new contents of the page being written. 309 | struct write_t final 310 | { 311 | bool verify{false}; 312 | page_t page{}; 313 | 314 | constexpr write_t() noexcept = default; 315 | constexpr write_t(const page_t pageNumber, bool verifyWrite = false) noexcept : 316 | verify{verifyWrite}, page{pageNumber} { } 317 | 318 | #ifndef __arm__ 319 | [[nodiscard]] bool write(const usbDeviceHandle_t &device, uint8_t interface, 320 | const uint16_t writeCount = 0) const noexcept 321 | { 322 | const auto request{verify ? messages_t::verifiedWrite : messages_t::write}; 323 | return device.writeControl({recipient_t::interface, request_t::typeClass}, 324 | static_cast(request), writeCount, interface, page); 325 | } 326 | #endif 327 | }; 328 | 329 | struct resetTarget_t final 330 | { 331 | messages_t type{messages_t::resetTarget}; 332 | }; 333 | 334 | struct status_t final 335 | { 336 | #ifndef __arm__ 337 | [[nodiscard]] bool read(const usbDeviceHandle_t &device, uint8_t interface, 338 | responses::status_t &status) const noexcept 339 | { 340 | return device.readControl({recipient_t::interface, request_t::typeClass}, 341 | static_cast(messages_t::status), 0, interface, status); 342 | } 343 | #endif 344 | }; 345 | 346 | struct abort_t final 347 | { 348 | #ifndef __arm__ 349 | [[nodiscard]] bool write(const usbDeviceHandle_t &device, uint8_t interface) const noexcept 350 | { 351 | return device.writeControl({recipient_t::interface, request_t::typeClass}, 352 | static_cast(messages_t::abort), 0, interface, nullptr); 353 | } 354 | #endif 355 | }; 356 | 357 | struct sfdp_t final 358 | { 359 | constexpr sfdp_t() noexcept = default; 360 | 361 | #ifndef __arm__ 362 | [[nodiscard]] bool write(const usbDeviceHandle_t &device, uint8_t interface, 363 | const uint16_t readCount, const uint32_t address) const noexcept 364 | { 365 | return device.writeControl({recipient_t::interface, request_t::typeClass}, 366 | static_cast(messages_t::sfdp), readCount, interface, address); 367 | } 368 | #endif 369 | }; 370 | 371 | static_assert(sizeof(deviceCount_t) == 1); 372 | static_assert(sizeof(listDevice_t) == 2); 373 | static_assert(sizeof(targetDevice_t) == 2); 374 | static_assert(sizeof(erase_t) == 6); 375 | static_assert(sizeof(read_t) == 3); 376 | static_assert(sizeof(write_t) == 4); 377 | } // namespace requests 378 | } // namespace flashProto 379 | 380 | #endif /*USB_PROTOCOL_HXX*/ 381 | -------------------------------------------------------------------------------- /common/scripts/get_fw_size.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: MIT 3 | # SPDX-FileCopyrightText: Stargirl Flowers (@theacodes) 2020-2021 4 | 5 | import argparse 6 | import colorsys 7 | import collections 8 | import dataclasses 9 | import json 10 | import math 11 | import pathlib 12 | import sys 13 | import subprocess 14 | import typing 15 | 16 | COLUMNS = ["<15", ">8", "^5", ">8", ">7"] 17 | COLUMNS_ALT = ["<15", ">8", "<13", ">7"] 18 | FIXED_SEG_COLOR = (255, 158, 221) 19 | BAR_FILL_CHAR = "░" 20 | BAR_FILL_COLOR = (0.4, 0.4, 0.4) 21 | GRADIENT_START = colorsys.hsv_to_rgb(188 / 360, 0.8, 1.0) 22 | GRADIENT_END = colorsys.hsv_to_rgb(0.8, 0.8, 1.0) 23 | PLUS_COLOR = (1.0, 1.0, 0.5) 24 | MINUS_COLOR = (127, 255, 191) 25 | 26 | 27 | class TermColor: 28 | reset = "\u001b[0m" 29 | 30 | @staticmethod 31 | def gradient(a, b, v): 32 | r = a[0] + v * (b[0] - a[0]) 33 | g = a[1] + v * (b[1] - a[1]) 34 | b = a[2] + v * (b[2] - a[2]) 35 | return r, g, b 36 | 37 | @staticmethod 38 | def esc(r, g=None, b=None): 39 | if isinstance(r, tuple): 40 | r, g, b = r 41 | 42 | if r > 1 or g > 1 or b > 1: 43 | r, g, b = r / 255, g / 255, b / 255 44 | 45 | r, g, b = [int(x * 255) for x in (r, g, b)] 46 | return f"\u001b[38;2;{r};{g};{b}m" 47 | 48 | 49 | class TermUI: 50 | @staticmethod 51 | def segmented_bar(length, *segments, fill=False): 52 | segments = list(segments) 53 | 54 | # Add end segment if needed. 55 | if fill: 56 | left_to_fill = 1.0 - sum(s["l"] for s in segments) 57 | segments.append(dict(l=left_to_fill, c=BAR_FILL_COLOR, p=BAR_FILL_CHAR)) 58 | 59 | # Largest remainder method allocation 60 | seg_lengths = [math.floor(s["l"] * length) for s in segments] 61 | seg_fract = [(n, (s["l"] * length) % 1.0) for n, s in enumerate(segments)] 62 | seg_fract.sort(key=lambda x: x[1], reverse=True) 63 | remainder = length - sum(seg_lengths) 64 | 65 | for n in range(remainder): 66 | seg_lengths[seg_fract[n][0]] += 1 67 | 68 | # Now draw 69 | for n, seg in enumerate(segments): 70 | print( 71 | TermColor.esc(*seg["c"]), 72 | seg.get("p", "▓") * seg_lengths[n], 73 | sep="", 74 | end="", 75 | ) 76 | 77 | print(end="\n") 78 | 79 | @staticmethod 80 | def columnize(columns, *values): 81 | n = 0 82 | for v in values: 83 | if isinstance(v, str) and v.startswith("\u001b"): 84 | print(v, end="") 85 | continue 86 | if isinstance(v, tuple) and len(v) == 3: 87 | print(TermColor.esc(v), end="") 88 | continue 89 | 90 | c = columns[n] 91 | formatter = f"{{: {c}}}" 92 | print(formatter.format(v), end="") 93 | 94 | n += 1 95 | 96 | print(TermColor.reset, end="\n") 97 | 98 | @staticmethod 99 | def columns_length(columns): 100 | return sum(int(s[1:]) for s in columns) 101 | 102 | 103 | BAR_LEN = TermUI.columns_length(COLUMNS) 104 | 105 | 106 | def analyze_elf(elf, size_prog): 107 | fw_size_output = subprocess.check_output([size_prog, "-A", "-d", elf]) 108 | fw_size_output = fw_size_output.decode("utf-8").split("\n")[2:] 109 | sections = {} 110 | bootloader_size = 0 111 | 112 | for line in fw_size_output: 113 | if not line: 114 | continue 115 | parts = line.split(None) 116 | sections[parts[0]] = int(parts[1], 10) 117 | if parts[0] == ".text": 118 | bootloader_size = int(parts[2], 10) 119 | 120 | program_size = sections[".text"] + sections.get(".relocate", 0) + sections.get(".data", 0) 121 | stack_size = sections[".stack"] 122 | variables_size = sections.get(".relocate", 0) + sections.get(".data", 0) + sections[".bss"] 123 | 124 | return bootloader_size, program_size, stack_size, variables_size 125 | 126 | 127 | def color_for_percent(percentage): 128 | return TermColor.gradient(GRADIENT_START, GRADIENT_END, percentage) 129 | 130 | 131 | @dataclasses.dataclass 132 | class MemorySection: 133 | name: str 134 | size: int 135 | last_size: typing.Optional[int] = None 136 | fixed: bool = False 137 | 138 | 139 | def print_memory_sections(name, size, *sections): 140 | used = sum(s.size for s in sections) 141 | used_fixed = sum(s.size for s in sections if s.fixed) 142 | used_percent = used / size 143 | color = color_for_percent(used_percent) 144 | 145 | TermUI.columnize( 146 | COLUMNS, 147 | f"{name} used:", 148 | color, 149 | f"{used:,}", 150 | TermColor.reset, 151 | "/", 152 | f"{size:,}", 153 | color, 154 | f"({round(used_percent * 100)}%)", 155 | ) 156 | 157 | TermUI.segmented_bar( 158 | BAR_LEN, 159 | dict(l=used_fixed / size, c=FIXED_SEG_COLOR), 160 | dict(l=(used - used_fixed) / size, c=color_for_percent(used_percent)), 161 | fill=True, 162 | ) 163 | 164 | for sec in sections: 165 | if sec.fixed: 166 | color = FIXED_SEG_COLOR 167 | else: 168 | color = color_for_percent(sec.size / size) 169 | 170 | if sec.last_size is not None: 171 | diff = sec.size - sec.last_size 172 | if diff != 0: 173 | last_size_sec = ( 174 | MINUS_COLOR if diff < 0 else PLUS_COLOR, 175 | f" {diff:+,}", 176 | TermColor.reset, 177 | ) 178 | else: 179 | last_size_sec = ("",) 180 | else: 181 | last_size_sec = ("",) 182 | 183 | TermUI.columnize( 184 | COLUMNS_ALT, 185 | color, 186 | f"{sec.name}: ", 187 | f"{sec.size:,}", 188 | *last_size_sec, 189 | color, 190 | f"({round(sec.size / size * 100)}%)", 191 | ) 192 | 193 | 194 | def main(): 195 | parser = argparse.ArgumentParser("get_fw_size.py") 196 | parser.add_argument("elf_file", type=pathlib.Path) 197 | parser.add_argument("--flash-size", type=lambda x: int(x, 0)) 198 | parser.add_argument("--ram-size", type=lambda x: int(x, 0)) 199 | parser.add_argument("--no-last", type=bool, default=False) 200 | parser.add_argument("--size-prog", type=pathlib.Path, default="arm-none-eabi-size") 201 | 202 | args = parser.parse_args() 203 | 204 | build_dir = args.elf_file.parent 205 | last_file = build_dir / "fw-size.last" 206 | 207 | if last_file.exists(): 208 | last_data = json.loads(last_file.read_text()) 209 | last_program_size = last_data["program_size"] 210 | last_variables_size = last_data["variables_size"] 211 | else: 212 | last_program_size = None 213 | last_variables_size = None 214 | 215 | bootloader_size, program_size, stack_size, variables_size = analyze_elf( 216 | args.elf_file, args.size_prog 217 | ) 218 | if last_file.exists(): 219 | last_data = json.loads(last_file.read_text()) 220 | 221 | print_memory_sections( 222 | "Flash", 223 | args.flash_size, 224 | MemorySection(name="Bootloader", size=bootloader_size, fixed=True), 225 | MemorySection(name="Program", size=program_size, last_size=last_program_size), 226 | ) 227 | print() 228 | print_memory_sections( 229 | "RAM", 230 | args.ram_size, 231 | MemorySection(name="Stack", size=stack_size, fixed=True), 232 | MemorySection( 233 | name="Variables", size=variables_size, last_size=last_variables_size 234 | ), 235 | ) 236 | 237 | if not args.no_last: 238 | last_file.write_text( 239 | json.dumps( 240 | dict( 241 | program_size=program_size, 242 | variables_size=variables_size, 243 | ) 244 | ) 245 | ) 246 | 247 | 248 | if __name__ == "__main__": 249 | main() 250 | -------------------------------------------------------------------------------- /cross-files/arm-none-eabi-cxc.meson: -------------------------------------------------------------------------------- 1 | [constants] 2 | prefix = '/opt/cxc' 3 | exec_prefix = prefix / 'bin' 4 | plugin_dir = prefix / 'lib/gcc/arm-none-eabi/12.1.0' 5 | 6 | [properties] 7 | sys_root = prefix 8 | -------------------------------------------------------------------------------- /cross-files/arm-none-eabi-system.meson: -------------------------------------------------------------------------------- 1 | [constants] 2 | prefix = '/usr' 3 | exec_prefix = prefix / 'bin' 4 | common_flags = ['-mthumb', '-mcpu=cortex-m4', '-march=armv7e-m+fp', '-mfpu=fpv4-sp-d16', '-mfloat-abi=softfp'] 5 | compile_flags = ['-ffunction-sections', '-fdata-sections', '-DPART_TM4C123GH6PM', '-DARM_MATH_CM4F', '-DTARGET_IS_BLIZZARD_RA1'] 6 | link_flags = ['-nostartfiles', '--static', '-Wl,--gc-sections'] 7 | 8 | [binaries] 9 | c = exec_prefix / 'arm-none-eabi-gcc' 10 | cpp = exec_prefix / 'arm-none-eabi-g++' 11 | ar = exec_prefix / 'arm-none-eabi-ar' 12 | as = exec_prefix / 'arm-none-eabi-as' 13 | strip = exec_prefix / 'arm-none-eabi-strip' 14 | objcopy = exec_prefix / 'arm-none-eabi-objcopy' 15 | objdump = exec_prefix / 'arm-none-eabi-objdump' 16 | size = exec_prefix / 'arm-none-eabi-size' 17 | gdb = exec_prefix / 'arm-none-eabi-gdb' 18 | cmake = 'false' 19 | 20 | [properties] 21 | sizeof_char = 1 22 | sizeof_short = 2 23 | sizeof_int = 4 24 | sizeof_long = 4 25 | sizeof_longlong = 8 26 | sizeof_size_t = 4 27 | sizeof_ptrdiff_t = 4 28 | sizeof_void* = 4 29 | sizeof_float = 4 30 | sizeof_double = 8 31 | sizeof_longdouble = 8 32 | sizeof_wchar_t = 2 33 | 34 | needs_exe_wrapper = true 35 | sys_root = prefix 36 | 37 | [built-in options] 38 | c_args = common_flags + compile_flags 39 | cpp_args = common_flags + compile_flags + ['-fno-rtti', '-fno-exceptions'] 40 | c_link_args = common_flags + link_flags 41 | cpp_link_args = common_flags + link_flags 42 | 43 | cpp_eh = 'none' 44 | cpp_rtti = false 45 | 46 | [host_machine] 47 | system = 'TM4C123GH6PM' 48 | cpu_family = 'arm' 49 | cpu = 'cortex-m4f' 50 | endian = 'little' 51 | -------------------------------------------------------------------------------- /cross-files/arm-none-eabi.meson: -------------------------------------------------------------------------------- 1 | [constants] 2 | common_flags = ['-mthumb', '-mcpu=cortex-m4', '-march=armv7e-m+fp', '-mfpu=fpv4-sp-d16', '-mfloat-abi=softfp', '-mgeneral-regs-only'] 3 | compile_flags = ['-ffunction-sections', '-fdata-sections', '-DPART_TM4C123GH6PM', '-DARM_MATH_CM4F', '-DTARGET_IS_BLIZZARD_RA1'] 4 | link_flags = ['-nostartfiles', '--static', '-Wl,--gc-sections'] 5 | 6 | [binaries] 7 | c = exec_prefix / 'arm-none-eabi-gcc' 8 | cpp = exec_prefix / 'arm-none-eabi-g++' 9 | ar = [exec_prefix / 'arm-none-eabi-ar', '--plugin', plugin_dir / 'liblto_plugin.so'] 10 | as = exec_prefix / 'arm-none-eabi-as' 11 | strip = exec_prefix / 'arm-none-eabi-strip' 12 | objcopy = exec_prefix / 'arm-none-eabi-objcopy' 13 | objdump = exec_prefix / 'arm-none-eabi-objdump' 14 | size = exec_prefix / 'arm-none-eabi-size' 15 | gdb = exec_prefix / 'arm-none-eabi-gdb' 16 | cmake = 'false' 17 | 18 | [properties] 19 | sizeof_char = 1 20 | sizeof_short = 2 21 | sizeof_int = 4 22 | sizeof_long = 4 23 | sizeof_longlong = 8 24 | sizeof_size_t = 4 25 | sizeof_ptrdiff_t = 4 26 | sizeof_void* = 4 27 | sizeof_float = 4 28 | sizeof_double = 8 29 | sizeof_longdouble = 8 30 | sizeof_wchar_t = 2 31 | 32 | needs_exe_wrapper = true 33 | 34 | [built-in options] 35 | c_args = common_flags + compile_flags 36 | cpp_args = common_flags + compile_flags + ['-fno-rtti', '-fno-exceptions'] 37 | c_link_args = common_flags + link_flags 38 | cpp_link_args = common_flags + link_flags 39 | 40 | cpp_eh = 'none' 41 | cpp_rtti = false 42 | 43 | [host_machine] 44 | system = 'TM4C123GH6PM' 45 | cpu_family = 'arm' 46 | cpu = 'cortex-m4f' 47 | endian = 'little' 48 | -------------------------------------------------------------------------------- /deps/dragonTI.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/DX-MON/dragonTI.git 3 | revision = head 4 | clone-recursive = false 5 | -------------------------------------------------------------------------------- /deps/dragonUSB.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/DX-MON/dragonUSB.git 3 | revision = head 4 | clone-recursive = false 5 | -------------------------------------------------------------------------------- /deps/fmt.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = fmt-9.1.0 3 | source_url = https://github.com/fmtlib/fmt/archive/9.1.0.tar.gz 4 | source_filename = fmt-9.1.0.tar.gz 5 | source_hash = 5dea48d1fcddc3ec571ce2058e13910a0d4a6bab4cc09a809d8b1dd1c88ae6f2 6 | patch_directory = fmt-9.1.0 7 | wrapdb_version = 9.1.0-1 8 | 9 | [provide] 10 | fmt = fmt_dep 11 | -------------------------------------------------------------------------------- /deps/packagefiles/fmt-9.1.0/LICENSE.build: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 The Meson development team 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /deps/packagefiles/fmt-9.1.0/meson.build: -------------------------------------------------------------------------------- 1 | project('fmt', 'cpp', version: '9.1.0', license: 'BSD', default_options: ['cpp_std=c++14']) 2 | 3 | fmt_private_cpp_args = [] 4 | fmt_interface_cpp_args = [] 5 | if get_option('default_library') == 'shared' 6 | fmt_private_cpp_args += ['-DFMT_EXPORT'] 7 | fmt_interface_cpp_args += ['-DFMT_SHARED'] 8 | endif 9 | 10 | fmt_lib = library( 11 | 'fmt', 12 | 'src/format.cc', 13 | 'src/os.cc', 14 | cpp_args: fmt_private_cpp_args, 15 | include_directories: 'include', 16 | native: true, 17 | ) 18 | 19 | fmt_dep = declare_dependency( 20 | include_directories: 'include', 21 | compile_args: fmt_interface_cpp_args, 22 | link_with: fmt_lib, 23 | variables: { 24 | 'compile_args': '-I@0@'.format(meson.current_source_dir() / 'include'), 25 | 'link_args': fmt_lib.full_path() 26 | }, 27 | ) 28 | 29 | if meson.version().version_compare('>=0.54.0') 30 | meson.override_dependency('fmt', fmt_dep) 31 | endif 32 | 33 | fmt_header_only_dep = declare_dependency( 34 | include_directories: 'include', 35 | compile_args: '-DFMT_HEADER_ONLY', 36 | ) 37 | -------------------------------------------------------------------------------- /deps/substrate.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://github.com/bad-alloc-heavy-industries/substrate.git 3 | revision = head 4 | clone-recursive = false 5 | -------------------------------------------------------------------------------- /firmware/constants.hxx: -------------------------------------------------------------------------------- 1 | #ifndef CONSTANTS_HXX 2 | #define CONSTANTS_HXX 3 | 4 | #include 5 | 6 | constexpr static uint16_t vid{0x1209}; 7 | constexpr static uint16_t pid{0xAB0C}; 8 | 9 | #endif /*CONSTANTS_HXX*/ 10 | -------------------------------------------------------------------------------- /firmware/flash.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #include 3 | #include 4 | #include 5 | #include "flash.hxx" 6 | #include "sfdp.hxx" 7 | 8 | #ifdef __GNUC__ 9 | // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) 10 | #define unlikely(x) __builtin_expect((x), 0) 11 | #else 12 | // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) 13 | #define unlikely(x) (x) 14 | #endif 15 | 16 | using namespace substrate; 17 | 18 | namespace flash 19 | { 20 | struct flashManufacturer_t 21 | { 22 | uint8_t manufacturerID; 23 | const flashChip_t *chips; 24 | size_t chipCount; 25 | 26 | [[nodiscard]] const flashChip_t *begin() const noexcept { return chips; } 27 | [[nodiscard]] const flashChip_t *end() const noexcept { return chips + chipCount; } 28 | }; 29 | 30 | constexpr static auto adestoChips 31 | { 32 | substrate::make_array 33 | ({ 34 | {0x32, 0x17, 0x17, 0x20, 4_KiB, 256, 50} 35 | }) 36 | }; 37 | 38 | constexpr static auto numonyxChips 39 | { 40 | substrate::make_array 41 | ({ 42 | {0x20, 0x14, 0x14, 0xD8, 64_KiB, 256, 33}, 43 | {0x20, 0x15, 0x15, 0xD8, 64_KiB, 256, 33} 44 | }) 45 | }; 46 | 47 | constexpr static auto gigaDeviceChips 48 | { 49 | substrate::make_array 50 | ({ 51 | {0x40, 0x13, 0x13, 0x20, 4_KiB, 256, 80}, 52 | {0x40, 0x17, 0x17, 0x20, 4_KiB, 256, 80} 53 | }) 54 | }; 55 | 56 | constexpr static auto winbondChips 57 | { 58 | substrate::make_array 59 | ({ 60 | {0x40, 0x14, 0x14, 0x20, 4_KiB, 256, 50}, 61 | {0x40, 0x15, 0x15, 0x20, 4_KiB, 256, 50}, 62 | {0x40, 0x16, 0x16, 0x20, 4_KiB, 256, 50}, 63 | {0x40, 0x18, 0x18, 0x20, 4_KiB, 256, 50}, 64 | {0xAA, 0x21, 0x1B, 0xD8, 128_KiB, 2_KiB, 104}, 65 | {0x70, 0x18, 0x18, 0x20, 4_KiB, 256, 50} 66 | }) 67 | }; 68 | 69 | constexpr static auto manufacturers 70 | { 71 | substrate::make_array 72 | ({ 73 | {0x1F, adestoChips.data(), adestoChips.size()}, 74 | {0x20, numonyxChips.data(), numonyxChips.size()}, 75 | {0xC8, gigaDeviceChips.data(), gigaDeviceChips.size()}, 76 | {0xEF, winbondChips.data(), winbondChips.size()}, 77 | }) 78 | }; 79 | 80 | static inline uint8_t log2(uint32_t value) noexcept 81 | { 82 | if (unlikely(!value)) 83 | return UINT8_MAX; 84 | #if defined(__GNUC__) 85 | return (sizeof(uint32_t) * 8U) - static_cast(__builtin_clz(value)); 86 | #else 87 | uint8_t result{}; 88 | if (value <= UINT32_C(0x0000FFFF)) 89 | { 90 | result += 16U; 91 | value <<= 16U; 92 | } 93 | if (value <= UINT32_C(0x00FFFFFF)) 94 | { 95 | result += 8U; 96 | value <<= 8U; 97 | } 98 | if (value <= UINT32_C(0x0FFFFFFF)) 99 | { 100 | result += 4U; 101 | value <<= 4U; 102 | } 103 | if (value <= UINT32_C(0x3FFFFFFF)) 104 | { 105 | result += 2U; 106 | value <<= 2U; 107 | } 108 | if (value <= UINT32_C(0x7FFFFFFF)) 109 | ++result; 110 | return (sizeof(uint32_t) * 8U) - result; 111 | #endif 112 | } 113 | 114 | static std::optional readSFDP(const flashID_t chipID, const spiChip_t targetDevice) noexcept 115 | { 116 | // The SFDP code will reclock the bus for us if the SFDP data can be read 117 | const auto parameters{sfdp::parameters(targetDevice)}; 118 | if (!parameters) 119 | return std::nullopt; 120 | // Build a flashChip_t from the SFDP data gathered 121 | flashChip_t device{}; 122 | device.type = chipID.type; 123 | device.reportedCapacity = chipID.capacity; 124 | device.actualCapacity = log2(parameters->capacity); 125 | device.eraseInstruction = parameters->sectorEraseOpcode; 126 | device.erasePageSize = parameters->sectorSize; 127 | device.flashPageSize = parameters->pageSize; 128 | // SFDP guarantees 50MHz operation minimum 129 | device.chipSpeedMHz = 50U; 130 | return device; 131 | } 132 | 133 | flashChip_t findChip(const flashID_t chipID, const spiChip_t targetDevice) noexcept 134 | { 135 | // Loop through the manufacturers 136 | for (const auto &mfr : manufacturers) 137 | { 138 | if (mfr.manufacturerID == chipID.manufacturer) 139 | { 140 | // Once we've found a matching manufacturer, loop through their devices 141 | for (const auto &chip : mfr) 142 | { 143 | if (chip == chipID) 144 | { 145 | // If we find the chip in the chipDB, then reclock to its designated speed 146 | spiSetClock(targetDevice, chip.chipSpeedMHz); 147 | return chip; 148 | } 149 | } 150 | // Once we've exhausted this manufactuer's chipDB, exit out and check SFDP instead 151 | break; 152 | } 153 | } 154 | // If we didn't find the chip in the database, no worries - read the SFDP data if available 155 | const auto parameters{readSFDP(chipID, targetDevice)}; 156 | if (parameters) 157 | return *parameters; 158 | // If we could not read the SFDP data then fabricate something based on some sensible fallbacks 159 | // NB: This leaves the bus set to 500kHz as a safe bet (hence the 0 for chipSpeedMHz) 160 | return {chipID.type, chipID.capacity, chipID.capacity, 0xD8, 64_KiB, 256, 0}; 161 | } 162 | } // namespace flash 163 | -------------------------------------------------------------------------------- /firmware/flash.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef FLASH_HXX 3 | #define FLASH_HXX 4 | 5 | #include 6 | #include 7 | #include "usbProtocol.hxx" 8 | 9 | enum class spiChip_t 10 | { 11 | local1, 12 | local2, 13 | target, 14 | none 15 | }; 16 | 17 | struct flashID_t 18 | { 19 | uint8_t manufacturer; 20 | uint8_t type; 21 | uint8_t capacity; 22 | }; 23 | 24 | struct flashChip_t 25 | { 26 | uint8_t type; 27 | uint8_t reportedCapacity; 28 | uint8_t actualCapacity; 29 | uint8_t eraseInstruction; 30 | flashProto::page_t erasePageSize; 31 | flashProto::page_t flashPageSize; 32 | uint8_t chipSpeedMHz; 33 | 34 | flashChip_t() = delete; 35 | constexpr bool operator ==(const flashID_t id) const noexcept 36 | { return type == id.type && reportedCapacity == id.capacity; } 37 | }; 38 | 39 | namespace flash 40 | { 41 | flashChip_t findChip(flashID_t chipID, spiChip_t targetDevice) noexcept; 42 | } 43 | 44 | #endif /*FLASH_HXX*/ 45 | -------------------------------------------------------------------------------- /firmware/indexedIterator.hxx: -------------------------------------------------------------------------------- 1 | #ifndef INDEXED_ITERATOR_HXX 2 | #define INDEXED_ITERATOR_HXX 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace utility 9 | { 10 | template struct indexedItem_t final 11 | { 12 | private: 13 | std::pair item_; 14 | 15 | public: 16 | constexpr indexedItem_t(const std::size_t index, const T item) noexcept : item_{index, item} { } 17 | [[nodiscard]] constexpr auto index() const noexcept { return item_.first; } 18 | [[nodiscard]] constexpr auto item() const noexcept { return item_.second; } 19 | [[nodiscard]] constexpr auto &operator *() const noexcept { return item_; } 20 | 21 | constexpr auto operator ++() noexcept 22 | { 23 | ++item_.first; 24 | ++item_.second; 25 | return *this; 26 | } 27 | }; 28 | 29 | template indexedItem_t(std::size_t, T) -> indexedItem_t; 30 | 31 | template 32 | [[nodiscard]] constexpr inline auto operator ==(const indexedItem_t &a, const indexedItem_t &b) noexcept 33 | { return a.index() == b.index(); } 34 | template 35 | [[nodiscard]] constexpr inline auto operator !=(const indexedItem_t &a, const indexedItem_t &b) noexcept 36 | { return a.index() != b.index(); } 37 | 38 | template struct indexedIterator_t final 39 | { 40 | private: 41 | T &container_; 42 | 43 | public: 44 | constexpr indexedIterator_t(T &container) noexcept : container_{container} { } 45 | 46 | [[nodiscard]] constexpr auto begin() const noexcept 47 | { return indexedItem_t{0, std::begin(container_)}; } 48 | [[nodiscard]] constexpr auto end() const noexcept 49 | { return indexedItem_t{std::size(container_), std::end(container_)}; } 50 | }; 51 | 52 | template indexedIterator_t(T) -> indexedIterator_t; 53 | } // namespace utility 54 | 55 | #endif /*INDEXED_ITERATOR_HXX*/ 56 | -------------------------------------------------------------------------------- /firmware/led.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #include 3 | #include 4 | #include "led.hxx" 5 | 6 | /*! 7 | * RGB LED pinout: 8 | * PD0 - G 9 | * PD1 - R 10 | * PD2 - B 11 | */ 12 | 13 | void ledInit() noexcept 14 | { 15 | // Switch over to port D's AHB apeture 16 | sysCtrl.gpioAHBCtrl |= vals::sysCtrl::gpioAHBCtrlPortD; 17 | // Enable port D 18 | sysCtrl.runClockGateCtrlGPIO |= vals::sysCtrl::runClockGateCtrlGPIOD; 19 | 20 | // Wait for the port to come online 21 | while (!(sysCtrl.periphReadyGPIO & vals::sysCtrl::periphReadyGPIOD)) 22 | continue; 23 | 24 | // Set the RGB LED pins as digital mode outputs 25 | gpioD.den |= 0x07U; 26 | gpioD.afSel &= ~0x07U; 27 | gpioD.dir = 0x07U; 28 | 29 | // Turn the LED purple. 30 | ledSetColour(true, false, true); 31 | } 32 | 33 | void ledSetColour(bool r, bool g, bool b) noexcept 34 | { 35 | gpioD.dataBits[0x07U] = 36 | (r ? 0x02U : 0x00U) | 37 | (g ? 0x01U : 0x00U) | 38 | (b ? 0x04U : 0x00U); 39 | } 40 | -------------------------------------------------------------------------------- /firmware/led.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef LED_HXX 3 | #define LED_HXX 4 | 5 | void ledInit() noexcept; 6 | void ledSetColour(bool r, bool g, bool b) noexcept; 7 | 8 | #endif /*LED_HXX*/ 9 | -------------------------------------------------------------------------------- /firmware/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | 3 | substrate = subproject( 4 | 'substrate', 5 | default_options: [ 6 | 'build_tests=false', 7 | 'cpp_std=c++17', 8 | ] 9 | ).get_variable( 10 | 'substrate_dep' 11 | ).partial_dependency( 12 | compile_args: true, 13 | includes: true 14 | ) 15 | 16 | dragonTI = subproject( 17 | 'dragonTI', 18 | default_options: [ 19 | 'chip=tm4c123gh6pm', 20 | ] 21 | ).get_variable( 22 | 'dragonTI_dep' 23 | ).partial_dependency( 24 | compile_args: true, 25 | includes: true 26 | ) 27 | 28 | dragonUSB = subproject( 29 | 'dragonUSB', 30 | default_options: [ 31 | 'chip=tm4c123gh6pm', 32 | 'interfaces=2', 33 | 'endpoints=1', 34 | 'epBufferSize=64', 35 | 'configDescriptors=1', 36 | 'ifaceDescriptors=2', 37 | 'endpointDescriptors=2', 38 | 'strings=5', 39 | 'dfuFlashBufferSize=128', 40 | 'dfuFlashPageSize=128', 41 | 'drivers=dfu' 42 | ] 43 | ).get_variable('dragonUSB_dep') 44 | 45 | firmwareSrc = [ 46 | 'startup.cxx', 'spiFlashProgrammer.cxx', 'led.cxx', 'spi.cxx', 47 | 'sfdp.cxx', 'flash.cxx', 'osc.cxx', 'timer.cxx', 48 | 'usb/descriptors.cxx', 'usb/flashProto.cxx' 49 | ] 50 | 51 | firmwareArgs = targetCXX.get_supported_arguments( 52 | '-Wvla', 53 | '-Wimplicit-fallthrough', 54 | '-Wconversion', 55 | '-Wstack-usage=4096' 56 | ) 57 | 58 | firmware = executable( 59 | 'SPIFlashProgrammer', 60 | firmwareSrc, 61 | include_directories: [commonInclude], 62 | dependencies: [substrate, dragonTI, dragonUSB], 63 | cpp_args: firmwareArgs, 64 | link_args: ['-T', '@0@/tm4c123gh6pm/memoryLayout.ld'.format(meson.current_source_dir())], 65 | gnu_symbol_visibility: 'inlineshidden', 66 | name_suffix: 'elf', 67 | build_by_default: true, 68 | install: false 69 | ) 70 | 71 | objcopy = find_program('objcopy') 72 | firmwareBin = custom_target( 73 | 'SPIFlashProgrammer.bin', 74 | input: firmware, 75 | output: 'SPIFlashProgrammer.bin', 76 | command: [ 77 | objcopy, 78 | '-j', '.text', 79 | '-j', '.data', 80 | '-j', '.note.gnu.build-id', 81 | '-O', 'binary', 82 | '@INPUT@', 83 | '@OUTPUT@', 84 | ], 85 | build_by_default: true, 86 | install: false 87 | ) 88 | 89 | dfuUtil = find_program('dfu-util') 90 | run_target( 91 | 'program', 92 | command: [ 93 | dfuUtil, 94 | '-d', '1209:ab0c,:badb', '-R', 95 | '-D', firmwareBin 96 | ] 97 | ) 98 | 99 | objdump = find_program('objdump') 100 | run_target( 101 | 'disasm', 102 | command: [objdump, '-dC', firmware] 103 | ) 104 | 105 | size = find_program('get_fw_size.py', dirs: '@0@/../common/scripts'.format(meson.current_source_dir())) 106 | run_target( 107 | 'size', 108 | command: [ 109 | size, 110 | '--flash-size=262144', # 256KiB of Flash 111 | '--ram-size=32768', # 32KiB of RAM 112 | '--size-prog=@0@'.format(find_program('size').path()), 113 | firmware 114 | ] 115 | ) 116 | -------------------------------------------------------------------------------- /firmware/osc.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #include 3 | #include 4 | #include "osc.hxx" 5 | 6 | extern void oscInit() noexcept 7 | { 8 | // Clock bring-up on this chip has to be divided into three distinct phases, thanks to shenanigans. 9 | // In the first phase, MOsc has to be enabled 10 | // In the second, the CPU clock source has to be switched to MOsc and the PLL powered up 11 | // In the third, once the PLL reads as stable, the bypass removed and SysClockDiv configured 12 | // to provide a suitable operating clock. 13 | // Once all this is done, we finally end up running on the PLL generated clock and can 14 | // do bring-up on the USB PLL. 15 | // Additionally, the third phase has to switch clock config registers from the primary 16 | // to the secondary so we can select 80MHz as our operating frequency 17 | 18 | sysCtrl.mainOscCtrl = vals::sysCtrl::mainOscCtrlOscFailInterrupt | vals::sysCtrl::mainOscCtrlClockMonitorEnable; 19 | sysCtrl.runClockConfig1 &= vals::sysCtrl::runClockCfg1MainOscEnableMask; 20 | 21 | // TI have provided no way to check that the main oscillator came up.. instead 22 | // even their own platform library busy loops for 524288 iterations, then hopes 23 | // and prays when they enable the source that things don't go sideways in a hurry. 24 | for (volatile uint32_t loops = 524288U; loops; --loops) 25 | loops; 26 | 27 | sysCtrl.runClockConfig1 = (sysCtrl.runClockConfig1 & vals::sysCtrl::runClockCfg1Mask) | 28 | vals::sysCtrl::runClockCfg1MainOscEnable | vals::sysCtrl::runClockCfg1MainOscXtal25MHz | 29 | vals::sysCtrl::runClockCfg1PLLPowerUp | vals::sysCtrl::runClockCfg1PLLBypass | 30 | vals::sysCtrl::runClockCfg1NoPWMClkDiv | vals::sysCtrl::runClockCfg1NoSysClkDiv | 31 | vals::sysCtrl::runClockCfg1OscSourceMainOsc | vals::sysCtrl::runClockCfg1SysClockDiv(0); 32 | while (!(sysCtrl.pllStatus & vals::sysCtrl::pllStatusLocked)) 33 | continue; 34 | 35 | sysCtrl.runClockConfig2 = (sysCtrl.runClockConfig2 & vals::sysCtrl::runClockCfg2Mask) | 36 | vals::sysCtrl::runClockCfg2UseRCC2 | vals::sysCtrl::runClockCfg2PLLPreDivDisable | 37 | vals::sysCtrl::runClockCfg2PLLUSBPowerUp | vals::sysCtrl::runClockCfg2PLLPowerUp | 38 | vals::sysCtrl::runClockCfg2PLLNoBypass | vals::sysCtrl::runClockCfg2OscSourceMainOsc | 39 | vals::sysCtrl::runClockCfg2SysClockDiv(2) | vals::sysCtrl::runClockCfgSysClockDivLSBClr; 40 | } 41 | -------------------------------------------------------------------------------- /firmware/osc.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef OSC_HXX 3 | #define OSC_HXX 4 | 5 | void oscInit() noexcept; 6 | 7 | #endif /*OSC_HXX*/ 8 | -------------------------------------------------------------------------------- /firmware/platform.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef PLATFORM_HXX 3 | #define PLATFORM_HXX 4 | 5 | void run() noexcept; 6 | [[gnu::isr]] void irqUSB() noexcept; 7 | 8 | #endif /*PLATFORM_HXX*/ 9 | -------------------------------------------------------------------------------- /firmware/sfdp.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #include 3 | #include "sfdp.hxx" 4 | 5 | namespace sfdp 6 | { 7 | constexpr static uint32_t sfdpHeaderAddress{0U}; 8 | constexpr static uint32_t tableHeaderAddress{sizeof(sfdpHeader_t)}; 9 | 10 | constexpr static std::array sfdpMagic{{'S', 'F', 'D', 'P'}}; 11 | constexpr static uint16_t basicSPIParameterTable{0xFF00}; 12 | 13 | static void sfdpRead(const spiChip_t targetDevice, const uint32_t address, void *const buffer, const size_t bufferLen) 14 | { 15 | auto &device{*spiDevice(targetDevice)}; 16 | spiSelect(targetDevice); 17 | spiWrite(device, spiOpcodes::readSFDP); 18 | spiWrite(device, uint8_t(address >> 16U)); 19 | spiWrite(device, uint8_t(address >> 8U)); 20 | spiWrite(device, uint8_t(address)); 21 | spiWrite(device, 0); 22 | 23 | auto *const data{static_cast(buffer)}; 24 | for (const auto idx : substrate::indexSequence_t{bufferLen}) 25 | data[idx] = spiRead(device); 26 | spiSelect(spiChip_t::none); 27 | } 28 | 29 | template static void sfdpRead(const spiChip_t targetDevice, const uint32_t address, T &buffer) 30 | { return sfdpRead(targetDevice, address, &buffer, sizeof(T)); } 31 | 32 | spiParameters_t readBasicParameterTable(const spiChip_t device, uint32_t address, size_t length) 33 | { 34 | basicParameterTable_t parameterTable{}; 35 | sfdpRead(device, address, ¶meterTable, std::min(sizeof(basicParameterTable_t), length)); 36 | 37 | spiParameters_t result{}; 38 | result.capacity = parameterTable.flashMemoryDensity.capacity(); 39 | for (const auto &eraseType : parameterTable.eraseTypes) 40 | { 41 | if (eraseType.opcode == parameterTable.sectorEraseOpcode) 42 | { 43 | result.sectorEraseOpcode = eraseType.opcode; 44 | result.sectorSize = eraseType.eraseSize(); 45 | break; 46 | } 47 | } 48 | result.pageSize = parameterTable.programmingAndChipEraseTiming.pageSize(); 49 | return result; 50 | } 51 | 52 | std::optional parameters(const spiChip_t device) 53 | { 54 | sfdpHeader_t header{}; 55 | sfdpRead(device, sfdpHeaderAddress, header); 56 | if (header.magic != sfdpMagic) 57 | return std::nullopt; 58 | // When we've read a valid SFDP header, we now know we can reclock the bus to 50MHz 59 | spiSetClock(device, 50U); 60 | 61 | for (const auto idx : substrate::indexSequence_t{header.parameterHeadersCount()}) 62 | { 63 | parameterTableHeader_t tableHeader{}; 64 | sfdpRead(device, tableHeaderAddress + (sizeof(parameterTableHeader_t) * idx), tableHeader); 65 | if (tableHeader.jedecParameterID() == basicSPIParameterTable) 66 | return readBasicParameterTable(device, tableHeader.tableAddress, tableHeader.tableLength()); 67 | } 68 | 69 | return std::nullopt; 70 | } 71 | } // namespace sfdp 72 | -------------------------------------------------------------------------------- /firmware/sfdp.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef SFDP_HXX 3 | #define SFDP_HXX 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "spi.hxx" 10 | 11 | namespace sfdp 12 | { 13 | enum struct accessProtocol_t : uint8_t 14 | { 15 | xspiNANDClass1 = 0xF0U, 16 | xspiNANDClass2 = 0xF1U, 17 | xspiNANDClass3WithClass1NVM = 0xF2U, 18 | xspiNANDClass3WithClass2NVM = 0xF3U, 19 | spiNANDClass1 = 0xF4U, 20 | spiNANDClass2 = 0xF5U, 21 | spiNANDClass3withClass1NVM = 0xF6U, 22 | spiNANDClass3withClass2NVM = 0xF7U, 23 | xspiNORProfile2 = 0xFAU, 24 | xspiNORProfile1with3ByteAddr = 0xFCU, 25 | xspiNORProfile1with4ByteAddr20Wait = 0xFDU, 26 | xspiNORProfile1with4ByteAddr8Wait = 0xFEU, 27 | legacyJESD216B = 0xFFU 28 | }; 29 | 30 | struct uint24_t 31 | { 32 | private: 33 | std::array value{}; 34 | 35 | public: 36 | operator uint32_t() const noexcept 37 | { return static_cast((value[2] << 16U) | (value[1] << 8U) | value[0]); } 38 | }; 39 | 40 | struct sfdpHeader_t 41 | { 42 | std::array magic{}; 43 | uint8_t versionMajor{}; 44 | uint8_t versionMinor{}; 45 | uint8_t rawParameterHeadersCount{}; 46 | accessProtocol_t accessProtocol{accessProtocol_t::legacyJESD216B}; 47 | 48 | [[nodiscard]] size_t parameterHeadersCount() const noexcept { return rawParameterHeadersCount + 1U; } 49 | }; 50 | 51 | struct parameterTableHeader_t 52 | { 53 | uint8_t jedecParameterIDLow{}; 54 | uint8_t versionMajor{}; 55 | uint8_t versionMinor{}; 56 | uint8_t tableLengthInU32s{}; 57 | uint24_t tableAddress{}; 58 | uint8_t jedecParameterIDHigh{}; 59 | 60 | [[nodiscard]] uint16_t jedecParameterID() const noexcept 61 | { return static_cast((jedecParameterIDHigh << 8U) | jedecParameterIDLow); } 62 | [[nodiscard]] size_t tableLength() const noexcept { return static_cast(tableLengthInU32s) * 4U; } 63 | }; 64 | 65 | struct memoryDensity_t 66 | { 67 | std::array data{}; 68 | 69 | private: 70 | [[nodiscard]] bool isExponential() const noexcept { return data[3] & 0x80U; } 71 | [[nodiscard]] uint32_t value() const noexcept 72 | { return ((data[3] & 0x7FU) << 24U) | (data[2] << 16U) | (data[1] << 8U) | data[0]; } 73 | 74 | public: 75 | [[nodiscard]] size_t capacity() const noexcept 76 | { 77 | const auto bits 78 | { 79 | [=]() -> size_t 80 | { 81 | if (isExponential()) 82 | return 1U << value(); 83 | else 84 | return value() + 1U; 85 | }() 86 | }; 87 | return bits / 8U; 88 | } 89 | }; 90 | 91 | struct [[gnu::packed]] timingsAndOpcode_t 92 | { 93 | uint8_t timings{}; 94 | uint8_t opcode{}; 95 | }; 96 | 97 | struct [[gnu::packed]] eraseParameters_t 98 | { 99 | uint8_t eraseSizeExponent{}; 100 | uint8_t opcode{}; 101 | 102 | [[nodiscard]] size_t eraseSize() const noexcept { return 1U << eraseSizeExponent; } 103 | }; 104 | 105 | struct programmingAndChipEraseTiming_t 106 | { 107 | uint8_t programmingTimingRatioAndPageSize{}; 108 | std::array eraseTimings; 109 | 110 | [[nodiscard]] size_t pageSize() const noexcept 111 | { 112 | const uint8_t pageSizeExponent = programmingTimingRatioAndPageSize >> 4U; 113 | return 1U << pageSizeExponent; 114 | } 115 | }; 116 | 117 | struct basicParameterTable_t 118 | { 119 | uint8_t value1{}; 120 | uint8_t sectorEraseOpcode{}; 121 | uint8_t value2{}; 122 | uint8_t reserved1{}; 123 | memoryDensity_t flashMemoryDensity{}; 124 | timingsAndOpcode_t fastQuadIO{}; 125 | timingsAndOpcode_t fastQuadOutput{}; 126 | timingsAndOpcode_t fastDualOutput{}; 127 | timingsAndOpcode_t fastDualIO{}; 128 | uint8_t fastSupportFlags{}; 129 | std::array reserved2{}; 130 | timingsAndOpcode_t fastDualDPI{}; 131 | std::array reserved3{}; 132 | timingsAndOpcode_t fastQuadQPI{}; 133 | std::array eraseTypes{}; 134 | uint32_t eraseTiming{}; 135 | programmingAndChipEraseTiming_t programmingAndChipEraseTiming{}; 136 | uint8_t operationalProhibitions{}; 137 | std::array suspendLatencySpecs{}; 138 | uint8_t programResumeOpcode{}; 139 | uint8_t programSuspendOpcode{}; 140 | uint8_t resumeOpcode{}; 141 | uint8_t suspendOpcode{}; 142 | uint8_t statusRegisterPollingFlags{}; 143 | std::array deepPowerdown{}; 144 | std::array dualAndQuadMode{}; 145 | uint8_t reserved4{}; 146 | uint32_t statusAndAddressingMode{}; 147 | }; 148 | 149 | static_assert(sizeof(uint24_t) == 3); 150 | static_assert(sizeof(sfdpHeader_t) == 8); 151 | static_assert(sizeof(parameterTableHeader_t) == 8); 152 | static_assert(sizeof(memoryDensity_t) == 4); 153 | static_assert(sizeof(timingsAndOpcode_t) == 2); 154 | static_assert(sizeof(programmingAndChipEraseTiming_t) == 4); 155 | static_assert(sizeof(basicParameterTable_t) == 64); 156 | 157 | struct spiParameters_t 158 | { 159 | uint32_t pageSize{}; 160 | uint32_t sectorSize{}; 161 | size_t capacity{}; 162 | uint8_t sectorEraseOpcode{}; 163 | }; 164 | 165 | std::optional parameters(spiChip_t device); 166 | } // namespace sfdp 167 | 168 | #endif /*SFDP_HXX*/ 169 | -------------------------------------------------------------------------------- /firmware/spi.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #include 3 | #include 4 | #include "spi.hxx" 5 | #include "led.hxx" 6 | #include "timer.hxx" 7 | 8 | /*! 9 | * Onboard SPI bus pinout: 10 | * PE0 - CS2 11 | * PE1 - CS1 12 | * 13 | * PF0 - CIPO 14 | * PF1 - COPI 15 | * PF2 - CLK 16 | * 17 | * Target SPI bus pinout: 18 | * PA2 - CLK 19 | * PA3 - CS 20 | * PA4 - CIPO 21 | * PA5 - COPI 22 | * 23 | * The SPI peripheral on this device works in a write-to-read manner, 24 | * so the code in this file attempts to always keep the read and write 25 | * FIFOs equally fed and consumed. 26 | */ 27 | 28 | namespace spi 29 | { 30 | // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) 31 | // These store what the local flash that's installed's manufacturer, type and capacity read as. 32 | std::array localChip{}; 33 | 34 | static spiChip_t targetDevice{spiChip_t::none}; 35 | // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) 36 | } 37 | 38 | using namespace spi; 39 | 40 | bool checkDeviceID(uint8_t index) noexcept; 41 | 42 | void spiInit() noexcept 43 | { 44 | // Switch over to port A, E & F's AHB apeture 45 | sysCtrl.gpioAHBCtrl |= vals::sysCtrl::gpioAHBCtrlPortA | vals::sysCtrl::gpioAHBCtrlPortE | 46 | vals::sysCtrl::gpioAHBCtrlPortF; 47 | // Enable port A, E & F 48 | sysCtrl.runClockGateCtrlGPIO |= vals::sysCtrl::runClockGateCtrlGPIOA | vals::sysCtrl::runClockGateCtrlGPIOE | 49 | vals::sysCtrl::runClockGateCtrlGPIOF; 50 | 51 | // Wait for the ports to come online 52 | while (!(sysCtrl.periphReadyGPIO & (vals::sysCtrl::periphReadyGPIOA | vals::sysCtrl::periphReadyGPIOE | 53 | vals::sysCtrl::periphReadyGPIOF))) 54 | continue; 55 | 56 | // Enable SSI (SPI) port 0 (PA2-5), and 1 (PF0-2) 57 | sysCtrl.runClockGateCtrlSSI |= vals::sysCtrl::runClockGateCtrlSSI0 | vals::sysCtrl::runClockGateCtrlSSI1; 58 | // Wait for it to come online 59 | while (!(sysCtrl.periphReadySSI & (vals::sysCtrl::periphReadySSI0 | vals::sysCtrl::periphReadySSI1))) 60 | continue; 61 | 62 | // Set the chip select pins as high digital mode outputs 63 | gpioA.dataBits[0x08U] = 0x08U; 64 | gpioA.den |= 0x08U; 65 | gpioA.afSel &= ~0x08U; 66 | gpioA.dir = 0x08U; 67 | gpioE.dataBits[0x03U] = 0x03U; 68 | gpioE.den |= 0x03U; 69 | gpioE.afSel &= ~0x03U; 70 | gpioE.dir = 0x03U; 71 | 72 | // Set the target reset pin as a high digital mode output 73 | gpioA.dataBits[0x80U] = 0x80U; 74 | gpioA.den |= 0x80U; 75 | gpioA.afSel &= ~0x80U; 76 | gpioA.dir |= 0x80U; 77 | 78 | // PF0 is an NMI, so unlock the port for reconfig 79 | gpioF.lock = vals::gpio::lockKey; 80 | gpioF.commit |= 1; 81 | gpioF.lock = 0; 82 | 83 | // Set the internal SPI bus pins as digital mode IO 84 | gpioF.den |= 0x07U; 85 | gpioF.portCtrl |= vals::gpio::portF::portCtrlPin0SSI1Rx | 86 | vals::gpio::portF::portCtrlPin1SSI1Tx | vals::gpio::portF::portCtrlPin2SSI1Clk; 87 | gpioF.afSel |= 0x07U; 88 | gpioF.dir = (gpioF.dir & 0xF8U) | 0x06U; 89 | 90 | // We want to use 8-bit SPI (Motorola) in mode 0 91 | ssi1.ctrl0 = vals::ssi::ctrl0FormatMotorola | vals::ssi::ctrl0ClockPolarityLow | 92 | vals::ssi::ctrl0ClockPhaseLeading | vals::ssi::ctrl0Data8Bit; 93 | ssi1.clockConfig = vals::ssi::clockConfigSysClk; 94 | // Enable the interface 95 | ssi1.ctrl1 = vals::ssi::control1ModeController | vals::ssi::control1EnableOperations; 96 | 97 | // Set the external SPI bus pins as digital mode IO 98 | gpioA.den |= 0x34U; 99 | gpioA.portCtrl |= vals::gpio::portA::portCtrlPin2SSI0Clk | 100 | vals::gpio::portA::portCtrlPin4SSI0Rx | vals::gpio::portA::portCtrlPin5SSI0Tx; 101 | gpioA.afSel |= 0x34U; 102 | gpioA.dir = (gpioA.dir & 0xCBU) | 0x24U; 103 | 104 | // For the target interface it's much the same but start out with a much slower clock (500kHz) 105 | // to guarantee comms then spin it up once we know the device can handle the speed 106 | ssi0.ctrl0 = vals::ssi::ctrl0FormatMotorola | vals::ssi::ctrl0ClockPolarityLow | 107 | vals::ssi::ctrl0ClockPhaseLeading | vals::ssi::ctrl0Data8Bit; 108 | ssi0.clockConfig = vals::ssi::clockConfigSysClk; 109 | // Enable the interface 110 | ssi0.ctrl1 = vals::ssi::control1ModeController | vals::ssi::control1EnableOperations; 111 | 112 | spiResetClocks(); 113 | 114 | localChip[0] = identDevice(spiChip_t::local1); 115 | localChip[1] = identDevice(spiChip_t::local2); 116 | if (!checkDeviceID(0) || !checkDeviceID(1)) 117 | ledSetColour(true, false, false); 118 | else 119 | ledSetColour(false, true, false); 120 | } 121 | 122 | // Reset the bus clocks after completing target deselection. 123 | // We set the internal bus to 40MHz as we know the chips on it can handle that 124 | // and the external bus down to 500kHz as we have no idea until re-discovery what 125 | // the device attached, if any, can do. 126 | void spiResetClocks() noexcept 127 | { 128 | // SSI1 is the internal bus, SSI0 is the external. 129 | // We have a 25MHz clock PLL'd to 80MHz, which we want divided down as little as possible 130 | // Scale the clock by 2 to make it 1/2 the system clock 131 | ssi1.cpsr = 2; 132 | // 80MHz -> 500kHz = 160 133 | ssi0.cpsr = 160; 134 | } 135 | 136 | // Switch the specified bus to the given clock frequency (in MHz) 137 | // a value of 0 is treated as the special value 500kHz 138 | void spiSetClock(const spiChip_t chip, const uint8_t valueMHz) noexcept 139 | { 140 | // As the CPSR register only allows even values, we have to pick an even number of MHz. 141 | // Our top speed is 40MHz, so this masks off the bottom bit after doing the maths to figure out 142 | // the divider value, and then mask off the bottom bit 143 | const auto speed 144 | { 145 | [&]() 146 | { 147 | // If the user tries to pick more than 40MHz, set the divider to give them 40MHz. 148 | if (valueMHz > 40U) 149 | return 2U; 150 | // Special-case 0MHz to 500kHz. 151 | if (!valueMHz) 152 | return 160U; 153 | // 1MHz and up 154 | return 80U / valueMHz; 155 | }() & ~1U 156 | }; 157 | // Now we know the divider setting, reconfigure the appropriate SPI controller 158 | if (chip == spiChip_t::local1 || chip == spiChip_t::local2) 159 | ssi1.cpsr = speed; 160 | if (chip == spiChip_t::target) 161 | ssi0.cpsr = speed; 162 | } 163 | 164 | void spiSelect(const spiChip_t chip) noexcept 165 | { 166 | // NOLINTBEGIN(bugprone-branch-clone) 167 | switch (chip) 168 | { 169 | case spiChip_t::local1: 170 | gpioA.dataBits[0x08U] = 0x08U; 171 | gpioE.dataBits[0x03U] = 0x02U; 172 | break; 173 | case spiChip_t::local2: 174 | gpioA.dataBits[0x08U] = 0x08U; 175 | gpioE.dataBits[0x03U] = 0x01U; 176 | break; 177 | case spiChip_t::target: 178 | gpioE.dataBits[0x03U] = 0x03U; 179 | gpioA.dataBits[0x08U] = 0x00U; 180 | break; 181 | case spiChip_t::none: 182 | gpioE.dataBits[0x03U] = 0x03U; 183 | gpioA.dataBits[0x08U] = 0x08U; 184 | break; 185 | } 186 | // NOLINTEND(bugprone-branch-clone) 187 | targetDevice = chip; 188 | } 189 | 190 | tivaC::ssi_t *spiDevice(const spiChip_t chip) noexcept 191 | { 192 | if (chip == spiChip_t::local1 || chip == spiChip_t::local2) 193 | return &ssi1; 194 | else if (chip == spiChip_t::target) 195 | return &ssi0; 196 | return nullptr; 197 | } 198 | 199 | tivaC::ssi_t *spiDevice() noexcept { return spiDevice(targetDevice); } 200 | 201 | void spiResync(tivaC::ssi_t &device) 202 | { 203 | // TxFIFOEmpty should always be true on entry as otherwise read-to-write desynced, 204 | // but lets check all the same just in case. 205 | while (!(device.status & vals::ssi::statusTxFIFOEmpty)) 206 | continue; 207 | // RxFIFONotEmpty should always be false on entry as otherwise read-to-write desynced, 208 | // but lets check all the same just in case. 209 | while (device.status & vals::ssi::statusRxFIFONotEmpty) 210 | // NOLINTNEXTLINE(readability-identifier-length) 211 | [[maybe_unused]] const volatile auto _{device.data}; 212 | } 213 | 214 | uint8_t spiRead(tivaC::ssi_t &device) noexcept 215 | { 216 | spiResync(device); 217 | device.data = 0; 218 | // Wait for the dummy data to be shifted out and the reply read in 219 | while (!(device.status & vals::ssi::statusTxFIFOEmpty)) 220 | continue; 221 | while (!(device.status & vals::ssi::statusRxFIFONotEmpty)) 222 | continue; 223 | return uint8_t(device.data); 224 | } 225 | 226 | uint8_t spiRead() noexcept 227 | { 228 | auto *device{spiDevice()}; 229 | if (device) 230 | return spiRead(*device); 231 | return {}; 232 | } 233 | 234 | void spiWrite(tivaC::ssi_t &device, const uint8_t value) noexcept 235 | { 236 | spiResync(device); 237 | device.data = value; 238 | while (!(device.status & vals::ssi::statusTxFIFOEmpty)) 239 | continue; 240 | while (!(device.status & vals::ssi::statusRxFIFONotEmpty)) 241 | continue; 242 | // NOLINTNEXTLINE(readability-identifier-length) 243 | [[maybe_unused]] const volatile auto _{device.data}; 244 | } 245 | 246 | void spiWrite(const uint8_t value) noexcept 247 | { 248 | auto *device{spiDevice()}; 249 | if (device) 250 | spiWrite(*device, value); 251 | } 252 | 253 | flashID_t readID(const spiChip_t chip) noexcept 254 | { 255 | spiSelect(chip); 256 | auto &device{*spiDevice()}; 257 | spiWrite(device, spiOpcodes::jedecID); 258 | const auto mfr 259 | { 260 | [&]() 261 | { 262 | if (chip == spiChip_t::target) 263 | { 264 | volatile auto value = spiRead(device); 265 | if (value != 255U) 266 | return value; 267 | } 268 | return spiRead(device); 269 | }() 270 | }; 271 | const auto type{spiRead(device)}; 272 | const auto capacity{spiRead(device)}; 273 | spiSelect(spiChip_t::none); 274 | return {mfr, type, capacity}; 275 | } 276 | 277 | void setDeviceReset(const bool resetState) noexcept 278 | { gpioA.dataBits[0x80U] = resetState ? 0x80U : 0x00U; } 279 | bool isDeviceReset() noexcept { return gpioA.dataBits[0x80U]; } 280 | 281 | flashID_t identDevice(const spiChip_t chip, const bool releaseReset) noexcept 282 | { 283 | if (chip == spiChip_t::target) 284 | setDeviceReset(true); 285 | auto chipID{readID(chip)}; 286 | if (chipID.manufacturer == 0xFFU && chipID.type == 0xFFU) 287 | { 288 | spiSelect(spiChip_t::target); 289 | spiWrite(spiOpcodes::wakeUp); 290 | spiSelect(spiChip_t::none); 291 | waitFor(20); // 20us 292 | chipID = readID(chip); 293 | } 294 | if (chip == spiChip_t::target && releaseReset) 295 | setDeviceReset(false); 296 | return chipID; 297 | } 298 | 299 | bool checkDeviceID(const uint8_t index) noexcept 300 | { 301 | return 302 | localChip[index].manufacturer == 0x1FU && 303 | localChip[index].type == 0x32U && 304 | localChip[index].capacity == 0x17U; 305 | } 306 | -------------------------------------------------------------------------------- /firmware/spi.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef SPI_HXX 3 | #define SPI_HXX 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "flash.hxx" 10 | 11 | void spiInit() noexcept; 12 | void spiResetClocks() noexcept; 13 | void spiSetClock(spiChip_t chip, uint8_t valueMHz) noexcept; 14 | void spiSelect(spiChip_t chip) noexcept; 15 | tivaC::ssi_t *spiDevice() noexcept; 16 | tivaC::ssi_t *spiDevice(spiChip_t chip) noexcept; 17 | uint8_t spiRead(tivaC::ssi_t &device) noexcept; 18 | void spiWrite(tivaC::ssi_t &device, uint8_t value) noexcept; 19 | uint8_t spiRead() noexcept; 20 | void spiWrite(uint8_t value) noexcept; 21 | flashID_t identDevice(spiChip_t chip, bool releaseReset = true) noexcept; 22 | void setDeviceReset(bool resetState) noexcept; 23 | bool isDeviceReset() noexcept; 24 | 25 | namespace spi 26 | { 27 | constexpr static const uint8_t internalChips{2}; 28 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 29 | extern std::array localChip; 30 | } 31 | 32 | namespace spiOpcodes 33 | { 34 | constexpr static uint8_t jedecID{0x9FU}; 35 | constexpr static uint8_t chipErase{0xC7U}; 36 | constexpr static uint8_t blockErase{0xD8U}; 37 | constexpr static uint8_t pageRead{0x03U}; 38 | constexpr static uint8_t pageAddressRead{0x13U}; 39 | constexpr static uint8_t pageWrite{0x02U}; 40 | constexpr static uint8_t pageAddressWrite{0x10U}; 41 | constexpr static uint8_t statusRead{0x05U}; 42 | constexpr static uint8_t statusWrite{0x01U}; 43 | constexpr static uint8_t writeEnable{0x06U}; 44 | constexpr static uint8_t writeDisable{0x04U}; 45 | constexpr static uint8_t readSFDP{0x5AU}; 46 | constexpr static uint8_t wakeUp{0xABU}; 47 | constexpr static uint8_t reset{0xFFU}; 48 | } // namespace spiOpcodes 49 | 50 | #endif /*SPI_HXX*/ 51 | -------------------------------------------------------------------------------- /firmware/spiFlashProgrammer.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #include "platform.hxx" 3 | #include "osc.hxx" 4 | #include "led.hxx" 5 | #include "spi.hxx" 6 | #include "timer.hxx" 7 | #include 8 | #include 9 | #include "usb/flashProto.hxx" 10 | 11 | using usb::types::endpointDir_t; 12 | 13 | void run() noexcept 14 | { 15 | ledInit(); 16 | oscInit(); 17 | timerInit(); 18 | spiInit(); 19 | usb::core::init(); 20 | usb::flashProto::registerHandlers(1, 1, 0, 1); 21 | usb::dfu::registerHandlers({}, 1, 1); 22 | usb::core::attach(); 23 | 24 | while (true) 25 | __asm__("wfi"); 26 | } 27 | 28 | void irqUSB() noexcept { usb::core::handleIRQ(); } 29 | 30 | namespace usb::dfu 31 | { 32 | // The linker script pins this to 0x20001000 33 | [[gnu::section(".bootMagic")]] static volatile uint16_t bootMagic; 34 | constexpr static uint16_t bootMagicDFU{0xBADB}; 35 | 36 | void reboot() noexcept 37 | { 38 | bootMagic = bootMagicDFU; 39 | scb.apint = vals::scb::apintKey | vals::scb::apintSystemResetRequest; 40 | while (true) 41 | continue; 42 | } 43 | 44 | bool flashBusy() noexcept { return false; } 45 | void erase(const std::uintptr_t) noexcept { } 46 | void write(const std::uintptr_t, const std::size_t, const uint8_t *const) noexcept { } 47 | } 48 | -------------------------------------------------------------------------------- /firmware/startup.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #include 3 | #include 4 | #include "platform.hxx" 5 | 6 | [[gnu::naked]] void irqReset() noexcept; 7 | void irqNMI() noexcept; 8 | void irqEmptyDef() noexcept; 9 | [[gnu::naked]] void irqHardFault() noexcept; 10 | 11 | extern const uint32_t stackTop; 12 | extern const uint32_t endText; 13 | extern uint32_t beginData; 14 | extern const uint32_t endData; 15 | extern uint32_t beginBSS; 16 | extern const uint32_t endBSS; 17 | 18 | using ctorFuncs_t = void (*)(); 19 | extern const ctorFuncs_t beginCtors, endCtors; 20 | 21 | using irqFunction_t = void (*)(); 22 | 23 | struct nvicTable_t final 24 | { 25 | const void *stackTop; 26 | std::array vectorTable; 27 | }; 28 | 29 | [[gnu::section(".nvic_table"), gnu::used]] static const nvicTable_t nvicTable 30 | { 31 | &stackTop, 32 | { 33 | irqReset, /* Reset handler */ 34 | irqNMI, /* NMI handler */ 35 | irqHardFault, /* Hard Fault handler */ 36 | 37 | /* Configurable priority handlers */ 38 | irqEmptyDef, /* MMU Fault handler */ 39 | irqEmptyDef, /* Bus Fault handler */ 40 | irqEmptyDef, /* Usage Fault */ 41 | nullptr, /* Reserved */ 42 | nullptr, /* Reserved */ 43 | nullptr, /* Reserved */ 44 | nullptr, /* Reserved */ 45 | irqEmptyDef, /* SV Call */ 46 | irqEmptyDef, /* Debug Monitor */ 47 | nullptr, /* Reserved */ 48 | irqEmptyDef, /* Pending SV */ 49 | irqEmptyDef, /* Sys Tick */ 50 | 51 | /* Peripheral handlers */ 52 | irqEmptyDef, /* GPIO Port A */ 53 | irqEmptyDef, /* GPIO Port B */ 54 | irqEmptyDef, /* GPIO Port C */ 55 | irqEmptyDef, /* GPIO Port D */ 56 | irqEmptyDef, /* GPIO Port E */ 57 | irqEmptyDef, /* UART 0 */ 58 | irqEmptyDef, /* UART 1 */ 59 | irqEmptyDef, /* SSI 0 */ 60 | irqEmptyDef, /* I2C 0 */ 61 | irqEmptyDef, /* PWM 0 Fault */ 62 | irqEmptyDef, /* PWM 0 Generator 0 */ 63 | irqEmptyDef, /* PWM 0 Generator 1 */ 64 | irqEmptyDef, /* PWM 0 Generator 2 */ 65 | irqEmptyDef, /* QEI 0 */ 66 | irqEmptyDef, /* ADC 0 Sequence 0 */ 67 | irqEmptyDef, /* ADC 0 Sequence 1 */ 68 | irqEmptyDef, /* ADC 0 Sequence 2 */ 69 | irqEmptyDef, /* ADC 0 Sequence 3 */ 70 | irqEmptyDef, /* WDT */ 71 | irqEmptyDef, /* Timer 0 A (16/32-bit) */ 72 | irqEmptyDef, /* Timer 0 B (16/32-bit) */ 73 | irqEmptyDef, /* Timer 1 A (16/32-bit) */ 74 | irqEmptyDef, /* Timer 1 B (16/32-bit) */ 75 | irqEmptyDef, /* Timer 2 A (16/32-bit) */ 76 | irqEmptyDef, /* Timer 2 B (16/32-bit) */ 77 | irqEmptyDef, /* Analog Comparator 0 */ 78 | irqEmptyDef, /* Analog Comparator 1 */ 79 | irqEmptyDef, /* Analog Comparator 2 */ 80 | irqEmptyDef, /* SysCtl */ 81 | irqEmptyDef, /* Flash + EEPROM Ctl */ 82 | irqEmptyDef, /* GPIO Port F */ 83 | irqEmptyDef, /* GPIO Port G */ 84 | irqEmptyDef, /* GPIO Port H */ 85 | irqEmptyDef, /* UART 2 */ 86 | irqEmptyDef, /* SSI 1 */ 87 | irqEmptyDef, /* Timer 3 A (16/32-bit) */ 88 | irqEmptyDef, /* Timer 3 B (16/32-bit) */ 89 | irqEmptyDef, /* I2C 1 */ 90 | irqEmptyDef, /* QEI 1 */ 91 | irqEmptyDef, /* CAN 0 */ 92 | irqEmptyDef, /* CAN 1 */ 93 | irqEmptyDef, /* CAN 2 */ 94 | nullptr, /* Reserved */ 95 | irqEmptyDef, /* Hibernation Module */ 96 | irqUSB, /* USB */ 97 | irqEmptyDef, /* PWM 0 Generator 3 */ 98 | irqEmptyDef, /* UDMA SW */ 99 | irqEmptyDef, /* UDMA Error */ 100 | irqEmptyDef, /* ADC 1 Sequence 0 */ 101 | irqEmptyDef, /* ADC 1 Sequence 1 */ 102 | irqEmptyDef, /* ADC 1 Sequence 2 */ 103 | irqEmptyDef, /* ADC 1 Sequence 3 */ 104 | nullptr, /* Reserved */ 105 | nullptr, /* Reserved */ 106 | irqEmptyDef, /* GPIO Port J */ 107 | irqEmptyDef, /* GPIO Port K */ 108 | irqEmptyDef, /* GPIO Port L */ 109 | irqEmptyDef, /* SSI 2 */ 110 | irqEmptyDef, /* SSI 3 */ 111 | irqEmptyDef, /* UART 3 */ 112 | irqEmptyDef, /* UART 4 */ 113 | irqEmptyDef, /* UART 5 */ 114 | irqEmptyDef, /* UART 6 */ 115 | irqEmptyDef, /* UART 7 */ 116 | nullptr, /* Reserved */ 117 | nullptr, /* Reserved */ 118 | nullptr, /* Reserved */ 119 | nullptr, /* Reserved */ 120 | irqEmptyDef, /* I2C 2 */ 121 | irqEmptyDef, /* I2C 3 */ 122 | irqEmptyDef, /* Timer 4 A (16/32-bit) */ 123 | irqEmptyDef, /* Timer 4 B (16/32-bit) */ 124 | nullptr, /* Reserved */ 125 | nullptr, /* Reserved */ 126 | nullptr, /* Reserved */ 127 | nullptr, /* Reserved */ 128 | nullptr, /* Reserved */ 129 | nullptr, /* Reserved */ 130 | nullptr, /* Reserved */ 131 | nullptr, /* Reserved */ 132 | nullptr, /* Reserved */ 133 | nullptr, /* Reserved */ 134 | nullptr, /* Reserved */ 135 | nullptr, /* Reserved */ 136 | nullptr, /* Reserved */ 137 | nullptr, /* Reserved */ 138 | nullptr, /* Reserved */ 139 | nullptr, /* Reserved */ 140 | nullptr, /* Reserved */ 141 | nullptr, /* Reserved */ 142 | nullptr, /* Reserved */ 143 | nullptr, /* Reserved */ 144 | irqEmptyDef, /* Timer 5 A (16/32-bit) */ 145 | irqEmptyDef, /* Timer 5 B (16/32-bit) */ 146 | irqEmptyDef, /* Timer 0 A (32/64-bit) */ 147 | irqEmptyDef, /* Timer 0 B (32/64-bit) */ 148 | irqEmptyDef, /* Timer 1 A (32/64-bit) */ 149 | irqEmptyDef, /* Timer 1 B (32/64-bit) */ 150 | irqEmptyDef, /* Timer 2 A (32/64-bit) */ 151 | irqEmptyDef, /* Timer 2 B (32/64-bit) */ 152 | irqEmptyDef, /* Timer 3 A (32/64-bit) */ 153 | irqEmptyDef, /* Timer 3 B (32/64-bit) */ 154 | irqEmptyDef, /* Timer 4 A (32/64-bit) */ 155 | irqEmptyDef, /* Timer 4 B (32/64-bit) */ 156 | irqEmptyDef, /* Timer 5 A (32/64-bit) */ 157 | irqEmptyDef, /* Timer 5 B (32/64-bit) */ 158 | irqEmptyDef, /* FPU */ 159 | nullptr, /* Reserved */ 160 | nullptr, /* Reserved */ 161 | irqEmptyDef, /* I2C 4 */ 162 | irqEmptyDef, /* I2C 5 */ 163 | irqEmptyDef, /* GPIO Port M */ 164 | irqEmptyDef, /* GPIO Port N */ 165 | irqEmptyDef, /* QEI 2 */ 166 | nullptr, /* Reserved */ 167 | nullptr, /* Reserved */ 168 | irqEmptyDef, /* GPIO Port P0/Summary */ 169 | irqEmptyDef, /* GPIO Port P1 */ 170 | irqEmptyDef, /* GPIO Port P2 */ 171 | irqEmptyDef, /* GPIO Port P3 */ 172 | irqEmptyDef, /* GPIO Port P4 */ 173 | irqEmptyDef, /* GPIO Port P5 */ 174 | irqEmptyDef, /* GPIO Port P6 */ 175 | irqEmptyDef, /* GPIO Port P7 */ 176 | irqEmptyDef, /* GPIO Port Q0/Summary */ 177 | irqEmptyDef, /* GPIO Port Q1 */ 178 | irqEmptyDef, /* GPIO Port Q2 */ 179 | irqEmptyDef, /* GPIO Port Q3 */ 180 | irqEmptyDef, /* GPIO Port Q4 */ 181 | irqEmptyDef, /* GPIO Port Q5 */ 182 | irqEmptyDef, /* GPIO Port Q6 */ 183 | irqEmptyDef, /* GPIO Port Q7 */ 184 | irqEmptyDef, /* GPIO Port R */ 185 | irqEmptyDef, /* GPIO Port S */ 186 | irqEmptyDef, /* PWM 1 Generator 0 */ 187 | irqEmptyDef, /* PWM 1 Generator 1 */ 188 | irqEmptyDef, /* PWM 1 Generator 2 */ 189 | irqEmptyDef, /* PWM 1 Generator 3 */ 190 | irqEmptyDef, /* PWM 1 Fault */ 191 | } 192 | }; 193 | 194 | void irqReset() noexcept 195 | { 196 | while (true) 197 | { 198 | auto *src{&endText}; 199 | for (auto *dst{&beginData}; dst < &endData; ++dst, ++src) 200 | *dst = *src; 201 | for (auto *dst{&beginBSS}; dst < &endBSS; ++dst) 202 | *dst = 0; 203 | for (auto *ctor{&beginCtors}; ctor != &endCtors; ++ctor) 204 | (*ctor)(); 205 | 206 | run(); 207 | } 208 | } 209 | 210 | void irqNMI() noexcept 211 | { 212 | while (true); 213 | } 214 | 215 | void irqHardFault() noexcept 216 | { 217 | /* Get some information about the fault for the debugger.. */ 218 | __asm__(R"( 219 | movs r0, #4 220 | movs r1, lr 221 | tst r0, r1 222 | beq _MSP 223 | mrs r0, psp 224 | b _HALT 225 | _MSP: 226 | mrs r0, msp 227 | _HALT: 228 | ldr r1, [r0, 0x00] /* r0 */ 229 | ldr r2, [r0, 0x04] /* r1 */ 230 | ldr r3, [r0, 0x08] /* r2 */ 231 | ldr r4, [r0, 0x0C] /* r3 */ 232 | ldr r5, [r0, 0x10] /* r12 */ 233 | ldr r6, [r0, 0x14] /* lr */ 234 | ldr r7, [r0, 0x18] /* pc */ 235 | ldr r8, [r0, 0x1C] /* xpsr */ 236 | bkpt #0 237 | _DEADLOOP: 238 | b _DEADLOOP 239 | )"); 240 | /* The lowest 8 bits of r8 (xpsr) contain which handler triggered this, if there is a signal handler frame before this. */ 241 | } 242 | 243 | void irqEmptyDef() noexcept 244 | { 245 | while (true); 246 | } 247 | -------------------------------------------------------------------------------- /firmware/timer.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #include 3 | #include 4 | #include "timer.hxx" 5 | 6 | void timerInit() noexcept 7 | { 8 | sysCtrl.runClockGateCtrlTimer |= vals::sysCtrl::runClockGateCtrlTimer0; 9 | while (!(sysCtrl.periphReadyTimer & vals::sysCtrl::periphReadyTimer0)) 10 | continue; 11 | 12 | timer0.ctrl &= vals::timer::ctrlMask; 13 | timer0.config = (timer0.config & vals::timer::configMask) | vals::timer::configNarrow; 14 | timer0.timerAMode = (timer0.timerAMode & vals::timer::modeMask) | 15 | vals::timer::modeOneShot | vals::timer::modeCountDown; 16 | timer0.icr |= ~vals::timer::itrMask; 17 | // This sets the prescaler up so our 80MHz clock (12.5ns/cycle) will cause the counter to count in 1us increments 18 | timer0.timerAPrescale = 80U; 19 | } 20 | 21 | void waitFor(const uint32_t microSeconds) noexcept 22 | { 23 | timer0.timerAIntervalLoad = microSeconds; 24 | timer0.ctrl |= vals::timer::ctrlTimerAEnable; 25 | while (!(timer0.ris & vals::timer::itrTimerATimeOut)) 26 | continue; 27 | timer0.icr |= vals::timer::itrTimerATimeOut; 28 | } 29 | -------------------------------------------------------------------------------- /firmware/timer.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef TIMER_HXX 3 | #define TIMER_HXX 4 | 5 | void timerInit() noexcept; 6 | void waitFor(uint32_t microSeconds) noexcept; 7 | 8 | #endif /*TIMER_HXX*/ 9 | -------------------------------------------------------------------------------- /firmware/tm4c123gh6pm/memoryLayout.ld: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: BSD-3-Clause */ 2 | 3 | MEMORY 4 | { 5 | FLASH (rx): ORIGIN = 0x00002000, LENGTH = 0x0003E000 6 | /* This chip has 32k of RAM, so reserve 28k for the application */ 7 | RAM (rwx): ORIGIN = 0x20000000, LENGTH = 0x00008000 8 | } 9 | 10 | /* 11 | * Section definitions: 12 | * 13 | * .text - machine instructions. 14 | * .data - initialized data defined in the program. 15 | * .bss - un-initialized global and static variables (to be initialized to 0 before starting main). 16 | * .stack - just contains the pointer to the stack end at the right place. 17 | */ 18 | SECTIONS 19 | { 20 | .text : 21 | { 22 | PROVIDE(beginText = .); 23 | KEEP(*(.nvic_table)) 24 | *(.text.* .text .gnu.linkonce.t.*) 25 | *(.rodata.* .rodata .gnu.linkonce.r.*) 26 | 27 | . = ALIGN(4); 28 | PROVIDE(beginCtors = .); 29 | *(.init_array.* .ctors.*) 30 | KEEP(*(.init_array .ctors)) 31 | PROVIDE(endCtors = .); 32 | *(.got .got.*) 33 | PROVIDE(endText = .); 34 | } >FLASH 35 | 36 | .stack : 37 | { 38 | /* Reserve 4k for the stack (which grows down so is at the start of RAM) */ 39 | . += 0x00001000; 40 | PROVIDE(stackTop = .); 41 | } >RAM 42 | 43 | .bootMagic : 44 | { 45 | KEEP(*(.bootMagic)) 46 | } > RAM 47 | 48 | .data : 49 | { 50 | PROVIDE(beginData = .); 51 | *(.data.* .data) 52 | *(vtable) 53 | PROVIDE(endData = .); 54 | } >RAM AT >FLASH 55 | 56 | .bss : 57 | { 58 | PROVIDE(beginBSS = .); 59 | *(.bss.* .bss) 60 | *(COMMON) 61 | PROVIDE(endBSS = .); 62 | } >RAM 63 | 64 | .note.gnu.build-id : 65 | { 66 | PROVIDE(buildID = .); 67 | KEEP(*(.note.gnu.build-id)) 68 | } >FLASH 69 | 70 | /* Stabs debugging sections. */ 71 | .stab 0 : { *(.stab) } 72 | .stabstr 0 : { *(.stabstr) } 73 | .stab.excl 0 : { *(.stab.excl) } 74 | .stab.exclstr 0 : { *(.stab.exclstr) } 75 | .stab.index 0 : { *(.stab.index) } 76 | .stab.indexstr 0 : { *(.stab.indexstr) } 77 | .comment 0 : { *(.comment) } 78 | 79 | /* 80 | * DWARF debug sections. 81 | * Symbols in the DWARF debugging sections are relative to the beginning 82 | * of the section so we begin them at 0. 83 | */ 84 | /* DWARF 1 */ 85 | .debug 0 : { *(.debug) } 86 | .line 0 : { *(.line) } 87 | /* GNU DWARF 1 extensions */ 88 | .debug_srcinfo 0 : { *(.debug_srcinfo) } 89 | .debug_sfnames 0 : { *(.debug_sfnames) } 90 | /* DWARF 1.1 and DWARF 2 */ 91 | .debug_aranges 0 : { *(.debug_aranges) } 92 | .debug_pubnames 0 : { *(.debug_pubnames) } 93 | /* DWARF 2 */ 94 | .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } 95 | .debug_abbrev 0 : { *(.debug_abbrev) } 96 | .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end ) } 97 | .debug_frame 0 : { *(.debug_frame) } 98 | .debug_str 0 : { *(.debug_str) } 99 | .debug_loc 0 : { *(.debug_loc) } 100 | .debug_macinfo 0 : { *(.debug_macinfo) } 101 | /* SGI/MIPS DWARF 2 extensions */ 102 | .debug_weaknames 0 : { *(.debug_weaknames) } 103 | .debug_funcnames 0 : { *(.debug_funcnames) } 104 | .debug_typenames 0 : { *(.debug_typenames) } 105 | .debug_varnames 0 : { *(.debug_varnames) } 106 | /* DWARF 3 */ 107 | .debug_pubtypes 0 : { *(.debug_pubtypes) } 108 | .debug_ranges 0 : { *(.debug_ranges) } 109 | /* DWARF Extension. */ 110 | .debug_macro 0 : { *(.debug_macro) } 111 | .debug_addr 0 : { *(.debug_addr) } 112 | .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) } 113 | } 114 | -------------------------------------------------------------------------------- /firmware/usb/descriptors.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #include 3 | #include 4 | #include "constants.hxx" 5 | 6 | using namespace std::literals::string_view_literals; 7 | 8 | namespace usb::descriptors 9 | { 10 | const usbDeviceDescriptor_t deviceDescriptor 11 | { 12 | sizeof(usbDeviceDescriptor_t), 13 | usbDescriptor_t::device, 14 | 0x0200, // This is 2.00 in USB's BCD format 15 | usbClass_t::none, 16 | uint8_t(subclasses::device_t::none), 17 | uint8_t(protocols::device_t::none), 18 | epBufferSize, 19 | vid, 20 | pid, 21 | 0x0001, // BCD encoded device version 22 | 1, // Manufacturer string index 23 | 2, // Product string index 24 | 0, // Temporarily do not support a serial number string 25 | configsCount 26 | }; 27 | 28 | static const std::array configDescs 29 | {{ 30 | { 31 | sizeof(usbConfigDescriptor_t), 32 | usbDescriptor_t::configuration, 33 | sizeof(usbConfigDescriptor_t) + sizeof(usbInterfaceDescriptor_t) + 34 | sizeof(usbEndpointDescriptor_t) + sizeof(usbEndpointDescriptor_t) + 35 | sizeof(usbInterfaceDescriptor_t) + sizeof(dfu::functionalDescriptor_t), 36 | interfaceCount, 37 | 1, // This config 38 | 0, // No string to describe this configuration (for now) 39 | usbConfigAttr_t::defaults, 40 | 50 // 100mA (the max our regulator can do) 41 | } 42 | }}; 43 | 44 | const std::array interfaceDescriptors 45 | {{ 46 | { 47 | sizeof(usbInterfaceDescriptor_t), 48 | usbDescriptor_t::interface, 49 | 0, // interface index 0 50 | 0, // alternate 0 51 | 2, // two endpoints to the interface 52 | usbClass_t::vendor, 53 | uint8_t(subclasses::vendor_t::none), 54 | uint8_t(protocols::vendor_t::flashprog), 55 | 4 // "Flash access interface" string index 56 | }, 57 | { 58 | sizeof(usbInterfaceDescriptor_t), 59 | usbDescriptor_t::interface, 60 | 1, // Interface index 1 61 | 0, // Alternate 0 62 | 0, // No endpoints for this interface 63 | usbClass_t::application, 64 | uint8_t(subclasses::application_t::dfu), 65 | uint8_t(protocols::dfu_t::runtime), 66 | 5, // "SPIFlashProgrammer Firmware Upgrade interface" string index 67 | } 68 | }}; 69 | 70 | const std::array endpointDescriptors 71 | {{ 72 | { 73 | sizeof(usbEndpointDescriptor_t), 74 | usbDescriptor_t::endpoint, 75 | endpointAddress(usbEndpointDir_t::controllerOut, 1), 76 | usbEndpointType_t::interrupt, 77 | epBufferSize, 78 | 1 // Poll once per frame 79 | }, 80 | { 81 | sizeof(usbEndpointDescriptor_t), 82 | usbDescriptor_t::endpoint, 83 | endpointAddress(usbEndpointDir_t::controllerIn, 1), 84 | usbEndpointType_t::bulk, 85 | epBufferSize, 86 | 1 // Poll once per frame 87 | } 88 | }}; 89 | 90 | static const dfu::functionalDescriptor_t dfuFunctionalDesc 91 | { 92 | sizeof(dfu::functionalDescriptor_t), 93 | dfu::descriptor_t::functional, 94 | {dfu::willDetach_t::yes, dfu::manifestationTolerant_t::no, dfu::canUpload_t::no, dfu::canDownload_t::yes}, 95 | // Set the detach timeout to 1000ms which should be the upper bound for how long a PC takes to renumerate us 96 | 1000, 97 | epBufferSize, // Set the max transfer size to the endpoint buffer size 98 | 0x0110 // This is 1.1 in USB's BCD format 99 | }; 100 | 101 | static const std::array configSecs 102 | {{ 103 | { 104 | sizeof(usbConfigDescriptor_t), 105 | &configDescs[0] 106 | }, 107 | { 108 | sizeof(usbInterfaceDescriptor_t), 109 | &interfaceDescriptors[0] 110 | }, 111 | { 112 | sizeof(usbEndpointDescriptor_t), 113 | &endpointDescriptors[0] 114 | }, 115 | { 116 | sizeof(usbEndpointDescriptor_t), 117 | &endpointDescriptors[1] 118 | }, 119 | { 120 | sizeof(usbInterfaceDescriptor_t), 121 | &interfaceDescriptors[1] 122 | }, 123 | { 124 | sizeof(dfu::functionalDescriptor_t), 125 | &dfuFunctionalDesc 126 | } 127 | }}; 128 | 129 | const std::array configDescriptors 130 | {{ 131 | {configSecs.begin(), configSecs.end()} 132 | }}; 133 | 134 | static const std::array stringDescs 135 | {{ 136 | {{u"bad_alloc Heavy Industries", 26}}, 137 | {{u"SPIFlashProgrammer rev 2", 24}}, 138 | {{u"", 0}}, // NOLINT(bugprone-string-constructor) 139 | {{u"Flash access interface", 22}}, 140 | {{u"SPIFlashProgrammer Firmware Upgrade interface", 45}} 141 | }}; 142 | 143 | static const std::array, stringCount> stringParts 144 | {{ 145 | stringDescs[0].asParts(), 146 | stringDescs[1].asParts(), 147 | stringDescs[2].asParts(), 148 | stringDescs[3].asParts(), 149 | stringDescs[4].asParts() 150 | }}; 151 | 152 | const std::array strings 153 | {{ 154 | {stringParts[0].begin(), stringParts[0].end()}, 155 | {stringParts[1].begin(), stringParts[1].end()}, 156 | {stringParts[2].begin(), stringParts[2].end()}, 157 | {stringParts[3].begin(), stringParts[3].end()}, 158 | {stringParts[4].begin(), stringParts[4].end()} 159 | }}; 160 | } // namespace usb::descriptors 161 | -------------------------------------------------------------------------------- /firmware/usb/flashProto.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef USB_FLASH_PROTO_HXX 3 | #define USB_FLASH_PROTO_HXX 4 | 5 | #include 6 | 7 | namespace usb::flashProto 8 | { 9 | void registerHandlers(uint8_t inEP, uint8_t outEP, uint8_t interface, uint8_t config) noexcept; 10 | } // namespace usb::flashProto 11 | 12 | #endif /*USB_FLASH_PROTO_HXX*/ 13 | -------------------------------------------------------------------------------- /hardware/.gitignore: -------------------------------------------------------------------------------- 1 | /*-backups/ 2 | -------------------------------------------------------------------------------- /hardware/SPIFlashProgrammer.kicad_pro: -------------------------------------------------------------------------------- 1 | { 2 | "board": { 3 | "design_settings": { 4 | "defaults": { 5 | "board_outline_line_width": 0.049999999999999996, 6 | "copper_line_width": 0.19999999999999998, 7 | "copper_text_italic": false, 8 | "copper_text_size_h": 1.5, 9 | "copper_text_size_v": 1.5, 10 | "copper_text_thickness": 0.3, 11 | "copper_text_upright": false, 12 | "courtyard_line_width": 0.049999999999999996, 13 | "dimension_precision": 4, 14 | "dimension_units": 3, 15 | "dimensions": { 16 | "arrow_length": 1270000, 17 | "extension_offset": 500000, 18 | "keep_text_aligned": true, 19 | "suppress_zeroes": false, 20 | "text_position": 0, 21 | "units_format": 1 22 | }, 23 | "fab_line_width": 0.09999999999999999, 24 | "fab_text_italic": false, 25 | "fab_text_size_h": 1.0, 26 | "fab_text_size_v": 1.0, 27 | "fab_text_thickness": 0.15, 28 | "fab_text_upright": false, 29 | "other_line_width": 0.09999999999999999, 30 | "other_text_italic": false, 31 | "other_text_size_h": 1.0, 32 | "other_text_size_v": 1.0, 33 | "other_text_thickness": 0.15, 34 | "other_text_upright": false, 35 | "pads": { 36 | "drill": 0.9, 37 | "height": 1.27, 38 | "width": 1.27 39 | }, 40 | "silk_line_width": 0.12, 41 | "silk_text_italic": false, 42 | "silk_text_size_h": 1.0, 43 | "silk_text_size_v": 1.0, 44 | "silk_text_thickness": 0.15, 45 | "silk_text_upright": false, 46 | "zones": { 47 | "45_degree_only": false, 48 | "min_clearance": 0.19999999999999998 49 | } 50 | }, 51 | "diff_pair_dimensions": [], 52 | "drc_exclusions": [], 53 | "meta": { 54 | "filename": "board_design_settings.json", 55 | "version": 2 56 | }, 57 | "rule_severities": { 58 | "annular_width": "error", 59 | "clearance": "error", 60 | "copper_edge_clearance": "error", 61 | "courtyards_overlap": "error", 62 | "diff_pair_gap_out_of_range": "error", 63 | "diff_pair_uncoupled_length_too_long": "error", 64 | "drill_out_of_range": "error", 65 | "duplicate_footprints": "warning", 66 | "extra_footprint": "warning", 67 | "footprint_type_mismatch": "error", 68 | "hole_clearance": "error", 69 | "hole_near_hole": "error", 70 | "invalid_outline": "error", 71 | "item_on_disabled_layer": "error", 72 | "items_not_allowed": "error", 73 | "length_out_of_range": "error", 74 | "malformed_courtyard": "error", 75 | "microvia_drill_out_of_range": "error", 76 | "missing_courtyard": "ignore", 77 | "missing_footprint": "warning", 78 | "net_conflict": "warning", 79 | "npth_inside_courtyard": "ignore", 80 | "padstack": "error", 81 | "pth_inside_courtyard": "ignore", 82 | "shorting_items": "error", 83 | "silk_over_copper": "warning", 84 | "silk_overlap": "warning", 85 | "skew_out_of_range": "error", 86 | "through_hole_pad_without_hole": "error", 87 | "too_many_vias": "error", 88 | "track_dangling": "warning", 89 | "track_width": "error", 90 | "tracks_crossing": "error", 91 | "unconnected_items": "error", 92 | "unresolved_variable": "error", 93 | "via_dangling": "warning", 94 | "zone_has_empty_net": "error", 95 | "zones_intersect": "error" 96 | }, 97 | "rule_severitieslegacy_courtyards_overlap": true, 98 | "rule_severitieslegacy_no_courtyard_defined": false, 99 | "rules": { 100 | "allow_blind_buried_vias": false, 101 | "allow_microvias": false, 102 | "max_error": 0.005, 103 | "min_clearance": 0.0, 104 | "min_copper_edge_clearance": 0.024999999999999998, 105 | "min_hole_clearance": 0.25, 106 | "min_hole_to_hole": 0.25, 107 | "min_microvia_diameter": 0.19999999999999998, 108 | "min_microvia_drill": 0.09999999999999999, 109 | "min_silk_clearance": 0.0, 110 | "min_through_hole_diameter": 0.19999999999999998, 111 | "min_track_width": 0.19999999999999998, 112 | "min_via_annular_width": 0.049999999999999996, 113 | "min_via_diameter": 0.39999999999999997, 114 | "use_height_for_length_calcs": true 115 | }, 116 | "track_widths": [ 117 | 0.0, 118 | 0.2, 119 | 0.25, 120 | 0.3, 121 | 0.4, 122 | 0.5, 123 | 1.0 124 | ], 125 | "via_dimensions": [ 126 | { 127 | "diameter": 0.0, 128 | "drill": 0.0 129 | }, 130 | { 131 | "diameter": 0.5, 132 | "drill": 0.3 133 | } 134 | ], 135 | "zones_allow_external_fillets": false, 136 | "zones_use_no_outline": true 137 | }, 138 | "layer_presets": [] 139 | }, 140 | "boards": [], 141 | "cvpcb": { 142 | "equivalence_files": [] 143 | }, 144 | "erc": { 145 | "erc_exclusions": [], 146 | "meta": { 147 | "version": 0 148 | }, 149 | "pin_map": [ 150 | [ 151 | 0, 152 | 0, 153 | 0, 154 | 0, 155 | 0, 156 | 0, 157 | 1, 158 | 0, 159 | 0, 160 | 0, 161 | 0, 162 | 2 163 | ], 164 | [ 165 | 0, 166 | 2, 167 | 0, 168 | 1, 169 | 0, 170 | 0, 171 | 1, 172 | 0, 173 | 2, 174 | 2, 175 | 2, 176 | 2 177 | ], 178 | [ 179 | 0, 180 | 0, 181 | 0, 182 | 0, 183 | 0, 184 | 0, 185 | 1, 186 | 0, 187 | 1, 188 | 0, 189 | 1, 190 | 2 191 | ], 192 | [ 193 | 0, 194 | 1, 195 | 0, 196 | 0, 197 | 0, 198 | 0, 199 | 1, 200 | 1, 201 | 2, 202 | 1, 203 | 1, 204 | 2 205 | ], 206 | [ 207 | 0, 208 | 0, 209 | 0, 210 | 0, 211 | 0, 212 | 0, 213 | 1, 214 | 0, 215 | 0, 216 | 0, 217 | 0, 218 | 2 219 | ], 220 | [ 221 | 0, 222 | 0, 223 | 0, 224 | 0, 225 | 0, 226 | 0, 227 | 0, 228 | 0, 229 | 0, 230 | 0, 231 | 0, 232 | 2 233 | ], 234 | [ 235 | 1, 236 | 1, 237 | 1, 238 | 1, 239 | 1, 240 | 0, 241 | 1, 242 | 1, 243 | 1, 244 | 1, 245 | 1, 246 | 2 247 | ], 248 | [ 249 | 0, 250 | 0, 251 | 0, 252 | 1, 253 | 0, 254 | 0, 255 | 1, 256 | 0, 257 | 0, 258 | 0, 259 | 0, 260 | 2 261 | ], 262 | [ 263 | 0, 264 | 2, 265 | 1, 266 | 2, 267 | 0, 268 | 0, 269 | 1, 270 | 0, 271 | 2, 272 | 2, 273 | 2, 274 | 2 275 | ], 276 | [ 277 | 0, 278 | 2, 279 | 0, 280 | 1, 281 | 0, 282 | 0, 283 | 1, 284 | 0, 285 | 2, 286 | 0, 287 | 0, 288 | 2 289 | ], 290 | [ 291 | 0, 292 | 2, 293 | 1, 294 | 1, 295 | 0, 296 | 0, 297 | 1, 298 | 0, 299 | 2, 300 | 0, 301 | 0, 302 | 2 303 | ], 304 | [ 305 | 2, 306 | 2, 307 | 2, 308 | 2, 309 | 2, 310 | 2, 311 | 2, 312 | 2, 313 | 2, 314 | 2, 315 | 2, 316 | 2 317 | ] 318 | ], 319 | "rule_severities": { 320 | "bus_definition_conflict": "error", 321 | "bus_entry_needed": "error", 322 | "bus_label_syntax": "error", 323 | "bus_to_bus_conflict": "error", 324 | "bus_to_net_conflict": "error", 325 | "different_unit_footprint": "error", 326 | "different_unit_net": "error", 327 | "duplicate_reference": "error", 328 | "duplicate_sheet_names": "error", 329 | "extra_units": "error", 330 | "global_label_dangling": "warning", 331 | "hier_label_mismatch": "error", 332 | "label_dangling": "error", 333 | "lib_symbol_issues": "warning", 334 | "multiple_net_names": "warning", 335 | "net_not_bus_member": "warning", 336 | "no_connect_connected": "warning", 337 | "no_connect_dangling": "warning", 338 | "pin_not_connected": "error", 339 | "pin_not_driven": "error", 340 | "pin_to_pin": "warning", 341 | "power_pin_not_driven": "error", 342 | "similar_labels": "warning", 343 | "unannotated": "error", 344 | "unit_value_mismatch": "error", 345 | "unresolved_variable": "error", 346 | "wire_dangling": "error" 347 | } 348 | }, 349 | "libraries": { 350 | "pinned_footprint_libs": [], 351 | "pinned_symbol_libs": [] 352 | }, 353 | "meta": { 354 | "filename": "SPIFlashProgrammer.kicad_pro", 355 | "version": 1 356 | }, 357 | "net_settings": { 358 | "classes": [ 359 | { 360 | "bus_width": 12.0, 361 | "clearance": 0.2, 362 | "diff_pair_gap": 0.2, 363 | "diff_pair_via_gap": 0.25, 364 | "diff_pair_width": 0.2, 365 | "line_style": 0, 366 | "microvia_diameter": 0.3, 367 | "microvia_drill": 0.1, 368 | "name": "Default", 369 | "pcb_color": "rgba(0, 0, 0, 0.000)", 370 | "schematic_color": "rgba(0, 0, 0, 0.000)", 371 | "track_width": 0.2, 372 | "via_diameter": 0.5, 373 | "via_drill": 0.3, 374 | "wire_width": 6.0 375 | } 376 | ], 377 | "meta": { 378 | "version": 2 379 | }, 380 | "net_colors": null 381 | }, 382 | "pcbnew": { 383 | "last_paths": { 384 | "gencad": "", 385 | "idf": "", 386 | "netlist": "", 387 | "specctra_dsn": "", 388 | "step": "", 389 | "vrml": "" 390 | }, 391 | "page_layout_descr_file": "~/.kicad/default.kicad_wks" 392 | }, 393 | "schematic": { 394 | "annotate_start_num": 0, 395 | "drawing": { 396 | "default_line_thickness": 6.0, 397 | "default_text_size": 50.0, 398 | "field_names": [], 399 | "intersheets_ref_own_page": false, 400 | "intersheets_ref_prefix": "", 401 | "intersheets_ref_short": false, 402 | "intersheets_ref_show": false, 403 | "intersheets_ref_suffix": "", 404 | "junction_size_choice": 3, 405 | "label_size_ratio": 0.25, 406 | "pin_symbol_size": 0.0, 407 | "text_offset_ratio": 0.08 408 | }, 409 | "legacy_lib_dir": "", 410 | "legacy_lib_list": [], 411 | "meta": { 412 | "version": 1 413 | }, 414 | "net_format_name": "Pcbnew", 415 | "ngspice": { 416 | "fix_include_paths": true, 417 | "fix_passive_vals": false, 418 | "meta": { 419 | "version": 0 420 | }, 421 | "model_mode": 0, 422 | "workbook_filename": "" 423 | }, 424 | "page_layout_descr_file": "", 425 | "plot_directory": "", 426 | "spice_adjust_passive_values": false, 427 | "spice_external_command": "spice \"%I\"", 428 | "subpart_first_id": 65, 429 | "subpart_id_separator": 0 430 | }, 431 | "sheets": [ 432 | [ 433 | "b6135480-ace6-42b2-9c47-856ef57cded1", 434 | "" 435 | ], 436 | [ 437 | "00000000-0000-0000-0000-00005f8b7b2c", 438 | "Onboard Flash" 439 | ], 440 | [ 441 | "00000000-0000-0000-0000-00005f0a81b1", 442 | "Processor" 443 | ], 444 | [ 445 | "00000000-0000-0000-0000-00005f6e308f", 446 | "Power Supply" 447 | ], 448 | [ 449 | "00000000-0000-0000-0000-00005f0a82b6", 450 | "Connectors" 451 | ] 452 | ], 453 | "text_variables": {} 454 | } 455 | -------------------------------------------------------------------------------- /hardware/gerber/.gitignore: -------------------------------------------------------------------------------- 1 | /SPIFlashProgrammer.zip 2 | /SPIFlashProgrammer-*.g* 3 | /SPIFlashProgrammer.drl 4 | -------------------------------------------------------------------------------- /hardware/gerber/SPIFlashProgrammer.gvp: -------------------------------------------------------------------------------- 1 | (gerbv-file-version! "2.0A") 2 | (define-layer! 11 (cons 'filename "SPIFlashProgrammer-B_Silkscreen.gbo") 3 | (cons 'visible #f) 4 | (cons 'color #(0 50115 50115)) 5 | ) 6 | (define-layer! 10 (cons 'filename "SPIFlashProgrammer-B_Paste.gbp") 7 | (cons 'visible #f) 8 | (cons 'color #(49601 0 57568)) 9 | ) 10 | (define-layer! 9 (cons 'filename "SPIFlashProgrammer-B_Mask.gbs") 11 | (cons 'visible #f) 12 | (cons 'color #(65021 53970 52942)) 13 | ) 14 | (define-layer! 8 (cons 'filename "SPIFlashProgrammer-Bottom.gbl") 15 | (cons 'visible #f) 16 | (cons 'color #(30069 62194 26471)) 17 | ) 18 | (define-layer! 7 (cons 'filename "SPIFlashProgrammer-Inner_2.g3") 19 | (cons 'visible #f) 20 | (cons 'color #(65535 32639 29555)) 21 | ) 22 | (define-layer! 6 (cons 'filename "SPIFlashProgrammer-Inner_1.g2") 23 | (cons 'visible #f) 24 | (cons 'color #(29555 29555 57054)) 25 | ) 26 | (define-layer! 5 (cons 'filename "SPIFlashProgrammer-Top.gtl") 27 | (cons 'visible #f) 28 | (cons 'color #(65535 50629 13107)) 29 | ) 30 | (define-layer! 4 (cons 'filename "SPIFlashProgrammer-F_Mask.gts") 31 | (cons 'visible #f) 32 | (cons 'color #(60652 49858 62194)) 33 | ) 34 | (define-layer! 3 (cons 'filename "SPIFlashProgrammer-F_Paste.gtp") 35 | (cons 'visible #f) 36 | (cons 'color #(53713 6939 26728)) 37 | ) 38 | (define-layer! 2 (cons 'filename "SPIFlashProgrammer-F_Silkscreen.gto") 39 | (cons 'visible #f) 40 | (cons 'color #(47802 47802 47802)) 41 | ) 42 | (define-layer! 1 (cons 'filename "SPIFlashProgrammer-Edge_Cuts.gm1") 43 | (cons 'visible #t) 44 | (cons 'color #(54741 65021 13107)) 45 | ) 46 | (define-layer! 0 (cons 'filename "SPIFlashProgrammer.drl") 47 | (cons 'visible #t) 48 | (cons 'color #(54227 54227 65535)) 49 | (cons 'attribs (list 50 | (list 'autodetect 'Boolean 1) 51 | (list 'zero_suppression 'Enum 0) 52 | (list 'units 'Enum 1) 53 | (list 'digits 'Integer 4) 54 | )) 55 | ) 56 | (define-layer! -1 (cons 'filename ".") 57 | (cons 'color #(0 0 0)) 58 | ) 59 | (set-render-type! 3) 60 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | project( 3 | 'SPIFlashProgrammer', 4 | 'cpp', 5 | default_options: [ 6 | 'cpp_std=c++17', 7 | 'build.cpp_std=c++17', 8 | 'warning_level=3', 9 | 'buildtype=release', 10 | 'strip=true', 11 | 'b_ndebug=if-release', 12 | 'b_lto=true' 13 | ], 14 | version: '2.0.0', 15 | meson_version: '>= 0.55', 16 | subproject_dir: 'deps' 17 | ) 18 | 19 | if not meson.is_cross_build() 20 | error('SPIFlashProgrammer must be cross-compiled to the target microcontroller and cannot be built for the host machine') 21 | endif 22 | 23 | targetCXX = meson.get_compiler('cpp', native: false) 24 | hostCXX = meson.get_compiler('cpp', native: true) 25 | commonInclude = include_directories('common/include') 26 | 27 | subdir('firmware') 28 | subdir('software') 29 | 30 | runClangTidy = find_program('runClangTidy.py') 31 | run_target( 32 | 'clang-tidy', 33 | command: [runClangTidy, '-s', meson.current_source_dir(), '-p', meson.current_build_dir()] 34 | ) 35 | -------------------------------------------------------------------------------- /runClangTidy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: GPL-3.0-or-later 3 | from argparse import ArgumentParser 4 | from pathlib import Path 5 | from subprocess import run 6 | from concurrent.futures import ThreadPoolExecutor 7 | from sys import exit 8 | 9 | parser = ArgumentParser( 10 | description = 'Light-weight wrapper around clang-tidy to enable `ninja clang-tidy` to function properly', 11 | allow_abbrev = False 12 | ) 13 | parser.add_argument('-s', required = True, type = str, metavar = 'sourcePath', 14 | dest = 'sourcePath', help = 'Path to the source directory to run clang-tidy in') 15 | parser.add_argument('-p', required = True, type = str, metavar = 'buildPath', 16 | dest = 'buildPath', help = 'Path to the build directory containing a compile_commands.json') 17 | args = parser.parse_args() 18 | 19 | def globFiles(): 20 | srcDir = Path(args.sourcePath) 21 | paths = set(('common', 'firmware', 'software')) 22 | suffixes = set(('c','C', 'cc', 'cpp', 'cxx', 'CC', 'h', 'H', 'hh', 'hpp', 'hxx', 'HH')) 23 | for path in paths: 24 | for suffix in suffixes: 25 | yield srcDir.glob('{}/**/*.{}'.format(path, suffix)) 26 | 27 | def gatherFiles(): 28 | for fileGlob in globFiles(): 29 | for file in fileGlob: 30 | yield file 31 | 32 | extraArgs = [] 33 | 34 | futures = [] 35 | returncode = 0 36 | with ThreadPoolExecutor() as pool: 37 | for file in gatherFiles(): 38 | futures.append(pool.submit(run, ['clang-tidy'] + extraArgs + ['-p', args.buildPath, file])) 39 | returncode = max((future.result().returncode for future in futures)) 40 | exit(returncode) 41 | -------------------------------------------------------------------------------- /software/flashprog.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "options.hxx" 16 | #include "help.hxx" 17 | #include "usbContext.hxx" 18 | #include "usbProtocol.hxx" 19 | #include "sfdp.hxx" 20 | #include "progress.hxx" 21 | #include "utils/units.hxx" 22 | 23 | // TODO: Add ChaiScript support for the flashing algorithms. 24 | 25 | namespace flashprog 26 | { 27 | inline int32_t printHelp() noexcept 28 | { 29 | console.info(helpString); 30 | return 0; 31 | } 32 | } // namespace flashprog 33 | 34 | using namespace flashProto; 35 | using namespace std::literals::chrono_literals; 36 | using namespace substrate; 37 | using substrate::commandLine::arguments_t; 38 | using substrate::commandLine::flag_t; 39 | using substrate::commandLine::choice_t; 40 | using flashprog::chip_t; 41 | 42 | constexpr static auto transferBlockSize{4_KiB}; 43 | static arguments_t args{}; 44 | 45 | auto requestCount(const usbDeviceHandle_t &device) 46 | { 47 | responses::deviceCount_t deviceCount{}; 48 | if (!requests::deviceCount_t{}.read(device, 0, deviceCount)) 49 | throw requests::usbError_t{}; 50 | return std::make_tuple(deviceCount.internalCount, deviceCount.externalCount); 51 | } 52 | 53 | responses::listDevice_t readChipInfo(const usbDeviceHandle_t &device, const chip_t &chip) 54 | { 55 | responses::listDevice_t chipInfo{}; 56 | try 57 | { 58 | requests::listDevice_t request{chip.index, chip.bus}; 59 | if (!request.read(device, 0, chipInfo)) 60 | throw responses::usbError_t{}; 61 | } 62 | catch (const responses::usbError_t &error) 63 | { 64 | console.error("Error getting target Flash chip information: "sv, error.what()); 65 | if (!device.releaseInterface(0)) 66 | throw std::nested_exception{}; 67 | throw error; 68 | } 69 | return chipInfo; 70 | } 71 | 72 | bool listDevice(const usbDeviceHandle_t &device, const chip_t &chip) noexcept 73 | { 74 | try 75 | { 76 | const auto chipInfo{readChipInfo(device, chip)}; 77 | console.info('\t', chip.index, ": Manufacturer - "sv, chipInfo.manufacturer, 78 | ", Capacity - "sv, chipInfo.deviceSize, ", Page size - "sv, uint32_t{chipInfo.pageSize}, 79 | ", Erase page size - "sv, uint32_t{chipInfo.eraseSize}); 80 | return true; 81 | } 82 | catch (std::exception &) 83 | { return false; } 84 | } 85 | 86 | int32_t listDevices(const usbDevice_t &rawDevice) 87 | { 88 | const auto device{rawDevice.open()}; 89 | if (!device.valid() || 90 | !device.claimInterface(0)) 91 | return 1; 92 | 93 | try 94 | { 95 | const auto &[internalDeviceCount, externalDeviceCount] = requestCount(device); 96 | 97 | console.info("Programmer has "sv, internalDeviceCount, " internal Flash chips, and "sv, 98 | externalDeviceCount, " external Flash chips"sv); 99 | console.info("Internal devices:"sv); 100 | for (uint8_t internalDevice{0}; internalDevice < internalDeviceCount; ++internalDevice) 101 | listDevice(device, {flashBus_t::internal, internalDevice}); 102 | if (externalDeviceCount) 103 | console.info("External devices:"sv); 104 | for (uint8_t externalDevice{0}; externalDevice < externalDeviceCount; ++externalDevice) 105 | listDevice(device, {flashBus_t::external, externalDevice}); 106 | } 107 | catch (const requests::usbError_t &error) 108 | { 109 | console.error("Failure while listing devices: "sv, error.what()); 110 | [[maybe_unused]] const auto result{device.releaseInterface(0)}; 111 | return 1; 112 | } 113 | 114 | if (!device.releaseInterface(0)) 115 | return 1; 116 | return 0; 117 | } 118 | 119 | bool targetDevice(const usbDeviceHandle_t &device, const flashBus_t deviceType, const uint8_t deviceNumber) noexcept 120 | { return requests::targetDevice_t{deviceNumber, deviceType}.write(device, 0); } 121 | 122 | void displayChipSize(const uint32_t chipSize) noexcept 123 | { 124 | const auto [size, units] = flashprog::utils::humanReadableSize(chipSize); 125 | console.info("Chip is "sv, size, units, " in size"sv); 126 | } 127 | 128 | int32_t eraseDevice(const usbDevice_t &rawDevice, const arguments_t &eraseArgs) 129 | { 130 | const auto &chip{std::any_cast(std::get(*eraseArgs["chip"sv]).value())}; 131 | 132 | const auto device{rawDevice.open()}; 133 | if (!device.valid() || 134 | !device.claimInterface(0)) 135 | return 1; 136 | 137 | if (!requests::abort_t{}.write(device, 0) || 138 | !targetDevice(device, chip.bus, chip.index)) 139 | { 140 | if (!device.releaseInterface(0)) 141 | return 2; 142 | return 1; 143 | } 144 | 145 | progressBar_t progress{"Erasing chip"sv}; 146 | progress.display(); 147 | const auto startTime{std::chrono::steady_clock::now()}; 148 | 149 | if (!requests::erase_t{}.write(device, 0, eraseOperation_t::all)) 150 | { 151 | if (!device.releaseInterface(0)) 152 | return 2; 153 | return 1; 154 | } 155 | ++progress; 156 | 157 | responses::status_t status{}; 158 | while (!status.eraseComplete) 159 | { 160 | std::this_thread::sleep_for(125ms); 161 | if (!requests::status_t{}.read(device, 0, status)) 162 | { 163 | if (!device.releaseInterface(0)) 164 | return 2; 165 | return 1; 166 | } 167 | ++progress; 168 | } 169 | progress.close(); 170 | const auto endTime{std::chrono::steady_clock::now()}; 171 | 172 | console.info("Complete"sv); 173 | const auto elapsedSeconds{std::chrono::duration_cast(endTime - startTime)}; 174 | console.info("Total time elapsed: "sv, substrate::asTime_t{uint64_t(elapsedSeconds.count())}); 175 | 176 | // This deselects the device 177 | if (!targetDevice(device, flashBus_t::unknown, 0)) 178 | { 179 | if (!device.releaseInterface(0)) 180 | return 2; 181 | return 1; 182 | } 183 | 184 | if (!device.releaseInterface(0)) 185 | return 1; 186 | // As complete is non-0 at this point, if it has the value 1, it was a good completion 187 | // otherwise, the programmer said we asked it to do something insane, so error out. 188 | return status.eraseComplete == 1 ? 0 : 1; 189 | } 190 | 191 | [[nodiscard]] int32_t readNormalDevice(const usbDeviceHandle_t &device, const responses::listDevice_t &chipInfo, 192 | substrate::fd_t &file) 193 | { 194 | if (chipInfo.deviceSize % transferBlockSize) 195 | { 196 | console.error("Funky device size, is "sv, chipInfo.deviceSize, 197 | ", was expecting a device size that divided by "sv, transferBlockSize); 198 | if (!device.releaseInterface(0)) 199 | return 1; 200 | return 1; 201 | } 202 | const auto pagesPerBlock{static_cast(transferBlockSize / chipInfo.pageSize)}; 203 | const auto blockCount{static_cast(chipInfo.deviceSize / transferBlockSize)}; 204 | progressBar_t progress{"Reading chip "sv, blockCount}; 205 | progress.display(); 206 | for (uint32_t block{}; block < blockCount; ++block) 207 | { 208 | const auto page{block * pagesPerBlock}; 209 | if (!requests::read_t{page}.write(device, 0, transferBlockSize)) 210 | { 211 | if (!device.releaseInterface(0)) 212 | return 2; 213 | return 1; 214 | } 215 | 216 | std::array data{}; 217 | if (!device.readBulk(1, data.data(), data.size()) || 218 | !file.write(data)) 219 | { 220 | console.error("Failed to read pages "sv, page, ":"sv, page + pagesPerBlock - 1, 221 | " back from the device"sv); 222 | if (!device.releaseInterface(0)) 223 | return 2; 224 | return 1; 225 | } 226 | ++progress; 227 | } 228 | progress.close(); 229 | return 0; 230 | } 231 | 232 | [[nodiscard]] int32_t readTinyDevice(const usbDeviceHandle_t &device, const responses::listDevice_t &chipInfo, 233 | substrate::fd_t &file) 234 | { 235 | const uint32_t pageSize{chipInfo.pageSize}; 236 | const uint32_t pageCount{chipInfo.deviceSize / pageSize}; 237 | progressBar_t progress{"Reading chip "sv, pageCount}; 238 | // NOLINTNEXTLINE: cppcoreguidelines-avoid-c-arrays 239 | auto data{std::make_unique(pageSize)}; 240 | progress.display(); 241 | for (uint32_t page{}; page < pageCount; ++page) 242 | { 243 | if (!requests::read_t{page}.write(device, 0)) 244 | { 245 | if (!device.releaseInterface(0)) 246 | return 2; 247 | return 1; 248 | } 249 | 250 | if (!device.readBulk(1, data.get(), static_cast(pageSize)) || 251 | !file.write(data, pageSize)) 252 | { 253 | console.error("Failed to read page "sv, page, " back from the device"sv); 254 | if (!device.releaseInterface(0)) 255 | return 2; 256 | return 1; 257 | } 258 | ++progress; 259 | } 260 | progress.close(); 261 | return 0; 262 | } 263 | 264 | int32_t readDevice(const usbDevice_t &rawDevice, const arguments_t &readArgs) 265 | { 266 | const auto &chip{std::any_cast(std::get(*readArgs["chip"sv]).value())}; 267 | const auto &fileName 268 | { 269 | [](const commandLine::item_t *arg) 270 | { 271 | if (!arg) 272 | throw std::logic_error{"File name to write data read from the device is null"}; 273 | return std::any_cast(std::get(*arg).value()); 274 | }(readArgs["file"sv]) 275 | }; 276 | 277 | const auto device{rawDevice.open()}; 278 | if (!device.valid() || 279 | !device.claimInterface(0)) 280 | return 1; 281 | 282 | substrate::fd_t file{fileName, O_CREAT | O_WRONLY | O_NOCTTY, substrate::normalMode}; 283 | if (!file.valid()) 284 | { 285 | console.error("Failed to open output file '"sv, fileName.u8string(), "'"sv); 286 | if (!device.releaseInterface(0)) 287 | return 2; 288 | return 1; 289 | } 290 | 291 | const auto chipInfo{readChipInfo(device, chip)}; 292 | if (!requests::abort_t{}.write(device, 0) || 293 | !targetDevice(device, chip.bus, chip.index)) 294 | { 295 | if (!device.releaseInterface(0)) 296 | return 2; 297 | return 1; 298 | } 299 | 300 | displayChipSize(chipInfo.deviceSize); 301 | const auto startTime{std::chrono::steady_clock::now()}; 302 | const auto result 303 | { 304 | [&]() 305 | { 306 | if (chipInfo.deviceSize >= transferBlockSize) 307 | return readNormalDevice(device, chipInfo, file); 308 | else 309 | return readTinyDevice(device, chipInfo, file); 310 | }() 311 | }; 312 | if (result != 0) 313 | return result; 314 | const auto endTime{std::chrono::steady_clock::now()}; 315 | 316 | console.info("Complete"sv); 317 | const auto elapsedSeconds{std::chrono::duration_cast(endTime - startTime)}; 318 | console.info("Total time elapsed: "sv, substrate::asTime_t{uint64_t(elapsedSeconds.count())}); 319 | 320 | // This deselects the device 321 | if (!targetDevice(device, flashBus_t::unknown, 0)) 322 | { 323 | if (!device.releaseInterface(0)) 324 | return 2; 325 | return 1; 326 | } 327 | 328 | if (!device.releaseInterface(0)) 329 | return 1; 330 | return 0; 331 | } 332 | 333 | int32_t erasePages(const usbDeviceHandle_t &device, const responses::listDevice_t chipInfo, size_t fileLength) 334 | { 335 | responses::status_t status{}; 336 | const uint32_t pageSize{chipInfo.eraseSize}; 337 | const uint32_t pageCount 338 | { 339 | [fileLength, pageSize] () -> uint32_t 340 | { 341 | const auto pages{fileLength / pageSize}; 342 | const auto remainder{fileLength % pageSize}; 343 | return pages + (remainder ? 1U : 0U); 344 | }() 345 | }; 346 | 347 | progressBar_t progress{"Erasing chip "sv, pageCount}; 348 | progress.display(); 349 | if (!requests::erase_t{{}, {pageCount}}.write(device, 0, eraseOperation_t::pageRange)) 350 | { 351 | if (!device.releaseInterface(0)) 352 | return 2; 353 | return 1; 354 | } 355 | 356 | page_t currentPage{}; 357 | while (!status.eraseComplete) 358 | { 359 | std::this_thread::sleep_for(10ms); 360 | if (!requests::status_t{}.read(device, 0, status)) 361 | { 362 | if (!device.releaseInterface(0)) 363 | return 2; 364 | return 1; 365 | } 366 | if (currentPage != status.erasePage) 367 | { 368 | progress += status.erasePage - currentPage; 369 | currentPage = status.erasePage; 370 | } 371 | else 372 | progress.display(); 373 | } 374 | progress.close(); 375 | return 0; 376 | } 377 | 378 | [[nodiscard]] int32_t verifyPages(const usbDeviceHandle_t &device, const uint32_t pagesPerBlock, const uint32_t page) 379 | { 380 | responses::status_t status{}; 381 | // Read back the programmer status 382 | if (!requests::status_t{}.read(device, 0, status)) 383 | { 384 | if (!device.releaseInterface(0)) 385 | return 2; 386 | return 1; 387 | } 388 | // Then check the verification status 389 | if (!status.writeOK) 390 | { 391 | console.error("Verification of data on pages "sv, page, ":"sv, 392 | page + pagesPerBlock - 1, " failed"sv); 393 | if (!device.releaseInterface(0)) 394 | return 2; 395 | return 1; 396 | } 397 | return 0; 398 | } 399 | 400 | [[nodiscard]] int32_t writeNormalDevice(const usbDeviceHandle_t &device, const responses::listDevice_t &chipInfo, 401 | const substrate::fd_t &file, const substrate::off_t fileLength, const bool verify) 402 | { 403 | if (chipInfo.deviceSize % transferBlockSize) 404 | { 405 | console.error("Funky device size, is "sv, chipInfo.deviceSize, 406 | ", was expecting a device size that divided by "sv, transferBlockSize); 407 | if (!device.releaseInterface(0)) 408 | return 2; 409 | return 1; 410 | } 411 | const auto pagesPerBlock{static_cast(transferBlockSize / chipInfo.pageSize)}; 412 | const auto blockCount 413 | { 414 | [fileLength] () -> uint32_t 415 | { 416 | const auto blocks{fileLength / transferBlockSize}; 417 | const auto remainder{fileLength % transferBlockSize}; 418 | return blocks + (remainder ? 1U : 0U); 419 | }() 420 | }; 421 | progressBar_t progress{"Writing chip "sv, blockCount}; 422 | progress.display(); 423 | for (uint32_t block{}; block < blockCount; ++block) 424 | { 425 | const auto page{block * pagesPerBlock}; 426 | const auto byteCount{std::min(uint32_t(fileLength) - (block * transferBlockSize), transferBlockSize)}; 427 | if (!requests::write_t{page, verify}.write(device, 0, byteCount)) 428 | { 429 | if (!device.releaseInterface(0)) 430 | return 2; 431 | return 1; 432 | } 433 | 434 | std::array data{}; 435 | if (!file.read(data.data(), byteCount) || 436 | !device.writeBulk(1, data.data(), static_cast(byteCount))) 437 | { 438 | console.error("Failed to write pages "sv, page, ":"sv, page + pagesPerBlock - 1, 439 | " to the device"sv); 440 | if (!device.releaseInterface(0)) 441 | return 2; 442 | return 1; 443 | } 444 | if (verify) 445 | { 446 | const auto result{verifyPages(device, pagesPerBlock, page)}; 447 | if (result != 0) 448 | return result; 449 | } 450 | ++progress; 451 | } 452 | progress.close(); 453 | return 0; 454 | } 455 | 456 | [[nodiscard]] int32_t writeTinyDevice(const usbDeviceHandle_t &device, const responses::listDevice_t &chipInfo, 457 | const substrate::fd_t &file, const substrate::off_t fileLength, [[maybe_unused]] const bool verify) 458 | { 459 | const uint32_t pageSize{chipInfo.pageSize}; 460 | const uint32_t pageCount 461 | { 462 | [fileLength, pageSize] () -> uint32_t 463 | { 464 | const auto pages{fileLength / pageSize}; 465 | const auto remainder{fileLength % pageSize}; 466 | return pages + (remainder ? 1U : 0U); 467 | }() 468 | }; 469 | progressBar_t progress{"Writing chip "sv, pageCount}; 470 | // NOLINTNEXTLINE: cppcoreguidelines-avoid-c-arrays 471 | auto data{std::make_unique(pageSize)}; 472 | progress.display(); 473 | for (uint32_t page{}; page < pageCount; ++page) 474 | { 475 | const auto byteCount{std::min(uint32_t(fileLength) - (page * pageSize), pageSize)}; 476 | if (!requests::write_t{page}.write(device, 0, byteCount)) 477 | { 478 | if (!device.releaseInterface(0)) 479 | return 2; 480 | return 1; 481 | } 482 | 483 | if (!file.read(data, byteCount) || 484 | !device.writeBulk(1, data.get(), static_cast(byteCount))) 485 | { 486 | console.error("Failed to write page "sv, page, " to the device"sv); 487 | if (!device.releaseInterface(0)) 488 | return 2; 489 | return 1; 490 | } 491 | // XXX: We need to write the veriication step for tiny devices still 492 | ++progress; 493 | } 494 | progress.close(); 495 | return 0; 496 | } 497 | 498 | int32_t writeDevice(const usbDevice_t &rawDevice, const arguments_t &writeArgs, const bool verify) 499 | { 500 | const auto &chip{std::any_cast(std::get(*writeArgs["chip"sv]).value())}; 501 | const auto &fileName 502 | { 503 | [](const commandLine::item_t *arg) 504 | { 505 | if (!arg) 506 | throw std::logic_error{"File name to consume data from to write to device is null"}; 507 | return std::any_cast(std::get(*arg).value()); 508 | }(writeArgs["file"sv]) 509 | }; 510 | 511 | const auto device{rawDevice.open()}; 512 | if (!device.valid() || 513 | !device.claimInterface(0)) 514 | return 1; 515 | 516 | const substrate::fd_t file{fileName, O_RDONLY | O_NOCTTY}; 517 | if (!file.valid()) 518 | { 519 | console.error("Failed to open input file '"sv, fileName.u8string(), "'"sv); 520 | if (!device.releaseInterface(0)) 521 | return 2; 522 | return 1; 523 | } 524 | 525 | const auto chipInfo{readChipInfo(device, chip)}; 526 | const auto fileLength{file.length()}; 527 | if (fileLength < 0 || fileLength > chipInfo.deviceSize) 528 | { 529 | console.error("The file given is larger than the target device"sv); 530 | if (!device.releaseInterface(0)) 531 | return 2; 532 | return 1; 533 | } 534 | 535 | if (!requests::abort_t{}.write(device, 0) || 536 | !targetDevice(device, chip.bus, chip.index)) 537 | { 538 | if (!device.releaseInterface(0)) 539 | return 2; 540 | return 1; 541 | } 542 | 543 | displayChipSize(chipInfo.deviceSize); 544 | const auto startTime{std::chrono::steady_clock::now()}; 545 | const auto eraseResult{erasePages(device, chipInfo, fileLength)}; 546 | if (eraseResult) 547 | return eraseResult; 548 | 549 | const auto result 550 | { 551 | [&]() 552 | { 553 | if (chipInfo.deviceSize >= transferBlockSize) 554 | return writeNormalDevice(device, chipInfo, file, fileLength, verify); 555 | else 556 | return writeTinyDevice(device, chipInfo, file, fileLength, verify); 557 | }() 558 | }; 559 | if (result != 0) 560 | return result; 561 | 562 | const auto endTime{std::chrono::steady_clock::now()}; 563 | 564 | console.info("Complete"sv); 565 | const auto elapsedSeconds{std::chrono::duration_cast(endTime - startTime)}; 566 | console.info("Total time elapsed: "sv, substrate::asTime_t{uint64_t(elapsedSeconds.count())}); 567 | 568 | // This deselects the device 569 | if (!targetDevice(device, flashBus_t::unknown, 0)) 570 | { 571 | if (!device.releaseInterface(0)) 572 | return 2; 573 | return 1; 574 | } 575 | 576 | if (!device.releaseInterface(0)) 577 | return 1; 578 | return 0; 579 | } 580 | 581 | 582 | int32_t dumpSFDP(const usbDevice_t &rawDevice, const arguments_t &sfdpArgs) 583 | { 584 | const auto &chip{std::any_cast(std::get(*sfdpArgs["chip"sv]).value())}; 585 | 586 | // Setup the USB interface for use 587 | const auto device{rawDevice.open()}; 588 | if (!device.valid() || 589 | !device.claimInterface(0)) 590 | return 1; 591 | 592 | // Abort any stale running command and select the requested Flash chip 593 | if (!requests::abort_t{}.write(device, 0) || 594 | !targetDevice(device, chip.bus, chip.index)) 595 | { 596 | if (!device.releaseInterface(0)) 597 | return 2; 598 | return 1; 599 | } 600 | 601 | // Call into the PC-side SFDP reader and display engine 602 | if (!sfdp::readAndDisplay(device, {0, 1})) 603 | { 604 | console.error("Failed to read SFDP data"sv); 605 | if (!device.releaseInterface(0)) 606 | return 2; 607 | return 1; 608 | } 609 | 610 | // Deselect the Flash chip now we're complete 611 | if (!targetDevice(device, flashBus_t::unknown, 0)) 612 | { 613 | if (!device.releaseInterface(0)) 614 | return 2; 615 | return 1; 616 | } 617 | 618 | // And clean up 619 | return device.releaseInterface(0) ? 0 : 1; 620 | } 621 | 622 | /*! 623 | * flashprog usage: 624 | * 625 | * - List the available SPIFlashProgrammer v2's 626 | * listDevices - List the available SPIFlashProgrammer v2's 627 | * --device N - Selects which SPIFlashProgrammer to use 628 | * list - List the available flash on a given device 629 | * erase N - Erases the contents of the given device 630 | * read N file - Reads the contents of the given device into the given file 631 | * write N file - Writes the contents of the given file into the selected device 632 | * verifiedWrite N file - writes the contents of the given file into the 633 | * selected device, verifying the writes as it does. 634 | * sfdp N - Dump the SFDP data for the given device 635 | */ 636 | 637 | const static commandLine::item_t defaultOperation{commandLine::choice_t{"action"sv, "listDevices"sv, {}}}; 638 | 639 | int main(const int argCount, const char *const *const argList) noexcept 640 | { 641 | console = {stdout, stderr}; 642 | if (const auto parsedArgs{parseArguments(argCount, argList, flashprog::programOptions)}; !parsedArgs) 643 | { 644 | console.error("Failed to parse arguments"sv); 645 | return 1; 646 | } 647 | else 648 | args = *parsedArgs; 649 | const auto &version{args.find("version"sv)}; 650 | const auto &help{args.find("help"sv)}; 651 | if (version != args.end() && help != args.end()) 652 | { 653 | console.error("Can only specify one of --help and --version, not both."sv); 654 | return 1; 655 | } 656 | if (version != args.end()) 657 | return flashprog::versionInfo::printVersion(); 658 | if (help != args.end()) 659 | return flashprog::printHelp(); 660 | 661 | const auto *operation{args["action"sv]}; 662 | if (!operation) 663 | operation = &defaultOperation; 664 | 665 | usbContext_t context{}; 666 | if (!context.valid()) 667 | return 2; 668 | 669 | std::vector devices{}; 670 | for (auto device : context.deviceList()) 671 | { 672 | if (device.vid() == 0x1209 && device.pid() == 0xAB0C) 673 | { 674 | console.info("Found programmer at address "sv, device.busNumber(), ':', device.portNumber()); 675 | devices.emplace_back(std::move(device)); 676 | } 677 | } 678 | console.info("Found "sv, devices.size(), " programmers"sv); 679 | 680 | if (devices.size() == 1) 681 | { 682 | const auto &operationArg{std::get(*operation)}; 683 | if (operationArg.value() == "listDevices"sv) 684 | return listDevices(devices[0]); 685 | if (operationArg.value() == "erase"sv) 686 | return eraseDevice(devices[0], operationArg.arguments()); 687 | if (operationArg.value() == "read"sv) 688 | return readDevice(devices[0], operationArg.arguments()); 689 | if (operationArg.value() == "write"sv) 690 | return writeDevice(devices[0], operationArg.arguments(), false); 691 | if (operationArg.value() == "verifiedWrite"sv) 692 | return writeDevice(devices[0], operationArg.arguments(), true); 693 | if (operationArg.value() == "sfdp"sv) 694 | return dumpSFDP(devices[0], operationArg.arguments()); 695 | } 696 | 697 | return 0; 698 | } 699 | -------------------------------------------------------------------------------- /software/include/help.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef HELP_HXX 3 | #define HELP_HXX 4 | 5 | #include 6 | 7 | using namespace std::literals::string_view_literals; 8 | 9 | namespace flashprog 10 | { 11 | constexpr static auto helpString{R"(SPIFlashProgrammer - SPI Flash programming utility 12 | 13 | Usage: 14 | flashprog [options] 15 | flashprog {operation} <[--device N] --chip bus:N file> 16 | 17 | Options: 18 | --version Print the version information for flashprog 19 | -h, --help Prints this help message 20 | 21 | Operations: 22 | listDevices Lists the available SPIFlashProgrammers attached to your system 23 | (This is the default if no operation is given) 24 | list Lists the available Flash chips a given SPIFlashProgrammer can see 25 | read Reads the contents of a specific Flash chip into the requested file 26 | write Writes the contents of the requested file into a specific Flash chip 27 | verifiedWrite Does the same as write, but verifies the contents of the Flash chip after writing 28 | erase Performs a full chip erases on the requested Flash chip 29 | sfdp Reads and dumps the SFDP data from the requested Flash chip 30 | 31 | Options for list, read, write, verifiedWrite, erase and sfdp: 32 | --device The SPIFlashProgrammer to use for the operation 33 | 34 | Options for read, write, verifiedWrite, erase and sfdp: 35 | --chip bus:N Specifies what Flash chip on which bus you want to target. 36 | The chip specification works as follows: 37 | 'bus' can be one of 'int' or 'ext' representing the internal (on-chip) 38 | and external (8-pin Flash connector) SPI busses. 39 | N is a number from 0 to 255 which specifies a detected Flash chip as given by the 40 | listDevices operation 41 | 42 | Options for read, write and verifiedWrite: 43 | file The local file to use for the operation 44 | 45 | This utility is licensed under BSD-3-Clase 46 | Report bugs using https://github.com/bad-alloc-heavy-industries/flashprog/issues)"sv 47 | }; 48 | } // namespace flashprog 49 | 50 | #endif /*HELP_HXX*/ 51 | -------------------------------------------------------------------------------- /software/include/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | config = configuration_data() 3 | config.set('AUTOGEN_HEADER', '/* THIS FILE IS AUTOGENERATED, DO NOT EDIT */') 4 | config.set('VERSION', '@VERSION@') 5 | config.set('COMPILER', hostCXX.get_id()) 6 | config.set('COMPILER_VERSION', hostCXX.version()) 7 | config.set('TARGET_SYS', build_machine.system()) 8 | config.set('TARGET_ARCH', build_machine.cpu()) 9 | 10 | versionHeaderSrc = configure_file( 11 | configuration: config, 12 | input: 'version.hxx.in', 13 | output: 'version.hxx.in' 14 | ) 15 | 16 | versionHeader = vcs_tag( 17 | input: versionHeaderSrc, 18 | output: 'version.hxx', 19 | replace_string: '@VERSION@', 20 | fallback: 'v@0@'.format(meson.project_version()) 21 | ) 22 | -------------------------------------------------------------------------------- /software/include/progress.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef PROGRESS_HXX 3 | #define PROGRESS_HXX 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct progressBar_t final 11 | { 12 | private: 13 | using time_t = typename std::chrono::steady_clock::time_point; 14 | 15 | std::size_t count_{}; 16 | std::optional total_; 17 | std::size_t rows_{}; 18 | std::size_t cols_{}; 19 | std::size_t spinnerStep_{}; 20 | std::string_view prefix_{}; 21 | time_t spinnerLastUpdated_{}; 22 | bool disable{false}; 23 | std::optional startTime_{std::nullopt}; 24 | 25 | public: 26 | progressBar_t(std::string_view prefix, std::optional total = std::nullopt) noexcept; 27 | progressBar_t(const progressBar_t &) = delete; 28 | progressBar_t(progressBar_t &&) = default; 29 | progressBar_t &operator =(const progressBar_t &) = delete; 30 | progressBar_t &operator =(progressBar_t &&) = default; 31 | ~progressBar_t() noexcept { close(); } 32 | 33 | progressBar_t &operator +=(std::size_t amount) noexcept; 34 | progressBar_t &operator ++() noexcept { return *this += 1; } 35 | void update(const std::size_t amount) noexcept { *this += amount; } 36 | void updateWindowSize() noexcept; 37 | void display() noexcept; 38 | void close() noexcept; 39 | }; 40 | 41 | #endif /*PROGRESS_HXX*/ 42 | -------------------------------------------------------------------------------- /software/include/usbContext.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef USB_CONTEXT_HXX 3 | #define USB_CONTEXT_HXX 4 | 5 | // Horrible hack to make libusb conformant and not do stupid things. 6 | // NOLINTNEXTLINE(bugprone-reserved-identifier,cppcoreguidelines-macro-usage,cert-dcl37-c,cert-dcl51-cpp) 7 | #define __STDC_VERSION__ 199901L 8 | 9 | #include 10 | #ifdef __GNUC__ 11 | #pragma GCC diagnostic push 12 | #pragma GCC diagnostic ignored "-Wpedantic" 13 | #endif 14 | #include 15 | #ifdef __GNUC__ 16 | #pragma GCC diagnostic pop 17 | #endif 18 | #include 19 | 20 | #include "usbDeviceList.hxx" 21 | 22 | using namespace std::literals::string_view_literals; 23 | using substrate::console; 24 | 25 | struct usbContext_t final 26 | { 27 | private: 28 | libusb_context *context{nullptr}; 29 | 30 | public: 31 | usbContext_t() noexcept 32 | { 33 | if (auto result = libusb_init(&context); result) 34 | { 35 | console.error("Failed to initialise libusb-1.0: "sv, libusb_error_name(result)); 36 | context = nullptr; 37 | } 38 | } 39 | 40 | usbContext_t(const usbContext_t &) noexcept = delete; 41 | usbContext_t(usbContext_t &&other) noexcept : usbContext_t{} { swap(other); } 42 | usbContext_t &operator =(const usbContext_t &) noexcept = delete; 43 | 44 | ~usbContext_t() noexcept 45 | { 46 | if (context) 47 | libusb_exit(context); 48 | } 49 | 50 | usbContext_t &operator =(usbContext_t &&other) noexcept 51 | { 52 | swap(other); 53 | return *this; 54 | } 55 | 56 | [[nodiscard]] bool valid() const noexcept { return context; } 57 | 58 | [[nodiscard]] usbDeviceList_t deviceList() const noexcept 59 | { 60 | libusb_device **list{nullptr}; 61 | auto result = libusb_get_device_list(context, &list); 62 | if (result < 0) 63 | { 64 | console.error("Failed to get device list: "sv, libusb_error_name(result)); 65 | return {}; 66 | } 67 | return {list, result}; 68 | } 69 | 70 | void swap(usbContext_t &other) noexcept 71 | { std::swap(context, other.context); } 72 | }; 73 | 74 | #endif /*USB_CONSTEXT_HXX*/ 75 | -------------------------------------------------------------------------------- /software/include/usbDevice.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef USB_DEVICE_HXX 3 | #define USB_DEVICE_HXX 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace std::literals::string_view_literals; 11 | using substrate::console; 12 | 13 | enum class endpointDir_t : uint8_t 14 | { 15 | controllerOut = 0x00U, 16 | controllerIn = 0x80U 17 | }; 18 | 19 | constexpr static const uint8_t endpointDirMask{0x7F}; 20 | constexpr inline uint8_t endpointAddress(const endpointDir_t dir, const uint8_t number) noexcept 21 | { return uint8_t(dir) | (number & endpointDirMask); } 22 | 23 | enum class request_t : uint8_t 24 | { 25 | typeStandard = 0x00, 26 | typeClass = 0x20U, 27 | typeVendor = 0x40U 28 | }; 29 | 30 | enum class recipient_t : uint8_t 31 | { 32 | device = 0, 33 | interface = 1, 34 | endpoint = 2, 35 | other = 3 36 | }; 37 | 38 | struct requestType_t final 39 | { 40 | private: 41 | uint8_t value{}; 42 | 43 | public: 44 | constexpr requestType_t() noexcept = default; 45 | constexpr requestType_t(const recipient_t recipient, const request_t type) noexcept : 46 | requestType_t{recipient, type, endpointDir_t::controllerOut} { } 47 | constexpr requestType_t(const recipient_t recipient, const request_t type, const endpointDir_t direction) noexcept : 48 | value(static_cast(recipient) | static_cast(type) | static_cast(direction)) { } 49 | 50 | void recipient(const recipient_t recipient) noexcept 51 | { 52 | value &= 0xE0U; 53 | value |= static_cast(recipient); 54 | } 55 | 56 | void type(const request_t type) noexcept 57 | { 58 | value &= 0x9FU; 59 | value |= static_cast(type); 60 | } 61 | 62 | void dir(const endpointDir_t direction) noexcept 63 | { 64 | value &= 0x7FU; 65 | value |= static_cast(direction); 66 | } 67 | 68 | [[nodiscard]] recipient_t recipient() const noexcept 69 | { return static_cast(value & 0x1FU); } 70 | [[nodiscard]] request_t type() const noexcept 71 | { return static_cast(value & 0x60U); } 72 | [[nodiscard]] endpointDir_t dir() const noexcept 73 | { return static_cast(value & 0x80U); } 74 | [[nodiscard]] operator uint8_t() const noexcept { return value; } 75 | }; 76 | 77 | struct usbDeviceHandle_t final 78 | { 79 | private: 80 | libusb_device_handle *device{nullptr}; 81 | 82 | // NOLINTNEXTLINE(readability-convert-member-functions-to-static) 83 | [[nodiscard]] bool interruptTransfer(const uint8_t endpoint, const void *const bufferPtr, 84 | const int32_t bufferLen) const noexcept 85 | { 86 | // The const-cast here is required becasue libusb is not const-correct. It is UB, but we cannot avoid it. 87 | const auto result 88 | { 89 | libusb_interrupt_transfer(device, endpoint, 90 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) 91 | const_cast(static_cast(bufferPtr)), bufferLen, nullptr, 0) 92 | }; 93 | 94 | if (result) 95 | { 96 | const auto endpointNumber{uint8_t(endpoint & 0x7FU)}; 97 | const auto direction{endpointDir_t(endpoint & 0x80U)}; 98 | console.error("Failed to complete interrupt transfer of "sv, bufferLen, 99 | " byte(s) to endpoint "sv, endpointNumber, ' ', 100 | direction == endpointDir_t::controllerIn ? "IN"sv : "OUT"sv, 101 | ", reason:"sv, libusb_error_name(result)); 102 | } 103 | return !result; 104 | } 105 | 106 | // NOLINTNEXTLINE(readability-convert-member-functions-to-static) 107 | [[nodiscard]] bool bulkTransfer(const uint8_t endpoint, const void *const bufferPtr, 108 | const int32_t bufferLen) const noexcept 109 | { 110 | // The const-cast here is required becasue libusb is not const-correct. It is UB, but we cannot avoid it. 111 | const auto result 112 | { 113 | libusb_bulk_transfer(device, endpoint, 114 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) 115 | const_cast(static_cast(bufferPtr)), bufferLen, nullptr, 0) 116 | }; 117 | if (result) 118 | { 119 | const auto endpointNumber{uint8_t(endpoint & 0x7FU)}; 120 | const auto direction{endpointDir_t(endpoint & 0x80U)}; 121 | console.error("Failed to complete bulk transfer of "sv, bufferLen, 122 | " byte(s) to endpoint "sv, endpointNumber, ' ', 123 | direction == endpointDir_t::controllerIn ? "IN"sv : "OUT"sv, 124 | ", reason:"sv, libusb_error_name(result)); 125 | } 126 | return !result; 127 | } 128 | 129 | // NOLINTNEXTLINE(readability-convert-member-functions-to-static) 130 | [[nodiscard]] bool controlTransfer(const requestType_t requestType, const uint8_t request, const uint16_t value, 131 | const uint16_t index, const void *const bufferPtr, const uint16_t bufferLen) const noexcept 132 | { 133 | // The const-cast here is required becasue libusb is not const-correct. It is UB, but we cannot avoid it. 134 | const auto result 135 | { 136 | libusb_control_transfer(device, requestType, request, value, index, 137 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) 138 | const_cast(static_cast(bufferPtr)), bufferLen, 0) 139 | }; 140 | if (result < 0) 141 | { 142 | console.error("Failed to complete control transfer of "sv, bufferLen, 143 | " bytes(s), reason:"sv, libusb_error_name(result)); 144 | } 145 | else if (result != bufferLen) 146 | { 147 | console.error("Control transfer incomplete, got "sv, result, 148 | ", expected "sv, bufferLen); 149 | } 150 | return result == bufferLen; 151 | } 152 | 153 | public: 154 | usbDeviceHandle_t() noexcept = default; 155 | usbDeviceHandle_t(libusb_device_handle *const device_) noexcept : device{device_} 156 | { autoDetachKernelDriver(true); } 157 | [[nodiscard]] bool valid() const noexcept { return device; } 158 | 159 | // NOLINTNEXTLINE(readability-convert-member-functions-to-static) 160 | void autoDetachKernelDriver(bool autoDetach) const noexcept 161 | { 162 | if (const auto result{libusb_set_auto_detach_kernel_driver(device, autoDetach)}; result) 163 | console.warning("Automatic detach of kernel driver not supported on this platform"sv); 164 | } 165 | 166 | // NOLINTNEXTLINE(readability-convert-member-functions-to-static) 167 | [[nodiscard]] bool claimInterface(const int32_t interfaceNumber) const noexcept 168 | { 169 | const auto result{libusb_claim_interface(device, interfaceNumber)}; 170 | if (result) 171 | console.error("Failed to claim interface "sv, interfaceNumber, ": "sv, libusb_error_name(result)); 172 | return !result; 173 | } 174 | 175 | // NOLINTNEXTLINE(readability-convert-member-functions-to-static) 176 | [[nodiscard]] bool releaseInterface(const int32_t interfaceNumber) const noexcept 177 | { 178 | const auto result{libusb_release_interface(device, interfaceNumber)}; 179 | if (result) 180 | console.error("Failed to release interface "sv, interfaceNumber, ": "sv, libusb_error_name(result)); 181 | return !result; 182 | } 183 | 184 | [[nodiscard]] bool writeInterrupt(const uint8_t endpoint, const void *const bufferPtr, const int32_t bufferLen) const noexcept 185 | { return interruptTransfer(endpointAddress(endpointDir_t::controllerOut, endpoint), bufferPtr, bufferLen); } 186 | 187 | [[nodiscard]] bool readInterrupt(const uint8_t endpoint, void *const bufferPtr, const int32_t bufferLen) const noexcept 188 | { return interruptTransfer(endpointAddress(endpointDir_t::controllerIn, endpoint), bufferPtr, bufferLen); } 189 | 190 | [[nodiscard]] bool writeBulk(const uint8_t endpoint, const void *const bufferPtr, const int32_t bufferLen) const noexcept 191 | { return bulkTransfer(endpointAddress(endpointDir_t::controllerOut, endpoint), bufferPtr, bufferLen); } 192 | 193 | [[nodiscard]] bool readBulk(const uint8_t endpoint, void *const bufferPtr, const int32_t bufferLen) const noexcept 194 | { return bulkTransfer(endpointAddress(endpointDir_t::controllerIn, endpoint), bufferPtr, bufferLen); } 195 | 196 | template bool writeControl(requestType_t requestType, const uint8_t request, 197 | const uint16_t value, const uint16_t index, const T &data) const noexcept 198 | { 199 | requestType.dir(endpointDir_t::controllerOut); 200 | static_assert(sizeof(T) <= UINT16_MAX); 201 | return controlTransfer(requestType, request, value, index, &data, sizeof(T)); 202 | } 203 | 204 | [[nodiscard]] bool writeControl(requestType_t requestType, const uint8_t request, 205 | const uint16_t value, const uint16_t index, std::nullptr_t) const noexcept 206 | { 207 | requestType.dir(endpointDir_t::controllerOut); 208 | return controlTransfer(requestType, request, value, index, nullptr, 0); 209 | } 210 | 211 | template bool readControl(requestType_t requestType, const uint8_t request, 212 | const uint16_t value, const uint16_t index, T &data) const noexcept 213 | { 214 | requestType.dir(endpointDir_t::controllerIn); 215 | static_assert(sizeof(T) <= UINT16_MAX); 216 | return controlTransfer(requestType, request, value, index, &data, sizeof(T)); 217 | } 218 | 219 | template bool readControl(requestType_t requestType, const uint8_t request, 220 | const uint16_t value, const uint16_t index, std::array &data) const noexcept 221 | { 222 | requestType.dir(endpointDir_t::controllerIn); 223 | static_assert(length * sizeof(T) <= UINT16_MAX); 224 | return controlTransfer(requestType, request, value, index, data.data(), length * sizeof(T)); 225 | } 226 | }; 227 | 228 | struct usbDevice_t final 229 | { 230 | private: 231 | libusb_device *device{nullptr}; 232 | libusb_device_descriptor descriptor{}; 233 | 234 | usbDevice_t() noexcept = default; 235 | 236 | public: 237 | usbDevice_t(libusb_device *const device_) noexcept : device{device_} 238 | { 239 | libusb_ref_device(device); 240 | if (const auto result{libusb_get_device_descriptor(device, &descriptor)}; result) 241 | { 242 | console.warning("Failed to get descriptor for device: ", libusb_error_name(result)); 243 | descriptor = {}; 244 | } 245 | } 246 | 247 | usbDevice_t(const usbDevice_t &) noexcept = delete; 248 | usbDevice_t(usbDevice_t &&other) noexcept : usbDevice_t{} { swap(other); } 249 | usbDevice_t &operator =(const usbDevice_t &) noexcept = delete; 250 | 251 | // NOLINTNEXTLINE(modernize-use-equals-default) 252 | ~usbDevice_t() noexcept 253 | { 254 | if (device) 255 | libusb_unref_device(device); 256 | } 257 | 258 | usbDevice_t &operator =(usbDevice_t &&other) noexcept 259 | { 260 | swap(other); 261 | return *this; 262 | } 263 | 264 | [[nodiscard]] auto vid() const noexcept { return descriptor.idVendor; } 265 | [[nodiscard]] auto pid() const noexcept { return descriptor.idProduct; } 266 | 267 | [[nodiscard]] auto busNumber() const noexcept { return libusb_get_bus_number(device); } 268 | [[nodiscard]] auto portNumber() const noexcept { return libusb_get_port_number(device); } 269 | 270 | // NOLINTNEXTLINE(readability-convert-member-functions-to-static) 271 | [[nodiscard]] usbDeviceHandle_t open() const noexcept 272 | { 273 | libusb_device_handle *handle{nullptr}; 274 | if (const auto result{libusb_open(device, &handle)}; result) 275 | { 276 | console.error("Failed to open requested device: "sv, libusb_error_name(result)); 277 | return {}; 278 | } 279 | return {handle}; 280 | } 281 | 282 | void swap(usbDevice_t &other) noexcept 283 | { 284 | std::swap(device, other.device); 285 | std::swap(descriptor, other.descriptor); 286 | } 287 | }; 288 | 289 | #endif /*USB_DEVICE_HXX*/ 290 | -------------------------------------------------------------------------------- /software/include/usbDeviceList.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef USB_DEVICE_LIST_HXX 3 | #define USB_DEVICE_LIST_HXX 4 | 5 | #include 6 | #include 7 | #include 8 | #include "usbDevice.hxx" 9 | 10 | using namespace std::literals::string_view_literals; 11 | using substrate::console; 12 | 13 | struct usbDeviceIter_t final 14 | { 15 | private: 16 | libusb_device **current{nullptr}; 17 | 18 | public: 19 | usbDeviceIter_t(libusb_device **current_) noexcept : current{current_} { } 20 | usbDevice_t operator *() const noexcept { return {*current}; } 21 | 22 | auto operator ++() noexcept 23 | { 24 | ++current; 25 | return *this; 26 | } 27 | 28 | bool operator ==(const usbDeviceIter_t &other) const noexcept 29 | { return current == other.current; } 30 | bool operator !=(const usbDeviceIter_t &other) const noexcept 31 | { return current != other.current; } 32 | }; 33 | 34 | struct usbDeviceList_t final 35 | { 36 | private: 37 | libusb_device **deviceList{nullptr}; 38 | size_t count{0}; 39 | 40 | public: 41 | usbDeviceList_t() noexcept = default; 42 | usbDeviceList_t(libusb_device **const deviceList_, ssize_t count_) noexcept : 43 | deviceList{deviceList_}, count{static_cast(count_)} { } 44 | 45 | usbDeviceList_t(const usbDeviceList_t &) noexcept = delete; 46 | usbDeviceList_t(usbDeviceList_t &&other) noexcept : usbDeviceList_t{} { swap(other); } 47 | usbDeviceList_t &operator =(const usbDeviceList_t &) noexcept = delete; 48 | 49 | // NOLINTNEXTLINE(modernize-use-equals-default) 50 | ~usbDeviceList_t() noexcept 51 | { 52 | if (count) 53 | libusb_free_device_list(deviceList, false); 54 | } 55 | 56 | usbDeviceList_t &operator =(usbDeviceList_t &&other) noexcept 57 | { 58 | swap(other); 59 | return *this; 60 | } 61 | 62 | [[nodiscard]] usbDeviceIter_t begin() const noexcept { return {deviceList}; } 63 | [[nodiscard]] usbDeviceIter_t end() const noexcept { return {deviceList + count}; } 64 | 65 | void swap(usbDeviceList_t &other) noexcept 66 | { 67 | std::swap(deviceList, other.deviceList); 68 | std::swap(count, other.count); 69 | } 70 | }; 71 | 72 | #endif /*USB_DEVICE_LIST_HXX*/ 73 | -------------------------------------------------------------------------------- /software/include/version.hxx.in: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // version.hxx - program version information 3 | @AUTOGEN_HEADER@ 4 | #ifndef VERSION__HXX 5 | #define VERSION__HXX 6 | 7 | #include 8 | #include 9 | 10 | using namespace std::literals::string_view_literals; 11 | 12 | namespace flashprog::versionInfo 13 | { 14 | constexpr static auto version{"@VERSION@"sv}; 15 | constexpr static auto compiler{"@COMPILER@"sv}; 16 | constexpr static auto compilerVersion{"@COMPILER_VERSION@"sv}; 17 | constexpr static auto system{"@TARGET_SYS@"sv}; 18 | constexpr static auto arch{"@TARGET_ARCH@"sv}; 19 | 20 | inline int32_t printVersion() noexcept 21 | { 22 | substrate::console.info("flashprog "sv, version, " ("sv, compiler, ' ', 23 | compilerVersion, ' ', system, '-', arch, ')'); 24 | return 0; 25 | } 26 | } // namespace flashprog::versionInfo 27 | 28 | #endif /*VERSION__HXX*/ 29 | -------------------------------------------------------------------------------- /software/meson.build: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: BSD-3-Clause 2 | 3 | substrate = subproject( 4 | 'substrate', 5 | default_options: [ 6 | 'build_tests=false', 7 | 'cpp_std=c++17', 8 | ] 9 | ).get_variable('substrate_native_dep') 10 | 11 | fmt = subproject( 12 | 'fmt', 13 | default_options: [ 14 | 'default_library=static', 15 | ], 16 | ).get_variable('fmt_dep') 17 | 18 | libusb = dependency('libusb-1.0', version: '>=1.0.21', native: true) 19 | 20 | subdir('include') 21 | 22 | flashprogSrc = [ 23 | 'flashprog.cxx', 'sfdp.cxx', 'progress.cxx', versionHeader 24 | ] 25 | 26 | executable( 27 | 'flashprog', 28 | flashprogSrc, 29 | include_directories: [include_directories('include'), commonInclude], 30 | dependencies: [libusb, substrate, fmt], 31 | gnu_symbol_visibility: 'inlineshidden', 32 | build_by_default: true, 33 | install: false, 34 | native: true 35 | ) 36 | -------------------------------------------------------------------------------- /software/options.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #include 3 | #include 4 | #include "usbProtocol.hxx" 5 | 6 | namespace flashprog 7 | { 8 | using namespace std::literals::string_view_literals; 9 | using namespace substrate::commandLine; 10 | 11 | using chipBus_t = flashProto::flashBus_t; 12 | 13 | struct chip_t 14 | { 15 | chipBus_t bus{chipBus_t::unknown}; 16 | uint8_t index{UINT8_MAX}; 17 | }; 18 | 19 | static inline std::optional chipSelectionParser(const std::string_view &value) noexcept 20 | { 21 | // Check if we have enough characters to form a valid chip selector 22 | if (value.length() < 5) 23 | return std::nullopt; 24 | // If we do, check that the start of the string is one of "int" or "ext" 25 | const auto bus{value.substr(0, 3)}; 26 | if (bus != "int"sv && bus != "ext"sv) 27 | return std::nullopt; 28 | // Check that the 4th character is a ':' 29 | if (value[3] != ':') 30 | return std::nullopt; 31 | // Now convert the final part as an unsigned integer 32 | substrate::toInt_t index{value.substr(4).data()}; 33 | if (!index.isDec()) 34 | return std::nullopt; 35 | const auto number{index.fromDec()}; 36 | // And check that it's in the range [0,256) 37 | if (number > 255U) 38 | return std::nullopt; 39 | // Construct and return the chip selection state 40 | return chip_t{bus[0] == 'i' ? chipBus_t::internal : chipBus_t::external, static_cast(number)}; 41 | } 42 | 43 | constexpr static auto deviceOption 44 | { 45 | option_t 46 | { 47 | "--device"sv, 48 | "The SPIFlashProgrammer to use for the operation"sv, 49 | }.takesParameter(optionValueType_t::unsignedInt) 50 | }; 51 | 52 | constexpr static auto deviceOptions 53 | { 54 | options 55 | ( 56 | deviceOption, 57 | option_t 58 | { 59 | "--chip"sv, 60 | "Specifies what Flash chip on which bus you want to target.\n" 61 | "The chip specification works as follows:\n" 62 | "'bus' can be one of 'int' or 'ext' representing the internal (on-chip)\n" 63 | "and external (8-pin Flash connector) SPI busses.\n" 64 | "N is a number from 0 to 255 which specifies a detected Flash chip as given by the\n" 65 | "listDevices operation"sv, 66 | }.takesParameter(optionValueType_t::userDefined, chipSelectionParser).required() 67 | ) 68 | }; 69 | 70 | constexpr static auto fileOptions 71 | { 72 | options 73 | ( 74 | deviceOptions, 75 | option_t{optionValue_t{"file"sv}, "The local file to use for the operation"sv} 76 | .valueType(optionValueType_t::path).required() 77 | ) 78 | }; 79 | 80 | constexpr static auto listOptions{options(deviceOption)}; 81 | 82 | constexpr static auto actions 83 | { 84 | optionAlternations 85 | ({ 86 | { 87 | "listDevices"sv, 88 | "Lists the available SPIFlashProgrammers attached to your system\n" 89 | "(This is the default if no operation is given)"sv, 90 | }, 91 | { 92 | "list"sv, 93 | "Lists the available Flash chips a given SPIFlashProgrammer can see"sv, 94 | listOptions, 95 | }, 96 | { 97 | "read"sv, 98 | "Reads the contents of a specific Flash chip into the requested file"sv, 99 | fileOptions, 100 | }, 101 | { 102 | "write"sv, 103 | "Writes the contents of the requested file into a specific Flash chip"sv, 104 | fileOptions, 105 | }, 106 | { 107 | "verifiedWrite"sv, 108 | "Does the same as write, but verifies the contents of the Flash chip after writing"sv, 109 | fileOptions, 110 | }, 111 | { 112 | "erase"sv, 113 | "Performs a full chip erases on the requested Flash chip"sv, 114 | deviceOptions, 115 | }, 116 | { 117 | "sfdp"sv, 118 | "Read and display the SFDP (Serial Flash Discoverable Parameters) data for a specific Flash chip"sv, 119 | deviceOptions, 120 | }, 121 | }) 122 | }; 123 | 124 | constexpr static auto programOptions 125 | { 126 | options 127 | ( 128 | option_t{optionFlagPair_t{"-h"sv, "--help"sv}, "Display this help message and exit"sv}, 129 | option_t{"--version"sv, "Display the version information for flashprog and exit"sv}, 130 | optionSet_t{"action"sv, actions} 131 | ) 132 | }; 133 | } // namespace flashprog 134 | -------------------------------------------------------------------------------- /software/progress.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "progress.hxx" 13 | 14 | using substrate::console; 15 | using namespace std::literals::string_view_literals; 16 | using namespace std::literals::chrono_literals; 17 | 18 | // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 19 | progressBar_t *currentProgressBar{nullptr}; 20 | 21 | constexpr static auto prefixSeperator{": "sv}; 22 | constexpr static auto percentageSeperator{"% |"sv}; 23 | constexpr static auto endSeperator{"|"sv}; 24 | 25 | #define STYLE1 26 | 27 | constexpr static auto spinner{substrate::make_array( 28 | { 29 | #if defined(STYLE1) 30 | u8"\u283B"sv, 31 | u8"\u283D"sv, 32 | u8"\u283E"sv, 33 | u8"\u2837"sv, 34 | u8"\u282F"sv, 35 | u8"\u281F"sv 36 | #elif defined(STYLE2) 37 | u8"\u2599"sv, 38 | u8"\u259b"sv, 39 | u8"\u259c"sv, 40 | u8"\u259f"sv 41 | #elif defined(STYLE3) 42 | u8"\u25DC"sv, 43 | u8"\u25DD"sv, 44 | u8"\u25DE"sv, 45 | u8"\u25DF"sv 46 | #elif defined(STYLE4) 47 | u8"\u2500"sv, 48 | u8"\u2572"sv, 49 | u8"\u2502"sv, 50 | u8"\u2571"sv, 51 | #else 52 | #error "Must define a valid spinner style" 53 | #endif 54 | })}; 55 | 56 | constexpr static std::array percentageUnknown{"---"}; 57 | 58 | constexpr static auto spinnerTimestep{75ms}; 59 | 60 | // Handler for console window size changes 61 | void sigwinchHandler(const int32_t) noexcept 62 | { 63 | if (currentProgressBar) 64 | currentProgressBar->updateWindowSize(); 65 | } 66 | 67 | // Construct a new progress bar with a descriptive string prefix and optionally some 68 | // total amount of progress to count up to 69 | progressBar_t::progressBar_t(std::string_view prefix, std::optional total) noexcept : 70 | total_{total}, prefix_{prefix} 71 | { 72 | struct sigaction action{}; 73 | action.sa_flags = SA_RESTART; 74 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) 75 | action.sa_handler = sigwinchHandler; 76 | sigaction(SIGWINCH, &action, nullptr); 77 | updateWindowSize(); 78 | currentProgressBar = this; 79 | // If we do not have a valid total, capture the construction time as when the progress indication was started 80 | if (!total_) 81 | startTime_ = std::make_optional(std::chrono::steady_clock::now()); 82 | } 83 | 84 | void progressBar_t::updateWindowSize() noexcept 85 | { 86 | // Grab the new console width/height if possible 87 | winsize result{}; 88 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) 89 | if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &result) < 0) 90 | { 91 | // If we cannot, read them from the COLUMNS and LINES environment variables 92 | const auto *const columns{getenv("COLUMNS")}; 93 | const auto *const rows{getenv("LINES")}; 94 | // Assuming we got valid strings (nullptr indicates they weren't found in our environment block) 95 | if (columns && rows) 96 | { 97 | // Convert them to numbers safely 98 | rows_ = substrate::toInt_t{rows}; 99 | cols_ = substrate::toInt_t{columns} - 7; 100 | } 101 | } 102 | else 103 | { 104 | // We got the console size with the ioctl 105 | rows_ = result.ws_row; 106 | cols_ = result.ws_col - 7; 107 | } 108 | } 109 | 110 | // Update the progress indication by some amount and redisplay the bar 111 | progressBar_t &progressBar_t::operator +=(std::size_t amount) noexcept 112 | { 113 | count_ += amount; 114 | display(); 115 | return *this; 116 | } 117 | 118 | // Helper for building the actual progress bar graphic in the console 119 | struct bar_t final 120 | { 121 | private: 122 | float fraction; 123 | std::size_t length; 124 | 125 | // This is a series of Unicode characters that provide an extremely smooth bar 126 | constexpr static auto charset 127 | { 128 | substrate::make_array( 129 | { 130 | u8" "sv, 131 | u8"\u258F"sv, 132 | u8"\u258E"sv, 133 | u8"\u258D"sv, 134 | u8"\u258C"sv, 135 | u8"\u258B"sv, 136 | u8"\u258A"sv, 137 | u8"\u2589"sv, 138 | u8"\u2588"sv 139 | }) 140 | }; 141 | 142 | public: 143 | bar_t(const float frac, const std::size_t cols) : fraction{frac}, length{cols} { } 144 | 145 | // Convert the bar state into the progress bar string 146 | operator std::string() const noexcept 147 | { 148 | const auto charsetSyms{charset.size() - 1U}; 149 | // Figure out how many full blocks to display and how wide the fractional block is 150 | const auto [barLength, fractionalBarIndex] = 151 | std::div(int64_t(fraction * static_cast(length * charsetSyms)), charsetSyms); 152 | std::string result{}; 153 | // Fill the full block component of the string with the full block character 154 | //std::string result{charset.back(), 0U, std::size_t(barLength)}; 155 | for (std::size_t i{}; i < std::size_t(barLength); ++i) 156 | result += charset.back(); 157 | if (std::size_t(barLength) < length) 158 | { 159 | // Now if there's space to go, deal with the fractional component 160 | result += charset[fractionalBarIndex]; 161 | // And fill the remaining space with the empty (space) block character 162 | //result += std::string{charset[0], 0U, std::size_t{length - barLength - 1U}}; 163 | for (std::size_t i{}; i < std::size_t{length - barLength - 1U}; ++i) 164 | result += charset[0]; 165 | } 166 | return result; 167 | } 168 | }; 169 | 170 | void progressBar_t::display() noexcept 171 | { 172 | // Turn the progress indication into fraction and reserve space for the stringification of that fraction 173 | const auto frac{float(count_) / static_cast(total_ ? *total_ : 1)}; 174 | std::array percentageBuffer{}; 175 | if (total_) 176 | { 177 | fmt::format_to_n(percentageBuffer.begin(), percentageBuffer.size(), "{:>3.0f}"sv, frac * 100); 178 | // We only have to NUL terminate because format_to_n doesn't guarantee to. 179 | percentageBuffer[percentageBuffer.size() - 1] = 0; 180 | } 181 | else 182 | percentageBuffer = percentageUnknown; 183 | // Having built the progress indication, turn it into a string_view for use in the string handling below 184 | const std::string_view percentage{percentageBuffer.data(), percentageBuffer.size()}; 185 | 186 | // Compute how long the progress bar needs to be in characters 187 | const auto barLength{prefix_.size() + prefixSeperator.size() + percentage.size() + 188 | percentageSeperator.size() + endSeperator.size() + 1U}; 189 | // Create a suitable progress bar to display 190 | std::string bar( 191 | total_ ? 192 | bar_t{frac, std::max(1U, cols_ - barLength)} : 193 | bar_t{0.f, std::max(1U, cols_ - barLength)} 194 | ); 195 | 196 | // Determine the current end-of-bar spinner character and update the index based on the the time since last step 197 | const auto spinnerChar{spinner[spinnerStep_]}; 198 | const auto now{std::chrono::steady_clock::now()}; 199 | if (now - spinnerLastUpdated_ >= spinnerTimestep) 200 | { 201 | spinnerStep_ = (spinnerStep_ + 1) % spinner.size(); 202 | spinnerLastUpdated_ = now; 203 | } 204 | 205 | // If the total progress target is unknown (and the start time stored as a result is valid) 206 | if (!total_ && startTime_) 207 | { 208 | // Figure out how long has elapsed since we started 209 | const auto value{std::chrono::duration_cast(now - *startTime_).count()}; 210 | const auto minsValue{value / 60U}; 211 | const auto secsValue{value % 60U}; 212 | // Convert the resulting number of minutes and seconds to strings 213 | const substrate::fromInt_t mins{minsValue}; 214 | const substrate::fromInt_t secs{secsValue}; 215 | const auto totalDigits{mins.digits() + secs.digits() + 3U}; 216 | 217 | // If there's sufficient space to display the result, format it out 218 | if (totalDigits < barLength) 219 | { 220 | size_t offset{(cols_ - barLength - totalDigits) / 2U}; 221 | // First deal with inserting the minutes count in the progress bar space 222 | mins.formatTo(bar.data() + offset); 223 | offset += mins.digits(); 224 | bar[offset] = 'm'; 225 | offset += 2U; 226 | // Then the seconds count 227 | secs.formatTo(bar.data() + offset); 228 | offset += secs.digits(); 229 | bar[offset] = 's'; 230 | } 231 | } 232 | 233 | // Zip the cursor back to the start of the line and display the new progress bar 234 | console.writeln('\r', nullptr); 235 | console.info(prefix_, prefixSeperator, percentage, percentageSeperator, bar, 236 | endSeperator, spinnerChar, ' ', nullptr); 237 | } 238 | 239 | void progressBar_t::close() noexcept 240 | { 241 | if (disable) 242 | return; 243 | disable = true; 244 | console.writeln(); 245 | currentProgressBar = nullptr; 246 | } 247 | -------------------------------------------------------------------------------- /software/sfdp.cxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "sfdp.hxx" 7 | #include "sfdpInternal.hxx" 8 | #include "usbProtocol.hxx" 9 | #include "utils/units.hxx" 10 | 11 | using namespace std::literals::string_view_literals; 12 | using substrate::console; 13 | using substrate::asHex_t; 14 | using substrate::indexSequence_t; 15 | using substrate::indexedIterator_t; 16 | using namespace flashProto; 17 | using flashprog::utils::humanReadableSize; 18 | 19 | namespace sfdp 20 | { 21 | constexpr static uint32_t sfdpHeaderAddress{0U}; 22 | constexpr static uint32_t tableHeaderAddress{sizeof(sfdpHeader_t)}; 23 | 24 | constexpr static std::array sfdpMagic{{'S', 'F', 'D', 'P'}}; 25 | constexpr static uint16_t basicSPIParameterTable{0xFF00U}; 26 | 27 | [[nodiscard]] static bool sfdpRead(const usbDeviceHandle_t &device, const usbDataSource_t &dataSource, 28 | const uint32_t address, void *const buffer, const size_t bufferLen) 29 | { 30 | return 31 | requests::sfdp_t{}.write(device, dataSource.interface, bufferLen, address) && 32 | device.readBulk(dataSource.endpoint, buffer, bufferLen); 33 | } 34 | 35 | template [[nodiscard]] static bool sfdpRead(const usbDeviceHandle_t &device, 36 | const usbDataSource_t &dataSource, const uint32_t address, T &buffer) 37 | { return sfdpRead(device, dataSource, address, &buffer, sizeof(T)); } 38 | 39 | static void displayHeader(const sfdpHeader_t &header) 40 | { 41 | console.info("SFDP Header:"sv); 42 | console.info("-> magic '"sv, header.magic, "'"sv); 43 | console.info("-> version "sv, header.versionMajor, '.', header.versionMinor); 44 | console.info("-> "sv, header.parameterHeadersCount(), " parameter headers"sv); 45 | console.info("-> access protocol "sv, asHex_t<2, '0'>{uint8_t(header.accessProtocol)}); 46 | } 47 | 48 | static void displayTableHeader(const parameterTableHeader_t &header, const size_t index) 49 | { 50 | console.info("Parameter table header "sv, index, ":"sv); 51 | console.info("-> type "sv, asHex_t<4, '0'>{header.jedecParameterID()}); 52 | console.info("-> version "sv, header.versionMajor, '.', header.versionMinor); 53 | console.info("-> table is "sv, header.tableLength(), " bytes long"sv); 54 | console.info("-> table SFDP address: "sv, uint32_t{header.tableAddress}); 55 | } 56 | 57 | static bool displayBasicParameterTable(const usbDeviceHandle_t &device, const usbDataSource_t &dataSource, 58 | const uint32_t address, const size_t length) 59 | { 60 | basicParameterTable_t parameterTable{}; 61 | if (!sfdpRead(device, dataSource, address, ¶meterTable, std::min(sizeof(basicParameterTable_t), length))) 62 | return false; 63 | 64 | console.info("Basic parameter table:"); 65 | const auto [capacityValue, capacityUnits] = humanReadableSize(parameterTable.flashMemoryDensity.capacity()); 66 | console.info("-> capacity "sv, capacityValue, capacityUnits); 67 | console.info("-> program page size: "sv, parameterTable.programmingAndChipEraseTiming.pageSize()); 68 | console.info("-> sector erase opcode: "sv, asHex_t<2, '0'>(parameterTable.sectorEraseOpcode)); 69 | console.info("-> supported erase types:"sv); 70 | for (const auto &[idx, eraseType] : indexedIterator_t{parameterTable.eraseTypes}) 71 | { 72 | console.info("\t-> "sv, idx + 1U, ": "sv, nullptr); 73 | if (eraseType.eraseSizeExponent != 0U) 74 | { 75 | const auto [sizeValue, sizeUnits] = humanReadableSize(eraseType.eraseSize()); 76 | console.writeln("opcode "sv, asHex_t<2, '0'>(eraseType.opcode), ", erase size: "sv, 77 | sizeValue, sizeUnits); 78 | } 79 | else 80 | console.writeln("invalid erase type"sv); 81 | } 82 | console.info("-> power down opcode: "sv, asHex_t<2, '0'>(parameterTable.deepPowerdown.enterInstruction())); 83 | console.info("-> wake up opcode: "sv, asHex_t<2, '0'>(parameterTable.deepPowerdown.exitInstruction())); 84 | return true; 85 | } 86 | 87 | bool readAndDisplay(const usbDeviceHandle_t &device, const usbDataSource_t dataSource) 88 | { 89 | console.info("Reading SFDP data for device"sv); 90 | sfdpHeader_t header{}; 91 | if (!sfdpRead(device, dataSource, sfdpHeaderAddress, header)) 92 | return false; 93 | if (header.magic != sfdpMagic) 94 | { 95 | console.error("Device does not have a valid SFDP block"sv); 96 | console.error(" -> Read signature '"sv, header.magic, "'"); 97 | console.error(" -> Expected signature '"sv, sfdpMagic, "'"); 98 | return true; 99 | } 100 | displayHeader(header); 101 | 102 | for (const auto idx : indexSequence_t{header.parameterHeadersCount()}) 103 | { 104 | parameterTableHeader_t tableHeader{}; 105 | if (!sfdpRead(device, dataSource, tableHeaderAddress + (sizeof(parameterTableHeader_t) * idx), tableHeader)) 106 | return false; 107 | displayTableHeader(tableHeader, idx + 1U); 108 | if (tableHeader.jedecParameterID() == basicSPIParameterTable) 109 | { 110 | if (!displayBasicParameterTable(device, dataSource, tableHeader.tableAddress, tableHeader.tableLength())) 111 | return false; 112 | } 113 | } 114 | return true; 115 | } 116 | } // namespace sfdp 117 | -------------------------------------------------------------------------------- /software/sfdp.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef SFDP_HXX 3 | #define SFDP_HXX 4 | 5 | #include 6 | #include "usbContext.hxx" 7 | 8 | namespace sfdp 9 | { 10 | struct usbDataSource_t 11 | { 12 | uint8_t interface; 13 | uint8_t endpoint; 14 | }; 15 | 16 | bool readAndDisplay(const usbDeviceHandle_t &device, usbDataSource_t dataSource); 17 | } // namespace sfdp 18 | 19 | #endif /*SFDP_HXX*/ 20 | -------------------------------------------------------------------------------- /software/sfdpInternal.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef SFDP_INTERNAL_HXX 3 | #define SFDP_INTERNAL_HXX 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace sfdp 10 | { 11 | enum struct accessProtocol_t : uint8_t 12 | { 13 | xspiNANDClass1 = 0xF0U, 14 | xspiNANDClass2 = 0xF1U, 15 | xspiNANDClass3WithClass1NVM = 0xF2U, 16 | xspiNANDClass3WithClass2NVM = 0xF3U, 17 | spiNANDClass1 = 0xF4U, 18 | spiNANDClass2 = 0xF5U, 19 | spiNANDClass3withClass1NVM = 0xF6U, 20 | spiNANDClass3withClass2NVM = 0xF7U, 21 | xspiNORProfile2 = 0xFAU, 22 | xspiNORProfile1with3ByteAddr = 0xFCU, 23 | xspiNORProfile1with4ByteAddr20Wait = 0xFDU, 24 | xspiNORProfile1with4ByteAddr8Wait = 0xFEU, 25 | legacyJESD216B = 0xFFU 26 | }; 27 | 28 | struct uint24_t 29 | { 30 | private: 31 | std::array value{}; 32 | 33 | public: 34 | operator uint32_t() const noexcept 35 | { return static_cast((value[2] << 16U) | (value[1] << 8U) | value[0]); } 36 | }; 37 | 38 | struct sfdpHeader_t 39 | { 40 | std::array magic{}; 41 | uint8_t versionMajor{}; 42 | uint8_t versionMinor{}; 43 | uint8_t rawParameterHeadersCount{}; 44 | accessProtocol_t accessProtocol{accessProtocol_t::legacyJESD216B}; 45 | 46 | [[nodiscard]] size_t parameterHeadersCount() const noexcept { return rawParameterHeadersCount + 1U; } 47 | }; 48 | 49 | struct parameterTableHeader_t 50 | { 51 | uint8_t jedecParameterIDLow{}; 52 | uint8_t versionMajor{}; 53 | uint8_t versionMinor{}; 54 | uint8_t tableLengthInU32s{}; 55 | uint24_t tableAddress{}; 56 | uint8_t jedecParameterIDHigh{}; 57 | 58 | [[nodiscard]] uint16_t jedecParameterID() const noexcept 59 | { return static_cast((jedecParameterIDHigh << 8U) | jedecParameterIDLow); } 60 | [[nodiscard]] size_t tableLength() const noexcept { return static_cast(tableLengthInU32s) * 4U; } 61 | }; 62 | 63 | struct memoryDensity_t 64 | { 65 | std::array data{}; 66 | 67 | private: 68 | [[nodiscard]] bool isExponential() const noexcept { return data[3] & 0x80U; } 69 | [[nodiscard]] uint32_t value() const noexcept 70 | { return ((data[3] & 0x7FU) << 24U) | (data[2] << 16U) | (data[1] << 8U) | data[0]; } 71 | 72 | public: 73 | [[nodiscard]] size_t capacity() const noexcept 74 | { 75 | const auto bits 76 | { 77 | [=]() -> size_t 78 | { 79 | if (isExponential()) 80 | return 1U << value(); 81 | else 82 | return value() + 1U; 83 | }() 84 | }; 85 | return bits / 8U; 86 | } 87 | }; 88 | 89 | struct [[gnu::packed]] timingsAndOpcode_t 90 | { 91 | uint8_t timings{}; 92 | uint8_t opcode{}; 93 | }; 94 | 95 | struct [[gnu::packed]] eraseParameters_t 96 | { 97 | uint8_t eraseSizeExponent{}; 98 | uint8_t opcode{}; 99 | 100 | [[nodiscard]] size_t eraseSize() const noexcept { return 1U << eraseSizeExponent; } 101 | }; 102 | 103 | struct programmingAndChipEraseTiming_t 104 | { 105 | uint8_t programmingTimingRatioAndPageSize{}; 106 | std::array eraseTimings; 107 | 108 | [[nodiscard]] size_t pageSize() const noexcept 109 | { 110 | const uint8_t pageSizeExponent = programmingTimingRatioAndPageSize >> 4U; 111 | return 1U << pageSizeExponent; 112 | } 113 | }; 114 | 115 | struct deepPowerDown_t 116 | { 117 | std::array data{}; 118 | 119 | [[nodiscard]] uint8_t enterInstruction() const noexcept 120 | { 121 | const auto value{(uint16_t{data[2]} << 8U) | uint16_t{data[1]}}; 122 | return value >> 7U; 123 | } 124 | 125 | [[nodiscard]] uint8_t exitInstruction() const noexcept 126 | { 127 | const auto value{(uint16_t{data[1]} << 8U) | uint16_t{data[0]}}; 128 | return value >> 7U; 129 | } 130 | }; 131 | 132 | struct basicParameterTable_t 133 | { 134 | uint8_t value1{}; 135 | uint8_t sectorEraseOpcode{}; 136 | uint8_t value2{}; 137 | uint8_t reserved1{}; 138 | memoryDensity_t flashMemoryDensity{}; 139 | timingsAndOpcode_t fastQuadIO{}; 140 | timingsAndOpcode_t fastQuadOutput{}; 141 | timingsAndOpcode_t fastDualOutput{}; 142 | timingsAndOpcode_t fastDualIO{}; 143 | uint8_t fastSupportFlags{}; 144 | std::array reserved2{}; 145 | timingsAndOpcode_t fastDualDPI{}; 146 | std::array reserved3{}; 147 | timingsAndOpcode_t fastQuadQPI{}; 148 | std::array eraseTypes{}; 149 | uint32_t eraseTiming{}; 150 | programmingAndChipEraseTiming_t programmingAndChipEraseTiming{}; 151 | uint8_t operationalProhibitions{}; 152 | std::array suspendLatencySpecs{}; 153 | uint8_t programResumeOpcode{}; 154 | uint8_t programSuspendOpcode{}; 155 | uint8_t resumeOpcode{}; 156 | uint8_t suspendOpcode{}; 157 | uint8_t statusRegisterPollingFlags{}; 158 | deepPowerDown_t deepPowerdown{}; 159 | std::array dualAndQuadMode{}; 160 | uint8_t reserved4{}; 161 | uint32_t statusAndAddressingMode{}; 162 | }; 163 | 164 | static_assert(sizeof(uint24_t) == 3); 165 | static_assert(sizeof(sfdpHeader_t) == 8); 166 | static_assert(sizeof(parameterTableHeader_t) == 8); 167 | static_assert(sizeof(memoryDensity_t) == 4); 168 | static_assert(sizeof(timingsAndOpcode_t) == 2); 169 | static_assert(sizeof(programmingAndChipEraseTiming_t) == 4); 170 | static_assert(sizeof(basicParameterTable_t) == 64); 171 | } // namespace sfdp 172 | 173 | #endif /*SFDP_INTERNAL_HXX*/ 174 | -------------------------------------------------------------------------------- /software/utils/iterator.hxx: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_ITERATOR_HXX 2 | #define UTILS_ITERATOR_HXX 3 | 4 | #include 5 | 6 | namespace flashprog::utils 7 | { 8 | using std::iterator_traits; 9 | using std::iterator; 10 | 11 | /** 12 | * Defines an iterator adapter that is 'normal' in the sense that 13 | * it does not change the semantics of any of the operators of its 14 | * \c iterator_t parameter. Its primary purpose is to convert an 15 | * iterator that is not a class (ie a pointer), into an iterator that is. 16 | * \c container_t exists as a container-specific tag for instanciation 17 | * differentiation purposes. 18 | */ 19 | template struct normalIterator_t 20 | { 21 | protected: 22 | // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes) 23 | iterator_t current{iterator_t{}}; 24 | 25 | using traits_t = iterator_traits; 26 | 27 | public: 28 | using iterator_type = iterator_t; 29 | using iterator_category = typename traits_t::iterator_category; 30 | using value_type = typename traits_t::value_type; 31 | using difference_type = typename traits_t::difference_type; 32 | using reference = typename traits_t::reference; 33 | using pointer = typename traits_t::pointer; 34 | 35 | constexpr normalIterator_t() noexcept = default; 36 | explicit normalIterator_t(const iterator_t &iter) noexcept : current{iter} { } 37 | 38 | // We specifically allow iterator_t to const iterator_t conversion. 39 | template normalIterator_t(const normalIterator_t, 41 | container_t>> &iter) noexcept : current{iter.base()} { } 42 | 43 | [[nodiscard]] reference operator *() const noexcept { return *current; } 44 | [[nodiscard]] pointer operator ->() const noexcept { return current; } 45 | const normalIterator_t operator++(int) const noexcept // NOLINT(readability-const-return-type) 46 | { return normalIterator_t{current++}; } 47 | const normalIterator_t operator--(int) const noexcept // NOLINT(readability-const-return-type) 48 | { return normalIterator_t{current--}; } 49 | 50 | normalIterator_t &operator ++() noexcept 51 | { 52 | ++current; 53 | return *this; 54 | } 55 | 56 | normalIterator_t &operator --() noexcept 57 | { 58 | --current; 59 | return *this; 60 | } 61 | 62 | [[nodiscard]] reference operator [](const difference_type offset) const noexcept 63 | { return current[offset]; } 64 | [[nodiscard]] normalIterator_t operator +(const difference_type offset) const noexcept 65 | { return normalIterator_t{current + offset}; } 66 | [[nodiscard]] normalIterator_t operator -(const difference_type offset) const noexcept 67 | { return normalIterator_t{current - offset}; } 68 | 69 | normalIterator_t operator +=(const difference_type offset) const noexcept 70 | { 71 | current += offset; 72 | return *this; 73 | } 74 | 75 | normalIterator_t operator -=(const difference_type offset) const noexcept 76 | { 77 | current -= offset; 78 | return *this; 79 | } 80 | 81 | [[nodiscard]] iterator_t base() const noexcept { return current; } 82 | }; 83 | 84 | template inline bool operator ==( 85 | const normalIterator_t &lhs, 86 | const normalIterator_t &rhs 87 | ) noexcept 88 | { return lhs.base() == rhs.base(); } 89 | 90 | template inline bool operator ==( 91 | const normalIterator_t &lhs, 92 | const normalIterator_t &rhs 93 | ) noexcept 94 | { return lhs.base() == rhs.base(); } 95 | 96 | template inline bool operator !=( 97 | const normalIterator_t &lhs, 98 | const normalIterator_t &rhs 99 | ) noexcept 100 | { return lhs.base() != rhs.base(); } 101 | 102 | template inline bool operator !=( 103 | const normalIterator_t &lhs, 104 | const normalIterator_t &rhs 105 | ) noexcept 106 | { return lhs.base() != rhs.base(); } 107 | 108 | template inline bool operator <( 109 | const normalIterator_t &lhs, 110 | const normalIterator_t &rhs 111 | ) noexcept 112 | { return lhs.base() < rhs.base(); } 113 | 114 | template inline bool operator <( 115 | const normalIterator_t &lhs, 116 | const normalIterator_t &rhs 117 | ) noexcept 118 | { return lhs.base() < rhs.base(); } 119 | 120 | template inline bool operator >( 121 | const normalIterator_t &lhs, 122 | const normalIterator_t &rhs 123 | ) noexcept 124 | { return lhs.base() > rhs.base(); } 125 | 126 | template inline bool operator >( 127 | const normalIterator_t &lhs, 128 | const normalIterator_t &rhs 129 | ) noexcept 130 | { return lhs.base() > rhs.base(); } 131 | 132 | template inline bool operator <=( 133 | const normalIterator_t &lhs, 134 | const normalIterator_t &rhs 135 | ) noexcept 136 | { return lhs.base() <= rhs.base(); } 137 | 138 | template inline bool operator <=( 139 | const normalIterator_t &lhs, 140 | const normalIterator_t &rhs 141 | ) noexcept 142 | { return lhs.base() <= rhs.base(); } 143 | 144 | template inline bool operator >=( 145 | const normalIterator_t &lhs, 146 | const normalIterator_t &rhs 147 | ) noexcept 148 | { return lhs.base() >= rhs.base(); } 149 | 150 | template inline bool operator >=( 151 | const normalIterator_t &lhs, 152 | const normalIterator_t &rhs 153 | ) noexcept 154 | { return lhs.base() >= rhs.base(); } 155 | 156 | template inline auto operator -( 157 | const normalIterator_t &lhs, 158 | const normalIterator_t &rhs 159 | ) noexcept -> decltype(lhs.base() - rhs.base()) 160 | { return lhs.base() - rhs.base(); } 161 | 162 | template inline auto operator -( 163 | const normalIterator_t &lhs, 164 | const normalIterator_t &rhs 165 | ) noexcept -> typename normalIterator_t::difference_type 166 | { return lhs.base() - rhs.base(); } 167 | 168 | template inline auto operator +( 169 | const typename normalIterator_t::difference_type n, 170 | const normalIterator_t &iter 171 | ) noexcept -> normalIterator_t 172 | { return normalIterator_t{iter.base() + n}; } 173 | } // namespace flashprog::utils 174 | 175 | #endif /*UTILS_ITERATOR_HXX*/ 176 | -------------------------------------------------------------------------------- /software/utils/span.hxx: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_SPAN_HXX 2 | #define UTILS_SPAN_HXX 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "iterator.hxx" 9 | 10 | namespace flashprog::utils 11 | { 12 | inline constexpr size_t dynamicExtent = std::numeric_limits::max(); 13 | template struct span_t; 14 | 15 | namespace impl 16 | { 17 | using std::false_type; 18 | using std::true_type; 19 | 20 | template using removeCVRef_t = std::remove_cv_t>; 21 | template struct isSpan_t : false_type { }; 22 | template struct isSpan_t> : true_type { }; 23 | template constexpr inline bool isSpan = isSpan_t::value; 24 | template struct isArray_t : false_type { }; 25 | template struct isArray_t> : true_type { }; 26 | template constexpr inline bool isArray = isArray_t::value; 27 | template struct isContainer_t : false_type { }; 28 | 29 | template struct isContainer_t> && 32 | !isArray> && 33 | !std::is_array_v> 34 | >, 35 | decltype(std::data(std::declval())), 36 | decltype(std::size(std::declval())) 37 | >> : true_type { }; 38 | 39 | template constexpr inline bool isContainer = isContainer_t::value; 40 | template struct and_t; 41 | template<> struct and_t<> : public true_type { }; 42 | template struct and_t : public type1_t { }; 43 | template struct and_t : 44 | public std::conditional::type { }; 45 | template using isArrayConvertible = 46 | std::is_convertible; // NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) 47 | 48 | template struct extentStorage_t 49 | { 50 | constexpr extentStorage_t(size_t) noexcept { } 51 | [[nodiscard]] constexpr static size_t extent() noexcept { return _extent; } 52 | }; 53 | 54 | template<> struct extentStorage_t 55 | { 56 | private: 57 | size_t _extent; 58 | 59 | public: 60 | constexpr extentStorage_t(const size_t extent) noexcept : _extent{extent} { } 61 | [[nodiscard]] constexpr size_t extent() const noexcept { return _extent; } 62 | }; 63 | } // namespace impl 64 | 65 | template struct span_t final 66 | { 67 | template [[nodiscard]] static constexpr size_t _subspanExtent() noexcept 68 | { 69 | if constexpr (count != dynamicExtent) 70 | return count; 71 | else if constexpr (extent_v != dynamicExtent) 72 | return extent_v - offset; 73 | else 74 | return dynamicExtent; 75 | } 76 | 77 | template using isCompatArray_t = impl::and_t, impl::isArrayConvertible>; 79 | template constexpr static inline bool isCompatArray = 80 | isCompatArray_t::value; 81 | 82 | public: 83 | using value_type = std::remove_cv_t; 84 | using element_type = T; 85 | using size_type = size_t; 86 | using reference = T &; 87 | using const_reference = const T &; 88 | using pointer = T *; 89 | using const_pointer = const T *; 90 | using iterator = normalIterator_t; 91 | using const_iterator = normalIterator_t; 92 | using reverse_iterator = std::reverse_iterator; 93 | using const_reverse_iterator = std::reverse_iterator; 94 | using difference_type = ptrdiff_t; 95 | 96 | constexpr static inline size_t extent = extent_v; 97 | 98 | private: 99 | [[no_unique_address]] impl::extentStorage_t _extent{0}; 100 | pointer _ptr{nullptr}; 101 | 102 | public: 103 | constexpr span_t() noexcept = default; 104 | constexpr span_t(const span_t &) noexcept = default; 105 | constexpr span_t(pointer ptr, size_t count) noexcept : _extent{count}, _ptr{ptr} { } 106 | constexpr span_t(pointer first, pointer last) noexcept : span_t(first, last - first) { } 107 | 108 | template, void *> = nullptr> 109 | constexpr span_t(type_t (&array)[N]) noexcept : span_t(static_cast(array), N) { } // NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) 110 | template, void *> = nullptr> 111 | constexpr span_t(std::array &array) noexcept : 112 | span_t(static_cast(array.data()), N) { } 113 | template, void *> = nullptr> 114 | constexpr span_t(const std::array &array) noexcept : 115 | span_t(static_cast(array.data()), N) { } 116 | 117 | template, void *> = nullptr> 119 | constexpr span_t(container_t &container) noexcept(noexcept(std::data(container)) && 120 | noexcept(std::size(container))) : span_t(std::data(container), std::size(container)) { } 121 | 122 | template, void *> = nullptr> 124 | constexpr span_t(const container_t &container) noexcept(noexcept(std::data(container)) && 125 | noexcept(std::size(container))) : span_t(std::data(container), std::size(container)) { } 126 | 127 | span_t(span_t &&) = delete; 128 | ~span_t() noexcept = default; 129 | span_t &operator =(span_t &&) = delete; 130 | constexpr span_t &operator =(const span_t &) noexcept = default; 131 | [[nodiscard]] constexpr size_t size() const noexcept { return _extent.extent(); } 132 | [[nodiscard]] constexpr size_t size_bytes() const noexcept { return size() * sizeof(T); } 133 | [[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; } 134 | 135 | [[nodiscard]] constexpr reference front() const noexcept 136 | { 137 | static_assert(extent != 0); 138 | static_assert(!empty()); 139 | return *_ptr; 140 | } 141 | 142 | [[nodiscard]] constexpr reference back() const noexcept 143 | { 144 | static_assert(extent != 0); 145 | static_assert(!empty()); 146 | return _ptr[size() - 1]; 147 | } 148 | 149 | constexpr reference operator [](const size_t idx) const noexcept 150 | { 151 | static_assert(extent != 0); 152 | static_assert(idx < size()); 153 | return _ptr[idx]; 154 | } 155 | 156 | [[nodiscard]] constexpr pointer data() const noexcept { return _ptr; } 157 | [[nodiscard]] constexpr iterator begin() const noexcept { return iterator{_ptr}; } 158 | [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return const_iterator{_ptr}; } 159 | [[nodiscard]] constexpr iterator end() const noexcept { return iterator{_ptr + size()}; } 160 | [[nodiscard]] constexpr const_iterator cend() const noexcept { return const_iterator{_ptr + size()}; } 161 | [[nodiscard]] constexpr pointer rbegin() const noexcept { return reverse_iterator{end()}; } 162 | [[nodiscard]] constexpr pointer crbegin() const noexcept { return const_reverse_iterator{cend()}; } 163 | [[nodiscard]] constexpr pointer rend() const noexcept { return reverse_iterator{begin()}; } 164 | [[nodiscard]] constexpr pointer crend() const noexcept { return const_reverse_iterator{cbegin()}; } 165 | 166 | template [[nodiscard]] constexpr span_t first() const noexcept 167 | { 168 | if constexpr (extent_v == dynamicExtent) 169 | static_assert(count <= size()); 170 | else 171 | static_assert(count <= extent); 172 | return {data(), count}; 173 | } 174 | 175 | [[nodiscard]] constexpr span_t first(size_t count) const noexcept 176 | { 177 | static_assert(count <= size()); 178 | return {data(), count}; 179 | } 180 | 181 | template [[nodiscard]] constexpr span_t last() const noexcept 182 | { 183 | if constexpr (extent_v == dynamicExtent) 184 | static_assert(count <= size()); 185 | else 186 | static_assert(count <= extent); 187 | return {data() + (size() - count), count}; 188 | } 189 | 190 | [[nodiscard]] constexpr span_t last(size_t count) const noexcept 191 | { 192 | static_assert(count <= size()); 193 | return {data() + (size() - count), count}; 194 | } 195 | 196 | template [[nodiscard]] constexpr auto 197 | subspan() const noexcept -> span_t()> 198 | { 199 | if constexpr (extent_v == dynamicExtent) 200 | static_assert(offset <= size()); 201 | else 202 | static_assert(offset <= extent); 203 | 204 | if constexpr (count == dynamicExtent) 205 | return {data() + offset, size() - offset}; 206 | else if constexpr (extent_v == dynamicExtent) 207 | { 208 | static_assert(count <= size()); 209 | static_assert(count <= size() - offset); 210 | } 211 | else 212 | { 213 | static_assert(count <= extent); 214 | static_assert(count <= extent - offset); 215 | } 216 | return {data() + offset, count}; 217 | } 218 | 219 | [[nodiscard]] constexpr span_t subspan(size_t offset, size_t count = dynamicExtent) const noexcept 220 | { 221 | static_assert(offset <= size()); 222 | if (count == dynamicExtent) 223 | count = size() - offset; 224 | else 225 | { 226 | static_assert(count <= size()); 227 | static_assert(offset + count <= size()); 228 | } 229 | return {data() + offset, count}; 230 | } 231 | }; 232 | 233 | // These are the type deduction guides for span_t 234 | template span_t(T (&)[N]) -> span_t; // NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) 235 | template span_t(std::array &) -> span_t; 236 | template span_t(const std::array &) -> span_t; 237 | template span_t(T *, size_t) -> span_t; 238 | template span_t(T *, T *) -> span_t; 239 | template span_t(container_t &) -> span_t; 240 | template span_t(const container_t &) -> span_t; 241 | 242 | template inline span_t 244 | as_bytes(span_t span) noexcept 245 | { 246 | return { 247 | reinterpret_cast(span.data()), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) 248 | span.size_bytes() 249 | }; 250 | } 251 | 252 | template inline span_t 254 | as_writeable_bytes(span_t span) noexcept 255 | { 256 | return { 257 | reinterpret_cast(span.data()), // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) 258 | span.size_bytes() 259 | }; 260 | } 261 | } // namespace flashprog::utils 262 | 263 | namespace std // NOLINT(cert-dcl58-cpp) 264 | { 265 | using flashprog::utils::dynamicExtent; 266 | using flashprog::utils::span_t; 267 | 268 | template constexpr T &get(span_t span) noexcept 269 | { 270 | static_assert(extent != dynamicExtent && index < extent, 271 | "get can only be used with a span of non-dynamic (fixed) extent"); 272 | return span[index]; 273 | } 274 | 275 | template struct tuple_size; 276 | template struct tuple_element; 277 | 278 | template 279 | struct tuple_size> : public integral_constant 280 | { 281 | static_assert(extent != dynamicExtent, 282 | "tuple_size can only be used with a span of non-dynamic (fixed) extent"); 283 | }; 284 | 285 | template struct tuple_element> 286 | { 287 | static_assert(extent != dynamicExtent, 288 | "tuple_element can only be used with a span of non-dynamic (fixed) extent"); 289 | static_assert(index < extent, "index is less than extent"); 290 | using type = T; 291 | }; 292 | } // namespace std 293 | 294 | #endif /*UTILS_SPAN_HXX*/ 295 | -------------------------------------------------------------------------------- /software/utils/units.hxx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | #ifndef UTILS_UNITS_HXX 3 | #define UTILS_UNITS_HXX 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace flashprog::utils 10 | { 11 | using namespace std::literals::string_view_literals; 12 | 13 | inline std::tuple humanReadableSize(uint32_t size) 14 | { 15 | if (size < 1024U) 16 | return {size, "B"sv}; 17 | size /= 1024U; 18 | if (size < 1024U) 19 | return {size, "kiB"sv}; 20 | size /= 1024U; 21 | if (size < 1024U) 22 | return {size, "MiB"sv}; 23 | size /= 1024U; 24 | return {size, "GiB"sv}; 25 | } 26 | } // namespace flashprog::utils 27 | 28 | #endif /*UTILS_UNITS_HXX*/ 29 | --------------------------------------------------------------------------------