diff --git a/hw/application_fpga/fw/.clang-format b/.clang-format
similarity index 100%
rename from hw/application_fpga/fw/.clang-format
rename to .clang-format
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 71aa646..8f44243 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -10,10 +10,10 @@ on:
workflow_dispatch: {}
jobs:
- check-firmware:
+ check-formatting:
runs-on: ubuntu-latest
container:
- image: ghcr.io/tillitis/tkey-builder:4
+ image: ghcr.io/tillitis/tkey-builder:5rc1
steps:
- name: checkout
uses: actions/checkout@v4
@@ -26,11 +26,29 @@ jobs:
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
- - name: check indentation in firmware C code
+ - name: check formatting on Verilog and C
working-directory: hw/application_fpga
run: |
- make -C fw/tk1 checkfmt
- make -C fw/testfw checkfmt
+ make checkfmt
+
+ - name: check for SPDX tags
+ run: ./LICENSES/spdx-ensure
+
+ check-firmware:
+ runs-on: ubuntu-latest
+ container:
+ image: ghcr.io/tillitis/tkey-builder:5rc1
+ steps:
+ - name: checkout
+ uses: actions/checkout@v4
+ with:
+ # fetch-depth: 0
+ persist-credentials: false
+
+ - name: fix
+ # https://github.com/actions/runner-images/issues/6775
+ run: |
+ git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: run static analysis on firmware C code
working-directory: hw/application_fpga
@@ -44,7 +62,7 @@ jobs:
check-verilog:
runs-on: ubuntu-latest
container:
- image: ghcr.io/tillitis/tkey-builder:4
+ image: ghcr.io/tillitis/tkey-builder:5rc1
steps:
- name: checkout
uses: actions/checkout@v4
@@ -64,7 +82,7 @@ jobs:
build-usb-firmware:
runs-on: ubuntu-latest
container:
- image: ghcr.io/tillitis/tkey-builder:4
+ image: ghcr.io/tillitis/tkey-builder:5rc1
steps:
- name: checkout
uses: actions/checkout@v4
@@ -86,7 +104,7 @@ jobs:
commit_sha: ${{ github.sha }}
runs-on: ubuntu-latest
container:
- image: ghcr.io/tillitis/tkey-builder:4
+ image: ghcr.io/tillitis/tkey-builder:5rc1
steps:
- name: checkout
uses: actions/checkout@v4
@@ -115,7 +133,7 @@ jobs:
needs: build-bitstream
runs-on: ubuntu-latest
container:
- image: ghcr.io/tillitis/tkey-builder:4
+ image: ghcr.io/tillitis/tkey-builder:5rc1
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -133,8 +151,3 @@ jobs:
- name: check matching hashes for firmware.bin & application_fpga.bin
working-directory: hw/application_fpga
run: make check-binary-hashes
-
-
- # TODO? first deal with hw/boards/ and hw/production_test/
- # - name: check for SPDX tags
- # run: ./LICENSES/spdx-ensure
diff --git a/.gitignore b/.gitignore
index 932ab96..cf1c889 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,12 @@
/testbench_verilator*
/check.smt2
/check.vcd
+/hw/application_fpga/tkey-libs/libblake2s.a
+/hw/application_fpga/tkey-libs/libcommon.a
+/hw/application_fpga/tkey-libs/libcrt0.a
+/hw/application_fpga/tkey-libs/libmonocypher.a
+/hw/application_fpga/tools/tkeyimage/tkeyimage
+/hw/application_fpga/tools/b2s/b2s
synth.json
synth.txt
synth.v
@@ -40,6 +46,7 @@ verilated/
*.o
*.asc
*.bin
+!/hw/application_fpga/tools/default_partition.bin
*.elf
*.map
*.tmp
@@ -49,3 +56,5 @@ verilated/
.*.swp
*.sch-bak
*.sim
+compile_commands.json
+.cache
diff --git a/LICENSES/README.md b/LICENSES/README.md
index 8830ede..7874a75 100644
--- a/LICENSES/README.md
+++ b/LICENSES/README.md
@@ -2,11 +2,13 @@
## Main license
-Unless otherwise noted, the project sources are licensed under the
-terms and conditions of the "GNU General Public License v2.0 only".
+Unless otherwise noted, the project sources are copyright Tillitis AB,
+but you can redistribute it and/or modify it under the terms of the
+GNU General Public License version 2 as published by the Free Software
+Foundation. See `gpl-2.0.txt`.
-The `LICENSES/` directory contains copies of the license texts used by
-the sources included in the project source tree.
+This directory contains copies of the license texts used by the
+sources included in the project source tree.
## SPDX
@@ -19,3 +21,21 @@ The current set of valid, predefined SPDX identifiers can be found on
the SPDX License List at:
https://spdx.org/licenses/
+
+## Notable imported projects
+
+- ch552 firmware: `hw/usb_interface/ch552_fw/`
+
+ Originally by WCH under MIT. Much changed by Tillitis.
+
+- picorv32: `hw/application_fpga/core/picorv32`
+
+ From https://github.com/YosysHQ/picorv32
+
+ ISC.
+
+- PicoRV32 custom ops: `hw/application_fpga/fw/tk1/picorv32/`
+
+- tkey-libs: `hw/application_fpga/tkey-libs/`
+
+ BSD2. From https://github.com/tillitis/tkey-libs
diff --git a/LICENSES/gpl-2.0.txt b/LICENSES/gpl-2.0.txt
new file mode 100644
index 0000000..9efa6fb
--- /dev/null
+++ b/LICENSES/gpl-2.0.txt
@@ -0,0 +1,338 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Moe Ghoul, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/LICENSES/gpl-v2.only.txt b/LICENSES/gpl-v2.only.txt
deleted file mode 100644
index 708a516..0000000
--- a/LICENSES/gpl-v2.only.txt
+++ /dev/null
@@ -1,245 +0,0 @@
-GNU GENERAL PUBLIC LICENSE
-
-Version 2, June 1991
-
-
-Copyright (C) 1989, 1991 Free Software Foundation, Inc.
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
-
-Everyone is permitted to copy and distribute verbatim copies
-of this license document, but changing it is not allowed.
-
-
-Preamble
-
-The licenses for most software are designed to take away your freedom to share and
-change it. By contrast, the GNU General Public License is intended to guarantee your
-freedom to share and change free software--to make sure the software is free for all
-its users. This General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to using it.
-(Some other Free Software Foundation software is covered by the GNU Lesser General
-Public License instead.) You can apply it to your programs, too.
-
-When we speak of free software, we are referring to freedom, not price. Our General
-Public Licenses are designed to make sure that you have the freedom to distribute
-copies of free software (and charge for this service if you wish), that you receive
-source code or can get it if you want it, that you can change the software or use
-pieces of it in new free programs; and that you know you can do these things.
-
-To protect your rights, we need to make restrictions that forbid anyone to deny you
-these rights or to ask you to surrender the rights. These restrictions translate to
-certain responsibilities for you if you distribute copies of the software, or if you
-modify it.
-
-For example, if you distribute copies of such a program, whether gratis or for a
-fee, you must give the recipients all the rights that you have. You must make sure
-that they, too, receive or can get the source code. And you must show them these
-terms so they know their rights.
-
-We protect your rights with two steps: (1) copyright the software, and (2) offer you
-this license which gives you legal permission to copy, distribute and/or modify the
-software.
-
-Also, for each author's protection and ours, we want to make certain that everyone
-understands that there is no warranty for this free software. If the software is
-modified by someone else and passed on, we want its recipients to know that what
-they have is not the original, so that any problems introduced by others will not
-reflect on the original authors' reputations.
-
-Finally, any free program is threatened constantly by software patents. We wish to
-avoid the danger that redistributors of a free program will individually obtain
-patent licenses, in effect making the program proprietary. To prevent this, we have
-made it clear that any patent must be licensed for everyone's free use or not
-licensed at all.
-
-The precise terms and conditions for copying, distribution and modification follow.
-
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-0. This License applies to any program or other work which contains a notice placed
-by the copyright holder saying it may be distributed under the terms of this General
-Public License. The "Program", below, refers to any such program or work, and a
-"work based on the Program" means either the Program or any derivative work under
-copyright law: that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another language.
-(Hereinafter, translation is included without limitation in the term
-"modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not covered by this
-License; they are outside its scope. The act of running the Program is not
-restricted, and the output from the Program is covered only if its contents
-constitute a work based on the Program (independent of having been made by running
-the Program). Whether that is true depends on what the Program does.
-
-1. You may copy and distribute verbatim copies of the Program's source code as you
-receive it, in any medium, provided that you conspicuously and appropriately publish
-on each copy an appropriate copyright notice and disclaimer of warranty; keep intact
-all the notices that refer to this License and to the absence of any warranty; and
-give any other recipients of the Program a copy of this License along with the
-Program.
-
-You may charge a fee for the physical act of transferring a copy, and you may at
-your option offer warranty protection in exchange for a fee.
-
-2. You may modify your copy or copies of the Program or any portion of it, thus
-forming a work based on the Program, and copy and distribute such modifications or
-work under the terms of Section 1 above, provided that you also meet all of these
-conditions:
-
- a) You must cause the modified files to carry prominent notices stating that you
- changed the files and the date of any change.
- b) You must cause any work that you distribute or publish, that in whole or in
- part contains or is derived from the Program or any part thereof, to be licensed
- as a whole at no charge to all third parties under the terms of this License.
- c) If the modified program normally reads commands interactively when run, you
- must cause it, when started running for such interactive use in the most
- ordinary way, to print or display an announcement including an appropriate
- copyright notice and a notice that there is no warranty (or else, saying that
- you provide a warranty) and that users may redistribute the program under these
- conditions, and telling the user how to view a copy of this License. (Exception:
- if the Program itself is interactive but does not normally print such an
- announcement, your work based on the Program is not required to print an
- announcement.)
-
-These requirements apply to the modified work as a whole. If identifiable sections
-of that work are not derived from the Program, and can be reasonably considered
-independent and separate works in themselves, then this License, and its terms, do
-not apply to those sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based on the
-Program, the distribution of the whole must be on the terms of this License, whose
-permissions for other licensees extend to the entire whole, and thus to each and
-every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest your rights to
-work written entirely by you; rather, the intent is to exercise the right to control
-the distribution of derivative or collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program with the
-Program (or with a work based on the Program) on a volume of a storage or
-distribution medium does not bring the other work under the scope of this License.
-
-3. You may copy and distribute the Program (or a work based on it, under Section 2)
-in object code or executable form under the terms of Sections 1 and 2 above provided
-that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable source code,
- which must be distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
- b) Accompany it with a written offer, valid for at least three years, to give
- any third party, for a charge no more than your cost of physically performing
- source distribution, a complete machine-readable copy of the corresponding
- source code, to be distributed under the terms of Sections 1 and 2 above on a
- medium customarily used for software interchange; or,
- c) Accompany it with the information you received as to the offer to distribute
- corresponding source code. (This alternative is allowed only for noncommercial
- distribution and only if you received the program in object code or executable
- form with such an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for making
-modifications to it. For an executable work, complete source code means all the
-source code for all modules it contains, plus any associated interface definition
-files, plus the scripts used to control compilation and installation of the
-executable. However, as a special exception, the source code distributed need not
-include anything that is normally distributed (in either source or binary form) with
-the major components (compiler, kernel, and so on) of the operating system on which
-the executable runs, unless that component itself accompanies the executable.
-
-If distribution of executable or object code is made by offering access to copy from
-a designated place, then offering equivalent access to copy the source code from the
-same place counts as distribution of the source code, even though third parties are
-not compelled to copy the source along with the object code.
-
-4. You may not copy, modify, sublicense, or distribute the Program except as
-expressly provided under this License. Any attempt otherwise to copy, modify,
-sublicense or distribute the Program is void, and will automatically terminate your
-rights under this License. However, parties who have received copies, or rights,
-from you under this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
-5. You are not required to accept this License, since you have not signed it.
-However, nothing else grants you permission to modify or distribute the Program or
-its derivative works. These actions are prohibited by law if you do not accept this
-License. Therefore, by modifying or distributing the Program (or any work based on
-the Program), you indicate your acceptance of this License to do so, and all its
-terms and conditions for copying, distributing or modifying the Program or works
-based on it.
-
-6. Each time you redistribute the Program (or any work based on the Program), the
-recipient automatically receives a license from the original licensor to copy,
-distribute or modify the Program subject to these terms and conditions. You may not
-impose any further restrictions on the recipients' exercise of the rights granted
-herein. You are not responsible for enforcing compliance by third parties to this
-License.
-
-7. If, as a consequence of a court judgment or allegation of patent infringement or
-for any other reason (not limited to patent issues), conditions are imposed on you
-(whether by court order, agreement or otherwise) that contradict the conditions of
-this License, they do not excuse you from the conditions of this License. If you
-cannot distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you may not
-distribute the Program at all. For example, if a patent license would not permit
-royalty-free redistribution of the Program by all those who receive copies directly
-or indirectly through you, then the only way you could satisfy both it and this
-License would be to refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under any particular
-circumstance, the balance of the section is intended to apply and the section as a
-whole is intended to apply in other circumstances.
-
-It is not the purpose of this section to induce you to infringe any patents or other
-property right claims or to contest validity of any such claims; this section has
-the sole purpose of protecting the integrity of the free software distribution
-system, which is implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed through that system
-in reliance on consistent application of that system; it is up to the author/donor
-to decide if he or she is willing to distribute software through any other system
-and a licensee cannot impose that choice.
-
-This section is intended to make thoroughly clear what is believed to be a
-consequence of the rest of this License.
-
-8. If the distribution and/or use of the Program is restricted in certain countries
-either by patents or by copyrighted interfaces, the original copyright holder who
-places the Program under this License may add an explicit geographical distribution
-limitation excluding those countries, so that distribution is permitted only in or
-among countries not thus excluded. In such case, this License incorporates the
-limitation as if written in the body of this License.
-
-9. The Free Software Foundation may publish revised and/or new versions of the
-General Public License from time to time. Such new versions will be similar in
-spirit to the present version, but may differ in detail to address new problems or
-concerns.
-
-Each version is given a distinguishing version number. If the Program specifies a
-version number of this License which applies to it and "any later version", you have
-the option of following the terms and conditions either of that version or of any
-later version published by the Free Software Foundation. If the Program does not
-specify a version number of this License, you may choose any version ever published
-by the Free Software Foundation.
-
-10. If you wish to incorporate parts of the Program into other free programs whose
-distribution conditions are different, write to the author to ask for permission.
-For software which is copyrighted by the Free Software Foundation, write to the Free
-Software Foundation; we sometimes make exceptions for this. Our decision will be
-guided by the two goals of preserving the free status of all derivatives of our free
-software and of promoting the sharing and reuse of software generally.
-
-NO WARRANTY
-
-11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE
-PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN
-WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
-WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH
-YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY
-SERVICING, REPAIR OR CORRECTION.
-
-12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY
-COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM
-AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
-INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
-PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE
-OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE
-WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
diff --git a/LICENSES/spdx-ensure b/LICENSES/spdx-ensure
index ece9e26..3385211 100755
--- a/LICENSES/spdx-ensure
+++ b/LICENSES/spdx-ensure
@@ -16,34 +16,73 @@ LICENSES/
doc/
hw/application_fpga/core/picorv32/
hw/application_fpga/core/uart/
-hw/application_fpga/fw/tk1/blake2s/
)
missingok_files=(
.editorconfig
.gitattributes
.gitignore
+.clang-format
README.md
contrib/99-tillitis.rules
contrib/Dockerfile
contrib/Makefile
-dco.md
+contrib/verible.sha512
+
+hw/application_fpga/README.md
+hw/application_fpga/core/clk_reset_gen/README.md
+hw/application_fpga/core/fw_ram/README.md
+hw/application_fpga/core/ram/README.md
+hw/application_fpga/core/rom/README.md
+hw/application_fpga/core/tk1/tb/udi.hex
+hw/application_fpga/core/uds/README.md
+hw/application_fpga/fw/README.md
+hw/application_fpga/fw/tk1/picorv32/README.md
+hw/application_fpga/apps/Makefile
+hw/application_fpga/apps/README.md
hw/application_fpga/application_fpga.bin.sha256
hw/application_fpga/config.vlt
hw/application_fpga/core/timer/README.md
hw/application_fpga/core/tk1/README.md
hw/application_fpga/core/touch_sense/README.md
hw/application_fpga/core/trng/README.md
-hw/application_fpga/core/uds/README.txt
hw/application_fpga/data/udi.hex
hw/application_fpga/data/uds.hex
hw/application_fpga/firmware.bin.sha512
-hw/application_fpga/fw/.clang-format
hw/application_fpga/fw/testfw/Makefile
hw/application_fpga/fw/tk1/Makefile
-hw/application_fpga/tools/makehex/makehex.py
-hw/application_fpga/tools/reset-tk1
hw/application_fpga/tools/tpt/README.md
+hw/usb_interface/ch552_fw/.gitignore
+hw/usb_interface/ch552_fw/LICENSES/GPL-2.0-only.txt
+hw/usb_interface/ch552_fw/LICENSES/MIT.txt
+hw/usb_interface/ch552_fw/Makefile
+hw/usb_interface/ch552_fw/README.md
+
+# tkey-libs is assumed to be REUSE compliant
+hw/application_fpga/tkey-libs/LICENSE
+hw/application_fpga/tkey-libs/LICENSES/BSD-2-Clause.txt
+hw/application_fpga/tkey-libs/LICENSES/CC0-1.0.txt
+hw/application_fpga/tkey-libs/Makefile
+hw/application_fpga/tkey-libs/README-DIST.txt
+hw/application_fpga/tkey-libs/README.md
+hw/application_fpga/tkey-libs/RELEASE.md
+hw/application_fpga/tkey-libs/blake2s/LICENSE
+hw/application_fpga/tkey-libs/blake2s/Makefile
+hw/application_fpga/tkey-libs/blake2s/blake2s.c
+hw/application_fpga/tkey-libs/blake2s/blake2s.h
+hw/application_fpga/tkey-libs/blake2s/blake2s_test.c
+hw/application_fpga/tkey-libs/example-app/Makefile
+hw/application_fpga/tkey-libs/monocypher/LICENSE
+hw/application_fpga/tkey-libs/monocypher/README.md
+
+hw/application_fpga/tools/README.md
+hw/application_fpga/tools/b2s/README.md
+hw/application_fpga/tools/b2s/go.mod
+hw/application_fpga/tools/b2s/go.sum
+hw/application_fpga/tools/default_partition.bin
+hw/application_fpga/tools/tkeyimage/README.md
+hw/application_fpga/tools/tkeyimage/go.mod
+hw/application_fpga/tools/tkeyimage/go.sum
)
is_missingok() {
diff --git a/README.md b/README.md
index ce77ef3..11dc8fa 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,9 @@
# Tillitis TKey
+Read about current work in progress
+[here](#current-work-in-progress-in-this-repository).
+
 *The TK1 PCB, also known as
TKey.*
@@ -25,11 +28,11 @@ With the right application, the TKey can be used for:
If you want to know more about Tillitis and the TKey, visit:
-- Main web: https://tillitis.se/
-- Shop: https://shop.tillitis.se/
-- Developer Handbook: https://dev.tillitis.se/
-- Officially supported apps: https://tillitis.se/download/
-- Other known apps: https://dev.tillitis.se/projects/
+- Main web:
+- Shop:
+- Developer Handbook:
+- Officially supported apps:
+- Other known apps:
All of the TKey software, firmware, FPGA Verilog code, schematics and
PCB design files are open source, just like all trustworthy security
@@ -37,9 +40,17 @@ software and hardware should be.
## Licensing
+Unless otherwise noted, the project sources are copyright Tillitis AB.
+but you can redistribute it and/or modify it under the terms of the
+GNU General Public License version 2 as published by the Free Software
+Foundation.
+
See [LICENSES](./LICENSES/README.md) for more information about
the projects' licenses.
+Each imported project is typically kept in its own directory with its
+own LICENSE file.
+
## Repositories
This repository contains the FPGA design, the source of the
@@ -56,17 +67,138 @@ releases.
The TKey PCB [KiCad](https://www.kicad.org/) design files are kept in
a separate repository:
-https://github.com/tillitis/tk1-pcba
+
The TP1 (TKey programmer 1) PCB design files and the firmware sources
are kept in:
-https://github.com/tillitis/tp1
+
Note that the TP1 is only used for provisioning the FPGA bitstream
into flash or the FPGA configuration memory. It's not necessary if you
just want to develop apps for the TKey.
+We use the tkey-libs libraries used for device app development in the
+firmware, too:
+
+https://github.com/tillitis/tkey-libs
+
+but keep our own copy of it in the repo. See below.
+
+## Building & flashing
+
+These instructions assume you're using a Linux distribution. Most of
+them also assume you're using our OCI image
+[tkey-builder](https://ghcr.io/tillitis/tkey-builder). If you want to
+run native tools, look in `contrib/Dockerfile` and
+`contrib/buildtools.sh` for the tools and versions to use.
+
+### FPGA
+
+You need a [TKey
+Unlocked](https://shop.tillitis.se/products/tkey-not-provisioned), a
+[the TP1 TKey Programmer
+board](https://shop.tillitis.se/products/tkey-dev-kit), and probably a
+[Blinkinlabs CH55x Reset
+Controller](https://shop-nl.blinkinlabs.com/products/ch55x-reset-controller)
+to use this on real hardware.
+
+Building is probably easiest using make and Podman.
+
+To build everything and then flash the resulting bitstream with the
+testloadapp in app slot 0 and the partition table copies in one go,
+place the TKey Unlocked in the TP1, then do:
+
+```
+cd contrib
+make flash
+```
+
+This uses the make target `prog_flash` in
+`hw/application_fpga/Makefile` behind the scenes, but mounts your TP1
+device into the container.
+
+To see all targets:
+
+```
+cd contrib
+make
+```
+
+See the [Tillitis Developer Handbook](https://dev.tillitis.se) for
+more.
+
+### USB Controller
+
+The TKey uses a WCH CH552 chip as a USB controller. It has its own
+firmware.
+
+Build:
+
+```
+cd contrib
+make run
+cd hw/usb_interface/ch552_fw
+make
+```
+
+To flash the controller with new firmware you need hardware like the
+[Blinkinlabs CH55x Reset
+Controller](https://shop-nl.blinkinlabs.com/products/ch55x-reset-controller)
+and a USB-A to USB-C converter.
+
+[Reset Controller source](https://github.com/Blinkinlabs/ch55x_programmer).
+
+You also need [chprog](https://github.com/ole00/chprog).
+
+The bootloader identifies itself as USB VID 4348, PID 55e0. To be able
+to access it and run `chprog` without root you need to allow your user
+to access it. Place `contrib/99-tillitis.rules` in `/etc/udev/rules.d`
+and run `udevadm control --reload`. Now you can add your user to the
+`dialout` group and access it.
+
+1. Connect the Reset Controller to your computer through "DUT\_IN"/"PC".
+2. Connect the TKey to "DUT\_OUT"/"DUT".
+3. Press the "Bootloader" button.
+4. Run `make flash_patched` in `hw/usb_interface/ch552_fw` outside of
+ a container.
+
+## Updating and working with tkey-libs
+
+A copy of [tkey-libs](https://github.com/tillitis/tkey-libs) is kept
+in `hw/application_fpga/tkey-libs`. This is mostly to avoid the
+subtleties of Git submodules.
+
+If you want to change something in tkey-libs, always change in the
+upstream library at:
+
+https://github.com/tillitis/tkey-libs
+
+You can build with an out-of-tree copy if you set `LIBDIR`, for
+example:
+
+```
+make LIBDIR=~/git/tkey-libs firmware.elf
+```
+
+When it's time to update the in-tree tkey-lib first tag the upstream
+repo with an `fw` prefix, like `fw-1` even if it already has an
+official version tag.
+
+Easiest is probably to just remove the tkey-libs directory and then
+git clone the desired tag. Use the entire repo, but remove the .-files
+like `.git`, `.github`, et cetera. Something like:
+
+```
+$ rm -rf tkey-libs
+$ git clone git@github.com:tillitis/tkey-libs.git
+$ cd tkey-libs
+$ git checkout fw-3
+```
+
+Note that you need to change the optimization flag in the tkey-libs'
+Makefile to `-Os`.
+
## Measured boot
The key behind guaranteeing security even as a general computer is the
@@ -104,3 +236,15 @@ deterministically generate any cryptographic keys it needs.
The TKey unconditional measured boot is inspired by, but not exactly
the same as part of [TCG
DICE](https://trustedcomputinggroup.org/work-groups/dice-architectures/).
+
+# Current Work in Progress in this repository
+
+We are updating the FPGA and firmware on TKey as part of the Castor
+release. This update will simplify TKey’s usage, laying the groundwork
+for future support of U2F/FIDO authentication.
+
+You can track our progress through this
+[milestone](https://github.com/tillitis/tillitis-key1/milestone/1).
+
+Note that main branch is in development. We try to keep status of main
+branch updated in the [release notes](/doc/release_notes.md#upcoming-release-castor).
diff --git a/contrib/Dockerfile b/contrib/Dockerfile
index 37ed248..7616eab 100644
--- a/contrib/Dockerfile
+++ b/contrib/Dockerfile
@@ -12,6 +12,7 @@ RUN apt-get -qq update -y \
clang-format \
clang-tidy \
cmake \
+ curl \
flex \
g++ \
gawk \
@@ -57,109 +58,12 @@ RUN apt-get -qq update -y \
zlib1g-dev \
&& rm -rf /var/lib/apt/lists/*
-# Enable bash completion
-RUN sed -i '/#if ! shopt -oq posix; then/ s/^#//' /etc/bash.bashrc
-RUN sed -i '/# if \[ -f \/usr\/share\/bash-completion\/bash_completion \]; then/ s/^#//' /etc/bash.bashrc
-RUN sed -i '/# . \/usr\/share\/bash-completion\/bash_completion/ s/^#//' /etc/bash.bashrc
-RUN sed -i '/# elif \[ -f \/etc\/bash_completion \]; then/ s/^#//' /etc/bash.bashrc
-RUN sed -i '/# . \/etc\/bash_completion/ s/^#//' /etc/bash.bashrc
-RUN sed -i '/# fi/ s/^#//' /etc/bash.bashrc
-RUN sed -i '/#fi/ s/^#//' /etc/bash.bashrc
-
FROM base as toolsbuilder
-RUN git clone --depth=1 https://github.com/YosysHQ/icestorm /src
-WORKDIR /src
-RUN git checkout 738af822905fdcf0466e9dd784b9ae4b0b34987f \
- && make -j$(nproc --ignore=2) \
- && make install \
- && git describe --all --always --long --dirty > /usr/local/repo-commit-icestorm
-WORKDIR /
-RUN rm -rf /src
+COPY buildtools.sh /buildtools.sh
+COPY verible.sha512 /verible.sha512
-# Custom iceprog for the RPi 2040-based programmer (will be upstreamed).
-RUN git clone -b interfaces --depth=1 https://github.com/tillitis/icestorm /src
-WORKDIR /src/iceprog
-RUN make -j$(nproc --ignore=2) \
- && make PROGRAM_PREFIX=tillitis- install \
- && git describe --all --always --long --dirty > /usr/local/repo-commit-tillitis--icestorm
-WORKDIR /
-RUN rm -rf /src
-
-RUN git clone -b 0.45 --depth=1 https://github.com/YosysHQ/yosys /src
-WORKDIR /src
-RUN git submodule update --init \
- && make -j$(nproc --ignore=2) \
- && make install \
- && git describe --all --always --long --dirty > /usr/local/repo-commit-yosys
-WORKDIR /
-RUN rm -rf /src
-
-RUN git clone -b nextpnr-0.7 https://github.com/YosysHQ/nextpnr /src
-WORKDIR /src
-# Add "Fix handling of RNG seed" #1369
-RUN git cherry-pick --no-commit 6ca64526bb18ace8690872b09ca1251567c116de
-# Add early exit if place fails on timing
-RUN sed -i \
- '345i \ \ \ \ general.add_options()("exit-on-failed-target-frequency",' \
- common/kernel/command.cc
-RUN sed -i \
- '346i \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "exit if target frequency is not achieved (use together with "' \
- common/kernel/command.cc
-RUN sed -i \
- '347i \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "--randomize-seed option)");' \
- common/kernel/command.cc
-RUN sed -i \
- '348i \\' \
- common/kernel/command.cc
-RUN sed -i \
- '662s/if (do_route) {/if (do_route \&\& (vm.count("exit-on-failed-target-frequency") ? !had_nonfatal_error : true)) {/' \
- common/kernel/command.cc
-RUN sed -i \
- '244s/bool warn_on_failure = false/bool warn_on_failure = true/' \
- common/kernel/timing.h
-RUN cmake -DARCH=ice40 . \
- && make -j$(nproc --ignore=2) \
- && make install \
- && git describe --all --always --long --dirty > /usr/local/repo-commit-nextpnr
-WORKDIR /
-RUN rm -rf /src
-
-RUN git clone -b v12_0 --depth=1 https://github.com/steveicarus/iverilog /src
-WORKDIR /src
-RUN sh autoconf.sh \
- && ./configure \
- && make -j$(nproc --ignore=2) \
- && make install \
- && git describe --all --always --long --dirty > /usr/local/repo-commit-iverilog
-WORKDIR /
-RUN rm -rf /src
-
-RUN git clone -b v5.028 --depth=1 https://github.com/verilator/verilator /src
-WORKDIR /src
-RUN autoconf \
- && ./configure \
- && make -j$(nproc --ignore=2) \
- && make test \
- && make install \
- && git describe --all --always --long --dirty > /usr/local/repo-commit-verilator
-WORKDIR /
-RUN rm -rf /src
-
-ADD https://github.com/chipsalliance/verible/releases/download/v0.0-3795-gf4d72375/verible-v0.0-3795-gf4d72375-linux-static-x86_64.tar.gz /src/verible.tar.gz
-WORKDIR /src
-RUN tar xvf verible.tar.gz
-RUN mv -v verible*/bin/* /usr/local/bin
-RUN verible-verilog-format --version | head -1 > /usr/local/repo-commit-verible
-WORKDIR /
-RUN rm -rf /src
-
-RUN git clone -b v1.9.1 https://github.com/cocotb/cocotb.git /src
-WORKDIR /src
-RUN pip install . --break-system-packages \
- && git describe --all --always --long --dirty > /usr/local/repo-commit-cocotb
-WORKDIR /
-RUN rm -rf /src
+RUN /buildtools.sh
FROM base
LABEL org.opencontainers.image.description="Toolchain for building TKey FPGA bitstream"
diff --git a/contrib/Makefile b/contrib/Makefile
index 202f671..8c925e1 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -5,34 +5,40 @@
BUILDIMAGE=tkey-builder-local
# default image used when running a container
-IMAGE=ghcr.io/tillitis/tkey-builder:4
+IMAGE=ghcr.io/tillitis/tkey-builder:5rc1
all:
@echo "Targets:"
- @echo "run Run a shell using image '$(IMAGE)' (Podman)"
- @echo "run-make Build the FPGA bitstream using image '$(IMAGE)' (Podman)"
- @echo "run-tb Run all the testbenches using image '$(IMAGE)' (Podman)"
- @echo "run-make-no-clean Like run-make but without cleaning first, useful for iterative firmware dev"
- @echo "run-make-clean_fw Like run-make but cleans only firmware"
- @echo "flash Program the SPI flash on the TKey - needs an existing bitstream"
- @echo "pull Pull down the image '$(IMAGE)' (Podman)"
- @echo "build-image Build a toolchain image named '$(BUILDIMAGE)' (Podman)"
- @echo " A newly built image can be used like: make IMAGE=$(BUILDIMAGE) run"
- @echo "docker-run Run a shell using image '$(IMAGE)' (Docker)"
- @echo "docker-build-image Build a toolchain image named '$(BUILDIMAGE)' (Docker)"
+ @echo "run Run a shell using image '$(IMAGE)' (Podman)"
+ @echo "run-make Build the FPGA bitstream using image '$(IMAGE)' (Podman)"
+ @echo "run-make-ch552 Build the CH552 firmware using image '$(IMAGE)' (Podman)"
+ @echo "run-tb Run all the testbenches using image '$(IMAGE)' (Podman)"
+ @echo "run-make-no-clean Like run-make but without cleaning first, useful for iterative firmware dev"
+ @echo "run-make-ch552-no-clean Like run-make-ch552 but without cleaning first, useful for iterative firmware dev"
+ @echo "run-make-clean_fw Like run-make but cleans only firmware"
+ @echo "flash Build bitstream and testloadapp.bin then program them and the partition table onto the TKey SPI flash"
+ @echo "pull Pull down the image '$(IMAGE)' (Podman)"
+ @echo "build-image Build a toolchain image named '$(BUILDIMAGE)' (Podman)"
+ @echo " A newly built image can be used like: make IMAGE=$(BUILDIMAGE) run"
+ @echo "docker-run Run a shell using image '$(IMAGE)' (Docker)"
+ @echo "docker-build-image Build a toolchain image named '$(BUILDIMAGE)' (Docker)"
run:
podman run --rm --mount type=bind,source="`pwd`/../",target=/build -w /build -it \
- $(IMAGE) /usr/bin/bash
+ $(IMAGE) /usr/bin/bash -l
docker-run:
docker run --rm --mount type=bind,source="`pwd`/../",target=/build -w /build -it \
- $(IMAGE) /usr/bin/bash
+ $(IMAGE) /usr/bin/bash -l
run-make:
podman run --rm --mount type=bind,source="`pwd`/../hw/application_fpga",target=/build -w /build -it \
$(IMAGE) make clean application_fpga.bin
+run-make-ch552:
+ podman run --rm --mount type=bind,source="`pwd`/../hw/usb_interface/ch552_fw",target=/build -w /build -it \
+ $(IMAGE) make clean usb_device.bin
+
run-tb:
podman run --rm --mount type=bind,source="`pwd`/../hw/application_fpga",target=/build -w /build -it \
$(IMAGE) make clean_tb tb
@@ -45,6 +51,10 @@ run-make-no-clean:
podman run --rm --mount type=bind,source="`pwd`/../hw/application_fpga",target=/build -w /build -it \
$(IMAGE) make application_fpga.bin
+run-make-ch552-no-clean:
+ podman run --rm --mount type=bind,source="`pwd`/../hw/usb_interface/ch552_fw",target=/build -w /build -it \
+ $(IMAGE) make usb_device.bin
+
run-make-clean_fw:
podman run --rm --mount type=bind,source="`pwd`/../hw/application_fpga",target=/build -w /build -it \
$(IMAGE) make clean_fw application_fpga.bin
@@ -54,7 +64,7 @@ flash:
--device /dev/bus/usb/$(lsusb | grep -m 1 1209:8886 | awk '{ printf "%s/%s", $2, substr($4,1,3) }') \
--mount type=bind,source="`pwd`/../hw/application_fpga",target=/build \
-w /build \
- -it $(IMAGE) tillitis-iceprog /build/application_fpga.bin
+ -it $(IMAGE) make prog_flash
pull:
diff --git a/contrib/buildtools.sh b/contrib/buildtools.sh
new file mode 100755
index 0000000..11e9f74
--- /dev/null
+++ b/contrib/buildtools.sh
@@ -0,0 +1,134 @@
+#! /bin/sh -e
+
+# Copyright (C) 2025 Tillitis AB
+# SPDX-License-Identifier: GPL-2.0-only
+
+## Build the specific versions of the tools we need to build the TKey
+## FPGA bitstream and apps.
+
+cd /
+mkdir src
+
+# ----------------------------------------------------------------------
+# Project icestorm
+# ----------------------------------------------------------------------
+git clone https://github.com/YosysHQ/icestorm /src/icestorm
+cd /src/icestorm
+
+# No tags or releases yet. Pin down to a specific commit.
+git checkout 738af822905fdcf0466e9dd784b9ae4b0b34987f
+make -j$(nproc --ignore=2)
+make install
+git describe --all --always --long --dirty > /usr/local/repo-commit-icestorm
+
+# ----------------------------------------------------------------------
+# Our own custom iceprog for the RPi 2040-based programmer. Will be
+# upstreamed.
+# ----------------------------------------------------------------------
+git clone -b interfaces --depth=1 https://github.com/tillitis/icestorm /src/icestorm-tillitis
+cd /src/icestorm-tillitis/iceprog
+make -j$(nproc --ignore=2)
+make PROGRAM_PREFIX=tillitis- install
+git describe --all --always --long --dirty > /usr/local/repo-commit-tillitis--icestorm
+
+# ----------------------------------------------------------------------
+# yosys
+# ----------------------------------------------------------------------
+git clone -b 0.45 --depth=1 https://github.com/YosysHQ/yosys /src/yosys
+cd /src/yosys
+
+# Make sure the digest is correct and no history has changed
+git checkout 9ed031ddd588442f22be13ce608547a5809b62f0
+
+git submodule update --init
+make -j$(nproc --ignore=2)
+make install
+git describe --all --always --long --dirty > /usr/local/repo-commit-yosys
+
+# ----------------------------------------------------------------------
+# nextpnr
+# ----------------------------------------------------------------------
+git clone -b nextpnr-0.7 https://github.com/YosysHQ/nextpnr /src/nextpnr
+cd /src/nextpnr
+# Make sure the digest is correct and no history has changed
+git checkout 73b7de74a5769095acb96eb6c6333ffe161452f2
+
+# Add "Fix handling of RNG seed" #1369
+git cherry-pick --no-commit 6ca64526bb18ace8690872b09ca1251567c116de
+
+# Add early exit if place fails on timing
+sed -i \
+ '345i \ \ \ \ general.add_options()("exit-on-failed-target-frequency",' \
+ common/kernel/command.cc
+sed -i \
+ '346i \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "exit if target frequency is not achieved (use together with "' \
+ common/kernel/command.cc
+sed -i \
+ '347i \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ "--randomize-seed option)");' \
+ common/kernel/command.cc
+sed -i \
+ '348i \\' \
+ common/kernel/command.cc
+sed -i \
+ '662s/if (do_route) {/if (do_route \&\& (vm.count("exit-on-failed-target-frequency") ? !had_nonfatal_error : true)) {/' \
+ common/kernel/command.cc
+sed -i \
+ '244s/bool warn_on_failure = false/bool warn_on_failure = true/' \
+ common/kernel/timing.h
+
+cmake -DARCH=ice40 .
+make -j$(nproc --ignore=2)
+make install
+git describe --all --always --long --dirty > /usr/local/repo-commit-nextpnr
+
+# ----------------------------------------------------------------------
+# icarus verilog
+# ----------------------------------------------------------------------
+git clone -b v12_0 --depth=1 https://github.com/steveicarus/iverilog /src/iverilog
+cd /src/iverilog
+
+# Make sure the digest is correct and no history has changed
+git checkout 4fd5291632232fbe1ba49b2c26bb6b2bf1c6c9cf
+sh autoconf.sh
+./configure
+make -j$(nproc --ignore=2)
+make install
+git describe --all --always --long --dirty > /usr/local/repo-commit-iverilog
+
+# ----------------------------------------------------------------------
+# verilator
+# ----------------------------------------------------------------------
+git clone -b v5.028 --depth=1 https://github.com/verilator/verilator /src/verilator
+cd /src/verilator
+
+# Make sure the digest is correct and no history has changed
+git checkout 8ca45df9c75c611989ae5bfc4112a32212c3dacf
+
+autoconf
+./configure
+make -j$(nproc --ignore=2)
+make test
+make install
+git describe --all --always --long --dirty > /usr/local/repo-commit-verilator
+
+# ----------------------------------------------------------------------
+# verible
+# ----------------------------------------------------------------------
+curl --output /src/verible.tar.gz -L https://github.com/chipsalliance/verible/releases/download/v0.0-3795-gf4d72375/verible-v0.0-3795-gf4d72375-linux-static-x86_64.tar.gz
+
+# Check against the expected digest
+sha512sum -c /verible.sha512
+
+cd /src
+
+tar xvf verible.tar.gz
+mv -v verible*/bin/* /usr/local/bin
+verible-verilog-format --version | head -1 > /usr/local/repo-commit-verible
+
+# ----------------------------------------------------------------------
+# cocotb
+# ----------------------------------------------------------------------
+git clone -b v1.9.1 https://github.com/cocotb/cocotb.git /src/cocotb
+cd /src/cocotb
+pip install . --break-system-packages
+git describe --all --always --long --dirty > /usr/local/repo-commit-cocotb
diff --git a/contrib/verible.sha512 b/contrib/verible.sha512
new file mode 100644
index 0000000..9c8fb74
--- /dev/null
+++ b/contrib/verible.sha512
@@ -0,0 +1 @@
+3e997b8cd494556fa107b96a6daa4e51133208a85b3c0250c0223d7194aedbc98b3f5213fedaee083edd96c317e50057aa9cdc0517197f4638be3834133e2c9f /src/verible.tar.gz
diff --git a/doc/release_notes.md b/doc/release_notes.md
index 5173cde..66ce98f 100644
--- a/doc/release_notes.md
+++ b/doc/release_notes.md
@@ -2,6 +2,205 @@
Descriptions of the tagged TKey releases.
+## Upcoming release: Castor
+
+Overview of changes since [TK1-24.03
+Bellatrix](https://github.com/tillitis/tillitis-key1/releases/tag/TK1-24.03)
+for the Castor milestone so far.
+
+Main branch in the repository contains Castor design and specifically
+the tag `TK1-Castor-alpha-1` reflects the upcoming release.
+
+**Note well**: BREAKING CHANGE! Older device apps WILL NOT WORK.
+
+The introduction of the USB Mode Protocol between the programs running
+on the PicoRV32 CPU and the CH552 means that device apps that have not
+been changed to use the protocol will not have any way to communicate
+with the outside world.
+
+### How to test TK1-Castor-alpha
+
+To test this alpha release you will need:
+
+- Tkey Unlocked
+- TKey Programmer Board
+- CH55x Reset Controller from [Blinkinlabs](https://shop-nl.blinkinlabs.com/products/ch55x-reset-controller)
+
+Read
+[here](https://github.com/tillitis/tillitis-key1/blob/main/README.md#building--flashing)
+for instructions.
+
+### General
+
+- Split repo:
+
+ - tk1, mta1-usb-dev, mta-usb-v1 and mta1-library moves to
+
+
+ - tp1, mta1-usb-programmer, mta1-library and KiCad-RP Pico moves to
+
+
+For full change log [see](https://github.com/tillitis/tillitis-key1/compare/TK1-23.03.2...coming-tag)
+
+### FPGA
+
+- Make Security Monitor memory access checks more complete.
+
+- Add SPI main controller mainly to access the flash chip.
+
+- Add system reset API. Device apps can reset the FPGA and restart
+ the firmware.
+
+- Increase clock frequence to 24 MHz.
+
+- Increase UART baudrate to 500,000.
+
+- Fix UART baudrate counter issues noticable at higher baudrates.
+
+- Fix missing clock cycles in timer core.
+
+- Remove the UART runtime configuration API.
+
+- Several minor clean ups of design and testbench.
+
+- Add hardware clear to send (CTS) signals for communication between
+ UART and CH552.
+
+- Increase size of ROM and FW\_RAM.
+
+- Make ROM non-executable in app mode.
+
+- Remove MMIO address for access to the firmware blake2s() function
+ from apps.
+
+- Automatically leave firmware mode when execution leaves ROM and
+ remove the now unnecessary APP\_MODE\_CTRL register.
+
+- Change UDS read protection: When execution leaves ROM the first
+ time, UDS is hardware protected from reads. The already existing
+ protection that UDS is protected after the first read is also still
+ available.
+
+- Introduce interrupt handler for hardware-based privilege raising and
+ automatically privelege lowering for system calls.
+
+### Firmware
+
+- At startup, fill RAM with random data using the xorwow PRNG, seeded
+ by TRNG.
+
+- Add support for the new USB Mode Protocol to communicate with
+ different USB endpoints in the USB controller.
+
+- Support a filesystem on flash: There's space for two pre-loaded
+ apps and four storage areas for device apps.
+
+ A typical use is that app slot 0 will contain a loader app for
+ verified boot and app slot 1 contains the app to be verified.
+
+- Automatically start an app in flash app slot 0 after power cycle and
+ when instructed to by reset intentions.
+
+ The automatically started app is trusted by the firmware by
+ including an app digest in the firmware ROM. This means we extend
+ the user's trust in the firmware to the first app, but only if it's
+ measured to the correct digest by the firmware. Anything else is a
+ hard error which halts the CPU.
+
+- Support chaining of apps through soft resets, including support for
+ verifying that the next app is the expected one (exact measured
+ digest the previous app expected), and leaving data for the next app
+ to use.
+
+- Add a system call mechanism and system calls. See [firmware's
+ README](../hw/application_fpga/fw/README.md) for documentation, but
+ its probably easier to use the the syscall wrappers in libsyscall in
+ [tkey-libs](https://github.com/tillitis/tkey-libs) if you're writing
+ in C.
+
+- Harmonize with [tkey-libs](https://github.com/tillitis/tkey-libs).
+ Import tkey-libs to this repo for convenience.
+
+- Rewrite test firmware to work with the new leaving ROM-scenario.
+ Introduce a separate `testapp` for the app mode parts.
+
+### Device apps
+
+Introduce some device apps mostly for testing.
+
+- `reset_test`: Test the different types of soft reset.
+
+- `testapp`: Tests in app mode that used to live in `testfw`.
+
+- `testloadapp`: A simple loader app for management and verification
+ of a second app.
+
+- `defaultapp`: An app that immediately resets the TKey to load an app
+ from the client, just like earlier releases.
+
+### CH552 firmware
+
+- Use the new CTS signals for communication over the UART.
+
+- Add support for two HID endpoints (security token and our debug
+ HID).
+
+- Add support for CCID endpoint.
+
+- Add a protocol to communicate with the different endpoints: CDC,
+ CCID, FIDO, debug.
+
+- Change USB frame sending from a software timer to instead be
+ controlled by the USB Controller Protocol.
+
+Note that to update the CH552 firmware you will need something like
+the Blinkinlabs CH55x Reset Controller:
+
+
+
+
+
+### Tooling
+
+- Add tools to parse and generate partition tables and flash images.
+
+- Add tool to compute a print a BLAKE2s digest, optionally as C code.
+
+### tkey-builder
+
+- New versions of:
+ - clang (18.1.3, part of ubuntu 24.04)
+ - icestorm (commit 738af822905fdcf0466e9dd784b9ae4b0b34987f
+)
+ - yosys (0.45)
+ - nextpnr (0.7) + extra patches for RNG seed handling and early exit
+ - iverilog (v12)
+ - verilator (v5.028)
+ - verible (v0.0-3795)
+ - cocotb (v1.9.1)
+
+- Remove TKey Programmer (TP) toolchain:
+
+ - gcc-arm-none-eabi: Used for the TKey Programmer firmware, now
+ moved to it's own repo.
+ - libnewlib-arm-none-eabi
+ - libstdc++-arm-none-eabi-newlib
+ - pico-sdk
+
+ TP1 is now in
+
+- Remove Go compiler support.
+
+- Introduce buildtools.sh for building upstream tools for inclusion
+ in the image.
+
+### Docs
+
+- All docs now in READMEs close to the design or code.
+
+- Protocol docs moved to [the Developer
+ Handbook](https://dev.tillitis.se/)
+ [repo](https://github.com/tillitis/dev-tillitis)
## TK1-24.03
@@ -19,6 +218,7 @@ the following digest:
```
### FPGA
+
- Security Monitor now prevents access to RAM outside of the physical
memory. If it detects an access outside of the RAM address space, it
will halt the CPU.
@@ -30,6 +230,7 @@ the following digest:
- Complete testbenches and add 9 tests for the FPGA cores.
### Firmware
+
- Protect zeroisation against compiler optimisation by using
secure_wipe(), fixing a memset() that was removed during
compilation.
@@ -44,31 +245,35 @@ the following digest:
- Fix warnings from splint.
### TP1
+
- New plastic clip o and update of BOM.
- Build TP1 firmware in CI.
### CH552
+
- Fixed a bug where a byte of data could in some rare circumstances be
dropped, causing a client app to hang.
- General clean-up of code, translated all comments to English.
### TK1
+
- New injection moulded plastic case
### tkey-builder
+
- Updated to version 3. Bumping Ubuntu to 23.10, Yosys to 0.36 and
nextpnr to 0.6.
- Updated to version 4. Bumping pico-sdk to 1.5.1, adding clang-tidy
and splint.
### Docs
+
- Fixing broken links, cleaning up docs and READMEs.
- Clarify warm boot attack mitigations and scope for Bellatrix in
threat model.
For full change log [see](https://github.com/tillitis/tillitis-key1/compare/TK1-23.03.2...TK1-24.03)
-
## TK1-23.03.2
This is the official release of the "Bellatrix" version of the
@@ -98,8 +303,8 @@ This bug fix release contains the following changes:
- Change the firmware protocol max frame size back to 128 bytes
- Correct a bug with the reading out of UDS
-
## TK1-23.03
+
This is the official release of the "Bellatrix" version of
the Tillitis TKey device. This version is ready for general
use.
@@ -113,7 +318,6 @@ shasum -a256 application_fpga.bin
f11d6b0f57c5405598206dcfea284008413391a2c51f124a2e2ae8600cb78f0b application_fpga.bin
```
-
### New and improved functionality
- (ALL) The TKey HW design, FW, protocol and first applications has
@@ -177,10 +381,9 @@ f11d6b0f57c5405598206dcfea284008413391a2c51f124a2e2ae8600cb78f0b application_fp
- (TOOLS) There is now a version of iceprog able to write to the FPGA
bitstream to the NVCM and lock the NVCM from external access
-
### Bugs fixed
-- No known bugs have been fixed. Numerous issues has been closed.
+- No known bugs have been fixed. Numerous issues has been closed.
### Limitations
@@ -188,7 +391,6 @@ f11d6b0f57c5405598206dcfea284008413391a2c51f124a2e2ae8600cb78f0b application_fp
cryptographically secure. It his however randomized every time
a TKey device is powered up.
-
## engineering-release-2
### New and improved functionality
diff --git a/doc/threat_model/threat_model.md b/doc/threat_model/threat_model.md
index 3a37a2d..c67231e 100644
--- a/doc/threat_model/threat_model.md
+++ b/doc/threat_model/threat_model.md
@@ -115,8 +115,8 @@ allowed
* Access to compute resources. Possibly access to lab equipment
* Will try all possible SW and HW attack vectors. In and out of scope
* End game is to find flaws in threat model. Acquire knowledge and
- findings to produce an interesting talk at CCC, USENIX or Security
- Fest
+ findings to produce an interesting talk at Chaos Communication
+ Congress, USENIX or Security Fest
Over time (with new releases), and given feedback by the CCC Hacker,
the TKey device should be able to withstand attacks by the CCC Hacker.
@@ -258,7 +258,7 @@ information, see the [Release Notes](/doc/release_notes.md)
Note that this mitigates an attack from outside the CPU, not from
an exploit towards applications running on it.
-#### Known possible weakneses
+#### Known possible weaknesses
The CH552 MCU providing USB host communication contains firmware that
implements the UART communication with the FPGA. The CH552 firmware
@@ -297,7 +297,7 @@ board, and is even shipped with a programmer to download new FPGA
bitstreams.
-#### Known weakneses
+#### Known weaknesses
The bitstream, which includes the Unique Device Secret (UDS) as well
as the firmware implementing the measured boot are stored as part of
diff --git a/doc/toolchain_setup.md b/doc/toolchain_setup.md
deleted file mode 100644
index e0e2454..0000000
--- a/doc/toolchain_setup.md
+++ /dev/null
@@ -1,149 +0,0 @@
-# Toolchain setup
-
-**NOTE:** Documentation migrated to dev.tillitis.se, this is kept for
-history. This is likely to be outdated.
-
-Here are instructions for setting up the tools required to build the
-project. Tested on Ubuntu 22.10.
-
-## General development environment
-
-The following is intended to be a complete list of the packages that
-are required for doing all of the following:
-
- - building and developing [TKey device and client
- apps](https://github.com/tillitis/tillitis-key1-apps)
- - building our [QEMU machine](https://github.com/tillitis/qemu/tree/tk1)
- (useful for apps dev)
- - building and developing firmware and FPGA gateware (which also
- requires building the toolchain below)
-
-```
-sudo apt install build-essential clang lld llvm bison flex libreadline-dev \
- gawk tcl-dev libffi-dev git mercurial graphviz \
- xdot pkg-config python3 libftdi-dev \
- python3-dev libeigen3-dev \
- libboost-dev libboost-filesystem-dev \
- libboost-thread-dev libboost-program-options-dev \
- libboost-iostreams-dev cmake libusb-1.0-0-dev \
- ninja-build libglib2.0-dev libpixman-1-dev \
- golang clang-format \
- gcc-arm-none-eabi libnewlib-arm-none-eabi \
- libstdc++-arm-none-eabi-newlib
-```
-
-## Device permissions
-
-To allow sudo-less programming, you can install a udev rule that will
-assign the tkey programmer, and also an unprogrammed CH552, to the
-dialout group. You will also need to add your user to this group:
-
-```
-sudo cp contrib/99-tillitis.rules /etc/udev/rules.d
-sudo udevadm control --reload-rules
-sudo usermod -aG dialout ${USER}
-```
-
-To apply the new group, log out and then log back in.
-
-You can check the device permissions to determine if the group was
-successfully applied. First, use lsusb to find the location of the
-programmer:
-
-```
-lsusb -d 1209:8886
-Bus 001 Device 023: ID 1209:8886 Generic TP-1
-```
-
-Then, you can check the permissions by using the bus and device
-number reported above. Note that this pair is ephemeral and may
-change after every device insertion:
-
-```
-ls -l /dev/bus/usb/001/023
-crw-rw---- 1 root dialout 189, 22 Feb 16 14:58 /dev/bus/usb/001/023
-```
-
-## Gateware: Yosys/Icestorm toolchain
-
-If the LED of your TKey is steady white when you plug it, then the
-firmware is running and it's already usable! If you want to develop
-TKey apps, then only the above general development environment is
-needed.
-
-Compiling and installing Yosys and friends is only needed if your TKey
-is not already running the required firmware and FPGA gateware, or if
-you want to do development on these components.
-
-These steps are used to build and install the
-[icestorm](http://bygone.clairexen.net/icestorm/) toolchain. The
-binaries are installed in `/usr/local`. Note that if you have or
-install other versions of these tools locally, they could conflict
-(case in point: `yosys` installed on MacOS using brew).
-
- git clone https://github.com/YosysHQ/icestorm
- cd icestorm
- git checkout d20a5e9001f46262bf0cef220f1a6943946e421d
- make -j$(nproc)
- sudo make install
- cd ..
-
- # Custom iceprog for the RPi 2040-based programmer (will be upstreamed).
- # Note: install dependencies for building tillitis-iceprog on Ubuntu:
- # sudo apt install libftdi-dev libusb-1.0-0-dev
- git clone -b interfaces https://github.com/tillitis/icestorm tillitis--icestorm
- cd tillitis--icestorm/iceprog
- make
- sudo make PROGRAM_PREFIX=tillitis- install
- cd ../..
-
- git clone https://github.com/YosysHQ/yosys
- cd yosys
- git checkout yosys-0.26
- make -j$(nproc)
- sudo make install
- cd ..
-
- git clone https://github.com/YosysHQ/nextpnr
- cd nextpnr
- git checkout nextpnr-0.5
- cmake -DARCH=ice40 -DCMAKE_INSTALL_PREFIX=/usr/local .
- make -j$(nproc)
- sudo make install
- cd ..
-
-References:
-* http://bygone.clairexen.net/icestorm/
-
-## Firmware: riscv toolchain
-
-The TKey implements a [picorv32](https://github.com/YosysHQ/picorv32)
-soft core CPU, which is a RISC-V microcontroller with the C
-instructions and Zmmul extension, multiply without divide
-(RV32ICZmmul). You can read
-[more](https://www.sifive.com/blog/all-aboard-part-1-compiler-args)
-about it.
-
-The project uses the LLVM/Clang suite and version 15 or later is
-required. As of writing Ubuntu 22.10 has version 15 packaged. You may
-be able to get it installed on older Ubuntu and Debian using the
-instructions on https://apt.llvm.org/ . There are also binary releases
-here: https://github.com/llvm/llvm-project/releases
-
-References:
-* https://github.com/YosysHQ/picorv32
-
-If your available `objcopy` and `size` commands is anything other than
-the default `llvm-objcopy` and `llvm-size` define `OBJCOPY` and `SIZE`
-to whatever they're called on your system before calling `make`.
-
-## CH552 USB to Serial firmware
-
-The USB to Serial firmware runs on the CH552 microcontroller, and
-provides a USB CDC profile which should work with the default drivers
-on all major operating systems. MTA1-USB-V1 and TK-1 devices come
-with the CH552 microcontroller pre-programmed.
-
-Toolchain setup and build instructions for this firmware are detailed
-in the
-[ch552_fw directory](../hw/usb_interface/ch552_fw/README.md)
diff --git a/hw/application_fpga/Makefile b/hw/application_fpga/Makefile
index 1c82f9d..35d13ed 100644
--- a/hw/application_fpga/Makefile
+++ b/hw/application_fpga/Makefile
@@ -32,7 +32,7 @@ TARGET_FREQ ?= 24
# Size in 32-bit words, must be divisible by 256 (pairs of EBRs, because 16
# bits wide; an EBR is 128 32-bits words)
-BRAM_FW_SIZE ?= 1536
+BRAM_FW_SIZE ?= 2048
PIN_FILE ?= application_fpga_tk1.pcf
@@ -41,13 +41,15 @@ OBJCOPY ?= llvm-objcopy
CC = clang
+LIBDIR ?= tkey-libs
+
CFLAGS = \
-target riscv32-unknown-none-elf \
-march=rv32iczmmul \
-mabi=ilp32 \
-static \
-std=gnu99 \
- -O2 \
+ -Os \
-ffast-math \
-fno-common \
-fno-builtin-printf \
@@ -58,8 +60,12 @@ CFLAGS = \
-Wall \
-Wpedantic \
-Wno-language-extension-token \
+ -Wextra \
-flto \
- -g
+ -g \
+ -I $(LIBDIR)/include \
+ -I $(LIBDIR) \
+ -I $(LIBDIR)/blake2s
AS = clang
@@ -67,7 +73,8 @@ ASFLAGS = \
-target riscv32-unknown-none-elf \
-march=rv32iczmmul \
-mabi=ilp32 \
- -mno-relax
+ -mno-relax \
+ -I $(LIBDIR)/include
ICE40_SIM_CELLS = $(shell yosys-config --datdir/ice40/cells_sim.v)
@@ -113,36 +120,30 @@ PICORV32_SRCS = \
$(P)/core/picorv32/rtl/picorv32.v
FIRMWARE_DEPS = \
- $(P)/fw/tk1_mem.h \
- $(P)/fw/tk1/types.h \
- $(P)/fw/tk1/lib.h \
- $(P)/fw/tk1/proto.h \
- $(P)/fw/tk1/assert.h \
- $(P)/fw/tk1/led.h
+ $(P)/fw/tk1/proto.h
FIRMWARE_OBJS = \
$(P)/fw/tk1/main.o \
$(P)/fw/tk1/start.o \
$(P)/fw/tk1/proto.o \
- $(P)/fw/tk1/lib.o \
- $(P)/fw/tk1/assert.o \
- $(P)/fw/tk1/led.o \
- $(P)/fw/tk1/blake2s/blake2s.o
+ $(P)/fw/tk1/syscall_enable.o \
+ $(P)/fw/tk1/syscall_handler.o \
+ $(P)/fw/tk1/spi.o \
+ $(P)/fw/tk1/flash.o \
+ $(P)/fw/tk1/storage.o \
+ $(P)/fw/tk1/partition_table.o \
+ $(P)/fw/tk1/auth_app.o \
+ $(P)/fw/tk1/rng.o \
+ $(P)/fw/tk1/reset.o \
+ $(P)/fw/tk1/preload_app.o \
+ $(P)/fw/tk1/mgmt_app.o
-FIRMWARE_SOURCES = \
- $(P)/fw/tk1/main.c \
- $(P)/fw/tk1/proto.c \
- $(P)/fw/tk1/lib.c \
- $(P)/fw/tk1/assert.c \
- $(P)/fw/tk1/led.c \
- $(P)/fw/tk1/blake2s/blake2s.c
+CHECK_SOURCES = \
+ $(P)/fw/tk1/*.[ch]
TESTFW_OBJS = \
$(P)/fw/testfw/main.o \
- $(P)/fw/testfw/start.o \
- $(P)/fw/tk1/proto.o \
- $(P)/fw/tk1/lib.o \
- $(P)/fw/tk1/blake2s/blake2s.o
+ $(P)/fw/testfw/start.o
#-------------------------------------------------------------------
# All: Complete build of HW and FW.
@@ -155,7 +156,10 @@ all: application_fpga.bin
# incorrect BRAM_FW_SIZE
# -------------------------------------------------------------------
%_size_mismatch: %.elf phony_explicit
- @test $$($(SIZE) $< | awk 'NR==2{print $$4}') -le $$(( 32 / 8 * $(BRAM_FW_SIZE) )) \
+ @test $$(( \
+ $$($(SIZE) -A $< | grep text | awk 'NR==1{print $$2}') + \
+ $$($(SIZE) -A $< | grep text | awk 'NR==2{print $$2}') \
+ )) -le $$(( 32 / 8 * $(BRAM_FW_SIZE) )) \
|| { printf "The 'BRAM_FW_SIZE' variable needs to be increased\n"; \
[[ $< =~ testfw ]] && printf "Note that testfw fits if built with -Os\n"; \
false; }
@@ -176,21 +180,39 @@ secret:
# Firmware generation.
# Included in the bitstream.
#-------------------------------------------------------------------
-LDFLAGS = -T $(P)/fw/tk1/firmware.lds
+LDFLAGS = \
+ -T $(P)/fw/tk1/firmware.lds \
+ -Wl,--cref,-M \
+ -L $(LIBDIR) -lcommon -lblake2s
+
+QEMU_LDFLAGS = \
+ -T $(P)/fw/tk1/qemu_firmware.lds \
+ -Wl,--cref,-M \
+ -L $(LIBDIR) -lcommon -lblake2s
+
+# Common libraries the firmware and testfw depend on. See
+# https://github.com/tillitis/tkey-libs/
+.PHONY: tkey-libs
+tkey-libs:
+ make -C $(LIBDIR)
$(FIRMWARE_OBJS): $(FIRMWARE_DEPS)
$(TESTFW_OBJS): $(FIRMWARE_DEPS)
-firmware.elf: $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds
- $(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@
+#firmware.elf: CFLAGS += -DTKEY_DEBUG
+firmware.elf: tkey-libs $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds
+ $(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map
simfirmware.elf: CFLAGS += -DSIMULATION
simfirmware.elf: $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds
- $(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@
+ $(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map
-qemu_firmware.elf: CFLAGS += -DQEMU_CONSOLE
-qemu_firmware.elf: firmware.elf
- mv firmware.elf qemu_firmware.elf
+qemu_firmware.elf: CFLAGS += -DQEMU_DEBUG
+qemu_firmware.elf: ASFLAGS += -DQEMU_DEBUG
+qemu_firmware.elf: CFLAGS += -DQEMU_SYSCALL
+qemu_firmware.elf: ASFLAGS += -DQEMU_SYSCALL
+qemu_firmware.elf: tkey-libs $(FIRMWARE_OBJS) $(P)/fw/tk1/qemu_firmware.lds
+ $(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(QEMU_LDFLAGS) -o $@ > $(basename $@).map
# Create compile_commands.json for clangd and LSP
.PHONY: clangd
@@ -201,12 +223,12 @@ compile_commands.json:
.PHONY: check
check:
- clang-tidy -header-filter=.* -checks=cert-* $(FIRMWARE_SOURCES) -- $(CFLAGS)
+ clang-tidy -header-filter=.* -checks=cert-* $(CHECK_SOURCES) -- $(CFLAGS)
.PHONY: splint
splint:
splint \
- -nolib \
+ +unixlib \
-predboolint \
+boolint \
-nullpass \
@@ -217,21 +239,27 @@ splint:
-unreachable \
-unqualifiedtrans \
-fullinitblock \
- $(FIRMWARE_SOURCES)
+ +gnuextensions \
+ -fixedformalarray \
+ -mustfreeonly \
+ -I $(LIBDIR)/include \
+ -I $(LIBDIR) \
+ -I $(LIBDIR)/blake2s \
+ $(CHECK_SOURCES)
-testfw.elf: $(TESTFW_OBJS) $(P)/fw/tk1/firmware.lds
- $(CC) $(CFLAGS) $(TESTFW_OBJS) $(LDFLAGS) -o $@
+testfw.elf: tkey-libs $(TESTFW_OBJS) $(P)/fw/tk1/firmware.lds
+ $(CC) $(CFLAGS) $(TESTFW_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map
# Generate a fake BRAM file that will be filled in later after place-n-route
bram_fw.hex:
$(ICESTORM_PATH)icebram -v -g 32 $(BRAM_FW_SIZE) > $@
firmware.hex: firmware.bin firmware_size_mismatch
- python3 $(P)/tools/makehex/makehex.py $< $(BRAM_FW_SIZE) > $@
+ python3 $(P)/tools/makehex.py $< $(BRAM_FW_SIZE) > $@
simfirmware.hex: simfirmware.bin simfirmware_size_mismatch
- python3 $(P)/tools/makehex/makehex.py $< $(BRAM_FW_SIZE) > $@
+ python3 $(P)/tools/makehex.py $< $(BRAM_FW_SIZE) > $@
testfw.hex: testfw.bin testfw_size_mismatch
- python3 $(P)/tools/makehex/makehex.py $< $(BRAM_FW_SIZE) > $@
+ python3 $(P)/tools/makehex.py $< $(BRAM_FW_SIZE) > $@
.PHONY: check-binary-hashes
check-binary-hashes:
@@ -241,9 +269,6 @@ check-binary-hashes:
sha256sum -c application_fpga.bin.sha256
%.bin: %.elf
- $(SIZE) $<
- @test "$$($(SIZE) $< | awk 'NR==2{print $$2, $$3}')" = "0 0" \
- || { printf "Non-empty data or bss section!\n"; false; }
$(OBJCOPY) --input-target=elf32-littleriscv --output-target=binary $< $@
chmod -x $@
@@ -261,7 +286,8 @@ LINT_FLAGS = \
-Wno-WIDTHEXPAND \
-Wno-UNOPTFLAT \
--timescale 1ns/1ns \
- -DNO_ICE40_DEFAULT_ASSIGNMENTS
+ -DNO_ICE40_DEFAULT_ASSIGNMENTS \
+ -Wno-GENUNNAMED
lint: $(FPGA_VERILOG_SRCS) \
$(SIM_VERILOG_SRCS) \
@@ -303,6 +329,9 @@ fmt: $(FPGA_VERILOG_SRCS) $(SIM_VERILOG_SRCS) $(VERILATOR_VERILOG_SRCS) $(VERILO
# Temporary fix using grep, since the verible with --verify flag only returns
# error if the last file is malformatted.
checkfmt: $(FPGA_VERILOG_SRCS) $(SIM_VERILOG_SRCS) $(VERILATOR_VERILOG_SRCS) $(VERILOG_SRCS)
+ make -C fw/tk1 checkfmt
+ make -C fw/testfw checkfmt
+ make -C apps checkfmt
$(FORMAT) $(CHECK_FORMAT_FLAGS) $^ 2>&1 | \
grep "Needs formatting" && exit 1 || true
.PHONY: checkfmt
@@ -353,8 +382,7 @@ tb:
YOSYS_FLAG ?=
-synth.json: $(FPGA_VERILOG_SRCS) $(VERILOG_SRCS) $(PICORV32_SRCS) bram_fw.hex \
- $(P)/data/uds.hex $(P)/data/udi.hex
+synth.json: $(FPGA_VERILOG_SRCS) $(VERILOG_SRCS) $(PICORV32_SRCS) bram_fw.hex
$(YOSYS_PATH)yosys \
-v3 \
-l synth.txt \
@@ -368,7 +396,7 @@ synth.json: $(FPGA_VERILOG_SRCS) $(VERILOG_SRCS) $(PICORV32_SRCS) bram_fw.hex \
application_fpga_par.json: synth.json $(P)/data/$(PIN_FILE)
$(NEXTPNR_PATH)nextpnr-ice40 \
-l application_fpga_par.txt \
- --seed 9106179903728618585 \
+ --seed 18160564147838858264 \
--freq $(TARGET_FREQ) \
--ignore-loops \
--up5k \
@@ -444,17 +472,23 @@ tb_application_fpga: $(SIM_VERILOG_SRCS) \
#-------------------------------------------------------------------
prog_flash: check-hardware application_fpga.bin
- sudo tillitis-iceprog application_fpga.bin
+ tillitis-iceprog application_fpga.bin
+ make -C apps
+ (cd tools && ./load_preloaded_app.sh 0 ../apps/testloadapp.bin)
.PHONY: prog_flash
+prog_flash_bs: check-hardware application_fpga.bin
+ tillitis-iceprog application_fpga.bin
+.PHONY: prog_flash_bs
+
prog_flash_testfw: check-hardware application_fpga_testfw.bin
- sudo tillitis-iceprog application_fpga_testfw.bin
+ tillitis-iceprog application_fpga_testfw.bin
.PHONY: prog_flash_testfw
check-hardware:
- @sudo tillitis-iceprog -t >/dev/null 2>&1 || \
+ @tillitis-iceprog -t >/dev/null 2>&1 || \
{ echo "Programmer not plugged in or not accessible"; false; }
- @if sudo tillitis-iceprog -t 2>&1 | grep -qi "^flash.id:\( 0x\(00\|ff\)\)\{4\}"; then \
+ @if tillitis-iceprog -t 2>&1 | grep -qi "^flash.id:\( 0x\(00\|ff\)\)\{4\}"; then \
echo "No USB stick in the programmer?"; false; else true; fi
.PHONY: check-hardware
@@ -478,18 +512,20 @@ clean: clean_sim clean_fw clean_tb
rm -f lint_issues.txt
rm -f tools/tpt/*.hex
rm -rf tools/tpt/__pycache__
+ make -C apps clean
.PHONY: clean
clean_fw:
- rm -f firmware.{elf,elf.map,bin,hex}
+ rm -f firmware.{elf,map,bin,hex}
rm -f $(FIRMWARE_OBJS)
- rm -f testfw.{elf,elf.map,bin,hex}
+ rm -f testfw.{elf,map,bin,hex}
rm -f $(TESTFW_OBJS)
rm -f qemu_firmware.elf
+ make -C tkey-libs clean
.PHONY: clean_fw
clean_sim:
- rm -f simfirmware.{elf,elf.map,bin,hex}
+ rm -f simfirmware.{elf,map,bin,hex}
rm -f tb_application_fpga_sim.fst
rm -f tb_application_fpga_sim.fst.hier
rm -f tb/output_spram*.hex
@@ -525,7 +561,8 @@ help:
@echo "tb_application_fpga Build testbench simulation for the design"
@echo "lint Run lint on Verilog source files."
@echo "tb Run all testbenches"
- @echo "prog_flash Program device flash with FGPA bitstream including firmware (using the RPi Pico-based programmer)."
+ @echo "prog_flash Program device flash with FGPA bitstream (including firmware), partition table, and testloadapp.bin (using the RPi Pico-based programmer)."
+ @echo "prog_flash_bs Program device flash with FGPA bitstream including firmware (using the RPi Pico-based programmer)."
@echo "prog_flash_testfw Program device flash as above, but with testfw."
@echo "clean Delete all generated files."
@echo "clean_fw Delete only generated files for firmware. Useful for fw devs."
diff --git a/hw/application_fpga/README.md b/hw/application_fpga/README.md
index 1f3ab78..7a9023a 100644
--- a/hw/application_fpga/README.md
+++ b/hw/application_fpga/README.md
@@ -1,4 +1,4 @@
-# TKey hardware design
+## TKey hardware design

@@ -11,16 +11,18 @@ The design top level is in `rtl/application_fpga.v`. It contains
instances of all cores as well as the memory system.
The memory system allows the CPU to access cores in different ways
-given the current execution mode. There are two execution modes -
-firmware and application. Basically, in application mode the access is
-more restrictive.
+given the current execution mode. There are three execution modes -
+firmware, application and system call. Each mode give access to a
+different set of resources. Where app mode is the most restrictive and
+firmware mode is the least restrictive.
-The rest of the components are under `cores`. They typically have
-their own `README.md` file documenting them and their API in detail.
+The rest of the components are under `cores`, except the firmware,
+which is under `fw/tk1`. They typically have their own `README.md`
+file documenting them and their API in detail.
Hardware functions with APIs, assets, and input/output are memory
mapped starting at base address `0xc000_0000`. For specific offsets
-and bitmasks, see the file `fw/tk1_mem.h`.
+and bitmasks, see the file `tk1_mem.h` in tkey-libs.
Rough memory map:
@@ -34,8 +36,14 @@ Rough memory map:
| UART | 0xc3 |
| Touch | 0xc4 |
| FW\_RAM | 0xd0 |
+| Syscall | 0xe1 |
| TK1 | 0xff |
+## Firmware
+
+Firmware is kept in ROM. See the [Firmware implementation
+notes](fw/README.md).
+
## `clk_reset_gen`
Generator for system clock and system reset.
@@ -96,6 +104,54 @@ hours, days) there is also a 32 bit prescaler.
The timer is available to use by firmware and applications.
+## Syscall
+
+System call trigger area. A 32-bit write to address 0xe1000000 will
+trigger interrupt 31, which in turn triggers a system call in the
+firmware.
+
+## Interrupts
+
+Triggering an interrupt will cause the CPU to execute the interrupt
+handler att address 0x10.
+
+The interrupt handler is shared by all PicoRV32 interrupts but only
+interrupt 31 is enabled on the Tkey. Register `x4` can be inspected to
+determine the interrupt source. Each interrupt source is assigned one
+bit in x4. Triggered interrupts have their bit set to `1`.
+
+| *Source* | *x4 Bit* |
+|----------|----------|
+| Syscall | 31 |
+
+The return address is located in register `x3`. Calling the PicoRV32
+specific instruction `retirq` exits the interrupt handler and clears
+the interrupt source.
+
+No registers are stored/restored when entering/exiting the interrupt
+handler. It is up to the software to store/restore as necessary.
+
+Interrupts can be enabled/disabled using the PicoRV32 specific
+`maskirq` instruction.
+
+## Restricted resources
+
+The following table shows resource availablility for each execution
+mode:
+
+| *Execution Mode* | *ROM* | *FW RAM* | *SPI* | *UDS* |
+|------------------|-------|----------|-------|-------|
+| Firmware mode | r/x | r/w | r/w | r/w* |
+| Syscall | r/x | r/w | r/w | i |
+| Application mode | r | i | i | i |
+
+Legend:
+r = readable
+w = writeable
+x = executable
+i = invisible
+* = UDS words are readable only once in firmware mode.
+
## `tk1`
See [tk1 README](core/tk1/README.md) for details.
@@ -107,7 +163,6 @@ Contains:
- RGB LED control.
- General purpose input/output (GPIO) pin control.
- Application introspection: start address and size of binary.
-- BLAKE2s function access.
- Compound Device Identity (CDI).
- Unique Device Identity (UDI).
- RAM memory protection.
@@ -135,13 +190,13 @@ should make it infeasible to improve asset extraction by observing
multiple memory dumps from the same TKey device. The attack should
also not directly scale to multiple TKey devices.
-The RAM address and data scrambling is done in de RAM core.
+The RAM address and data scrambling is done in the RAM core.
The memory protection is setup by the firmware. Access to the memory
protection controls is disabled for applications. Before the memory
protecetion is enabled, the RAM is filled with randomised data using
-Xorwow. So during boot the firmware perform the following steps to
-setup the memory protection:
+Xorwow. During boot the firmware perform the following steps to setup
+the memory protection:
1. Get a random 32-bit value from the TRNG to use as data state for
Xorwow.
diff --git a/hw/application_fpga/application_fpga.bin.sha256 b/hw/application_fpga/application_fpga.bin.sha256
index c24efd9..70c38b2 100644
--- a/hw/application_fpga/application_fpga.bin.sha256
+++ b/hw/application_fpga/application_fpga.bin.sha256
@@ -1 +1 @@
-8b09a7b2c9b864711e19f85de2785c8ea52f454207943c13bb17fff1ce095711 application_fpga.bin
+dfba361c83337c6bac2364d1fd8f115eeb7feeae5faabbdf0713c79b44bbd78d application_fpga.bin
diff --git a/hw/application_fpga/apps/Makefile b/hw/application_fpga/apps/Makefile
new file mode 100644
index 0000000..b8ce38c
--- /dev/null
+++ b/hw/application_fpga/apps/Makefile
@@ -0,0 +1,123 @@
+P := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+LIBDIR ?= ../tkey-libs
+OBJCOPY ?= llvm-objcopy
+CC = clang
+CFLAGS = \
+ -target riscv32-unknown-none-elf \
+ -march=rv32iczmmul \
+ -mabi=ilp32 \
+ -mcmodel=medany \
+ -static \
+ -std=gnu99 \
+ -Os \
+ -ffast-math \
+ -fno-common \
+ -fno-builtin-printf \
+ -fno-builtin-putchar \
+ -fno-builtin-memcpy \
+ -nostdlib \
+ -mno-relax \
+ -Wall \
+ -Wpedantic \
+ -Wno-language-extension-token \
+ -Werror \
+ -flto \
+ -g \
+ -I $(LIBDIR)/include \
+ -I $(LIBDIR) \
+ -I include \
+ -I ../
+
+AS = clang
+
+ASFLAGS = \
+ -target riscv32-unknown-none-elf \
+ -march=rv32iczmmul \
+ -mabi=ilp32 \
+ -mno-relax
+
+LDFLAGS = \
+ -T $(LIBDIR)/app.lds \
+ -L $(LIBDIR) -lcrt0 -lcommon -lmonocypher -lblake2s
+
+.PHONY: all
+all: defaultapp.bin reset_test.bin testapp.bin testloadapp.bin
+
+# Turn elf into bin for device
+%.bin: %.elf
+ $(OBJCOPY) --input-target=elf32-littleriscv --output-target=binary $^ $@
+ chmod a-x $@
+
+.PHONY: tkey-libs
+tkey-libs:
+ make -C $(LIBDIR)
+
+OBJS=syscall.o
+
+# syscall.o: syscall.S
+# $(CC) $(CFLAGS) $(DEFAULTAPP_OBJS) $(LDFLAGS) -o $@
+
+# defaultapp
+DEFAULTAPP_FMTFILES = *.[ch]
+
+DEFAULTAPP_OBJS = \
+ $(P)/defaultapp/main.o
+
+defaultapp.elf: tkey-libs $(OBJS) $(DEFAULTAPP_OBJS)
+ $(CC) $(CFLAGS) $(OBJS) $(DEFAULTAPP_OBJS) $(LDFLAGS) -o $@
+
+# reset_test
+
+RESET_TEST_FMTFILES = *.[ch]
+
+RESET_TEST_OBJS = \
+ $(P)/reset_test/main.o
+
+reset_test.elf: tkey-libs $(RESET_TEST_OBJS)
+ $(CC) $(CFLAGS) $(OBJS) $(RESET_TEST_OBJS) $(LDFLAGS) -o $@
+
+# testapp
+
+TESTAPP_OBJS = \
+ $(P)/testapp/main.o
+
+testapp.elf: tkey-libs $(TESTAPP_OBJS)
+ $(CC) $(CFLAGS) $(OBJS) $(TESTAPP_OBJS) $(LDFLAGS) -o $@
+
+# testloadapp
+
+TESTLOADAPP_OBJS = \
+ $(P)/testloadapp/main.o
+
+testloadapp.elf: tkey-libs $(TESTLOADAPP_OBJS)
+ $(CC) $(CFLAGS) $(OBJS) $(TESTLOADAPP_OBJS) $(LDFLAGS) -o $@
+
+.PHONY: fmt
+fmt:
+ clang-format --dry-run --ferror-limit=0 defaultapp/*.[ch]
+ clang-format --verbose -i defaultapp/*.[ch]
+
+ clang-format --dry-run --ferror-limit=0 reset_test/*.[ch]
+ clang-format --verbose -i reset_test/*.[ch]
+
+ clang-format --dry-run --ferror-limit=0 testapp/*.[ch]
+ clang-format --verbose -i testapp/*.[ch]
+
+ clang-format --dry-run --ferror-limit=0 testloadapp/*.[ch]
+ clang-format --verbose -i testloadapp/*.[ch]
+
+.PHONY: checkfmt
+checkfmt:
+ clang-format --dry-run --ferror-limit=0 defaultapp/*.[ch]
+
+ clang-format --dry-run --ferror-limit=0 reset_test/*.[ch]
+
+ clang-format --dry-run --ferror-limit=0 testapp/*.[ch]
+
+ clang-format --dry-run --ferror-limit=0 testloadapp/*.[ch]
+
+.PHONY: clean
+clean:
+ rm -f *.elf *.bin $(OBJS) $(DEFAULTAPP_OBJS) $(RESET_TEST_OBJS) \
+ $(TESTAPP_OBJS) $(TESTLOADAPP_OBJS)
+
diff --git a/hw/application_fpga/apps/README.md b/hw/application_fpga/apps/README.md
new file mode 100644
index 0000000..ca69f88
--- /dev/null
+++ b/hw/application_fpga/apps/README.md
@@ -0,0 +1,36 @@
+# Test applications
+
+- `defaultapp`: Immediately resets the TKey with the intention to
+ start an app from the client, replicating the behaviour of earlier
+ generations.
+- `testapp`: Runs through a couple of tests that are now impossible
+ to do in the `testfw`.
+- `reset_test`: Interactively test different reset scenarios.
+- `testloadapp`: Interactively test management app things like
+ installing an app (hardcoded for a small happy blinking app, see
+ `blink.h` for the entire binary!) and to test verified boot.
+
+## Build
+
+```
+$ make
+```
+
+will build all the .elf and .bin files on the top level.
+
+## Use
+
+Use `tkey-runapp` from
+[tkey-devtools](https://github.com/tillitis/tkey-devtools) to load the
+apps:
+
+```
+$ tkey-runapp testapp.bin
+```
+
+All of these test apps are controlled through the USB CDC, typically
+by running picocom or similar terminal program, like:
+
+```
+$ picocom /dev/ttyACM1
+```
diff --git a/hw/application_fpga/apps/defaultapp/main.c b/hw/application_fpga/apps/defaultapp/main.c
new file mode 100644
index 0000000..93dd91a
--- /dev/null
+++ b/hw/application_fpga/apps/defaultapp/main.c
@@ -0,0 +1,18 @@
+// Copyright (C) 2025 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include
+#include
+#include
+#include
+#include
+
+int main(void)
+{
+ struct reset rst = {0};
+
+ led_set(LED_BLUE);
+
+ rst.type = START_CLIENT;
+ syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
+}
diff --git a/hw/application_fpga/apps/include/syscall.h b/hw/application_fpga/apps/include/syscall.h
new file mode 100644
index 0000000..8f1b7c1
--- /dev/null
+++ b/hw/application_fpga/apps/include/syscall.h
@@ -0,0 +1,11 @@
+// SPDX-FileCopyrightText: 2024 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include
+
+#ifndef TKEY_APP_SYSCALL_H
+#define TKEY_APP_SYSCALL_H
+
+int syscall(uint32_t number, uint32_t arg1, uint32_t arg2, uint32_t arg3);
+
+#endif
diff --git a/hw/application_fpga/apps/reset_test/main.c b/hw/application_fpga/apps/reset_test/main.c
new file mode 100644
index 0000000..c3b3998
--- /dev/null
+++ b/hw/application_fpga/apps/reset_test/main.c
@@ -0,0 +1,158 @@
+// Copyright (C) 2022, 2023 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// Converts a single hex character to its integer value
+static uint8_t hex_char_to_byte(uint8_t c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ return 0; // Invalid character, should not happen if input is valid
+}
+
+// Converts a 64-char hex string into a 32-byte array
+int hex_string_to_bytes(uint8_t *hex_str, uint8_t *out_bytes, size_t out_len)
+{
+ if (!hex_str || !out_bytes || out_len < 32)
+ return -1; // Error handling
+
+ for (size_t i = 0; i < 32; i++) {
+ out_bytes[i] = (hex_char_to_byte(hex_str[i * 2]) << 4) |
+ hex_char_to_byte(hex_str[i * 2 + 1]);
+ }
+
+ return 0; // Success
+}
+//---------------------------------------
+
+#define BUFSIZE 32
+
+int main(void)
+{
+ uint8_t available = 0;
+ uint8_t cmdbuf[BUFSIZE] = {0};
+ enum ioend endpoint = IO_NONE;
+ led_set(LED_BLUE);
+ struct reset rst = {0};
+
+ while (1) {
+
+ puts(IO_CDC, "reset_test: Waiting for command\r\n");
+
+ memset(cmdbuf, 0, BUFSIZE);
+
+ // Wait for data
+ if (readselect(IO_CDC, &endpoint, &available) < 0) {
+ assert(1 == 2);
+ }
+
+ if (read(IO_CDC, cmdbuf, BUFSIZE, available) < 0) {
+ // read failed! I/O broken? Just redblink.
+ assert(1 == 2);
+ }
+
+ led_set(LED_BLUE | LED_RED);
+
+ switch (cmdbuf[0]) {
+ case '1':
+ // Reset into default state
+
+ rst.type = START_DEFAULT;
+ syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
+ break;
+
+ case '2':
+ // Reset and load app from client
+
+ rst.type = START_CLIENT;
+ syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
+ break;
+
+ case '3':
+ // Reset and load app from second flash slot
+
+ rst.type = START_FLASH1;
+ syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
+ break;
+
+ case '4': {
+ // Reset and load app from client with verification
+ // using an invalid digest.
+ //
+ // Should cause firmware to refuse to start app.
+
+ uint8_t string[] = "0123456789abcdef0123456789abcdef012"
+ "3456789abcdef0123456789abcdef";
+ rst.type = START_CLIENT_VER;
+ hex_string_to_bytes(string, (uint8_t *)&rst.app_digest,
+ sizeof(rst.app_digest));
+ syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
+ } break;
+
+ case '5': {
+ // Reset and load app from client with verification
+ // using a digest matching the example app (blue.bin)
+ // from tkey-libs
+
+ uint8_t tkeylibs_example_app_digest[] =
+ "96bb4c90603dbbbe09b9a1d7259b5e9e61bedd89a897105c30"
+ "c9d4bf66a98d97";
+ rst.type = START_CLIENT_VER;
+ hex_string_to_bytes(tkeylibs_example_app_digest,
+ (uint8_t *)&rst.app_digest,
+ sizeof(rst.app_digest));
+ syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
+ } break;
+
+ case '6': {
+ // Reset and load app from second flash slot with
+ // verification using an invalid digest.
+ //
+ // Should cause firmware to refuse to start app.
+
+ uint8_t string[] = "0123456789abcdef0123456789abcdef012"
+ "3456789abcdef0123456789abcdef";
+ rst.type = START_FLASH1_VER;
+ hex_string_to_bytes(string, (uint8_t *)&rst.app_digest,
+ sizeof(rst.app_digest));
+ syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
+ } break;
+
+ case '7': {
+ // Reset and load app from second flash slot with
+ // verification using a digest matching the example app
+ // (blue.bin) from tkey-libs
+ //
+ // Blue.bin has to be present on flash in the second
+ // preloaded app slot (slot 1).
+
+ uint8_t tkeylibs_example_app_digest[] =
+ "96bb4c90603dbbbe09b9a1d7259b5e9e61bedd89a897105c30"
+ "c9d4bf66a98d97";
+ rst.type = START_FLASH1_VER;
+ hex_string_to_bytes(tkeylibs_example_app_digest,
+ (uint8_t *)&rst.app_digest,
+ sizeof(rst.app_digest));
+ syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
+ } break;
+
+ default:
+ break;
+ }
+ }
+}
diff --git a/hw/application_fpga/apps/syscall.S b/hw/application_fpga/apps/syscall.S
new file mode 100644
index 0000000..17712ab
--- /dev/null
+++ b/hw/application_fpga/apps/syscall.S
@@ -0,0 +1,85 @@
+// SPDX-FileCopyrightText: 2024 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include "../fw/tk1/picorv32/custom_ops.S"
+
+ .section ".text"
+ .globl syscall
+
+
+syscall:
+ // Save registers to stack
+ addi sp, sp, -32*4
+ sw x0, 0*4(sp)
+ sw x1, 1*4(sp)
+ // x2 (sp) is assumed to be preserved by the interrupt handler.
+ sw x3, 3*4(sp)
+ sw x4, 4*4(sp)
+ sw x5, 5*4(sp)
+ sw x6, 6*4(sp)
+ sw x7, 7*4(sp)
+ sw x8, 8*4(sp)
+ sw x9, 9*4(sp)
+ // x10 (a0) will contain syscall return value. And should not be saved.
+ sw x11, 11*4(sp)
+ sw x12, 12*4(sp)
+ sw x13, 13*4(sp)
+ sw x14, 14*4(sp)
+ sw x15, 15*4(sp)
+ sw x16, 16*4(sp)
+ sw x17, 17*4(sp)
+ sw x18, 18*4(sp)
+ sw x19, 19*4(sp)
+ sw x20, 20*4(sp)
+ sw x21, 21*4(sp)
+ sw x22, 22*4(sp)
+ sw x23, 23*4(sp)
+ sw x24, 24*4(sp)
+ sw x25, 25*4(sp)
+ sw x26, 26*4(sp)
+ sw x27, 27*4(sp)
+ sw x28, 28*4(sp)
+ sw x29, 29*4(sp)
+ sw x30, 30*4(sp)
+ sw x31, 31*4(sp)
+
+ // Trigger syscall interrupt
+ li t1, 0xe1000000 // Syscall interrupt trigger address
+ sw zero, 0(t1) // Trigger interrupt
+
+ // Restore registers from stack
+ lw x0, 0*4(sp)
+ lw x1, 1*4(sp)
+ // x2 (sp) is assumed to be preserved by the interrupt handler.
+ lw x3, 3*4(sp)
+ lw x4, 4*4(sp)
+ lw x5, 5*4(sp)
+ lw x6, 6*4(sp)
+ lw x7, 7*4(sp)
+ lw x8, 8*4(sp)
+ lw x9, 9*4(sp)
+ // x10 (a0) contains syscall return value. And should not be destroyed.
+ lw x11, 11*4(sp)
+ lw x12, 12*4(sp)
+ lw x13, 13*4(sp)
+ lw x14, 14*4(sp)
+ lw x15, 15*4(sp)
+ lw x16, 16*4(sp)
+ lw x17, 17*4(sp)
+ lw x18, 18*4(sp)
+ lw x19, 19*4(sp)
+ lw x20, 20*4(sp)
+ lw x21, 21*4(sp)
+ lw x22, 22*4(sp)
+ lw x23, 23*4(sp)
+ lw x24, 24*4(sp)
+ lw x25, 25*4(sp)
+ lw x26, 26*4(sp)
+ lw x27, 27*4(sp)
+ lw x28, 28*4(sp)
+ lw x29, 29*4(sp)
+ lw x30, 30*4(sp)
+ lw x31, 31*4(sp)
+ addi sp, sp, 32*4
+
+ ret
diff --git a/hw/application_fpga/apps/testapp/main.c b/hw/application_fpga/apps/testapp/main.c
new file mode 100644
index 0000000..6b773d0
--- /dev/null
+++ b/hw/application_fpga/apps/testapp/main.c
@@ -0,0 +1,295 @@
+// Copyright (C) 2022, 2023 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "syscall.h"
+
+#define USBMODE_PACKET_SIZE 64
+
+// clang-format off
+volatile uint32_t *tk1name0 = (volatile uint32_t *)TK1_MMIO_TK1_NAME0;
+volatile uint32_t *tk1name1 = (volatile uint32_t *)TK1_MMIO_TK1_NAME1;
+volatile uint32_t *tk1version = (volatile uint32_t *)TK1_MMIO_TK1_VERSION;
+volatile uint32_t *uds = (volatile uint32_t *)TK1_MMIO_UDS_FIRST;
+volatile uint32_t *cdi = (volatile uint32_t *)TK1_MMIO_TK1_CDI_FIRST;
+volatile uint32_t *udi = (volatile uint32_t *)TK1_MMIO_TK1_UDI_FIRST;
+volatile uint8_t *fw_ram = (volatile uint8_t *)TK1_MMIO_FW_RAM_BASE;
+volatile uint32_t *system_reset = (volatile uint32_t *)TK1_MMIO_TK1_SYSTEM_RESET;
+volatile uint32_t *timer = (volatile uint32_t *)TK1_MMIO_TIMER_TIMER;
+volatile uint32_t *timer_prescaler = (volatile uint32_t *)TK1_MMIO_TIMER_PRESCALER;
+volatile uint32_t *timer_status = (volatile uint32_t *)TK1_MMIO_TIMER_STATUS;
+volatile uint32_t *timer_ctrl = (volatile uint32_t *)TK1_MMIO_TIMER_CTRL;
+volatile uint32_t *trng_status = (volatile uint32_t *)TK1_MMIO_TRNG_STATUS;
+volatile uint32_t *trng_entropy = (volatile uint32_t *)TK1_MMIO_TRNG_ENTROPY;
+// clang-format on
+
+#define UDS_WORDS 8
+#define UDI_WORDS 2
+#define CDI_WORDS 8
+
+void puthexn(uint8_t *p, int n)
+{
+ for (int i = 0; i < n; i++) {
+ puthex(IO_CDC, p[i]);
+ }
+}
+
+void reverseword(uint32_t *wordp)
+{
+ *wordp = ((*wordp & 0xff000000) >> 24) | ((*wordp & 0x00ff0000) >> 8) |
+ ((*wordp & 0x0000ff00) << 8) | ((*wordp & 0x000000ff) << 24);
+}
+
+uint32_t wait_timer_tick(uint32_t last_timer)
+{
+ uint32_t newtimer;
+ for (;;) {
+ newtimer = *timer;
+ if (newtimer != last_timer) {
+ return newtimer;
+ }
+ }
+}
+
+void failmsg(char *s)
+{
+ puts(IO_CDC, "FAIL: ");
+ puts(IO_CDC, s);
+ puts(IO_CDC, "\r\n");
+}
+
+int main(void)
+{
+ uint8_t in = 0;
+ uint8_t available = 0;
+ enum ioend endpoint = IO_NONE;
+
+ led_set(LED_BLUE);
+
+ // Wait for terminal program and a character to be typed
+ if (readselect(IO_CDC, &endpoint, &available) < 0) {
+ // readselect failed! I/O broken? Just redblink.
+ assert(1 == 2);
+ }
+
+ if (read(IO_CDC, &in, 1, 1) < 0) {
+ // read failed! I/O broken? Just redblink.
+ assert(1 == 2);
+ }
+
+ puts(IO_CDC, "\r\nI'm testapp on:");
+ // Output the TK1 core's NAME0 and NAME1
+ uint32_t name;
+ wordcpy_s(&name, 1, (void *)tk1name0, 1);
+ reverseword(&name);
+ write(IO_CDC, (const uint8_t *)&name, 4);
+ puts(IO_CDC, " ");
+ wordcpy_s(&name, 1, (void *)tk1name1, 1);
+ reverseword(&name);
+ write(IO_CDC, (const uint8_t *)&name, 4);
+ puts(IO_CDC, "\r\n");
+ puts(IO_CDC, "Version: ");
+ putinthex(IO_CDC, *tk1version);
+ puts(IO_CDC, "\r\n");
+
+ uint32_t zeros[8];
+ memset(zeros, 0, 8 * 4);
+
+ int anyfailed = 0;
+
+ uint32_t uds_local[UDS_WORDS];
+ uint32_t udi_local[UDI_WORDS];
+
+ // Should NOT be able to read from UDS in app-mode.
+ wordcpy_s(uds_local, UDS_WORDS, (void *)uds, UDS_WORDS);
+ if (!memeq(uds_local, zeros, UDS_WORDS * 4)) {
+ failmsg("Read from UDS in app-mode");
+ anyfailed = 1;
+ }
+
+ // Should NOT be able to read from UDI in app-mode.
+ wordcpy_s(udi_local, UDI_WORDS, (void *)udi, UDI_WORDS);
+ if (!memeq(udi_local, zeros, UDI_WORDS * 4)) {
+ failmsg("Read from UDI in app-mode");
+ anyfailed = 1;
+ }
+
+ // But a syscall to get parts of UDI should be able to run
+ int vidpid = syscall(TK1_SYSCALL_GET_VIDPID, 0, 0, 0);
+
+ if (vidpid != 0x073570c0) {
+ failmsg("Expected VID/PID to be 0x073570c0");
+ anyfailed = 1;
+ }
+
+ puts(IO_CDC, "\r\nAllocating storage area...");
+
+ if (syscall(TK1_SYSCALL_ALLOC_AREA, 0, 0, 0) != 0) {
+ failmsg("Failed to allocate storage area");
+ }
+ puts(IO_CDC, "done.\r\n");
+
+ puts(IO_CDC, "\r\nWriting to storage area...");
+
+ uint8_t out_data[14] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
+ if (syscall(TK1_SYSCALL_WRITE_DATA, 0, (uint32_t)out_data,
+ sizeof(out_data)) != 0) {
+ failmsg("Failed to write to storage area");
+ }
+ puts(IO_CDC, "done.\r\n");
+
+ puts(IO_CDC, "\r\nReading data from storage area...");
+
+ uint8_t in_data[14] = {0};
+ if (syscall(TK1_SYSCALL_READ_DATA, 0, (uint32_t)in_data,
+ sizeof(in_data)) != 0) {
+ failmsg("Failed to write to storage area");
+ }
+ if (!memeq(in_data, out_data, sizeof(in_data))) {
+ failmsg("Failed to read back data from storage area");
+ anyfailed = 1;
+ }
+ puts(IO_CDC, "done.\r\n");
+
+ puts(IO_CDC, "\r\nErasing written data from storage area...");
+
+ if (syscall(TK1_SYSCALL_ERASE_DATA, 0, 4096, 0) != 0) {
+ failmsg("Failed to erase storage area");
+ }
+ puts(IO_CDC, "done.\r\n");
+
+ puts(IO_CDC, "\r\nVerify erased storage area data...");
+
+ if (syscall(TK1_SYSCALL_READ_DATA, 0, (uint32_t)in_data,
+ sizeof(in_data)) != 0) {
+ failmsg("Failed to write to storage area");
+ }
+ uint8_t check_data[sizeof(in_data)] = {0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff};
+ if (!memeq(in_data, check_data, sizeof(check_data))) {
+ failmsg("Failed to read back data from storage area");
+ anyfailed = 1;
+ }
+ puts(IO_CDC, "done.\r\n");
+
+ puts(IO_CDC, "\r\nDeallocating storage area...");
+
+ if (syscall(TK1_SYSCALL_DEALLOC_AREA, 0, 0, 0) != 0) {
+ failmsg("Failed to deallocate storage area");
+ }
+ puts(IO_CDC, "done.\r\n");
+
+ uint32_t cdi_local[CDI_WORDS];
+ uint32_t cdi_local2[CDI_WORDS];
+ wordcpy_s(cdi_local, CDI_WORDS, (void *)cdi, CDI_WORDS);
+
+ // Write to CDI should NOT have any effect in app mode.
+ wordcpy_s((void *)cdi, CDI_WORDS, zeros, CDI_WORDS);
+ wordcpy_s(cdi_local2, CDI_WORDS, (void *)cdi, CDI_WORDS);
+ if (!memeq(cdi_local, cdi_local2, CDI_WORDS * 4)) {
+ failmsg("Write to CDI in app-mode");
+ anyfailed = 1;
+ }
+
+ // Should NOT be able to reset Tkey from app mode
+ puts(IO_CDC, "\r\nTesting system reset...");
+ *system_reset = 1;
+ puts(IO_CDC, "done.\r\n");
+
+ // Test FW_RAM.
+ *fw_ram = 0x21;
+ if (*fw_ram == 0x21) {
+ failmsg("Write and read FW RAM in app-mode");
+ anyfailed = 1;
+ }
+
+ puts(IO_CDC, "\r\nTesting timer... 3");
+ // Matching clock at 24 MHz, giving us timer in seconds
+ *timer_prescaler = 24 * 1000000;
+
+ // Test timer expiration after 1s
+ *timer = 1;
+ // Start the timer
+ *timer_ctrl = (1 << TK1_MMIO_TIMER_CTRL_START_BIT);
+ while (*timer_status & (1 << TK1_MMIO_TIMER_STATUS_RUNNING_BIT)) {
+ }
+ // Now timer has expired and is ready to run again
+ puts(IO_CDC, " 2");
+
+ // Test to interrupt a timer - and reads from timer register
+ // Starting 10s timer and interrupting it in 3s...
+ *timer = 10;
+ *timer_ctrl = (1 << TK1_MMIO_TIMER_CTRL_START_BIT);
+ uint32_t last_timer = 10;
+ for (int i = 0; i < 3; i++) {
+ last_timer = wait_timer_tick(last_timer);
+ }
+
+ // Stop the timer
+ *timer_ctrl = (1 << TK1_MMIO_TIMER_CTRL_STOP_BIT);
+ puts(IO_CDC, " 1. done.\r\n");
+
+ if (*timer_status & (1 << TK1_MMIO_TIMER_STATUS_RUNNING_BIT)) {
+ failmsg("Timer didn't stop");
+ anyfailed = 1;
+ }
+
+ if (*timer != 10) {
+ failmsg("Timer didn't reset to 10");
+ anyfailed = 1;
+ }
+
+ // Check and display test results.
+ puts(IO_CDC, "\r\n--> ");
+ if (anyfailed) {
+ puts(IO_CDC, "Some test FAILED!\r\n");
+ } else {
+ puts(IO_CDC, "All tests passed.\r\n");
+ }
+
+ puts(IO_CDC, "\r\nHere are 256 bytes from the TRNG:\r\n");
+ for (int j = 0; j < 8; j++) {
+ for (int i = 0; i < 8; i++) {
+ while ((*trng_status &
+ (1 << TK1_MMIO_TRNG_STATUS_READY_BIT)) == 0) {
+ }
+ uint32_t rnd = *trng_entropy;
+ puthexn((uint8_t *)&rnd, 4);
+ puts(IO_CDC, " ");
+ }
+ puts(IO_CDC, "\r\n");
+ }
+ puts(IO_CDC, "\r\n");
+
+ puts(IO_CDC, "Now echoing what you type...Type + to reset device\r\n");
+ for (;;) {
+ if (readselect(IO_CDC, &endpoint, &available) < 0) {
+ // readselect failed! I/O broken? Just redblink.
+ assert(1 == 2);
+ }
+
+ if (read(IO_CDC, &in, 1, 1) < 0) {
+ // read failed! I/O broken? Just redblink.
+ assert(1 == 2);
+ }
+
+ if (in == '+') {
+ struct reset rst;
+ memset(&rst, 0, sizeof(rst));
+ rst.type = START_DEFAULT;
+ syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
+ }
+
+ write(IO_CDC, &in, 1);
+ }
+}
diff --git a/hw/application_fpga/apps/testloadapp/blink.h b/hw/application_fpga/apps/testloadapp/blink.h
new file mode 100644
index 0000000..9aeb21f
--- /dev/null
+++ b/hw/application_fpga/apps/testloadapp/blink.h
@@ -0,0 +1,31 @@
+// Copyright (C) 2025 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifndef BLINK_APP_H
+#define BLINK_APP_H
+
+uint8_t blink[] = {
+ 0x81, 0x40, 0x01, 0x41, 0x81, 0x41, 0x01, 0x42, 0x81, 0x42, 0x01, 0x43,
+ 0x81, 0x43, 0x01, 0x44, 0x81, 0x44, 0x01, 0x45, 0x81, 0x45, 0x01, 0x46,
+ 0x81, 0x46, 0x01, 0x47, 0x81, 0x47, 0x01, 0x48, 0x81, 0x48, 0x01, 0x49,
+ 0x81, 0x49, 0x01, 0x4a, 0x81, 0x4a, 0x01, 0x4b, 0x81, 0x4b, 0x01, 0x4c,
+ 0x81, 0x4c, 0x01, 0x4d, 0x81, 0x4d, 0x01, 0x4e, 0x81, 0x4e, 0x01, 0x4f,
+ 0x81, 0x4f, 0x37, 0x01, 0x02, 0x40, 0x41, 0x11, 0x17, 0x05, 0x00, 0x00,
+ 0x13, 0x05, 0x45, 0x0c, 0x97, 0x05, 0x00, 0x00, 0x93, 0x85, 0xc5, 0x0b,
+ 0x63, 0x57, 0xb5, 0x00, 0x23, 0x20, 0x05, 0x00, 0x11, 0x05, 0xe3, 0x4d,
+ 0xb5, 0xfe, 0x97, 0x00, 0x00, 0x00, 0xe7, 0x80, 0xa0, 0x00, 0x00, 0x00,
+ 0x41, 0x11, 0x37, 0x05, 0x00, 0xff, 0x11, 0x48, 0xe1, 0x66, 0x13, 0x86,
+ 0xf6, 0x69, 0x93, 0x86, 0x06, 0x6a, 0x09, 0x47, 0x85, 0x47, 0x23, 0x22,
+ 0x05, 0x03, 0x02, 0xc2, 0x92, 0x45, 0x63, 0x68, 0xb6, 0x00, 0x92, 0x45,
+ 0x85, 0x05, 0x2e, 0xc2, 0x92, 0x45, 0xe3, 0xec, 0xd5, 0xfe, 0x58, 0xd1,
+ 0x02, 0xc4, 0xa2, 0x45, 0x63, 0x68, 0xb6, 0x00, 0xa2, 0x45, 0x85, 0x05,
+ 0x2e, 0xc4, 0xa2, 0x45, 0xe3, 0xec, 0xd5, 0xfe, 0x5c, 0xd1, 0x02, 0xc6,
+ 0xb2, 0x45, 0xe3, 0x66, 0xb6, 0xfc, 0xb2, 0x45, 0x85, 0x05, 0x2e, 0xc6,
+ 0xb2, 0x45, 0xe3, 0xec, 0xd5, 0xfe, 0x75, 0xbf, 0x19, 0xca, 0x2a, 0x96,
+ 0xaa, 0x86, 0x03, 0xc7, 0x05, 0x00, 0x23, 0x80, 0xe6, 0x00, 0x85, 0x06,
+ 0x85, 0x05, 0xe3, 0x9a, 0xc6, 0xfe, 0x82, 0x80, 0x11, 0xca, 0x0a, 0x06,
+ 0x2a, 0x96, 0xaa, 0x86, 0x98, 0x41, 0x98, 0xc2, 0x91, 0x06, 0x91, 0x05,
+ 0xe3, 0x9c, 0xc6, 0xfe, 0x82, 0x80, 0x01, 0xca, 0x2a, 0x96, 0xaa, 0x86,
+ 0x23, 0x80, 0xb6, 0x00, 0x85, 0x06, 0xe3, 0x9d, 0xc6, 0xfe, 0x82, 0x80};
+
+#endif
diff --git a/hw/application_fpga/apps/testloadapp/main.c b/hw/application_fpga/apps/testloadapp/main.c
new file mode 100644
index 0000000..98300ac
--- /dev/null
+++ b/hw/application_fpga/apps/testloadapp/main.c
@@ -0,0 +1,228 @@
+// Copyright (C) 2025 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "blink.h"
+#include "syscall.h"
+
+// clang-format off
+static volatile uint32_t *cdi = (volatile uint32_t *) TK1_MMIO_TK1_CDI_FIRST;
+// clang-format on
+
+int install_app(uint8_t secret_key[64])
+{
+ uint8_t app_digest[32];
+ uint8_t app_signature[64];
+ size_t app_size = sizeof(blink);
+ int ret = 0;
+
+ ret = syscall(TK1_SYSCALL_PRELOAD_DELETE, 0, 0, 0);
+
+ if (ret != 0) {
+ puts(IO_CDC, "couldn't delete preloaded app. error: 0x");
+ putinthex(IO_CDC, ret);
+ puts(IO_CDC, "\r\n");
+
+ return -1;
+ }
+
+ ret = syscall(TK1_SYSCALL_PRELOAD_STORE, 0, (uint32_t)blink,
+ sizeof(blink));
+
+ if (ret != 0) {
+ puts(IO_CDC, "couldn't store app, error: 0x");
+ putinthex(IO_CDC, ret);
+ puts(IO_CDC, "\r\n");
+
+ return -1;
+ }
+
+ puts(IO_CDC, "blink: ");
+ putinthex(IO_CDC, (uint32_t)blink);
+ puts(IO_CDC, "\r\n");
+
+ puts(IO_CDC, "blink[0]: ");
+ putinthex(IO_CDC, blink[0]);
+ puts(IO_CDC, "\r\n");
+
+ puts(IO_CDC, "sizeof(blink): ");
+ putinthex(IO_CDC, sizeof(blink));
+ puts(IO_CDC, "\r\n");
+
+ if (blake2s(app_digest, 32, NULL, 0, blink, sizeof(blink)) != 0) {
+ puts(IO_CDC, "couldn't compute digest\r\n");
+ return -1;
+ }
+
+ crypto_ed25519_sign(app_signature, secret_key, app_digest,
+ sizeof(app_digest));
+
+ puts(IO_CDC, "app_digest:\r\n");
+ hexdump(IO_CDC, app_digest, sizeof(app_digest));
+ puts(IO_CDC, "\r\n");
+
+ puts(IO_CDC, "app_signature:\r\n");
+ hexdump(IO_CDC, app_signature, sizeof(app_signature));
+ puts(IO_CDC, "\r\n");
+
+ puts(IO_CDC, "secret_key:\r\n");
+ hexdump(IO_CDC, secret_key, 64);
+ puts(IO_CDC, "\r\n");
+
+ ret = syscall(TK1_SYSCALL_PRELOAD_STORE_FIN, app_size,
+ (uint32_t)app_digest, (uint32_t)app_signature);
+
+ if (ret != 0) {
+ puts(IO_CDC, "couldn't finalize storing app, error:");
+ putinthex(IO_CDC, ret);
+ puts(IO_CDC, "\r\n");
+
+ return -1;
+ }
+
+ return 0;
+}
+
+int verify(uint8_t pubkey[32])
+{
+ uint8_t app_digest[32];
+ uint8_t app_signature[64];
+ int ret = 0;
+
+ // pubkey we already have
+ // read signature
+ // read digest
+ ret = syscall(TK1_SYSCALL_PRELOAD_GET_DIGSIG, (uint32_t)app_digest,
+ (uint32_t)app_signature, 0);
+
+ if (ret != 0) {
+ puts(IO_CDC, "couldn't get digsig, error:");
+ putinthex(IO_CDC, ret);
+ puts(IO_CDC, "\r\n");
+
+ return -1;
+ }
+
+ puts(IO_CDC, "app_digest:\r\n");
+ hexdump(IO_CDC, app_digest, sizeof(app_digest));
+ puts(IO_CDC, "\r\n");
+
+ puts(IO_CDC, "app_signature:\r\n");
+ hexdump(IO_CDC, app_signature, sizeof(app_signature));
+ puts(IO_CDC, "\r\n");
+
+ puts(IO_CDC, "pubkey:\r\n");
+ hexdump(IO_CDC, pubkey, 32);
+ puts(IO_CDC, "\r\n");
+
+ puts(IO_CDC, "Checking signature...\r\n");
+
+ if (crypto_ed25519_check(app_signature, pubkey, app_digest,
+ sizeof(app_digest)) != 0) {
+ puts(IO_CDC, "signature check failed\r\n");
+
+ return -1;
+ }
+
+ puts(IO_CDC, "Resetting into pre loaded app (slot 2)...\r\n");
+
+ // syscall reset flash1_ver with app_digest
+ struct reset rst;
+ rst.type = START_FLASH1_VER;
+ memcpy_s(rst.app_digest, sizeof(rst.app_digest), app_digest,
+ sizeof(app_digest));
+ memset(rst.next_app_data, 0, sizeof(rst.next_app_data));
+
+ syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
+
+ return -2;
+}
+
+void reset_from_client(void)
+{
+ struct reset rst = {0};
+
+ rst.type = START_CLIENT;
+
+ // Give the next in chain something to look at.
+ memset(rst.next_app_data, 17, sizeof(rst.next_app_data));
+
+ syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, sizeof(rst.next_app_data),
+ 0);
+}
+
+int main(void)
+{
+ uint8_t secret_key[64];
+ uint8_t pubkey[32];
+ enum ioend endpoint;
+ uint8_t available;
+ uint8_t in = 0;
+
+ led_set(LED_BLUE);
+
+ // Generate a key pair from CDI
+ crypto_ed25519_key_pair(secret_key, pubkey, (uint8_t *)cdi);
+
+ if (readselect(IO_CDC, &endpoint, &available) < 0) {
+ // readselect failed! I/O broken? Just redblink.
+ assert(1 == 2);
+ }
+
+ if (read(IO_CDC, &in, 1, 1) < 0) {
+ // read failed! I/O broken? Just redblink.
+ assert(1 == 2);
+ }
+
+ puts(IO_CDC, "Hello from testloadapp! 0 = install app in slot 1, 1 = "
+ "verify app, 2 == load app from client\r\n");
+
+ for (;;) {
+ if (readselect(IO_CDC, &endpoint, &available) < 0) {
+ // readselect failed! I/O broken? Just redblink.
+ assert(1 == 2);
+ }
+
+ if (read(IO_CDC, &in, 1, 1) < 0) {
+ // read failed! I/O broken? Just redblink.
+ assert(1 == 2);
+ }
+
+ switch (in) {
+ case '0':
+ if (install_app(secret_key) < 0) {
+ puts(IO_CDC, "Failed to install app\r\n");
+ } else {
+ puts(IO_CDC, "Installed app!\r\n");
+ }
+
+ break;
+
+ case '1':
+ if (verify(pubkey) < 0) {
+ puts(IO_CDC, "Failed to verify app\r\n");
+ } else {
+ puts(IO_CDC, "Verified app!\r\n");
+ }
+
+ break;
+
+ case '2':
+ reset_from_client();
+ break;
+
+ default:
+ break;
+ }
+ }
+}
diff --git a/hw/application_fpga/core/fw_ram/README.md b/hw/application_fpga/core/fw_ram/README.md
index 17587dc..d5583ab 100644
--- a/hw/application_fpga/core/fw_ram/README.md
+++ b/hw/application_fpga/core/fw_ram/README.md
@@ -21,6 +21,6 @@ The contents of the fw_ram is cleared when the FPGA is powered up and
configured by the bitstream. The contents is not cleared by a system
reset.
-If the system_mode input is set, i.e. in app mode, no memory
-accesses are allowed. Any reads when the system_mode is set will
+If the app_mode input is set, i.e. in app mode, no memory
+accesses are allowed. Any reads when the app_mode is set will
return an all zero word.
diff --git a/hw/application_fpga/core/fw_ram/rtl/fw_ram.v b/hw/application_fpga/core/fw_ram/rtl/fw_ram.v
index 150f6b5..77bfe2a 100644
--- a/hw/application_fpga/core/fw_ram/rtl/fw_ram.v
+++ b/hw/application_fpga/core/fw_ram/rtl/fw_ram.v
@@ -17,11 +17,11 @@ module fw_ram (
input wire clk,
input wire reset_n,
- input wire system_mode,
+ input wire app_mode,
input wire cs,
input wire [ 3 : 0] we,
- input wire [ 8 : 0] address,
+ input wire [ 9 : 0] address,
input wire [31 : 0] write_data,
output wire [31 : 0] read_data,
output wire ready
@@ -34,77 +34,272 @@ module fw_ram (
reg [31 : 0] tmp_read_data;
reg [31 : 0] mem_read_data0;
reg [31 : 0] mem_read_data1;
+ reg [31 : 0] mem_read_data2;
+ reg [31 : 0] mem_read_data3;
reg ready_reg;
- wire system_mode_cs;
+ wire app_mode_cs;
reg bank0;
reg bank1;
+ reg bank2;
+ reg bank3;
//----------------------------------------------------------------
// Concurrent assignment of ports.
//----------------------------------------------------------------
- assign read_data = tmp_read_data;
- assign ready = ready_reg;
- assign system_mode_cs = cs && ~system_mode;
+ assign read_data = tmp_read_data;
+ assign ready = ready_reg;
+ assign app_mode_cs = cs && ~app_mode;
//----------------------------------------------------------------
// Block RAM instances.
//----------------------------------------------------------------
- SB_RAM40_4K fw_ram0_0 (
+ SB_RAM40_4K #(
+ .INIT_0(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_1(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_2(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_3(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_4(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_5(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_6(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_7(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_8(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_9(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_A(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_B(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_C(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_D(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_E(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_F(256'h0000000000000000000000000000000000000000000000000000000000000000)
+ ) fw_ram0_0 (
.RDATA(mem_read_data0[15 : 0]),
.RADDR({3'h0, address[7 : 0]}),
.RCLK(clk),
.RCLKE(1'h1),
- .RE(system_mode_cs & bank0),
+ .RE(app_mode_cs & bank0),
.WADDR({3'h0, address[7 : 0]}),
.WCLK(clk),
.WCLKE(1'h1),
.WDATA(write_data[15 : 0]),
- .WE((|we & system_mode_cs & bank0)),
+ .WE((|we & app_mode_cs & bank0)),
.MASK({{8{~we[1]}}, {8{~we[0]}}})
);
- SB_RAM40_4K fw_ram0_1 (
+ SB_RAM40_4K #(
+ .INIT_0(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_1(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_2(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_3(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_4(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_5(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_6(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_7(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_8(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_9(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_A(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_B(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_C(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_D(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_E(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_F(256'h0000000000000000000000000000000000000000000000000000000000000000)
+ ) fw_ram0_1 (
.RDATA(mem_read_data0[31 : 16]),
.RADDR({3'h0, address[7 : 0]}),
.RCLK(clk),
.RCLKE(1'h1),
- .RE(system_mode_cs & bank0),
+ .RE(app_mode_cs & bank0),
.WADDR({3'h0, address[7 : 0]}),
.WCLK(clk),
.WCLKE(1'h1),
.WDATA(write_data[31 : 16]),
- .WE((|we & system_mode_cs & bank0)),
+ .WE((|we & app_mode_cs & bank0)),
.MASK({{8{~we[3]}}, {8{~we[2]}}})
);
-
- SB_RAM40_4K fw_ram1_0 (
+ SB_RAM40_4K #(
+ .INIT_0(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_1(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_2(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_3(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_4(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_5(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_6(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_7(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_8(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_9(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_A(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_B(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_C(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_D(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_E(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_F(256'h0000000000000000000000000000000000000000000000000000000000000000)
+ ) fw_ram1_0 (
.RDATA(mem_read_data1[15 : 0]),
.RADDR({3'h0, address[7 : 0]}),
.RCLK(clk),
.RCLKE(1'h1),
- .RE(system_mode_cs & bank1),
+ .RE(app_mode_cs & bank1),
.WADDR({3'h0, address[7 : 0]}),
.WCLK(clk),
.WCLKE(1'h1),
.WDATA(write_data[15 : 0]),
- .WE((|we & system_mode_cs & bank1)),
+ .WE((|we & app_mode_cs & bank1)),
.MASK({{8{~we[1]}}, {8{~we[0]}}})
);
- SB_RAM40_4K fw_ram1_1 (
+ SB_RAM40_4K #(
+ .INIT_0(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_1(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_2(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_3(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_4(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_5(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_6(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_7(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_8(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_9(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_A(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_B(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_C(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_D(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_E(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_F(256'h0000000000000000000000000000000000000000000000000000000000000000)
+ ) fw_ram1_1 (
.RDATA(mem_read_data1[31 : 16]),
.RADDR({3'h0, address[7 : 0]}),
.RCLK(clk),
.RCLKE(1'h1),
- .RE(system_mode_cs & bank1),
+ .RE(app_mode_cs & bank1),
.WADDR({3'h0, address[7 : 0]}),
.WCLK(clk),
.WCLKE(1'h1),
.WDATA(write_data[31 : 16]),
- .WE((|we & system_mode_cs & bank1)),
+ .WE((|we & app_mode_cs & bank1)),
+ .MASK({{8{~we[3]}}, {8{~we[2]}}})
+ );
+
+ SB_RAM40_4K #(
+ .INIT_0(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_1(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_2(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_3(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_4(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_5(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_6(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_7(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_8(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_9(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_A(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_B(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_C(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_D(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_E(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_F(256'h0000000000000000000000000000000000000000000000000000000000000000)
+ ) fw_ram2_0 (
+ .RDATA(mem_read_data2[15 : 0]),
+ .RADDR({3'h0, address[7 : 0]}),
+ .RCLK(clk),
+ .RCLKE(1'h1),
+ .RE(app_mode_cs & bank2),
+ .WADDR({3'h0, address[7 : 0]}),
+ .WCLK(clk),
+ .WCLKE(1'h1),
+ .WDATA(write_data[15 : 0]),
+ .WE((|we & app_mode_cs & bank2)),
+ .MASK({{8{~we[1]}}, {8{~we[0]}}})
+ );
+
+ SB_RAM40_4K #(
+ .INIT_0(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_1(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_2(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_3(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_4(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_5(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_6(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_7(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_8(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_9(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_A(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_B(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_C(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_D(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_E(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_F(256'h0000000000000000000000000000000000000000000000000000000000000000)
+ ) fw_ram2_1 (
+ .RDATA(mem_read_data2[31 : 16]),
+ .RADDR({3'h0, address[7 : 0]}),
+ .RCLK(clk),
+ .RCLKE(1'h1),
+ .RE(app_mode_cs & bank2),
+ .WADDR({3'h0, address[7 : 0]}),
+ .WCLK(clk),
+ .WCLKE(1'h1),
+ .WDATA(write_data[31 : 16]),
+ .WE((|we & app_mode_cs & bank2)),
+ .MASK({{8{~we[3]}}, {8{~we[2]}}})
+ );
+
+ SB_RAM40_4K #(
+ .INIT_0(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_1(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_2(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_3(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_4(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_5(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_6(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_7(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_8(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_9(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_A(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_B(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_C(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_D(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_E(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_F(256'h0000000000000000000000000000000000000000000000000000000000000000)
+ ) fw_ram3_0 (
+ .RDATA(mem_read_data3[15 : 0]),
+ .RADDR({3'h0, address[7 : 0]}),
+ .RCLK(clk),
+ .RCLKE(1'h1),
+ .RE(app_mode_cs & bank3),
+ .WADDR({3'h0, address[7 : 0]}),
+ .WCLK(clk),
+ .WCLKE(1'h1),
+ .WDATA(write_data[15 : 0]),
+ .WE((|we & app_mode_cs & bank3)),
+ .MASK({{8{~we[1]}}, {8{~we[0]}}})
+ );
+
+ SB_RAM40_4K #(
+ .INIT_0(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_1(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_2(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_3(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_4(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_5(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_6(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_7(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_8(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_9(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_A(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_B(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_C(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_D(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_E(256'h0000000000000000000000000000000000000000000000000000000000000000),
+ .INIT_F(256'h0000000000000000000000000000000000000000000000000000000000000000)
+ ) fw_ram3_1 (
+ .RDATA(mem_read_data3[31 : 16]),
+ .RADDR({3'h0, address[7 : 0]}),
+ .RCLK(clk),
+ .RCLKE(1'h1),
+ .RE(app_mode_cs & bank3),
+ .WADDR({3'h0, address[7 : 0]}),
+ .WCLK(clk),
+ .WCLKE(1'h1),
+ .WDATA(write_data[31 : 16]),
+ .WE((|we & app_mode_cs & bank3)),
.MASK({{8{~we[3]}}, {8{~we[2]}}})
);
@@ -127,17 +322,29 @@ module fw_ram (
always @* begin : rw_mux
bank0 = 1'h0;
bank1 = 1'h0;
+ bank2 = 1'h0;
+ bank3 = 1'h0;
tmp_read_data = 32'h0;
- if (system_mode_cs) begin
- if (address[8]) begin
- bank1 = 1'h1;
- tmp_read_data = mem_read_data1;
- end
- else begin
- bank0 = 1'h1;
- tmp_read_data = mem_read_data0;
- end
+ if (app_mode_cs) begin
+ case (address[9:8])
+ 2'b11: begin
+ bank3 = 1'h1;
+ tmp_read_data = mem_read_data3;
+ end
+ 2'b10: begin
+ bank2 = 1'h1;
+ tmp_read_data = mem_read_data2;
+ end
+ 2'b01: begin
+ bank1 = 1'h1;
+ tmp_read_data = mem_read_data1;
+ end
+ 2'b00: begin
+ bank0 = 1'h1;
+ tmp_read_data = mem_read_data0;
+ end
+ endcase
end
end
diff --git a/hw/application_fpga/core/tk1/README.md b/hw/application_fpga/core/tk1/README.md
index b53484b..df22f04 100644
--- a/hw/application_fpga/core/tk1/README.md
+++ b/hw/application_fpga/core/tk1/README.md
@@ -23,17 +23,6 @@ and version of the device. They can be read by FW as well as
applications.
-### Control of execution mode
-
-```
-ADDR_SYSTEM_MODE_CTRL: 0x08
-```
-
-This register controls if the device is executing in FW mode or in App
-mode. The register can be written once between power cycles, and only
-by FW. If set the device is in app mode.
-
-
### Control of RGB LED
```
@@ -75,19 +64,7 @@ ADDR_APP_SIZE: 0x0d
These registers provide read only information to the loaded app to
itself - where it was loaded and its size. The values are written by
FW as part of the loading of the app. The registers can't be written
-when the `ADDR_SYSTEM_MODE_CTRL` has been set.
-
-
-### Access to Blake2s
-
-```
-ADDR_BLAKE2S: 0x10
-```
-
-This register provides the 32-bit function pointer address to the
-Blake2s hash function in the FW. It is written by FW during boot. The
-register can't be written to when the `ADDR_SYSTEM_MODE_CTRL` has been
-set.
+in application mode.
### Access to CDI
@@ -99,10 +76,10 @@ ADDR_CDI_LAST: 0x27
These registers provide access to the 256-bit compound device secret
calculated by the FW as part of loading an application. The registers
-are written by the FW. The register can't be written to when the
-`ADDR_SYSTEM_MODE_CTRL` has been set. The CDI is readable by apps,
-which can then use it as a base secret for any other secrets required
-to carry out their intended use case.
+are written by the FW. The register can't be written in application
+mode. The CDI is readable by apps, which can then use it as a base
+secret for any other secrets required to carry out their intended use
+case.
### Access to UDI
diff --git a/hw/application_fpga/core/tk1/rtl/tk1.v b/hw/application_fpga/core/tk1/rtl/tk1.v
index 939897c..bcdfc1d 100644
--- a/hw/application_fpga/core/tk1/rtl/tk1.v
+++ b/hw/application_fpga/core/tk1/rtl/tk1.v
@@ -20,7 +20,8 @@ module tk1 #(
input wire reset_n,
input wire cpu_trap,
- output wire system_mode,
+ output wire app_mode,
+ output wire fw_startup_done,
input wire [31 : 0] cpu_addr,
input wire cpu_instr,
@@ -45,6 +46,8 @@ module tk1 #(
output wire gpio3,
output wire gpio4,
+ input wire syscall,
+
input wire cs,
input wire we,
input wire [ 7 : 0] address,
@@ -61,8 +64,6 @@ module tk1 #(
localparam ADDR_NAME1 = 8'h01;
localparam ADDR_VERSION = 8'h02;
- localparam ADDR_SYSTEM_MODE_CTRL = 8'h08;
-
localparam ADDR_LED = 8'h09;
localparam LED_R_BIT = 2;
localparam LED_G_BIT = 1;
@@ -79,8 +80,6 @@ module tk1 #(
localparam ADDR_APP_START = 8'h0c;
localparam ADDR_APP_SIZE = 8'h0d;
- localparam ADDR_BLAKE2S = 8'h10;
-
localparam ADDR_CDI_FIRST = 8'h20;
localparam ADDR_CDI_LAST = 8'h27;
@@ -102,20 +101,21 @@ module tk1 #(
localparam TK1_NAME0 = 32'h746B3120; // "tk1 "
localparam TK1_NAME1 = 32'h6d6b6466; // "mkdf"
- localparam TK1_VERSION = 32'h00000005;
+ localparam TK1_VERSION = 32'h00000006;
localparam FW_RAM_FIRST = 32'hd0000000;
- localparam FW_RAM_LAST = 32'hd00007ff;
+ localparam FW_RAM_LAST = 32'hd0000fff; // 4 KB
+ localparam FW_ROM_LAST = 32'h00001fff;
//----------------------------------------------------------------
// Registers including update variables and write enable.
//----------------------------------------------------------------
- reg [31 : 0] cdi_mem [0 : 7];
+ reg [31 : 0] cdi_mem [0 : 7];
reg cdi_mem_we;
- reg system_mode_reg;
- reg system_mode_we;
+ reg fw_startup_done_reg;
+ reg fw_startup_done_set;
reg [ 2 : 0] led_reg;
reg led_we;
@@ -133,9 +133,6 @@ module tk1 #(
reg [31 : 0] app_size_reg;
reg app_size_we;
- reg [31 : 0] blake2s_addr_reg;
- reg blake2s_addr_we;
-
reg [23 : 0] cpu_trap_ctr_reg;
reg [23 : 0] cpu_trap_ctr_new;
reg [ 2 : 0] cpu_trap_led_reg;
@@ -184,21 +181,21 @@ module tk1 #(
//----------------------------------------------------------------
// Concurrent connectivity for ports etc.
//----------------------------------------------------------------
- assign read_data = tmp_read_data;
- assign ready = tmp_ready;
+ assign read_data = tmp_read_data;
+ assign ready = tmp_ready;
- assign system_mode = system_mode_reg;
+ assign app_mode = fw_startup_done_reg & ~syscall;
+ assign fw_startup_done = fw_startup_done_reg;
- assign force_trap = force_trap_reg;
+ assign force_trap = force_trap_reg;
- assign gpio3 = gpio3_reg;
- assign gpio4 = gpio4_reg;
+ assign gpio3 = gpio3_reg;
+ assign gpio4 = gpio4_reg;
- assign ram_addr_rand = ram_addr_rand_reg;
- assign ram_data_rand = ram_data_rand_reg;
-
- assign system_reset = system_reset_reg;
+ assign ram_addr_rand = ram_addr_rand_reg;
+ assign ram_data_rand = ram_data_rand_reg;
+ assign system_reset = system_reset_reg;
//----------------------------------------------------------------
// Module instance.
@@ -250,32 +247,31 @@ module tk1 #(
//----------------------------------------------------------------
always @(posedge clk) begin : reg_update
if (!reset_n) begin
- system_mode_reg <= 1'h0;
- led_reg <= 3'h6;
- gpio1_reg <= 2'h0;
- gpio2_reg <= 2'h0;
- gpio3_reg <= 1'h0;
- gpio4_reg <= 1'h0;
- app_start_reg <= 32'h0;
- app_size_reg <= APP_SIZE;
- blake2s_addr_reg <= 32'h0;
- cdi_mem[0] <= 32'h0;
- cdi_mem[1] <= 32'h0;
- cdi_mem[2] <= 32'h0;
- cdi_mem[3] <= 32'h0;
- cdi_mem[4] <= 32'h0;
- cdi_mem[5] <= 32'h0;
- cdi_mem[6] <= 32'h0;
- cdi_mem[7] <= 32'h0;
- cpu_trap_ctr_reg <= 24'h0;
- cpu_trap_led_reg <= 3'h0;
- cpu_mon_en_reg <= 1'h0;
- cpu_mon_first_reg <= 32'h0;
- cpu_mon_last_reg <= 32'h0;
- ram_addr_rand_reg <= 15'h0;
- ram_data_rand_reg <= 32'h0;
- force_trap_reg <= 1'h0;
- system_reset_reg <= 1'h0;
+ fw_startup_done_reg <= 1'h0;
+ led_reg <= 3'h6;
+ gpio1_reg <= 2'h0;
+ gpio2_reg <= 2'h0;
+ gpio3_reg <= 1'h0;
+ gpio4_reg <= 1'h0;
+ app_start_reg <= 32'h0;
+ app_size_reg <= APP_SIZE;
+ cdi_mem[0] <= 32'h0;
+ cdi_mem[1] <= 32'h0;
+ cdi_mem[2] <= 32'h0;
+ cdi_mem[3] <= 32'h0;
+ cdi_mem[4] <= 32'h0;
+ cdi_mem[5] <= 32'h0;
+ cdi_mem[6] <= 32'h0;
+ cdi_mem[7] <= 32'h0;
+ cpu_trap_ctr_reg <= 24'h0;
+ cpu_trap_led_reg <= 3'h0;
+ cpu_mon_en_reg <= 1'h0;
+ cpu_mon_first_reg <= 32'h0;
+ cpu_mon_last_reg <= 32'h0;
+ ram_addr_rand_reg <= 15'h0;
+ ram_data_rand_reg <= 32'h0;
+ force_trap_reg <= 1'h0;
+ system_reset_reg <= 1'h0;
end
else begin
@@ -289,8 +285,8 @@ module tk1 #(
gpio2_reg[0] <= gpio2;
gpio2_reg[1] <= gpio2_reg[0];
- if (system_mode_we) begin
- system_mode_reg <= 1'h1;
+ if (fw_startup_done_set) begin
+ fw_startup_done_reg <= 1'h1;
end
if (led_we) begin
@@ -313,10 +309,6 @@ module tk1 #(
app_size_reg <= write_data;
end
- if (blake2s_addr_we) begin
- blake2s_addr_reg <= write_data;
- end
-
if (cdi_mem_we) begin
cdi_mem[address[2 : 0]] <= write_data;
end
@@ -386,6 +378,9 @@ module tk1 #(
//
// Trying to execute instructions in FW-RAM.
//
+ // Executing instructions in ROM, while ROM is marked as not
+ // executable.
+ //
// Trying to execute code in mem area set to be data access only.
// This requires execution monitor to have been setup and
// enabled.
@@ -395,7 +390,7 @@ module tk1 #(
if (cpu_valid) begin
// Outside ROM area
- if (cpu_addr[31 : 30] == 2'h0 & |cpu_addr[29 : 14]) begin
+ if (cpu_addr[31 : 30] == 2'h0 & |cpu_addr[29 : 13]) begin
force_trap_set = 1'h1;
end
@@ -443,12 +438,22 @@ module tk1 #(
end
// Outside FW_RAM
- if (cpu_addr[29 : 24] == 6'h10 & |cpu_addr[23 : 11]) begin
+ if (cpu_addr[29 : 24] == 6'h10 & |cpu_addr[23 : 12]) begin
force_trap_set = 1'h1;
end
// In unused space
- if ((cpu_addr[29 : 24] > 6'h10) && (cpu_addr[29 : 24] < 6'h3f)) begin
+ if ((cpu_addr[29 : 24] > 6'h10) && (cpu_addr[29 : 24] < 6'h21)) begin
+ force_trap_set = 1'h1;
+ end
+
+ // Outside SYSCALL
+ if (cpu_addr[29 : 24] == 6'h21 & |cpu_addr[23 : 2]) begin
+ force_trap_set = 1'h1;
+ end
+
+ // In unused space
+ if ((cpu_addr[29 : 24] > 6'h21) && (cpu_addr[29 : 24] < 6'h3f)) begin
force_trap_set = 1'h1;
end
@@ -463,6 +468,12 @@ module tk1 #(
force_trap_set = 1'h1;
end
+ if (app_mode) begin
+ if (cpu_addr <= FW_ROM_LAST) begin // Only valid as long as ROM starts at address 0x00.
+ force_trap_set = 1'h1;
+ end
+ end
+
if (cpu_mon_en_reg) begin
if ((cpu_addr >= cpu_mon_first_reg) && (cpu_addr <= cpu_mon_last_reg)) begin
force_trap_set = 1'h1;
@@ -472,18 +483,30 @@ module tk1 #(
end
end
+ //----------------------------------------------------------------
+ // fw_startup_done_ctrl
+ //
+ // Automatically lower privilege when executing above ROM.
+ // ----------------------------------------------------------------
+ always @* begin : fw_startup_done_ctrl
+ fw_startup_done_set = 1'h0;
+
+ if (cpu_valid & cpu_instr) begin
+ if (cpu_addr > FW_ROM_LAST) begin
+ fw_startup_done_set = 1'h1;
+ end
+ end
+ end
//----------------------------------------------------------------
// api
//----------------------------------------------------------------
always @* begin : api
- system_mode_we = 1'h0;
led_we = 1'h0;
gpio3_we = 1'h0;
gpio4_we = 1'h0;
app_start_we = 1'h0;
app_size_we = 1'h0;
- blake2s_addr_we = 1'h0;
cdi_mem_we = 1'h0;
ram_addr_rand_we = 1'h0;
ram_data_rand_we = 1'h0;
@@ -498,16 +521,12 @@ module tk1 #(
spi_start = 1'h0;
spi_tx_data_vld = 1'h0;
- spi_enable = write_data[0];
- spi_tx_data = write_data[7 : 0];
+ spi_enable = write_data[0] & !app_mode;
+ spi_tx_data = write_data[7 : 0] & {8{!app_mode}};
if (cs) begin
tmp_ready = 1'h1;
if (we) begin
- if (address == ADDR_SYSTEM_MODE_CTRL) begin
- system_mode_we = 1'h1;
- end
-
if (address == ADDR_LED) begin
led_we = 1'h1;
end
@@ -518,41 +537,37 @@ module tk1 #(
end
if (address == ADDR_APP_START) begin
- if (!system_mode_reg) begin
+ if (!app_mode) begin
app_start_we = 1'h1;
end
end
if (address == ADDR_APP_SIZE) begin
- if (!system_mode_reg) begin
+ if (!app_mode) begin
app_size_we = 1'h1;
end
end
if (address == ADDR_SYSTEM_RESET) begin
- system_reset_new = 1'h1;
- end
-
- if (address == ADDR_BLAKE2S) begin
- if (!system_mode_reg) begin
- blake2s_addr_we = 1'h1;
+ if (!app_mode) begin
+ system_reset_new = 1'h1;
end
end
if ((address >= ADDR_CDI_FIRST) && (address <= ADDR_CDI_LAST)) begin
- if (!system_mode_reg) begin
+ if (!app_mode) begin
cdi_mem_we = 1'h1;
end
end
if (address == ADDR_RAM_ADDR_RAND) begin
- if (!system_mode_reg) begin
+ if (!app_mode) begin
ram_addr_rand_we = 1'h1;
end
end
if (address == ADDR_RAM_DATA_RAND) begin
- if (!system_mode_reg) begin
+ if (!app_mode) begin
ram_data_rand_we = 1'h1;
end
end
@@ -574,15 +589,21 @@ module tk1 #(
end
if (address == ADDR_SPI_EN) begin
- spi_enable_vld = 1'h1;
+ if (!app_mode) begin
+ spi_enable_vld = 1'h1;
+ end
end
if (address == ADDR_SPI_XFER) begin
- spi_start = 1'h1;
+ if (!app_mode) begin
+ spi_start = 1'h1;
+ end
end
if (address == ADDR_SPI_DATA) begin
- spi_tx_data_vld = 1'h1;
+ if (!app_mode) begin
+ spi_tx_data_vld = 1'h1;
+ end
end
end
@@ -599,10 +620,6 @@ module tk1 #(
tmp_read_data = TK1_VERSION;
end
- if (address == ADDR_SYSTEM_MODE_CTRL) begin
- tmp_read_data = {32{system_mode_reg}};
- end
-
if (address == ADDR_LED) begin
tmp_read_data = {29'h0, led_reg};
end
@@ -619,26 +636,26 @@ module tk1 #(
tmp_read_data = app_size_reg;
end
- if (address == ADDR_BLAKE2S) begin
- tmp_read_data = blake2s_addr_reg;
- end
-
if ((address >= ADDR_CDI_FIRST) && (address <= ADDR_CDI_LAST)) begin
tmp_read_data = cdi_mem[address[2 : 0]];
end
if ((address >= ADDR_UDI_FIRST) && (address <= ADDR_UDI_LAST)) begin
- if (!system_mode_reg) begin
+ if (!app_mode) begin
tmp_read_data = udi_rdata;
end
end
if (address == ADDR_SPI_XFER) begin
- tmp_read_data[0] = spi_ready;
+ if (!app_mode) begin
+ tmp_read_data[0] = spi_ready;
+ end
end
if (address == ADDR_SPI_DATA) begin
- tmp_read_data[7 : 0] = spi_rx_data;
+ if (!app_mode) begin
+ tmp_read_data[7 : 0] = spi_rx_data;
+ end
end
end
diff --git a/hw/application_fpga/core/tk1/tb/tb_tk1.v b/hw/application_fpga/core/tk1/tb/tb_tk1.v
index 2344849..83dd943 100644
--- a/hw/application_fpga/core/tk1/tb/tb_tk1.v
+++ b/hw/application_fpga/core/tk1/tb/tb_tk1.v
@@ -18,7 +18,7 @@ module tb_tk1 ();
//----------------------------------------------------------------
// Internal constant and parameter definitions.
//----------------------------------------------------------------
- parameter DEBUG = 1;
+ parameter DEBUG = 0;
parameter CLK_HALF_PERIOD = 1;
parameter CLK_PERIOD = 2 * CLK_HALF_PERIOD;
@@ -27,8 +27,6 @@ module tb_tk1 ();
localparam ADDR_NAME1 = 8'h01;
localparam ADDR_VERSION = 8'h02;
- localparam ADDR_SYSTEM_MODE_CTRL = 8'h08;
-
localparam ADDR_LED = 8'h09;
localparam LED_R_BIT = 2;
localparam LED_G_BIT = 1;
@@ -58,10 +56,16 @@ module tb_tk1 ();
localparam ADDR_CPU_MON_FIRST = 8'h61;
localparam ADDR_CPU_MON_LAST = 8'h62;
+ localparam ADDR_SYSTEM_RESET = 8'h70;
+
localparam ADDR_SPI_EN = 8'h80;
localparam ADDR_SPI_XFER = 8'h81;
localparam ADDR_SPI_DATA = 8'h82;
+ localparam APP_RAM_START = 32'h40000000;
+
+ localparam ROM_START = 32'h00000000;
+ localparam ROM_END = 32'h00001fff;
//----------------------------------------------------------------
// Register and Wire declarations.
@@ -76,12 +80,13 @@ module tb_tk1 ();
reg tb_clk;
reg tb_reset_n;
reg tb_cpu_trap;
- wire tb_system_mode;
+ wire tb_app_mode;
reg [31 : 0] tb_cpu_addr;
reg tb_cpu_instr;
reg tb_cpu_valid;
wire tb_force_trap;
+ wire tb_system_reset;
wire [14 : 0] tb_ram_addr_rand;
wire [31 : 0] tb_ram_data_rand;
@@ -95,6 +100,8 @@ module tb_tk1 ();
wire tb_gpio3;
wire tb_gpio4;
+ reg tb_syscall;
+
wire tb_spi_ss;
wire tb_spi_sck;
wire tb_spi_mosi;
@@ -122,12 +129,13 @@ module tb_tk1 ();
.reset_n(tb_reset_n),
.cpu_trap(tb_cpu_trap),
- .system_mode(tb_system_mode),
+ .app_mode(tb_app_mode),
.cpu_addr (tb_cpu_addr),
.cpu_instr (tb_cpu_instr),
.cpu_valid (tb_cpu_valid),
.force_trap(tb_force_trap),
+ .system_reset(tb_system_reset),
.ram_addr_rand(tb_ram_addr_rand),
.ram_data_rand(tb_ram_data_rand),
@@ -141,6 +149,8 @@ module tb_tk1 ();
.gpio3(tb_gpio3),
.gpio4(tb_gpio4),
+ .syscall(tb_syscall),
+
.spi_ss (tb_spi_ss),
.spi_sck (tb_spi_sck),
.spi_mosi(tb_spi_mosi),
@@ -192,7 +202,7 @@ module tb_tk1 ();
$display("------------");
if (tb_main_monitor) begin
$display("Inputs and outputs:");
- $display("tb_cpu_trap: 0x%1x, system_mode: 0x%1x", tb_cpu_trap, tb_system_mode);
+ $display("tb_cpu_trap: 0x%1x, app_mode: 0x%1x", tb_cpu_trap, dut.app_mode);
$display("cpu_addr: 0x%08x, cpu_instr: 0x%1x, cpu_valid: 0x%1x, force_tap: 0x%1x",
tb_cpu_addr, tb_cpu_instr, tb_cpu_valid, tb_force_trap);
$display("ram_addr_rand: 0x%08x, ram_data_rand: 0x%08x", tb_ram_addr_rand,
@@ -227,7 +237,6 @@ module tb_tk1 ();
//----------------------------------------------------------------
task reset_dut;
begin
- $display("--- Toggle reset.");
tb_reset_n = 0;
#(2 * CLK_PERIOD);
tb_reset_n = 1;
@@ -277,6 +286,8 @@ module tb_tk1 ();
tb_gpio1 = 1'h0;
tb_gpio2 = 1'h0;
+ tb_syscall = 1'h0;
+
tb_cs = 1'h0;
tb_we = 1'h0;
tb_address = 8'h0;
@@ -285,6 +296,25 @@ module tb_tk1 ();
endtask // init_sim
+ //----------------------------------------------------------------
+ // restore_mem_bus()
+ //
+ // Restore memory bus to its initial state
+ //----------------------------------------------------------------
+ task restore_mem_bus();
+ begin : restore_mem_bus
+ tb_cpu_addr = 32'h0;
+ tb_cpu_instr = 1'h0;
+ tb_cpu_valid = 1'h0;
+
+ tb_cs = 1'h0;
+ tb_we = 1'h0;
+ tb_address = 8'h0;
+ tb_write_data = 32'h0;
+ end
+ endtask
+
+
//----------------------------------------------------------------
// write_word()
//
@@ -301,7 +331,7 @@ module tb_tk1 ();
tb_write_data = word;
tb_cs = 1;
tb_we = 1;
- #(2 * CLK_PERIOD);
+ #(CLK_PERIOD);
tb_cs = 0;
tb_we = 0;
end
@@ -320,12 +350,16 @@ module tb_tk1 ();
reg [31 : 0] read_data;
tb_address = address;
- tb_cs = 1'h1;
+ tb_cpu_instr = 1'h0;
+ tb_cpu_valid = 1'h1;
+ tb_we = 1'h0;
+ tb_cs = 1'h1;
#(CLK_PERIOD);
read_data = tb_read_data;
#(CLK_PERIOD);
+ tb_cpu_valid = 1'h0;
tb_cs = 1'h0;
end
endtask // read_word
@@ -354,21 +388,138 @@ module tb_tk1 ();
#(CLK_PERIOD);
tb_cs = 1'h0;
- if (DEBUG) begin
- if (read_data == expected) begin
+ if (read_data == expected) begin
+ if (DEBUG) begin
$display("--- Reading 0x%08x from 0x%02x.", read_data, address);
end
- else begin
- $display("--- Error: Got 0x%08x when reading from 0x%02x, expected 0x%08x", read_data,
- address, expected);
- error_ctr = error_ctr + 1;
- end
- $display("");
+ end
+ else begin
+ $display("--- Error: Got 0x%08x when reading from 0x%02x, expected 0x%08x", read_data,
+ address, expected);
+ error_ctr = error_ctr + 1;
end
end
endtask // read_check_word
+ //----------------------------------------------------------------
+ // check_equal()
+ //
+ // Check that two values are equal
+ //----------------------------------------------------------------
+ task check_equal(input [31 : 0] value, input [31 : 0] expected);
+ begin : check_equal
+ if (value != expected) begin
+ $display("--- Error: Got 0x%08x, expected 0x%08x", value, expected);
+ error_ctr = error_ctr + 1;
+ end
+ end
+ endtask // check_equal
+
+
+ //----------------------------------------------------------------
+ // fetch_instruction()
+ //
+ // Simulate fetch of an instruction at specified address.
+ //----------------------------------------------------------------
+ task fetch_instruction(input [31 : 0] address);
+ begin : fetch_instruction
+ tb_cpu_addr = address;
+ tb_cpu_instr = 1'h1;
+ tb_cpu_valid = 1'h1;
+ #(CLK_PERIOD);
+ tb_cpu_addr = 32'h0;
+ tb_cpu_instr = 1'h0;
+ tb_cpu_valid = 1'h0;
+ end
+ endtask // fetch_instruction
+
+ // cpu_read_word()
+ //
+ // Read a data word from the given CPU address in the DUT.
+ // the word read will be available in the global variable
+ // tb_read_data.
+ //----------------------------------------------------------------
+ task cpu_read_word(input [31 : 0] address);
+ begin : cpu_read_word
+ reg [31 : 0] read_data;
+
+ tb_cpu_addr = address;
+ tb_address = tb_cpu_addr[13:2];
+ tb_cpu_instr = 1'h0;
+ tb_cpu_valid = 1'h1;
+ tb_we = 1'h0;
+ tb_cs = 1'h1;
+
+ #(CLK_PERIOD);
+ read_data = tb_read_data;
+
+ #(CLK_PERIOD);
+ tb_cpu_addr = 32'h0;
+ tb_cpu_valid = 1'h0;
+ tb_address = 12'h0;
+ tb_cs = 1'h0;
+ end
+ endtask // read_word
+
+
+ //----------------------------------------------------------------
+ // cpu_read_check_range_should_trap()
+ //
+ // Read data in a range of CPU addresses (32-bit addresses). Fail
+ // if trap signal is not asserted.
+ // Range is inclusive.
+ //----------------------------------------------------------------
+ task cpu_read_check_range_should_trap(input [31 : 0] start_address, input [31 : 0] end_address);
+ begin : read_check_range_should_not_trap
+ reg [32 : 0] address;
+ reg error_detected;
+
+ address = start_address;
+ error_detected = 0;
+
+ while (!error_detected && (address <= end_address)) begin
+ reset_dut();
+ cpu_read_word(address);
+ if (tb_force_trap == 0) begin
+ $display("--- Error: Expected trap when reading from address 0x%08x", address);
+ error_ctr += 1;
+ error_detected = 1;
+ end
+ address += 1;
+ end
+ end
+ endtask
+
+
+ //----------------------------------------------------------------
+ // cpu_read_check_range_should_not_trap()
+ //
+ // Read data in a range of CPU addresses (32-bit addresses). Fail
+ // if trap signal is asserted.
+ // Range is inclusive.
+ //----------------------------------------------------------------
+ task cpu_read_check_range_should_not_trap(input [31 : 0] start_address, input [31 : 0] end_address);
+ begin : read_check_should_not_trap
+ reg [31 : 0] address;
+ reg error_detected;
+
+ address = start_address;
+ error_detected = 0;
+
+ while (!error_detected && (address <= end_address)) begin
+ reset_dut();
+ cpu_read_word(address);
+ if (tb_force_trap == 1) begin
+ $display("--- Error: Did not expected trap when reading from address 0x%08x", address);
+ error_ctr += 1;
+ error_detected = 1;
+ end
+ address += 1;
+ end
+ end
+ endtask
+
//----------------------------------------------------------------
// test1()
// Read out name and version.
@@ -400,10 +551,27 @@ module tb_tk1 ();
$display("");
$display("--- test2: Read out UDI started.");
+ tb_syscall = 0;
+ reset_dut();
read_check_word(ADDR_UDI_FIRST, 32'h00010203);
read_check_word(ADDR_UDI_LAST, 32'h04050607);
+ $display("--- test2: Switch to app mode.");
+ fetch_instruction(APP_RAM_START);
+
+ read_check_word(ADDR_UDI_FIRST, 32'h0);
+ read_check_word(ADDR_UDI_LAST, 32'h0);
+
+ $display("--- test2: Enter syscall.");
+ tb_syscall = 1;
+
+ read_check_word(ADDR_UDI_FIRST, 32'h00010203);
+ read_check_word(ADDR_UDI_LAST, 32'h04050607);
+
+ $display("--- test2: Leave syscall.");
+ tb_syscall = 0;
+
$display("--- test2: completed.");
$display("");
end
@@ -418,6 +586,10 @@ module tb_tk1 ();
begin
tc_ctr = tc_ctr + 1;
+ $display("--- test5: Reset DUT to switch to fw mode.");
+ tb_syscall = 0;
+ reset_dut();
+
$display("");
$display("--- test3: Write and read CDI started.");
$display("--- test3: Write CDI.");
@@ -441,9 +613,9 @@ module tb_tk1 ();
read_check_word(ADDR_CDI_LAST + 0, 32'h70717273);
$display("--- test3: Switch to app mode.");
- write_word(ADDR_SYSTEM_MODE_CTRL, 32'hdeadbeef);
+ fetch_instruction(APP_RAM_START);
- $display("--- test3: Try to write CDI again.");
+ $display("--- test3: Try to write CDI from app mode.");
write_word(ADDR_CDI_FIRST + 0, 32'hfffefdfc);
write_word(ADDR_CDI_FIRST + 1, 32'hefeeedec);
write_word(ADDR_CDI_FIRST + 2, 32'hdfdedddc);
@@ -453,7 +625,7 @@ module tb_tk1 ();
write_word(ADDR_CDI_FIRST + 6, 32'h8f8e8d8c);
write_word(ADDR_CDI_FIRST + 7, 32'h7f7e7d7c);
- $display("--- test3: Read CDI again.");
+ $display("--- test3: Read CDI from app mode.");
read_check_word(ADDR_CDI_FIRST + 0, 32'hf0f1f2f3);
read_check_word(ADDR_CDI_FIRST + 1, 32'he0e1e2e3);
read_check_word(ADDR_CDI_FIRST + 2, 32'hd0d1d2d3);
@@ -463,46 +635,38 @@ module tb_tk1 ();
read_check_word(ADDR_CDI_FIRST + 6, 32'h80818283);
read_check_word(ADDR_CDI_LAST + 0, 32'h70717273);
+ $display("--- test3: Enter syscall.");
+ tb_syscall = 1;
+
+ $display("--- test3: Try to write CDI from syscall.");
+ write_word(ADDR_CDI_FIRST + 0, 32'hfffefdfc);
+ write_word(ADDR_CDI_FIRST + 1, 32'hefeeedec);
+ write_word(ADDR_CDI_FIRST + 2, 32'hdfdedddc);
+ write_word(ADDR_CDI_FIRST + 3, 32'hcfcecdcc);
+ write_word(ADDR_CDI_FIRST + 4, 32'hafaeadac);
+ write_word(ADDR_CDI_FIRST + 5, 32'h9f9e9d9c);
+ write_word(ADDR_CDI_FIRST + 6, 32'h8f8e8d8c);
+ write_word(ADDR_CDI_FIRST + 7, 32'h7f7e7d7c);
+
+ $display("--- test3: Read CDI from syscall.");
+ read_check_word(ADDR_CDI_FIRST + 0, 32'hfffefdfc);
+ read_check_word(ADDR_CDI_FIRST + 1, 32'hefeeedec);
+ read_check_word(ADDR_CDI_FIRST + 2, 32'hdfdedddc);
+ read_check_word(ADDR_CDI_FIRST + 3, 32'hcfcecdcc);
+ read_check_word(ADDR_CDI_FIRST + 4, 32'hafaeadac);
+ read_check_word(ADDR_CDI_FIRST + 5, 32'h9f9e9d9c);
+ read_check_word(ADDR_CDI_FIRST + 6, 32'h8f8e8d8c);
+ read_check_word(ADDR_CDI_LAST + 0, 32'h7f7e7d7c);
+
+ $display("--- test3: Leave syscall.");
+ tb_syscall = 0;
+
$display("--- test3: completed.");
$display("");
end
endtask // test3
- //----------------------------------------------------------------
- // test4()
- // Write and read blake2s entry point.
- //----------------------------------------------------------------
- task test4;
- begin
- tc_ctr = tc_ctr + 1;
-
- $display("");
- $display("--- test4: Write and read blake2s entry point in fw mode started.");
- $display("--- test4: Reset DUT to switch to fw mode.");
- reset_dut();
-
- $display("--- test4: Write Blake2s entry point.");
- write_word(ADDR_BLAKE2S, 32'hcafebabe);
-
- $display("--- test4: Read Blake2s entry point.");
- read_check_word(ADDR_BLAKE2S, 32'hcafebabe);
-
- $display("--- test4: Switch to app mode.");
- write_word(ADDR_SYSTEM_MODE_CTRL, 32'hf00ff00f);
-
- $display("--- test4: Write Blake2s entry point again.");
- write_word(ADDR_BLAKE2S, 32'hdeadbeef);
-
- $display("--- test4: Read Blake2s entry point again");
- read_check_word(ADDR_BLAKE2S, 32'hcafebabe);
-
- $display("--- test4: completed.");
- $display("");
- end
- endtask // test4
-
-
//----------------------------------------------------------------
// test5()
// Write and read APP start address end size.
@@ -525,7 +689,7 @@ module tb_tk1 ();
read_check_word(ADDR_APP_SIZE, 32'h47114711);
$display("--- test5: Switch to app mode.");
- write_word(ADDR_SYSTEM_MODE_CTRL, 32'hf000000);
+ fetch_instruction(APP_RAM_START);
$display("--- test5: Write app start address and size again.");
write_word(ADDR_APP_START, 32'hdeadbeef);
@@ -543,7 +707,7 @@ module tb_tk1 ();
//----------------------------------------------------------------
// test6()
- // Write RAM address and data randomizatio in fw mode.
+ // Write and read RAM-address and data randomization.
//----------------------------------------------------------------
task test6;
begin
@@ -552,6 +716,7 @@ module tb_tk1 ();
$display("");
$display("--- test6: Write RAM addr and data randomization in fw mode.");
$display("--- test6: Reset DUT to switch to fw mode.");
+ tb_syscall = 0;
reset_dut();
$display("--- test6: Write to ADDR_RAM_ADDR_RAND and ADDR_RAM_DATA_RAND .");
@@ -562,9 +727,14 @@ module tb_tk1 ();
"--- test6: Check value in dut ADDR_RAM_ADDR_RAND and ADDR_RAM_DATA_RAND registers.");
$display("--- test6: ram_addr_rand_reg: 0x%04x, ram_data_rand_reg: 0x%08x",
dut.ram_addr_rand, dut.ram_data_rand);
+ check_equal(dut.ram_addr_rand, 15'h1337);
+ check_equal(dut.ram_data_rand, 32'h47114711);
+ read_check_word(ADDR_RAM_ADDR_RAND, 32'h0);
+ read_check_word(ADDR_RAM_DATA_RAND, 32'h0);
+
$display("--- test6: Switch to app mode.");
- write_word(ADDR_SYSTEM_MODE_CTRL, 32'hf000000);
+ fetch_instruction(APP_RAM_START);
$display("--- test6: Write to ADDR_RAM_ADDR_RAND and ADDR_RAM_DATA_RAND again.");
write_word(ADDR_RAM_ADDR_RAND, 32'hdeadbeef);
@@ -574,6 +744,30 @@ module tb_tk1 ();
"--- test6: Check value in dut ADDR_RAM_ADDR_RAND and ADDR_RAM_DATA_RAND registers.");
$display("--- test6: ram_addr_rand_reg: 0x%04x, ram_data_rand_reg: 0x%08x",
dut.ram_addr_rand, dut.ram_data_rand);
+ check_equal(dut.ram_addr_rand, 15'h1337);
+ check_equal(dut.ram_data_rand, 32'h47114711);
+ read_check_word(ADDR_RAM_ADDR_RAND, 32'h0);
+ read_check_word(ADDR_RAM_DATA_RAND, 32'h0);
+
+
+ $display("--- test6: Enter syscall.");
+ tb_syscall = 1;
+
+ $display("--- test6: Write to ADDR_RAM_ADDR_RAND and ADDR_RAM_DATA_RAND again.");
+ write_word(ADDR_RAM_ADDR_RAND, 32'hdeadbeef);
+ write_word(ADDR_RAM_DATA_RAND, 32'hf00ff00f);
+
+ $display(
+ "--- test6: Check value in dut ADDR_RAM_ADDR_RAND and ADDR_RAM_DATA_RAND registers.");
+ $display("--- test6: ram_addr_rand_reg: 0x%04x, ram_data_rand_reg: 0x%08x",
+ dut.ram_addr_rand, dut.ram_data_rand);
+ check_equal(dut.ram_addr_rand, 15'h3eef);
+ check_equal(dut.ram_data_rand, 32'hf00ff00f);
+ read_check_word(ADDR_RAM_ADDR_RAND, 32'h0);
+ read_check_word(ADDR_RAM_DATA_RAND, 32'h0);
+
+ $display("--- test6: Leave syscall.");
+ tb_syscall = 0;
$display("--- test6: completed.");
$display("");
@@ -655,17 +849,20 @@ module tb_tk1 ();
write_word(ADDR_CPU_MON_LAST, 32'hdeadcafe);
$display("--- test9: cpu_mon_first_reg: 0x%08x, cpu_mon_last_reg: 0x%08x",
dut.cpu_mon_first_reg, dut.cpu_mon_last_reg);
+ check_equal(dut.cpu_mon_first_reg, 32'h10000000);
+ check_equal(dut.cpu_mon_last_reg, 32'h20000000);
$display("--- test9: force_trap before illegal access: 0x%1x", tb_force_trap);
$display("--- test9: Creating an illegal access.");
- tb_cpu_addr = 32'h13371337;
- tb_cpu_instr = 1'h1;
- tb_cpu_valid = 1'h1;
- #(2 * CLK_PERIOD);
+ fetch_instruction(32'h13371337);
$display("--- test9: cpu_addr: 0x%08x, cpu_instr: 0x%1x, cpu_valid: 0x%1x", tb_cpu_addr,
tb_cpu_instr, tb_cpu_valid);
+ check_equal(dut.cpu_mon_first_reg, 32'h10000000);
+ check_equal(dut.cpu_mon_last_reg, 32'h20000000);
+
$display("--- test9: force_trap: 0x%1x", tb_force_trap);
+ check_equal(tb_force_trap, 1);
$display("--- test9: completed.");
$display("");
@@ -673,6 +870,66 @@ module tb_tk1 ();
endtask // test9
+ //----------------------------------------------------------------
+ // check_inverting_spi_loopback_transfer_succeeds()
+ // Do an SPI tranfer. Check that the received value is the inverse
+ // of the value sent.
+ //----------------------------------------------------------------
+ task check_inverting_spi_loopback_transfer_succeeds(input [32 : 0] data);
+ begin : check_inverting_spi_loopback_transfer
+ $display("--- test10: Sending a byte.");
+ write_word(ADDR_SPI_EN, 32'h1);
+ write_word(ADDR_SPI_DATA, data);
+ write_word(ADDR_SPI_XFER, 32'h1);
+
+ // Ready ready flag in SPI until it is set.
+ read_word(ADDR_SPI_XFER);
+ while (!tb_read_data) begin
+ read_word(ADDR_SPI_XFER);
+ end
+ $display("--- test10: Byte should have been sent.");
+
+ #(2 * CLK_PERIOD);
+ read_check_word(ADDR_SPI_DATA, ~data[7 : 0] & 8'hff);
+ write_word(ADDR_SPI_EN, 32'h0);
+ end
+ endtask
+
+
+ //----------------------------------------------------------------
+ // check_spi_does_not_transfer()
+ // Do an SPI transfer. Check that the SS, SCK and MISO signal are
+ // not active.
+ //----------------------------------------------------------------
+ task check_spi_does_not_transfer;
+ begin : check_spi_does_not_transfer
+ reg [31 : 0] wait_ctr;
+ reg error;
+ localparam CLK_PER_SPI_BIT = 3;
+ localparam WAIT_MARGIN = 10;
+
+ error = 0;
+ wait_ctr = CLK_PER_SPI_BIT * 8 * WAIT_MARGIN;
+
+ $display("--- test10: Sending a byte.");
+ write_word(ADDR_SPI_EN, 32'h1);
+ write_word(ADDR_SPI_DATA, 32'haa);
+ write_word(ADDR_SPI_XFER, 32'h1);
+
+ $display("--- test10: Waiting to see if SPI signals change state.");
+ while (!error && (wait_ctr != 0)) begin
+ if (~tb_spi_ss || tb_spi_sck || tb_spi_mosi) begin
+ $display("--- Error: SPI signals changed state");
+ error_ctr = error_ctr + 1;
+ error = 1;
+ end
+ #(CLK_PERIOD);
+ wait_ctr = wait_ctr - 1;
+ end
+ end
+ endtask
+
+
//----------------------------------------------------------------
// test10()
// SPI master loopback test.
@@ -683,28 +940,28 @@ module tb_tk1 ();
tb_monitor = 0;
tb_spi_monitor = 0;
+ restore_mem_bus();
+ reset_dut();
+
$display("");
$display("--- test10: Loopback in SPI Master started.");
#(CLK_PERIOD);
- // Sending 0xa7 trough the inverting loopback.
- $display("--- test10: Sending a byte.");
- write_word(ADDR_SPI_EN, 32'h1);
- write_word(ADDR_SPI_DATA, 32'ha7);
- write_word(ADDR_SPI_XFER, 32'h1);
+ check_inverting_spi_loopback_transfer_succeeds(32'ha7);
- // Ready ready flag in SPI until it is set.
- read_word(ADDR_SPI_XFER);
- while (!tb_read_data) begin
- read_word(ADDR_SPI_XFER);
- end
- $display("--- test10: Byte should have been sent.");
+ $display("--- test10: Switch to app mode.");
+ fetch_instruction(APP_RAM_START);
- // 0x58 is the inverse of 0xa7.
- #(2 * CLK_PERIOD);
- read_check_word(ADDR_SPI_DATA, 32'h58);
- write_word(ADDR_SPI_EN, 32'h0);
+ check_spi_does_not_transfer();
+
+ $display("--- test10: Enter syscall.");
+ tb_syscall = 1;
+
+ check_inverting_spi_loopback_transfer_succeeds(32'hc8);
+
+ $display("--- test10: Leave syscall.");
+ tb_syscall = 0;
tb_monitor = 0;
tb_spi_monitor = 0;
@@ -714,6 +971,198 @@ module tb_tk1 ();
end
endtask // test10
+
+ //----------------------------------------------------------------
+ // test11()
+ // Test security monitor trap ranges.
+ // - Check that reading accessible areas does not trap
+ // - Check that reading the start and end of inaccessible areas
+ // trap
+ //----------------------------------------------------------------
+ task test11;
+ begin
+ tc_ctr = tc_ctr + 1;
+
+ $display("");
+ $display("--- test11: Test trap ranges.");
+
+ // ROM trap range: 0x00004000-0x3fffffff
+ $display("--- test11: ROM");
+ cpu_read_check_range_should_not_trap(32'h0, 32'h1fff);
+ cpu_read_check_range_should_trap(32'h2000, 32'h200f);
+ cpu_read_check_range_should_trap(32'h3ffffff0, 32'h3fffffff);
+
+ // RAM trap range: 0x40020000-0x7fffffff
+ $display("--- test11: RAM");
+ cpu_read_check_range_should_not_trap(32'h40000000, 32'h4000000f);
+ cpu_read_check_range_should_trap(32'h40020000, 32'h4002000f);
+ cpu_read_check_range_should_trap(32'h7ffffff0, 32'h7fffffff);
+
+ // Reserved trap range: 0x80000000-0xbfffffff
+ $display("--- test11: Reserved");
+ cpu_read_check_range_should_trap(32'h80000000, 32'h8000000f);
+ cpu_read_check_range_should_trap(32'hbffffff0, 32'hbfffffff);
+
+ // TRNG trap range: 0xc0000400-0xc0ffffff
+ $display("--- test11: TRNG");
+ cpu_read_check_range_should_not_trap(32'hc0000000, 32'hc00003ff);
+ cpu_read_check_range_should_trap(32'hc0000400, 32'hc000040f);
+ cpu_read_check_range_should_trap(32'hc0fffff0, 32'hc0ffffff);
+
+ // TIMER trap range: 0xc1000400-0xc1ffffff
+ $display("--- test11: TIMER");
+ cpu_read_check_range_should_not_trap(32'hc1000000, 32'hc10003ff);
+ cpu_read_check_range_should_trap(32'hc1000400, 32'hc100040f);
+ cpu_read_check_range_should_trap(32'hc1fffff0, 32'hc1ffffff);
+
+ // UDS trap range: 0xc2000020-0xc2ffffff
+ $display("--- test11: UDS");
+ cpu_read_check_range_should_not_trap(32'hc2000000, 32'hc200001f);
+ cpu_read_check_range_should_trap(32'hc2000020, 32'hc200002f);
+ cpu_read_check_range_should_trap(32'hc2fffff0, 32'hc2ffffff);
+
+ // UART trap range: 0xc3000400-0xc3ffffff
+ $display("--- test11: UART");
+ cpu_read_check_range_should_not_trap(32'hc3000000, 32'hc30003ff);
+ cpu_read_check_range_should_trap(32'hc3000400, 32'hc300040f);
+ cpu_read_check_range_should_trap(32'hc3fffff0, 32'hc3ffffff);
+
+ // TOUCH_SENSE trap range: 0xc4000400-0xc4ffffff
+ $display("--- test11: TOUCH_SENSE");
+ cpu_read_check_range_should_not_trap(32'hc4000000, 32'hc40003ff);
+ cpu_read_check_range_should_trap(32'hc4000400, 32'hc400040f);
+ cpu_read_check_range_should_trap(32'hc4fffff0, 32'hc4ffffff);
+
+ // Unused trap range: 0xc5000000-0xcfffffff
+ $display("--- test11: Unused");
+ cpu_read_check_range_should_trap(32'hc5000000, 32'hc500000f);
+ cpu_read_check_range_should_trap(32'hc5fffff0, 32'hc5ffffff);
+
+ // FW_RAM trap range: 0xd0000800-0xd0ffffff
+ $display("--- test11: FW_RAM");
+ cpu_read_check_range_should_not_trap(32'hd0000000, 32'hd0000fff);
+ cpu_read_check_range_should_trap(32'hd0001000, 32'hd000100f);
+ cpu_read_check_range_should_trap(32'hd0fffff0, 32'hd0ffffff);
+
+ // Unused trap range: 0xd1000000-0xfeffffff
+ $display("--- test11: Unused");
+ cpu_read_check_range_should_trap(32'hd1000000, 32'hd100000f);
+ cpu_read_check_range_should_trap(32'he0fffff0, 32'he0ffffff);
+
+ // SYSCALL trap range. 0xe1000004-0xe1ffffff
+ $display("--- test11: SYSCALL");
+ cpu_read_check_range_should_not_trap(32'he1000000, 32'he1000003);
+ cpu_read_check_range_should_trap(32'he1000004, 32'he100000f);
+ cpu_read_check_range_should_trap(32'he1fffff0, 32'he1ffffff);
+
+ // Unused trap range: 0xe2000000-0xfeffffff
+ $display("--- test11: Unused");
+ cpu_read_check_range_should_trap(32'he2000000, 32'he200000f);
+ cpu_read_check_range_should_trap(32'hfefffff0, 32'hfeffffff);
+
+ // TK1 trap range: 0xff000400-0xffffffff
+ $display("--- test11: TK1");
+ cpu_read_check_range_should_not_trap(32'hff000000, 32'hff0003ff);
+ cpu_read_check_range_should_trap(32'hff000400, 32'hff00040f);
+ cpu_read_check_range_should_trap(32'hfffffff0, 32'hffffffff);
+
+ $display("--- test11: completed.");
+ $display("");
+ end
+ endtask // test11
+
+
+ //----------------------------------------------------------------
+ // test12()
+ // Test ROM execution protection. Test trapping at ROM edges while
+ // executing in different contexts.
+ //----------------------------------------------------------------
+ task test12;
+ begin
+ tc_ctr = tc_ctr + 1;
+
+ restore_mem_bus();
+
+ $display("");
+ $display("--- test12: ROM execution allowed in firmware mode.");
+
+ reset_dut();
+ fetch_instruction(ROM_START);
+ check_equal(tb_force_trap, 0);
+
+ fetch_instruction(ROM_END);
+ check_equal(tb_force_trap, 0);
+
+ $display("--- test12: ROM execution not allowed in app mode.");
+ reset_dut();
+ fetch_instruction(APP_RAM_START);
+ fetch_instruction(ROM_START);
+ check_equal(tb_force_trap, 1);
+
+ reset_dut();
+ fetch_instruction(APP_RAM_START);
+ fetch_instruction(ROM_END);
+ check_equal(tb_force_trap, 1);
+
+ $display("--- test12: ROM execution allowed in syscalls made from app mode.");
+ reset_dut();
+ fetch_instruction(APP_RAM_START);
+ tb_syscall = 1;
+
+ fetch_instruction(ROM_START);
+ check_equal(tb_force_trap, 0);
+
+ fetch_instruction(ROM_END);
+ check_equal(tb_force_trap, 0);
+
+ $display("--- test12: Leave syscall.");
+ tb_syscall = 0;
+
+ $display("--- test12: completed.");
+ $display("");
+ end
+ endtask // test12
+
+
+ //----------------------------------------------------------------
+ // test13()
+ // System reset
+ //----------------------------------------------------------------
+ task test13;
+ begin
+ tc_ctr = tc_ctr + 1;
+
+ $display("");
+ $display("--- test13: Reset allowed from firmware mode.");
+ tb_syscall = 0;
+ reset_dut();
+
+ write_word(ADDR_SYSTEM_RESET, 32'h1);
+ check_equal(tb_system_reset, 1);
+
+ $display("--- test13: Reset not allowed from app mode.");
+ reset_dut();
+ fetch_instruction(APP_RAM_START);
+
+ write_word(ADDR_SYSTEM_RESET, 32'h1);
+ check_equal(tb_system_reset, 0);
+
+ $display("--- test13: Reset allowed from syscall.");
+ reset_dut();
+ fetch_instruction(APP_RAM_START);
+ tb_syscall = 1;
+
+ write_word(ADDR_SYSTEM_RESET, 32'h1);
+ check_equal(tb_system_reset, 1);
+
+ tb_syscall = 0;
+
+ $display("--- test13: completed.");
+ $display("");
+ end
+ endtask // test13
+
+
//----------------------------------------------------------------
// exit_with_error_code()
//
@@ -746,7 +1195,6 @@ module tb_tk1 ();
test1();
test2();
test3();
- test4();
test5();
test6();
test7();
@@ -754,6 +1202,9 @@ module tb_tk1 ();
test9();
test9();
test10();
+ test11();
+ test12();
+ test13();
display_test_result();
$display("");
diff --git a/hw/application_fpga/core/uart/rtl/uart_core.v b/hw/application_fpga/core/uart/rtl/uart_core.v
index 95716ad..7a4dd61 100644
--- a/hw/application_fpga/core/uart/rtl/uart_core.v
+++ b/hw/application_fpga/core/uart/rtl/uart_core.v
@@ -360,7 +360,7 @@ module uart_core (
// Just a glitch.
rxd_bitrate_ctr_rst = 1;
erx_ctrl_new = ERX_IDLE;
- erx_ctrl_we = 1;
+ erx_ctrl_we = 1;
end
else begin
diff --git a/hw/application_fpga/core/uart/rtl/uart_fifo.v b/hw/application_fpga/core/uart/rtl/uart_fifo.v
index 3c2449a..0963796 100644
--- a/hw/application_fpga/core/uart/rtl/uart_fifo.v
+++ b/hw/application_fpga/core/uart/rtl/uart_fifo.v
@@ -50,7 +50,7 @@ module uart_fifo (
output wire [7 : 0] out_data,
input wire out_ack,
- output wire fpga_cts
+ output wire fpga_cts
);
diff --git a/hw/application_fpga/core/uds/README.md b/hw/application_fpga/core/uds/README.md
index 0a100d8..8722bf5 100644
--- a/hw/application_fpga/core/uds/README.md
+++ b/hw/application_fpga/core/uds/README.md
@@ -6,8 +6,7 @@ Unique Device Secret core
This core store and protect the Unique Device Secret (UDS) asset. The
UDS can be accessed as eight separate 32-bit words. The words can only
-be accessed as long as the system_mode input is low, implying that the
-CPU is executing the FW.
+be accessed as long as the `en` input is high.
The UDS words can be accessed in any order, but a given word can only
be accessed once between reset cycles. This read once functionality is
diff --git a/hw/application_fpga/core/uds/rtl/uds.v b/hw/application_fpga/core/uds/rtl/uds.v
index 2aaa112..8c8f1a3 100644
--- a/hw/application_fpga/core/uds/rtl/uds.v
+++ b/hw/application_fpga/core/uds/rtl/uds.v
@@ -17,8 +17,7 @@ module uds (
input wire clk,
input wire reset_n,
- input wire system_mode,
-
+ input wire en,
input wire cs,
input wire [ 2 : 0] address,
output wire [31 : 0] read_data,
@@ -89,7 +88,7 @@ module uds (
if (cs) begin
tmp_ready = 1'h1;
- if (!system_mode) begin
+ if (en) begin
if (uds_rd_reg[address[2 : 0]] == 1'h0) begin
uds_rd_we = 1'h1;
end
diff --git a/hw/application_fpga/core/uds/tb/tb_uds.v b/hw/application_fpga/core/uds/tb/tb_uds.v
index 9792c19..9453417 100644
--- a/hw/application_fpga/core/uds/tb/tb_uds.v
+++ b/hw/application_fpga/core/uds/tb/tb_uds.v
@@ -37,7 +37,7 @@ module tb_uds ();
reg tb_clk;
reg tb_reset_n;
- reg tb_system_mode;
+ reg tb_app_mode;
reg tb_cs;
reg [ 7 : 0] tb_address;
wire [31 : 0] tb_read_data;
@@ -50,7 +50,7 @@ module tb_uds ();
.clk(tb_clk),
.reset_n(tb_reset_n),
- .system_mode(tb_system_mode),
+ .app_mode(tb_app_mode),
.cs(tb_cs),
.address(tb_address),
@@ -95,7 +95,7 @@ module tb_uds ();
$display("State of DUT at cycle: %08d", cycle_ctr);
$display("------------");
$display("Inputs and outputs:");
- $display("system_mode: 0x%1x", tb_system_mode);
+ $display("app_mode: 0x%1x", tb_app_mode);
$display("cs: 0x%1x, address: 0x%02x, read_data: 0x%08x", tb_cs, tb_address, tb_read_data);
$display("");
@@ -160,7 +160,7 @@ module tb_uds ();
tb_clk = 1'h0;
tb_reset_n = 1'h1;
- tb_system_mode = 1'h0;
+ tb_app_mode = 1'h0;
tb_cs = 1'h0;
tb_address = 8'h0;
end
diff --git a/hw/application_fpga/data/udi.hex b/hw/application_fpga/data/udi.hex
index 9bc5559..c127fdf 100644
--- a/hw/application_fpga/data/udi.hex
+++ b/hw/application_fpga/data/udi.hex
@@ -1,2 +1,2 @@
-00010203
+073570c0
04050607
diff --git a/hw/application_fpga/firmware.bin.sha512 b/hw/application_fpga/firmware.bin.sha512
index 22483e0..bf75d58 100644
--- a/hw/application_fpga/firmware.bin.sha512
+++ b/hw/application_fpga/firmware.bin.sha512
@@ -1 +1 @@
-39d5aee11b8553544ba9171f83fbe6f5b7546a15c70d03325e72a2b0ca86c8f7a2b5b6bf121d1d3ffc84a502a2a1a6f3ea140d1424cd424336e055be2f394f83 firmware.bin
+4be2767d5ddd30b5422f4b58075365cb6d988259e88ffa14d6d243560b289f54eaf0c351e9d744cff8ec3a18b1830f3925a86f36bd2096c12eccce25ed44993c firmware.bin
diff --git a/hw/application_fpga/fw/README.md b/hw/application_fpga/fw/README.md
index 8a11fb5..9f5771a 100644
--- a/hw/application_fpga/fw/README.md
+++ b/hw/application_fpga/fw/README.md
@@ -1,35 +1,44 @@
-# Firmware
+# Firmware implementation notes
## Introduction
-This text is an introduction to, a requirement specification of,
-and some implementation notes of the TKey firmware. It also gives a
-few hint on developing and debugging the firmware.
-
-This text is specific for the firmware. For a more general description
-on how to implement device apps, see [the TKey Developer
-Handbook](https://dev.tillitis.se/).
+This text is specific for the firmware, the piece of software in TKey
+ROM. For a more general description on how to implement device apps,
+see [the TKey Developer Handbook](https://dev.tillitis.se/).
## Definitions
-- Firmware - software in ROM responsible for loading applications. The
- firmware is included as part of the FPGA bitstream and not
- replacable on a usual consumer TKey.
-- Device application or app - software supplied by the client which is
- received, loaded, measured, and started by the firmware.
+- Firmware: Software in ROM responsible for loading, measuring,
+ starting applications, and providing system calls. The firmware is
+ included as part of the FPGA bitstream and not replacable on a usual
+ consumer TKey.
+- Client: Software running on a computer or a mobile phone the TKey is
+ inserted into.
+- Device application or app: Software supplied by the client or from
+ flash that runs on the TKey.
## CPU modes and firmware
The TKey has two modes of software operation: firmware mode and
-application mode. The TKey always starts in firmware mode and starts
-the firmware. When the firmware is about to start the application it
-switches to a more constrained environment, the application mode.
+application mode. The TKey always starts in firmware mode when it
+starts the firmware. When the application starts the hardware
+automatically switches to a more constrained environment: the
+application mode.
-The TKey hardware cores are memory mapped. Firmware has complete
-access, except that the UDS is readable only once. The memory map is
-constrained when running in application mode, e.g. FW\_RAM and UDS
-isn't readable, and several other hardware addresses are either not
-readable or not writable for the application.
+The TKey hardware cores are memory mapped but the memory access is
+different depending on mode. Firmware has complete access, except that
+the Unique Device Secret (UDS) words are readable only once even in
+firmware mode. The memory map is constrained when running in
+application mode, e.g. FW\_RAM and UDS isn't readable, and several
+other hardware addresses are either not readable or not writable for
+the application.
+
+When doing system calls from a device app the context switches back to
+firmware mode. However, the UDS is still not available, protected by
+two measures: 1) the UDS words can only be read out once and have
+already been read by firmware when measuring the app, and, 2) the UDS
+is protected by hardware after the execution leaves ROM for the first
+time.
See the table in [the Developer
Handbook](https://dev.tillitis.se/memory/) for an overview about the
@@ -37,171 +46,441 @@ memory access control.
## Communication
-The firmware communicates to the host via the
-`UART_{RX,TX}_{STATUS,DATA}` registers, using the framing protocol
-described in the [Framing
-Protocol](https://dev.tillitis.se/protocol/).
+The firmware communicates with the client using the
+`UART_{RX,TX}_{STATUS,DATA}` registers. On top of that is uses three
+protocols: The USB Mode protocol, the TKey framing protocol, and the
+firmware's own protocol.
+
+To communicate between the CPU and the CH552 USB controller it uses an
+internal protocol, used only within the TKey, which we call the USB
+Mode Protocol. It is used in both directions.
+
+| *Name* | *Size* | *Comment* |
+|----------|-----------|------------------------------------|
+| Endpoint | 1B | Origin or destination USB endpoint |
+| Length | 1B | Number of bytes following |
+| Payload | See above | Actual data from or to firmware |
+
+The different endpoints:
+
+| *Name* | *Value* | *Comment* |
+|--------|---------|----------------------------------------------------------------------|
+| CH552 | 0x04 | USB controller control |
+| CDC | 0x08 | USB CDC-ACM, a serial port on the client. |
+| FIDO | 0x10 | A USB FIDO security token device, useful for FIDO-type applications. |
+| CCID | 0x20 | USB CCID, a port for emulating a smart card |
+| DEBUG | 0x40 | A USB HID special debug pipe. Useful for debug prints. |
+
+You can turn on and off different endpoints dynamically by sending
+commands to the `CH552` control endpoint. When the TKey starts only
+the `CH552` and the `CDC` endpoints are active. To change this, send a
+command to `CH552` in this form:
+
+| *Name* | *Size* | *Comment* |
+|----------|--------|-------------------------------|
+| Command | 1B | Command to the CH552 firmware |
+| Argument | 1B | Data for the command |
+
+Commands:
+
+| *Name* | *Value* | *Argument* |
+|------------------|---------|----------------------|
+| Enable endpoints | 0x01 | Bitmask of endpoints |
+
+On top of the USB Mode Protocol is [the TKey Framing
+Protocol](https://dev.tillitis.se/protocol/) which is described in the
+Developer Handbook.
The firmware uses a protocol on top of this framing layer which is
-used to bootstrap the application. All commands are initiated by the
+used to load a device application. All commands are initiated by the
client. All commands receive a reply. See [Firmware
-protocol](#firmware-protocol) for specific details.
+protocol](http://dev.tillitis.se/protocol/#firmware-protocol) in the
+Dev Handbook for specific details.
## Memory constraints
-- ROM: 6 kByte.
-- FW\_RAM: 2 kByte.
-- RAM: 128 kByte.
+| *Name* | *Size* | *FW mode* | *App mode* |
+|---------|-----------|-----------|------------|
+| ROM | 8 kByte | r-x | r |
+| FW\_RAM | 4 kByte* | rw- | - |
+| RAM | 128 kByte | rwx | rwx |
+
+* FW\_RAM is divided into the following areas:
+
+- fw stack: 3000 bytes.
+- resetinfo: 256 bytes.
+- .data and .bss: 840 bytes.
## Firmware behaviour
-The purpose of the firmware is to load, measure, and start an
-application received from the client over the USB/UART.
+The purpose of the firmware is to:
+
+1. Load, measure, and start an application received from the client
+ over the USB/UART or from one of two flash app slots.
+2. Provide functionality to run only app's with specific BLAKE2s
+ digests.
+3. Provide system calls to access the filesystem and get other data.
The firmware binary is part of the FPGA bitstream as the initial
-values of the Block RAMs used to construct the `FW_ROM`. The `FW_ROM`
-start address is located at `0x0000_0000` in the CPU memory map, which
-is the CPU reset vector.
+values of the Block RAMs used to construct the ROM. The ROM is located
+at `0x0000_0000`. This is also the CPU reset vector.
+
+### Reset type
+
+When the TKey is started or resetted it can load an app from different
+sources. We call this the reset type. Reset type is located in the
+resetinfo part of FW\_RAM. The different reset types loads and start
+an app from:
+
+1. Flash slot 0 (default): `FLASH0` with a specific app hash defined
+ in a constant in firmware.
+2. Flash slot 1: `FLASH1`.
+3. Flash slot 0 with a specific app hash left from previous app:
+ `FLASH0_VER`
+4. Flash slot 1 with a specific app hash left from previous app:
+ `FLASH1_VER`.
+5. Client: `CLIENT`.
+6. Client with a specific app hash left from previous app:
+ `CLIENT_VER`.
### Firmware state machine
-This is the state diagram of the firmware. There are only four states.
Change of state occur when we receive specific I/O or a fatal error
occurs.
```mermaid
stateDiagram-v2
- S1: initial
- S2: loading
- S3: running
- SE: failed
+ S0: INITIAL
+ S1: WAITCOMMAND
+ S2: LOADING
+ S3: LOAD_FLASH
+ S4: LOAD_FLASH_MGMT
+ S5: START
+ SE: FAIL
- [*] --> S1
+ [*] --> S0
- S1 --> S1: Commands
- S1 --> S2: LOAD_APP
- S1 --> SE: Error
+ S0 --> S1
+ S0 --> S4: Default
+ S0 --> S3
+ S0 --> SE: Unknown reset type
- S2 --> S2: LOAD_APP_DATA
- S2 --> S3: Last block received
- S2 --> SE: Error
+ S1 --> S1: Other commands
+ S1 --> S2: LOAD_APP
+ S1 --> SE: Error
- S3 --> [*]
+ S2 --> S2: LOAD_APP_DATA
+ S2 --> S5: Last block received
+ S2 --> SE: Error
+
+ S3 --> S5
+ S3 --> SE
+
+ S4 --> S5
+ S4 --> SE
+
+ SE --> [*]
+ S5 --> [*]
```
States:
-- `initial` - At start. Allows the commands `NAME_VERSION`, `GET_UDI`,
- `LOAD_APP`.
-- `loading` - Expect application data. Allows only the command
- `LOAD_APP_DATA`.
-- `run` - Computes CDI and starts the application. Allows no commands.
-- `fail` - Stops waiting for commands, flashes LED forever. Allows no
- commands.
+- *INITIAL*: Transitions to next state through reset type left in
+ `FW_RAM`.
+- *WAITCOMMAND*: Waiting for initial commands from client. Allows the
+ commands `NAME_VERSION`, `GET_UDI`, `LOAD_APP`.
+- *LOADING*: Expecting application data from client. Allows only the
+ command `LOAD_APP_DATA` to continue loading the device app.
+- *LOAD_FLASH*: Loading an app from flash. Allows no commands.
+- *LOAD_FLASH_MGMT*: Loading an app from flash and registering it as a
+ prospective managment app. Allows no commands.
+- *START*: Computes CDI. Possibly verifies app. Starts the
+ application. Does not return to firmware. Allows no commands.
+- *FAIL* - Halts CPU. Allows no commands.
-Commands in state `initial`:
+Allowed data in state *INITIAL*:
-| *command* | *next state* |
-|-----------------------|--------------|
-| `FW_CMD_NAME_VERSION` | unchanged |
-| `FW_CMD_GET_UDI` | unchanged |
-| `FW_CMD_LOAD_APP` | `loading` |
-| | |
+| *reset type* | *next state* |
+|--------------|-------------------|
+| `FLASH0` | *LOAD_FLASH_MGMT* |
+| `FLASH1` | *LOAD_FLASH* |
+| `FLASH0_VER` | *LOAD_FLASH* |
+| `FLASH1_VER` | *LOAD_FLASH* |
+| `CLIENT` | *WAITCOMMAND* |
+| `CLIENT_VER` | *WAITCOMMAND* |
+| unknown | *FAIL* |
-Commands in state `loading`:
+I/O in state *LOAD_FLASH*:
-| *command* | *next state* |
-|------------------------|----------------------------------|
-| `FW_CMD_LOAD_APP_DATA` | unchanged or `run` on last chunk |
+| *I/O* | *next state* |
+|--------------------|--------------|
+| Last app data read | *START* |
+
+I/O in state *LOAD_FLASH_MGMT*:
+
+| *I/O* | *next state* |
+|--------------------|--------------|
+| Last app data read | *START* |
+
+Commands in state *WAITCOMMAND*:
+
+| *command* | *next state* |
+|-----------------------|--------------------------------------------|
+| `FW_CMD_NAME_VERSION` | unchanged |
+| `FW_CMD_GET_UDI` | unchanged |
+| `FW_CMD_LOAD_APP` | *LOADING* or unchanged on invalid app size |
+| | |
+
+Commands in state *LOADING*:
+
+| *command* | *next state* |
+|------------------------|------------------------------------|
+| `FW_CMD_LOAD_APP_DATA` | unchanged or *START* on last chunk |
+
+No other states allows commands.
See [Firmware protocol in the Dev
Handbook](http://dev.tillitis.se/protocol/#firmware-protocol) for the
definition of the specific commands and their responses.
-State changes from "initial" to "loading" when receiving `LOAD_APP`,
-which also sets the size of the number of data blocks to expect. After
-that we expect several `LOAD_APP_DATA` commands until the last block
-is received, when state is changed to "running".
+Plain text explanation of the states:
-In "running", the loaded device app is measured, the Compound Device
-Identifier (CDI) is computed, we do some cleanup of firmware data
-structures, flip to application mode, and finally start the app, which
-ends the firmware state machine.
+- *INITIAL*: Start here. Check the `FW_RAM` for the `resetinfo` type
+ for what to do next.
-The device app is now running in application mode. There is no other
-means of getting back from application mode to firmware mode than
-resetting/power cycling the device. Note that ROM is still accessible
-in the memory map, so it's still possible to execute firmware code in
-application mode, but with no privileged access.
+ For type `FLASH0` transition to *LOAD_FLASH_MGMT* because the app in
+ slot 0 is considered a special management app. For all other types
+ beginning with `FLASH*` transition to *LOAD_FLASH* to load an
+ ordinary app from flash.
-Firmware loads the application at the start of RAM (`0x4000_0000`). It
-uses the special FW\_RAM for its own stack.
+ For type `CLIENT*` transitionto *WAITCOMMAND* to expect a device app
+ from the client.
-When the firmware starts it clears all FW\_RAM, then sets up a stack
-there before jumping to `main()`.
+ If type is unknown, error out.
-When reset is released, the CPU starts executing the firmware. It
-begins by clearing all CPU registers, and then sets up a stack for
-itself and then jumps to main().
+- *LOAD_FLASH*: Load device app from flash into RAM, app slot taken
+ from context. Compute a BLAKE2s digest over the entire app.
+ Transition to *START*.
-Beginning at `main()` it sets up the "system calls", then fills the
-entire RAM with pseudo random data and setting up the RAM address and
-data hardware scrambling with values from the True Random Number
-Generator (TRNG). It then waits for data coming in through the UART.
+- *LOAD_FLASH_MGMT*: Load device app from flash into RAM, app slot
+ always 0. Compute a BLAKE2s digest over the entire app. Register the
+ app as a prospective management app. Transition to *START*.
-Typical expected use scenario:
+- *WAITCOMMAND*: Wait for commands from the client. Transition to
+ *LOADING* on `LOAD_APP` command, which also sets the size of the
+ number of data blocks to expect.
- 1. The client sends the `FW_CMD_LOAD_APP` command with the size of
- the device app and the optional 32 byte hash of the user-supplied
- secret as arguments and gets a `FW_RSP_LOAD_APP` back. After
- using this it's not possible to restart the loading of an
- application.
+- *LOADING*: Wait for several `LOAD_APP_DATA` commands until the last
+ block is received, then transition to *START*.
- 2. If the the client receive a sucessful response, it will send
- multiple `FW_CMD_LOAD_APP_DATA` commands, together containing the
- full application.
+- *START*: Compute the Compound Device Identifier (CDI). If we have a
+ registered verification digest, verify that the app we are about to
+ start is indeed the correct app. This also means that a prospective
+ management app is now verified.
- 3. On receiving`FW_CMD_LOAD_APP_DATA` commands the firmware places
- the data into `0x4000_0000` and upwards. The firmware replies
- with a `FW_RSP_LOAD_APP_DATA` response to the client for each
- received block except the last data block.
+ Clean up firmware data structures, enable the system calls, and
+ start the app, which ends the firmware state machine. Hardware
+ guarantees that we leave firmware mode automatically when the
+ program counter leaves ROM.
- 4. When the final block of the application image is received with a
- `FW_CMD_LOAD_APP_DATA`, the firmware measure the application by
- computing a BLAKE2s digest over the entire application. Then
- firmware send back the `FW_RSP_LOAD_APP_DATA_READY` response
- containing the digest.
+- *FAIL*: Execute an illegal instruction which traps the CPU. Hardware
+ detects a trapped CPU and blinks the status LED in red until power
+ loss. No further instructions are executed.
- 5. The Compound Device Identifier
- ([CDI]((#compound-device-identifier-computation))) is then
- computed by doing a new BLAKE2s using the Unique Device Secret
- (UDS), the application digest, and any User Supplied Secret
- (USS).
-
- 6. The start address of the device app, currently `0x4000_0000`, is
- written to `APP_ADDR` and the size of the binary to `APP_SIZE` to
- let the device application know where it is loaded and how large
- it is, if it wants to relocate in RAM.
-
- 7. The firmware now clears the special `FW_RAM` where it keeps it
- stack. After this it performs no more function calls and uses no
- more automatic variables.
-
- 8. Firmware starts the application by first switching to from
- firmware mode to application mode by writing to the `SYSTEM_MODE_CTRL`
- register. In this mode the MMIO region is restricted, e.g. some
- registers are removed (`UDS`), and some are switched from
- read/write to read-only (see [the memory
- map](https://dev.tillitis.se/memory/)).
-
- Then the firmware jumps to what is in `APP_ADDR` which starts the
- application.
+After leaving *START* the device app is now running in application
+mode. We can, however, return to firmware mode (excepting access to
+the UDS) by doing system calls. Note that ROM is still readable, but
+is now hardware protected from execution, except through the system
+call mechanism.
If during this whole time any commands are received which are not
-allowed in the current state, we enter the "failed" state and execute
-an illegal instruction. An illegal instruction traps the CPU and
-hardware blinks the status LED red until a power cycle. No further
-instructions are executed.
+allowed in the current state, or any errors occur, we enter the *FAIL*
+state.
+
+### Golden path from start to default app
+
+Firmware will load the device application at the start of RAM
+(`0x4000_0000`) from either flash or from the client through the UART.
+Firmware is using a part of the FW\_RAM for its own stack.
+
+When reset is released, the CPU starts executing the firmware. It
+begins in `start.S` by clearing all CPU registers, clears all FW\_RAM,
+except the part reserved for the resetinfo area, sets up a stack for
+itself there, and then jumps to `main()`. Also included in the
+assembly part of firmware is an interrupt handler for the system
+calls, but the handler is not yet enabled.
+
+Beginning at `main()` it fills the entire RAM with pseudo random data
+and setting up the RAM address and data hardware scrambling with
+values from the True Random Number Generator (TRNG).
+
+Firmware then proceeds to:
+
+1. Read the partition table from flash and store in FW\_RAM.
+
+2. Reset the CH552 USB controller to a known state, only allowing the
+ CDC USB endpoint and the internal command channel between the CPU
+ and the CH552.
+
+3. Check the special resetinfo area in FW\_RAM for reset type. Type
+ zero means default behaviour, load from flash app slot 0, expecting
+ the app there to have a specific hardcoded BLAKE2s digest.
+
+4. Load app data from flash slot 0 into RAM.
+
+5. Compute a BLAKE2s digest of the loaded app.
+
+6. Compare the computed digest against the allowed app digest
+ hardcoded in the firmware. If it's not equal, halt CPU.
+
+7. [Start the device app](#start-the-device-app).
+
+### Start the device app
+
+1. Check if there is a verification digest left from the previous app
+ in the resetinfo. If it is, compare with the loaded app's already
+ computed digest. Halt CPU if different.
+
+2. Compute the Compound Device Identifier
+ ([CDI]((#compound-device-identifier-computation))) by doing a
+ BLAKE2s using the Unique Device Secret (UDS), the application
+ digest, and any User Supplied Secret (USS) digest already received.
+
+3. Write the start address of the device app, currently `0x4000_0000`,
+ to `APP_ADDR` and the size of the loaded binary to `APP_SIZE` to
+ let the device application know where it is loaded and how large it
+ is, if it wants to relocate in RAM.
+
+4. Clear the stack part of `FW_RAM`.
+
+5. Enable system call interrupt handler.
+
+6. Start the application by jumping to the contents of `APP_ADDR`.
+ Hardware automatically switch from firmware mode to application
+ mode. In this mode some memory access is restricted, e.g. some
+ addresses are inaccessible (`UDS`), and some are switched from
+ read/write to read-only (see [the memory
+ map](https://dev.tillitis.se/memory/)).
+
+### Management app, chaining apps and verified boot
+
+Normally, the TKey measures a device app and mixes it together with
+the Unique Device Secret in hardware to produce the [Compound Device
+Identifier]((#compound-device-identifier-computation)). The CDI can
+then be used for creating key material. However, since any key
+material created like this will change if the app is changed even the
+slightest, this make it hard to upgrade apps and keep the key
+material.
+
+This is where a combination of measured boot and verified boot comes
+in!
+
+To support verified boot the firmware supports reset types with
+verification. This means that the firmware will load an app as usual
+either from flash or from the client, but before starting the app it
+will verify the new app's computed digest with a verification digest.
+The verification digest can either be stored in the firmware itself or
+left to it from a previous app, a verified boot loader app.
+
+Such a verified boot loader app:
+
+- Might be loaded from either flash or client.
+
+- Typically includes a security policy, for instance a public key and
+ code to check a crytographic signature.
+
+- Can be specifically trusted by firmware to be able to do filesystem
+ management to be able to update an app slot on flash. Add the app's
+ digest to `allowed_app_digest` in `mgmt_app.c` to allow it to use
+ `PRELOAD_DELETE`, `PRELOAD_STORE`, `PRELOAD_STORE_FIN`, and
+ `PRELOAD_GET_DIGSIG`.
+
+It works like this:
+
+- The app reads a digest of the next app in the chain and the
+ signature over the digest from either the filesystem (syscall
+ `PRELOAD_GET_DIGSIG`) or sent from the client.
+
+- If the signature provided over the digest is verified against the
+ public key the app use the system call `RESET` with the reset type
+ set to `START_FLASH0_VER`, `START_FLASH1_VER`, or `START_CLIENT_VER`
+ depending on where it wants the next app to start from. It also
+ sends the now verified app digest to the firmware in the same system
+ call.
+
+- The key is reset and firmware starts again. It checks:
+
+ 1. The reset type. Start from client or a slot in the filesystem?
+ 2. The expected digest of the next app.
+
+- Firmware loads the app from the expected source.
+
+- Firmware refuses to start if the loaded app has a different digest.
+
+- If the app was allowed to start it can now use something
+ deterministic left for it in resetinfo by the verified boot loader
+ app as a seed for it's key material and no longer use CDI for the
+ purpose.
+
+We propose that a loader app can derive the seed for the next app by
+creating a shared secret, perhaps something as easy as:
+
+```
+secret = blake2s(cdi, "name-of-next-app")
+```
+
+The loader shares the secret with the next app by putting it in the
+part of `resetinfo` that is reserved for inter-app communication.
+
+The next app can now use the secret as a seed for it's own key
+material. Depending on the app's behaviour and the number of keys it
+needs it can derive more keys, for instance by having nonces stored on
+its flash area and doing:
+
+```
+secret1 = blake2s(secret, nonce1)
+secret2 = blake2s(secret, nonce2)
+...
+```
+
+Now it can create many secrets deterministically, as long as there is
+some space left on flash for the nonces and all of them can be traced
+to the measured identity of the loader app, giving all the features of
+the measured boot system.
+
+### App loaded from client
+
+The default is always to start from a verified app in flash slot
+0. To be able to load an app from the client you have to send
+something to the app to reset the TKey with a reset type of
+`START_CLIENT` or `START_CLIENT_VER`.
+
+After reset, firmware will:
+
+1. Wait for data coming in through the UART.
+
+2. The client sends the `FW_CMD_LOAD_APP` command with the size of
+ the device app and the optional 32 byte hash of the user-supplied
+ secret as arguments and gets a `FW_RSP_LOAD_APP` back. After
+ using this it's not possible to restart the loading of an
+ application.
+
+3. On a sucessful response, the client will send multiple
+ `FW_CMD_LOAD_APP_DATA` commands, together containing the full
+ application.
+
+4. On receiving`FW_CMD_LOAD_APP_DATA` commands the firmware places
+ the data into `0x4000_0000` and upwards. The firmware replies
+ with a `FW_RSP_LOAD_APP_DATA` response to the client for each
+ received block except the last data block.
+
+5. When the final block of the application image is received with a
+ `FW_CMD_LOAD_APP_DATA`, the firmware measure the application by
+ computing a BLAKE2s digest over the entire application. Then
+ firmware send back the `FW_RSP_LOAD_APP_DATA_READY` response
+ containing the digest.
+
+6. [Start the device app](#start-the-device-app).
### User-supplied Secret (USS)
@@ -221,65 +500,231 @@ CDI = blake2s(UDS, blake2s(app), USS)
In an ideal world, software would never be able to read UDS at all and
we would have a BLAKE2s function in hardware that would be the only
thing able to read the UDS. Unfortunately, we couldn't fit a BLAKE2s
-implementation in the FPGA at this time.
+implementation in the FPGA.
The firmware instead does the CDI computation using the special
firmware-only `FW_RAM` which is invisible after switching to app mode.
-We keep the entire firmware stack in `FW_RAM` and clear it just before
-switching to app mode just in case.
+We keep the entire firmware stack in `FW_RAM` and clear the stack just
+before switching to app mode just in case.
We sleep for a random number of cycles before reading out the UDS,
call `blake2s_update()` with it and then immediately call
`blake2s_update()` again with the program digest, destroying the UDS
stored in the internal context buffer. UDS should now not be in
`FW_RAM` anymore. We can read UDS only once per power cycle so UDS
-should now not be available to firmware at all.
+should now not be available even to firmware.
Then we continue with the CDI computation by updating with an optional
-USS and then finalizing the hash, storing the resulting digest in
+USS digest and finalizing the hash, storing the resulting digest in
`CDI`.
-We also clear the entire `FW_RAM` where the stack lived, including the
-BLAKE2s context with the UDS, very soon after that, just before
-jumping to the application.
+### System calls
-### Firmware services
-
-The firmware exposes a BLAKE2s function through a function pointer
-located in MMIO `BLAKE2S` (see [memory
-map](system_description.md#memory-mapped-hardware-functions)) with the
-with function signature:
-
-```c
-int blake2s(void *out, unsigned long outlen, const void *key,
- unsigned long keylen, const void *in, unsigned long inlen,
- blake2s_ctx *ctx);
+The firmware provides a system call mechanism through the use of the
+PicoRV32 interrupt handler. They are triggered by writing to the
+trigger address: 0xe1000000. It's typically done with a function
+signature like this:
```
-
-where `blake2s_ctx` is:
-
-```c
-typedef struct {
- uint8_t b[64]; // input buffer
- uint32_t h[8]; // chained state
- uint32_t t[2]; // total number of bytes
- size_t c; // pointer for b[]
- size_t outlen; // digest size
-} blake2s_ctx;
+int syscall(uint32_t number, uint32_t arg1, uint32_t arg2,
+ uint32_t arg3);
```
-The `libcommon` library in
-[tkey-libs](https://github.com/tillitis/tkey-libs/)
-has a wrapper for using this function called `blake2s()` which needs
-to be maintained if you do any changes to the firmware call.
+Arguments are system call number and up to 6 generic arguments passed
+to the system call handler. The caller should place the system call
+number in the a0 register and the arguments in registers a1 to a7
+according to the RISC-V calling convention. The caller is responsible
+for saving and restoring registers.
+
+The syscall handler returns execution on the next instruction after
+the store instruction to the trigger address. The return value from
+the syscall is now available in x10 (a0).
+
+The syscall numbers are kept in `syscall_num.h`. The syscalls are
+handled in `syscall_handler()` in `syscall_handler.c`.
+
+#### `RESET`
+
+```
+struct reset {
+ uint32_t type; // Reset type
+ uint8_t app_digest[32]; // Digest of next app in chain to verify
+ uint8_t next_app_data[220]; // Data to leave around for next app
+};
+
+struct reset rst;
+uint32_t len; // Length of data in next_app_data.
+
+syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, len, 0);
+```
+
+Resets the TKey. Does not return.
+
+You can pass data to the firmware about the reset type `type` and a
+digest that the next app must have. You can also leave some data to
+the next app in the chain in `next_app_data`.
+
+The types of reset are defined in `reset.h`:
+
+| *Name* | *Comment* |
+|--------------------|------------------------------------------------|
+| `START_FLASH0` | Load next app from flash slot 0 |
+| `START_FLASH1` | Load next app from flash slot 1 |
+| `START_FLASH0_VER` | Load next app from flash slot 0, but verify it |
+| `START_FLASH1_VER` | Load next app from flash slot 1, but verify it |
+| `START_CLIENT` | Load next app from client |
+| `START_CLIENT_VER` | Load next app from client |
+
+#### `ALLOC_AREA`
+
+```
+syscall(TK1_SYSCALL_ALLOC_AREA, 0, 0, 0);
+```
+
+Allocate a flash area for the current app. Returns 0 on success.
+
+#### `DEALLOC_AREA`
+
+```
+syscall(TK1_SYSCALL_DEALLOC_AREA, 0, 0, 0);
+```
+
+Free an already allocated flash area for the current app. Returns 0 on
+success.
+
+#### `WRITE_DATA`
+
+```
+uint32_t offset = 0;
+uint8_t buf[17];
+
+syscall(TK1_SYSCALL_WRITE_DATA, offset, (uint32_t)buf, sizeof(buf))
+```
+
+Write data in `buf` to the app's flash area at byte `offset` within
+the area. Returns 0 on success.
+
+At most 4096 bytes can be written at once and `offset` must be a
+multiple of 4096 bytes.
+
+#### `READ_DATA`
+
+```
+uint32_t offset = 0;
+uint8_t buf[17];
+
+syscall(TK1_SYSCALL_READ_DATA, offset, (uint32_t)buf, sizeof(buf);
+```
+
+Read into `buf` at byte `offset` from the app's flash area.
+
+#### `ERASE_DATA`
+
+```
+uint32_t offset = 0;
+uint32_t size = 4096;
+
+syscall(TK1_SYSCALL_ERASE_DATA, offset, size, 0);
+```
+
+Erase `size` bytes from `offset` within the area. Returns 0 on
+success.
+
+Both `size` and `offset` must be a multiple of 4096 bytes.
+
+#### `PRELOAD_DELETE`
+
+```
+syscall(TK1_SYSCALL_PRELOAD_DELETE, 0, 0, 0);
+```
+
+Delete the app in flash slot 1. Returns 0 on success. Only available
+for the verified management app.
+
+#### `PRELOAD_STORE`
+
+```
+uint8_t *appbinary;
+uint32_t offset;
+uint32_t size;
+
+syscall(TK1_SYSCALL_PRELOAD_STORE, offset, (uint32_t)appbinary,
+ size);
+```
+
+Store an app, or possible just a block of an app, from the `appbinary`
+buffer in flash slot 1 at byte `offset`.
+
+If you can't fit your entire app in the buffer, call `PRELOAD_STORE`
+many times as you receive the binary from the client. Returns 0 on
+success.
+
+At most 4096 bytes can be written at once and `offset` must be a
+multiple of 4096 bytes.
+
+Only available for the verified management app.
+
+#### `PRELOAD_STORE_FIN`
+
+```
+uint8_t app_digest[32];
+uint8_t app_signature[64];
+size_t app_size;
+
+syscall(TK1_SYSCALL_PRELOAD_STORE_FIN, app_size,
+ (uint32_t)app_digest, (uint32_t)app_signature)
+```
+
+Finalize storing of an app where the complete binary size is
+`app_size` in flash slot 1. Returns 0 on success. Only available for
+the verified management app.
+
+Compute a BLAKE2s hash digest over the entire binary. Pass the result
+in `app_digest`.
+
+Sign `app_digest` with your Ed25519 private key and pass the
+resulting signature in `app_signature`.
+
+#### `PRELOAD_GET_DIGSIG`
+
+```
+uint8_t app_digest[32];
+uint8_t app_signature[64];
+
+syscall(TK1_SYSCALL_PRELOAD_GET_DIGSIG, (uint32_t)app_digest,
+ (uint32_t)app_signature, 0);
+```
+
+Copies the digest and signature of app in flash slot 1 to `app_digest`
+and `app_signature`. Returns 0 on success. Only available for the
+verified management app.
+
+#### `STATUS`
+
+```
+syscall(TK1_SYSCALL_PRELOAD_STATUS, 0, 0, 0);
+```
+
+Returns filesystem status. Non-zero when problems have been detected,
+so far only that the first copy of the partition table didn't pass
+checks.
+
+#### `GET_VIDPID`
+
+```
+syscall(TK1_SYSCALL_PRELOAD_STATUS, 0, 0, 0);
+```
+
+Returns Vendor and Product ID. Notably the serial number is not
+returned, so a device app can't identify what particular TKey it is
+running on.
## Developing firmware
Standing in `hw/application_fpga/` you can run `make firmware.elf` to
build just the firmware. You don't need all the FPGA development
tools. See [the Developer Handbook](https://dev.tillitis.se/tools/)
-for the tools you need. The easiest is probably to use your OCI image,
+for the tools you need. The easiest is probably to use our OCI image,
`ghcr.io/tillitis/tkey-builder`.
[Our version of qemu](https://dev.tillitis.se/tools/#qemu-emulator) is
@@ -287,11 +732,93 @@ also useful for debugging the firmware. You can attach GDB, use
breakpoints, et cetera.
There is a special make target for QEMU: `qemu_firmware.elf`, which
-sets `-DQEMU_CONSOLE`, so you can use plain debug prints using the
-helper functions in `lib.c` like `htif_puts()` `htif_putinthex()`
-`htif_hexdump()` and friends. Note that these functions are only
-usable in qemu and that you might need to `make clean` before
-building, if you have already built before.
+sets `-DQEMU_DEBUG`, so you can debug prints using the `debug_*()`
+functions. Note that these functions are only usable in QEMU and that
+you might need to `make clean` before building, if you have already
+built before.
+
+To build a flash image file suitable for use with qemu, use the
+`tools/tkeyimage` program. See its documentation.
+
+If you want debug prints to show up on the special TKey HID debug
+endpoint instead, define `-DTKEY_DEBUG`. This might mean you can't fit
+the firmware in the ROM space available, however. You will get a
+warning if it doesn't fit. In that case, just use explicit
+`puts(IO_DEBUG, ...)` or `puts(IO_CDC, ...)` and so on.
+
+Note that if you use `TKEY_DEBUG` you *must* have something listening
+on the corresponding HID device. It's usually the last HID device
+created. On Linux, for instance, this means the last reported hidraw
+in `dmesg` is the one you should do `cat /dev/hidrawX` on.
+
+### tkey-libs
+
+Most of the utility functions that the firmware use lives in
+`tkey-libs`. The canonical place where you can find tkey-libs is at:
+
+ https://github.com/tillitis/tkey-libs
+
+but we have vendored it in for firmware use in `../tkey-libs`. See top
+README for how to update.
+
+### Preparing the filesystem
+
+The TKey supports a simple filesystem. This filesystem must be
+initiated before starting for the first time. You need a [TKey
+Programmer Board](https://shop.tillitis.se/products/tkey-dev-kit) for
+this part.
+
+If you just want to build and flash the bitstream, the testloadapp in
+app slot 0, and the partition table copies in one go, place the TKey
+Unlocked in the TP1, then:
+
+Using Podman, from the top level directory:
+
+```
+cd contrib
+make flash
+```
+
+Using native tools:
+
+```
+cd hw/application_fpga
+make prog_flash
+```
+
+If you want to prepare the filesystem yourself:
+
+1. Choose your pre-loaded app. You *must* have a pre-loaded app, for
+ example `testloadapp`. Build it with the OCI image we use. The
+ binary needs to produce the BLAKE2s digest in `allowed_app_digest`
+ `tk1/mgmt_app.c`.
+
+2. Write the filesystem to flash:
+
+ ```
+ $ cd ../tools
+ $ ./load_preloaded_app.sh 0 ../fw/testloadapp/testloadapp.bin
+ ```
+
+If you want to use a different pre-loaded app you have to
+
+1. Check the BLAKE2s digest of the app. You can use `tools/b2s` to
+ compute it.
+
+2. Update the `allowed_app_digest` in `tk1/mgmt_app.c`.
+
+3. Create a new `default_partition.bin` using the
+ `tools/tkeyimage`, typically:
+
+ ```
+ $ tkeyimage -app0 path/to/your/app.bin -o default_partition.bin
+ ```
+
+4. Flash the filesystem image:
+
+ ```
+ $ ./load_preloaded_app.sh 0 path/to/your/app.bin
+ ```
### Test firmware
@@ -304,5 +831,9 @@ terminal program to the serial port device, even if it's running in
qemu. It waits for you to type a character before starting the tests.
It needs to be compiled with `-Os` instead of `-O2` in `CFLAGS` in the
-ordinary `application_fpga/Makefile` to be able to fit in the 6 kByte
-ROM.
+ordinary `application_fpga/Makefile` to be able to fit in ROM.
+
+### Test apps
+
+There are a couple of test apps, see `../apps`.
+
diff --git a/hw/application_fpga/fw/testfw/Makefile b/hw/application_fpga/fw/testfw/Makefile
index 82b9262..316aba9 100644
--- a/hw/application_fpga/fw/testfw/Makefile
+++ b/hw/application_fpga/fw/testfw/Makefile
@@ -1,5 +1,5 @@
# Uses ../.clang-format
-FMTFILES=main.c
+FMTFILES=*.[ch]
.PHONY: fmt
fmt:
clang-format --dry-run --ferror-limit=0 $(FMTFILES)
diff --git a/hw/application_fpga/fw/testfw/main.c b/hw/application_fpga/fw/testfw/main.c
index ebd9a5b..656a266 100644
--- a/hw/application_fpga/fw/testfw/main.c
+++ b/hw/application_fpga/fw/testfw/main.c
@@ -1,13 +1,16 @@
-/*
- * Copyright (C) 2022, 2023 - Tillitis AB
- * SPDX-License-Identifier: GPL-2.0-only
- */
+// Copyright (C) 2022, 2023 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include
+#include
+#include
+#include
+#include
+#include
-#include "../tk1/blake2s/blake2s.h"
-#include "../tk1/lib.h"
#include "../tk1/proto.h"
-#include "../tk1/types.h"
-#include "../tk1_mem.h"
+
+#define USBMODE_PACKET_SIZE 64
// clang-format off
volatile uint32_t *tk1name0 = (volatile uint32_t *)TK1_MMIO_TK1_NAME0;
@@ -15,80 +18,27 @@ volatile uint32_t *tk1name1 = (volatile uint32_t *)TK1_MMIO_TK1_NAME1;
volatile uint32_t *uds = (volatile uint32_t *)TK1_MMIO_UDS_FIRST;
volatile uint32_t *cdi = (volatile uint32_t *)TK1_MMIO_TK1_CDI_FIRST;
volatile uint32_t *udi = (volatile uint32_t *)TK1_MMIO_TK1_UDI_FIRST;
-volatile uint32_t *system_mode_ctrl = (volatile uint32_t *)TK1_MMIO_TK1_SYSTEM_MODE_CTRL;
volatile uint8_t *fw_ram = (volatile uint8_t *)TK1_MMIO_FW_RAM_BASE;
+volatile uint32_t *system_reset = (volatile uint32_t *)TK1_MMIO_TK1_SYSTEM_RESET;
volatile uint32_t *timer = (volatile uint32_t *)TK1_MMIO_TIMER_TIMER;
volatile uint32_t *timer_prescaler = (volatile uint32_t *)TK1_MMIO_TIMER_PRESCALER;
volatile uint32_t *timer_status = (volatile uint32_t *)TK1_MMIO_TIMER_STATUS;
volatile uint32_t *timer_ctrl = (volatile uint32_t *)TK1_MMIO_TIMER_CTRL;
volatile uint32_t *trng_status = (volatile uint32_t *)TK1_MMIO_TRNG_STATUS;
volatile uint32_t *trng_entropy = (volatile uint32_t *)TK1_MMIO_TRNG_ENTROPY;
-volatile uint32_t *fw_blake2s_addr = (volatile uint32_t *)TK1_MMIO_TK1_BLAKE2S;
// clang-format on
#define UDS_WORDS 8
#define UDI_WORDS 2
#define CDI_WORDS 8
-void *memcpy(void *dest, const void *src, size_t n)
-{
- uint8_t *src_byte = (uint8_t *)src;
- uint8_t *dest_byte = (uint8_t *)dest;
-
- for (int i = 0; i < n; i++) {
- dest_byte[i] = src_byte[i];
- }
-
- return dest;
-}
-
-void puts(char *reason)
-{
- for (char *c = reason; *c != '\0'; c++) {
- writebyte(*c);
- }
-}
-
-void putsn(char *p, int n)
-{
- for (int i = 0; i < n; i++) {
- writebyte(p[i]);
- }
-}
-
-void puthex(uint8_t c)
-{
- unsigned int upper = (c >> 4) & 0xf;
- unsigned int lower = c & 0xf;
- writebyte(upper < 10 ? '0' + upper : 'a' - 10 + upper);
- writebyte(lower < 10 ? '0' + lower : 'a' - 10 + lower);
-}
-
void puthexn(uint8_t *p, int n)
{
for (int i = 0; i < n; i++) {
- puthex(p[i]);
+ puthex(IO_CDC, p[i]);
}
}
-void hexdump(void *buf, int len)
-{
- uint8_t *byte_buf = (uint8_t *)buf;
-
- for (int i = 0; i < len; i++) {
- puthex(byte_buf[i]);
- if (i % 2 == 1) {
- writebyte(' ');
- }
-
- if (i != 1 && i % 16 == 1) {
- puts("\r\n");
- }
- }
-
- puts("\r\n");
-}
-
void reverseword(uint32_t *wordp)
{
*wordp = ((*wordp & 0xff000000) >> 24) | ((*wordp & 0x00ff0000) >> 8) |
@@ -124,19 +74,19 @@ int check_fwram_zero_except(unsigned int offset, uint8_t expected_val)
if (i == offset) {
if (val != expected_val) {
failed_now = 1;
- puts(" wrong value at: ");
+ puts(IO_CDC, " wrong value at: ");
}
} else {
if (val != 0) {
failed_now = 1;
- puts(" not zero at: ");
+ puts(IO_CDC, " not zero at: ");
}
}
if (failed_now) {
failed = 1;
reverseword(&addr);
puthexn((uint8_t *)&addr, 4);
- puts("\r\n");
+ puts(IO_CDC, "\r\n");
}
}
return failed;
@@ -144,19 +94,17 @@ int check_fwram_zero_except(unsigned int offset, uint8_t expected_val)
void failmsg(char *s)
{
- puts("FAIL: ");
- puts(s);
- puts("\r\n");
+ puts(IO_CDC, "FAIL: ");
+ puts(IO_CDC, s);
+ puts(IO_CDC, "\r\n");
}
int main(void)
{
- // Function pointer to blake2s()
- volatile int (*fw_blake2s)(void *, unsigned long, const void *,
- unsigned long, const void *, unsigned long,
- blake2s_ctx *);
+ uint8_t in = 0;
+ uint8_t available = 0;
+ enum ioend endpoint = IO_NONE;
- uint8_t in;
// Hard coded test UDS in ../../data/uds.hex
// clang-format off
uint32_t uds_test[8] = {
@@ -172,19 +120,27 @@ int main(void)
// clang-format on
// Wait for terminal program and a character to be typed
- in = readbyte();
+ if (readselect(IO_CDC, &endpoint, &available) < 0) {
+ // readselect failed! I/O broken? Just redblink.
+ assert(1 == 2);
+ }
- puts("\r\nI'm testfw on:");
+ if (read(IO_CDC, &in, 1, 1) < 0) {
+ // read failed! I/O broken? Just redblink.
+ assert(1 == 2);
+ }
+
+ puts(IO_CDC, "\r\nI'm testfw on:");
// Output the TK1 core's NAME0 and NAME1
uint32_t name;
wordcpy_s(&name, 1, (void *)tk1name0, 1);
reverseword(&name);
- putsn((char *)&name, 4);
- puts(" ");
+ write(IO_CDC, (const uint8_t *)&name, 4);
+ puts(IO_CDC, " ");
wordcpy_s(&name, 1, (void *)tk1name1, 1);
reverseword(&name);
- putsn((char *)&name, 4);
- puts("\r\n");
+ write(IO_CDC, (const uint8_t *)&name, 4);
+ puts(IO_CDC, "\r\n");
uint32_t zeros[8];
memset(zeros, 0, 8 * 4);
@@ -200,11 +156,11 @@ int main(void)
anyfailed = 1;
}
- puts("\r\nUDS: ");
+ puts(IO_CDC, "\r\nUDS: ");
for (int i = 0; i < UDS_WORDS * 4; i++) {
- puthex(((uint8_t *)uds_local)[i]);
+ puthex(IO_CDC, ((uint8_t *)uds_local)[i]);
}
- puts("\r\n");
+ puts(IO_CDC, "\r\n");
if (!memeq(uds_local, uds_test, UDS_WORDS * 4)) {
failmsg("UDS not equal to test UDS");
anyfailed = 1;
@@ -247,7 +203,7 @@ int main(void)
}
// Test FW_RAM.
- puts("\r\nTesting FW_RAM (takes 15s on hw)...\r\n");
+ puts(IO_CDC, "\r\nTesting FW_RAM (takes 50s on hw)...\r\n");
for (unsigned int i = 0; i < TK1_MMIO_FW_RAM_SIZE; i++) {
zero_fwram();
*(volatile uint8_t *)(TK1_MMIO_FW_RAM_BASE + i) = 0x42;
@@ -257,60 +213,7 @@ int main(void)
}
}
- uint32_t sw = *system_mode_ctrl;
- if (sw != 0) {
- failmsg("system_mode_ctrl is not 0 in fw mode");
- anyfailed = 1;
- }
-
- // Store function pointer to blake2s() so it's reachable from app
- *fw_blake2s_addr = (uint32_t)blake2s;
-
- // Turn on application mode.
- // -------------------------
-
- *system_mode_ctrl = 1;
-
- sw = *system_mode_ctrl;
- if (sw != 0xffffffff) {
- failmsg("system_mode_ctrl is not 0xffffffff in app mode");
- anyfailed = 1;
- }
-
- // Should NOT be able to read from UDS in app-mode.
- wordcpy_s(uds_local, UDS_WORDS, (void *)uds, UDS_WORDS);
- if (!memeq(uds_local, zeros, UDS_WORDS * 4)) {
- failmsg("Read from UDS in app-mode");
- anyfailed = 1;
- }
-
- // Should NOT be able to read from UDI in app-mode.
- wordcpy_s(udi_local, UDI_WORDS, (void *)udi, UDI_WORDS);
- if (!memeq(udi_local, zeros, UDI_WORDS * 4)) {
- failmsg("Read from UDI in app-mode");
- anyfailed = 1;
- }
-
- uint32_t cdi_local[CDI_WORDS];
- uint32_t cdi_local2[CDI_WORDS];
- wordcpy_s(cdi_local, CDI_WORDS, (void *)cdi, CDI_WORDS);
-
- // Write to CDI should NOT have any effect in app mode.
- wordcpy_s((void *)cdi, CDI_WORDS, zeros, CDI_WORDS);
- wordcpy_s(cdi_local2, CDI_WORDS, (void *)cdi, CDI_WORDS);
- if (!memeq(cdi_local, cdi_local2, CDI_WORDS * 4)) {
- failmsg("Write to CDI in app-mode");
- anyfailed = 1;
- }
-
- // Test FW_RAM.
- *fw_ram = 0x21;
- if (*fw_ram == 0x21) {
- failmsg("Write and read FW RAM in app-mode");
- anyfailed = 1;
- }
-
- puts("\r\nTesting timer... 3");
+ puts(IO_CDC, "\r\nTesting timer... 3");
// Matching clock at 24 MHz, giving us timer in seconds
*timer_prescaler = 24 * 1000000;
@@ -321,7 +224,7 @@ int main(void)
while (*timer_status & (1 << TK1_MMIO_TIMER_STATUS_RUNNING_BIT)) {
}
// Now timer has expired and is ready to run again
- puts(" 2");
+ puts(IO_CDC, " 2");
// Test to interrupt a timer - and reads from timer register
// Starting 10s timer and interrupting it in 3s...
@@ -334,7 +237,7 @@ int main(void)
// Stop the timer
*timer_ctrl = (1 << TK1_MMIO_TIMER_CTRL_STOP_BIT);
- puts(" 1. done.\r\n");
+ puts(IO_CDC, " 1. done.\r\n");
if (*timer_status & (1 << TK1_MMIO_TIMER_STATUS_RUNNING_BIT)) {
failmsg("Timer didn't stop");
@@ -346,41 +249,16 @@ int main(void)
anyfailed = 1;
}
- // Testing the blake2s MMIO in app mode
-
- fw_blake2s = (volatile int (*)(void *, unsigned long, const void *,
- unsigned long, const void *,
- unsigned long, blake2s_ctx *)) *
- fw_blake2s_addr;
-
- char msg[17] = "dldlkjsdkljdslsdj";
- uint32_t digest0[8];
- uint32_t digest1[8];
- blake2s_ctx b2s_ctx;
-
- blake2s(&digest0[0], 32, NULL, 0, &msg, 17, &b2s_ctx);
- fw_blake2s(&digest1[0], 32, NULL, 0, &msg, 17, &b2s_ctx);
-
- puts("\r\ndigest #0: \r\n");
- hexdump((uint8_t *)digest0, 32);
-
- puts("digest #1: \r\n");
- hexdump((uint8_t *)digest1, 32);
-
- if (!memeq(digest0, digest1, 32)) {
- failmsg("Digests not the same");
- anyfailed = 1;
- }
-
// Check and display test results.
- puts("\r\n--> ");
+ puts(IO_CDC, "\r\n--> ");
if (anyfailed) {
- puts("Some test FAILED!\r\n");
+ puts(IO_CDC, "Some test FAILED!\r\n");
} else {
- puts("All tests passed.\r\n");
+ puts(IO_CDC, "All tests passed.\r\n");
}
- puts("\r\nHere are 256 bytes from the TRNG:\r\n");
+ puts(IO_CDC, "\r\nHere are 256 bytes from the TRNG:\r\n");
+
for (int j = 0; j < 8; j++) {
for (int i = 0; i < 8; i++) {
while ((*trng_status &
@@ -388,15 +266,28 @@ int main(void)
}
uint32_t rnd = *trng_entropy;
puthexn((uint8_t *)&rnd, 4);
- puts(" ");
+ puts(IO_CDC, " ");
}
- puts("\r\n");
+ puts(IO_CDC, "\r\n");
}
- puts("\r\n");
+ puts(IO_CDC, "\r\n");
- puts("Now echoing what you type...\r\n");
+ puts(IO_CDC, "Now echoing what you type...Type + to reset device\r\n");
for (;;) {
- in = readbyte(); // blocks
- writebyte(in);
+ if (readselect(IO_CDC, &endpoint, &available) < 0) {
+ // readselect failed! I/O broken? Just redblink.
+ assert(1 == 2);
+ }
+
+ if (read(IO_CDC, &in, 1, 1) < 0) {
+ // read failed! I/O broken? Just redblink.
+ assert(1 == 2);
+ }
+
+ if (in == '+') {
+ *system_reset = 1;
+ }
+
+ write(IO_CDC, &in, 1);
}
}
diff --git a/hw/application_fpga/fw/testfw/start.S b/hw/application_fpga/fw/testfw/start.S
index 6ed1408..26d6186 100644
--- a/hw/application_fpga/fw/testfw/start.S
+++ b/hw/application_fpga/fw/testfw/start.S
@@ -1,7 +1,5 @@
-/*
- * Copyright (C) 2022, 2023 - Tillitis AB
- * SPDX-License-Identifier: GPL-2.0-only
-*/
+// Copyright (C) 2022, 2023 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
.section ".text.init"
.globl _start
@@ -38,7 +36,7 @@ _start:
li x30,0
li x31,0
- /* Clear all RAM */
+ // Clear all RAM
li a0, 0x40000000 // TK1_RAM_BASE
li a1, 0x40020000 // TK1_RAM_BASE + TK1_RAM_SIZE
clear:
@@ -46,9 +44,9 @@ clear:
addi a0, a0, 4
blt a0, a1, clear
- /*
- * For testfw we init stack at top of RAM
- */
+ // NOTE WELL
+ // For testfw we init stack at top of RAM
+ //
li sp, 0x40020000 // TK1_RAM_BASE + TK1_RAM_SIZE
call main
diff --git a/hw/application_fpga/fw/tk1/Makefile b/hw/application_fpga/fw/tk1/Makefile
index 68b8261..34b6da8 100644
--- a/hw/application_fpga/fw/tk1/Makefile
+++ b/hw/application_fpga/fw/tk1/Makefile
@@ -1,5 +1,6 @@
# Uses ../.clang-format
-FMTFILES=main.c lib.h lib.c proto.h proto.c types.h assert.c assert.h led.c led.h
+FMTFILES=*.[ch]
+
.PHONY: fmt
fmt:
clang-format --dry-run --ferror-limit=0 $(FMTFILES)
diff --git a/hw/application_fpga/fw/tk1/assert.c b/hw/application_fpga/fw/tk1/assert.c
deleted file mode 100644
index 3da2d59..0000000
--- a/hw/application_fpga/fw/tk1/assert.c
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2022, 2023 - Tillitis AB
- * SPDX-License-Identifier: GPL-2.0-only
- */
-
-#include "assert.h"
-#include "lib.h"
-
-void assert_fail(const char *assertion, const char *file, unsigned int line,
- const char *function)
-{
- htif_puts("assert: ");
- htif_puts(assertion);
- htif_puts(" ");
- htif_puts(file);
- htif_puts(":");
- htif_putinthex(line);
- htif_puts(" ");
- htif_puts(function);
- htif_lf();
-
-#ifndef S_SPLINT_S
- // Force illegal instruction to halt CPU
- asm volatile("unimp");
-#endif
-
- // Not reached
- __builtin_unreachable();
-}
diff --git a/hw/application_fpga/fw/tk1/assert.h b/hw/application_fpga/fw/tk1/assert.h
deleted file mode 100644
index 552dbfa..0000000
--- a/hw/application_fpga/fw/tk1/assert.h
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright (C) 2022, 2023 - Tillitis AB
- * SPDX-License-Identifier: GPL-2.0-only
- */
-
-#ifndef ASSERT_H
-#define ASSERT_H
-
-#define assert(expr) \
- ((expr) ? (void)(0) : assert_fail(#expr, __FILE__, __LINE__, __func__))
-
-void assert_fail(const char *assertion, const char *file, unsigned int line,
- const char *function);
-
-#endif
diff --git a/hw/application_fpga/fw/tk1/auth_app.c b/hw/application_fpga/fw/tk1/auth_app.c
new file mode 100644
index 0000000..d9175e1
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/auth_app.c
@@ -0,0 +1,77 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include
+#include
+#include
+#include
+#include
+
+#include "auth_app.h"
+#include "blake2s/blake2s.h"
+#include "partition_table.h"
+#include "rng.h"
+
+static volatile uint32_t *cdi = (volatile uint32_t *)TK1_MMIO_TK1_CDI_FIRST;
+
+// Calculates the authentication digest based on a supplied nonce and
+// the CDI. Requires that the CDI is already calculated and stored
+static void calculate_auth_digest(uint8_t *nonce, uint8_t *auth_digest)
+{
+ assert(nonce != NULL);
+ assert(auth_digest != NULL);
+
+ blake2s_ctx ctx = {0};
+
+ // Generate a 16 byte authentication digest
+ int blake2err = blake2s_init(&ctx, 16, NULL, 0);
+ assert(blake2err == 0);
+ blake2s_update(&ctx, (const void *)cdi, 32);
+ blake2s_update(&ctx, nonce, 16);
+ blake2s_final(&ctx, auth_digest);
+}
+
+// Generates a 16 byte nonce
+static void generate_nonce(uint32_t *nonce)
+{
+ assert(nonce != NULL);
+
+ for (uint8_t i = 0; i < 4; i++) {
+ nonce[i] = rng_get_word();
+ }
+ return;
+}
+
+// Returns the authentication digest and random nonce. Requires that
+// the CDI is already calculated and stored
+void auth_app_create(struct auth_metadata *auth_table)
+{
+ assert(auth_table != NULL);
+
+ uint8_t nonce[16] = {0};
+ uint8_t auth_digest[16] = {0};
+
+ generate_nonce((uint32_t *)nonce);
+
+ calculate_auth_digest(nonce, auth_digest);
+
+ memcpy_s(auth_table->authentication_digest, 16, auth_digest, 16);
+ memcpy_s(auth_table->nonce, 16, nonce, 16);
+
+ return;
+}
+
+bool auth_app_authenticate(struct auth_metadata *auth_table)
+{
+ assert(auth_table != NULL);
+
+ uint8_t auth_digest[16] = {0};
+
+ calculate_auth_digest(auth_table->nonce, auth_digest);
+
+ if (memeq(auth_digest, auth_table->authentication_digest, 16)) {
+ return true;
+ }
+
+ return false;
+}
diff --git a/hw/application_fpga/fw/tk1/auth_app.h b/hw/application_fpga/fw/tk1/auth_app.h
new file mode 100644
index 0000000..fcc411a
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/auth_app.h
@@ -0,0 +1,14 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifndef AUTH_APP_H
+#define AUTH_APP_H
+
+#include "partition_table.h"
+
+#include
+
+void auth_app_create(struct auth_metadata *auth_table);
+bool auth_app_authenticate(struct auth_metadata *auth_table);
+
+#endif
diff --git a/hw/application_fpga/fw/tk1/blake2s/README.md b/hw/application_fpga/fw/tk1/blake2s/README.md
deleted file mode 100644
index 9d255ff..0000000
--- a/hw/application_fpga/fw/tk1/blake2s/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# blake2s
-
-A Blake2s implementation taken from Joachim Strömbergson's
-
-https://github.com/secworks/blake2s
-
-Specifically from
-
-https://github.com/secworks/blake2s/tree/master/src/model
-
-Minor local changes for build purposes.
diff --git a/hw/application_fpga/fw/tk1/firmware.lds b/hw/application_fpga/fw/tk1/firmware.lds
index 5a423ff..ef1299a 100644
--- a/hw/application_fpga/fw/tk1/firmware.lds
+++ b/hw/application_fpga/fw/tk1/firmware.lds
@@ -3,13 +3,18 @@
* SPDX-License-Identifier: GPL-2.0-only
*/
-OUTPUT_ARCH( "riscv" )
+OUTPUT_ARCH("riscv")
ENTRY(_start)
+/* Define stack size */
+STACK_SIZE = 3000;
+
MEMORY
{
- ROM (rx) : ORIGIN = 0x00000000, LENGTH = 0x20000 /* 128 KB */
- RAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x20000 /* 128 KB */
+ ROM (rx) : ORIGIN = 0x00000000, LENGTH = 0x2000 /* 8 KB */
+ FWRAM (rw) : ORIGIN = 0xd0000000, LENGTH = 0xF00 /* 3840 B */
+ RESETINFO (rw) : ORIGIN = 0xd0000F00, LENGTH = 0x100 /* 256 B (part of FW_RAM area) */
+ RAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x20000 /* 128 KB */
}
SECTIONS
@@ -28,31 +33,38 @@ SECTIONS
.text :
{
. = ALIGN(4);
+ _stext = .;
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
- *(.srodata) /* .rodata sections (constants, strings, etc.) */
- *(.srodata*) /* .rodata* sections (constants, strings, etc.) */
+ *(.srodata) /* .srodata sections (constants, strings, etc.) */
+ *(.srodata*) /* .srodata* sections (constants, strings, etc.) */
. = ALIGN(4);
_etext = .;
- _sidata = _etext;
} >ROM
- /* XXX We don't allow any data or BSS - but they need be defined or linking will fail */
+ .stack (NOLOAD) :
+ {
+ . = ALIGN(16);
+ _sstack = .;
+ . += STACK_SIZE;
+ . = ALIGN(16);
+ _estack = .;
+ } >FWRAM
- .data : AT (_etext)
+ .data :
{
. = ALIGN(4);
_sdata = .;
- . = ALIGN(4);
*(.data) /* .data sections */
*(.data*) /* .data* sections */
- *(.sdata) /* .sdata sections */
- *(.sdata*) /* .sdata* sections */
+ *(.sdata) /* .sdata sections */
+ *(.sdata*) /* .sdata* sections */
. = ALIGN(4);
_edata = .;
- } >ROM
+ } >FWRAM AT>ROM
+ _sidata = LOADADDR(.data);
/* Uninitialized data section */
.bss :
@@ -64,8 +76,12 @@ SECTIONS
*(.sbss)
*(.sbss*)
*(COMMON)
-
. = ALIGN(4);
_ebss = .;
- } >ROM
+ } >FWRAM
}
+
+_sfwram = ORIGIN(FWRAM);
+_efwram = ORIGIN(FWRAM) + LENGTH(FWRAM);
+_sresetinfo = ORIGIN(RESETINFO);
+_eresetinfo = ORIGIN(RESETINFO) + LENGTH(RESETINFO);
diff --git a/hw/application_fpga/fw/tk1/flash.c b/hw/application_fpga/fw/tk1/flash.c
new file mode 100644
index 0000000..5c0d374
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/flash.c
@@ -0,0 +1,229 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include
+#include
+#include
+#include
+#include
+
+#include "flash.h"
+#include "spi.h"
+
+#define PAGE_SIZE 256
+
+static bool flash_is_busy(void);
+static void flash_wait_busy(void);
+static void flash_write_enable(void);
+
+static bool flash_is_busy(void)
+{
+ uint8_t tx_buf = READ_STATUS_REG_1;
+ uint8_t rx_buf = {0x00};
+
+ assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, &rx_buf,
+ sizeof(rx_buf)) == 0);
+
+ if (rx_buf & (1 << STATUS_REG_BUSY_BIT)) {
+ return true;
+ }
+
+ return false;
+}
+
+// Blocking until !busy
+static void flash_wait_busy(void)
+{
+ while (flash_is_busy())
+ ;
+}
+
+static void flash_write_enable(void)
+{
+ uint8_t tx_buf = WRITE_ENABLE;
+
+ assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
+}
+
+void flash_write_disable(void)
+{
+ uint8_t tx_buf = WRITE_DISABLE;
+
+ assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
+}
+
+void flash_sector_erase(uint32_t address)
+{
+ uint8_t tx_buf[4] = {0x00};
+ tx_buf[0] = SECTOR_ERASE;
+ tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF;
+ tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF;
+ /* tx_buf[3] is within a sector, and hence does not make a difference */
+
+ flash_write_enable();
+ assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
+ flash_wait_busy();
+}
+
+void flash_block_32_erase(uint32_t address)
+{
+ uint8_t tx_buf[4] = {0x00};
+ tx_buf[0] = BLOCK_ERASE_32K;
+ tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF;
+ tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF;
+ tx_buf[3] = (address >> ADDR_BYTE_1_BIT) & 0xFF;
+
+ flash_write_enable();
+ assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
+ flash_wait_busy();
+}
+
+// 64 KiB block erase, only cares about address bits 16 and above.
+void flash_block_64_erase(uint32_t address)
+{
+ uint8_t tx_buf[4] = {0x00};
+ tx_buf[0] = BLOCK_ERASE_64K;
+ tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF;
+ /* tx_buf[2] and tx_buf[3] is within a block, and hence does not make a
+ * difference */
+
+ flash_write_enable();
+ assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
+ flash_wait_busy();
+}
+
+void flash_release_powerdown(void)
+{
+ uint8_t tx_buf[4] = {0x00};
+ tx_buf[0] = RELEASE_POWER_DOWN;
+
+ assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
+}
+
+void flash_powerdown(void)
+{
+ uint8_t tx_buf = POWER_DOWN;
+
+ assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
+}
+
+void flash_read_manufacturer_device_id(uint8_t *device_id)
+{
+ assert(device_id != NULL);
+
+ uint8_t tx_buf[4] = {0x00};
+ tx_buf[0] = READ_MANUFACTURER_ID;
+
+ assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, device_id, 2) ==
+ 0);
+}
+
+void flash_read_jedec_id(uint8_t *jedec_id)
+{
+ assert(jedec_id != NULL);
+
+ uint8_t tx_buf = READ_JEDEC_ID;
+
+ assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, jedec_id, 3) ==
+ 0);
+}
+
+void flash_read_unique_id(uint8_t *unique_id)
+{
+ assert(unique_id != NULL);
+
+ uint8_t tx_buf[5] = {0x00};
+ tx_buf[0] = READ_UNIQUE_ID;
+
+ assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, unique_id, 8) ==
+ 0);
+}
+
+void flash_read_status(uint8_t *status_reg)
+{
+ assert(status_reg != NULL);
+
+ uint8_t tx_buf = READ_STATUS_REG_1;
+
+ assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg, 1) ==
+ 0);
+
+ tx_buf = READ_STATUS_REG_2;
+ assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg + 1,
+ 1) == 0);
+}
+
+int flash_read_data(uint32_t address, uint8_t *dest_buf, size_t size)
+{
+ if (dest_buf == NULL) {
+ return -1;
+ }
+
+ uint8_t tx_buf[4] = {0x00};
+ tx_buf[0] = READ_DATA;
+ tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF;
+ tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF;
+ tx_buf[3] = (address >> ADDR_BYTE_1_BIT) & 0xFF;
+
+ return spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, dest_buf, size);
+}
+
+// Only handles writes where the least significant byte of the start address is
+// zero.
+int flash_write_data(uint32_t address, uint8_t *data, size_t size)
+{
+ if (data == NULL) {
+ return -1;
+ }
+
+ if (size <= 0) {
+ return -1;
+ }
+
+ if (address % 256 != 0) {
+ return -1;
+ }
+
+ size_t left = size;
+ uint8_t *p_data = data;
+ size_t n_bytes = 0;
+
+ // Page Program allows 1-256 bytes of a page to be written. A page is
+ // 256 bytes. Behavior when writing past the end of a page is device
+ // specific.
+ //
+ // We set the address LSByte to 0 and only write 256 bytes or less in
+ // each transfer.
+ uint8_t tx_buf[4] = {
+ PAGE_PROGRAM, /* tx_buf[0] */
+ (address >> ADDR_BYTE_3_BIT) & 0xFF, /* tx_buf[1] */
+ (address >> ADDR_BYTE_2_BIT) & 0xFF, /* tx_buf[2] */
+ 0x00, /* tx_buf[3] */
+ };
+
+ while (left > 0) {
+ if (left >= PAGE_SIZE) {
+ n_bytes = PAGE_SIZE;
+ } else {
+ n_bytes = left;
+ }
+
+ flash_write_enable();
+
+ if (spi_transfer(tx_buf, sizeof(tx_buf), p_data, n_bytes, NULL,
+ 0) != 0) {
+ return -1;
+ }
+
+ left -= n_bytes;
+ p_data += n_bytes;
+
+ address += n_bytes;
+ tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF;
+ tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF;
+
+ flash_wait_busy();
+ }
+
+ return 0;
+}
diff --git a/hw/application_fpga/fw/tk1/flash.h b/hw/application_fpga/fw/tk1/flash.h
new file mode 100644
index 0000000..1151d4e
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/flash.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifndef TKEY_FLASH_H
+#define TKEY_FLASH_H
+
+#include
+#include
+#include
+
+#define WRITE_ENABLE 0x06
+#define WRITE_DISABLE 0x04
+
+#define READ_STATUS_REG_1 0x05
+#define READ_STATUS_REG_2 0x35
+#define WRITE_STATUS_REG 0x01
+
+#define PAGE_PROGRAM 0x02
+#define SECTOR_ERASE 0x20
+#define BLOCK_ERASE_32K 0x52
+#define BLOCK_ERASE_64K 0xD8
+#define CHIP_ERASE 0xC7
+
+#define POWER_DOWN 0xB9
+#define READ_DATA 0x03
+#define RELEASE_POWER_DOWN 0xAB
+
+#define READ_MANUFACTURER_ID 0x90
+#define READ_JEDEC_ID 0x9F
+#define READ_UNIQUE_ID 0x4B
+
+#define ENABLE_RESET 0x66
+#define RESET 0x99
+
+#define ADDR_BYTE_3_BIT 16
+#define ADDR_BYTE_2_BIT 8
+#define ADDR_BYTE_1_BIT 0
+
+#define STATUS_REG_BUSY_BIT 0
+#define STATUS_REG_WEL_BIT 1
+
+void flash_write_disable(void);
+void flash_sector_erase(uint32_t address);
+void flash_block_32_erase(uint32_t address);
+void flash_block_64_erase(uint32_t address);
+void flash_release_powerdown(void);
+void flash_powerdown(void);
+void flash_read_manufacturer_device_id(uint8_t *device_id);
+void flash_read_jedec_id(uint8_t *jedec_id);
+void flash_read_unique_id(uint8_t *unique_id);
+void flash_read_status(uint8_t *status_reg);
+int flash_read_data(uint32_t address, uint8_t *dest_buf, size_t size);
+int flash_write_data(uint32_t address, uint8_t *data, size_t size);
+
+#endif
diff --git a/hw/application_fpga/fw/tk1/led.c b/hw/application_fpga/fw/tk1/led.c
deleted file mode 100644
index 023bafa..0000000
--- a/hw/application_fpga/fw/tk1/led.c
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * Copyright (C) 2022, 2023 - Tillitis AB
- * SPDX-License-Identifier: GPL-2.0-only
- */
-
-#include "led.h"
-#include "../tk1_mem.h"
-#include "types.h"
-
-static volatile uint32_t *led = (volatile uint32_t *)TK1_MMIO_TK1_LED;
-
-void set_led(uint32_t led_value)
-{
- *led = led_value;
-}
diff --git a/hw/application_fpga/fw/tk1/led.h b/hw/application_fpga/fw/tk1/led.h
deleted file mode 100644
index d2c162e..0000000
--- a/hw/application_fpga/fw/tk1/led.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2022, 2023 - Tillitis AB
- * SPDX-License-Identifier: GPL-2.0-only
- */
-
-#ifndef LED_H
-#define LED_H
-
-#include "../tk1_mem.h"
-#include "types.h"
-
-// clang-format off
-#define LED_BLACK 0
-#define LED_RED (1 << TK1_MMIO_TK1_LED_R_BIT)
-#define LED_GREEN (1 << TK1_MMIO_TK1_LED_G_BIT)
-#define LED_BLUE (1 << TK1_MMIO_TK1_LED_B_BIT)
-#define LED_WHITE (LED_RED | LED_GREEN | LED_BLUE)
-// clang-format on
-
-void set_led(uint32_t led_value);
-#endif
diff --git a/hw/application_fpga/fw/tk1/lib.c b/hw/application_fpga/fw/tk1/lib.c
deleted file mode 100644
index 5399dbb..0000000
--- a/hw/application_fpga/fw/tk1/lib.c
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2022-2024 - Tillitis AB
- * SPDX-License-Identifier: GPL-2.0-only
- */
-
-#include "lib.h"
-#include "assert.h"
-#include "types.h"
-
-#ifdef QEMU_CONSOLE
-struct {
- uint32_t arr[2];
-} static volatile tohost __attribute__((section(".htif")));
-struct {
- uint32_t arr[2];
-} /*@unused@*/ static volatile fromhost __attribute__((section(".htif")));
-
-static void htif_send(uint8_t dev, uint8_t cmd, int64_t data)
-{
- /* endian neutral encoding with ordered 32-bit writes */
- union {
- uint32_t arr[2];
- uint64_t val;
- } encode = {.val = (uint64_t)dev << 56 | (uint64_t)cmd << 48 | data};
- tohost.arr[0] = encode.arr[0];
- tohost.arr[1] = encode.arr[1];
-}
-
-static void htif_set_tohost(uint8_t dev, uint8_t cmd, int64_t data)
-{
- /* send data with specified device and command */
- while (tohost.arr[0]) {
-#ifndef S_SPLINT_S
- asm volatile("" : : "r"(fromhost.arr[0]));
- asm volatile("" : : "r"(fromhost.arr[1]));
-#endif
- }
- htif_send(dev, cmd, data);
-}
-
-static void htif_putchar(char ch)
-{
- htif_set_tohost((uint8_t)1, (uint8_t)1, (int64_t)ch & 0xff);
-}
-
-void htif_puts(const char *s)
-{
- while (*s != '\0')
- htif_putchar(*s++);
-}
-
-void htif_hexdump(void *buf, int len)
-{
- uint8_t *byte_buf = (uint8_t *)buf;
-
- for (int i = 0; i < len; i++) {
- htif_puthex(byte_buf[i]);
- if (i % 2 == 1) {
- (void)htif_putchar(' ');
- }
-
- if (i != 1 && i % 16 == 1) {
- htif_lf();
- }
- }
-
- htif_lf();
-}
-
-void htif_putc(char ch)
-{
- htif_putchar(ch);
-}
-
-void htif_lf(void)
-{
- htif_putchar('\n');
-}
-
-void htif_puthex(uint8_t c)
-{
- unsigned int upper = (c >> 4) & 0xf;
- unsigned int lower = c & 0xf;
-
- htif_putchar(upper < 10 ? '0' + upper : 'a' - 10 + upper);
- htif_putchar(lower < 10 ? '0' + lower : 'a' - 10 + lower);
-}
-
-void htif_putinthex(const uint32_t n)
-{
- uint8_t *buf = (uint8_t *)&n;
-
- htif_puts("0x");
- for (int i = 3; i > -1; i--) {
- htif_puthex(buf[i]);
- }
-}
-#endif
-
-void *memset(void *dest, int c, unsigned n)
-{
- uint8_t *s = dest;
-
- for (; n; n--, s++)
- *s = (uint8_t)c;
-
- /*@ -temptrans @*/
- return dest;
-}
-
-void memcpy_s(void *dest, size_t destsize, const void *src, size_t n)
-{
- assert(dest != NULL);
- assert(src != NULL);
- assert(destsize >= n);
-
- uint8_t *src_byte = (uint8_t *)src;
- uint8_t *dest_byte = (uint8_t *)dest;
-
- for (size_t i = 0; i < n; i++) {
- /*@ -nullderef @*/
- /* splint complains that dest_byte and src_byte can be
- * NULL, but it seems it doesn't understand assert.
- * See above.
- */
- dest_byte[i] = src_byte[i];
- }
-}
-
-void wordcpy_s(void *dest, size_t destsize, const void *src, size_t n)
-{
- assert(dest != NULL);
- assert(src != NULL);
- assert(destsize >= n);
-
- uint32_t *src_word = (uint32_t *)src;
- uint32_t *dest_word = (uint32_t *)dest;
-
- for (size_t i = 0; i < n; i++) {
- dest_word[i] = src_word[i];
- }
-}
-
-int memeq(void *dest, const void *src, size_t n)
-{
- uint8_t *src_byte = (uint8_t *)src;
- uint8_t *dest_byte = (uint8_t *)dest;
- int res = -1;
-
- for (size_t i = 0; i < n; i++) {
- if (dest_byte[i] != src_byte[i]) {
- res = 0;
- }
- }
-
- return res;
-}
-
-void secure_wipe(void *v, size_t n)
-{
- volatile uint8_t *p = (volatile uint8_t *)v;
- while (n--)
- *p++ = 0;
-}
diff --git a/hw/application_fpga/fw/tk1/lib.h b/hw/application_fpga/fw/tk1/lib.h
deleted file mode 100644
index 2d3d7e3..0000000
--- a/hw/application_fpga/fw/tk1/lib.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2022-2024 - Tillitis AB
- * SPDX-License-Identifier: GPL-2.0-only
- */
-
-#ifndef LIB_H
-#define LIB_H
-
-#include "types.h"
-
-#ifdef QEMU_CONSOLE
-void htif_putc(char ch);
-void htif_lf(void);
-void htif_puthex(uint8_t c);
-void htif_putinthex(const uint32_t n);
-void htif_puts(const char *s);
-void htif_hexdump(void *buf, int len);
-#else
-#define htif_putc(ch)
-#define htif_lf(void)
-#define htif_puthex(c)
-#define htif_putinthex(n)
-#define htif_puts(s)
-#define htif_hexdump(buf, len)
-#endif /* QEMU_CONSOLE */
-
-void *memset(void *dest, int c, unsigned n);
-void memcpy_s(void *dest, size_t destsize, const void *src, size_t n);
-void wordcpy_s(void *dest, size_t destsize, const void *src, size_t n);
-int memeq(void *dest, const void *src, size_t n);
-void secure_wipe(void *v, size_t n);
-#endif
diff --git a/hw/application_fpga/fw/tk1/main.c b/hw/application_fpga/fw/tk1/main.c
index 40e548c..658d3ef 100644
--- a/hw/application_fpga/fw/tk1/main.c
+++ b/hw/application_fpga/fw/tk1/main.c
@@ -1,19 +1,26 @@
-/*
- * Copyright (C) 2022, 2023 - Tillitis AB
- * SPDX-License-Identifier: GPL-2.0-only
- */
+// Copyright (C) 2022, 2023 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
-#include "../tk1_mem.h"
-#include "assert.h"
-#include "blake2s/blake2s.h"
-#include "lib.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "mgmt_app.h"
+#include "partition_table.h"
+#include "preload_app.h"
#include "proto.h"
+#include "reset.h"
#include "state.h"
-#include "types.h"
+#include "syscall_enable.h"
// clang-format off
static volatile uint32_t *uds = (volatile uint32_t *)TK1_MMIO_UDS_FIRST;
-static volatile uint32_t *system_mode_ctrl = (volatile uint32_t *)TK1_MMIO_TK1_SYSTEM_MODE_CTRL;
static volatile uint32_t *name0 = (volatile uint32_t *)TK1_MMIO_TK1_NAME0;
static volatile uint32_t *name1 = (volatile uint32_t *)TK1_MMIO_TK1_NAME1;
static volatile uint32_t *ver = (volatile uint32_t *)TK1_MMIO_TK1_VERSION;
@@ -21,7 +28,6 @@ static volatile uint32_t *udi = (volatile uint32_t *)TK1_MMIO_TK1_U
static volatile uint32_t *cdi = (volatile uint32_t *)TK1_MMIO_TK1_CDI_FIRST;
static volatile uint32_t *app_addr = (volatile uint32_t *)TK1_MMIO_TK1_APP_ADDR;
static volatile uint32_t *app_size = (volatile uint32_t *)TK1_MMIO_TK1_APP_SIZE;
-static volatile uint32_t *fw_blake2s_addr = (volatile uint32_t *)TK1_MMIO_TK1_BLAKE2S;
static volatile uint32_t *trng_status = (volatile uint32_t *)TK1_MMIO_TRNG_STATUS;
static volatile uint32_t *trng_entropy = (volatile uint32_t *)TK1_MMIO_TRNG_ENTROPY;
static volatile uint32_t *timer = (volatile uint32_t *)TK1_MMIO_TIMER_TIMER;
@@ -30,15 +36,21 @@ static volatile uint32_t *timer_status = (volatile uint32_t *)TK1_MMIO_TIMER
static volatile uint32_t *timer_ctrl = (volatile uint32_t *)TK1_MMIO_TIMER_CTRL;
static volatile uint32_t *ram_addr_rand = (volatile uint32_t *)TK1_MMIO_TK1_RAM_ADDR_RAND;
static volatile uint32_t *ram_data_rand = (volatile uint32_t *)TK1_MMIO_TK1_RAM_DATA_RAND;
+static volatile struct reset *resetinfo = (volatile struct reset *)TK1_MMIO_RESETINFO_BASE;
// clang-format on
+struct partition_table_storage part_table_storage;
+
// Context for the loading of a TKey program
struct context {
uint32_t left; // Bytes left to receive
uint8_t digest[32]; // Program digest
uint8_t *loadaddr; // Where we are currently loading a TKey program
- uint8_t use_uss; // Use USS?
+ bool use_uss; // Use USS?
uint8_t uss[32]; // User Supplied Secret, if any
+ uint8_t flash_slot; // App is loaded from flash slot number
+ /*@null@*/ volatile uint8_t
+ *ver_digest; // Verify loaded app against this digest
};
static void print_hw_version(void);
@@ -53,34 +65,38 @@ static enum state initial_commands(const struct frame_header *hdr,
static enum state loading_commands(const struct frame_header *hdr,
const uint8_t *cmd, enum state state,
struct context *ctx);
-static void run(const struct context *ctx);
#if !defined(SIMULATION)
static uint32_t xorwow(uint32_t state, uint32_t acc);
#endif
static void scramble_ram(void);
+static int compute_app_digest(uint8_t *digest);
+static int load_flash_app(struct partition_table *part_table,
+ uint8_t digest[32], uint8_t slot);
+static enum state start_where(struct context *ctx);
static void print_hw_version(void)
{
- htif_puts("Hello, I'm firmware with");
- htif_puts(" tk1_name0:");
- htif_putinthex(*name0);
- htif_puts(" tk1_name1:");
- htif_putinthex(*name1);
- htif_puts(" tk1_version:");
- htif_putinthex(*ver);
- htif_lf();
+ debug_puts("Hello, I'm firmware with");
+ debug_puts(" tk1_name0:");
+ debug_putinthex(*name0);
+ debug_puts(" tk1_name1:");
+ debug_putinthex(*name1);
+ debug_puts(" tk1_version:");
+ debug_putinthex(*ver);
+ debug_lf();
}
static void print_digest(uint8_t *md)
{
- htif_puts("The app digest:\n");
+ debug_puts("The app digest:\n");
for (int j = 0; j < 4; j++) {
for (int i = 0; i < 8; i++) {
- htif_puthex(md[i + 8 * j]);
+ debug_puthex(md[i + 8 * j]);
+ (void)md;
}
- htif_lf();
+ debug_lf();
}
- htif_lf();
+ debug_lf();
}
static uint32_t rnd_word(void)
@@ -141,6 +157,7 @@ static void compute_cdi(const uint8_t *digest, const uint8_t use_uss,
static void copy_name(uint8_t *buf, const size_t bufsiz, const uint32_t word)
{
assert(bufsiz >= 4);
+ assert(buf != NULL);
buf[0] = word >> 24;
buf[1] = word >> 16;
@@ -152,20 +169,20 @@ static enum state initial_commands(const struct frame_header *hdr,
const uint8_t *cmd, enum state state,
struct context *ctx)
{
- uint8_t rsp[CMDLEN_MAXBYTES] = {0};
+ uint8_t rsp[CMDSIZE] = {0};
switch (cmd[0]) {
case FW_CMD_NAME_VERSION:
- htif_puts("cmd: name-version\n");
+ debug_puts("cmd: name-version\n");
if (hdr->len != 1) {
// Bad length
state = FW_STATE_FAIL;
break;
}
- copy_name(rsp, CMDLEN_MAXBYTES, *name0);
- copy_name(&rsp[4], CMDLEN_MAXBYTES - 4, *name1);
- wordcpy_s(&rsp[8], CMDLEN_MAXBYTES / 4 - 2, (void *)ver, 1);
+ copy_name(rsp, CMDSIZE, *name0);
+ copy_name(&rsp[4], CMDSIZE - 4, *name1);
+ wordcpy_s(&rsp[8], CMDSIZE - 8, (void *)ver, 1);
fwreply(*hdr, FW_RSP_NAME_VERSION, rsp);
// still initial state
@@ -174,7 +191,7 @@ static enum state initial_commands(const struct frame_header *hdr,
case FW_CMD_GET_UDI: {
uint32_t udi_words[2];
- htif_puts("cmd: get-udi\n");
+ debug_puts("cmd: get-udi\n");
if (hdr->len != 1) {
// Bad length
state = FW_STATE_FAIL;
@@ -183,7 +200,7 @@ static enum state initial_commands(const struct frame_header *hdr,
rsp[0] = STATUS_OK;
wordcpy_s(&udi_words, 2, (void *)udi, 2);
- memcpy_s(&rsp[1], CMDLEN_MAXBYTES - 1, &udi_words, 2 * 4);
+ memcpy_s(&rsp[1], CMDSIZE - 1, &udi_words, 2 * 4);
fwreply(*hdr, FW_RSP_GET_UDI, rsp);
// still initial state
break;
@@ -192,7 +209,7 @@ static enum state initial_commands(const struct frame_header *hdr,
case FW_CMD_LOAD_APP: {
uint32_t local_app_size;
- htif_puts("cmd: load-app(size, uss)\n");
+ debug_puts("cmd: load-app(size, uss)\n");
if (hdr->len != 128) {
// Bad length
state = FW_STATE_FAIL;
@@ -203,9 +220,9 @@ static enum state initial_commands(const struct frame_header *hdr,
local_app_size =
cmd[1] + (cmd[2] << 8) + (cmd[3] << 16) + (cmd[4] << 24);
- htif_puts("app size: ");
- htif_putinthex(local_app_size);
- htif_lf();
+ debug_puts("app size: ");
+ debug_putinthex(local_app_size);
+ debug_lf();
if (local_app_size == 0 || local_app_size > TK1_APP_MAX_SIZE) {
rsp[0] = STATUS_BAD;
@@ -219,10 +236,10 @@ static enum state initial_commands(const struct frame_header *hdr,
// Do we have a USS at all?
if (cmd[5] != 0) {
// Yes
- ctx->use_uss = TRUE;
+ ctx->use_uss = true;
memcpy_s(ctx->uss, 32, &cmd[6], 32);
} else {
- ctx->use_uss = FALSE;
+ ctx->use_uss = false;
}
rsp[0] = STATUS_OK;
@@ -233,14 +250,16 @@ static enum state initial_commands(const struct frame_header *hdr,
ctx->left = *app_size;
+ led_set(LED_BLACK);
+
state = FW_STATE_LOADING;
break;
}
default:
- htif_puts("Got unknown firmware cmd: 0x");
- htif_puthex(cmd[0]);
- htif_lf();
+ debug_puts("Got unknown firmware cmd: 0x");
+ debug_puthex(cmd[0]);
+ debug_lf();
state = FW_STATE_FAIL;
break;
}
@@ -252,12 +271,12 @@ static enum state loading_commands(const struct frame_header *hdr,
const uint8_t *cmd, enum state state,
struct context *ctx)
{
- uint8_t rsp[CMDLEN_MAXBYTES] = {0};
+ uint8_t rsp[CMDSIZE] = {0};
uint32_t nbytes = 0;
switch (cmd[0]) {
case FW_CMD_LOAD_APP_DATA:
- htif_puts("cmd: load-app-data\n");
+ debug_puts("cmd: load-app-data\n");
if (hdr->len != 128) {
// Bad length
state = FW_STATE_FAIL;
@@ -276,29 +295,25 @@ static enum state loading_commands(const struct frame_header *hdr,
ctx->left -= nbytes;
if (ctx->left == 0) {
- blake2s_ctx b2s_ctx = {0};
int blake2err = 0;
- htif_puts("Fully loaded ");
- htif_putinthex(*app_size);
- htif_lf();
+ debug_puts("Fully loaded ");
+ debug_putinthex(*app_size);
+ debug_lf();
// Compute Blake2S digest of the app,
// storing it for FW_STATE_RUN
- blake2err = blake2s(&ctx->digest, 32, NULL, 0,
- (const void *)TK1_RAM_BASE,
- *app_size, &b2s_ctx);
+ blake2err = compute_app_digest(ctx->digest);
assert(blake2err == 0);
print_digest(ctx->digest);
// And return the digest in final
// response
rsp[0] = STATUS_OK;
- memcpy_s(&rsp[1], CMDLEN_MAXBYTES - 1, &ctx->digest,
- 32);
+ memcpy_s(&rsp[1], CMDSIZE - 1, &ctx->digest, 32);
fwreply(*hdr, FW_RSP_LOAD_APP_DATA_READY, rsp);
- state = FW_STATE_RUN;
+ state = FW_STATE_START;
break;
}
@@ -308,9 +323,9 @@ static enum state loading_commands(const struct frame_header *hdr,
break;
default:
- htif_puts("Got unknown firmware cmd: 0x");
- htif_puthex(cmd[0]);
- htif_lf();
+ debug_puts("Got unknown firmware cmd: 0x");
+ debug_puthex(cmd[0]);
+ debug_lf();
state = FW_STATE_FAIL;
break;
}
@@ -318,24 +333,22 @@ static enum state loading_commands(const struct frame_header *hdr,
return state;
}
-static void run(const struct context *ctx)
+static void jump_to_app(void)
{
+ /* Start of app is always at the beginning of RAM */
*app_addr = TK1_RAM_BASE;
- // CDI = hash(uds, hash(app), uss)
- compute_cdi(ctx->digest, ctx->use_uss, ctx->uss);
-
- htif_puts("Flipping to app mode!\n");
- htif_puts("Jumping to ");
- htif_putinthex(*app_addr);
- htif_lf();
+ debug_puts("Flipping to app mode!\n");
+ debug_puts("Jumping to ");
+ debug_putinthex(*app_addr);
+ debug_lf();
// Clear the firmware stack
// clang-format off
#ifndef S_SPLINT_S
asm volatile(
- "li a0, 0xd0000000;" // FW_RAM
- "li a1, 0xd0000800;" // End of 2 KB FW_RAM (just past the end)
+ "la a0, _sstack;"
+ "la a1, _estack;"
"loop:;"
"sw zero, 0(a0);"
"addi a0, a0, 4;"
@@ -344,13 +357,10 @@ static void run(const struct context *ctx)
#endif
// clang-format on
- // Flip over to application mode
- *system_mode_ctrl = 1;
-
- // XXX Firmware stack now no longer available
- // Don't use any function calls!
+ syscall_enable();
// Jump to app - doesn't return
+ // Hardware is responsible for switching to app mode
// clang-format off
#ifndef S_SPLINT_S
asm volatile(
@@ -366,6 +376,29 @@ static void run(const struct context *ctx)
__builtin_unreachable();
}
+static int load_flash_app(struct partition_table *part_table,
+ uint8_t digest[32], uint8_t slot)
+{
+ if (slot >= N_PRELOADED_APP) {
+ return -1;
+ }
+
+ if (preload_load(part_table, slot) == -1) {
+ return -1;
+ }
+
+ *app_size = part_table->pre_app_data[slot].size;
+ if (*app_size > TK1_APP_MAX_SIZE) {
+ return -1;
+ }
+
+ int digest_err = compute_app_digest(digest);
+ assert(digest_err == 0);
+ print_digest(digest);
+
+ return 0;
+}
+
#if !defined(SIMULATION)
static uint32_t xorwow(uint32_t state, uint32_t acc)
{
@@ -400,31 +433,105 @@ static void scramble_ram(void)
*ram_data_rand = rnd_word();
}
+/* Computes the blake2s digest of the app loaded into RAM */
+static int compute_app_digest(uint8_t *digest)
+{
+ return blake2s(digest, 32, NULL, 0, (const void *)TK1_RAM_BASE,
+ *app_size);
+}
+
+static enum state start_where(struct context *ctx)
+{
+ assert(ctx != NULL);
+
+ debug_puts("resetinfo->type: ");
+ debug_putinthex(resetinfo->type);
+ debug_lf();
+
+ debug_puts(" ->app_digest: \n");
+ debug_hexdump((void *)resetinfo->app_digest, RESET_DIGEST_SIZE);
+ debug_lf();
+
+ debug_puts(" ->next_app_data: \n");
+ debug_hexdump((void *)resetinfo->next_app_data, RESET_DATA_SIZE);
+ debug_lf();
+
+ // Where do we start?
+ switch (resetinfo->type) {
+ case START_DEFAULT:
+ // fallthrough
+ case START_FLASH0:
+ ctx->flash_slot = 0;
+ ctx->ver_digest = mgmt_app_allowed_digest();
+
+ return FW_STATE_LOAD_FLASH_MGMT;
+
+ case START_FLASH1:
+ ctx->flash_slot = 1;
+ ctx->ver_digest = NULL;
+
+ return FW_STATE_LOAD_FLASH;
+
+ case START_FLASH0_VER:
+ ctx->flash_slot = 0;
+ ctx->ver_digest = resetinfo->app_digest;
+
+ return FW_STATE_LOAD_FLASH;
+
+ case START_FLASH1_VER:
+ ctx->flash_slot = 1;
+ ctx->ver_digest = resetinfo->app_digest;
+
+ return FW_STATE_LOAD_FLASH;
+
+ case START_CLIENT:
+ ctx->ver_digest = NULL;
+
+ return FW_STATE_WAITCOMMAND;
+
+ case START_CLIENT_VER:
+ ctx->ver_digest = resetinfo->app_digest;
+
+ return FW_STATE_WAITCOMMAND;
+
+ default:
+ debug_puts("Unknown startfrom\n");
+
+ return FW_STATE_FAIL;
+ }
+}
+
int main(void)
{
struct context ctx = {0};
struct frame_header hdr = {0};
- uint8_t cmd[CMDLEN_MAXBYTES] = {0};
+ uint8_t cmd[CMDSIZE] = {0};
enum state state = FW_STATE_INITIAL;
print_hw_version();
- // Let the app know the function adddress for blake2s()
- *fw_blake2s_addr = (uint32_t)blake2s;
-
/*@-mustfreeonly@*/
/* Yes, splint, this points directly to RAM and we don't care
* about freeing anything was pointing to 0x0 before.
*/
ctx.loadaddr = (uint8_t *)TK1_RAM_BASE;
/*@+mustfreeonly@*/
- ctx.use_uss = FALSE;
-
- uint8_t mode = 0;
- uint8_t mode_bytes_left = 0;
+ ctx.use_uss = false;
scramble_ram();
+ if (part_table_read(&part_table_storage) != 0) {
+ // Couldn't read partition table
+ debug_puts("Couldn't read partition table\n");
+ assert(1 == 2);
+ }
+
+ // Reset the USB controller to only enable the USB CDC
+ // endpoint and the internal command channel.
+ config_endpoints(IO_CDC | IO_CH552);
+
+ led_set(LED_WHITE);
+
#if defined(SIMULATION)
run(&ctx);
#endif
@@ -432,18 +539,23 @@ int main(void)
for (;;) {
switch (state) {
case FW_STATE_INITIAL:
- if (readcommand(&hdr, cmd, state, &mode,
- &mode_bytes_left) == -1) {
+ state = start_where(&ctx);
+ break;
+
+ case FW_STATE_WAITCOMMAND:
+ if (readcommand(&hdr, cmd, state) == -1) {
state = FW_STATE_FAIL;
break;
}
+ debug_puts("cmd: \n");
+ debug_hexdump(cmd, hdr.len);
+
state = initial_commands(&hdr, cmd, state, &ctx);
break;
case FW_STATE_LOADING:
- if (readcommand(&hdr, cmd, state, &mode,
- &mode_bytes_left) == -1) {
+ if (readcommand(&hdr, cmd, state) == -1) {
state = FW_STATE_FAIL;
break;
}
@@ -451,22 +563,66 @@ int main(void)
state = loading_commands(&hdr, cmd, state, &ctx);
break;
- case FW_STATE_RUN:
- run(&ctx);
- break; // This is never reached!
+ case FW_STATE_LOAD_FLASH:
+ if (load_flash_app(&part_table_storage.table,
+ ctx.digest, ctx.flash_slot) < 0) {
+ debug_puts("Couldn't load app from flash\n");
+ state = FW_STATE_FAIL;
+ break;
+ }
+
+ state = FW_STATE_START;
+ break;
+
+ case FW_STATE_LOAD_FLASH_MGMT:
+ if (load_flash_app(&part_table_storage.table,
+ ctx.digest, ctx.flash_slot) < 0) {
+ debug_puts("Couldn't load app from flash\n");
+ state = FW_STATE_FAIL;
+ break;
+ }
+
+ if (mgmt_app_init(ctx.digest) != 0) {
+ state = FW_STATE_FAIL;
+ break;
+ }
+
+ state = FW_STATE_START;
+ break;
+
+ case FW_STATE_START:
+ // CDI = hash(uds, hash(app), uss)
+ compute_cdi(ctx.digest, ctx.use_uss, ctx.uss);
+
+ if (ctx.ver_digest != NULL) {
+ print_digest(ctx.digest);
+ if (!memeq(ctx.digest, (void *)ctx.ver_digest,
+ sizeof(ctx.digest))) {
+ debug_puts("Digests do not match\n");
+ state = FW_STATE_FAIL;
+ break;
+ }
+ }
+
+ (void)memset((void *)resetinfo->app_digest, 0,
+ sizeof(resetinfo->app_digest));
+
+ jump_to_app();
+ break; // Not reached
case FW_STATE_FAIL:
// fallthrough
default:
- htif_puts("firmware state 0x");
- htif_puthex(state);
- htif_lf();
+ debug_puts("firmware state 0x");
+ debug_puthex(state);
+ debug_lf();
assert(1 == 2);
break; // Not reached
}
}
/*@ -compdestroy @*/
- /* We don't care about memory leaks here. */
+ // We don't care about memory leaks here.
+
return (int)0xcafebabe;
}
diff --git a/hw/application_fpga/fw/tk1/mgmt_app.c b/hw/application_fpga/fw/tk1/mgmt_app.c
new file mode 100644
index 0000000..83b8433
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/mgmt_app.c
@@ -0,0 +1,46 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include
+#include
+#include
+#include
+
+#include "mgmt_app.h"
+
+// Lock down what app can start from flash slot 0.
+//
+// To update this, compute the BLAKE2s digest of the app.bin
+// clang-format off
+static const uint8_t allowed_app_digest[32] = {
+ 0x85, 0x29, 0xe3, 0x25, 0xf5, 0x8d, 0x53, 0x5f,
+ 0xe1, 0x2a, 0x77, 0x92, 0xe7, 0xdc, 0x4b, 0x4d,
+ 0x01, 0x85, 0x17, 0xca, 0xfd, 0x54, 0x83, 0xb3,
+ 0xbb, 0x28, 0x4f, 0xa1, 0x98, 0x5f, 0x9e, 0x56,
+};
+// clang-format on
+
+static uint8_t current_app_digest[32];
+
+int mgmt_app_init(uint8_t app_digest[32])
+{
+ if (app_digest == NULL) {
+ return -1;
+ }
+
+ memcpy_s(current_app_digest, sizeof(current_app_digest), app_digest,
+ 32);
+
+ return 0;
+}
+
+// Authenticate an management app
+bool mgmt_app_authenticate(void)
+{
+ return memeq(current_app_digest, allowed_app_digest, 32) != 0;
+}
+
+uint8_t *mgmt_app_allowed_digest(void)
+{
+ return (uint8_t *)allowed_app_digest;
+}
diff --git a/hw/application_fpga/fw/tk1/mgmt_app.h b/hw/application_fpga/fw/tk1/mgmt_app.h
new file mode 100644
index 0000000..1e5a3fb
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/mgmt_app.h
@@ -0,0 +1,14 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifndef MGMT_APP_H
+#define MGMT_APP_H
+
+#include
+#include
+
+int mgmt_app_init(uint8_t app_digest[32]);
+bool mgmt_app_authenticate(void);
+uint8_t *mgmt_app_allowed_digest(void);
+
+#endif
diff --git a/hw/application_fpga/fw/tk1/partition_table.c b/hw/application_fpga/fw/tk1/partition_table.c
new file mode 100644
index 0000000..110719a
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/partition_table.c
@@ -0,0 +1,104 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include
+#include
+#include
+
+#include "blake2s/blake2s.h"
+#include "flash.h"
+#include "partition_table.h"
+#include "proto.h"
+
+static enum part_status part_status;
+
+enum part_status part_get_status(void)
+{
+ return part_status;
+}
+
+static void part_checksum(struct partition_table *part_table,
+ uint8_t *out_digest, size_t out_len);
+
+// part_digest computes a checksum over the partition table to detect
+// flash problems
+static void part_checksum(struct partition_table *part_table,
+ uint8_t *out_digest, size_t out_len)
+{
+ int blake2err = 0;
+
+ assert(part_table != NULL);
+ assert(out_digest != NULL);
+
+ blake2err = blake2s(out_digest, out_len, NULL, 0, part_table,
+ sizeof(struct partition_table));
+
+ assert(blake2err == 0);
+}
+
+// part_table_read reads and verifies the partition table storage,
+// first trying slot 0, then slot 1 if slot 0 does not verify.
+//
+// It stores the partition table in storage.
+//
+// Returns negative values on errors.
+int part_table_read(struct partition_table_storage *storage)
+{
+ uint32_t offset[2] = {
+ ADDR_PARTITION_TABLE_0,
+ ADDR_PARTITION_TABLE_1,
+ };
+ uint8_t check_digest[PART_CHECKSUM_SIZE] = {0};
+
+ if (storage == NULL) {
+ return -1;
+ }
+
+ flash_release_powerdown();
+ (void)memset(storage, 0x00, sizeof(*storage));
+
+ for (int i = 0; i < 2; i++) {
+ if (flash_read_data(offset[i], (uint8_t *)storage,
+ sizeof(*storage)) != 0) {
+ return -1;
+ }
+ part_checksum(&storage->table, check_digest,
+ sizeof(check_digest));
+
+ if (memeq(check_digest, storage->checksum,
+ sizeof(check_digest))) {
+ if (i == 1) {
+ part_status = PART_SLOT0_INVALID;
+ }
+
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+int part_table_write(struct partition_table_storage *storage)
+{
+ uint32_t offset[2] = {
+ ADDR_PARTITION_TABLE_0,
+ ADDR_PARTITION_TABLE_1,
+ };
+
+ if (storage == NULL) {
+ return -1;
+ }
+
+ part_checksum(&storage->table, storage->checksum,
+ sizeof(storage->checksum));
+
+ for (int i = 0; i < 2; i++) {
+ flash_sector_erase(offset[i]);
+ if (flash_write_data(offset[i], (uint8_t *)storage,
+ sizeof(*storage)) != 0) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
diff --git a/hw/application_fpga/fw/tk1/partition_table.h b/hw/application_fpga/fw/tk1/partition_table.h
new file mode 100644
index 0000000..097942e
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/partition_table.h
@@ -0,0 +1,112 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifndef PARTITION_TABLE_H
+#define PARTITION_TABLE_H
+
+#include
+
+// ---- Flash ---- ----
+// name size start addr
+// ---- ---- ----
+// bitstream 128KiB 0x00
+// ---- ---- ----
+// Partition 64KiB 0x20000
+// ---- ---- ----
+// Pre load 1 128KiB 0x30000
+// Pre load 2 128KiB 0x50000
+// ---- ---- ----
+// storage 1 128KiB 0x70000
+// storage 2 128KiB 0x90000
+// storage 3 128KiB 0xB0000
+// storage 4 128KiB 0xD0000
+// ---- ---- ----
+// Partition2 64KiB 0xf0000
+
+// To simplify all blocks are aligned with the 64KiB blocks on the
+// W25Q80DL flash.
+
+#define PART_TABLE_VERSION 1
+
+#define ADDR_BITSTREAM 0UL
+#define SIZE_BITSTREAM 0x20000UL // 128KiB
+
+#define ADDR_PARTITION_TABLE_0 (ADDR_BITSTREAM + SIZE_BITSTREAM)
+#define ADDR_PARTITION_TABLE_1 0xf0000
+#define SIZE_PARTITION_TABLE \
+ 0x10000UL // 64KiB, 60 KiB reserved, 2 flash pages (2 x 4KiB) for the
+ // partition table
+
+#define N_PRELOADED_APP 2
+#define ADDR_PRE_LOADED_APP_0 (ADDR_PARTITION_TABLE_0 + SIZE_PARTITION_TABLE)
+#define SIZE_PRE_LOADED_APP 0x20000UL // 128KiB
+
+#define ADDR_STORAGE_AREA \
+ (ADDR_PRE_LOADED_APP_0 + (N_PRELOADED_APP * SIZE_PRE_LOADED_APP))
+#define SIZE_STORAGE_AREA 0x20000UL // 128KiB
+#define N_STORAGE_AREA 4
+
+#define PART_CHECKSUM_SIZE 32
+
+enum part_status {
+ PART_SLOT0_INVALID = 1,
+};
+
+// Partition Table
+// ----------------------------------------------------------------------
+// - Table header
+// - 1 bytes Version
+//
+// - Pre-loaded device app 1
+// - 4 bytes length.
+// - 32 bytes digest.
+// - 64 bytes signature.
+//
+// - Pre-loaded device app 2
+// - 4 bytes length.
+// - 32 bytes digest.
+// - 64 bytes signature.
+//
+// - Device app storage area
+// - 1 byte status.
+// - 16 bytes random nonce.
+// - 16 bytes authentication tag.
+//
+// - Checksum over the above
+
+struct auth_metadata {
+ uint8_t nonce[16];
+ uint8_t authentication_digest[16];
+} __attribute__((packed));
+
+struct pre_loaded_app_metadata {
+ uint32_t size;
+ uint8_t digest[32];
+ uint8_t signature[64];
+} __attribute__((packed));
+
+struct app_storage_area {
+ uint8_t status;
+ struct auth_metadata auth;
+} __attribute__((packed));
+
+struct table_header {
+ uint8_t version;
+} __attribute__((packed));
+
+struct partition_table {
+ struct table_header header;
+ struct pre_loaded_app_metadata pre_app_data[N_PRELOADED_APP];
+ struct app_storage_area app_storage[N_STORAGE_AREA];
+} __attribute__((packed));
+
+struct partition_table_storage {
+ struct partition_table table;
+ uint8_t checksum[PART_CHECKSUM_SIZE]; // Helps detect flash problems
+} __attribute__((packed));
+
+enum part_status part_get_status(void);
+int part_table_read(struct partition_table_storage *storage);
+int part_table_write(struct partition_table_storage *storage);
+
+#endif
diff --git a/hw/application_fpga/fw/tk1/picorv32/README.md b/hw/application_fpga/fw/tk1/picorv32/README.md
new file mode 100644
index 0000000..07077ff
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/picorv32/README.md
@@ -0,0 +1,12 @@
+# PicoRV32
+
+## custom_ops.S
+
+We have imported the custom Custom PicoRV32 instructions in
+`custom_ops.S` from https://github.com/YosysHQ/picorv32/ tag v1.0,
+commit 6d145b708d5dfa4caa3445bc599927cebc3291d8.
+
+Upstream path is `picorv32/firmware/custom_ops.S`.
+
+The picorv32 firmware is public domain, which we chose to mark as
+CC0-1.0.
diff --git a/hw/application_fpga/fw/tk1/picorv32/custom_ops.S b/hw/application_fpga/fw/tk1/picorv32/custom_ops.S
new file mode 100644
index 0000000..af380a6
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/picorv32/custom_ops.S
@@ -0,0 +1,105 @@
+// SPDX-FileCopyrightText: Claire Xenia Wolf
+// SPDX-License-Identifier: CC0-1.0
+
+// This is free and unencumbered software released into the public domain.
+//
+// Anyone is free to copy, modify, publish, use, compile, sell, or
+// distribute this software, either in source code form or as a compiled
+// binary, for any purpose, commercial or non-commercial, and by any
+// means.
+
+#define regnum_q0 0
+#define regnum_q1 1
+#define regnum_q2 2
+#define regnum_q3 3
+
+#define regnum_x0 0
+#define regnum_x1 1
+#define regnum_x2 2
+#define regnum_x3 3
+#define regnum_x4 4
+#define regnum_x5 5
+#define regnum_x6 6
+#define regnum_x7 7
+#define regnum_x8 8
+#define regnum_x9 9
+#define regnum_x10 10
+#define regnum_x11 11
+#define regnum_x12 12
+#define regnum_x13 13
+#define regnum_x14 14
+#define regnum_x15 15
+#define regnum_x16 16
+#define regnum_x17 17
+#define regnum_x18 18
+#define regnum_x19 19
+#define regnum_x20 20
+#define regnum_x21 21
+#define regnum_x22 22
+#define regnum_x23 23
+#define regnum_x24 24
+#define regnum_x25 25
+#define regnum_x26 26
+#define regnum_x27 27
+#define regnum_x28 28
+#define regnum_x29 29
+#define regnum_x30 30
+#define regnum_x31 31
+
+#define regnum_zero 0
+#define regnum_ra 1
+#define regnum_sp 2
+#define regnum_gp 3
+#define regnum_tp 4
+#define regnum_t0 5
+#define regnum_t1 6
+#define regnum_t2 7
+#define regnum_s0 8
+#define regnum_s1 9
+#define regnum_a0 10
+#define regnum_a1 11
+#define regnum_a2 12
+#define regnum_a3 13
+#define regnum_a4 14
+#define regnum_a5 15
+#define regnum_a6 16
+#define regnum_a7 17
+#define regnum_s2 18
+#define regnum_s3 19
+#define regnum_s4 20
+#define regnum_s5 21
+#define regnum_s6 22
+#define regnum_s7 23
+#define regnum_s8 24
+#define regnum_s9 25
+#define regnum_s10 26
+#define regnum_s11 27
+#define regnum_t3 28
+#define regnum_t4 29
+#define regnum_t5 30
+#define regnum_t6 31
+
+// x8 is s0 and also fp
+#define regnum_fp 8
+
+#define r_type_insn(_f7, _rs2, _rs1, _f3, _rd, _opc) \
+.word (((_f7) << 25) | ((_rs2) << 20) | ((_rs1) << 15) | ((_f3) << 12) | ((_rd) << 7) | ((_opc) << 0))
+
+#define picorv32_getq_insn(_rd, _qs) \
+r_type_insn(0b0000000, 0, regnum_ ## _qs, 0b100, regnum_ ## _rd, 0b0001011)
+
+#define picorv32_setq_insn(_qd, _rs) \
+r_type_insn(0b0000001, 0, regnum_ ## _rs, 0b010, regnum_ ## _qd, 0b0001011)
+
+#define picorv32_retirq_insn() \
+r_type_insn(0b0000010, 0, 0, 0b000, 0, 0b0001011)
+
+#define picorv32_maskirq_insn(_rd, _rs) \
+r_type_insn(0b0000011, 0, regnum_ ## _rs, 0b110, regnum_ ## _rd, 0b0001011)
+
+#define picorv32_waitirq_insn(_rd) \
+r_type_insn(0b0000100, 0, 0, 0b100, regnum_ ## _rd, 0b0001011)
+
+#define picorv32_timer_insn(_rd, _rs) \
+r_type_insn(0b0000101, 0, regnum_ ## _rs, 0b110, regnum_ ## _rd, 0b0001011)
+
diff --git a/hw/application_fpga/fw/tk1/preload_app.c b/hw/application_fpga/fw/tk1/preload_app.c
new file mode 100644
index 0000000..971077d
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/preload_app.c
@@ -0,0 +1,224 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "flash.h"
+#include "mgmt_app.h"
+#include "partition_table.h"
+#include "preload_app.h"
+
+static uint32_t slot_to_start_address(uint8_t slot)
+{
+ return ADDR_PRE_LOADED_APP_0 + slot * SIZE_PRE_LOADED_APP;
+}
+
+// Loads a preloaded app from flash to app RAM
+int preload_load(struct partition_table *part_table, uint8_t from_slot)
+{
+ if (part_table == NULL) {
+ return -1;
+ }
+
+ if (from_slot >= N_PRELOADED_APP) {
+ return -1;
+ }
+
+ // Check for a valid app in flash
+ if (part_table->pre_app_data[from_slot].size == 0 &&
+ part_table->pre_app_data[from_slot].size <= TK1_APP_MAX_SIZE) {
+ return -1;
+ }
+ uint8_t *loadaddr = (uint8_t *)TK1_RAM_BASE;
+
+ // Read from flash, straight into RAM
+ int ret = flash_read_data(slot_to_start_address(from_slot), loadaddr,
+ part_table->pre_app_data[from_slot].size);
+
+ return ret;
+}
+
+// preload_store stores chunks of an app in app slot to_slot. data is a buffer
+// of size size to be written at byte offset in the slot. offset needs to be
+// kept and updated between each call. offset must be a multiple of 256.
+//
+// When all data has been written call preload_store_finalize() with the last
+// parameters.
+//
+// Returns 0 on success.
+int preload_store(struct partition_table *part_table, uint32_t offset,
+ uint8_t *data, size_t size, uint8_t to_slot)
+{
+ if (part_table == NULL || data == NULL) {
+ return -1;
+ }
+
+ if (to_slot >= N_PRELOADED_APP) {
+ return -1;
+ }
+
+ // Check if we are allowed to store
+ if (!mgmt_app_authenticate()) {
+ return -1;
+ }
+
+ // Check for a valid app in flash, bale out if it already
+ // exists
+ if (part_table->pre_app_data[to_slot].size != 0) {
+ return -1;
+ }
+
+ if (offset > SIZE_PRE_LOADED_APP) {
+ return -1;
+ }
+
+ if (size > SIZE_PRE_LOADED_APP) {
+ return -1;
+ }
+
+ if ((offset + size) > SIZE_PRE_LOADED_APP) {
+ // Writing outside of area
+ return -1;
+ }
+
+ uint32_t address = slot_to_start_address(to_slot) + offset;
+
+ debug_puts("preload_store: write to addr: ");
+ debug_putinthex(address);
+ debug_lf();
+
+ return flash_write_data(address, data, size);
+}
+
+int preload_store_finalize(struct partition_table_storage *part_table_storage,
+ size_t app_size, uint8_t app_digest[32],
+ uint8_t app_signature[64], uint8_t to_slot)
+{
+ struct partition_table *part_table = &part_table_storage->table;
+
+ if (part_table == NULL) {
+ return -1;
+ }
+
+ // Allow data to point only to app RAM
+ if (app_digest < (uint8_t *)TK1_RAM_BASE ||
+ app_digest >= (uint8_t *)(TK1_RAM_BASE + TK1_RAM_SIZE)) {
+ return -1;
+ }
+
+ if (app_signature < (uint8_t *)TK1_RAM_BASE ||
+ app_signature >= (uint8_t *)(TK1_RAM_BASE + TK1_RAM_SIZE)) {
+ return -1;
+ }
+
+ if (to_slot >= N_PRELOADED_APP) {
+ return -1;
+ }
+
+ // Check if we are allowed to store
+ if (!mgmt_app_authenticate()) {
+ return -1;
+ }
+
+ // Check for a valid app in flash, bale out if it already
+ // exists
+ if (part_table->pre_app_data[to_slot].size != 0) {
+ return -1;
+ }
+
+ if (app_size == 0 || app_size > SIZE_PRE_LOADED_APP) {
+ return -1;
+ }
+
+ part_table->pre_app_data[to_slot].size = app_size;
+ memcpy_s(part_table->pre_app_data[to_slot].digest,
+ sizeof(part_table->pre_app_data[to_slot].digest), app_digest,
+ 32);
+ memcpy_s(part_table->pre_app_data[to_slot].signature,
+ sizeof(part_table->pre_app_data[to_slot].signature),
+ app_signature, 64);
+ debug_puts("preload_*_final: size: ");
+ debug_putinthex(app_size);
+ debug_lf();
+
+ if (part_table_write(part_table_storage) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int preload_delete(struct partition_table_storage *part_table_storage,
+ uint8_t slot)
+{
+ struct partition_table *part_table = &part_table_storage->table;
+
+ if (part_table_storage == NULL) {
+ return -1;
+ }
+
+ if (slot >= N_PRELOADED_APP) {
+ return -1;
+ }
+
+ // Check if we are allowed to delete
+ if (!mgmt_app_authenticate()) {
+ return -1;
+ }
+
+ // Check for a valid app in flash
+ if (part_table->pre_app_data[slot].size == 0) {
+ // Nothing to do.
+ return 0;
+ }
+
+ part_table->pre_app_data[slot].size = 0;
+
+ (void)memset(part_table->pre_app_data[slot].digest, 0,
+ sizeof(part_table->pre_app_data[slot].digest));
+
+ (void)memset(part_table->pre_app_data[slot].signature, 0,
+ sizeof(part_table->pre_app_data[slot].signature));
+
+ if (part_table_write(part_table_storage) != 0) {
+ return -1;
+ }
+
+ // Assumes the area is 64 KiB block aligned
+ flash_block_64_erase(
+ slot_to_start_address(slot)); // Erase first 64 KB block
+ flash_block_64_erase(slot_to_start_address(slot) +
+ 0x10000); // Erase first 64 KB block
+
+ return 0;
+}
+
+int preload_get_digsig(struct partition_table *part_table,
+ uint8_t app_digest[32], uint8_t app_signature[64],
+ uint8_t slot)
+{
+ if (part_table == NULL || app_digest == NULL || app_signature == NULL) {
+ return -1;
+ }
+
+ if (slot >= N_PRELOADED_APP) {
+ return -1;
+ }
+
+ // Check if we are allowed to read
+ if (!mgmt_app_authenticate()) {
+ return -1;
+ }
+
+ memcpy_s(app_digest, 32, part_table->pre_app_data[slot].digest,
+ sizeof(part_table->pre_app_data[slot].digest));
+ memcpy_s(app_signature, 64, part_table->pre_app_data[slot].signature,
+ sizeof(part_table->pre_app_data[slot].signature));
+
+ return 0;
+}
diff --git a/hw/application_fpga/fw/tk1/preload_app.h b/hw/application_fpga/fw/tk1/preload_app.h
new file mode 100644
index 0000000..af2c498
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/preload_app.h
@@ -0,0 +1,24 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifndef PRELOAD_APP_H
+#define PRELOAD_APP_H
+
+#include "partition_table.h"
+#include
+#include
+#include
+
+int preload_load(struct partition_table *part_table, uint8_t from_slot);
+int preload_store(struct partition_table *part_table, uint32_t offset,
+ uint8_t *data, size_t size, uint8_t to_slot);
+int preload_store_finalize(struct partition_table_storage *part_table_storage,
+ size_t app_size, uint8_t app_digest[32],
+ uint8_t app_signature[64], uint8_t to_slot);
+int preload_delete(struct partition_table_storage *part_table_storage,
+ uint8_t slot);
+int preload_get_digsig(struct partition_table *part_table,
+ uint8_t app_digest[32], uint8_t app_signature[64],
+ uint8_t slot);
+
+#endif
diff --git a/hw/application_fpga/fw/tk1/proto.c b/hw/application_fpga/fw/tk1/proto.c
index 08c5c22..4ddf1c4 100644
--- a/hw/application_fpga/fw/tk1/proto.c
+++ b/hw/application_fpga/fw/tk1/proto.c
@@ -1,29 +1,20 @@
-/*
- * Copyright (C) 2022, 2023 - Tillitis AB
- * SPDX-License-Identifier: GPL-2.0-only
- */
+// Copyright (C) 2022, 2023 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
#include "proto.h"
-#include "../tk1_mem.h"
-#include "assert.h"
-#include "led.h"
-#include "lib.h"
#include "state.h"
-#include "types.h"
-
-// clang-format off
-static volatile uint32_t *can_rx = (volatile uint32_t *)TK1_MMIO_UART_RX_STATUS;
-static volatile uint32_t *rx = (volatile uint32_t *)TK1_MMIO_UART_RX_DATA;
-static volatile uint32_t *can_tx = (volatile uint32_t *)TK1_MMIO_UART_TX_STATUS;
-static volatile uint32_t *tx = (volatile uint32_t *)TK1_MMIO_UART_TX_DATA;
-// clang-format on
static uint8_t genhdr(uint8_t id, uint8_t endpoint, uint8_t status,
enum cmdlen len);
static int parseframe(uint8_t b, struct frame_header *hdr);
-static void write(uint8_t *buf, size_t nbytes);
-static int read(uint8_t *buf, size_t bufsize, size_t nbytes, uint8_t *mode,
- uint8_t *mode_bytes_left);
static size_t bytelen(enum cmdlen cmdlen);
static uint8_t genhdr(uint8_t id, uint8_t endpoint, uint8_t status,
@@ -32,29 +23,59 @@ static uint8_t genhdr(uint8_t id, uint8_t endpoint, uint8_t status,
return (id << 5) | (endpoint << 3) | (status << 2) | len;
}
-int readcommand(struct frame_header *hdr, uint8_t *cmd, int state,
- uint8_t *mode, uint8_t *mode_bytes_left)
+int readcommand(struct frame_header *hdr, uint8_t *cmd, int state)
{
uint8_t in = 0;
+ uint8_t available = 0;
+ enum ioend endpoint = IO_NONE;
- set_led((state == FW_STATE_LOADING) ? LED_BLACK : LED_WHITE);
- in = readbyte(mode, mode_bytes_left);
+ led_set((state == FW_STATE_LOADING) ? LED_BLACK : LED_WHITE);
- if (parseframe(in, hdr) == -1) {
- htif_puts("Couldn't parse header\n");
+ debug_puts("readcommand\n");
+
+ if (readselect(IO_CDC, &endpoint, &available) < 0) {
return -1;
}
- (void)memset(cmd, 0, CMDLEN_MAXBYTES);
- // Now we know the size of the cmd frame, read it all
- if (read(cmd, CMDLEN_MAXBYTES, hdr->len, mode, mode_bytes_left) != 0) {
- htif_puts("read: buffer overrun\n");
+ if (read(IO_CDC, &in, 1, 1) == -1) {
return -1;
}
+ debug_puts("read 1 byte\n");
+
+ if (parseframe(in, hdr) == -1) {
+ debug_puts("Couldn't parse header\n");
+ return -1;
+ }
+
+ debug_puts("parseframe succeeded\n");
+
+ (void)memset(cmd, 0, CMDSIZE);
+
+ // Now we know the size of the cmd frame, read it all
+ uint8_t n = 0;
+ while (n < hdr->len) {
+ // Wait for something to be available
+ if (readselect(IO_CDC, &endpoint, &available) < 0) {
+ return -1;
+ }
+
+ // Read as much as is available of what we expect
+ available = available > hdr->len ? hdr->len : available;
+
+ assert(n < CMDSIZE);
+ int n_bytes_read =
+ read(IO_CDC, &cmd[n], CMDSIZE - n, available);
+ if (n_bytes_read < 0) {
+ return -1;
+ }
+
+ n += n_bytes_read;
+ }
+
// Is it for us?
if (hdr->endpoint != DST_FW) {
- htif_puts("Message not meant for us\n");
+ debug_puts("Message not meant for us\n");
return -1;
}
@@ -85,7 +106,8 @@ static int parseframe(uint8_t b, struct frame_header *hdr)
void fwreply(struct frame_header hdr, enum fwcmd rspcode, uint8_t *buf)
{
size_t nbytes = 0;
- enum cmdlen len = 0; // length covering (rspcode + length of buf)
+ enum cmdlen len = 0; // length covering (rspcode + length of buf)
+ uint8_t frame[1 + 128]; // Frame header + longest response
switch (rspcode) {
case FW_RSP_NAME_VERSION:
@@ -109,94 +131,23 @@ void fwreply(struct frame_header hdr, enum fwcmd rspcode, uint8_t *buf)
break;
default:
- htif_puts("fwreply(): Unknown response code: 0x");
- htif_puthex(rspcode);
- htif_lf();
+ debug_puts("fwreply(): Unknown response code: 0x");
+ debug_puthex(rspcode);
+ debug_lf();
return;
}
nbytes = bytelen(len);
- // Mode Protocol Header
- writebyte(MODE_CDC);
- writebyte(2);
-
// Frame Protocol Header
- writebyte(genhdr(hdr.id, hdr.endpoint, 0x0, len));
+ frame[0] = genhdr(hdr.id, hdr.endpoint, 0x0, len);
+ // App protocol header
+ frame[1] = rspcode;
- // FW protocol header
- writebyte(rspcode);
- nbytes--;
+ // Payload
+ memcpy(&frame[2], buf, nbytes - 1);
- while (nbytes > 0) {
- // Limit transfers to 64 bytes (2 byte header + 62 byte data) to
- // fit in a single USB frame.
- size_t tx_count = nbytes > 62 ? 62 : nbytes;
- // Mode Protocol Header
- writebyte(MODE_CDC);
- writebyte(tx_count & 0xff);
-
- // Data
- write(buf, tx_count);
- nbytes -= tx_count;
- buf += tx_count;
- }
-}
-
-void writebyte(uint8_t b)
-{
- for (;;) {
- if (*can_tx) {
- *tx = b;
- return;
- }
- }
-}
-
-static void write(uint8_t *buf, size_t nbytes)
-{
- for (int i = 0; i < nbytes; i++) {
- writebyte(buf[i]);
- }
-}
-
-uint8_t readbyte_(void)
-{
- for (;;) {
- if (*can_rx) {
- uint32_t b = *rx;
- return b;
- }
- }
-}
-
-uint8_t readbyte(uint8_t *mode, uint8_t *mode_bytes_left)
-{
- if (*mode_bytes_left == 0) {
- *mode = readbyte_();
- if (*mode != MODE_CDC) {
- htif_puts("We only support MODE_CDC\n");
- } else {
- *mode_bytes_left = readbyte_();
- }
- }
- uint8_t b = readbyte_();
- *mode_bytes_left -= 1;
- return b;
-}
-
-static int read(uint8_t *buf, size_t bufsize, size_t nbytes, uint8_t *mode,
- uint8_t *mode_bytes_left)
-{
- if (nbytes > bufsize) {
- return -1;
- }
-
- for (int n = 0; n < nbytes; n++) {
- buf[n] = readbyte(mode, mode_bytes_left);
- }
-
- return 0;
+ write(IO_CDC, frame, 1 + nbytes);
}
// bytelen returns the number of bytes a cmdlen takes
diff --git a/hw/application_fpga/fw/tk1/proto.h b/hw/application_fpga/fw/tk1/proto.h
index 4f77237..3a0d8ce 100644
--- a/hw/application_fpga/fw/tk1/proto.h
+++ b/hw/application_fpga/fw/tk1/proto.h
@@ -1,19 +1,12 @@
-/*
- * Copyright (C) 2022, 2023 - Tillitis AB
- * SPDX-License-Identifier: GPL-2.0-only
- */
+// Copyright (C) 2022, 2023 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
-#include "types.h"
+#include
+#include
#ifndef PROTO_H
#define PROTO_H
-enum mode {
- MODE_TKEYCTRL = 0x20,
- MODE_CDC = 0x40,
- MODE_HID = 0x80,
-};
-
enum endpoints {
DST_HW_IFPGA,
DST_HW_AFPGA,
@@ -28,7 +21,7 @@ enum cmdlen {
LEN_128
};
-#define CMDLEN_MAXBYTES 128
+#define CMDSIZE 128
// clang-format off
enum fwcmd {
@@ -57,9 +50,6 @@ struct frame_header {
};
/*@ -exportlocal @*/
-void writebyte(uint8_t b);
-uint8_t readbyte(uint8_t *mode, uint8_t *mode_bytes_left);
void fwreply(struct frame_header hdr, enum fwcmd rspcode, uint8_t *buf);
-int readcommand(struct frame_header *hdr, uint8_t *cmd, int state,
- uint8_t *mode, uint8_t *mode_bytes_left);
+int readcommand(struct frame_header *hdr, uint8_t *cmd, int state);
#endif
diff --git a/hw/application_fpga/fw/tk1/qemu_firmware.lds b/hw/application_fpga/fw/tk1/qemu_firmware.lds
new file mode 100644
index 0000000..73a63f5
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/qemu_firmware.lds
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022, 2023 - Tillitis AB
+ * SPDX-License-Identifier: GPL-2.0-only
+*/
+
+OUTPUT_ARCH("riscv")
+ENTRY(_start)
+
+/* Define stack size */
+STACK_SIZE = 3000;
+
+MEMORY
+{
+ ROM (rx) : ORIGIN = 0x00000000, LENGTH = 128k
+ FWRAM (rw) : ORIGIN = 0xd0000000, LENGTH = 0xF00 /* 3840 B */
+ RESETINFO (rw) : ORIGIN = 0xd0000F00, LENGTH = 0x100 /* 256 B (part of FW_RAM area) */
+ RAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x20000 /* 128 KB */
+}
+
+SECTIONS
+{
+ .text.init :
+ {
+ *(.text.init)
+ } >ROM
+
+ .htif :
+ {
+ . = ALIGN(0x00000000);
+ *(.htif)
+ } >ROM
+
+ .text :
+ {
+ . = ALIGN(4);
+ _stext = .;
+ *(.text) /* .text sections (code) */
+ *(.text*) /* .text* sections (code) */
+ *(.rodata) /* .rodata sections (constants, strings, etc.) */
+ *(.rodata*) /* .rodata* sections (constants, strings, etc.) */
+ *(.srodata) /* .srodata sections (constants, strings, etc.) */
+ *(.srodata*) /* .srodata* sections (constants, strings, etc.) */
+ . = ALIGN(4);
+ _etext = .;
+ } >ROM
+
+ .stack (NOLOAD) :
+ {
+ . = ALIGN(16);
+ _sstack = .;
+ . += STACK_SIZE;
+ . = ALIGN(16);
+ _estack = .;
+ } >FWRAM
+
+ .data :
+ {
+ . = ALIGN(4);
+ _sdata = .;
+ *(.data) /* .data sections */
+ *(.data*) /* .data* sections */
+ *(.sdata) /* .sdata sections */
+ *(.sdata*) /* .sdata* sections */
+ . = ALIGN(4);
+ _edata = .;
+ } >FWRAM AT>ROM
+ _sidata = LOADADDR(.data);
+
+ /* Uninitialized data section */
+ .bss :
+ {
+ . = ALIGN(4);
+ _sbss = .;
+ *(.bss)
+ *(.bss*)
+ *(.sbss)
+ *(.sbss*)
+ *(COMMON)
+ . = ALIGN(4);
+ _ebss = .;
+ } >FWRAM
+}
+
+_sfwram = ORIGIN(FWRAM);
+_efwram = ORIGIN(FWRAM) + LENGTH(FWRAM);
+_sresetinfo = ORIGIN(RESETINFO);
+_eresetinfo = ORIGIN(RESETINFO) + LENGTH(RESETINFO);
diff --git a/hw/application_fpga/fw/tk1/reset.c b/hw/application_fpga/fw/tk1/reset.c
new file mode 100644
index 0000000..93a1fb4
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/reset.c
@@ -0,0 +1,59 @@
+// Copyright (C) 2025 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include
+#include
+#include
+#include
+
+#include "reset.h"
+
+// clang-format off
+static volatile uint32_t *system_reset = (volatile uint32_t *)TK1_MMIO_TK1_SYSTEM_RESET;
+static volatile struct reset *resetinfo = (volatile struct reset *)TK1_MMIO_RESETINFO_BASE;
+// clang-format on
+
+int reset(struct reset *userreset, size_t nextlen)
+{
+ if ((uint32_t)userreset < TK1_RAM_BASE ||
+ (uint32_t)userreset >= TK1_RAM_BASE + TK1_RAM_SIZE) {
+ return -1;
+ }
+
+ if (nextlen > sizeof(resetinfo->next_app_data)) {
+ return -1;
+ }
+
+ (void)memset((void *)resetinfo, 0, sizeof(*resetinfo));
+ resetinfo->type = userreset->type;
+ memcpy((void *)resetinfo->app_digest, userreset->app_digest,
+ sizeof(resetinfo->app_digest));
+ memcpy((void *)resetinfo->next_app_data, userreset->next_app_data,
+ nextlen);
+
+ // Do the actual reset.
+ *system_reset = 1;
+
+ // Should not be reached.
+ assert(1 == 2);
+
+ __builtin_unreachable();
+}
+
+int reset_data(uint8_t *next_app_data)
+{
+ if ((uint32_t)next_app_data < TK1_RAM_BASE ||
+ (uint32_t)next_app_data >= TK1_RAM_BASE + TK1_RAM_SIZE) {
+ return -1;
+ }
+
+ if ((uint32_t)next_app_data + RESET_DATA_SIZE >
+ TK1_RAM_BASE + TK1_RAM_SIZE) {
+ return -1;
+ }
+
+ memcpy(next_app_data, (void *)resetinfo->next_app_data,
+ RESET_DATA_SIZE);
+
+ return 0;
+}
diff --git a/hw/application_fpga/fw/tk1/reset.h b/hw/application_fpga/fw/tk1/reset.h
new file mode 100644
index 0000000..f763d89
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/reset.h
@@ -0,0 +1,33 @@
+// Copyright (C) 2025 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifndef TKEY_RESET_H
+#define TKEY_RESET_H
+
+#include
+#include
+
+#define TK1_MMIO_RESETINFO_BASE 0xd0000f00
+#define TK1_MMIO_RESETINFO_SIZE 0x100
+#define RESET_DIGEST_SIZE 32
+#define RESET_DATA_SIZE 220
+
+enum reset_start {
+ START_DEFAULT = 0, // Probably cold boot
+ START_FLASH0 = 1,
+ START_FLASH1 = 2,
+ START_FLASH0_VER = 3,
+ START_FLASH1_VER = 4,
+ START_CLIENT = 5,
+ START_CLIENT_VER = 6,
+};
+
+struct reset {
+ enum reset_start type;
+ uint8_t app_digest[RESET_DIGEST_SIZE];
+ uint8_t next_app_data[RESET_DATA_SIZE];
+};
+
+int reset(struct reset *userreset, size_t nextlen);
+int reset_data(uint8_t *next_app_data);
+#endif
diff --git a/hw/application_fpga/fw/tk1/rng.c b/hw/application_fpga/fw/tk1/rng.c
new file mode 100644
index 0000000..242679f
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/rng.c
@@ -0,0 +1,28 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "rng.h"
+#include
+
+#include
+
+// clang-format off
+static volatile uint32_t *trng_status = (volatile uint32_t *)TK1_MMIO_TRNG_STATUS;
+static volatile uint32_t *trng_entropy = (volatile uint32_t *)TK1_MMIO_TRNG_ENTROPY;
+// clang-format on
+
+uint32_t rng_get_word(void)
+{
+ while ((*trng_status & (1 << TK1_MMIO_TRNG_STATUS_READY_BIT)) == 0) {
+ }
+ return *trng_entropy;
+}
+
+uint32_t rng_xorwow(uint32_t state, uint32_t acc)
+{
+ state ^= state << 13;
+ state ^= state >> 17;
+ state ^= state << 5;
+ state += acc;
+ return state;
+}
diff --git a/hw/application_fpga/fw/tk1/rng.h b/hw/application_fpga/fw/tk1/rng.h
new file mode 100644
index 0000000..616b551
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/rng.h
@@ -0,0 +1,11 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+#ifndef RNG_H
+#define RNG_H
+
+#include
+
+uint32_t rng_get_word(void);
+uint32_t rng_xorwow(uint32_t state, uint32_t acc);
+
+#endif
diff --git a/hw/application_fpga/fw/tk1/spi.c b/hw/application_fpga/fw/tk1/spi.c
new file mode 100644
index 0000000..815039d
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/spi.c
@@ -0,0 +1,100 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "spi.h"
+#include
+#include
+
+#include
+#include
+
+// clang-format off
+static volatile uint32_t *spi_en = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x200);
+static volatile uint32_t *spi_xfer = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x204);
+static volatile uint32_t *spi_data = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x208);
+// clang-format on
+
+static int spi_ready(void);
+static void spi_enable(void);
+static void spi_disable(void);
+static void spi_write(uint8_t *cmd, size_t size);
+static void spi_read(uint8_t *buf, size_t size);
+
+// Returns non-zero when the SPI-master is ready, and zero if not
+// ready. This can be used to check if the SPI-master is available
+// in the hardware.
+static int spi_ready(void)
+{
+ return *spi_xfer;
+}
+
+static void spi_enable(void)
+{
+ *spi_en = 1;
+}
+
+static void spi_disable(void)
+{
+ *spi_en = 0;
+}
+
+static void spi_write(uint8_t *cmd, size_t size)
+{
+ assert(cmd != NULL);
+
+ for (size_t i = 0; i < size; i++) {
+ while (!spi_ready()) {
+ }
+
+ *spi_data = cmd[i];
+ *spi_xfer = 1;
+ }
+
+ while (!spi_ready()) {
+ }
+}
+
+static void spi_read(uint8_t *buf, size_t size)
+{
+ assert(buf != NULL);
+
+ while (!spi_ready()) {
+ }
+
+ for (size_t i = 0; i < size; i++) {
+
+ *spi_data = 0x00;
+ *spi_xfer = 1;
+
+ // wait until spi master is done
+ while (!spi_ready()) {
+ }
+
+ buf[i] = (*spi_data & 0xff);
+ }
+}
+
+// Function to both read and write data to the connected SPI flash.
+int spi_transfer(uint8_t *cmd, size_t cmd_size, uint8_t *tx_buf, size_t tx_size,
+ uint8_t *rx_buf, size_t rx_size)
+{
+ if (cmd == NULL || cmd_size == 0) {
+ return -1;
+ }
+
+ spi_enable();
+
+ spi_write(cmd, cmd_size);
+
+ if (tx_buf != NULL && tx_size != 0) {
+ spi_write(tx_buf, tx_size);
+ }
+
+ if (rx_buf != NULL && rx_size != 0) {
+ spi_read(rx_buf, rx_size);
+ }
+
+ spi_disable();
+
+ return 0;
+}
diff --git a/hw/application_fpga/fw/tk1/spi.h b/hw/application_fpga/fw/tk1/spi.h
new file mode 100644
index 0000000..d2571e0
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/spi.h
@@ -0,0 +1,13 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifndef TKEY_SPI_H
+#define TKEY_SPI_H
+
+#include
+#include
+
+int spi_transfer(uint8_t *cmd, size_t cmd_size, uint8_t *tx_buf, size_t tx_size,
+ uint8_t *rx_buf, size_t rx_size);
+
+#endif
diff --git a/hw/application_fpga/fw/tk1/start.S b/hw/application_fpga/fw/tk1/start.S
index 93a2fb0..d0be087 100644
--- a/hw/application_fpga/fw/tk1/start.S
+++ b/hw/application_fpga/fw/tk1/start.S
@@ -1,11 +1,117 @@
-/*
- * Copyright (C) 2022, 2023 - Tillitis AB
- * SPDX-License-Identifier: GPL-2.0-only
-*/
+// Copyright (C) 2022-2025 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include
+
+#ifdef QEMU_SYSCALL
+
+#define picorv32_retirq_insn(...) \
+ mv ra, x3; \
+ ret
+
+#else
+
+#include "picorv32/custom_ops.S" // PicoRV32 custom instructions
+
+#endif
+
+#define illegal_insn() .word 0
+
+ // Variables in bss
+ .lcomm irq_ret_addr, 4
+ .lcomm app_sp, 4
.section ".text.init"
.globl _start
_start:
+ j init
+
+// IRQ handler
+ .=0x10
+irq_handler:
+ // PicoRV32 stores the IRQ bitmask in x4.
+ // If bit 31 is 1: IRQ31 was triggered.
+ li t4, (1 << 31)
+ beq x4, t4, irq_source_ok
+unexpected_irq_source:
+ illegal_insn()
+ j unexpected_irq_source
+irq_source_ok:
+
+ // Save interrupt return address (x3)
+ la t0, irq_ret_addr
+ sw x3, 0(t0)
+
+ // Save app stack pointer. App is responsible for saving the rest of
+ // the registers.
+ la t0, app_sp
+ sw sp, 0(t0)
+
+ // Setup firmware stack pointer
+ la sp, _estack
+
+ // Run syscall handler
+ call syscall_handler
+
+ // Restore app stack pointer
+ la t0, app_sp
+ lw sp, 0(t0)
+
+ // Restore interrupt return address (x3)
+ la t0, irq_ret_addr
+ lw x3, 0(t0)
+
+ // Verify that interrupt return address (x3) is in app RAM
+ li t0, TK1_RAM_BASE // 0x40000000
+ blt x3, t0, x3_invalid
+ li t0, TK1_RAM_BASE + TK1_RAM_SIZE // 0x40020000
+ bge x3, t0, x3_invalid
+ j x3_valid
+x3_invalid:
+ illegal_insn()
+ j x3_invalid
+x3_valid:
+
+ // Remove data left over from the syscall handling
+ mv x0, zero
+ mv x1, zero
+ // x2 (sp) is assumed to be preserved by the interrupt handler
+ // x3 (interrupt return address) need to be preserved
+ mv x4, zero
+ mv x5, zero
+ mv x6, zero
+ mv x7, zero
+ mv x8, zero
+ mv x9, zero
+ // x10 (a0) contains syscall return value. And should not be destroyed.
+ mv x11, zero
+ mv x12, zero
+ mv x13, zero
+ mv x14, zero
+ mv x15, zero
+ mv x16, zero
+ mv x17, zero
+ mv x18, zero
+ mv x19, zero
+ mv x20, zero
+ mv x21, zero
+ mv x22, zero
+ mv x23, zero
+ mv x24, zero
+ mv x25, zero
+ mv x26, zero
+ mv x27, zero
+ mv x28, zero
+ mv x29, zero
+ mv x30, zero
+ mv x31, zero
+
+ picorv32_retirq_insn() // Return from interrupt
+
+// Init
+
+ .=0x100
+init:
li x1, 0
li x2, 0
li x3, 0
@@ -38,18 +144,25 @@ _start:
li x30,0
li x31,0
- /* Clear FW_RAM */
- li a0, 0xd0000000 // TK1_MMIO_FW_RAM_BASE
- li a1, 0xd0000800 // TK1_MMIO_FW_RAM_BASE + TK1_MMIO_FW_RAM_SIZE
+ // Clear FW_RAM
+ la a0, _sfwram
+ la a1, _efwram
clear:
sw zero, 0(a0)
addi a0, a0, 4
blt a0, a1, clear
- /*
- * Init stack at top of fw_ram.
- */
- li sp, 0xd0000800 // 2 kiB (TK1_MMIO_FW_RAM_SIZE)
+ // Zero-init bss section
+ la a0, _sbss
+ la a1, _ebss
+
+loop_init_bss:
+ sw zero, 0(a0)
+ addi a0, a0, 4
+ blt a0, a1, loop_init_bss
+
+ // Init stack
+ la sp, _estack
call main
diff --git a/hw/application_fpga/fw/tk1/state.h b/hw/application_fpga/fw/tk1/state.h
index 694448b..5df5385 100644
--- a/hw/application_fpga/fw/tk1/state.h
+++ b/hw/application_fpga/fw/tk1/state.h
@@ -1,15 +1,16 @@
-/*
- * Copyright (C) 2023 - Tillitis AB
- * SPDX-License-Identifier: GPL-2.0-only
- */
+// Copyright (C) 2023 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
#ifndef STATE_H
#define STATE_H
enum state {
FW_STATE_INITIAL,
+ FW_STATE_WAITCOMMAND,
FW_STATE_LOADING,
- FW_STATE_RUN,
+ FW_STATE_LOAD_FLASH,
+ FW_STATE_LOAD_FLASH_MGMT,
+ FW_STATE_START,
FW_STATE_FAIL,
FW_STATE_MAX,
};
diff --git a/hw/application_fpga/fw/tk1/storage.c b/hw/application_fpga/fw/tk1/storage.c
new file mode 100644
index 0000000..1ba1f28
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/storage.c
@@ -0,0 +1,317 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "auth_app.h"
+#include "flash.h"
+#include "partition_table.h"
+#include "storage.h"
+
+// Returns the index of the first empty area.
+//
+// Returns -1 on errors.
+static int get_first_empty(struct partition_table *part_table)
+{
+ if (part_table == NULL) {
+ return -1;
+ }
+
+ for (uint8_t i = 0; i < N_STORAGE_AREA; i++) {
+ if (part_table->app_storage[i].status == 0x00) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static int index_to_address(int index, uint32_t *address)
+{
+ if (address == NULL) {
+ return -1;
+ }
+
+ if ((index < 0) || (index >= N_STORAGE_AREA)) {
+ return -1;
+ }
+
+ *address = ADDR_STORAGE_AREA + index * SIZE_STORAGE_AREA;
+
+ return 0;
+}
+
+// Returns the index of the area an app has allocated.
+//
+// Returns -1 on errors.
+static int storage_get_area(struct partition_table *part_table)
+{
+ if (part_table == NULL) {
+ return -1;
+ }
+
+ for (uint8_t i = 0; i < N_STORAGE_AREA; i++) {
+ if (part_table->app_storage[i].status != 0x00) {
+ if (auth_app_authenticate(
+ &part_table->app_storage[i].auth)) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+}
+
+// Allocate a new area for an app. Returns zero on success.
+int storage_allocate_area(struct partition_table_storage *part_table_storage)
+{
+ if (part_table_storage == NULL) {
+ return -1;
+ }
+
+ struct partition_table *part_table = &part_table_storage->table;
+
+ if (storage_get_area(part_table) != -1) {
+ /* Already has an area */
+ return 0;
+ }
+
+ int index = get_first_empty(part_table);
+ if (index < 0) {
+ /* No empty slot */
+ return -1;
+ }
+
+ uint32_t start_address = 0;
+
+ if (index_to_address(index, &start_address) != 0) {
+ return -1;
+ }
+
+ // Allocate the empty index found
+ // Erase area first
+
+ // Assumes the area is 64 KiB block aligned
+ flash_block_64_erase(start_address); // Erase first 64 KB block
+ flash_block_64_erase(start_address +
+ 0x10000); // Erase second 64 KB block
+
+ // Write partition table lastly
+ part_table->app_storage[index].status = 0x01;
+ auth_app_create(&part_table->app_storage[index].auth);
+
+ if (part_table_write(part_table_storage) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+// Dealloacate a previously allocated storage area.
+//
+// Returns zero on success, and non-zero on errors.
+int storage_deallocate_area(struct partition_table_storage *part_table_storage)
+{
+ if (part_table_storage == NULL) {
+ return -1;
+ }
+
+ struct partition_table *part_table = &part_table_storage->table;
+
+ int index = storage_get_area(part_table);
+ if (index < 0) {
+ // No area to deallocate
+ return -1;
+ }
+
+ uint32_t start_address = 0;
+ if (index_to_address(index, &start_address) != 0) {
+ return -1;
+ }
+
+ // Erase area first
+
+ // Assumes the area is 64 KiB block aligned
+ flash_block_64_erase(start_address); // Erase first 64 KB block
+ flash_block_64_erase(start_address +
+ 0x10000); // Erase second 64 KB block
+
+ // Clear partition table lastly
+ part_table->app_storage[index].status = 0;
+
+ (void)memset(part_table->app_storage[index].auth.nonce, 0x00,
+ sizeof(part_table->app_storage[index].auth.nonce));
+
+ (void)memset(
+ part_table->app_storage[index].auth.authentication_digest, 0x00,
+ sizeof(part_table->app_storage[index].auth.authentication_digest));
+
+ if (part_table_write(part_table_storage) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+// Erases sector. Offset of a sector to begin erasing, must be a
+// multiple of the sector size. Size to erase in bytes, must be a
+// multiple of the sector size.
+//
+// Returns zero on success, negative error code on failure
+int storage_erase_sector(struct partition_table *part_table, uint32_t offset,
+ size_t size)
+{
+ if (part_table == NULL) {
+ return -1;
+ }
+
+ int index = storage_get_area(part_table);
+ if (index == -1) {
+ // No allocated area
+ return -1;
+ }
+
+ uint32_t start_address = 0;
+ if (index_to_address(index, &start_address) != 0) {
+ return -1;
+ }
+
+ if (offset > SIZE_STORAGE_AREA) {
+ return -1;
+ }
+
+ // Cannot only erase entire sectors
+ if (offset % 4096 != 0) {
+ return -1;
+ }
+
+ // Cannot erase less than one sector
+ if (size < 4096 || size > SIZE_STORAGE_AREA || size % 4096 != 0) {
+ return -1;
+ }
+
+ if ((offset + size) > SIZE_STORAGE_AREA) {
+ return -1;
+ }
+
+ uint32_t address = start_address + offset;
+
+ debug_puts("storage: erase addr: ");
+ debug_putinthex(address);
+ debug_lf();
+
+ for (size_t i = 0; i < size; i += 4096) {
+ flash_sector_erase(address);
+ address += 4096;
+ }
+
+ return 0;
+}
+
+// Writes the specified data to the offset inside of the allocated area.
+// Assumes area has been erased before hand. Offset must be a multiple of 256.
+//
+// Returns zero on success.
+int storage_write_data(struct partition_table *part_table, uint32_t offset,
+ uint8_t *data, size_t size)
+{
+ if (part_table == NULL) {
+ return -1;
+ }
+
+ // Allow data to point only to app RAM
+ if (data < (uint8_t *)TK1_RAM_BASE ||
+ data >= (uint8_t *)(TK1_RAM_BASE + TK1_RAM_SIZE)) {
+ return -1;
+ }
+
+ int index = storage_get_area(part_table);
+ if (index == -1) {
+ // No allocated area
+ return -1;
+ }
+
+ uint32_t start_address = 0;
+ if (index_to_address(index, &start_address) != 0) {
+ return -1;
+ }
+
+ if (offset > SIZE_STORAGE_AREA) {
+ return -1;
+ }
+
+ if (size > SIZE_STORAGE_AREA) {
+ return -1;
+ }
+
+ if ((offset + size) > SIZE_STORAGE_AREA) {
+ // Writing outside of area
+ return -1;
+ }
+
+ uint32_t address = start_address + offset;
+
+ debug_puts("storage: write to addr: ");
+ debug_putinthex(address);
+ debug_lf();
+
+ return flash_write_data(address, data, size);
+}
+
+// Reads size bytes of data at the specified offset inside of the
+// allocated area.
+//
+// Only read limit is the size of the allocated area.
+//
+// Returns zero on success.
+int storage_read_data(struct partition_table *part_table, uint32_t offset,
+ uint8_t *data, size_t size)
+{
+ if (part_table == NULL) {
+ return -1;
+ }
+
+ // Allow data to point only to app RAM
+ if (data < (uint8_t *)TK1_RAM_BASE ||
+ data >= (uint8_t *)(TK1_RAM_BASE + TK1_RAM_SIZE)) {
+ return -1;
+ }
+
+ int index = storage_get_area(part_table);
+ if (index == -1) {
+ // No allocated area
+ return -1;
+ }
+
+ uint32_t start_address = 0;
+
+ if (index_to_address(index, &start_address) != 0) {
+ return -1;
+ }
+
+ if (offset > SIZE_STORAGE_AREA) {
+ return -1;
+ }
+
+ if (size > 4096) {
+ return -1;
+ }
+
+ if ((offset + size) > SIZE_STORAGE_AREA) {
+ // Reading outside of area
+ return -1;
+ }
+
+ uint32_t address = start_address + offset;
+
+ debug_puts("storage: read from addr: ");
+ debug_putinthex(address);
+ debug_lf();
+
+ return flash_read_data(address, data, size);
+}
diff --git a/hw/application_fpga/fw/tk1/storage.h b/hw/application_fpga/fw/tk1/storage.h
new file mode 100644
index 0000000..c61860f
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/storage.h
@@ -0,0 +1,22 @@
+// Copyright (C) 2024 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifndef STORAGE_H
+#define STORAGE_H
+
+#include "partition_table.h"
+
+#include
+#include
+#include
+
+int storage_deallocate_area(struct partition_table_storage *part_table_storage);
+int storage_allocate_area(struct partition_table_storage *part_table_storage);
+int storage_erase_sector(struct partition_table *part_table, uint32_t offset,
+ size_t size);
+int storage_write_data(struct partition_table *part_table, uint32_t offset,
+ uint8_t *data, size_t size);
+int storage_read_data(struct partition_table *part_table, uint32_t offset,
+ uint8_t *data, size_t size);
+
+#endif
diff --git a/hw/application_fpga/fw/tk1/syscall_enable.S b/hw/application_fpga/fw/tk1/syscall_enable.S
new file mode 100644
index 0000000..88b6b0f
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/syscall_enable.S
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: 2025 Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifdef QEMU_SYSCALL
+
+#define picorv32_maskirq_insn(...)
+
+#else
+
+#include "../tk1/picorv32/custom_ops.S"
+
+#endif
+
+ .section ".text"
+ .globl syscall_enable
+
+
+syscall_enable:
+ // Enable syscall IRQ
+ li t0, 0x7fffffff // IRQ31 mask
+ picorv32_maskirq_insn(zero, t0) // Enable IRQs
+
+ ret
diff --git a/hw/application_fpga/fw/tk1/syscall_enable.h b/hw/application_fpga/fw/tk1/syscall_enable.h
new file mode 100644
index 0000000..4377d0d
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/syscall_enable.h
@@ -0,0 +1,9 @@
+// Copyright (C) 2025 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifndef TKEY_SYSCALL_ENABLE_H
+#define TKEY_SYSCALL_ENABLE_H
+
+void syscall_enable(void);
+
+#endif
diff --git a/hw/application_fpga/fw/tk1/syscall_handler.c b/hw/application_fpga/fw/tk1/syscall_handler.c
new file mode 100644
index 0000000..e916e21
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/syscall_handler.c
@@ -0,0 +1,112 @@
+// Copyright (C) 2025 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include
+#include
+#include
+#include
+#include
+
+#include "partition_table.h"
+#include "preload_app.h"
+#include "reset.h"
+#include "storage.h"
+#include "syscall_num.h"
+
+// clang-format off
+static volatile uint32_t *udi = (volatile uint32_t *)TK1_MMIO_TK1_UDI_FIRST;
+// clang-format on
+
+extern struct partition_table_storage part_table_storage;
+extern uint8_t part_status;
+
+int32_t syscall_handler(uint32_t number, uint32_t arg1, uint32_t arg2,
+ uint32_t arg3)
+{
+ switch (number) {
+ case TK1_SYSCALL_RESET:
+ return reset((struct reset *)arg1, (size_t)arg2);
+ break;
+
+ case TK1_SYSCALL_ALLOC_AREA:
+ if (storage_allocate_area(&part_table_storage) < 0) {
+ debug_puts("couldn't allocate storage area\n");
+ return -1;
+ }
+ return 0;
+
+ case TK1_SYSCALL_DEALLOC_AREA:
+ if (storage_deallocate_area(&part_table_storage) < 0) {
+ debug_puts("couldn't deallocate storage area\n");
+ return -1;
+ }
+ return 0;
+
+ case TK1_SYSCALL_WRITE_DATA:
+ if (storage_write_data(&part_table_storage.table, arg1,
+ (uint8_t *)arg2, arg3) < 0) {
+ debug_puts("couldn't write storage area\n");
+ return -1;
+ }
+ return 0;
+
+ case TK1_SYSCALL_READ_DATA:
+ if (storage_read_data(&part_table_storage.table, arg1,
+ (uint8_t *)arg2, arg3) < 0) {
+ debug_puts("couldn't read storage area\n");
+ return -1;
+ }
+ return 0;
+
+ case TK1_SYSCALL_ERASE_DATA:
+ if (storage_erase_sector(&part_table_storage.table, arg1,
+ arg2) < 0) {
+ debug_puts("couldn't erase storage area\n");
+ return -1;
+ }
+ return 0;
+
+ case TK1_SYSCALL_GET_VIDPID:
+ // UDI is 2 words: VID/PID & serial. Return just the
+ // first word. Serial is kept secret to the device
+ // app.
+ return udi[0];
+
+ case TK1_SYSCALL_PRELOAD_DELETE:
+ return preload_delete(&part_table_storage, 1);
+
+ case TK1_SYSCALL_PRELOAD_STORE:
+ // arg1 offset
+ // arg2 data
+ // arg3 size
+ // always using slot 1
+ return preload_store(&part_table_storage.table, arg1,
+ (uint8_t *)arg2, arg3, 1);
+
+ case TK1_SYSCALL_PRELOAD_STORE_FIN:
+ // arg1 app_size
+ // arg2 app_digest
+ // arg3 app_signature
+ // always using slot 1
+ return preload_store_finalize(&part_table_storage, arg1,
+ (uint8_t *)arg2, (uint8_t *)arg3,
+ 1);
+
+ case TK1_SYSCALL_PRELOAD_GET_DIGSIG:
+ return preload_get_digsig(&part_table_storage.table,
+ (uint8_t *)arg1, (uint8_t *)arg2, 1);
+
+ case TK1_SYSCALL_STATUS:
+ return part_get_status();
+
+ case TK1_SYSCALL_GET_APP_DATA:
+ // arg1 next_app_data
+ return reset_data((uint8_t *)arg1);
+
+ default:
+ assert(1 == 2);
+ }
+
+ assert(1 == 2);
+ return -1; // This should never run
+}
diff --git a/hw/application_fpga/fw/tk1/syscall_num.h b/hw/application_fpga/fw/tk1/syscall_num.h
new file mode 100644
index 0000000..14109a0
--- /dev/null
+++ b/hw/application_fpga/fw/tk1/syscall_num.h
@@ -0,0 +1,24 @@
+// Copyright (C) 2025 - Tillitis AB
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifndef TKEY_SYSCALL_NUM_H
+#define TKEY_SYSCALL_NUM_H
+
+enum syscall_num {
+ TK1_SYSCALL_RESET = 1,
+ TK1_SYSCALL_ALLOC_AREA = 2,
+ TK1_SYSCALL_DEALLOC_AREA = 3,
+ TK1_SYSCALL_WRITE_DATA = 4,
+ TK1_SYSCALL_READ_DATA = 5,
+ TK1_SYSCALL_ERASE_DATA = 6,
+ TK1_SYSCALL_GET_VIDPID = 7,
+ TK1_SYSCALL_PRELOAD_STORE = 8,
+ TK1_SYSCALL_PRELOAD_STORE_FIN = 9,
+ TK1_SYSCALL_PRELOAD_DELETE = 10,
+ TK1_SYSCALL_PRELOAD_GET_DIGSIG = 11,
+ TK1_SYSCALL_REG_MGMT = 12,
+ TK1_SYSCALL_STATUS = 13,
+ TK1_SYSCALL_GET_APP_DATA = 14,
+};
+
+#endif
diff --git a/hw/application_fpga/fw/tk1/types.h b/hw/application_fpga/fw/tk1/types.h
deleted file mode 100644
index 9a1fcff..0000000
--- a/hw/application_fpga/fw/tk1/types.h
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2022 - Tillitis AB
- * SPDX-License-Identifier: GPL-2.0-only
- */
-
-#ifndef TYPES_H
-#define TYPES_H
-
-typedef unsigned int uintptr_t;
-typedef unsigned long long uint64_t;
-typedef unsigned int uint32_t;
-typedef int int32_t;
-typedef long long int64_t;
-typedef unsigned char uint8_t;
-typedef unsigned long size_t;
-
-#define NULL ((char *)0)
-
-#define FALSE 0
-#define TRUE !FALSE
-
-#endif
diff --git a/hw/application_fpga/rtl/application_fpga.v b/hw/application_fpga/rtl/application_fpga.v
index 59428e3..f7d21ea 100644
--- a/hw/application_fpga/rtl/application_fpga.v
+++ b/hw/application_fpga/rtl/application_fpga.v
@@ -20,8 +20,8 @@ module application_fpga (
output wire interface_rx,
input wire interface_tx,
- input wire interface_ch552_cts, // CH552 clear to send, 1 = OK, 0 = NOK
- output wire interface_fpga_cts, // FPGA clear to send, 1 = OK, 0 = NOK
+ input wire interface_ch552_cts, // CH552 clear to send, 1 = OK, 0 = NOK
+ output wire interface_fpga_cts, // FPGA clear to send, 1 = OK, 0 = NOK
output wire spi_ss,
output wire spi_sck,
@@ -57,11 +57,13 @@ module application_fpga (
localparam UART_PREFIX = 6'h03;
localparam TOUCH_SENSE_PREFIX = 6'h04;
localparam FW_RAM_PREFIX = 6'h10;
+ localparam SYSCALL_PREFIX = 6'h21;
localparam TK1_PREFIX = 6'h3f;
// Instruction used to cause a trap.
localparam ILLEGAL_INSTRUCTION = 32'h0;
+ localparam IRQ31_IRQ_MASK = 2 ** 31;
//----------------------------------------------------------------
// Registers, memories with associated wires.
@@ -80,16 +82,18 @@ module application_fpga (
wire reset_n;
/* verilator lint_off UNOPTFLAT */
+ reg [31 : 0] cpu_irq;
wire cpu_trap;
wire cpu_valid;
wire cpu_instr;
wire [03 : 0] cpu_wstrb;
/* verilator lint_off UNUSED */
+ wire [31 : 0] cpu_eoi;
wire [31 : 0] cpu_addr;
wire [31 : 0] cpu_wdata;
reg rom_cs;
- reg [11 : 0] rom_address;
+ reg [10 : 0] rom_address;
wire [31 : 0] rom_read_data;
wire rom_ready;
@@ -128,7 +132,7 @@ module application_fpga (
reg fw_ram_cs;
reg [ 3 : 0] fw_ram_we;
- reg [ 8 : 0] fw_ram_address;
+ reg [ 9 : 0] fw_ram_address;
reg [31 : 0] fw_ram_write_data;
wire [31 : 0] fw_ram_read_data;
wire fw_ram_ready;
@@ -139,13 +143,18 @@ module application_fpga (
wire [31 : 0] touch_sense_read_data;
wire touch_sense_ready;
+ reg irq31_cs;
+ reg irq31_we;
+ reg irq31_eoi;
+
reg tk1_cs;
reg tk1_we;
reg [ 7 : 0] tk1_address;
reg [31 : 0] tk1_write_data;
wire [31 : 0] tk1_read_data;
wire tk1_ready;
- wire system_mode;
+ wire app_mode;
+ wire fw_startup_done;
wire force_trap;
wire [14 : 0] ram_addr_rand;
wire [31 : 0] ram_data_rand;
@@ -166,12 +175,17 @@ module application_fpga (
picorv32 #(
- .ENABLE_COUNTERS(0),
- .TWO_STAGE_SHIFT(0),
- .CATCH_MISALIGN (0),
- .COMPRESSED_ISA (1),
- .ENABLE_FAST_MUL(1),
- .BARREL_SHIFTER (1)
+ .ENABLE_COUNTERS (0),
+ .TWO_STAGE_SHIFT (0),
+ .CATCH_MISALIGN (0),
+ .COMPRESSED_ISA (1),
+ .ENABLE_FAST_MUL (1),
+ .BARREL_SHIFTER (1),
+ .ENABLE_IRQ (1),
+ .ENABLE_IRQ_QREGS(0),
+ .ENABLE_IRQ_TIMER(0),
+ .MASKED_IRQ (~IRQ31_IRQ_MASK),
+ .LATCHED_IRQ (IRQ31_IRQ_MASK)
) cpu (
.clk(clk),
.resetn(reset_n),
@@ -185,11 +199,12 @@ module application_fpga (
.mem_rdata(muxed_rdata_reg),
.mem_instr(cpu_instr),
+ .irq(cpu_irq),
+ .eoi(cpu_eoi),
+
// Defined unused ports. Makes lint happy. But
// we still needs to help lint with empty ports.
/* verilator lint_off PINCONNECTEMPTY */
- .irq(32'h0),
- .eoi(),
.trace_valid(),
.trace_data(),
.mem_la_read(),
@@ -240,7 +255,7 @@ module application_fpga (
.clk(clk),
.reset_n(reset_n),
- .system_mode(system_mode),
+ .app_mode(app_mode),
.cs(fw_ram_cs),
.we(fw_ram_we),
@@ -280,7 +295,7 @@ module application_fpga (
.clk(clk),
.reset_n(reset_n),
- .system_mode(system_mode),
+ .en(~fw_startup_done),
.cs(uds_cs),
.address(uds_address),
@@ -297,7 +312,7 @@ module application_fpga (
.txd(interface_rx),
.ch552_cts(interface_ch552_cts),
- .fpga_cts(interface_fpga_cts),
+ .fpga_cts (interface_fpga_cts),
.cs(uart_cs),
.we(uart_we),
@@ -326,7 +341,8 @@ module application_fpga (
.clk(clk),
.reset_n(reset_n),
- .system_mode(system_mode),
+ .app_mode(app_mode),
+ .fw_startup_done(fw_startup_done),
.cpu_addr (cpu_addr),
.cpu_instr (cpu_instr),
@@ -353,6 +369,8 @@ module application_fpga (
.gpio3(app_gpio3),
.gpio4(app_gpio4),
+ .syscall(irq31_eoi),
+
.cs(tk1_cs),
.we(tk1_we),
.address(tk1_address),
@@ -379,6 +397,20 @@ module application_fpga (
end
+ //----------------------------------------------------------------
+ // irq_ctrl
+ // Interrupt logic
+ //----------------------------------------------------------------
+ always @* begin : irq_ctrl
+ reg irq31_set;
+
+ irq31_set = irq31_cs & irq31_we;
+ cpu_irq = {irq31_set, 31'h0};
+
+ irq31_eoi = cpu_eoi[31];
+ end
+
+
//----------------------------------------------------------------
// cpu_mem_ctrl
// CPU memory decode and control logic.
@@ -394,7 +426,7 @@ module application_fpga (
muxed_rdata_new = 32'h0;
rom_cs = 1'h0;
- rom_address = cpu_addr[13 : 2];
+ rom_address = cpu_addr[12 : 2];
ram_cs = 1'h0;
ram_we = 4'h0;
@@ -403,7 +435,7 @@ module application_fpga (
fw_ram_cs = 1'h0;
fw_ram_we = cpu_wstrb;
- fw_ram_address = cpu_addr[10 : 2];
+ fw_ram_address = cpu_addr[11 : 2];
fw_ram_write_data = cpu_wdata;
trng_cs = 1'h0;
@@ -428,6 +460,9 @@ module application_fpga (
touch_sense_we = |cpu_wstrb;
touch_sense_address = cpu_addr[9 : 2];
+ irq31_cs = 1'h0;
+ irq31_we = |cpu_wstrb;
+
tk1_cs = 1'h0;
tk1_we = |cpu_wstrb;
tk1_address = cpu_addr[9 : 2];
@@ -500,6 +535,11 @@ module application_fpga (
muxed_ready_new = fw_ram_ready;
end
+ SYSCALL_PREFIX: begin
+ irq31_cs = 1'h1;
+ muxed_ready_new = 1'h1;
+ end
+
TK1_PREFIX: begin
tk1_cs = 1'h1;
muxed_rdata_new = tk1_read_data;
diff --git a/hw/application_fpga/tb/application_fpga_sim.v b/hw/application_fpga/tb/application_fpga_sim.v
index 047e624..a3314a7 100644
--- a/hw/application_fpga/tb/application_fpga_sim.v
+++ b/hw/application_fpga/tb/application_fpga_sim.v
@@ -70,11 +70,13 @@ module application_fpga_sim (
localparam UART_PREFIX = 6'h03;
localparam TOUCH_SENSE_PREFIX = 6'h04;
localparam FW_RAM_PREFIX = 6'h10;
+ localparam SYSCALL_PREFIX = 6'h21;
localparam TK1_PREFIX = 6'h3f;
// Instruction used to cause a trap.
localparam ILLEGAL_INSTRUCTION = 32'h0;
+ localparam IRQ31_IRQ_MASK = 2 ** 31;
//----------------------------------------------------------------
// Registers, memories with associated wires.
@@ -92,16 +94,18 @@ module application_fpga_sim (
wire reset_n;
/* verilator lint_off UNOPTFLAT */
+ reg [31 : 0] cpu_irq;
wire cpu_trap;
wire cpu_valid;
wire cpu_instr;
wire [ 3 : 0] cpu_wstrb;
/* verilator lint_off UNUSED */
+ wire [31 : 0] cpu_eoi;
wire [31 : 0] cpu_addr;
wire [31 : 0] cpu_wdata;
reg rom_cs;
- reg [11 : 0] rom_address;
+ reg [10 : 0] rom_address;
wire [31 : 0] rom_read_data;
wire rom_ready;
@@ -140,7 +144,7 @@ module application_fpga_sim (
reg fw_ram_cs;
reg [ 3 : 0] fw_ram_we;
- reg [ 8 : 0] fw_ram_address;
+ reg [ 9 : 0] fw_ram_address;
reg [31 : 0] fw_ram_write_data;
wire [31 : 0] fw_ram_read_data;
wire fw_ram_ready;
@@ -151,13 +155,18 @@ module application_fpga_sim (
wire [31 : 0] touch_sense_read_data;
wire touch_sense_ready;
+ reg irq31_cs;
+ reg irq31_we;
+ reg irq31_eoi;
+
reg tk1_cs;
reg tk1_we;
reg [ 7 : 0] tk1_address;
reg [31 : 0] tk1_write_data;
wire [31 : 0] tk1_read_data;
wire tk1_ready;
- wire system_mode;
+ wire app_mode;
+ wire fw_startup_done;
wire force_trap;
wire [14 : 0] ram_addr_rand;
wire [31 : 0] ram_data_rand;
@@ -177,12 +186,17 @@ module application_fpga_sim (
picorv32 #(
- .ENABLE_COUNTERS(0),
- .TWO_STAGE_SHIFT(0),
- .CATCH_MISALIGN (0),
- .COMPRESSED_ISA (1),
- .ENABLE_FAST_MUL(1),
- .BARREL_SHIFTER (1)
+ .ENABLE_COUNTERS (0),
+ .TWO_STAGE_SHIFT (0),
+ .CATCH_MISALIGN (0),
+ .COMPRESSED_ISA (1),
+ .ENABLE_FAST_MUL (1),
+ .BARREL_SHIFTER (1),
+ .ENABLE_IRQ (1),
+ .ENABLE_IRQ_QREGS(0),
+ .ENABLE_IRQ_TIMER(0),
+ .MASKED_IRQ (~IRQ31_IRQ_MASK),
+ .LATCHED_IRQ (IRQ31_IRQ_MASK)
) cpu (
.clk(clk),
.resetn(reset_n),
@@ -196,11 +210,12 @@ module application_fpga_sim (
.mem_rdata(muxed_rdata_reg),
.mem_instr(cpu_instr),
+ .irq(cpu_irq),
+ .eoi(cpu_eoi),
+
// Defined unused ports. Makes lint happy. But
// we still needs to help lint with empty ports.
/* verilator lint_off PINCONNECTEMPTY */
- .irq(32'h0),
- .eoi(),
.trace_valid(),
.trace_data(),
.mem_la_read(),
@@ -251,7 +266,7 @@ module application_fpga_sim (
.clk(clk),
.reset_n(reset_n),
- .system_mode(system_mode),
+ .app_mode(app_mode),
.cs(fw_ram_cs),
.we(fw_ram_we),
@@ -291,7 +306,7 @@ module application_fpga_sim (
.clk(clk),
.reset_n(reset_n),
- .system_mode(system_mode),
+ .en(~fw_startup_done),
.cs(uds_cs),
.address(uds_address),
@@ -308,7 +323,7 @@ module application_fpga_sim (
.txd(interface_rx),
.ch552_cts(interface_ch552_cts),
- .fpga_cts(interface_fpga_cts),
+ .fpga_cts (interface_fpga_cts),
.cs(uart_cs),
.we(uart_we),
@@ -339,7 +354,8 @@ module application_fpga_sim (
.clk(clk),
.reset_n(reset_n),
- .system_mode(system_mode),
+ .app_mode(app_mode),
+ .fw_startup_done(fw_startup_done),
.cpu_addr (cpu_addr),
.cpu_instr (cpu_instr),
@@ -366,6 +382,8 @@ module application_fpga_sim (
.gpio3(app_gpio3),
.gpio4(app_gpio4),
+ .syscall(irq31_eoi),
+
.cs(tk1_cs),
.we(tk1_we),
.address(tk1_address),
@@ -391,6 +409,20 @@ module application_fpga_sim (
end
+ //----------------------------------------------------------------
+ // irq_ctrl
+ // Interrupt logic
+ //----------------------------------------------------------------
+ always @* begin : irq_ctrl
+ reg irq31_set;
+
+ irq31_set = irq31_cs & irq31_we;
+ cpu_irq = {irq31_set, 31'h0};
+
+ irq31_eoi = cpu_eoi[31];
+ end
+
+
//----------------------------------------------------------------
// cpu_mem_ctrl
// CPU memory decode and control logic.
@@ -408,7 +440,7 @@ module application_fpga_sim (
muxed_rdata_new = 32'h0;
rom_cs = 1'h0;
- rom_address = cpu_addr[13 : 2];
+ rom_address = cpu_addr[12 : 2];
ram_cs = 1'h0;
ram_we = 4'h0;
@@ -417,7 +449,7 @@ module application_fpga_sim (
fw_ram_cs = 1'h0;
fw_ram_we = cpu_wstrb;
- fw_ram_address = cpu_addr[10 : 2];
+ fw_ram_address = cpu_addr[11 : 2];
fw_ram_write_data = cpu_wdata;
trng_cs = 1'h0;
@@ -442,6 +474,9 @@ module application_fpga_sim (
touch_sense_we = |cpu_wstrb;
touch_sense_address = cpu_addr[9 : 2];
+ irq31_cs = 1'h0;
+ irq31_we = |cpu_wstrb;
+
tk1_cs = 1'h0;
tk1_we = |cpu_wstrb;
tk1_address = cpu_addr[9 : 2];
@@ -534,6 +569,13 @@ module application_fpga_sim (
muxed_ready_new = fw_ram_ready;
end
+ SYSCALL_PREFIX: begin
+ `verbose($display("Access to syscall interrupt trigger");)
+ ascii_state = "Syscall IRQ trigger";
+ irq31_cs = 1'h1;
+ muxed_ready_new = 1'h1;
+ end
+
TK1_PREFIX: begin
`verbose($display("Access to TK1 core");)
ascii_state = "TK1 core";
diff --git a/hw/application_fpga/tkey-libs/LICENSE b/hw/application_fpga/tkey-libs/LICENSE
new file mode 100644
index 0000000..e673a06
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/LICENSE
@@ -0,0 +1,26 @@
+BSD 2-Clause License
+
+Copyright 2022 Tillitis AB
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/hw/application_fpga/tkey-libs/LICENSES/BSD-2-Clause.txt b/hw/application_fpga/tkey-libs/LICENSES/BSD-2-Clause.txt
new file mode 100644
index 0000000..99bc18f
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/LICENSES/BSD-2-Clause.txt
@@ -0,0 +1,24 @@
+Copyright 2022 Tillitis AB
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/hw/application_fpga/tkey-libs/LICENSES/CC0-1.0.txt b/hw/application_fpga/tkey-libs/LICENSES/CC0-1.0.txt
new file mode 100644
index 0000000..0e259d4
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/LICENSES/CC0-1.0.txt
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
diff --git a/hw/application_fpga/tkey-libs/Makefile b/hw/application_fpga/tkey-libs/Makefile
new file mode 100644
index 0000000..9dea0d9
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/Makefile
@@ -0,0 +1,101 @@
+OBJCOPY ?= llvm-objcopy
+
+CC = clang
+
+INCLUDE=include
+
+# Set QEMU_DEBUG and TKEY_DEBUG below when compiling tkey-libs if you
+# want debug prints from tkey-libs functions.
+#
+# - QEMU_DEBUG: the debug port on our qemu emulator
+#
+# - TKEY_DEBUG: The extra HID endpoint on a real TKey which you can
+# listen on for debug prints.
+#
+# NOTE WELL: If you just want debug prints on either of them in *your
+# own device app* you just need to include tkey/debug.h and define
+# either of them. You don't need to recompile tkey-libs.
+
+CFLAGS = -target riscv32-unknown-none-elf -march=rv32iczmmul -mabi=ilp32 \
+ -mcmodel=medany -static -std=gnu99 -Os -ffast-math -fno-common \
+ -fno-builtin-printf -fno-builtin-putchar -nostdlib -mno-relax -flto \
+ -Wall -Werror=implicit-function-declaration \
+ -I $(INCLUDE) -I .
+
+AS = clang
+AR = llvm-ar
+ASFLAGS = -target riscv32-unknown-none-elf -march=rv32iczmmul -mabi=ilp32 \
+ -mcmodel=medany -mno-relax
+
+LDFLAGS=-T app.lds -L libcommon/ -lcommon -L libcrt0/ -lcrt0
+
+
+.PHONY: all
+all: libcrt0.a libcommon.a libmonocypher.a libblake2s.a
+
+IMAGE=ghcr.io/tillitis/tkey-builder:4
+
+podman:
+ podman run --rm --mount type=bind,source=$(CURDIR),target=/src \
+ -w /src -it $(IMAGE) make -j
+
+.PHONY: check
+check:
+ clang-tidy -header-filter=.* -checks=cert-* libcommon/*.c -- $(CFLAGS)
+
+# C runtime library
+libcrt0.a: libcrt0/crt0.o
+ $(AR) -qc $@ libcrt0/crt0.o
+
+# Common C functions
+LIBOBJS=libcommon/assert.o libcommon/led.o libcommon/lib.o \
+ libcommon/proto.o libcommon/touch.o libcommon/io.o
+
+libcommon.a: $(LIBOBJS)
+ $(AR) -qc $@ $(LIBOBJS)
+$(LIBOBJS): include/tkey/assert.h include/tkey/led.h \
+ include/tkey/lib.h include/tkey/proto.h include/tkey/tk1_mem.h \
+ include/tkey/touch.h include/tkey/debug.h
+
+# Monocypher
+MONOOBJS=monocypher/monocypher.o monocypher/monocypher-ed25519.o
+libmonocypher.a: $(MONOOBJS)
+ $(AR) -qc $@ $(MONOOBJS)
+$MONOOBJS: monocypher/monocypher-ed25519.h monocypher/monocypher.h
+
+# blake2s
+B2OBJS=blake2s/blake2s.o
+libblake2s.a: $(B2OBJS)
+ $(AR) -qc $@ $(B2OBJS)
+$B2OBJS: blake2s/blake2s.h
+
+LIBS=libcrt0.a libcommon.a
+
+.PHONY: clean
+clean:
+ rm -f $(LIBS) $(LIBOBJS) libcrt0/crt0.o
+ rm -f libmonocypher.a $(MONOOBJS)
+ rm -f libblake2s.a $(B2OBJS)
+
+# Create compile_commands.json for clangd and LSP
+.PHONY: clangd
+clangd: compile_commands.json
+compile_commands.json:
+ $(MAKE) clean
+ bear -- make all
+
+# Uses ../.clang-format
+FMTFILES=include/tkey/*.h libcommon/*.c
+.PHONY: fmt
+fmt:
+ clang-format --dry-run --ferror-limit=0 $(FMTFILES)
+ clang-format --verbose -i $(FMTFILES)
+.PHONY: checkfmt
+checkfmt:
+ clang-format --dry-run --ferror-limit=0 --Werror $(FMTFILES)
+
+.PHONY: update-mem-include
+update-mem-include:
+ cp -af ../tillitis-key1/hw/application_fpga/fw/tk1_mem.h \
+ include/tkey/tk1_mem.h
+ echo "Remember to update header include guard!"
diff --git a/hw/application_fpga/tkey-libs/README-DIST.txt b/hw/application_fpga/tkey-libs/README-DIST.txt
new file mode 100644
index 0000000..868e890
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/README-DIST.txt
@@ -0,0 +1,33 @@
+tkey-libs binary distribution
+
+This is the binary distribution of:
+
+ https://github.com/tillitis/tkey-libs
+
+Which is an SDK for developing device apps for the Tillitis TKey in C.
+Please see the TKey Developer Handbook for more:
+
+ https://dev.tillitis.se/
+
+and the company web site:
+
+ https://tillitis.se/
+
+You should be able to use this distribution directly in device apps
+simply by pointing LIBDIR to where you unpacked this archive:
+
+ make LIBDIR=~/Download/tkey-libs
+
+Copyright Tillitis AB.
+
+These programs are free software: you can redistribute it and/or
+modify it under the terms of the BSD-2-Clause license.
+
+See LICENSE for the full BSD-2-Clause license text.
+
+Note that:
+
+- Monocypher is Copyright Loup Vaillant and released under CC0
+ 1.0 Universal, see monocypher/LICENSE.
+- blake2s is Copyright Markku-Juhani O. Saarinen and released under CC0
+ 1.0 Universal, see blake2s/LICENSE.
diff --git a/hw/application_fpga/tkey-libs/README.md b/hw/application_fpga/tkey-libs/README.md
new file mode 100644
index 0000000..722f4cd
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/README.md
@@ -0,0 +1,147 @@
+[](https://github.com/tillitis/tkey-libs/actions/workflows/ci.yaml)
+
+# Device libraries for the Tillitis TKey
+
+- C runtime: libcrt0.
+- Common C functions including protocol calls: libcommon.
+- Cryptographic functions: libmonocypher. Based on
+ [Monocypher](https://github.com/LoupVaillant/Monocypher) version
+ 4.0.2
+- BLAKE2s hash function: libblake2s.
+
+Release notes in [RELEASE.md](RELEASE.md).
+
+## Licenses
+
+Unless otherwise noted, the project sources are copyright Tillitis AB,
+licensed under the terms and conditions of the "BSD-2-Clause" license.
+See [LICENSE](LICENSE) for the full license text.
+
+Until Oct 8, 2024, the license was GPL-2.0 Only.
+
+External source code we have imported are isolated in their own
+directories. They may be released under other licenses. This is noted
+with a similar `LICENSE` file in every directory containing imported
+sources.
+
+Imported sources:
+
+- [Monocypher](https://github.com/LoupVaillant/Monocypher) (BSD-2) by
+ Loup Vaillant.
+
+- blake2s (CC-0), originally based on the reference implementation in
+ [RFC 7693](https://www.rfc-editor.org/rfc/rfc7693.html) written by
+ Markku-Juhani O. Saarinen ([original
+ repository](https://github.com/mjosaarinen/blake2_mjosref). Imported
+ from [Joachim Strömbergson's
+ fork](https://github.com/secworks/blake2s/) used as a model for a
+ hardware implementation.
+
+### SPDX tags
+
+The project uses single-line references to Unique License Identifiers
+as defined by the Linux Foundation's [SPDX project](https://spdx.org/)
+on its own source files, but not necessarily imported files. The line
+in each individual source file identifies the license applicable to
+that file.
+
+The current set of valid, predefined SPDX identifiers can be found on
+the SPDX License List at:
+
+https://spdx.org/licenses/
+
+We attempt to follow the [REUSE
+specification](https://reuse.software/).
+
+## Hardware support
+
+### Bellatrix and earlier
+
+Please note that:
+
+- For reading, only use the blocking `uart_read()`.
+
+- Only `IO_UART` and `IO_QEMU` destinations are useful for writing as
+ in `write(IO_UART, ...)`, `puts(IO_UART, ...)`, and so on.
+
+- Defining `QEMU_DEBUG` works with all the `debug_*` functions, but
+ `TKEY_DEBUG` does not.
+
+## Building
+
+In order to build, you must have the `make`, `clang`, `llvm`, and
+`lld` packages installed.
+
+Version 15 or higher of LLVM/Clang is necessary for the RV32IC\_Zmmul
+architecture we are using. For more detailed information on the
+supported build and development environment, please refer to the
+[Developer Handbook](https://dev.tillitis.se/).
+## Building using Podman
+
+You can also build the libraries with our OCI image
+`ghcr.io/tillitis/tkey-builder`.
+
+The easiest way to build this is if you have `make` installed:
+
+```
+make podman
+```
+
+You can also specify a different image by using
+`IMAGE=localhost/tkey-builder-local`.
+
+Or use Podman directly:
+
+```
+podman run --rm --mount type=bind,source=.,target=/src -w /src -it ghcr.io/tillitis/tkey-builder:4 make -j
+```
+
+## Minimal application build
+
+You will typically need to link at least the `libcrt0` C runtime
+otherwise your program won't even reach `main()`.
+
+We provide a linker script in `apps.lds` which shows the linker the
+memory layout.
+
+Minimal compilation would look something like:
+
+```
+clang -target riscv32-unknown-none-elf -march=rv32iczmmul -mabi=ilp32 \
+ -mcmodel=medany -static -std=gnu99 -O2 -ffast-math -fno-common \
+ -fno-builtin-printf -fno-builtin-putchar -nostdlib -mno-relax -flto \
+ -Wall -Werror=implicit-function-declaration \
+ -I ../tkey-libs/include \
+ -I ../tkey-libs -c -o foo.o foo.c
+
+clang -target riscv32-unknown-none-elf -march=rv32iczmmul -mabi=ilp32 \
+ -mcmodel=medany -static -ffast-math -fno-common -nostdlib \
+ -T ../tkey-libs/app.lds \
+ -L ../tkey-libs -lcrt0 \
+ -I ../tkey-libs -o foo.elf foo.o
+
+```
+
+## Makefile example
+
+See `example-app/Makefile` for an example Makefile for a simple device
+application.
+
+## Debug output
+
+If you want to have debug prints in your program you can use the
+`debug_putchar()`, `debug_puts()`, `debug_putinthex()`,
+`debug_hexdump()` and friends. See `include/tkey/debug.h` for list of
+functions.
+
+These functions will be turned on if you define either of these when
+compiling your program and linking with `libcommon`:
+
+- `QEMU_DEBUG`: Uses the special debug port only available in qemu to
+ print to the qemu console.
+- `TKEY_DEBUG`: Uses the extra HID device.
+
+Note that if you use `TKEY_DEBUG` you *must* have something listening
+on the corresponding HID device. It's usually the last HID device
+created. On Linux, for instance, this means the last reported hidraw
+in `dmesg` is the one you should do `cat /dev/hidrawX` on.
diff --git a/hw/application_fpga/tkey-libs/RELEASE.md b/hw/application_fpga/tkey-libs/RELEASE.md
new file mode 100644
index 0000000..347e2dc
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/RELEASE.md
@@ -0,0 +1,191 @@
+# Release notes
+
+## Upcoming release
+
+- NOTE WELL! Rewritten I/O functions with new signatures and
+ semantics!
+- `blake2s()` with new signature.
+
+### BLAKE2s hash function
+
+The `blake2s()` function no longer call the firmware.
+
+- The `blake2s.h` header file has moved to `blake2s/blake2s.h`.
+
+- The `blake2s()` hash function has changed signature. It's now defined
+ as:
+
+ ```
+ // All-in-one convenience function.
+ int blake2s(void *out, size_t outlen, // return buffer for digest
+ const void *key, size_t keylen, // optional secret key
+ const void *in, size_t inlen); // data to be hashed
+
+ ```
+
+- The component functions `blake2s_init()`, `blake2s_update()`, and
+ `blake2s_final()` are now available.
+
+### I/O
+
+The Castor TKey hardware supports more USB endpoints:
+
+- CDC - the same thing as older versions.
+- FIDO security token, for FIDO-like apps.
+- CCID, smart card interface.
+- DEBUG, a HID debug port.
+
+The communication is still over a single UART. To differ between the
+endpoints we use an internal USB Mode Protocol between programs
+running on the PicoRV32 and the CH552 USB Controller.
+
+The I/O functions has changed accordingly. Please use:
+
+- `readselect()` with appropriate bitmask (e.g. `IO_CDC|IO_FIDO`) to
+ see if there's anything to read in the endpoints you are interested
+ in. Data from endpoints not mentioned in the bitmask will be
+ discarded.
+
+- `read()` is now non-blocking and returns the number of bytes read
+ from the endpoint you specify, because more might not be available
+ yet.
+
+- `write()` now takes an endpoint destination.
+
+- We also introduce generic `putchar()`, `puts()`, `puthex()`,
+ `putinthex()`, and `hexdump()` functions that take a destination
+ argument.
+
+We recommend you use only these functions for I/O on Castor and going
+forward.
+
+For compatibility to develop device apps for the Bellatrix platform
+and earlier, use the low-level, blocking function `uart_read()` for
+reads and *only* the `IO_UART` and `IO_QEMU` destinations for output
+functions like `write()`, `puts()`.
+
+### Debug prints
+
+The optionally built debug prints have changed. You now use
+`debug_puts()` et cetera instead of `qemu_*()`.
+
+You define the debug output endpoint when you compile your program by
+including `debug.h` and defining `QEMU_DEBUG` for the qemu debug port
+or `TKEY_DEBUG` for output on the DEBUG HID endpoint. If you don't
+define either, they won't appear in your code.
+
+Similiarly, `assert()` now also follows `QEMU_DEBUG` or `TKEY_DEBUG`,
+and prints something on either before halting the CPU.
+
+Note that on the Bellatrix platform only `QEMU_DEBUG` works.
+
+## v0.1.2
+
+From now on tkey-libs is licensed under the BSD-2-Clause license,
+moving from the previous GPLv2-only.
+
+Note: There is a possibility that this update may impact the generated
+CDI for an app that relies on this library. It is recommended to
+always check for potential CDI changes for each specific app with
+every update. If the generated CDI does change, and if applicable, it
+should be clearly communicated to end users to prevent unintentional
+changes to their identity.
+
+Changes:
+- New license, BSD-2-Clause
+- Reuse compliant, see https://reuse.software/
+- Fix row alignment in qemu_hexdump
+- Update memory map, tk1_mem.h, from canonical tillitis-key1 repo
+- Added make target for creating compile_commands.json for clangd
+- Added missing include in touch.h
+
+Full changelog:
+[v0.1.1...v0.1.2](https://github.com/tillitis/tkey-libs/compare/v0.1.1...v0.1.2)
+
+## v0.1.1
+
+This is a minor release correcting a mistake and syncing with the
+latest HW release, TK1-24.03.
+
+
+Note: There is a possibility that this update may impact the generated
+CDI for an app that relies on this library. It is recommended to
+always check for potential CDI changes for each specific app with
+every update. If the generated CDI does change, and if applicable, it
+should be clearly communicated to end users to prevent unintentional
+changes to their identity.
+
+Changes:
+- Update memory map, tk1_mem.h, to match the latest TK1-24.03 release.
+- Default to tkey-builder:4 for the podman target
+- Default to have QEMU debug enabled in tkey-libs. Mistakenly removed
+ in previous release.
+- Revise readme accordingly
+
+Full changelog:
+[v0.1.0...v0.1.1](https://github.com/tillitis/tkey-libs/compare/v0.1.0...v0.1.1)
+
+## v0.1.0
+
+This release contains some changes that forces applications that use
+tkey-libs to be updated to work with this release.
+
+Note: It is highly likely that this update will affect the CDI of the
+TKey. It is advised to always verify this for each specific app, for
+every update. If the CDI changes, and it is applicable, it should be
+stated clearly to end users to avoid unknowingly changing the TKey
+identity.
+
+Breaking changes:
+- Check destination buffer's size for read(). To prevent writing
+ outside of destination buffer.
+- Renaming LED-functions to follow led_*().
+
+Changes:
+- New function, secure_wipe(), to clean memory of secret data.
+- New function, touch_wait(). Waits for a touch by the user, with
+ selectable timeout.
+- New function, led_get(). Get the value of the applied LED color.
+- Upgraded Monocypher to 4.0.2.
+- Add variable AR in Makefile to enabling passing llvm-ar from command
+ line.
+- Update example app to use led.h.
+- Don't have QEMU debug enabled by default.
+- Minor tweaks and formatting.
+
+Full changelog:
+[v0.0.2...v0.1.0](https://github.com/tillitis/tkey-libs/compare/v0.0.2...v0.1.0)
+
+## v0.0.2
+
+This release contains some changes that forces applications that use
+tkey-libs to be updated to work with this release.
+
+Breaking changes:
+- Introducing include hierarchy to make it less generic, e.g.,
+ `#include `.
+- Use stdint.h/stddef.h infavor of types.h.
+- Library .a files built on top level to simplify inclusion.
+- Upgraded Monocypher to 4.0.1.
+- QEMU debug behaviour changed, instead of defining `NODEBUG` to
+ disable debug, one has to enable it by defining `QEMU_DEBUG`.
+
+Changes:
+- Introduce functions to control the LED, led.h and led.c.
+- New function, assert() to make an illegal instruction and forcing
+ the CPU to halt.
+- Add functions memcpy_s(), wordcpy_s(), memeq() from firmware
+- Adding `const` to MMIO variables and qemu_* functions.
+- Minor tweaks, clean up and bugfixes.
+
+Full changelog:
+[v0.0.1...v0.0.2](https://github.com/tillitis/tkey-libs/compare/v0.0.1...v0.0.2)
+
+
+## v0.0.1
+
+Just ripped from
+
+https://github.com/tillitis/tillitis-key1-apps
+
+No semantic changes.
diff --git a/hw/application_fpga/tkey-libs/REUSE.toml b/hw/application_fpga/tkey-libs/REUSE.toml
new file mode 100644
index 0000000..38daa65
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/REUSE.toml
@@ -0,0 +1,39 @@
+# SPDX-FileCopyrightText: 2024 Tillitis AB
+# SPDX-License-Identifier: BSD-2-Clause
+version = 1
+
+[[annotations]]
+path = ".github/workflows/*"
+SPDX-FileCopyrightText = "2022 Tillitis AB "
+SPDX-License-Identifier = "BSD-2-Clause"
+
+[[annotations]]
+path = [
+ ".clang-format",
+ ".editorconfig",
+ ".gitignore",
+ "example-app/Makefile",
+ "monocypher/README.md",
+ "Makefile",
+ "README-DIST.txt",
+ "README.md",
+ "RELEASE.md"
+]
+SPDX-FileCopyrightText = "2022 Tillitis AB "
+SPDX-License-Identifier = "BSD-2-Clause"
+
+[[annotations]]
+path = [
+ "blake2s/*",
+]
+
+SPDX-FileCopyrightText = "Markku-Juhani O. Saarinen"
+SPDX-License-Identifier = "CC0-1.0"
+
+[[annotations]]
+path = [
+ "blake2s/Makefile",
+]
+
+SPDX-FileCopyrightText = "2014 Secworks Sweden AB"
+SPDX-License-Identifier = "BSD-2-Clause"
diff --git a/hw/application_fpga/tkey-libs/app.lds b/hw/application_fpga/tkey-libs/app.lds
new file mode 100644
index 0000000..421122c
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/app.lds
@@ -0,0 +1,64 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Tillitis AB
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+OUTPUT_ARCH( "riscv" )
+ENTRY(_start)
+
+MEMORY
+{
+ RAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x20000 /* 128 KB */
+}
+
+SECTIONS
+{
+ .text.init :
+ {
+ *(.text.init)
+ } >RAM
+
+ .text :
+ {
+ . = ALIGN(4);
+ *(.text) /* .text sections (code) */
+ *(.text*) /* .text* sections (code) */
+ *(.rodata) /* .rodata sections (constants, strings, etc.) */
+ *(.rodata*) /* .rodata* sections (constants, strings, etc.) */
+ *(.srodata) /* .rodata sections (constants, strings, etc.) */
+ *(.srodata*) /* .rodata* sections (constants, strings, etc.) */
+ . = ALIGN(4);
+ _etext = .;
+ _sidata = _etext;
+ } >RAM
+
+ .data : AT (_etext)
+ {
+ . = ALIGN(4);
+ _sdata = .;
+ . = ALIGN(4);
+ *(.data) /* .data sections */
+ *(.data*) /* .data* sections */
+ *(.sdata) /* .sdata sections */
+ *(.sdata*) /* .sdata* sections */
+ . = ALIGN(4);
+ _edata = .;
+ } >RAM
+
+ /* Uninitialized data section */
+ .bss :
+ {
+ . = ALIGN(4);
+ _sbss = .;
+ *(.bss)
+ *(.bss*)
+ *(.sbss)
+ *(.sbss*)
+ *(COMMON)
+
+ . = ALIGN(4);
+ _ebss = .;
+ } >RAM
+
+ /* libcrt0/crt0.S inits stack to start just below end of RAM */
+}
diff --git a/hw/application_fpga/fw/tk1/blake2s/LICENSE b/hw/application_fpga/tkey-libs/blake2s/LICENSE
similarity index 100%
rename from hw/application_fpga/fw/tk1/blake2s/LICENSE
rename to hw/application_fpga/tkey-libs/blake2s/LICENSE
diff --git a/hw/application_fpga/tkey-libs/blake2s/Makefile b/hw/application_fpga/tkey-libs/blake2s/Makefile
new file mode 100644
index 0000000..b5bdfd0
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/blake2s/Makefile
@@ -0,0 +1,52 @@
+#===================================================================
+#
+# Makefile
+# --------
+# Makefile for building the blake2s model.
+#
+#
+# Author: Joachim Strombergson
+# Copyright (c) 2014, Secworks Sweden AB
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+#===================================================================
+
+SRC = blake2s_test.c blake2s.c
+INC = blake2s.h
+
+CC = clang
+CC_FLAGS = -Wall
+
+blake2s_test: $(SRC) $(INC)
+ $(CC) $(CC_FLAGS) -o $@ $(SRC) -I $(INC)
+
+
+clean:
+ rm -f ./blake2s_test
+ rm -f *.log
+ rm -f *.txt
diff --git a/hw/application_fpga/fw/tk1/blake2s/blake2s.c b/hw/application_fpga/tkey-libs/blake2s/blake2s.c
similarity index 91%
rename from hw/application_fpga/fw/tk1/blake2s/blake2s.c
rename to hw/application_fpga/tkey-libs/blake2s/blake2s.c
index b46ad8a..6f67dad 100644
--- a/hw/application_fpga/fw/tk1/blake2s/blake2s.c
+++ b/hw/application_fpga/tkey-libs/blake2s/blake2s.c
@@ -3,22 +3,22 @@
// blake2s.c
// ---------
//
-// A simple blake2s Reference Implementation.
+// A simple BLAKE2s reference implementation.
+//
+// See LICENSE for license terms.
+// See README.md in the repo root for info about source code origin.
//======================================================================
-#include "../types.h"
-#include "../lib.h"
+#include
#include "blake2s.h"
-// Dummy printf() for verbose mode
-static void printf(const char *format, ...)
-{
-}
-
#define VERBOSE 0
#define SHOW_V 0
#define SHOW_M_WORDS 0
+#if VERBOSE || SHOW_V || SHOW_M_WORDS
+#include
+#endif
// Cyclic right rotation.
#ifndef ROTR32
@@ -41,6 +41,7 @@ static const uint32_t blake2s_iv[8] = {
};
+#if VERBOSE || SHOW_V
//------------------------------------------------------------------
//------------------------------------------------------------------
void print_v(uint32_t *v) {
@@ -71,24 +72,25 @@ void print_ctx(blake2s_ctx *ctx) {
printf("\n");
}
+#endif
//------------------------------------------------------------------
// B2S_G macro redefined as a G function.
// Allows us to output intermediate values for debugging.
//------------------------------------------------------------------
void G(uint32_t *v, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t y) {
- if (VERBOSE) {
+#if VERBOSE
printf("G started.\n");
- }
+#endif
- if (SHOW_V) {
+#if SHOW_V
printf("v before processing:\n");
print_v(&v[0]);
- }
+#endif
- if (SHOW_M_WORDS) {
+#if SHOW_M_WORDS
printf("x: 0x%08x, y: 0x%08x\n", x, y);
- }
+#endif
v[a] = v[a] + v[b] + x;
v[d] = ROTR32(v[d] ^ v[a], 16);
@@ -99,14 +101,14 @@ void G(uint32_t *v, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t x,
v[c] = v[c] + v[d];
v[b] = ROTR32(v[b] ^ v[c], 7);
- if (SHOW_V) {
+#if SHOW_V
printf("v after processing:\n");
print_v(&v[0]);
- }
+#endif
- if (VERBOSE) {
+#if VERBOSE
printf("G completed.\n\n");
- }
+#endif
}
@@ -131,9 +133,9 @@ static void blake2s_compress(blake2s_ctx *ctx, int last)
int i;
uint32_t v[16], m[16];
- if (VERBOSE) {
+#if VERBOSE
printf("blake2s_compress started.\n");
- }
+#endif
// init work variables
for (i = 0; i < 8; i++) {
@@ -143,9 +145,9 @@ static void blake2s_compress(blake2s_ctx *ctx, int last)
// low 32 bits of offset
// high 32 bits
- if (VERBOSE) {
+#if VERBOSE
printf("t[0]: 0x%08x, t[1]: 0x%08x\n", ctx->t[0], ctx->t[1]);
- }
+#endif
v[12] ^= ctx->t[0];
v[13] ^= ctx->t[1];
@@ -159,52 +161,52 @@ static void blake2s_compress(blake2s_ctx *ctx, int last)
m[i] = B2S_GET32(&ctx->b[4 * i]);
}
- if (VERBOSE) {
+#if VERBOSE
printf("v before G processing:\n");
print_v(&v[0]);
- }
+#endif
// Ten rounds of the G function applied on rows, diagonal.
for (i = 0; i < 10; i++) {
- if (VERBOSE) {
+#if VERBOSE
printf("Round %02d:\n", (i + 1));
printf("Row processing started.\n");
- }
+#endif
G(&v[0], 0, 4, 8, 12, m[sigma[i][ 0]], m[sigma[i][ 1]]);
G(&v[0], 1, 5, 9, 13, m[sigma[i][ 2]], m[sigma[i][ 3]]);
G(&v[0], 2, 6, 10, 14, m[sigma[i][ 4]], m[sigma[i][ 5]]);
G(&v[0], 3, 7, 11, 15, m[sigma[i][ 6]], m[sigma[i][ 7]]);
- if (VERBOSE) {
+#if VERBOSE
printf("Row processing completed.\n");
printf("Diagonal processing started.\n");
- }
+#endif
G(&v[0], 0, 5, 10, 15, m[sigma[i][ 8]], m[sigma[i][ 9]]);
G(&v[0], 1, 6, 11, 12, m[sigma[i][10]], m[sigma[i][11]]);
G(&v[0], 2, 7, 8, 13, m[sigma[i][12]], m[sigma[i][13]]);
G(&v[0], 3, 4, 9, 14, m[sigma[i][14]], m[sigma[i][15]]);
- if (VERBOSE) {
+#if VERBOSE
printf("Diagonal processing completed.\n");
printf("\n");
- }
+#endif
}
- if (VERBOSE) {
+#if VERBOSE
printf("v after G processing:\n");
print_v(&v[0]);
- }
+#endif
// Update the hash state.
for (i = 0; i < 8; ++i) {
ctx->h[i] ^= v[i] ^ v[i + 8];
}
- if (VERBOSE) {
+#if VERBOSE
printf("blake2s_compress completed.\n");
- }
+#endif
}
@@ -218,11 +220,11 @@ int blake2s_init(blake2s_ctx *ctx, size_t outlen,
{
size_t i;
- if (VERBOSE) {
+#if VERBOSE
printf("blake2s_init started.\n");
printf("Context before blake2s_init processing:\n");
print_ctx(ctx);
- }
+#endif
if (outlen == 0 || outlen > 32 || keylen > 32)
return -1; // illegal parameters
@@ -243,11 +245,11 @@ int blake2s_init(blake2s_ctx *ctx, size_t outlen,
ctx->c = 64; // at the end
}
- if (VERBOSE) {
+#if VERBOSE
printf("Context after blake2s_init processing:\n");
print_ctx(ctx);
printf("blake2s_init completed.\n");
- }
+#endif
return 0;
}
@@ -261,11 +263,11 @@ void blake2s_update(blake2s_ctx *ctx,
{
size_t i;
- if (VERBOSE) {
+#if VERBOSE
printf("blake2s_update started.\n");
printf("Context before blake2s_update processing:\n");
print_ctx(ctx);
- }
+#endif
for (i = 0; i < inlen; i++) {
if (ctx->c == 64) { // buffer full ?
@@ -278,11 +280,11 @@ void blake2s_update(blake2s_ctx *ctx,
ctx->b[ctx->c++] = ((const uint8_t *) in)[i];
}
- if (VERBOSE) {
+#if VERBOSE
printf("Context after blake2s_update processing:\n");
print_ctx(ctx);
printf("blake2s_update completed.\n");
- }
+#endif
}
@@ -294,11 +296,11 @@ void blake2s_final(blake2s_ctx *ctx, void *out)
{
size_t i;
- if (VERBOSE) {
+#if VERBOSE
printf("blake2s_final started.\n");
printf("Context before blake2s_final processing:\n");
print_ctx(ctx);
- }
+#endif
ctx->t[0] += ctx->c; // mark last block offset
@@ -321,11 +323,11 @@ void blake2s_final(blake2s_ctx *ctx, void *out)
(ctx->h[i >> 2] >> (8 * (i & 3))) & 0xFF;
}
- if (VERBOSE) {
+#if VERBOSE
printf("Context after blake2s_final processing:\n");
print_ctx(ctx);
printf("blake2s_final completed.\n");
- }
+#endif
}
@@ -334,15 +336,16 @@ void blake2s_final(blake2s_ctx *ctx, void *out)
//------------------------------------------------------------------
int blake2s(void *out, size_t outlen,
const void *key, size_t keylen,
- const void *in, size_t inlen,
- blake2s_ctx *ctx)
+ const void *in, size_t inlen)
{
- if (blake2s_init(ctx, outlen, key, keylen))
+ blake2s_ctx ctx;
+
+ if (blake2s_init(&ctx, outlen, key, keylen))
return -1;
- blake2s_update(ctx, in, inlen);
+ blake2s_update(&ctx, in, inlen);
- blake2s_final(ctx, out);
+ blake2s_final(&ctx, out);
return 0;
}
diff --git a/hw/application_fpga/fw/tk1/blake2s/blake2s.h b/hw/application_fpga/tkey-libs/blake2s/blake2s.h
similarity index 76%
rename from hw/application_fpga/fw/tk1/blake2s/blake2s.h
rename to hw/application_fpga/tkey-libs/blake2s/blake2s.h
index 3156669..888fc20 100644
--- a/hw/application_fpga/fw/tk1/blake2s/blake2s.h
+++ b/hw/application_fpga/tkey-libs/blake2s/blake2s.h
@@ -1,10 +1,18 @@
+//======================================================================
+//
// blake2s.h
+// ---------
// BLAKE2s Hashing Context and API Prototypes
+//
+// See LICENSE for license terms.
+// See README.md in the repo root for info about source code origin.
+//======================================================================
#ifndef BLAKE2S_H
#define BLAKE2S_H
-#include "../types.h"
+#include
+#include
// state context
typedef struct {
@@ -32,8 +40,6 @@ void blake2s_final(blake2s_ctx *ctx, void *out);
// All-in-one convenience function.
int blake2s(void *out, size_t outlen, // return buffer for digest
const void *key, size_t keylen, // optional secret key
- const void *in, size_t inlen, // data to be hashed
- blake2s_ctx *ctx);
+ const void *in, size_t inlen); // data to be hashed
#endif
-
diff --git a/hw/application_fpga/tkey-libs/blake2s/blake2s_test.c b/hw/application_fpga/tkey-libs/blake2s/blake2s_test.c
new file mode 100644
index 0000000..d735a82
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/blake2s/blake2s_test.c
@@ -0,0 +1,138 @@
+//======================================================================
+//
+// blake2s_test.c
+// --------------
+//
+//======================================================================
+
+#include
+#include "blake2s.h"
+
+
+//------------------------------------------------------------------
+//------------------------------------------------------------------
+void print_message(uint8_t *m, int mlen) {
+ printf("The message:\n");
+ for (int i = 1 ; i <= mlen ; i++) {
+ printf("0x%02x ", m[(i - 1)]);
+ if (i % 8 == 0) {
+ printf("\n");
+ }
+ }
+ printf("\n");
+}
+
+
+//------------------------------------------------------------------
+//------------------------------------------------------------------
+void print_digest(uint8_t *md) {
+ printf("The digest:\n");
+ for (int j = 0 ; j < 4 ; j++) {
+ for (int i = 0 ; i < 8 ; i++) {
+ printf("0x%02x ", md[i + 8 * j]);
+ }
+ printf("\n");
+ }
+ printf("\n");
+}
+
+
+//------------------------------------------------------------------
+// test_zero_length()
+// Test with a zero length mwssage.
+//------------------------------------------------------------------
+void test_zero_length() {
+
+ uint8_t md[32];
+
+ printf("Testing zero byte message.\n");
+ blake2s(md, 32, NULL, 0, NULL, 0);
+ print_digest(md);
+ printf("\n");
+}
+
+
+//------------------------------------------------------------------
+// test_abc_message()
+// Test with a zero length mwssage.
+//------------------------------------------------------------------
+void test_abc_message() {
+
+ uint8_t md[32];
+ uint8_t msg[64] = {'a', 'b', 'c'};
+
+ printf("Testing with RFC 7693 three byte 'abc' message.\n");
+ print_message(msg, 3);
+
+ blake2s(md, 32, NULL, 0, msg, 3);
+ print_digest(md);
+ printf("\n");
+}
+
+
+//------------------------------------------------------------------
+// test_one_block_message()
+// Test with a 64 byte message, filling one block.
+//------------------------------------------------------------------
+void test_one_block_message() {
+
+ uint8_t md[32];
+ uint8_t msg[64];
+
+ for (uint8_t i = 0 ; i < 64 ; i++) {
+ msg[i] = i;
+ }
+
+ printf("Testing with 64 byte message.\n");
+ print_message(msg, 64);
+
+ blake2s(md, 32, NULL, 0, msg, 64);
+ print_digest(md);
+ printf("\n");
+}
+
+
+//------------------------------------------------------------------
+// test_one_block_one_byte_message()
+// Test with a 65 byte message, filling one block and a single
+// byte in the next block.
+//------------------------------------------------------------------
+void test_one_block_one_byte_message() {
+
+ uint8_t md[32];
+ uint8_t msg[65];
+
+ for (uint8_t i = 0 ; i < 65 ; i++) {
+ msg[i] = i;
+ }
+
+ printf("Testing with 65 byte message.\n");
+ print_message(msg, 65);
+
+ blake2s(md, 32, NULL, 0, msg, 65);
+ print_digest(md);
+ printf("\n");
+}
+
+
+//------------------------------------------------------------------
+//------------------------------------------------------------------
+int main(void) {
+ printf("\n");
+ printf("BLAKE2s reference model started. Performing a set of tests..\n");
+ printf("Performing a set of tests.\n");
+
+ test_zero_length();
+ test_abc_message();
+ test_one_block_message();
+ test_one_block_one_byte_message();
+
+ printf("BLAKE2s reference model completed.\n");
+ printf("\n");
+
+ return 0;
+}
+
+//======================================================================
+/// EOF blake2s_test.c
+//======================================================================
diff --git a/hw/application_fpga/tkey-libs/example-app/Makefile b/hw/application_fpga/tkey-libs/example-app/Makefile
new file mode 100644
index 0000000..fc13395
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/example-app/Makefile
@@ -0,0 +1,33 @@
+P := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
+LIBDIR ?= $(P)/../
+OBJCOPY ?= llvm-objcopy
+CC = clang
+
+# If you want debug_puts() etcetera to output something on our QEMU
+# debug port, use -DQEMU_DEBUG below, or -DTKEY_DEBUG to use Tkeys USB debug pipe
+CFLAGS = -g -target riscv32-unknown-none-elf -march=rv32iczmmul -mabi=ilp32 -mcmodel=medany \
+ -static -std=gnu99 -O2 -ffast-math -fno-common -fno-builtin-printf \
+ -fno-builtin-putchar -nostdlib -mno-relax -flto \
+ -Wall -Werror=implicit-function-declaration \
+ -I $(LIBDIR)/include -I $(LIBDIR)
+ # -DQEMU_DEBUG -DTKEY_DEBUG
+
+INCLUDE=$(LIBDIR)/include
+
+LDFLAGS=-T $(LIBDIR)/app.lds -L $(LIBDIR) -lcommon -lcrt0
+
+.PHONY: all
+all: blue.bin
+
+# Turn elf into bin for device
+%.bin: %.elf
+ $(OBJCOPY) --input-target=elf32-littleriscv --output-target=binary $^ $@
+ chmod a-x $@
+
+BLUEOBJS=blue.o
+blue.elf: blue.o
+ $(CC) $(CFLAGS) $(BLUEOBJS) $(LDFLAGS) -I $(LIBDIR) -o $@
+
+.PHONY: clean
+clean:
+ rm -f blue.bin blue.elf $(BLUEOBJS)
diff --git a/hw/application_fpga/tkey-libs/example-app/blue.c b/hw/application_fpga/tkey-libs/example-app/blue.c
new file mode 100644
index 0000000..0dd3e0f
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/example-app/blue.c
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: 2023 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include
+#include
+#include
+#include
+
+#define SLEEPTIME 100000
+
+void sleep(uint32_t n)
+{
+ for (volatile int i = 0; i < n; i++);
+}
+
+int main(void)
+{
+ debug_puts("Hello, world!\n");
+ debug_puts("Going to sleep between blinks: ");
+ debug_putinthex(SLEEPTIME);
+ debug_lf();
+
+ for (;;) {
+ led_set(LED_RED);
+ sleep(SLEEPTIME);
+ led_set(LED_GREEN);
+ sleep(SLEEPTIME);
+ led_set(LED_BLUE);
+ sleep(SLEEPTIME);
+ }
+}
diff --git a/hw/application_fpga/tkey-libs/include/tkey/assert.h b/hw/application_fpga/tkey-libs/include/tkey/assert.h
new file mode 100644
index 0000000..3039123
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/include/tkey/assert.h
@@ -0,0 +1,28 @@
+// SPDX-FileCopyrightText: 2022 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#ifndef TKEY_ASSERT_H
+#define TKEY_ASSERT_H
+
+#include
+
+#if defined(QEMU_DEBUG)
+#define assert(expr) \
+ ((expr) ? (void)(0) \
+ : assert_fail(IO_QEMU, #expr, __FILE__, __LINE__, __func__))
+
+#elif defined(TKEY_DEBUG)
+
+#define assert(expr) \
+ ((expr) ? (void)(0) \
+ : assert_fail(IO_DEBUG, #expr, __FILE__, __LINE__, __func__))
+
+#else
+
+#define assert(expr) ((expr) ? (void)(0) : assert_halt())
+#endif
+
+void assert_fail(enum ioend dest, const char *assertion, const char *file,
+ unsigned int line, const char *function);
+void assert_halt(void);
+#endif
diff --git a/hw/application_fpga/tkey-libs/include/tkey/debug.h b/hw/application_fpga/tkey-libs/include/tkey/debug.h
new file mode 100644
index 0000000..11e530c
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/include/tkey/debug.h
@@ -0,0 +1,39 @@
+// SPDX-FileCopyrightText: 2023 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#ifndef TKEY_DEBUG_H
+#define TKEY_DEBUG_H
+
+#include
+
+#include "io.h"
+
+#if defined(QEMU_DEBUG)
+#define debug_putchar(ch) putchar(IO_QEMU, ch)
+#define debug_lf() putchar(IO_QEMU, '\n')
+#define debug_putinthex(ch) putinthex(IO_QEMU, ch)
+#define debug_puts(s) puts(IO_QEMU, s)
+#define debug_puthex(ch) puthex(IO_QEMU, ch)
+#define debug_hexdump(buf, len) hexdump(IO_QEMU, buf, len)
+
+#elif defined(TKEY_DEBUG)
+
+#define debug_putchar(ch) putchar(IO_DEBUG, ch)
+#define debug_lf() putchar(IO_DEBUG, '\n')
+#define debug_putinthex(ch) putinthex(IO_DEBUG, ch)
+#define debug_puts(s) puts(IO_DEBUG, s)
+#define debug_puthex(ch) puthex(IO_DEBUG, ch)
+#define debug_hexdump(buf, len) hexdump(IO_DEBUG, buf, len)
+
+#else
+
+#define debug_putchar(ch)
+#define debug_lf()
+#define debug_putinthex(n)
+#define debug_puts(s)
+#define debug_puthex(ch)
+#define debug_hexdump(buf, len)
+
+#endif
+
+#endif
diff --git a/hw/application_fpga/tkey-libs/include/tkey/io.h b/hw/application_fpga/tkey-libs/include/tkey/io.h
new file mode 100644
index 0000000..60c5f60
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/include/tkey/io.h
@@ -0,0 +1,42 @@
+// SPDX-FileCopyrightText: 2025 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include
+#include
+
+#ifndef TKEY_IO_H
+#define TKEY_IO_H
+
+// I/O endpoints. Keep it as bits possible to use in a bitmask in
+// readselect().
+//
+// Note that the values for IO_CH552, IO_CDC, IO_FIDO, IO_CCID and IO_DEBUG
+// should be kept the same in the code for the CH552 side.
+enum ioend {
+ IO_NONE = 0x00, // No endpoint
+ IO_UART = 0x01, // Only destination, raw UART access
+ IO_QEMU = 0x02, // Only destination, QEMU debug port
+ IO_CH552 = 0x04, // Internal CH552 control port
+ IO_CDC = 0x08, // CDC "serial" port
+ IO_FIDO = 0x10, // FIDO security token port
+ IO_CCID = 0x20, // CCID "smart card" port
+ IO_DEBUG = 0x40, // Debug port over USB HID
+};
+
+enum ch552cmd {
+ SET_ENDPOINTS = 0x01, // Config USB endpoints on the CH552
+ CH552_CMD_MAX,
+};
+
+void write(enum ioend dest, const uint8_t *buf, size_t nbytes);
+int read(enum ioend src, uint8_t *buf, size_t bufsize, size_t nbytes);
+int uart_read(uint8_t *buf, size_t bufsize, size_t nbytes);
+int readselect(int bitmask, enum ioend *endpoint, uint8_t *len);
+void putchar(enum ioend dest, const uint8_t ch);
+void puthex(enum ioend dest, const uint8_t ch);
+void putinthex(enum ioend dest, const uint32_t n);
+void puts(enum ioend dest, const char *s);
+void hexdump(enum ioend dest, void *buf, int len);
+void config_endpoints(enum ioend endpoints);
+
+#endif
diff --git a/hw/application_fpga/tkey-libs/include/tkey/led.h b/hw/application_fpga/tkey-libs/include/tkey/led.h
new file mode 100644
index 0000000..5253d78
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/include/tkey/led.h
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: 2022 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#ifndef TKEY_LED_H
+#define TKEY_LED_H
+
+#include
+#include
+
+// clang-format off
+#define LED_BLACK 0
+#define LED_RED (1 << TK1_MMIO_TK1_LED_R_BIT)
+#define LED_GREEN (1 << TK1_MMIO_TK1_LED_G_BIT)
+#define LED_BLUE (1 << TK1_MMIO_TK1_LED_B_BIT)
+#define LED_WHITE (LED_RED | LED_GREEN | LED_BLUE)
+// clang-format on
+
+uint32_t led_get(void);
+void led_set(uint32_t ledvalue);
+void led_flash_forever(uint32_t ledvalue);
+#endif
diff --git a/hw/application_fpga/tkey-libs/include/tkey/lib.h b/hw/application_fpga/tkey-libs/include/tkey/lib.h
new file mode 100644
index 0000000..f87648c
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/include/tkey/lib.h
@@ -0,0 +1,18 @@
+// SPDX-FileCopyrightText: 2022 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#ifndef TKEY_LIB_H
+#define TKEY_LIB_H
+
+#include
+#include
+
+void *memset(void *dest, int c, unsigned n);
+void *memcpy(void *dest, const void *src, unsigned n);
+void memcpy_s(void *dest, size_t destsize, const void *src, size_t n);
+void *wordcpy(void *dest, const void *src, unsigned n);
+void wordcpy_s(void *dest, size_t destsize, const void *src, size_t n);
+int memeq(void *dest, const void *src, size_t n);
+void secure_wipe(void *v, size_t n);
+size_t strlen(const char *str);
+#endif
diff --git a/hw/application_fpga/tkey-libs/include/tkey/proto.h b/hw/application_fpga/tkey-libs/include/tkey/proto.h
new file mode 100644
index 0000000..f2b89d5
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/include/tkey/proto.h
@@ -0,0 +1,39 @@
+// SPDX-FileCopyrightText: 2022 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include
+#include
+
+#ifndef TKEY_PROTO_H
+#define TKEY_PROTO_H
+
+enum endpoints {
+ DST_HW_IFPGA = 0x00,
+ DST_HW_AFPGA = 0x01,
+ DST_FW = 0x02,
+ DST_SW = 0x03
+};
+
+enum cmdlen {
+ LEN_1,
+ LEN_4,
+ LEN_32,
+ LEN_128
+};
+
+#define CMDLEN_MAXBYTES 128
+
+enum status {
+ STATUS_OK,
+ STATUS_BAD
+};
+
+struct frame_header {
+ uint8_t id;
+ enum endpoints endpoint;
+ size_t len;
+};
+
+uint8_t genhdr(uint8_t id, uint8_t endpoint, uint8_t status, enum cmdlen len);
+int parseframe(uint8_t b, struct frame_header *hdr);
+#endif
diff --git a/hw/application_fpga/fw/tk1_mem.h b/hw/application_fpga/tkey-libs/include/tkey/tk1_mem.h
similarity index 77%
rename from hw/application_fpga/fw/tk1_mem.h
rename to hw/application_fpga/tkey-libs/include/tkey/tk1_mem.h
index 89d6b7d..53b82e9 100644
--- a/hw/application_fpga/fw/tk1_mem.h
+++ b/hw/application_fpga/tkey-libs/include/tkey/tk1_mem.h
@@ -1,12 +1,12 @@
/*
* Tillitis TKey Memory Map
*
- * Copyright (c) 2022, 2023, 2024 Tillitis AB
- * SPDX-License-Identifier: GPL-2.0-or-later
+ * SPDX-FileCopyrightText: 2022 Tillitis AB
+ * SPDX-License-Identifier: BSD-2-Clause
*
- * Note that this file is also included in at least qemu
- * (GPL-2.0-or-later) besides tillitis-key1 (GPL-2.0-only) and
- * tkey-libs (GPL-2.0-only) so it's licensed as GPL v2 or later.
+ * Note that this file is also in tillitis-key1 and qemu
+ * (GPL-2.0-or-later). Needs to stay in sync and have a compatible
+ * license.
*/
// clang-format off
@@ -15,15 +15,17 @@
#define TKEY_TK1_MEM_H
/*
-
The canonical location of this file is in:
- https://github.com/tillitis/tillitis-key1
+ https://github.com/tillitis/tkey-libs
- /hw/application_fpga/fw/tk1_mem.h
+ Under:
- The contents are derived from the Verilog code. For use by QEMU model,
- firmware, and apps.
+ include/tkey/tk1_mem.h
+
+ The contents are mostly derived from the Verilog code in
+
+ https://github.com/tillitis/tillitis-key1
Memory map
@@ -34,7 +36,7 @@
ROM 0b00 30 bit address
RAM 0b01 30 bit address
Reserved 0b10
- MMIO 0b11 6 bits for core select, 24 bits rest
+ Cores 0b11 6 bits for core select, 24 bits rest
Address Prefix, the first 8 bits in a 32-bit address:
@@ -42,18 +44,19 @@
--------------------
ROM 0x00
RAM 0x40
- MMIO 0xc0
- MMIO TRNG 0xc0
- MMIO TIMER 0xc1
- MMIO UDS 0xc2
- MMIO UART 0xc3
- MMIO TOUCH 0xc4
- MMIO FW_RAM 0xd0
- MMIO QEMU 0xfe Not used in real hardware
- MMIO TK1 0xff
+ TRNG 0xc0
+ TIMER 0xc1
+ UDS 0xc2
+ UART 0xc3
+ TOUCH 0xc4
+ FW_RAM 0xd0
+ QEMU 0xfe Not used in real hardware
+ TK1 0xff
*/
#define TK1_ROM_BASE 0x00000000
+#define TK1_ROM_SIZE 0x2000
+
#define TK1_RAM_BASE 0x40000000
#define TK1_RAM_SIZE 0x20000
@@ -63,8 +66,8 @@
#define TK1_APP_MAX_SIZE 0x20000
#define TK1_MMIO_FW_RAM_BASE 0xd0000000
-// FW_RAM is 2048 bytes
-#define TK1_MMIO_FW_RAM_SIZE 0x800
+// FW_RAM is 4096 bytes
+#define TK1_MMIO_FW_RAM_SIZE 0x1000
#define TK1_MMIO_TRNG_BASE 0xc0000000
#define TK1_MMIO_TRNG_STATUS 0xc0000024
@@ -106,10 +109,6 @@
#define TK1_MMIO_TK1_NAME1 0xff000004
#define TK1_MMIO_TK1_VERSION 0xff000008
-// Deprecated - use _SYSTEM_MODE_CTRL instead
-#define TK1_MMIO_TK1_SWITCH_APP 0xff000020
-#define TK1_MMIO_TK1_SYSTEM_MODE_CTRL 0xff000020
-
#define TK1_MMIO_TK1_LED 0xff000024
#define TK1_MMIO_TK1_LED_R_BIT 2
#define TK1_MMIO_TK1_LED_G_BIT 1
@@ -124,8 +123,6 @@
#define TK1_MMIO_TK1_APP_ADDR 0xff000030
#define TK1_MMIO_TK1_APP_SIZE 0xff000034
-#define TK1_MMIO_TK1_BLAKE2S 0xff000040
-
#define TK1_MMIO_TK1_CDI_FIRST 0xff000080
#define TK1_MMIO_TK1_CDI_LAST 0xff00009c
diff --git a/hw/application_fpga/tkey-libs/include/tkey/touch.h b/hw/application_fpga/tkey-libs/include/tkey/touch.h
new file mode 100644
index 0000000..ea6c1cf
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/include/tkey/touch.h
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: 2023 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#ifndef TKEY_TOUCH_H
+#define TKEY_TOUCH_H
+
+#include
+#include
+#include
+
+// touchwait() waits for a touch event while blinking color on the
+// status LED. timeout_s is the timeout in seconds.
+//
+// If a touch event occurs it returns true. If the timeout expires it
+// returns false.
+bool touch_wait(int color, int timeout_s);
+#endif
diff --git a/hw/application_fpga/tkey-libs/libcommon/assert.c b/hw/application_fpga/tkey-libs/libcommon/assert.c
new file mode 100644
index 0000000..2bc9d12
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/libcommon/assert.c
@@ -0,0 +1,35 @@
+// SPDX-FileCopyrightText: 2022 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include
+#include
+#include
+
+void assert_fail(enum ioend dest, const char *assertion, const char *file,
+ unsigned int line, const char *function)
+{
+ puts(dest, "assert: ");
+ puts(dest, assertion);
+ puts(dest, " ");
+ puts(dest, file);
+ puts(dest, ":");
+ putinthex(dest, line);
+ puts(dest, " ");
+ puts(dest, function);
+ puts(dest, "\n");
+
+ // Force illegal instruction to halt CPU
+ asm volatile("unimp");
+
+ // Not reached
+ __builtin_unreachable();
+}
+
+void assert_halt(void)
+{
+ // Force illegal instruction to halt CPU
+ asm volatile("unimp");
+
+ // Not reached
+ __builtin_unreachable();
+}
diff --git a/hw/application_fpga/tkey-libs/libcommon/io.c b/hw/application_fpga/tkey-libs/libcommon/io.c
new file mode 100644
index 0000000..03d67a1
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/libcommon/io.c
@@ -0,0 +1,382 @@
+// SPDX-FileCopyrightText: 2025 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+// Maximum payload size sent over the USB Mode Protocol.
+//
+// USB Mode Protocol:
+// 1 byte mode
+// 1 byte length
+//
+// Our USB Mode Protocol packets has room for 255 bytes according to
+// the header but we send at most 64 bytes of payload + the 2 byte
+// header. The header is removed in the USB controller and the maximum
+// payload fits in a single USB frame on the other side.
+#define USBMODE_PACKET_SIZE 64
+
+static void hex(uint8_t buf[2], const uint8_t c);
+static int discard(size_t nbytes);
+static uint8_t readbyte(void);
+static void writebyte(uint8_t b);
+
+struct usb_mode {
+ enum ioend endpoint; // Current USB endpoint with data
+ uint8_t len; // Data available in from current USB mode.
+};
+
+static struct usb_mode cur_endpoint = {
+ IO_NONE,
+ 0,
+};
+
+// clang-format off
+static volatile uint32_t* const can_rx = (volatile uint32_t *)TK1_MMIO_UART_RX_STATUS;
+static volatile uint32_t* const rx = (volatile uint32_t *)TK1_MMIO_UART_RX_DATA;
+static volatile uint32_t* const can_tx = (volatile uint32_t *)TK1_MMIO_UART_TX_STATUS;
+static volatile uint32_t* const tx = (volatile uint32_t *)TK1_MMIO_UART_TX_DATA;
+static volatile uint8_t* const debugtx = (volatile uint8_t *)TK1_MMIO_QEMU_DEBUG;
+// clang-format on
+
+// writebyte blockingly writes byte b to UART
+static void writebyte(uint8_t b)
+{
+ for (;;) {
+ if (*can_tx) {
+ *tx = b;
+ return;
+ }
+ }
+}
+
+// write_with_header writes nbytes of buf to UART with a USB Mode
+// Protocol header telling the receiver about the mode and length.
+static void write_with_header(enum ioend dest, const uint8_t *buf,
+ size_t nbytes)
+{
+ // USB Mode Protocol header:
+ // 1 byte mode
+ // 1 byte length
+
+ writebyte(dest);
+ writebyte(nbytes);
+
+ for (int i = 0; i < nbytes; i++) {
+ writebyte(buf[i]);
+ }
+}
+
+// write blockingly writes nbytes bytes of data from buf to dest which
+// is either:
+//
+// - IO_UART: Low-level UART access, no USB Mode Header added.
+//
+// - IO_QEMU: QEMU debug port
+//
+// - IO_CH552: Internal communication between the FPGA and the
+// CH552, with header.
+//
+// - IO_CDC: Through the UART for the CDC endpoint, with header.
+//
+// - IO_FIDO: Through the UART for the FIDO endpoint, with header.
+//
+// - IO_CCID: Through the UART for the CCID endpoint, with header.
+//
+// - IO_DEBUG: Through the UART for the DEBUG endpoint (USB HID), with
+// header.
+void write(enum ioend dest, const uint8_t *buf, size_t nbytes)
+{
+ if (dest == IO_QEMU) {
+ for (int i = 0; i < nbytes; i++) {
+ *debugtx = buf[i];
+ }
+
+ return;
+ } else if (dest == IO_UART) {
+ for (int i = 0; i < nbytes; i++) {
+ writebyte(buf[i]);
+ }
+
+ return;
+ }
+
+ while (nbytes > 0) {
+ // We split the data into chunks that will fit in the
+ // USB Mode Protocol and fits neatly in the USB frames
+ // on the other side of the USB controller.
+ uint8_t len =
+ nbytes < USBMODE_PACKET_SIZE ? nbytes : USBMODE_PACKET_SIZE;
+
+ write_with_header(dest, (const uint8_t *)buf, len);
+
+ buf += len;
+ nbytes -= len;
+ }
+}
+
+// readbyte reads a byte from UART and returns it. Blocking.
+static uint8_t readbyte(void)
+{
+ for (;;) {
+ if (*can_rx) {
+ return *rx;
+ }
+ }
+
+ return 0;
+}
+
+// read reads into buf of size bufsize from UART, nbytes or less, from
+// the current USB endpoint. It doesn't block.
+//
+// Returns the number of bytes read. Empty data returns 0.
+int read(enum ioend src, uint8_t *buf, size_t bufsize, size_t nbytes)
+{
+ if (buf == NULL || nbytes > bufsize) {
+ return -1;
+ }
+
+ if (src == IO_NONE || src == IO_UART || src == IO_QEMU) {
+ // Destination only endpoints
+ return -1;
+ }
+
+ if (src != cur_endpoint.endpoint) {
+ // No data for this source available right now.
+ return 0;
+ }
+
+ int n = 0;
+
+ for (n = 0; n < nbytes; n++) {
+ buf[n] = readbyte();
+ cur_endpoint.len--;
+ }
+
+ return n;
+}
+
+// uart_read reads blockingly into buf o size bufsize from UART nbytes
+// bytes.
+//
+// Returns negative on error.
+int uart_read(uint8_t *buf, size_t bufsize, size_t nbytes)
+{
+ if (nbytes > bufsize) {
+ return -1;
+ }
+
+ for (int n = 0; n < nbytes; n++) {
+ buf[n] = readbyte();
+ }
+
+ return 0;
+}
+
+// discard nbytes of what's available.
+//
+// Returns how many bytes were discarded.
+static int discard(size_t nbytes)
+{
+ int n = 0;
+ uint8_t len = nbytes < cur_endpoint.len ? nbytes : cur_endpoint.len;
+
+ for (n = 0; n < len; n++) {
+ (void)readbyte();
+ cur_endpoint.len--;
+ }
+
+ return n;
+}
+
+// readselect blocks and returns when there is something readable from
+// some mode.
+//
+// Use like this:
+//
+// readselect(IO_CDC|IO_FIDO, &endpoint, &len)
+//
+// to wait for some data from either the CDC or the FIDO endpoint.
+//
+// NOTE WELL: You need to call readselect() first, before doing any
+// calls to read().
+//
+// Only endpoints available for read are:
+//
+// - IO_CH552
+// - IO_CDC
+// - IO_FIDO
+// - IO_CCID
+// - IO_DEBUG
+//
+// If you need blocking low-level UART reads, use uart_read() instead.
+//
+// Sets endpoint of the first endpoint in the bitmask with data
+// available. Indicates how many bytes available in len.
+//
+// Returns non-zero on error.
+int readselect(int bitmask, enum ioend *endpoint, uint8_t *len)
+{
+ if ((bitmask & IO_UART) || (bitmask & IO_QEMU)) {
+ // Not possible to use readselect() on these
+ // endpoints.
+ return -1;
+ }
+
+ for (;;) {
+ // Check what is in the current UART buffer.
+ //
+ // - If nothing known, block until something comes along.
+ //
+ // - If not in bitmask, discard the data available
+ // from that endpoint.
+ //
+ // - If in the bitmask, return the first endpoint with
+ // data available and indicate how much data in len.
+ if (cur_endpoint.len == 0) {
+ // Read USB Mode Protocol header:
+ // 1 byte mode
+ // 1 byte length
+ cur_endpoint.endpoint = readbyte();
+ cur_endpoint.len = readbyte();
+ }
+
+ *len = cur_endpoint.len;
+
+ if (cur_endpoint.endpoint & bitmask) {
+ *endpoint = cur_endpoint.endpoint;
+
+ return 0;
+ }
+
+ // Not the USB endpoint caller asked for. Discard the
+ // rest from this endpoint.
+ if (discard(*len) != *len) {
+ // We couldn't discard what the USB Mode
+ // Protocol itself reported was available!
+ // Something's fishy. Halt.
+ assert(1 == 2);
+ }
+ }
+
+ return 0;
+}
+
+void putchar(enum ioend dest, const uint8_t ch)
+{
+ write(dest, &ch, 1);
+}
+
+static void hex(uint8_t buf[2], const uint8_t c)
+{
+ unsigned int upper = (c >> 4) & 0xf;
+ unsigned int lower = c & 0xf;
+
+ buf[0] = upper < 10 ? '0' + upper : 'a' - 10 + upper;
+ buf[1] = lower < 10 ? '0' + lower : 'a' - 10 + lower;
+}
+
+void puthex(enum ioend dest, const uint8_t c)
+{
+ uint8_t hexbuf[2] = {0};
+
+ hex(hexbuf, c);
+ write(dest, hexbuf, 2);
+}
+
+// Size of of a maximum integer in hex text format
+#define INTBUFSIZE 10
+
+void putinthex(enum ioend dest, const uint32_t n)
+{
+ uint8_t buf[INTBUFSIZE] = {0};
+ uint8_t hexbuf[2] = {0};
+ uint8_t *intbuf = (uint8_t *)&n;
+ int j = 0;
+
+ buf[j++] = '0';
+ buf[j++] = 'x';
+
+ for (int i = 3; i > -1; i--) {
+ hex(hexbuf, intbuf[i]);
+ buf[j++] = hexbuf[0];
+ buf[j++] = hexbuf[1];
+ }
+
+ write(dest, buf, INTBUFSIZE);
+}
+
+void puts(enum ioend dest, const char *s)
+{
+ write(dest, (const uint8_t *)s, strlen(s));
+}
+
+// Size of a hex row: Contains 16 bytes where each byte is printed as
+// 3 characters (hex + hex + space). Every row ends with newline or at
+// most CR+LF.
+#define FULLROW (16 * 3)
+#define ROWBUFSIZE (FULLROW + 2)
+
+void hexdump(enum ioend dest, void *buf, int len)
+{
+ uint8_t rowbuf[ROWBUFSIZE] = {0};
+ uint8_t hexbuf[2] = {0};
+ uint8_t *byte_buf = (uint8_t *)buf;
+
+ int rowpos = 0;
+ for (int i = 0; i < len; i++) {
+ hex(hexbuf, byte_buf[i]);
+ rowbuf[rowpos++] = hexbuf[0];
+ rowbuf[rowpos++] = hexbuf[1];
+ rowbuf[rowpos++] = ' ';
+
+ // If the row is full, print it now.
+ if (rowpos == FULLROW) {
+ if (dest == IO_CDC) {
+ rowbuf[rowpos++] = '\r';
+ }
+ rowbuf[rowpos++] = '\n';
+ write(dest, rowbuf, rowpos);
+ rowpos = 0;
+ }
+ }
+
+ // If final row wasn't full, print it now.
+ if (rowpos != 0) {
+ if (dest == IO_CDC) {
+ rowbuf[rowpos++] = '\r';
+ }
+ rowbuf[rowpos++] = '\n';
+ write(dest, rowbuf, rowpos);
+ }
+}
+
+// Configure USB endpoints that should be enabled/disabled
+//
+// Allowed options are:
+// - IO_FIDO (can't be used used together with IO_CCID)
+// - IO_CCID (can't be used used together with IO_FIDO)
+// - IO_DEBUG
+//
+// The following are always enabled:
+// - IO_CDC
+// - IO_CH552
+//
+// Use like this:
+//
+// config_endpoints(IO_FIDO|IO_DEBUG)
+//
+void config_endpoints(enum ioend endpoints)
+{
+ uint8_t cmdbuf[2] = {0};
+
+ cmdbuf[0] = SET_ENDPOINTS;
+ cmdbuf[1] = endpoints;
+
+ write(IO_CH552, cmdbuf, 2);
+}
diff --git a/hw/application_fpga/tkey-libs/libcommon/led.c b/hw/application_fpga/tkey-libs/libcommon/led.c
new file mode 100644
index 0000000..2eacb84
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/libcommon/led.c
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: 2022 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include
+#include
+
+// clang-format off
+static volatile uint32_t* const led = (volatile uint32_t *)TK1_MMIO_TK1_LED;
+// clang-format on
+
+void led_set(uint32_t ledvalue)
+{
+ *led = ledvalue;
+}
+
+uint32_t led_get()
+{
+ return *led;
+}
+
+void led_flash_forever(uint32_t ledvalue)
+{
+ int led_on = 0;
+
+ for (;;) {
+ *led = led_on ? ledvalue : LED_BLACK;
+ for (volatile int i = 0; i < 800000; i++) {
+ }
+ led_on = !led_on;
+ }
+}
diff --git a/hw/application_fpga/tkey-libs/libcommon/lib.c b/hw/application_fpga/tkey-libs/libcommon/lib.c
new file mode 100644
index 0000000..4929f89
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/libcommon/lib.c
@@ -0,0 +1,101 @@
+// SPDX-FileCopyrightText: 2022 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include
+#include
+#include
+#include
+
+void *memset(void *dest, int c, unsigned n)
+{
+ uint8_t *s = dest;
+
+ for (; n; n--, s++)
+ *s = c;
+
+ return dest;
+}
+
+__attribute__((used)) void *memcpy(void *dest, const void *src, unsigned n)
+{
+ uint8_t *src_byte = (uint8_t *)src;
+ uint8_t *dest_byte = (uint8_t *)dest;
+
+ for (int i = 0; i < n; i++) {
+ dest_byte[i] = src_byte[i];
+ }
+
+ return dest;
+}
+
+void memcpy_s(void *dest, size_t destsize, const void *src, size_t n)
+{
+ assert(dest != NULL);
+ assert(src != NULL);
+ assert(destsize >= n);
+
+ uint8_t *src_byte = (uint8_t *)src;
+ uint8_t *dest_byte = (uint8_t *)dest;
+
+ for (size_t i = 0; i < n; i++) {
+ dest_byte[i] = src_byte[i];
+ }
+}
+
+__attribute__((used)) void *wordcpy(void *dest, const void *src, unsigned n)
+{
+ uint32_t *src_word = (uint32_t *)src;
+ uint32_t *dest_word = (uint32_t *)dest;
+
+ for (int i = 0; i < n; i++) {
+ dest_word[i] = src_word[i];
+ }
+
+ return dest;
+}
+
+void wordcpy_s(void *dest, size_t destsize, const void *src, size_t n)
+{
+ assert(dest != NULL);
+ assert(src != NULL);
+ assert(destsize >= n);
+
+ uint32_t *src_word = (uint32_t *)src;
+ uint32_t *dest_word = (uint32_t *)dest;
+
+ for (size_t i = 0; i < n; i++) {
+ dest_word[i] = src_word[i];
+ }
+}
+
+int memeq(void *dest, const void *src, size_t n)
+{
+ uint8_t *src_byte = (uint8_t *)src;
+ uint8_t *dest_byte = (uint8_t *)dest;
+ int res = -1;
+
+ for (size_t i = 0; i < n; i++) {
+ if (dest_byte[i] != src_byte[i]) {
+ res = 0;
+ }
+ }
+
+ return res;
+}
+
+void secure_wipe(void *v, size_t n)
+{
+ volatile uint8_t *p = (volatile uint8_t *)v;
+ while (n--)
+ *p++ = 0;
+}
+
+size_t strlen(const char *str)
+{
+ const char *s;
+
+ for (s = str; *s; ++s)
+ ;
+
+ return (s - str);
+}
diff --git a/hw/application_fpga/tkey-libs/libcommon/proto.c b/hw/application_fpga/tkey-libs/libcommon/proto.c
new file mode 100644
index 0000000..8d1a975
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/libcommon/proto.c
@@ -0,0 +1,51 @@
+// SPDX-FileCopyrightText: 2022 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+uint8_t genhdr(uint8_t id, uint8_t endpoint, uint8_t status, enum cmdlen len)
+{
+ return (id << 5) | (endpoint << 3) | (status << 2) | len;
+}
+
+int parseframe(uint8_t b, struct frame_header *hdr)
+{
+ if ((b & 0x80) != 0) {
+ // Bad version
+ return -1;
+ }
+
+ if ((b & 0x4) != 0) {
+ // Must be 0
+ return -1;
+ }
+
+ hdr->id = (b & 0x60) >> 5;
+ hdr->endpoint = (b & 0x18) >> 3;
+
+ // Length
+ switch (b & 0x3) {
+ case LEN_1:
+ hdr->len = 1;
+ break;
+ case LEN_4:
+ hdr->len = 4;
+ break;
+ case LEN_32:
+ hdr->len = 32;
+ break;
+ case LEN_128:
+ hdr->len = 128;
+ break;
+ default:
+ // Unknown length
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/hw/application_fpga/tkey-libs/libcommon/touch.c b/hw/application_fpga/tkey-libs/libcommon/touch.c
new file mode 100644
index 0000000..0309a3b
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/libcommon/touch.c
@@ -0,0 +1,67 @@
+// SPDX-FileCopyrightText: 2023 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+#include
+#include
+#include
+#include
+
+// CPU clock frequenzy in Hz
+#define CPUFREQ 18000000
+
+// clang-format off
+static volatile uint32_t *timer = (volatile uint32_t *)TK1_MMIO_TIMER_TIMER;
+static volatile uint32_t *timer_prescaler = (volatile uint32_t *)TK1_MMIO_TIMER_PRESCALER;
+static volatile uint32_t *timer_status = (volatile uint32_t *)TK1_MMIO_TIMER_STATUS;
+static volatile uint32_t *timer_ctrl = (volatile uint32_t *)TK1_MMIO_TIMER_CTRL;
+static volatile uint32_t *touch = (volatile uint32_t *)TK1_MMIO_TOUCH_STATUS;
+// clang-format on
+
+// Returns !0 if touch sensor has been touched
+#define touched() (*touch & (1 << TK1_MMIO_TOUCH_STATUS_EVENT_BIT))
+
+bool touch_wait(int color, int timeout_s)
+{
+ int ledon = 0;
+ int orig_color = led_get();
+ uint32_t time = 0;
+ uint32_t lasttime = 0;
+
+ // Tick once every decisecond
+ *timer_prescaler = CPUFREQ / 10;
+ *timer = timeout_s * 10; // Seconds
+
+ // Start timer
+ *timer_ctrl |= (1 << TK1_MMIO_TIMER_CTRL_START_BIT);
+
+ // Acknowledge any stray touch events before waiting for real
+ // touch
+ *touch = 0;
+
+ // Blink until either the touch sensor has been touched or the
+ // timer hits 0.
+ while (!touched() && *timer_status != 0) {
+ time = *timer;
+ if (time % 2 == 0 && time != lasttime) {
+ lasttime = time;
+ ledon = !ledon;
+ led_set(ledon ? color : LED_BLACK);
+ }
+ }
+
+ // Restore LED
+ led_set(orig_color);
+
+ // Do we have a timeout?
+ if (*timer_status == 0) {
+ return false;
+ }
+
+ // Stop timer
+ *timer_ctrl |= (1 << TK1_MMIO_TIMER_CTRL_STOP_BIT);
+
+ // Confirm touch event
+ *touch = 0;
+
+ return true;
+}
diff --git a/hw/application_fpga/tkey-libs/libcrt0/crt0.S b/hw/application_fpga/tkey-libs/libcrt0/crt0.S
new file mode 100644
index 0000000..f484b7d
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/libcrt0/crt0.S
@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: 2022 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+ .section ".text.init"
+ .global _start
+_start:
+ li x1, 0
+ li x2, 0
+ li x3, 0
+ li x4, 0
+ li x5, 0
+ li x6, 0
+ li x7, 0
+ li x8, 0
+ li x9, 0
+ li x10,0
+ li x11,0
+ li x12,0
+ li x13,0
+ li x14,0
+ li x15,0
+ li x16,0
+ li x17,0
+ li x18,0
+ li x19,0
+ li x20,0
+ li x21,0
+ li x22,0
+ li x23,0
+ li x24,0
+ li x25,0
+ li x26,0
+ li x27,0
+ li x28,0
+ li x29,0
+ li x30,0
+ li x31,0
+
+ /* init stack below 0x40020000 (TK1_RAM_BASE+TK1_RAM_SIZE) */
+ li sp, 0x4001fff0
+
+ /* zero-init bss section */
+ la a0, _sbss
+ la a1, _ebss
+ bge a0, a1, end_init_bss
+
+loop_init_bss:
+ sw zero, 0(a0)
+ addi a0, a0, 4
+ blt a0, a1, loop_init_bss
+
+end_init_bss:
+ call main
diff --git a/hw/application_fpga/tkey-libs/monocypher/LICENSE b/hw/application_fpga/tkey-libs/monocypher/LICENSE
new file mode 100644
index 0000000..670154e
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/monocypher/LICENSE
@@ -0,0 +1,116 @@
+CC0 1.0 Universal
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator and
+subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for the
+purpose of contributing to a commons of creative, cultural and scientific
+works ("Commons") that the public can reliably and without fear of later
+claims of infringement build upon, modify, incorporate in other works, reuse
+and redistribute as freely as possible in any form whatsoever and for any
+purposes, including without limitation commercial purposes. These owners may
+contribute to the Commons to promote the ideal of a free culture and the
+further production of creative, cultural and scientific works, or to gain
+reputation or greater distribution for their Work in part through the use and
+efforts of others.
+
+For these and/or other purposes and motivations, and without any expectation
+of additional consideration or compensation, the person associating CC0 with a
+Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
+and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
+and publicly distribute the Work under its terms, with knowledge of his or her
+Copyright and Related Rights in the Work and the meaning and intended legal
+effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not limited
+to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display, communicate,
+ and translate a Work;
+
+ ii. moral rights retained by the original author(s) and/or performer(s);
+
+ iii. publicity and privacy rights pertaining to a person's image or likeness
+ depicted in a Work;
+
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+
+ v. rights protecting the extraction, dissemination, use and reuse of data in
+ a Work;
+
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation thereof,
+ including any amended or successor version of such directive); and
+
+ vii. other similar, equivalent or corresponding rights throughout the world
+ based on applicable law or treaty, and any national implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention of,
+applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
+unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
+and Related Rights and associated claims and causes of action, whether now
+known or unknown (including existing as well as future claims and causes of
+action), in the Work (i) in all territories worldwide, (ii) for the maximum
+duration provided by applicable law or treaty (including future time
+extensions), (iii) in any current or future medium and for any number of
+copies, and (iv) for any purpose whatsoever, including without limitation
+commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
+the Waiver for the benefit of each member of the public at large and to the
+detriment of Affirmer's heirs and successors, fully intending that such Waiver
+shall not be subject to revocation, rescission, cancellation, termination, or
+any other legal or equitable action to disrupt the quiet enjoyment of the Work
+by the public as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason be
+judged legally invalid or ineffective under applicable law, then the Waiver
+shall be preserved to the maximum extent permitted taking into account
+Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
+is so judged Affirmer hereby grants to each affected person a royalty-free,
+non transferable, non sublicensable, non exclusive, irrevocable and
+unconditional license to exercise Affirmer's Copyright and Related Rights in
+the Work (i) in all territories worldwide, (ii) for the maximum duration
+provided by applicable law or treaty (including future time extensions), (iii)
+in any current or future medium and for any number of copies, and (iv) for any
+purpose whatsoever, including without limitation commercial, advertising or
+promotional purposes (the "License"). The License shall be deemed effective as
+of the date CC0 was applied by Affirmer to the Work. Should any part of the
+License for any reason be judged legally invalid or ineffective under
+applicable law, such partial invalidity or ineffectiveness shall not
+invalidate the remainder of the License, and in such case Affirmer hereby
+affirms that he or she will not (i) exercise any of his or her remaining
+Copyright and Related Rights in the Work or (ii) assert any associated claims
+and causes of action with respect to the Work, in either case contrary to
+Affirmer's express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+
+ b. Affirmer offers the Work as-is and makes no representations or warranties
+ of any kind concerning the Work, express, implied, statutory or otherwise,
+ including without limitation warranties of title, merchantability, fitness
+ for a particular purpose, non infringement, or the absence of latent or
+ other defects, accuracy, or the present or absence of errors, whether or not
+ discoverable, all to the greatest extent permissible under applicable law.
+
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without limitation
+ any person's Copyright and Related Rights in the Work. Further, Affirmer
+ disclaims responsibility for obtaining any necessary consents, permissions
+ or other rights required for any use of the Work.
+
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to this
+ CC0 or use of the Work.
+
+For more information, please see
+
diff --git a/hw/application_fpga/tkey-libs/monocypher/README.md b/hw/application_fpga/tkey-libs/monocypher/README.md
new file mode 100644
index 0000000..2e7b01f
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/monocypher/README.md
@@ -0,0 +1,5 @@
+# Monocypher
+
+A ed25519 implementation from https://github.com/LoupVaillant/Monocypher
+
+Small changes made for building.
diff --git a/hw/application_fpga/tkey-libs/monocypher/monocypher-ed25519.c b/hw/application_fpga/tkey-libs/monocypher/monocypher-ed25519.c
new file mode 100644
index 0000000..1dbcfbb
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/monocypher/monocypher-ed25519.c
@@ -0,0 +1,500 @@
+// Monocypher version 4.0.2
+//
+// This file is dual-licensed. Choose whichever licence you want from
+// the two licences listed below.
+//
+// The first licence is a regular 2-clause BSD licence. The second licence
+// is the CC-0 from Creative Commons. It is intended to release Monocypher
+// to the public domain. The BSD licence serves as a fallback option.
+//
+// SPDX-License-Identifier: BSD-2-Clause OR CC0-1.0
+//
+// ------------------------------------------------------------------------
+//
+// Copyright (c) 2017-2019, Loup Vaillant
+// All rights reserved.
+//
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the
+// distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// ------------------------------------------------------------------------
+//
+// Written in 2017-2019 by Loup Vaillant
+//
+// To the extent possible under law, the author(s) have dedicated all copyright
+// and related neighboring rights to this software to the public domain
+// worldwide. This software is distributed without any warranty.
+//
+// You should have received a copy of the CC0 Public Domain Dedication along
+// with this software. If not, see
+//
+
+#include "monocypher-ed25519.h"
+
+#ifdef MONOCYPHER_CPP_NAMESPACE
+namespace MONOCYPHER_CPP_NAMESPACE {
+#endif
+
+/////////////////
+/// Utilities ///
+/////////////////
+#define FOR(i, min, max) for (size_t i = min; i < max; i++)
+#define COPY(dst, src, size) FOR(_i_, 0, size) (dst)[_i_] = (src)[_i_]
+#define ZERO(buf, size) FOR(_i_, 0, size) (buf)[_i_] = 0
+#define WIPE_CTX(ctx) crypto_wipe(ctx , sizeof(*(ctx)))
+#define WIPE_BUFFER(buffer) crypto_wipe(buffer, sizeof(buffer))
+#define MIN(a, b) ((a) <= (b) ? (a) : (b))
+typedef uint8_t u8;
+typedef uint64_t u64;
+
+// Returns the smallest positive integer y such that
+// (x + y) % pow_2 == 0
+// Basically, it's how many bytes we need to add to "align" x.
+// Only works when pow_2 is a power of 2.
+// Note: we use ~x+1 instead of -x to avoid compiler warnings
+static size_t align(size_t x, size_t pow_2)
+{
+ return (~x + 1) & (pow_2 - 1);
+}
+
+static u64 load64_be(const u8 s[8])
+{
+ return((u64)s[0] << 56)
+ | ((u64)s[1] << 48)
+ | ((u64)s[2] << 40)
+ | ((u64)s[3] << 32)
+ | ((u64)s[4] << 24)
+ | ((u64)s[5] << 16)
+ | ((u64)s[6] << 8)
+ | (u64)s[7];
+}
+
+static void store64_be(u8 out[8], u64 in)
+{
+ out[0] = (in >> 56) & 0xff;
+ out[1] = (in >> 48) & 0xff;
+ out[2] = (in >> 40) & 0xff;
+ out[3] = (in >> 32) & 0xff;
+ out[4] = (in >> 24) & 0xff;
+ out[5] = (in >> 16) & 0xff;
+ out[6] = (in >> 8) & 0xff;
+ out[7] = in & 0xff;
+}
+
+static void load64_be_buf (u64 *dst, const u8 *src, size_t size) {
+ FOR(i, 0, size) { dst[i] = load64_be(src + i*8); }
+}
+
+///////////////
+/// SHA 512 ///
+///////////////
+static u64 rot(u64 x, int c ) { return (x >> c) | (x << (64 - c)); }
+static u64 ch (u64 x, u64 y, u64 z) { return (x & y) ^ (~x & z); }
+static u64 maj(u64 x, u64 y, u64 z) { return (x & y) ^ ( x & z) ^ (y & z); }
+static u64 big_sigma0(u64 x) { return rot(x, 28) ^ rot(x, 34) ^ rot(x, 39); }
+static u64 big_sigma1(u64 x) { return rot(x, 14) ^ rot(x, 18) ^ rot(x, 41); }
+static u64 lit_sigma0(u64 x) { return rot(x, 1) ^ rot(x, 8) ^ (x >> 7); }
+static u64 lit_sigma1(u64 x) { return rot(x, 19) ^ rot(x, 61) ^ (x >> 6); }
+
+static const u64 K[80] = {
+ 0x428a2f98d728ae22,0x7137449123ef65cd,0xb5c0fbcfec4d3b2f,0xe9b5dba58189dbbc,
+ 0x3956c25bf348b538,0x59f111f1b605d019,0x923f82a4af194f9b,0xab1c5ed5da6d8118,
+ 0xd807aa98a3030242,0x12835b0145706fbe,0x243185be4ee4b28c,0x550c7dc3d5ffb4e2,
+ 0x72be5d74f27b896f,0x80deb1fe3b1696b1,0x9bdc06a725c71235,0xc19bf174cf692694,
+ 0xe49b69c19ef14ad2,0xefbe4786384f25e3,0x0fc19dc68b8cd5b5,0x240ca1cc77ac9c65,
+ 0x2de92c6f592b0275,0x4a7484aa6ea6e483,0x5cb0a9dcbd41fbd4,0x76f988da831153b5,
+ 0x983e5152ee66dfab,0xa831c66d2db43210,0xb00327c898fb213f,0xbf597fc7beef0ee4,
+ 0xc6e00bf33da88fc2,0xd5a79147930aa725,0x06ca6351e003826f,0x142929670a0e6e70,
+ 0x27b70a8546d22ffc,0x2e1b21385c26c926,0x4d2c6dfc5ac42aed,0x53380d139d95b3df,
+ 0x650a73548baf63de,0x766a0abb3c77b2a8,0x81c2c92e47edaee6,0x92722c851482353b,
+ 0xa2bfe8a14cf10364,0xa81a664bbc423001,0xc24b8b70d0f89791,0xc76c51a30654be30,
+ 0xd192e819d6ef5218,0xd69906245565a910,0xf40e35855771202a,0x106aa07032bbd1b8,
+ 0x19a4c116b8d2d0c8,0x1e376c085141ab53,0x2748774cdf8eeb99,0x34b0bcb5e19b48a8,
+ 0x391c0cb3c5c95a63,0x4ed8aa4ae3418acb,0x5b9cca4f7763e373,0x682e6ff3d6b2b8a3,
+ 0x748f82ee5defb2fc,0x78a5636f43172f60,0x84c87814a1f0ab72,0x8cc702081a6439ec,
+ 0x90befffa23631e28,0xa4506cebde82bde9,0xbef9a3f7b2c67915,0xc67178f2e372532b,
+ 0xca273eceea26619c,0xd186b8c721c0c207,0xeada7dd6cde0eb1e,0xf57d4f7fee6ed178,
+ 0x06f067aa72176fba,0x0a637dc5a2c898a6,0x113f9804bef90dae,0x1b710b35131c471b,
+ 0x28db77f523047d84,0x32caab7b40c72493,0x3c9ebe0a15c9bebc,0x431d67c49c100d4c,
+ 0x4cc5d4becb3e42b6,0x597f299cfc657e2a,0x5fcb6fab3ad6faec,0x6c44198c4a475817
+};
+
+static void sha512_compress(crypto_sha512_ctx *ctx)
+{
+ u64 a = ctx->hash[0]; u64 b = ctx->hash[1];
+ u64 c = ctx->hash[2]; u64 d = ctx->hash[3];
+ u64 e = ctx->hash[4]; u64 f = ctx->hash[5];
+ u64 g = ctx->hash[6]; u64 h = ctx->hash[7];
+
+ FOR (j, 0, 16) {
+ u64 in = K[j] + ctx->input[j];
+ u64 t1 = big_sigma1(e) + ch (e, f, g) + h + in;
+ u64 t2 = big_sigma0(a) + maj(a, b, c);
+ h = g; g = f; f = e; e = d + t1;
+ d = c; c = b; b = a; a = t1 + t2;
+ }
+ size_t i16 = 0;
+ FOR(i, 1, 5) {
+ i16 += 16;
+ FOR (j, 0, 16) {
+ ctx->input[j] += lit_sigma1(ctx->input[(j- 2) & 15]);
+ ctx->input[j] += lit_sigma0(ctx->input[(j-15) & 15]);
+ ctx->input[j] += ctx->input[(j- 7) & 15];
+ u64 in = K[i16 + j] + ctx->input[j];
+ u64 t1 = big_sigma1(e) + ch (e, f, g) + h + in;
+ u64 t2 = big_sigma0(a) + maj(a, b, c);
+ h = g; g = f; f = e; e = d + t1;
+ d = c; c = b; b = a; a = t1 + t2;
+ }
+ }
+
+ ctx->hash[0] += a; ctx->hash[1] += b;
+ ctx->hash[2] += c; ctx->hash[3] += d;
+ ctx->hash[4] += e; ctx->hash[5] += f;
+ ctx->hash[6] += g; ctx->hash[7] += h;
+}
+
+// Write 1 input byte
+static void sha512_set_input(crypto_sha512_ctx *ctx, u8 input)
+{
+ size_t word = ctx->input_idx >> 3;
+ size_t byte = ctx->input_idx & 7;
+ ctx->input[word] |= (u64)input << (8 * (7 - byte));
+}
+
+// Increment a 128-bit "word".
+static void sha512_incr(u64 x[2], u64 y)
+{
+ x[1] += y;
+ if (x[1] < y) {
+ x[0]++;
+ }
+}
+
+void crypto_sha512_init(crypto_sha512_ctx *ctx)
+{
+ ctx->hash[0] = 0x6a09e667f3bcc908;
+ ctx->hash[1] = 0xbb67ae8584caa73b;
+ ctx->hash[2] = 0x3c6ef372fe94f82b;
+ ctx->hash[3] = 0xa54ff53a5f1d36f1;
+ ctx->hash[4] = 0x510e527fade682d1;
+ ctx->hash[5] = 0x9b05688c2b3e6c1f;
+ ctx->hash[6] = 0x1f83d9abfb41bd6b;
+ ctx->hash[7] = 0x5be0cd19137e2179;
+ ctx->input_size[0] = 0;
+ ctx->input_size[1] = 0;
+ ctx->input_idx = 0;
+ ZERO(ctx->input, 16);
+}
+
+void crypto_sha512_update(crypto_sha512_ctx *ctx,
+ const u8 *message, size_t message_size)
+{
+ // Avoid undefined NULL pointer increments with empty messages
+ if (message_size == 0) {
+ return;
+ }
+
+ // Align ourselves with word boundaries
+ if ((ctx->input_idx & 7) != 0) {
+ size_t nb_bytes = MIN(align(ctx->input_idx, 8), message_size);
+ FOR (i, 0, nb_bytes) {
+ sha512_set_input(ctx, message[i]);
+ ctx->input_idx++;
+ }
+ message += nb_bytes;
+ message_size -= nb_bytes;
+ }
+
+ // Align ourselves with block boundaries
+ if ((ctx->input_idx & 127) != 0) {
+ size_t nb_words = MIN(align(ctx->input_idx, 128), message_size) >> 3;
+ load64_be_buf(ctx->input + (ctx->input_idx >> 3), message, nb_words);
+ ctx->input_idx += nb_words << 3;
+ message += nb_words << 3;
+ message_size -= nb_words << 3;
+ }
+
+ // Compress block if needed
+ if (ctx->input_idx == 128) {
+ sha512_incr(ctx->input_size, 1024); // size is in bits
+ sha512_compress(ctx);
+ ctx->input_idx = 0;
+ ZERO(ctx->input, 16);
+ }
+
+ // Process the message block by block
+ FOR (i, 0, message_size >> 7) { // number of blocks
+ load64_be_buf(ctx->input, message, 16);
+ sha512_incr(ctx->input_size, 1024); // size is in bits
+ sha512_compress(ctx);
+ ctx->input_idx = 0;
+ ZERO(ctx->input, 16);
+ message += 128;
+ }
+ message_size &= 127;
+
+ if (message_size != 0) {
+ // Remaining words
+ size_t nb_words = message_size >> 3;
+ load64_be_buf(ctx->input, message, nb_words);
+ ctx->input_idx += nb_words << 3;
+ message += nb_words << 3;
+ message_size -= nb_words << 3;
+
+ // Remaining bytes
+ FOR (i, 0, message_size) {
+ sha512_set_input(ctx, message[i]);
+ ctx->input_idx++;
+ }
+ }
+}
+
+void crypto_sha512_final(crypto_sha512_ctx *ctx, u8 hash[64])
+{
+ // Add padding bit
+ if (ctx->input_idx == 0) {
+ ZERO(ctx->input, 16);
+ }
+ sha512_set_input(ctx, 128);
+
+ // Update size
+ sha512_incr(ctx->input_size, ctx->input_idx * 8);
+
+ // Compress penultimate block (if any)
+ if (ctx->input_idx > 111) {
+ sha512_compress(ctx);
+ ZERO(ctx->input, 14);
+ }
+ // Compress last block
+ ctx->input[14] = ctx->input_size[0];
+ ctx->input[15] = ctx->input_size[1];
+ sha512_compress(ctx);
+
+ // Copy hash to output (big endian)
+ FOR (i, 0, 8) {
+ store64_be(hash + i*8, ctx->hash[i]);
+ }
+
+ WIPE_CTX(ctx);
+}
+
+void crypto_sha512(u8 hash[64], const u8 *message, size_t message_size)
+{
+ crypto_sha512_ctx ctx;
+ crypto_sha512_init (&ctx);
+ crypto_sha512_update(&ctx, message, message_size);
+ crypto_sha512_final (&ctx, hash);
+}
+
+////////////////////
+/// HMAC SHA 512 ///
+////////////////////
+void crypto_sha512_hmac_init(crypto_sha512_hmac_ctx *ctx,
+ const u8 *key, size_t key_size)
+{
+ // hash key if it is too long
+ if (key_size > 128) {
+ crypto_sha512(ctx->key, key, key_size);
+ key = ctx->key;
+ key_size = 64;
+ }
+ // Compute inner key: padded key XOR 0x36
+ FOR (i, 0, key_size) { ctx->key[i] = key[i] ^ 0x36; }
+ FOR (i, key_size, 128) { ctx->key[i] = 0x36; }
+ // Start computing inner hash
+ crypto_sha512_init (&ctx->ctx);
+ crypto_sha512_update(&ctx->ctx, ctx->key, 128);
+}
+
+void crypto_sha512_hmac_update(crypto_sha512_hmac_ctx *ctx,
+ const u8 *message, size_t message_size)
+{
+ crypto_sha512_update(&ctx->ctx, message, message_size);
+}
+
+void crypto_sha512_hmac_final(crypto_sha512_hmac_ctx *ctx, u8 hmac[64])
+{
+ // Finish computing inner hash
+ crypto_sha512_final(&ctx->ctx, hmac);
+ // Compute outer key: padded key XOR 0x5c
+ FOR (i, 0, 128) {
+ ctx->key[i] ^= 0x36 ^ 0x5c;
+ }
+ // Compute outer hash
+ crypto_sha512_init (&ctx->ctx);
+ crypto_sha512_update(&ctx->ctx, ctx->key , 128);
+ crypto_sha512_update(&ctx->ctx, hmac, 64);
+ crypto_sha512_final (&ctx->ctx, hmac); // outer hash
+ WIPE_CTX(ctx);
+}
+
+void crypto_sha512_hmac(u8 hmac[64], const u8 *key, size_t key_size,
+ const u8 *message, size_t message_size)
+{
+ crypto_sha512_hmac_ctx ctx;
+ crypto_sha512_hmac_init (&ctx, key, key_size);
+ crypto_sha512_hmac_update(&ctx, message, message_size);
+ crypto_sha512_hmac_final (&ctx, hmac);
+}
+
+////////////////////
+/// HKDF SHA 512 ///
+////////////////////
+void crypto_sha512_hkdf_expand(u8 *okm, size_t okm_size,
+ const u8 *prk, size_t prk_size,
+ const u8 *info, size_t info_size)
+{
+ int not_first = 0;
+ u8 ctr = 1;
+ u8 blk[64];
+
+ while (okm_size > 0) {
+ size_t out_size = MIN(okm_size, sizeof(blk));
+
+ crypto_sha512_hmac_ctx ctx;
+ crypto_sha512_hmac_init(&ctx, prk , prk_size);
+ if (not_first) {
+ // For some reason HKDF uses some kind of CBC mode.
+ // For some reason CTR mode alone wasn't enough.
+ // Like what, they didn't trust HMAC in 2010? Really??
+ crypto_sha512_hmac_update(&ctx, blk , sizeof(blk));
+ }
+ crypto_sha512_hmac_update(&ctx, info, info_size);
+ crypto_sha512_hmac_update(&ctx, &ctr, 1);
+ crypto_sha512_hmac_final(&ctx, blk);
+
+ COPY(okm, blk, out_size);
+
+ not_first = 1;
+ okm += out_size;
+ okm_size -= out_size;
+ ctr++;
+ }
+}
+
+void crypto_sha512_hkdf(u8 *okm , size_t okm_size,
+ const u8 *ikm , size_t ikm_size,
+ const u8 *salt, size_t salt_size,
+ const u8 *info, size_t info_size)
+{
+ // Extract
+ u8 prk[64];
+ crypto_sha512_hmac(prk, salt, salt_size, ikm, ikm_size);
+
+ // Expand
+ crypto_sha512_hkdf_expand(okm, okm_size, prk, sizeof(prk), info, info_size);
+}
+
+///////////////
+/// Ed25519 ///
+///////////////
+void crypto_ed25519_key_pair(u8 secret_key[64], u8 public_key[32], u8 seed[32])
+{
+ u8 a[64];
+ COPY(a, seed, 32); // a[ 0..31] = seed
+ crypto_wipe(seed, 32);
+ COPY(secret_key, a, 32); // secret key = seed
+ crypto_sha512(a, a, 32); // a[ 0..31] = scalar
+ crypto_eddsa_trim_scalar(a, a); // a[ 0..31] = trimmed scalar
+ crypto_eddsa_scalarbase(public_key, a); // public key = [trimmed scalar]B
+ COPY(secret_key + 32, public_key, 32); // secret key includes public half
+ WIPE_BUFFER(a);
+}
+
+static void hash_reduce(u8 h[32],
+ const u8 *a, size_t a_size,
+ const u8 *b, size_t b_size,
+ const u8 *c, size_t c_size,
+ const u8 *d, size_t d_size)
+{
+ u8 hash[64];
+ crypto_sha512_ctx ctx;
+ crypto_sha512_init (&ctx);
+ crypto_sha512_update(&ctx, a, a_size);
+ crypto_sha512_update(&ctx, b, b_size);
+ crypto_sha512_update(&ctx, c, c_size);
+ crypto_sha512_update(&ctx, d, d_size);
+ crypto_sha512_final (&ctx, hash);
+ crypto_eddsa_reduce(h, hash);
+}
+
+static void ed25519_dom_sign(u8 signature [64], const u8 secret_key[32],
+ const u8 *dom, size_t dom_size,
+ const u8 *message, size_t message_size)
+{
+ u8 a[64]; // secret scalar and prefix
+ u8 r[32]; // secret deterministic "random" nonce
+ u8 h[32]; // publically verifiable hash of the message (not wiped)
+ u8 R[32]; // first half of the signature (allows overlapping inputs)
+ const u8 *pk = secret_key + 32;
+
+ crypto_sha512(a, secret_key, 32);
+ crypto_eddsa_trim_scalar(a, a);
+ hash_reduce(r, dom, dom_size, a + 32, 32, message, message_size, 0, 0);
+ crypto_eddsa_scalarbase(R, r);
+ hash_reduce(h, dom, dom_size, R, 32, pk, 32, message, message_size);
+ COPY(signature, R, 32);
+ crypto_eddsa_mul_add(signature + 32, h, a, r);
+
+ WIPE_BUFFER(a);
+ WIPE_BUFFER(r);
+}
+
+void crypto_ed25519_sign(u8 signature [64], const u8 secret_key[64],
+ const u8 *message, size_t message_size)
+{
+ ed25519_dom_sign(signature, secret_key, 0, 0, message, message_size);
+}
+
+int crypto_ed25519_check(const u8 signature[64], const u8 public_key[32],
+ const u8 *msg, size_t msg_size)
+{
+ u8 h_ram[32];
+ hash_reduce(h_ram, signature, 32, public_key, 32, msg, msg_size, 0, 0);
+ return crypto_eddsa_check_equation(signature, public_key, h_ram);
+}
+
+static const u8 domain[34] = "SigEd25519 no Ed25519 collisions\1";
+
+void crypto_ed25519_ph_sign(uint8_t signature[64], const uint8_t secret_key[64],
+ const uint8_t message_hash[64])
+{
+ ed25519_dom_sign(signature, secret_key, domain, sizeof(domain),
+ message_hash, 64);
+}
+
+int crypto_ed25519_ph_check(const uint8_t sig[64], const uint8_t pk[32],
+ const uint8_t msg_hash[64])
+{
+ u8 h_ram[32];
+ hash_reduce(h_ram, domain, sizeof(domain), sig, 32, pk, 32, msg_hash, 64);
+ return crypto_eddsa_check_equation(sig, pk, h_ram);
+}
+
+
+#ifdef MONOCYPHER_CPP_NAMESPACE
+}
+#endif
diff --git a/hw/application_fpga/tkey-libs/monocypher/monocypher-ed25519.h b/hw/application_fpga/tkey-libs/monocypher/monocypher-ed25519.h
new file mode 100644
index 0000000..1e6d705
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/monocypher/monocypher-ed25519.h
@@ -0,0 +1,140 @@
+// Monocypher version 4.0.2
+//
+// This file is dual-licensed. Choose whichever licence you want from
+// the two licences listed below.
+//
+// The first licence is a regular 2-clause BSD licence. The second licence
+// is the CC-0 from Creative Commons. It is intended to release Monocypher
+// to the public domain. The BSD licence serves as a fallback option.
+//
+// SPDX-License-Identifier: BSD-2-Clause OR CC0-1.0
+//
+// ------------------------------------------------------------------------
+//
+// Copyright (c) 2017-2019, Loup Vaillant
+// All rights reserved.
+//
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the
+// distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// ------------------------------------------------------------------------
+//
+// Written in 2017-2019 by Loup Vaillant
+//
+// To the extent possible under law, the author(s) have dedicated all copyright
+// and related neighboring rights to this software to the public domain
+// worldwide. This software is distributed without any warranty.
+//
+// You should have received a copy of the CC0 Public Domain Dedication along
+// with this software. If not, see
+//
+
+#ifndef ED25519_H
+#define ED25519_H
+
+#include "monocypher.h"
+
+#ifdef MONOCYPHER_CPP_NAMESPACE
+namespace MONOCYPHER_CPP_NAMESPACE {
+#elif defined(__cplusplus)
+extern "C" {
+#endif
+
+////////////////////////
+/// Type definitions ///
+////////////////////////
+
+// Do not rely on the size or content on any of those types,
+// they may change without notice.
+typedef struct {
+ uint64_t hash[8];
+ uint64_t input[16];
+ uint64_t input_size[2];
+ size_t input_idx;
+} crypto_sha512_ctx;
+
+typedef struct {
+ uint8_t key[128];
+ crypto_sha512_ctx ctx;
+} crypto_sha512_hmac_ctx;
+
+
+// SHA 512
+// -------
+void crypto_sha512_init (crypto_sha512_ctx *ctx);
+void crypto_sha512_update(crypto_sha512_ctx *ctx,
+ const uint8_t *message, size_t message_size);
+void crypto_sha512_final (crypto_sha512_ctx *ctx, uint8_t hash[64]);
+void crypto_sha512(uint8_t hash[64],
+ const uint8_t *message, size_t message_size);
+
+// SHA 512 HMAC
+// ------------
+void crypto_sha512_hmac_init(crypto_sha512_hmac_ctx *ctx,
+ const uint8_t *key, size_t key_size);
+void crypto_sha512_hmac_update(crypto_sha512_hmac_ctx *ctx,
+ const uint8_t *message, size_t message_size);
+void crypto_sha512_hmac_final(crypto_sha512_hmac_ctx *ctx, uint8_t hmac[64]);
+void crypto_sha512_hmac(uint8_t hmac[64],
+ const uint8_t *key , size_t key_size,
+ const uint8_t *message, size_t message_size);
+
+// SHA 512 HKDF
+// ------------
+void crypto_sha512_hkdf_expand(uint8_t *okm, size_t okm_size,
+ const uint8_t *prk, size_t prk_size,
+ const uint8_t *info, size_t info_size);
+void crypto_sha512_hkdf(uint8_t *okm , size_t okm_size,
+ const uint8_t *ikm , size_t ikm_size,
+ const uint8_t *salt, size_t salt_size,
+ const uint8_t *info, size_t info_size);
+
+// Ed25519
+// -------
+// Signatures (EdDSA with curve25519 + SHA-512)
+// --------------------------------------------
+void crypto_ed25519_key_pair(uint8_t secret_key[64],
+ uint8_t public_key[32],
+ uint8_t seed[32]);
+void crypto_ed25519_sign(uint8_t signature [64],
+ const uint8_t secret_key[64],
+ const uint8_t *message, size_t message_size);
+int crypto_ed25519_check(const uint8_t signature [64],
+ const uint8_t public_key[32],
+ const uint8_t *message, size_t message_size);
+
+// Pre-hash variants
+void crypto_ed25519_ph_sign(uint8_t signature [64],
+ const uint8_t secret_key [64],
+ const uint8_t message_hash[64]);
+int crypto_ed25519_ph_check(const uint8_t signature [64],
+ const uint8_t public_key [32],
+ const uint8_t message_hash[64]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ED25519_H
diff --git a/hw/application_fpga/tkey-libs/monocypher/monocypher.c b/hw/application_fpga/tkey-libs/monocypher/monocypher.c
new file mode 100644
index 0000000..d3930fb
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/monocypher/monocypher.c
@@ -0,0 +1,2956 @@
+// Monocypher version 4.0.2
+//
+// This file is dual-licensed. Choose whichever licence you want from
+// the two licences listed below.
+//
+// The first licence is a regular 2-clause BSD licence. The second licence
+// is the CC-0 from Creative Commons. It is intended to release Monocypher
+// to the public domain. The BSD licence serves as a fallback option.
+//
+// SPDX-License-Identifier: BSD-2-Clause OR CC0-1.0
+//
+// ------------------------------------------------------------------------
+//
+// Copyright (c) 2017-2020, Loup Vaillant
+// All rights reserved.
+//
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the
+// distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// ------------------------------------------------------------------------
+//
+// Written in 2017-2020 by Loup Vaillant
+//
+// To the extent possible under law, the author(s) have dedicated all copyright
+// and related neighboring rights to this software to the public domain
+// worldwide. This software is distributed without any warranty.
+//
+// You should have received a copy of the CC0 Public Domain Dedication along
+// with this software. If not, see
+//
+
+#include "monocypher.h"
+
+#ifdef MONOCYPHER_CPP_NAMESPACE
+namespace MONOCYPHER_CPP_NAMESPACE {
+#endif
+
+/////////////////
+/// Utilities ///
+/////////////////
+#define FOR_T(type, i, start, end) for (type i = (start); i < (end); i++)
+#define FOR(i, start, end) FOR_T(size_t, i, start, end)
+#define COPY(dst, src, size) FOR(_i_, 0, size) (dst)[_i_] = (src)[_i_]
+#define ZERO(buf, size) FOR(_i_, 0, size) (buf)[_i_] = 0
+#define WIPE_CTX(ctx) crypto_wipe(ctx , sizeof(*(ctx)))
+#define WIPE_BUFFER(buffer) crypto_wipe(buffer, sizeof(buffer))
+#define MIN(a, b) ((a) <= (b) ? (a) : (b))
+#define MAX(a, b) ((a) >= (b) ? (a) : (b))
+
+typedef int8_t i8;
+typedef uint8_t u8;
+typedef int16_t i16;
+typedef uint32_t u32;
+typedef int32_t i32;
+typedef int64_t i64;
+typedef uint64_t u64;
+
+static const u8 zero[128] = {0};
+
+// returns the smallest positive integer y such that
+// (x + y) % pow_2 == 0
+// Basically, y is the "gap" missing to align x.
+// Only works when pow_2 is a power of 2.
+// Note: we use ~x+1 instead of -x to avoid compiler warnings
+static size_t gap(size_t x, size_t pow_2)
+{
+ return (~x + 1) & (pow_2 - 1);
+}
+
+static u32 load24_le(const u8 s[3])
+{
+ return
+ ((u32)s[0] << 0) |
+ ((u32)s[1] << 8) |
+ ((u32)s[2] << 16);
+}
+
+static u32 load32_le(const u8 s[4])
+{
+ return
+ ((u32)s[0] << 0) |
+ ((u32)s[1] << 8) |
+ ((u32)s[2] << 16) |
+ ((u32)s[3] << 24);
+}
+
+static u64 load64_le(const u8 s[8])
+{
+ return load32_le(s) | ((u64)load32_le(s+4) << 32);
+}
+
+static void store32_le(u8 out[4], u32 in)
+{
+ out[0] = in & 0xff;
+ out[1] = (in >> 8) & 0xff;
+ out[2] = (in >> 16) & 0xff;
+ out[3] = (in >> 24) & 0xff;
+}
+
+static void store64_le(u8 out[8], u64 in)
+{
+ store32_le(out , (u32)in );
+ store32_le(out + 4, in >> 32);
+}
+
+static void load32_le_buf (u32 *dst, const u8 *src, size_t size) {
+ FOR(i, 0, size) { dst[i] = load32_le(src + i*4); }
+}
+static void load64_le_buf (u64 *dst, const u8 *src, size_t size) {
+ FOR(i, 0, size) { dst[i] = load64_le(src + i*8); }
+}
+static void store32_le_buf(u8 *dst, const u32 *src, size_t size) {
+ FOR(i, 0, size) { store32_le(dst + i*4, src[i]); }
+}
+static void store64_le_buf(u8 *dst, const u64 *src, size_t size) {
+ FOR(i, 0, size) { store64_le(dst + i*8, src[i]); }
+}
+
+static u64 rotr64(u64 x, u64 n) { return (x >> n) ^ (x << (64 - n)); }
+static u32 rotl32(u32 x, u32 n) { return (x << n) ^ (x >> (32 - n)); }
+
+static int neq0(u64 diff)
+{
+ // constant time comparison to zero
+ // return diff != 0 ? -1 : 0
+ u64 half = (diff >> 32) | ((u32)diff);
+ return (1 & ((half - 1) >> 32)) - 1;
+}
+
+static u64 x16(const u8 a[16], const u8 b[16])
+{
+ return (load64_le(a + 0) ^ load64_le(b + 0))
+ | (load64_le(a + 8) ^ load64_le(b + 8));
+}
+static u64 x32(const u8 a[32],const u8 b[32]){return x16(a,b)| x16(a+16, b+16);}
+static u64 x64(const u8 a[64],const u8 b[64]){return x32(a,b)| x32(a+32, b+32);}
+int crypto_verify16(const u8 a[16], const u8 b[16]){ return neq0(x16(a, b)); }
+int crypto_verify32(const u8 a[32], const u8 b[32]){ return neq0(x32(a, b)); }
+int crypto_verify64(const u8 a[64], const u8 b[64]){ return neq0(x64(a, b)); }
+
+void crypto_wipe(void *secret, size_t size)
+{
+ volatile u8 *v_secret = (u8*)secret;
+ ZERO(v_secret, size);
+}
+
+/////////////////
+/// Chacha 20 ///
+/////////////////
+#define QUARTERROUND(a, b, c, d) \
+ a += b; d = rotl32(d ^ a, 16); \
+ c += d; b = rotl32(b ^ c, 12); \
+ a += b; d = rotl32(d ^ a, 8); \
+ c += d; b = rotl32(b ^ c, 7)
+
+static void chacha20_rounds(u32 out[16], const u32 in[16])
+{
+ // The temporary variables make Chacha20 10% faster.
+ u32 t0 = in[ 0]; u32 t1 = in[ 1]; u32 t2 = in[ 2]; u32 t3 = in[ 3];
+ u32 t4 = in[ 4]; u32 t5 = in[ 5]; u32 t6 = in[ 6]; u32 t7 = in[ 7];
+ u32 t8 = in[ 8]; u32 t9 = in[ 9]; u32 t10 = in[10]; u32 t11 = in[11];
+ u32 t12 = in[12]; u32 t13 = in[13]; u32 t14 = in[14]; u32 t15 = in[15];
+
+ FOR (i, 0, 10) { // 20 rounds, 2 rounds per loop.
+ QUARTERROUND(t0, t4, t8 , t12); // column 0
+ QUARTERROUND(t1, t5, t9 , t13); // column 1
+ QUARTERROUND(t2, t6, t10, t14); // column 2
+ QUARTERROUND(t3, t7, t11, t15); // column 3
+ QUARTERROUND(t0, t5, t10, t15); // diagonal 0
+ QUARTERROUND(t1, t6, t11, t12); // diagonal 1
+ QUARTERROUND(t2, t7, t8 , t13); // diagonal 2
+ QUARTERROUND(t3, t4, t9 , t14); // diagonal 3
+ }
+ out[ 0] = t0; out[ 1] = t1; out[ 2] = t2; out[ 3] = t3;
+ out[ 4] = t4; out[ 5] = t5; out[ 6] = t6; out[ 7] = t7;
+ out[ 8] = t8; out[ 9] = t9; out[10] = t10; out[11] = t11;
+ out[12] = t12; out[13] = t13; out[14] = t14; out[15] = t15;
+}
+
+static const u8 *chacha20_constant = (const u8*)"expand 32-byte k"; // 16 bytes
+
+void crypto_chacha20_h(u8 out[32], const u8 key[32], const u8 in [16])
+{
+ u32 block[16];
+ load32_le_buf(block , chacha20_constant, 4);
+ load32_le_buf(block + 4, key , 8);
+ load32_le_buf(block + 12, in , 4);
+
+ chacha20_rounds(block, block);
+
+ // prevent reversal of the rounds by revealing only half of the buffer.
+ store32_le_buf(out , block , 4); // constant
+ store32_le_buf(out+16, block+12, 4); // counter and nonce
+ WIPE_BUFFER(block);
+}
+
+u64 crypto_chacha20_djb(u8 *cipher_text, const u8 *plain_text,
+ size_t text_size, const u8 key[32], const u8 nonce[8],
+ u64 ctr)
+{
+ u32 input[16];
+ load32_le_buf(input , chacha20_constant, 4);
+ load32_le_buf(input + 4, key , 8);
+ load32_le_buf(input + 14, nonce , 2);
+ input[12] = (u32) ctr;
+ input[13] = (u32)(ctr >> 32);
+
+ // Whole blocks
+ u32 pool[16];
+ size_t nb_blocks = text_size >> 6;
+ FOR (i, 0, nb_blocks) {
+ chacha20_rounds(pool, input);
+ if (plain_text != 0) {
+ FOR (j, 0, 16) {
+ u32 p = pool[j] + input[j];
+ store32_le(cipher_text, p ^ load32_le(plain_text));
+ cipher_text += 4;
+ plain_text += 4;
+ }
+ } else {
+ FOR (j, 0, 16) {
+ u32 p = pool[j] + input[j];
+ store32_le(cipher_text, p);
+ cipher_text += 4;
+ }
+ }
+ input[12]++;
+ if (input[12] == 0) {
+ input[13]++;
+ }
+ }
+ text_size &= 63;
+
+ // Last (incomplete) block
+ if (text_size > 0) {
+ if (plain_text == 0) {
+ plain_text = zero;
+ }
+ chacha20_rounds(pool, input);
+ u8 tmp[64];
+ FOR (i, 0, 16) {
+ store32_le(tmp + i*4, pool[i] + input[i]);
+ }
+ FOR (i, 0, text_size) {
+ cipher_text[i] = tmp[i] ^ plain_text[i];
+ }
+ WIPE_BUFFER(tmp);
+ }
+ ctr = input[12] + ((u64)input[13] << 32) + (text_size > 0);
+
+ WIPE_BUFFER(pool);
+ WIPE_BUFFER(input);
+ return ctr;
+}
+
+u32 crypto_chacha20_ietf(u8 *cipher_text, const u8 *plain_text,
+ size_t text_size,
+ const u8 key[32], const u8 nonce[12], u32 ctr)
+{
+ u64 big_ctr = ctr + ((u64)load32_le(nonce) << 32);
+ return (u32)crypto_chacha20_djb(cipher_text, plain_text, text_size,
+ key, nonce + 4, big_ctr);
+}
+
+u64 crypto_chacha20_x(u8 *cipher_text, const u8 *plain_text,
+ size_t text_size,
+ const u8 key[32], const u8 nonce[24], u64 ctr)
+{
+ u8 sub_key[32];
+ crypto_chacha20_h(sub_key, key, nonce);
+ ctr = crypto_chacha20_djb(cipher_text, plain_text, text_size,
+ sub_key, nonce + 16, ctr);
+ WIPE_BUFFER(sub_key);
+ return ctr;
+}
+
+/////////////////
+/// Poly 1305 ///
+/////////////////
+
+// h = (h + c) * r
+// preconditions:
+// ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff
+// ctx->r <= 0ffffffc_0ffffffc_0ffffffc_0fffffff
+// end <= 1
+// Postcondition:
+// ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff
+static void poly_blocks(crypto_poly1305_ctx *ctx, const u8 *in,
+ size_t nb_blocks, unsigned end)
+{
+ // Local all the things!
+ const u32 r0 = ctx->r[0];
+ const u32 r1 = ctx->r[1];
+ const u32 r2 = ctx->r[2];
+ const u32 r3 = ctx->r[3];
+ const u32 rr0 = (r0 >> 2) * 5; // lose 2 bits...
+ const u32 rr1 = (r1 >> 2) + r1; // rr1 == (r1 >> 2) * 5
+ const u32 rr2 = (r2 >> 2) + r2; // rr1 == (r2 >> 2) * 5
+ const u32 rr3 = (r3 >> 2) + r3; // rr1 == (r3 >> 2) * 5
+ const u32 rr4 = r0 & 3; // ...recover 2 bits
+ u32 h0 = ctx->h[0];
+ u32 h1 = ctx->h[1];
+ u32 h2 = ctx->h[2];
+ u32 h3 = ctx->h[3];
+ u32 h4 = ctx->h[4];
+
+ FOR (i, 0, nb_blocks) {
+ // h + c, without carry propagation
+ const u64 s0 = (u64)h0 + load32_le(in); in += 4;
+ const u64 s1 = (u64)h1 + load32_le(in); in += 4;
+ const u64 s2 = (u64)h2 + load32_le(in); in += 4;
+ const u64 s3 = (u64)h3 + load32_le(in); in += 4;
+ const u32 s4 = h4 + end;
+
+ // (h + c) * r, without carry propagation
+ const u64 x0 = s0*r0+ s1*rr3+ s2*rr2+ s3*rr1+ s4*rr0;
+ const u64 x1 = s0*r1+ s1*r0 + s2*rr3+ s3*rr2+ s4*rr1;
+ const u64 x2 = s0*r2+ s1*r1 + s2*r0 + s3*rr3+ s4*rr2;
+ const u64 x3 = s0*r3+ s1*r2 + s2*r1 + s3*r0 + s4*rr3;
+ const u32 x4 = s4*rr4;
+
+ // partial reduction modulo 2^130 - 5
+ const u32 u5 = x4 + (x3 >> 32); // u5 <= 7ffffff5
+ const u64 u0 = (u5 >> 2) * 5 + (x0 & 0xffffffff);
+ const u64 u1 = (u0 >> 32) + (x1 & 0xffffffff) + (x0 >> 32);
+ const u64 u2 = (u1 >> 32) + (x2 & 0xffffffff) + (x1 >> 32);
+ const u64 u3 = (u2 >> 32) + (x3 & 0xffffffff) + (x2 >> 32);
+ const u32 u4 = (u3 >> 32) + (u5 & 3); // u4 <= 4
+
+ // Update the hash
+ h0 = u0 & 0xffffffff;
+ h1 = u1 & 0xffffffff;
+ h2 = u2 & 0xffffffff;
+ h3 = u3 & 0xffffffff;
+ h4 = u4;
+ }
+ ctx->h[0] = h0;
+ ctx->h[1] = h1;
+ ctx->h[2] = h2;
+ ctx->h[3] = h3;
+ ctx->h[4] = h4;
+}
+
+void crypto_poly1305_init(crypto_poly1305_ctx *ctx, const u8 key[32])
+{
+ ZERO(ctx->h, 5); // Initial hash is zero
+ ctx->c_idx = 0;
+ // load r and pad (r has some of its bits cleared)
+ load32_le_buf(ctx->r , key , 4);
+ load32_le_buf(ctx->pad, key+16, 4);
+ FOR (i, 0, 1) { ctx->r[i] &= 0x0fffffff; }
+ FOR (i, 1, 4) { ctx->r[i] &= 0x0ffffffc; }
+}
+
+void crypto_poly1305_update(crypto_poly1305_ctx *ctx,
+ const u8 *message, size_t message_size)
+{
+ // Avoid undefined NULL pointer increments with empty messages
+ if (message_size == 0) {
+ return;
+ }
+
+ // Align ourselves with block boundaries
+ size_t aligned = MIN(gap(ctx->c_idx, 16), message_size);
+ FOR (i, 0, aligned) {
+ ctx->c[ctx->c_idx] = *message;
+ ctx->c_idx++;
+ message++;
+ message_size--;
+ }
+
+ // If block is complete, process it
+ if (ctx->c_idx == 16) {
+ poly_blocks(ctx, ctx->c, 1, 1);
+ ctx->c_idx = 0;
+ }
+
+ // Process the message block by block
+ size_t nb_blocks = message_size >> 4;
+ poly_blocks(ctx, message, nb_blocks, 1);
+ message += nb_blocks << 4;
+ message_size &= 15;
+
+ // remaining bytes (we never complete a block here)
+ FOR (i, 0, message_size) {
+ ctx->c[ctx->c_idx] = message[i];
+ ctx->c_idx++;
+ }
+}
+
+void crypto_poly1305_final(crypto_poly1305_ctx *ctx, u8 mac[16])
+{
+ // Process the last block (if any)
+ // We move the final 1 according to remaining input length
+ // (this will add less than 2^130 to the last input block)
+ if (ctx->c_idx != 0) {
+ ZERO(ctx->c + ctx->c_idx, 16 - ctx->c_idx);
+ ctx->c[ctx->c_idx] = 1;
+ poly_blocks(ctx, ctx->c, 1, 0);
+ }
+
+ // check if we should subtract 2^130-5 by performing the
+ // corresponding carry propagation.
+ u64 c = 5;
+ FOR (i, 0, 4) {
+ c += ctx->h[i];
+ c >>= 32;
+ }
+ c += ctx->h[4];
+ c = (c >> 2) * 5; // shift the carry back to the beginning
+ // c now indicates how many times we should subtract 2^130-5 (0 or 1)
+ FOR (i, 0, 4) {
+ c += (u64)ctx->h[i] + ctx->pad[i];
+ store32_le(mac + i*4, (u32)c);
+ c = c >> 32;
+ }
+ WIPE_CTX(ctx);
+}
+
+void crypto_poly1305(u8 mac[16], const u8 *message,
+ size_t message_size, const u8 key[32])
+{
+ crypto_poly1305_ctx ctx;
+ crypto_poly1305_init (&ctx, key);
+ crypto_poly1305_update(&ctx, message, message_size);
+ crypto_poly1305_final (&ctx, mac);
+}
+
+////////////////
+/// BLAKE2 b ///
+////////////////
+static const u64 iv[8] = {
+ 0x6a09e667f3bcc908, 0xbb67ae8584caa73b,
+ 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
+ 0x510e527fade682d1, 0x9b05688c2b3e6c1f,
+ 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179,
+};
+
+static void blake2b_compress(crypto_blake2b_ctx *ctx, int is_last_block)
+{
+ static const u8 sigma[12][16] = {
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
+ { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
+ { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 },
+ { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
+ { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 },
+ { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
+ { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 },
+ { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
+ { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 },
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 },
+ };
+
+ // increment input offset
+ u64 *x = ctx->input_offset;
+ size_t y = ctx->input_idx;
+ x[0] += y;
+ if (x[0] < y) {
+ x[1]++;
+ }
+
+ // init work vector
+ u64 v0 = ctx->hash[0]; u64 v8 = iv[0];
+ u64 v1 = ctx->hash[1]; u64 v9 = iv[1];
+ u64 v2 = ctx->hash[2]; u64 v10 = iv[2];
+ u64 v3 = ctx->hash[3]; u64 v11 = iv[3];
+ u64 v4 = ctx->hash[4]; u64 v12 = iv[4] ^ ctx->input_offset[0];
+ u64 v5 = ctx->hash[5]; u64 v13 = iv[5] ^ ctx->input_offset[1];
+ u64 v6 = ctx->hash[6]; u64 v14 = iv[6] ^ (u64)~(is_last_block - 1);
+ u64 v7 = ctx->hash[7]; u64 v15 = iv[7];
+
+ // mangle work vector
+ u64 *input = ctx->input;
+#define BLAKE2_G(a, b, c, d, x, y) \
+ a += b + x; d = rotr64(d ^ a, 32); \
+ c += d; b = rotr64(b ^ c, 24); \
+ a += b + y; d = rotr64(d ^ a, 16); \
+ c += d; b = rotr64(b ^ c, 63)
+#define BLAKE2_ROUND(i) \
+ BLAKE2_G(v0, v4, v8 , v12, input[sigma[i][ 0]], input[sigma[i][ 1]]); \
+ BLAKE2_G(v1, v5, v9 , v13, input[sigma[i][ 2]], input[sigma[i][ 3]]); \
+ BLAKE2_G(v2, v6, v10, v14, input[sigma[i][ 4]], input[sigma[i][ 5]]); \
+ BLAKE2_G(v3, v7, v11, v15, input[sigma[i][ 6]], input[sigma[i][ 7]]); \
+ BLAKE2_G(v0, v5, v10, v15, input[sigma[i][ 8]], input[sigma[i][ 9]]); \
+ BLAKE2_G(v1, v6, v11, v12, input[sigma[i][10]], input[sigma[i][11]]); \
+ BLAKE2_G(v2, v7, v8 , v13, input[sigma[i][12]], input[sigma[i][13]]); \
+ BLAKE2_G(v3, v4, v9 , v14, input[sigma[i][14]], input[sigma[i][15]])
+
+#ifdef BLAKE2_NO_UNROLLING
+ FOR (i, 0, 12) {
+ BLAKE2_ROUND(i);
+ }
+#else
+ BLAKE2_ROUND(0); BLAKE2_ROUND(1); BLAKE2_ROUND(2); BLAKE2_ROUND(3);
+ BLAKE2_ROUND(4); BLAKE2_ROUND(5); BLAKE2_ROUND(6); BLAKE2_ROUND(7);
+ BLAKE2_ROUND(8); BLAKE2_ROUND(9); BLAKE2_ROUND(10); BLAKE2_ROUND(11);
+#endif
+
+ // update hash
+ ctx->hash[0] ^= v0 ^ v8; ctx->hash[1] ^= v1 ^ v9;
+ ctx->hash[2] ^= v2 ^ v10; ctx->hash[3] ^= v3 ^ v11;
+ ctx->hash[4] ^= v4 ^ v12; ctx->hash[5] ^= v5 ^ v13;
+ ctx->hash[6] ^= v6 ^ v14; ctx->hash[7] ^= v7 ^ v15;
+}
+
+void crypto_blake2b_keyed_init(crypto_blake2b_ctx *ctx, size_t hash_size,
+ const u8 *key, size_t key_size)
+{
+ // initial hash
+ COPY(ctx->hash, iv, 8);
+ ctx->hash[0] ^= 0x01010000 ^ (key_size << 8) ^ hash_size;
+
+ ctx->input_offset[0] = 0; // beginning of the input, no offset
+ ctx->input_offset[1] = 0; // beginning of the input, no offset
+ ctx->hash_size = hash_size;
+ ctx->input_idx = 0;
+ ZERO(ctx->input, 16);
+
+ // if there is a key, the first block is that key (padded with zeroes)
+ if (key_size > 0) {
+ u8 key_block[128] = {0};
+ COPY(key_block, key, key_size);
+ // same as calling crypto_blake2b_update(ctx, key_block , 128)
+ load64_le_buf(ctx->input, key_block, 16);
+ ctx->input_idx = 128;
+ }
+}
+
+void crypto_blake2b_init(crypto_blake2b_ctx *ctx, size_t hash_size)
+{
+ crypto_blake2b_keyed_init(ctx, hash_size, 0, 0);
+}
+
+void crypto_blake2b_update(crypto_blake2b_ctx *ctx,
+ const u8 *message, size_t message_size)
+{
+ // Avoid undefined NULL pointer increments with empty messages
+ if (message_size == 0) {
+ return;
+ }
+
+ // Align with word boundaries
+ if ((ctx->input_idx & 7) != 0) {
+ size_t nb_bytes = MIN(gap(ctx->input_idx, 8), message_size);
+ size_t word = ctx->input_idx >> 3;
+ size_t byte = ctx->input_idx & 7;
+ FOR (i, 0, nb_bytes) {
+ ctx->input[word] |= (u64)message[i] << ((byte + i) << 3);
+ }
+ ctx->input_idx += nb_bytes;
+ message += nb_bytes;
+ message_size -= nb_bytes;
+ }
+
+ // Align with block boundaries (faster than byte by byte)
+ if ((ctx->input_idx & 127) != 0) {
+ size_t nb_words = MIN(gap(ctx->input_idx, 128), message_size) >> 3;
+ load64_le_buf(ctx->input + (ctx->input_idx >> 3), message, nb_words);
+ ctx->input_idx += nb_words << 3;
+ message += nb_words << 3;
+ message_size -= nb_words << 3;
+ }
+
+ // Process block by block
+ size_t nb_blocks = message_size >> 7;
+ FOR (i, 0, nb_blocks) {
+ if (ctx->input_idx == 128) {
+ blake2b_compress(ctx, 0);
+ }
+ load64_le_buf(ctx->input, message, 16);
+ message += 128;
+ ctx->input_idx = 128;
+ }
+ message_size &= 127;
+
+ if (message_size != 0) {
+ // Compress block & flush input buffer as needed
+ if (ctx->input_idx == 128) {
+ blake2b_compress(ctx, 0);
+ ctx->input_idx = 0;
+ }
+ if (ctx->input_idx == 0) {
+ ZERO(ctx->input, 16);
+ }
+ // Fill remaining words (faster than byte by byte)
+ size_t nb_words = message_size >> 3;
+ load64_le_buf(ctx->input, message, nb_words);
+ ctx->input_idx += nb_words << 3;
+ message += nb_words << 3;
+ message_size -= nb_words << 3;
+
+ // Fill remaining bytes
+ FOR (i, 0, message_size) {
+ size_t word = ctx->input_idx >> 3;
+ size_t byte = ctx->input_idx & 7;
+ ctx->input[word] |= (u64)message[i] << (byte << 3);
+ ctx->input_idx++;
+ }
+ }
+}
+
+void crypto_blake2b_final(crypto_blake2b_ctx *ctx, u8 *hash)
+{
+ blake2b_compress(ctx, 1); // compress the last block
+ size_t hash_size = MIN(ctx->hash_size, 64);
+ size_t nb_words = hash_size >> 3;
+ store64_le_buf(hash, ctx->hash, nb_words);
+ FOR (i, nb_words << 3, hash_size) {
+ hash[i] = (ctx->hash[i >> 3] >> (8 * (i & 7))) & 0xff;
+ }
+ WIPE_CTX(ctx);
+}
+
+void crypto_blake2b_keyed(u8 *hash, size_t hash_size,
+ const u8 *key, size_t key_size,
+ const u8 *message, size_t message_size)
+{
+ crypto_blake2b_ctx ctx;
+ crypto_blake2b_keyed_init(&ctx, hash_size, key, key_size);
+ crypto_blake2b_update (&ctx, message, message_size);
+ crypto_blake2b_final (&ctx, hash);
+}
+
+void crypto_blake2b(u8 *hash, size_t hash_size, const u8 *msg, size_t msg_size)
+{
+ crypto_blake2b_keyed(hash, hash_size, 0, 0, msg, msg_size);
+}
+
+//////////////
+/// Argon2 ///
+//////////////
+// references to R, Z, Q etc. come from the spec
+
+// Argon2 operates on 1024 byte blocks.
+typedef struct { u64 a[128]; } blk;
+
+// updates a BLAKE2 hash with a 32 bit word, little endian.
+static void blake_update_32(crypto_blake2b_ctx *ctx, u32 input)
+{
+ u8 buf[4];
+ store32_le(buf, input);
+ crypto_blake2b_update(ctx, buf, 4);
+ WIPE_BUFFER(buf);
+}
+
+static void blake_update_32_buf(crypto_blake2b_ctx *ctx,
+ const u8 *buf, u32 size)
+{
+ blake_update_32(ctx, size);
+ crypto_blake2b_update(ctx, buf, size);
+}
+
+
+static void copy_block(blk *o,const blk*in){FOR(i, 0, 128) o->a[i] = in->a[i];}
+static void xor_block(blk *o,const blk*in){FOR(i, 0, 128) o->a[i] ^= in->a[i];}
+
+// Hash with a virtually unlimited digest size.
+// Doesn't extract more entropy than the base hash function.
+// Mainly used for filling a whole kilobyte block with pseudo-random bytes.
+// (One could use a stream cipher with a seed hash as the key, but
+// this would introduce another dependency —and point of failure.)
+static void extended_hash(u8 *digest, u32 digest_size,
+ const u8 *input , u32 input_size)
+{
+ crypto_blake2b_ctx ctx;
+ crypto_blake2b_init (&ctx, MIN(digest_size, 64));
+ blake_update_32 (&ctx, digest_size);
+ crypto_blake2b_update(&ctx, input, input_size);
+ crypto_blake2b_final (&ctx, digest);
+
+ if (digest_size > 64) {
+ // the conversion to u64 avoids integer overflow on
+ // ludicrously big hash sizes.
+ u32 r = (u32)(((u64)digest_size + 31) >> 5) - 2;
+ u32 i = 1;
+ u32 in = 0;
+ u32 out = 32;
+ while (i < r) {
+ // Input and output overlap. This is intentional
+ crypto_blake2b(digest + out, 64, digest + in, 64);
+ i += 1;
+ in += 32;
+ out += 32;
+ }
+ crypto_blake2b(digest + out, digest_size - (32 * r), digest + in , 64);
+ }
+}
+
+#define LSB(x) ((u64)(u32)x)
+#define G(a, b, c, d) \
+ a += b + ((LSB(a) * LSB(b)) << 1); d ^= a; d = rotr64(d, 32); \
+ c += d + ((LSB(c) * LSB(d)) << 1); b ^= c; b = rotr64(b, 24); \
+ a += b + ((LSB(a) * LSB(b)) << 1); d ^= a; d = rotr64(d, 16); \
+ c += d + ((LSB(c) * LSB(d)) << 1); b ^= c; b = rotr64(b, 63)
+#define ROUND(v0, v1, v2, v3, v4, v5, v6, v7, \
+ v8, v9, v10, v11, v12, v13, v14, v15) \
+ G(v0, v4, v8, v12); G(v1, v5, v9, v13); \
+ G(v2, v6, v10, v14); G(v3, v7, v11, v15); \
+ G(v0, v5, v10, v15); G(v1, v6, v11, v12); \
+ G(v2, v7, v8, v13); G(v3, v4, v9, v14)
+
+// Core of the compression function G. Computes Z from R in place.
+static void g_rounds(blk *b)
+{
+ // column rounds (work_block = Q)
+ for (int i = 0; i < 128; i += 16) {
+ ROUND(b->a[i ], b->a[i+ 1], b->a[i+ 2], b->a[i+ 3],
+ b->a[i+ 4], b->a[i+ 5], b->a[i+ 6], b->a[i+ 7],
+ b->a[i+ 8], b->a[i+ 9], b->a[i+10], b->a[i+11],
+ b->a[i+12], b->a[i+13], b->a[i+14], b->a[i+15]);
+ }
+ // row rounds (b = Z)
+ for (int i = 0; i < 16; i += 2) {
+ ROUND(b->a[i ], b->a[i+ 1], b->a[i+ 16], b->a[i+ 17],
+ b->a[i+32], b->a[i+33], b->a[i+ 48], b->a[i+ 49],
+ b->a[i+64], b->a[i+65], b->a[i+ 80], b->a[i+ 81],
+ b->a[i+96], b->a[i+97], b->a[i+112], b->a[i+113]);
+ }
+}
+
+const crypto_argon2_extras crypto_argon2_no_extras = { 0, 0, 0, 0 };
+
+void crypto_argon2(u8 *hash, u32 hash_size, void *work_area,
+ crypto_argon2_config config,
+ crypto_argon2_inputs inputs,
+ crypto_argon2_extras extras)
+{
+ const u32 segment_size = config.nb_blocks / config.nb_lanes / 4;
+ const u32 lane_size = segment_size * 4;
+ const u32 nb_blocks = lane_size * config.nb_lanes; // rounding down
+
+ // work area seen as blocks (must be suitably aligned)
+ blk *blocks = (blk*)work_area;
+ {
+ u8 initial_hash[72]; // 64 bytes plus 2 words for future hashes
+ crypto_blake2b_ctx ctx;
+ crypto_blake2b_init (&ctx, 64);
+ blake_update_32 (&ctx, config.nb_lanes ); // p: number of "threads"
+ blake_update_32 (&ctx, hash_size);
+ blake_update_32 (&ctx, config.nb_blocks);
+ blake_update_32 (&ctx, config.nb_passes);
+ blake_update_32 (&ctx, 0x13); // v: version number
+ blake_update_32 (&ctx, config.algorithm); // y: Argon2i, Argon2d...
+ blake_update_32_buf (&ctx, inputs.pass, inputs.pass_size);
+ blake_update_32_buf (&ctx, inputs.salt, inputs.salt_size);
+ blake_update_32_buf (&ctx, extras.key, extras.key_size);
+ blake_update_32_buf (&ctx, extras.ad, extras.ad_size);
+ crypto_blake2b_final(&ctx, initial_hash); // fill 64 first bytes only
+
+ // fill first 2 blocks of each lane
+ u8 hash_area[1024];
+ FOR_T(u32, l, 0, config.nb_lanes) {
+ FOR_T(u32, i, 0, 2) {
+ store32_le(initial_hash + 64, i); // first additional word
+ store32_le(initial_hash + 68, l); // second additional word
+ extended_hash(hash_area, 1024, initial_hash, 72);
+ load64_le_buf(blocks[l * lane_size + i].a, hash_area, 128);
+ }
+ }
+
+ WIPE_BUFFER(initial_hash);
+ WIPE_BUFFER(hash_area);
+ }
+
+ // Argon2i and Argon2id start with constant time indexing
+ int constant_time = config.algorithm != CRYPTO_ARGON2_D;
+
+ // Fill (and re-fill) the rest of the blocks
+ //
+ // Note: even though each segment within the same slice can be
+ // computed in parallel, (one thread per lane), we are computing
+ // them sequentially, because Monocypher doesn't support threads.
+ //
+ // Yet optimal performance (and therefore security) requires one
+ // thread per lane. The only reason Monocypher supports multiple
+ // lanes is compatibility.
+ blk tmp;
+ FOR_T(u32, pass, 0, config.nb_passes) {
+ FOR_T(u32, slice, 0, 4) {
+ // On the first slice of the first pass,
+ // blocks 0 and 1 are already filled, hence pass_offset.
+ u32 pass_offset = pass == 0 && slice == 0 ? 2 : 0;
+ u32 slice_offset = slice * segment_size;
+
+ // Argon2id switches back to non-constant time indexing
+ // after the first two slices of the first pass
+ if (slice == 2 && config.algorithm == CRYPTO_ARGON2_ID) {
+ constant_time = 0;
+ }
+
+ // Each iteration of the following loop may be performed in
+ // a separate thread. All segments must be fully completed
+ // before we start filling the next slice.
+ FOR_T(u32, segment, 0, config.nb_lanes) {
+ blk index_block;
+ u32 index_ctr = 1;
+ FOR_T (u32, block, pass_offset, segment_size) {
+ // Current and previous blocks
+ u32 lane_offset = segment * lane_size;
+ blk *segment_start = blocks + lane_offset + slice_offset;
+ blk *current = segment_start + block;
+ blk *previous =
+ block == 0 && slice_offset == 0
+ ? segment_start + lane_size - 1
+ : segment_start + block - 1;
+
+ u64 index_seed;
+ if (constant_time) {
+ if (block == pass_offset || (block % 128) == 0) {
+ // Fill or refresh deterministic indices block
+
+ // seed the beginning of the block...
+ ZERO(index_block.a, 128);
+ index_block.a[0] = pass;
+ index_block.a[1] = segment;
+ index_block.a[2] = slice;
+ index_block.a[3] = nb_blocks;
+ index_block.a[4] = config.nb_passes;
+ index_block.a[5] = config.algorithm;
+ index_block.a[6] = index_ctr;
+ index_ctr++;
+
+ // ... then shuffle it
+ copy_block(&tmp, &index_block);
+ g_rounds (&index_block);
+ xor_block (&index_block, &tmp);
+ copy_block(&tmp, &index_block);
+ g_rounds (&index_block);
+ xor_block (&index_block, &tmp);
+ }
+ index_seed = index_block.a[block % 128];
+ } else {
+ index_seed = previous->a[0];
+ }
+
+ // Establish the reference set. *Approximately* comprises:
+ // - The last 3 slices (if they exist yet)
+ // - The already constructed blocks in the current segment
+ u32 next_slice = ((slice + 1) % 4) * segment_size;
+ u32 window_start = pass == 0 ? 0 : next_slice;
+ u32 nb_segments = pass == 0 ? slice : 3;
+ u64 lane =
+ pass == 0 && slice == 0
+ ? segment
+ : (index_seed >> 32) % config.nb_lanes;
+ u32 window_size =
+ nb_segments * segment_size +
+ (lane == segment ? block-1 :
+ block == 0 ? (u32)-1 : 0);
+
+ // Find reference block
+ u64 j1 = index_seed & 0xffffffff; // block selector
+ u64 x = (j1 * j1) >> 32;
+ u64 y = (window_size * x) >> 32;
+ u64 z = (window_size - 1) - y;
+ u64 ref = (window_start + z) % lane_size;
+ u32 index = lane * lane_size + (u32)ref;
+ blk *reference = blocks + index;
+
+ // Shuffle the previous & reference block
+ // into the current block
+ copy_block(&tmp, previous);
+ xor_block (&tmp, reference);
+ if (pass == 0) { copy_block(current, &tmp); }
+ else { xor_block (current, &tmp); }
+ g_rounds (&tmp);
+ xor_block (current, &tmp);
+ }
+ }
+ }
+ }
+
+ // Wipe temporary block
+ volatile u64* p = tmp.a;
+ ZERO(p, 128);
+
+ // XOR last blocks of each lane
+ blk *last_block = blocks + lane_size - 1;
+ FOR_T (u32, lane, 1, config.nb_lanes) {
+ blk *next_block = last_block + lane_size;
+ xor_block(next_block, last_block);
+ last_block = next_block;
+ }
+
+ // Serialize last block
+ u8 final_block[1024];
+ store64_le_buf(final_block, last_block->a, 128);
+
+ // Wipe work area
+ p = (u64*)work_area;
+ ZERO(p, 128 * nb_blocks);
+
+ // Hash the very last block with H' into the output hash
+ extended_hash(hash, hash_size, final_block, 1024);
+ WIPE_BUFFER(final_block);
+}
+
+////////////////////////////////////
+/// Arithmetic modulo 2^255 - 19 ///
+////////////////////////////////////
+// Originally taken from SUPERCOP's ref10 implementation.
+// A bit bigger than TweetNaCl, over 4 times faster.
+
+// field element
+typedef i32 fe[10];
+
+// field constants
+//
+// fe_one : 1
+// sqrtm1 : sqrt(-1)
+// d : -121665 / 121666
+// D2 : 2 * -121665 / 121666
+// lop_x, lop_y: low order point in Edwards coordinates
+// ufactor : -sqrt(-1) * 2
+// A2 : 486662^2 (A squared)
+static const fe fe_one = {1};
+static const fe sqrtm1 = {
+ -32595792, -7943725, 9377950, 3500415, 12389472,
+ -272473, -25146209, -2005654, 326686, 11406482,
+};
+static const fe d = {
+ -10913610, 13857413, -15372611, 6949391, 114729,
+ -8787816, -6275908, -3247719, -18696448, -12055116,
+};
+static const fe D2 = {
+ -21827239, -5839606, -30745221, 13898782, 229458,
+ 15978800, -12551817, -6495438, 29715968, 9444199,
+};
+static const fe lop_x = {
+ 21352778, 5345713, 4660180, -8347857, 24143090,
+ 14568123, 30185756, -12247770, -33528939, 8345319,
+};
+static const fe lop_y = {
+ -6952922, -1265500, 6862341, -7057498, -4037696,
+ -5447722, 31680899, -15325402, -19365852, 1569102,
+};
+static const fe ufactor = {
+ -1917299, 15887451, -18755900, -7000830, -24778944,
+ 544946, -16816446, 4011309, -653372, 10741468,
+};
+static const fe A2 = {
+ 12721188, 3529, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static void fe_0(fe h) { ZERO(h , 10); }
+static void fe_1(fe h) { h[0] = 1; ZERO(h+1, 9); }
+
+static void fe_copy(fe h,const fe f ){FOR(i,0,10) h[i] = f[i]; }
+static void fe_neg (fe h,const fe f ){FOR(i,0,10) h[i] = -f[i]; }
+static void fe_add (fe h,const fe f,const fe g){FOR(i,0,10) h[i] = f[i] + g[i];}
+static void fe_sub (fe h,const fe f,const fe g){FOR(i,0,10) h[i] = f[i] - g[i];}
+
+static void fe_cswap(fe f, fe g, int b)
+{
+ i32 mask = -b; // -1 = 0xffffffff
+ FOR (i, 0, 10) {
+ i32 x = (f[i] ^ g[i]) & mask;
+ f[i] = f[i] ^ x;
+ g[i] = g[i] ^ x;
+ }
+}
+
+static void fe_ccopy(fe f, const fe g, int b)
+{
+ i32 mask = -b; // -1 = 0xffffffff
+ FOR (i, 0, 10) {
+ i32 x = (f[i] ^ g[i]) & mask;
+ f[i] = f[i] ^ x;
+ }
+}
+
+
+// Signed carry propagation
+// ------------------------
+//
+// Let t be a number. It can be uniquely decomposed thus:
+//
+// t = h*2^26 + l
+// such that -2^25 <= l < 2^25
+//
+// Let c = (t + 2^25) / 2^26 (rounded down)
+// c = (h*2^26 + l + 2^25) / 2^26 (rounded down)
+// c = h + (l + 2^25) / 2^26 (rounded down)
+// c = h (exactly)
+// Because 0 <= l + 2^25 < 2^26
+//
+// Let u = t - c*2^26
+// u = h*2^26 + l - h*2^26
+// u = l
+// Therefore, -2^25 <= u < 2^25
+//
+// Additionally, if |t| < x, then |h| < x/2^26 (rounded down)
+//
+// Notations:
+// - In C, 1<<25 means 2^25.
+// - In C, x>>25 means floor(x / (2^25)).
+// - All of the above applies with 25 & 24 as well as 26 & 25.
+//
+//
+// Note on negative right shifts
+// -----------------------------
+//
+// In C, x >> n, where x is a negative integer, is implementation
+// defined. In practice, all platforms do arithmetic shift, which is
+// equivalent to division by 2^26, rounded down. Some compilers, like
+// GCC, even guarantee it.
+//
+// If we ever stumble upon a platform that does not propagate the sign
+// bit (we won't), visible failures will show at the slightest test, and
+// the signed shifts can be replaced by the following:
+//
+// typedef struct { i64 x:39; } s25;
+// typedef struct { i64 x:38; } s26;
+// i64 shift25(i64 x) { s25 s; s.x = ((u64)x)>>25; return s.x; }
+// i64 shift26(i64 x) { s26 s; s.x = ((u64)x)>>26; return s.x; }
+//
+// Current compilers cannot optimise this, causing a 30% drop in
+// performance. Fairly expensive for something that never happens.
+//
+//
+// Precondition
+// ------------
+//
+// |t0| < 2^63
+// |t1|..|t9| < 2^62
+//
+// Algorithm
+// ---------
+// c = t0 + 2^25 / 2^26 -- |c| <= 2^36
+// t0 -= c * 2^26 -- |t0| <= 2^25
+// t1 += c -- |t1| <= 2^63
+//
+// c = t4 + 2^25 / 2^26 -- |c| <= 2^36
+// t4 -= c * 2^26 -- |t4| <= 2^25
+// t5 += c -- |t5| <= 2^63
+//
+// c = t1 + 2^24 / 2^25 -- |c| <= 2^38
+// t1 -= c * 2^25 -- |t1| <= 2^24
+// t2 += c -- |t2| <= 2^63
+//
+// c = t5 + 2^24 / 2^25 -- |c| <= 2^38
+// t5 -= c * 2^25 -- |t5| <= 2^24
+// t6 += c -- |t6| <= 2^63
+//
+// c = t2 + 2^25 / 2^26 -- |c| <= 2^37
+// t2 -= c * 2^26 -- |t2| <= 2^25 < 1.1 * 2^25 (final t2)
+// t3 += c -- |t3| <= 2^63
+//
+// c = t6 + 2^25 / 2^26 -- |c| <= 2^37
+// t6 -= c * 2^26 -- |t6| <= 2^25 < 1.1 * 2^25 (final t6)
+// t7 += c -- |t7| <= 2^63
+//
+// c = t3 + 2^24 / 2^25 -- |c| <= 2^38
+// t3 -= c * 2^25 -- |t3| <= 2^24 < 1.1 * 2^24 (final t3)
+// t4 += c -- |t4| <= 2^25 + 2^38 < 2^39
+//
+// c = t7 + 2^24 / 2^25 -- |c| <= 2^38
+// t7 -= c * 2^25 -- |t7| <= 2^24 < 1.1 * 2^24 (final t7)
+// t8 += c -- |t8| <= 2^63
+//
+// c = t4 + 2^25 / 2^26 -- |c| <= 2^13
+// t4 -= c * 2^26 -- |t4| <= 2^25 < 1.1 * 2^25 (final t4)
+// t5 += c -- |t5| <= 2^24 + 2^13 < 1.1 * 2^24 (final t5)
+//
+// c = t8 + 2^25 / 2^26 -- |c| <= 2^37
+// t8 -= c * 2^26 -- |t8| <= 2^25 < 1.1 * 2^25 (final t8)
+// t9 += c -- |t9| <= 2^63
+//
+// c = t9 + 2^24 / 2^25 -- |c| <= 2^38
+// t9 -= c * 2^25 -- |t9| <= 2^24 < 1.1 * 2^24 (final t9)
+// t0 += c * 19 -- |t0| <= 2^25 + 2^38*19 < 2^44
+//
+// c = t0 + 2^25 / 2^26 -- |c| <= 2^18
+// t0 -= c * 2^26 -- |t0| <= 2^25 < 1.1 * 2^25 (final t0)
+// t1 += c -- |t1| <= 2^24 + 2^18 < 1.1 * 2^24 (final t1)
+//
+// Postcondition
+// -------------
+// |t0|, |t2|, |t4|, |t6|, |t8| < 1.1 * 2^25
+// |t1|, |t3|, |t5|, |t7|, |t9| < 1.1 * 2^24
+#define FE_CARRY \
+ i64 c; \
+ c = (t0 + ((i64)1<<25)) >> 26; t0 -= c * ((i64)1 << 26); t1 += c; \
+ c = (t4 + ((i64)1<<25)) >> 26; t4 -= c * ((i64)1 << 26); t5 += c; \
+ c = (t1 + ((i64)1<<24)) >> 25; t1 -= c * ((i64)1 << 25); t2 += c; \
+ c = (t5 + ((i64)1<<24)) >> 25; t5 -= c * ((i64)1 << 25); t6 += c; \
+ c = (t2 + ((i64)1<<25)) >> 26; t2 -= c * ((i64)1 << 26); t3 += c; \
+ c = (t6 + ((i64)1<<25)) >> 26; t6 -= c * ((i64)1 << 26); t7 += c; \
+ c = (t3 + ((i64)1<<24)) >> 25; t3 -= c * ((i64)1 << 25); t4 += c; \
+ c = (t7 + ((i64)1<<24)) >> 25; t7 -= c * ((i64)1 << 25); t8 += c; \
+ c = (t4 + ((i64)1<<25)) >> 26; t4 -= c * ((i64)1 << 26); t5 += c; \
+ c = (t8 + ((i64)1<<25)) >> 26; t8 -= c * ((i64)1 << 26); t9 += c; \
+ c = (t9 + ((i64)1<<24)) >> 25; t9 -= c * ((i64)1 << 25); t0 += c * 19; \
+ c = (t0 + ((i64)1<<25)) >> 26; t0 -= c * ((i64)1 << 26); t1 += c; \
+ h[0]=(i32)t0; h[1]=(i32)t1; h[2]=(i32)t2; h[3]=(i32)t3; h[4]=(i32)t4; \
+ h[5]=(i32)t5; h[6]=(i32)t6; h[7]=(i32)t7; h[8]=(i32)t8; h[9]=(i32)t9
+
+// Decodes a field element from a byte buffer.
+// mask specifies how many bits we ignore.
+// Traditionally we ignore 1. It's useful for EdDSA,
+// which uses that bit to denote the sign of x.
+// Elligator however uses positive representatives,
+// which means ignoring 2 bits instead.
+static void fe_frombytes_mask(fe h, const u8 s[32], unsigned nb_mask)
+{
+ u32 mask = 0xffffff >> nb_mask;
+ i64 t0 = load32_le(s); // t0 < 2^32
+ i64 t1 = load24_le(s + 4) << 6; // t1 < 2^30
+ i64 t2 = load24_le(s + 7) << 5; // t2 < 2^29
+ i64 t3 = load24_le(s + 10) << 3; // t3 < 2^27
+ i64 t4 = load24_le(s + 13) << 2; // t4 < 2^26
+ i64 t5 = load32_le(s + 16); // t5 < 2^32
+ i64 t6 = load24_le(s + 20) << 7; // t6 < 2^31
+ i64 t7 = load24_le(s + 23) << 5; // t7 < 2^29
+ i64 t8 = load24_le(s + 26) << 4; // t8 < 2^28
+ i64 t9 = (load24_le(s + 29) & mask) << 2; // t9 < 2^25
+ FE_CARRY; // Carry precondition OK
+}
+
+static void fe_frombytes(fe h, const u8 s[32])
+{
+ fe_frombytes_mask(h, s, 1);
+}
+
+
+// Precondition
+// |h[0]|, |h[2]|, |h[4]|, |h[6]|, |h[8]| < 1.1 * 2^25
+// |h[1]|, |h[3]|, |h[5]|, |h[7]|, |h[9]| < 1.1 * 2^24
+//
+// Therefore, |h| < 2^255-19
+// There are two possibilities:
+//
+// - If h is positive, all we need to do is reduce its individual
+// limbs down to their tight positive range.
+// - If h is negative, we also need to add 2^255-19 to it.
+// Or just remove 19 and chop off any excess bit.
+static void fe_tobytes(u8 s[32], const fe h)
+{
+ i32 t[10];
+ COPY(t, h, 10);
+ i32 q = (19 * t[9] + (((i32) 1) << 24)) >> 25;
+ // |t9| < 1.1 * 2^24
+ // -1.1 * 2^24 < t9 < 1.1 * 2^24
+ // -21 * 2^24 < 19 * t9 < 21 * 2^24
+ // -2^29 < 19 * t9 + 2^24 < 2^29
+ // -2^29 / 2^25 < (19 * t9 + 2^24) / 2^25 < 2^29 / 2^25
+ // -16 < (19 * t9 + 2^24) / 2^25 < 16
+ FOR (i, 0, 5) {
+ q += t[2*i ]; q >>= 26; // q = 0 or -1
+ q += t[2*i+1]; q >>= 25; // q = 0 or -1
+ }
+ // q = 0 iff h >= 0
+ // q = -1 iff h < 0
+ // Adding q * 19 to h reduces h to its proper range.
+ q *= 19; // Shift carry back to the beginning
+ FOR (i, 0, 5) {
+ t[i*2 ] += q; q = t[i*2 ] >> 26; t[i*2 ] -= q * ((i32)1 << 26);
+ t[i*2+1] += q; q = t[i*2+1] >> 25; t[i*2+1] -= q * ((i32)1 << 25);
+ }
+ // h is now fully reduced, and q represents the excess bit.
+
+ store32_le(s + 0, ((u32)t[0] >> 0) | ((u32)t[1] << 26));
+ store32_le(s + 4, ((u32)t[1] >> 6) | ((u32)t[2] << 19));
+ store32_le(s + 8, ((u32)t[2] >> 13) | ((u32)t[3] << 13));
+ store32_le(s + 12, ((u32)t[3] >> 19) | ((u32)t[4] << 6));
+ store32_le(s + 16, ((u32)t[5] >> 0) | ((u32)t[6] << 25));
+ store32_le(s + 20, ((u32)t[6] >> 7) | ((u32)t[7] << 19));
+ store32_le(s + 24, ((u32)t[7] >> 13) | ((u32)t[8] << 12));
+ store32_le(s + 28, ((u32)t[8] >> 20) | ((u32)t[9] << 6));
+
+ WIPE_BUFFER(t);
+}
+
+// Precondition
+// -------------
+// |f0|, |f2|, |f4|, |f6|, |f8| < 1.65 * 2^26
+// |f1|, |f3|, |f5|, |f7|, |f9| < 1.65 * 2^25
+//
+// |g0|, |g2|, |g4|, |g6|, |g8| < 1.65 * 2^26
+// |g1|, |g3|, |g5|, |g7|, |g9| < 1.65 * 2^25
+static void fe_mul_small(fe h, const fe f, i32 g)
+{
+ i64 t0 = f[0] * (i64) g; i64 t1 = f[1] * (i64) g;
+ i64 t2 = f[2] * (i64) g; i64 t3 = f[3] * (i64) g;
+ i64 t4 = f[4] * (i64) g; i64 t5 = f[5] * (i64) g;
+ i64 t6 = f[6] * (i64) g; i64 t7 = f[7] * (i64) g;
+ i64 t8 = f[8] * (i64) g; i64 t9 = f[9] * (i64) g;
+ // |t0|, |t2|, |t4|, |t6|, |t8| < 1.65 * 2^26 * 2^31 < 2^58
+ // |t1|, |t3|, |t5|, |t7|, |t9| < 1.65 * 2^25 * 2^31 < 2^57
+
+ FE_CARRY; // Carry precondition OK
+}
+
+// Precondition
+// -------------
+// |f0|, |f2|, |f4|, |f6|, |f8| < 1.65 * 2^26
+// |f1|, |f3|, |f5|, |f7|, |f9| < 1.65 * 2^25
+//
+// |g0|, |g2|, |g4|, |g6|, |g8| < 1.65 * 2^26
+// |g1|, |g3|, |g5|, |g7|, |g9| < 1.65 * 2^25
+static void fe_mul(fe h, const fe f, const fe g)
+{
+ // Everything is unrolled and put in temporary variables.
+ // We could roll the loop, but that would make curve25519 twice as slow.
+ i32 f0 = f[0]; i32 f1 = f[1]; i32 f2 = f[2]; i32 f3 = f[3]; i32 f4 = f[4];
+ i32 f5 = f[5]; i32 f6 = f[6]; i32 f7 = f[7]; i32 f8 = f[8]; i32 f9 = f[9];
+ i32 g0 = g[0]; i32 g1 = g[1]; i32 g2 = g[2]; i32 g3 = g[3]; i32 g4 = g[4];
+ i32 g5 = g[5]; i32 g6 = g[6]; i32 g7 = g[7]; i32 g8 = g[8]; i32 g9 = g[9];
+ i32 F1 = f1*2; i32 F3 = f3*2; i32 F5 = f5*2; i32 F7 = f7*2; i32 F9 = f9*2;
+ i32 G1 = g1*19; i32 G2 = g2*19; i32 G3 = g3*19;
+ i32 G4 = g4*19; i32 G5 = g5*19; i32 G6 = g6*19;
+ i32 G7 = g7*19; i32 G8 = g8*19; i32 G9 = g9*19;
+ // |F1|, |F3|, |F5|, |F7|, |F9| < 1.65 * 2^26
+ // |G0|, |G2|, |G4|, |G6|, |G8| < 2^31
+ // |G1|, |G3|, |G5|, |G7|, |G9| < 2^30
+
+ i64 t0 = f0*(i64)g0 + F1*(i64)G9 + f2*(i64)G8 + F3*(i64)G7 + f4*(i64)G6
+ + F5*(i64)G5 + f6*(i64)G4 + F7*(i64)G3 + f8*(i64)G2 + F9*(i64)G1;
+ i64 t1 = f0*(i64)g1 + f1*(i64)g0 + f2*(i64)G9 + f3*(i64)G8 + f4*(i64)G7
+ + f5*(i64)G6 + f6*(i64)G5 + f7*(i64)G4 + f8*(i64)G3 + f9*(i64)G2;
+ i64 t2 = f0*(i64)g2 + F1*(i64)g1 + f2*(i64)g0 + F3*(i64)G9 + f4*(i64)G8
+ + F5*(i64)G7 + f6*(i64)G6 + F7*(i64)G5 + f8*(i64)G4 + F9*(i64)G3;
+ i64 t3 = f0*(i64)g3 + f1*(i64)g2 + f2*(i64)g1 + f3*(i64)g0 + f4*(i64)G9
+ + f5*(i64)G8 + f6*(i64)G7 + f7*(i64)G6 + f8*(i64)G5 + f9*(i64)G4;
+ i64 t4 = f0*(i64)g4 + F1*(i64)g3 + f2*(i64)g2 + F3*(i64)g1 + f4*(i64)g0
+ + F5*(i64)G9 + f6*(i64)G8 + F7*(i64)G7 + f8*(i64)G6 + F9*(i64)G5;
+ i64 t5 = f0*(i64)g5 + f1*(i64)g4 + f2*(i64)g3 + f3*(i64)g2 + f4*(i64)g1
+ + f5*(i64)g0 + f6*(i64)G9 + f7*(i64)G8 + f8*(i64)G7 + f9*(i64)G6;
+ i64 t6 = f0*(i64)g6 + F1*(i64)g5 + f2*(i64)g4 + F3*(i64)g3 + f4*(i64)g2
+ + F5*(i64)g1 + f6*(i64)g0 + F7*(i64)G9 + f8*(i64)G8 + F9*(i64)G7;
+ i64 t7 = f0*(i64)g7 + f1*(i64)g6 + f2*(i64)g5 + f3*(i64)g4 + f4*(i64)g3
+ + f5*(i64)g2 + f6*(i64)g1 + f7*(i64)g0 + f8*(i64)G9 + f9*(i64)G8;
+ i64 t8 = f0*(i64)g8 + F1*(i64)g7 + f2*(i64)g6 + F3*(i64)g5 + f4*(i64)g4
+ + F5*(i64)g3 + f6*(i64)g2 + F7*(i64)g1 + f8*(i64)g0 + F9*(i64)G9;
+ i64 t9 = f0*(i64)g9 + f1*(i64)g8 + f2*(i64)g7 + f3*(i64)g6 + f4*(i64)g5
+ + f5*(i64)g4 + f6*(i64)g3 + f7*(i64)g2 + f8*(i64)g1 + f9*(i64)g0;
+ // t0 < 0.67 * 2^61
+ // t1 < 0.41 * 2^61
+ // t2 < 0.52 * 2^61
+ // t3 < 0.32 * 2^61
+ // t4 < 0.38 * 2^61
+ // t5 < 0.22 * 2^61
+ // t6 < 0.23 * 2^61
+ // t7 < 0.13 * 2^61
+ // t8 < 0.09 * 2^61
+ // t9 < 0.03 * 2^61
+
+ FE_CARRY; // Everything below 2^62, Carry precondition OK
+}
+
+// Precondition
+// -------------
+// |f0|, |f2|, |f4|, |f6|, |f8| < 1.65 * 2^26
+// |f1|, |f3|, |f5|, |f7|, |f9| < 1.65 * 2^25
+//
+// Note: we could use fe_mul() for this, but this is significantly faster
+static void fe_sq(fe h, const fe f)
+{
+ i32 f0 = f[0]; i32 f1 = f[1]; i32 f2 = f[2]; i32 f3 = f[3]; i32 f4 = f[4];
+ i32 f5 = f[5]; i32 f6 = f[6]; i32 f7 = f[7]; i32 f8 = f[8]; i32 f9 = f[9];
+ i32 f0_2 = f0*2; i32 f1_2 = f1*2; i32 f2_2 = f2*2; i32 f3_2 = f3*2;
+ i32 f4_2 = f4*2; i32 f5_2 = f5*2; i32 f6_2 = f6*2; i32 f7_2 = f7*2;
+ i32 f5_38 = f5*38; i32 f6_19 = f6*19; i32 f7_38 = f7*38;
+ i32 f8_19 = f8*19; i32 f9_38 = f9*38;
+ // |f0_2| , |f2_2| , |f4_2| , |f6_2| , |f8_2| < 1.65 * 2^27
+ // |f1_2| , |f3_2| , |f5_2| , |f7_2| , |f9_2| < 1.65 * 2^26
+ // |f5_38|, |f6_19|, |f7_38|, |f8_19|, |f9_38| < 2^31
+
+ i64 t0 = f0 *(i64)f0 + f1_2*(i64)f9_38 + f2_2*(i64)f8_19
+ + f3_2*(i64)f7_38 + f4_2*(i64)f6_19 + f5 *(i64)f5_38;
+ i64 t1 = f0_2*(i64)f1 + f2 *(i64)f9_38 + f3_2*(i64)f8_19
+ + f4 *(i64)f7_38 + f5_2*(i64)f6_19;
+ i64 t2 = f0_2*(i64)f2 + f1_2*(i64)f1 + f3_2*(i64)f9_38
+ + f4_2*(i64)f8_19 + f5_2*(i64)f7_38 + f6 *(i64)f6_19;
+ i64 t3 = f0_2*(i64)f3 + f1_2*(i64)f2 + f4 *(i64)f9_38
+ + f5_2*(i64)f8_19 + f6 *(i64)f7_38;
+ i64 t4 = f0_2*(i64)f4 + f1_2*(i64)f3_2 + f2 *(i64)f2
+ + f5_2*(i64)f9_38 + f6_2*(i64)f8_19 + f7 *(i64)f7_38;
+ i64 t5 = f0_2*(i64)f5 + f1_2*(i64)f4 + f2_2*(i64)f3
+ + f6 *(i64)f9_38 + f7_2*(i64)f8_19;
+ i64 t6 = f0_2*(i64)f6 + f1_2*(i64)f5_2 + f2_2*(i64)f4
+ + f3_2*(i64)f3 + f7_2*(i64)f9_38 + f8 *(i64)f8_19;
+ i64 t7 = f0_2*(i64)f7 + f1_2*(i64)f6 + f2_2*(i64)f5
+ + f3_2*(i64)f4 + f8 *(i64)f9_38;
+ i64 t8 = f0_2*(i64)f8 + f1_2*(i64)f7_2 + f2_2*(i64)f6
+ + f3_2*(i64)f5_2 + f4 *(i64)f4 + f9 *(i64)f9_38;
+ i64 t9 = f0_2*(i64)f9 + f1_2*(i64)f8 + f2_2*(i64)f7
+ + f3_2*(i64)f6 + f4 *(i64)f5_2;
+ // t0 < 0.67 * 2^61
+ // t1 < 0.41 * 2^61
+ // t2 < 0.52 * 2^61
+ // t3 < 0.32 * 2^61
+ // t4 < 0.38 * 2^61
+ // t5 < 0.22 * 2^61
+ // t6 < 0.23 * 2^61
+ // t7 < 0.13 * 2^61
+ // t8 < 0.09 * 2^61
+ // t9 < 0.03 * 2^61
+
+ FE_CARRY;
+}
+
+// Parity check. Returns 0 if even, 1 if odd
+static int fe_isodd(const fe f)
+{
+ u8 s[32];
+ fe_tobytes(s, f);
+ u8 isodd = s[0] & 1;
+ WIPE_BUFFER(s);
+ return isodd;
+}
+
+// Returns 1 if equal, 0 if not equal
+static int fe_isequal(const fe f, const fe g)
+{
+ u8 fs[32];
+ u8 gs[32];
+ fe_tobytes(fs, f);
+ fe_tobytes(gs, g);
+ int isdifferent = crypto_verify32(fs, gs);
+ WIPE_BUFFER(fs);
+ WIPE_BUFFER(gs);
+ return 1 + isdifferent;
+}
+
+// Inverse square root.
+// Returns true if x is a square, false otherwise.
+// After the call:
+// isr = sqrt(1/x) if x is a non-zero square.
+// isr = sqrt(sqrt(-1)/x) if x is not a square.
+// isr = 0 if x is zero.
+// We do not guarantee the sign of the square root.
+//
+// Notes:
+// Let quartic = x^((p-1)/4)
+//
+// x^((p-1)/2) = chi(x)
+// quartic^2 = chi(x)
+// quartic = sqrt(chi(x))
+// quartic = 1 or -1 or sqrt(-1) or -sqrt(-1)
+//
+// Note that x is a square if quartic is 1 or -1
+// There are 4 cases to consider:
+//
+// if quartic = 1 (x is a square)
+// then x^((p-1)/4) = 1
+// x^((p-5)/4) * x = 1
+// x^((p-5)/4) = 1/x
+// x^((p-5)/8) = sqrt(1/x) or -sqrt(1/x)
+//
+// if quartic = -1 (x is a square)
+// then x^((p-1)/4) = -1
+// x^((p-5)/4) * x = -1
+// x^((p-5)/4) = -1/x
+// x^((p-5)/8) = sqrt(-1) / sqrt(x)
+// x^((p-5)/8) * sqrt(-1) = sqrt(-1)^2 / sqrt(x)
+// x^((p-5)/8) * sqrt(-1) = -1/sqrt(x)
+// x^((p-5)/8) * sqrt(-1) = -sqrt(1/x) or sqrt(1/x)
+//
+// if quartic = sqrt(-1) (x is not a square)
+// then x^((p-1)/4) = sqrt(-1)
+// x^((p-5)/4) * x = sqrt(-1)
+// x^((p-5)/4) = sqrt(-1)/x
+// x^((p-5)/8) = sqrt(sqrt(-1)/x) or -sqrt(sqrt(-1)/x)
+//
+// Note that the product of two non-squares is always a square:
+// For any non-squares a and b, chi(a) = -1 and chi(b) = -1.
+// Since chi(x) = x^((p-1)/2), chi(a)*chi(b) = chi(a*b) = 1.
+// Therefore a*b is a square.
+//
+// Since sqrt(-1) and x are both non-squares, their product is a
+// square, and we can compute their square root.
+//
+// if quartic = -sqrt(-1) (x is not a square)
+// then x^((p-1)/4) = -sqrt(-1)
+// x^((p-5)/4) * x = -sqrt(-1)
+// x^((p-5)/4) = -sqrt(-1)/x
+// x^((p-5)/8) = sqrt(-sqrt(-1)/x)
+// x^((p-5)/8) = sqrt( sqrt(-1)/x) * sqrt(-1)
+// x^((p-5)/8) * sqrt(-1) = sqrt( sqrt(-1)/x) * sqrt(-1)^2
+// x^((p-5)/8) * sqrt(-1) = sqrt( sqrt(-1)/x) * -1
+// x^((p-5)/8) * sqrt(-1) = -sqrt(sqrt(-1)/x) or sqrt(sqrt(-1)/x)
+static int invsqrt(fe isr, const fe x)
+{
+ fe t0, t1, t2;
+
+ // t0 = x^((p-5)/8)
+ // Can be achieved with a simple double & add ladder,
+ // but it would be slower.
+ fe_sq(t0, x);
+ fe_sq(t1,t0); fe_sq(t1, t1); fe_mul(t1, x, t1);
+ fe_mul(t0, t0, t1);
+ fe_sq(t0, t0); fe_mul(t0, t1, t0);
+ fe_sq(t1, t0); FOR (i, 1, 5) { fe_sq(t1, t1); } fe_mul(t0, t1, t0);
+ fe_sq(t1, t0); FOR (i, 1, 10) { fe_sq(t1, t1); } fe_mul(t1, t1, t0);
+ fe_sq(t2, t1); FOR (i, 1, 20) { fe_sq(t2, t2); } fe_mul(t1, t2, t1);
+ fe_sq(t1, t1); FOR (i, 1, 10) { fe_sq(t1, t1); } fe_mul(t0, t1, t0);
+ fe_sq(t1, t0); FOR (i, 1, 50) { fe_sq(t1, t1); } fe_mul(t1, t1, t0);
+ fe_sq(t2, t1); FOR (i, 1, 100) { fe_sq(t2, t2); } fe_mul(t1, t2, t1);
+ fe_sq(t1, t1); FOR (i, 1, 50) { fe_sq(t1, t1); } fe_mul(t0, t1, t0);
+ fe_sq(t0, t0); FOR (i, 1, 2) { fe_sq(t0, t0); } fe_mul(t0, t0, x);
+
+ // quartic = x^((p-1)/4)
+ i32 *quartic = t1;
+ fe_sq (quartic, t0);
+ fe_mul(quartic, quartic, x);
+
+ i32 *check = t2;
+ fe_0 (check); int z0 = fe_isequal(x , check);
+ fe_1 (check); int p1 = fe_isequal(quartic, check);
+ fe_neg(check, check ); int m1 = fe_isequal(quartic, check);
+ fe_neg(check, sqrtm1); int ms = fe_isequal(quartic, check);
+
+ // if quartic == -1 or sqrt(-1)
+ // then isr = x^((p-1)/4) * sqrt(-1)
+ // else isr = x^((p-1)/4)
+ fe_mul(isr, t0, sqrtm1);
+ fe_ccopy(isr, t0, 1 - (m1 | ms));
+
+ WIPE_BUFFER(t0);
+ WIPE_BUFFER(t1);
+ WIPE_BUFFER(t2);
+ return p1 | m1 | z0;
+}
+
+// Inverse in terms of inverse square root.
+// Requires two additional squarings to get rid of the sign.
+//
+// 1/x = x * (+invsqrt(x^2))^2
+// = x * (-invsqrt(x^2))^2
+//
+// A fully optimised exponentiation by p-1 would save 6 field
+// multiplications, but it would require more code.
+static void fe_invert(fe out, const fe x)
+{
+ fe tmp;
+ fe_sq(tmp, x);
+ invsqrt(tmp, tmp);
+ fe_sq(tmp, tmp);
+ fe_mul(out, tmp, x);
+ WIPE_BUFFER(tmp);
+}
+
+// trim a scalar for scalar multiplication
+void crypto_eddsa_trim_scalar(u8 out[32], const u8 in[32])
+{
+ COPY(out, in, 32);
+ out[ 0] &= 248;
+ out[31] &= 127;
+ out[31] |= 64;
+}
+
+// get bit from scalar at position i
+static int scalar_bit(const u8 s[32], int i)
+{
+ if (i < 0) { return 0; } // handle -1 for sliding windows
+ return (s[i>>3] >> (i&7)) & 1;
+}
+
+///////////////
+/// X-25519 /// Taken from SUPERCOP's ref10 implementation.
+///////////////
+static void scalarmult(u8 q[32], const u8 scalar[32], const u8 p[32],
+ int nb_bits)
+{
+ // computes the scalar product
+ fe x1;
+ fe_frombytes(x1, p);
+
+ // computes the actual scalar product (the result is in x2 and z2)
+ fe x2, z2, x3, z3, t0, t1;
+ // Montgomery ladder
+ // In projective coordinates, to avoid divisions: x = X / Z
+ // We don't care about the y coordinate, it's only 1 bit of information
+ fe_1(x2); fe_0(z2); // "zero" point
+ fe_copy(x3, x1); fe_1(z3); // "one" point
+ int swap = 0;
+ for (int pos = nb_bits-1; pos >= 0; --pos) {
+ // constant time conditional swap before ladder step
+ int b = scalar_bit(scalar, pos);
+ swap ^= b; // xor trick avoids swapping at the end of the loop
+ fe_cswap(x2, x3, swap);
+ fe_cswap(z2, z3, swap);
+ swap = b; // anticipates one last swap after the loop
+
+ // Montgomery ladder step: replaces (P2, P3) by (P2*2, P2+P3)
+ // with differential addition
+ fe_sub(t0, x3, z3);
+ fe_sub(t1, x2, z2);
+ fe_add(x2, x2, z2);
+ fe_add(z2, x3, z3);
+ fe_mul(z3, t0, x2);
+ fe_mul(z2, z2, t1);
+ fe_sq (t0, t1 );
+ fe_sq (t1, x2 );
+ fe_add(x3, z3, z2);
+ fe_sub(z2, z3, z2);
+ fe_mul(x2, t1, t0);
+ fe_sub(t1, t1, t0);
+ fe_sq (z2, z2 );
+ fe_mul_small(z3, t1, 121666);
+ fe_sq (x3, x3 );
+ fe_add(t0, t0, z3);
+ fe_mul(z3, x1, z2);
+ fe_mul(z2, t1, t0);
+ }
+ // last swap is necessary to compensate for the xor trick
+ // Note: after this swap, P3 == P2 + P1.
+ fe_cswap(x2, x3, swap);
+ fe_cswap(z2, z3, swap);
+
+ // normalises the coordinates: x == X / Z
+ fe_invert(z2, z2);
+ fe_mul(x2, x2, z2);
+ fe_tobytes(q, x2);
+
+ WIPE_BUFFER(x1);
+ WIPE_BUFFER(x2); WIPE_BUFFER(z2); WIPE_BUFFER(t0);
+ WIPE_BUFFER(x3); WIPE_BUFFER(z3); WIPE_BUFFER(t1);
+}
+
+void crypto_x25519(u8 raw_shared_secret[32],
+ const u8 your_secret_key [32],
+ const u8 their_public_key [32])
+{
+ // restrict the possible scalar values
+ u8 e[32];
+ crypto_eddsa_trim_scalar(e, your_secret_key);
+ scalarmult(raw_shared_secret, e, their_public_key, 255);
+ WIPE_BUFFER(e);
+}
+
+void crypto_x25519_public_key(u8 public_key[32],
+ const u8 secret_key[32])
+{
+ static const u8 base_point[32] = {9};
+ crypto_x25519(public_key, secret_key, base_point);
+}
+
+///////////////////////////
+/// Arithmetic modulo L ///
+///////////////////////////
+static const u32 L[8] = {
+ 0x5cf5d3ed, 0x5812631a, 0xa2f79cd6, 0x14def9de,
+ 0x00000000, 0x00000000, 0x00000000, 0x10000000,
+};
+
+// p = a*b + p
+static void multiply(u32 p[16], const u32 a[8], const u32 b[8])
+{
+ FOR (i, 0, 8) {
+ u64 carry = 0;
+ FOR (j, 0, 8) {
+ carry += p[i+j] + (u64)a[i] * b[j];
+ p[i+j] = (u32)carry;
+ carry >>= 32;
+ }
+ p[i+8] = (u32)carry;
+ }
+}
+
+static int is_above_l(const u32 x[8])
+{
+ // We work with L directly, in a 2's complement encoding
+ // (-L == ~L + 1)
+ u64 carry = 1;
+ FOR (i, 0, 8) {
+ carry += (u64)x[i] + (~L[i] & 0xffffffff);
+ carry >>= 32;
+ }
+ return (int)carry; // carry is either 0 or 1
+}
+
+// Final reduction modulo L, by conditionally removing L.
+// if x < l , then r = x
+// if l <= x 2*l, then r = x-l
+// otherwise the result will be wrong
+static void remove_l(u32 r[8], const u32 x[8])
+{
+ u64 carry = (u64)is_above_l(x);
+ u32 mask = ~(u32)carry + 1; // carry == 0 or 1
+ FOR (i, 0, 8) {
+ carry += (u64)x[i] + (~L[i] & mask);
+ r[i] = (u32)carry;
+ carry >>= 32;
+ }
+}
+
+// Full reduction modulo L (Barrett reduction)
+static void mod_l(u8 reduced[32], const u32 x[16])
+{
+ static const u32 r[9] = {
+ 0x0a2c131b,0xed9ce5a3,0x086329a7,0x2106215d,
+ 0xffffffeb,0xffffffff,0xffffffff,0xffffffff,0xf,
+ };
+ // xr = x * r
+ u32 xr[25] = {0};
+ FOR (i, 0, 9) {
+ u64 carry = 0;
+ FOR (j, 0, 16) {
+ carry += xr[i+j] + (u64)r[i] * x[j];
+ xr[i+j] = (u32)carry;
+ carry >>= 32;
+ }
+ xr[i+16] = (u32)carry;
+ }
+ // xr = floor(xr / 2^512) * L
+ // Since the result is guaranteed to be below 2*L,
+ // it is enough to only compute the first 256 bits.
+ // The division is performed by saying xr[i+16]. (16 * 32 = 512)
+ ZERO(xr, 8);
+ FOR (i, 0, 8) {
+ u64 carry = 0;
+ FOR (j, 0, 8-i) {
+ carry += xr[i+j] + (u64)xr[i+16] * L[j];
+ xr[i+j] = (u32)carry;
+ carry >>= 32;
+ }
+ }
+ // xr = x - xr
+ u64 carry = 1;
+ FOR (i, 0, 8) {
+ carry += (u64)x[i] + (~xr[i] & 0xffffffff);
+ xr[i] = (u32)carry;
+ carry >>= 32;
+ }
+ // Final reduction modulo L (conditional subtraction)
+ remove_l(xr, xr);
+ store32_le_buf(reduced, xr, 8);
+
+ WIPE_BUFFER(xr);
+}
+
+void crypto_eddsa_reduce(u8 reduced[32], const u8 expanded[64])
+{
+ u32 x[16];
+ load32_le_buf(x, expanded, 16);
+ mod_l(reduced, x);
+ WIPE_BUFFER(x);
+}
+
+// r = (a * b) + c
+void crypto_eddsa_mul_add(u8 r[32],
+ const u8 a[32], const u8 b[32], const u8 c[32])
+{
+ u32 A[8]; load32_le_buf(A, a, 8);
+ u32 B[8]; load32_le_buf(B, b, 8);
+ u32 p[16]; load32_le_buf(p, c, 8); ZERO(p + 8, 8);
+ multiply(p, A, B);
+ mod_l(r, p);
+ WIPE_BUFFER(p);
+ WIPE_BUFFER(A);
+ WIPE_BUFFER(B);
+}
+
+///////////////
+/// Ed25519 ///
+///////////////
+
+// Point (group element, ge) in a twisted Edwards curve,
+// in extended projective coordinates.
+// ge : x = X/Z, y = Y/Z, T = XY/Z
+// ge_cached : Yp = X+Y, Ym = X-Y, T2 = T*D2
+// ge_precomp: Z = 1
+typedef struct { fe X; fe Y; fe Z; fe T; } ge;
+typedef struct { fe Yp; fe Ym; fe Z; fe T2; } ge_cached;
+typedef struct { fe Yp; fe Ym; fe T2; } ge_precomp;
+
+static void ge_zero(ge *p)
+{
+ fe_0(p->X);
+ fe_1(p->Y);
+ fe_1(p->Z);
+ fe_0(p->T);
+}
+
+static void ge_tobytes(u8 s[32], const ge *h)
+{
+ fe recip, x, y;
+ fe_invert(recip, h->Z);
+ fe_mul(x, h->X, recip);
+ fe_mul(y, h->Y, recip);
+ fe_tobytes(s, y);
+ s[31] ^= fe_isodd(x) << 7;
+
+ WIPE_BUFFER(recip);
+ WIPE_BUFFER(x);
+ WIPE_BUFFER(y);
+}
+
+// h = -s, where s is a point encoded in 32 bytes
+//
+// Variable time! Inputs must not be secret!
+// => Use only to *check* signatures.
+//
+// From the specifications:
+// The encoding of s contains y and the sign of x
+// x = sqrt((y^2 - 1) / (d*y^2 + 1))
+// In extended coordinates:
+// X = x, Y = y, Z = 1, T = x*y
+//
+// Note that num * den is a square iff num / den is a square
+// If num * den is not a square, the point was not on the curve.
+// From the above:
+// Let num = y^2 - 1
+// Let den = d*y^2 + 1
+// x = sqrt((y^2 - 1) / (d*y^2 + 1))
+// x = sqrt(num / den)
+// x = sqrt(num^2 / (num * den))
+// x = num * sqrt(1 / (num * den))
+//
+// Therefore, we can just compute:
+// num = y^2 - 1
+// den = d*y^2 + 1
+// isr = invsqrt(num * den) // abort if not square
+// x = num * isr
+// Finally, negate x if its sign is not as specified.
+static int ge_frombytes_neg_vartime(ge *h, const u8 s[32])
+{
+ fe_frombytes(h->Y, s);
+ fe_1(h->Z);
+ fe_sq (h->T, h->Y); // t = y^2
+ fe_mul(h->X, h->T, d ); // x = d*y^2
+ fe_sub(h->T, h->T, h->Z); // t = y^2 - 1
+ fe_add(h->X, h->X, h->Z); // x = d*y^2 + 1
+ fe_mul(h->X, h->T, h->X); // x = (y^2 - 1) * (d*y^2 + 1)
+ int is_square = invsqrt(h->X, h->X);
+ if (!is_square) {
+ return -1; // Not on the curve, abort
+ }
+ fe_mul(h->X, h->T, h->X); // x = sqrt((y^2 - 1) / (d*y^2 + 1))
+ if (fe_isodd(h->X) == (s[31] >> 7)) {
+ fe_neg(h->X, h->X);
+ }
+ fe_mul(h->T, h->X, h->Y);
+ return 0;
+}
+
+static void ge_cache(ge_cached *c, const ge *p)
+{
+ fe_add (c->Yp, p->Y, p->X);
+ fe_sub (c->Ym, p->Y, p->X);
+ fe_copy(c->Z , p->Z );
+ fe_mul (c->T2, p->T, D2 );
+}
+
+// Internal buffers are not wiped! Inputs must not be secret!
+// => Use only to *check* signatures.
+static void ge_add(ge *s, const ge *p, const ge_cached *q)
+{
+ fe a, b;
+ fe_add(a , p->Y, p->X );
+ fe_sub(b , p->Y, p->X );
+ fe_mul(a , a , q->Yp);
+ fe_mul(b , b , q->Ym);
+ fe_add(s->Y, a , b );
+ fe_sub(s->X, a , b );
+
+ fe_add(s->Z, p->Z, p->Z );
+ fe_mul(s->Z, s->Z, q->Z );
+ fe_mul(s->T, p->T, q->T2);
+ fe_add(a , s->Z, s->T );
+ fe_sub(b , s->Z, s->T );
+
+ fe_mul(s->T, s->X, s->Y);
+ fe_mul(s->X, s->X, b );
+ fe_mul(s->Y, s->Y, a );
+ fe_mul(s->Z, a , b );
+}
+
+// Internal buffers are not wiped! Inputs must not be secret!
+// => Use only to *check* signatures.
+static void ge_sub(ge *s, const ge *p, const ge_cached *q)
+{
+ ge_cached neg;
+ fe_copy(neg.Ym, q->Yp);
+ fe_copy(neg.Yp, q->Ym);
+ fe_copy(neg.Z , q->Z );
+ fe_neg (neg.T2, q->T2);
+ ge_add(s, p, &neg);
+}
+
+static void ge_madd(ge *s, const ge *p, const ge_precomp *q, fe a, fe b)
+{
+ fe_add(a , p->Y, p->X );
+ fe_sub(b , p->Y, p->X );
+ fe_mul(a , a , q->Yp);
+ fe_mul(b , b , q->Ym);
+ fe_add(s->Y, a , b );
+ fe_sub(s->X, a , b );
+
+ fe_add(s->Z, p->Z, p->Z );
+ fe_mul(s->T, p->T, q->T2);
+ fe_add(a , s->Z, s->T );
+ fe_sub(b , s->Z, s->T );
+
+ fe_mul(s->T, s->X, s->Y);
+ fe_mul(s->X, s->X, b );
+ fe_mul(s->Y, s->Y, a );
+ fe_mul(s->Z, a , b );
+}
+
+// Internal buffers are not wiped! Inputs must not be secret!
+// => Use only to *check* signatures.
+static void ge_msub(ge *s, const ge *p, const ge_precomp *q, fe a, fe b)
+{
+ ge_precomp neg;
+ fe_copy(neg.Ym, q->Yp);
+ fe_copy(neg.Yp, q->Ym);
+ fe_neg (neg.T2, q->T2);
+ ge_madd(s, p, &neg, a, b);
+}
+
+static void ge_double(ge *s, const ge *p, ge *q)
+{
+ fe_sq (q->X, p->X);
+ fe_sq (q->Y, p->Y);
+ fe_sq (q->Z, p->Z); // qZ = pZ^2
+ fe_mul_small(q->Z, q->Z, 2); // qZ = pZ^2 * 2
+ fe_add(q->T, p->X, p->Y);
+ fe_sq (s->T, q->T);
+ fe_add(q->T, q->Y, q->X);
+ fe_sub(q->Y, q->Y, q->X);
+ fe_sub(q->X, s->T, q->T);
+ fe_sub(q->Z, q->Z, q->Y);
+
+ fe_mul(s->X, q->X , q->Z);
+ fe_mul(s->Y, q->T , q->Y);
+ fe_mul(s->Z, q->Y , q->Z);
+ fe_mul(s->T, q->X , q->T);
+}
+
+// 5-bit signed window in cached format (Niels coordinates, Z=1)
+static const ge_precomp b_window[8] = {
+ {{25967493,-14356035,29566456,3660896,-12694345,
+ 4014787,27544626,-11754271,-6079156,2047605,},
+ {-12545711,934262,-2722910,3049990,-727428,
+ 9406986,12720692,5043384,19500929,-15469378,},
+ {-8738181,4489570,9688441,-14785194,10184609,
+ -12363380,29287919,11864899,-24514362,-4438546,},},
+ {{15636291,-9688557,24204773,-7912398,616977,
+ -16685262,27787600,-14772189,28944400,-1550024,},
+ {16568933,4717097,-11556148,-1102322,15682896,
+ -11807043,16354577,-11775962,7689662,11199574,},
+ {30464156,-5976125,-11779434,-15670865,23220365,
+ 15915852,7512774,10017326,-17749093,-9920357,},},
+ {{10861363,11473154,27284546,1981175,-30064349,
+ 12577861,32867885,14515107,-15438304,10819380,},
+ {4708026,6336745,20377586,9066809,-11272109,
+ 6594696,-25653668,12483688,-12668491,5581306,},
+ {19563160,16186464,-29386857,4097519,10237984,
+ -4348115,28542350,13850243,-23678021,-15815942,},},
+ {{5153746,9909285,1723747,-2777874,30523605,
+ 5516873,19480852,5230134,-23952439,-15175766,},
+ {-30269007,-3463509,7665486,10083793,28475525,
+ 1649722,20654025,16520125,30598449,7715701,},
+ {28881845,14381568,9657904,3680757,-20181635,
+ 7843316,-31400660,1370708,29794553,-1409300,},},
+ {{-22518993,-6692182,14201702,-8745502,-23510406,
+ 8844726,18474211,-1361450,-13062696,13821877,},
+ {-6455177,-7839871,3374702,-4740862,-27098617,
+ -10571707,31655028,-7212327,18853322,-14220951,},
+ {4566830,-12963868,-28974889,-12240689,-7602672,
+ -2830569,-8514358,-10431137,2207753,-3209784,},},
+ {{-25154831,-4185821,29681144,7868801,-6854661,
+ -9423865,-12437364,-663000,-31111463,-16132436,},
+ {25576264,-2703214,7349804,-11814844,16472782,
+ 9300885,3844789,15725684,171356,6466918,},
+ {23103977,13316479,9739013,-16149481,817875,
+ -15038942,8965339,-14088058,-30714912,16193877,},},
+ {{-33521811,3180713,-2394130,14003687,-16903474,
+ -16270840,17238398,4729455,-18074513,9256800,},
+ {-25182317,-4174131,32336398,5036987,-21236817,
+ 11360617,22616405,9761698,-19827198,630305,},
+ {-13720693,2639453,-24237460,-7406481,9494427,
+ -5774029,-6554551,-15960994,-2449256,-14291300,},},
+ {{-3151181,-5046075,9282714,6866145,-31907062,
+ -863023,-18940575,15033784,25105118,-7894876,},
+ {-24326370,15950226,-31801215,-14592823,-11662737,
+ -5090925,1573892,-2625887,2198790,-15804619,},
+ {-3099351,10324967,-2241613,7453183,-5446979,
+ -2735503,-13812022,-16236442,-32461234,-12290683,},},
+};
+
+// Incremental sliding windows (left to right)
+// Based on Roberto Maria Avanzi[2005]
+typedef struct {
+ i16 next_index; // position of the next signed digit
+ i8 next_digit; // next signed digit (odd number below 2^window_width)
+ u8 next_check; // point at which we must check for a new window
+} slide_ctx;
+
+static void slide_init(slide_ctx *ctx, const u8 scalar[32])
+{
+ // scalar is guaranteed to be below L, either because we checked (s),
+ // or because we reduced it modulo L (h_ram). L is under 2^253, so
+ // so bits 253 to 255 are guaranteed to be zero. No need to test them.
+ //
+ // Note however that L is very close to 2^252, so bit 252 is almost
+ // always zero. If we were to start at bit 251, the tests wouldn't
+ // catch the off-by-one error (constructing one that does would be
+ // prohibitively expensive).
+ //
+ // We should still check bit 252, though.
+ int i = 252;
+ while (i > 0 && scalar_bit(scalar, i) == 0) {
+ i--;
+ }
+ ctx->next_check = (u8)(i + 1);
+ ctx->next_index = -1;
+ ctx->next_digit = -1;
+}
+
+static int slide_step(slide_ctx *ctx, int width, int i, const u8 scalar[32])
+{
+ if (i == ctx->next_check) {
+ if (scalar_bit(scalar, i) == scalar_bit(scalar, i - 1)) {
+ ctx->next_check--;
+ } else {
+ // compute digit of next window
+ int w = MIN(width, i + 1);
+ int v = -(scalar_bit(scalar, i) << (w-1));
+ FOR_T (int, j, 0, w-1) {
+ v += scalar_bit(scalar, i-(w-1)+j) << j;
+ }
+ v += scalar_bit(scalar, i-w);
+ int lsb = v & (~v + 1); // smallest bit of v
+ int s = // log2(lsb)
+ (((lsb & 0xAA) != 0) << 0) |
+ (((lsb & 0xCC) != 0) << 1) |
+ (((lsb & 0xF0) != 0) << 2);
+ ctx->next_index = (i16)(i-(w-1)+s);
+ ctx->next_digit = (i8) (v >> s );
+ ctx->next_check -= (u8) w;
+ }
+ }
+ return i == ctx->next_index ? ctx->next_digit: 0;
+}
+
+#define P_W_WIDTH 3 // Affects the size of the stack
+#define B_W_WIDTH 5 // Affects the size of the binary
+#define P_W_SIZE (1<<(P_W_WIDTH-2))
+
+int crypto_eddsa_check_equation(const u8 signature[64], const u8 public_key[32],
+ const u8 h[32])
+{
+ ge minus_A; // -public_key
+ ge minus_R; // -first_half_of_signature
+ const u8 *s = signature + 32;
+
+ // Check that A and R are on the curve
+ // Check that 0 <= S < L (prevents malleability)
+ // *Allow* non-cannonical encoding for A and R
+ {
+ u32 s32[8];
+ load32_le_buf(s32, s, 8);
+ if (ge_frombytes_neg_vartime(&minus_A, public_key) ||
+ ge_frombytes_neg_vartime(&minus_R, signature) ||
+ is_above_l(s32)) {
+ return -1;
+ }
+ }
+
+ // look-up table for minus_A
+ ge_cached lutA[P_W_SIZE];
+ {
+ ge minus_A2, tmp;
+ ge_double(&minus_A2, &minus_A, &tmp);
+ ge_cache(&lutA[0], &minus_A);
+ FOR (i, 1, P_W_SIZE) {
+ ge_add(&tmp, &minus_A2, &lutA[i-1]);
+ ge_cache(&lutA[i], &tmp);
+ }
+ }
+
+ // sum = [s]B - [h]A
+ // Merged double and add ladder, fused with sliding
+ slide_ctx h_slide; slide_init(&h_slide, h);
+ slide_ctx s_slide; slide_init(&s_slide, s);
+ int i = MAX(h_slide.next_check, s_slide.next_check);
+ ge *sum = &minus_A; // reuse minus_A for the sum
+ ge_zero(sum);
+ while (i >= 0) {
+ ge tmp;
+ ge_double(sum, sum, &tmp);
+ int h_digit = slide_step(&h_slide, P_W_WIDTH, i, h);
+ int s_digit = slide_step(&s_slide, B_W_WIDTH, i, s);
+ if (h_digit > 0) { ge_add(sum, sum, &lutA[ h_digit / 2]); }
+ if (h_digit < 0) { ge_sub(sum, sum, &lutA[-h_digit / 2]); }
+ fe t1, t2;
+ if (s_digit > 0) { ge_madd(sum, sum, b_window + s_digit/2, t1, t2); }
+ if (s_digit < 0) { ge_msub(sum, sum, b_window + -s_digit/2, t1, t2); }
+ i--;
+ }
+
+ // Compare [8](sum-R) and the zero point
+ // The multiplication by 8 eliminates any low-order component
+ // and ensures consistency with batched verification.
+ ge_cached cached;
+ u8 check[32];
+ static const u8 zero_point[32] = {1}; // Point of order 1
+ ge_cache(&cached, &minus_R);
+ ge_add(sum, sum, &cached);
+ ge_double(sum, sum, &minus_R); // reuse minus_R as temporary
+ ge_double(sum, sum, &minus_R); // reuse minus_R as temporary
+ ge_double(sum, sum, &minus_R); // reuse minus_R as temporary
+ ge_tobytes(check, sum);
+ return crypto_verify32(check, zero_point);
+}
+
+// 5-bit signed comb in cached format (Niels coordinates, Z=1)
+static const ge_precomp b_comb_low[8] = {
+ {{-6816601,-2324159,-22559413,124364,18015490,
+ 8373481,19993724,1979872,-18549925,9085059,},
+ {10306321,403248,14839893,9633706,8463310,
+ -8354981,-14305673,14668847,26301366,2818560,},
+ {-22701500,-3210264,-13831292,-2927732,-16326337,
+ -14016360,12940910,177905,12165515,-2397893,},},
+ {{-12282262,-7022066,9920413,-3064358,-32147467,
+ 2927790,22392436,-14852487,2719975,16402117,},
+ {-7236961,-4729776,2685954,-6525055,-24242706,
+ -15940211,-6238521,14082855,10047669,12228189,},
+ {-30495588,-12893761,-11161261,3539405,-11502464,
+ 16491580,-27286798,-15030530,-7272871,-15934455,},},
+ {{17650926,582297,-860412,-187745,-12072900,
+ -10683391,-20352381,15557840,-31072141,-5019061,},
+ {-6283632,-2259834,-4674247,-4598977,-4089240,
+ 12435688,-31278303,1060251,6256175,10480726,},
+ {-13871026,2026300,-21928428,-2741605,-2406664,
+ -8034988,7355518,15733500,-23379862,7489131,},},
+ {{6883359,695140,23196907,9644202,-33430614,
+ 11354760,-20134606,6388313,-8263585,-8491918,},
+ {-7716174,-13605463,-13646110,14757414,-19430591,
+ -14967316,10359532,-11059670,-21935259,12082603,},
+ {-11253345,-15943946,10046784,5414629,24840771,
+ 8086951,-6694742,9868723,15842692,-16224787,},},
+ {{9639399,11810955,-24007778,-9320054,3912937,
+ -9856959,996125,-8727907,-8919186,-14097242,},
+ {7248867,14468564,25228636,-8795035,14346339,
+ 8224790,6388427,-7181107,6468218,-8720783,},
+ {15513115,15439095,7342322,-10157390,18005294,
+ -7265713,2186239,4884640,10826567,7135781,},},
+ {{-14204238,5297536,-5862318,-6004934,28095835,
+ 4236101,-14203318,1958636,-16816875,3837147,},
+ {-5511166,-13176782,-29588215,12339465,15325758,
+ -15945770,-8813185,11075932,-19608050,-3776283,},
+ {11728032,9603156,-4637821,-5304487,-7827751,
+ 2724948,31236191,-16760175,-7268616,14799772,},},
+ {{-28842672,4840636,-12047946,-9101456,-1445464,
+ 381905,-30977094,-16523389,1290540,12798615,},
+ {27246947,-10320914,14792098,-14518944,5302070,
+ -8746152,-3403974,-4149637,-27061213,10749585,},
+ {25572375,-6270368,-15353037,16037944,1146292,
+ 32198,23487090,9585613,24714571,-1418265,},},
+ {{19844825,282124,-17583147,11004019,-32004269,
+ -2716035,6105106,-1711007,-21010044,14338445,},
+ {8027505,8191102,-18504907,-12335737,25173494,
+ -5923905,15446145,7483684,-30440441,10009108,},
+ {-14134701,-4174411,10246585,-14677495,33553567,
+ -14012935,23366126,15080531,-7969992,7663473,},},
+};
+
+static const ge_precomp b_comb_high[8] = {
+ {{33055887,-4431773,-521787,6654165,951411,
+ -6266464,-5158124,6995613,-5397442,-6985227,},
+ {4014062,6967095,-11977872,3960002,8001989,
+ 5130302,-2154812,-1899602,-31954493,-16173976,},
+ {16271757,-9212948,23792794,731486,-25808309,
+ -3546396,6964344,-4767590,10976593,10050757,},},
+ {{2533007,-4288439,-24467768,-12387405,-13450051,
+ 14542280,12876301,13893535,15067764,8594792,},
+ {20073501,-11623621,3165391,-13119866,13188608,
+ -11540496,-10751437,-13482671,29588810,2197295,},
+ {-1084082,11831693,6031797,14062724,14748428,
+ -8159962,-20721760,11742548,31368706,13161200,},},
+ {{2050412,-6457589,15321215,5273360,25484180,
+ 124590,-18187548,-7097255,-6691621,-14604792,},
+ {9938196,2162889,-6158074,-1711248,4278932,
+ -2598531,-22865792,-7168500,-24323168,11746309,},
+ {-22691768,-14268164,5965485,9383325,20443693,
+ 5854192,28250679,-1381811,-10837134,13717818,},},
+ {{-8495530,16382250,9548884,-4971523,-4491811,
+ -3902147,6182256,-12832479,26628081,10395408,},
+ {27329048,-15853735,7715764,8717446,-9215518,
+ -14633480,28982250,-5668414,4227628,242148,},
+ {-13279943,-7986904,-7100016,8764468,-27276630,
+ 3096719,29678419,-9141299,3906709,11265498,},},
+ {{11918285,15686328,-17757323,-11217300,-27548967,
+ 4853165,-27168827,6807359,6871949,-1075745,},
+ {-29002610,13984323,-27111812,-2713442,28107359,
+ -13266203,6155126,15104658,3538727,-7513788,},
+ {14103158,11233913,-33165269,9279850,31014152,
+ 4335090,-1827936,4590951,13960841,12787712,},},
+ {{1469134,-16738009,33411928,13942824,8092558,
+ -8778224,-11165065,1437842,22521552,-2792954,},
+ {31352705,-4807352,-25327300,3962447,12541566,
+ -9399651,-27425693,7964818,-23829869,5541287,},
+ {-25732021,-6864887,23848984,3039395,-9147354,
+ 6022816,-27421653,10590137,25309915,-1584678,},},
+ {{-22951376,5048948,31139401,-190316,-19542447,
+ -626310,-17486305,-16511925,-18851313,-12985140,},
+ {-9684890,14681754,30487568,7717771,-10829709,
+ 9630497,30290549,-10531496,-27798994,-13812825,},
+ {5827835,16097107,-24501327,12094619,7413972,
+ 11447087,28057551,-1793987,-14056981,4359312,},},
+ {{26323183,2342588,-21887793,-1623758,-6062284,
+ 2107090,-28724907,9036464,-19618351,-13055189,},
+ {-29697200,14829398,-4596333,14220089,-30022969,
+ 2955645,12094100,-13693652,-5941445,7047569,},
+ {-3201977,14413268,-12058324,-16417589,-9035655,
+ -7224648,9258160,1399236,30397584,-5684634,},},
+};
+
+static void lookup_add(ge *p, ge_precomp *tmp_c, fe tmp_a, fe tmp_b,
+ const ge_precomp comb[8], const u8 scalar[32], int i)
+{
+ u8 teeth = (u8)((scalar_bit(scalar, i) ) +
+ (scalar_bit(scalar, i + 32) << 1) +
+ (scalar_bit(scalar, i + 64) << 2) +
+ (scalar_bit(scalar, i + 96) << 3));
+ u8 high = teeth >> 3;
+ u8 index = (teeth ^ (high - 1)) & 7;
+ FOR (j, 0, 8) {
+ i32 select = 1 & (((j ^ index) - 1) >> 8);
+ fe_ccopy(tmp_c->Yp, comb[j].Yp, select);
+ fe_ccopy(tmp_c->Ym, comb[j].Ym, select);
+ fe_ccopy(tmp_c->T2, comb[j].T2, select);
+ }
+ fe_neg(tmp_a, tmp_c->T2);
+ fe_cswap(tmp_c->T2, tmp_a , high ^ 1);
+ fe_cswap(tmp_c->Yp, tmp_c->Ym, high ^ 1);
+ ge_madd(p, p, tmp_c, tmp_a, tmp_b);
+}
+
+// p = [scalar]B, where B is the base point
+static void ge_scalarmult_base(ge *p, const u8 scalar[32])
+{
+ // twin 4-bits signed combs, from Mike Hamburg's
+ // Fast and compact elliptic-curve cryptography (2012)
+ // 1 / 2 modulo L
+ static const u8 half_mod_L[32] = {
+ 247,233,122,46,141,49,9,44,107,206,123,81,239,124,111,10,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,
+ };
+ // (2^256 - 1) / 2 modulo L
+ static const u8 half_ones[32] = {
+ 142,74,204,70,186,24,118,107,184,231,190,57,250,173,119,99,
+ 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,7,
+ };
+
+ // All bits set form: 1 means 1, 0 means -1
+ u8 s_scalar[32];
+ crypto_eddsa_mul_add(s_scalar, scalar, half_mod_L, half_ones);
+
+ // Double and add ladder
+ fe tmp_a, tmp_b; // temporaries for addition
+ ge_precomp tmp_c; // temporary for comb lookup
+ ge tmp_d; // temporary for doubling
+ fe_1(tmp_c.Yp);
+ fe_1(tmp_c.Ym);
+ fe_0(tmp_c.T2);
+
+ // Save a double on the first iteration
+ ge_zero(p);
+ lookup_add(p, &tmp_c, tmp_a, tmp_b, b_comb_low , s_scalar, 31);
+ lookup_add(p, &tmp_c, tmp_a, tmp_b, b_comb_high, s_scalar, 31+128);
+ // Regular double & add for the rest
+ for (int i = 30; i >= 0; i--) {
+ ge_double(p, p, &tmp_d);
+ lookup_add(p, &tmp_c, tmp_a, tmp_b, b_comb_low , s_scalar, i);
+ lookup_add(p, &tmp_c, tmp_a, tmp_b, b_comb_high, s_scalar, i+128);
+ }
+ // Note: we could save one addition at the end if we assumed the
+ // scalar fit in 252 bits. Which it does in practice if it is
+ // selected at random. However, non-random, non-hashed scalars
+ // *can* overflow 252 bits in practice. Better account for that
+ // than leaving that kind of subtle corner case.
+
+ WIPE_BUFFER(tmp_a); WIPE_CTX(&tmp_d);
+ WIPE_BUFFER(tmp_b); WIPE_CTX(&tmp_c);
+ WIPE_BUFFER(s_scalar);
+}
+
+void crypto_eddsa_scalarbase(u8 point[32], const u8 scalar[32])
+{
+ ge P;
+ ge_scalarmult_base(&P, scalar);
+ ge_tobytes(point, &P);
+ WIPE_CTX(&P);
+}
+
+void crypto_eddsa_key_pair(u8 secret_key[64], u8 public_key[32], u8 seed[32])
+{
+ // To allow overlaps, observable writes happen in this order:
+ // 1. seed
+ // 2. secret_key
+ // 3. public_key
+ u8 a[64];
+ COPY(a, seed, 32);
+ crypto_wipe(seed, 32);
+ COPY(secret_key, a, 32);
+ crypto_blake2b(a, 64, a, 32);
+ crypto_eddsa_trim_scalar(a, a);
+ crypto_eddsa_scalarbase(secret_key + 32, a);
+ COPY(public_key, secret_key + 32, 32);
+ WIPE_BUFFER(a);
+}
+
+static void hash_reduce(u8 h[32],
+ const u8 *a, size_t a_size,
+ const u8 *b, size_t b_size,
+ const u8 *c, size_t c_size)
+{
+ u8 hash[64];
+ crypto_blake2b_ctx ctx;
+ crypto_blake2b_init (&ctx, 64);
+ crypto_blake2b_update(&ctx, a, a_size);
+ crypto_blake2b_update(&ctx, b, b_size);
+ crypto_blake2b_update(&ctx, c, c_size);
+ crypto_blake2b_final (&ctx, hash);
+ crypto_eddsa_reduce(h, hash);
+}
+
+// Digital signature of a message with from a secret key.
+//
+// The secret key comprises two parts:
+// - The seed that generates the key (secret_key[ 0..31])
+// - The public key (secret_key[32..63])
+//
+// The seed and the public key are bundled together to make sure users
+// don't use mismatched seeds and public keys, which would instantly
+// leak the secret scalar and allow forgeries (allowing this to happen
+// has resulted in critical vulnerabilities in the wild).
+//
+// The seed is hashed to derive the secret scalar and a secret prefix.
+// The sole purpose of the prefix is to generate a secret random nonce.
+// The properties of that nonce must be as follows:
+// - Unique: we need a different one for each message.
+// - Secret: third parties must not be able to predict it.
+// - Random: any detectable bias would break all security.
+//
+// There are two ways to achieve these properties. The obvious one is
+// to simply generate a random number. Here that would be a parameter
+// (Monocypher doesn't have an RNG). It works, but then users may reuse
+// the nonce by accident, which _also_ leaks the secret scalar and
+// allows forgeries. This has happened in the wild too.
+//
+// This is no good, so instead we generate that nonce deterministically
+// by reducing modulo L a hash of the secret prefix and the message.
+// The secret prefix makes the nonce unpredictable, the message makes it
+// unique, and the hash/reduce removes all bias.
+//
+// The cost of that safety is hashing the message twice. If that cost
+// is unacceptable, there are two alternatives:
+//
+// - Signing a hash of the message instead of the message itself. This
+// is fine as long as the hash is collision resistant. It is not
+// compatible with existing "pure" signatures, but at least it's safe.
+//
+// - Using a random nonce. Please exercise **EXTREME CAUTION** if you
+// ever do that. It is absolutely **critical** that the nonce is
+// really an unbiased random number between 0 and L-1, never reused,
+// and wiped immediately.
+//
+// To lower the likelihood of complete catastrophe if the RNG is
+// either flawed or misused, you can hash the RNG output together with
+// the secret prefix and the beginning of the message, and use the
+// reduction of that hash instead of the RNG output itself. It's not
+// foolproof (you'd need to hash the whole message) but it helps.
+//
+// Signing a message involves the following operations:
+//
+// scalar, prefix = HASH(secret_key)
+// r = HASH(prefix || message) % L
+// R = [r]B
+// h = HASH(R || public_key || message) % L
+// S = ((h * a) + r) % L
+// signature = R || S
+void crypto_eddsa_sign(u8 signature [64], const u8 secret_key[64],
+ const u8 *message, size_t message_size)
+{
+ u8 a[64]; // secret scalar and prefix
+ u8 r[32]; // secret deterministic "random" nonce
+ u8 h[32]; // publically verifiable hash of the message (not wiped)
+ u8 R[32]; // first half of the signature (allows overlapping inputs)
+
+ crypto_blake2b(a, 64, secret_key, 32);
+ crypto_eddsa_trim_scalar(a, a);
+ hash_reduce(r, a + 32, 32, message, message_size, 0, 0);
+ crypto_eddsa_scalarbase(R, r);
+ hash_reduce(h, R, 32, secret_key + 32, 32, message, message_size);
+ COPY(signature, R, 32);
+ crypto_eddsa_mul_add(signature + 32, h, a, r);
+
+ WIPE_BUFFER(a);
+ WIPE_BUFFER(r);
+}
+
+// To check the signature R, S of the message M with the public key A,
+// there are 3 steps:
+//
+// compute h = HASH(R || A || message) % L
+// check that A is on the curve.
+// check that R == [s]B - [h]A
+//
+// The last two steps are done in crypto_eddsa_check_equation()
+int crypto_eddsa_check(const u8 signature[64], const u8 public_key[32],
+ const u8 *message, size_t message_size)
+{
+ u8 h[32];
+ hash_reduce(h, signature, 32, public_key, 32, message, message_size);
+ return crypto_eddsa_check_equation(signature, public_key, h);
+}
+
+/////////////////////////
+/// EdDSA <--> X25519 ///
+/////////////////////////
+void crypto_eddsa_to_x25519(u8 x25519[32], const u8 eddsa[32])
+{
+ // (u, v) = ((1+y)/(1-y), sqrt(-486664)*u/x)
+ // Only converting y to u, the sign of x is ignored.
+ fe t1, t2;
+ fe_frombytes(t2, eddsa);
+ fe_add(t1, fe_one, t2);
+ fe_sub(t2, fe_one, t2);
+ fe_invert(t2, t2);
+ fe_mul(t1, t1, t2);
+ fe_tobytes(x25519, t1);
+ WIPE_BUFFER(t1);
+ WIPE_BUFFER(t2);
+}
+
+void crypto_x25519_to_eddsa(u8 eddsa[32], const u8 x25519[32])
+{
+ // (x, y) = (sqrt(-486664)*u/v, (u-1)/(u+1))
+ // Only converting u to y, x is assumed positive.
+ fe t1, t2;
+ fe_frombytes(t2, x25519);
+ fe_sub(t1, t2, fe_one);
+ fe_add(t2, t2, fe_one);
+ fe_invert(t2, t2);
+ fe_mul(t1, t1, t2);
+ fe_tobytes(eddsa, t1);
+ WIPE_BUFFER(t1);
+ WIPE_BUFFER(t2);
+}
+
+/////////////////////////////////////////////
+/// Dirty ephemeral public key generation ///
+/////////////////////////////////////////////
+
+// Those functions generates a public key, *without* clearing the
+// cofactor. Sending that key over the network leaks 3 bits of the
+// private key. Use only to generate ephemeral keys that will be hidden
+// with crypto_curve_to_hidden().
+//
+// The public key is otherwise compatible with crypto_x25519(), which
+// properly clears the cofactor.
+//
+// Note that the distribution of the resulting public keys is almost
+// uniform. Flipping the sign of the v coordinate (not provided by this
+// function), covers the entire key space almost perfectly, where
+// "almost" means a 2^-128 bias (undetectable). This uniformity is
+// needed to ensure the proper randomness of the resulting
+// representatives (once we apply crypto_curve_to_hidden()).
+//
+// Recall that Curve25519 has order C = 2^255 + e, with e < 2^128 (not
+// to be confused with the prime order of the main subgroup, L, which is
+// 8 times less than that).
+//
+// Generating all points would require us to multiply a point of order C
+// (the base point plus any point of order 8) by all scalars from 0 to
+// C-1. Clamping limits us to scalars between 2^254 and 2^255 - 1. But
+// by negating the resulting point at random, we also cover scalars from
+// -2^255 + 1 to -2^254 (which modulo C is congruent to e+1 to 2^254 + e).
+//
+// In practice:
+// - Scalars from 0 to e + 1 are never generated
+// - Scalars from 2^255 to 2^255 + e are never generated
+// - Scalars from 2^254 + 1 to 2^254 + e are generated twice
+//
+// Since e < 2^128, detecting this bias requires observing over 2^100
+// representatives from a given source (this will never happen), *and*
+// recovering enough of the private key to determine that they do, or do
+// not, belong to the biased set (this practically requires solving
+// discrete logarithm, which is conjecturally intractable).
+//
+// In practice, this means the bias is impossible to detect.
+
+// s + (x*L) % 8*L
+// Guaranteed to fit in 256 bits iff s fits in 255 bits.
+// L < 2^253
+// x%8 < 2^3
+// L * (x%8) < 2^255
+// s < 2^255
+// s + L * (x%8) < 2^256
+static void add_xl(u8 s[32], u8 x)
+{
+ u64 mod8 = x & 7;
+ u64 carry = 0;
+ FOR (i , 0, 8) {
+ carry = carry + load32_le(s + 4*i) + L[i] * mod8;
+ store32_le(s + 4*i, (u32)carry);
+ carry >>= 32;
+ }
+}
+
+// "Small" dirty ephemeral key.
+// Use if you need to shrink the size of the binary, and can afford to
+// slow down by a factor of two (compared to the fast version)
+//
+// This version works by decoupling the cofactor from the main factor.
+//
+// - The trimmed scalar determines the main factor
+// - The clamped bits of the scalar determine the cofactor.
+//
+// Cofactor and main factor are combined into a single scalar, which is
+// then multiplied by a point of order 8*L (unlike the base point, which
+// has prime order). That "dirty" base point is the addition of the
+// regular base point (9), and a point of order 8.
+void crypto_x25519_dirty_small(u8 public_key[32], const u8 secret_key[32])
+{
+ // Base point of order 8*L
+ // Raw scalar multiplication with it does not clear the cofactor,
+ // and the resulting public key will reveal 3 bits of the scalar.
+ //
+ // The low order component of this base point has been chosen
+ // to yield the same results as crypto_x25519_dirty_fast().
+ static const u8 dirty_base_point[32] = {
+ 0xd8, 0x86, 0x1a, 0xa2, 0x78, 0x7a, 0xd9, 0x26,
+ 0x8b, 0x74, 0x74, 0xb6, 0x82, 0xe3, 0xbe, 0xc3,
+ 0xce, 0x36, 0x9a, 0x1e, 0x5e, 0x31, 0x47, 0xa2,
+ 0x6d, 0x37, 0x7c, 0xfd, 0x20, 0xb5, 0xdf, 0x75,
+ };
+ // separate the main factor & the cofactor of the scalar
+ u8 scalar[32];
+ crypto_eddsa_trim_scalar(scalar, secret_key);
+
+ // Separate the main factor and the cofactor
+ //
+ // The scalar is trimmed, so its cofactor is cleared. The three
+ // least significant bits however still have a main factor. We must
+ // remove it for X25519 compatibility.
+ //
+ // cofactor = lsb * L (modulo 8*L)
+ // combined = scalar + cofactor (modulo 8*L)
+ add_xl(scalar, secret_key[0]);
+ scalarmult(public_key, scalar, dirty_base_point, 256);
+ WIPE_BUFFER(scalar);
+}
+
+// Select low order point
+// We're computing the [cofactor]lop scalar multiplication, where:
+//
+// cofactor = tweak & 7.
+// lop = (lop_x, lop_y)
+// lop_x = sqrt((sqrt(d + 1) + 1) / d)
+// lop_y = -lop_x * sqrtm1
+//
+// The low order point has order 8. There are 4 such points. We've
+// chosen the one whose both coordinates are positive (below p/2).
+// The 8 low order points are as follows:
+//
+// [0]lop = ( 0 , 1 )
+// [1]lop = ( lop_x , lop_y)
+// [2]lop = ( sqrt(-1), -0 )
+// [3]lop = ( lop_x , -lop_y)
+// [4]lop = (-0 , -1 )
+// [5]lop = (-lop_x , -lop_y)
+// [6]lop = (-sqrt(-1), 0 )
+// [7]lop = (-lop_x , lop_y)
+//
+// The x coordinate is either 0, sqrt(-1), lop_x, or their opposite.
+// The y coordinate is either 0, -1 , lop_y, or their opposite.
+// The pattern for both is the same, except for a rotation of 2 (modulo 8)
+//
+// This helper function captures the pattern, and we can use it thus:
+//
+// select_lop(x, lop_x, sqrtm1, cofactor);
+// select_lop(y, lop_y, fe_one, cofactor + 2);
+//
+// This is faster than an actual scalar multiplication,
+// and requires less code than naive constant time look up.
+static void select_lop(fe out, const fe x, const fe k, u8 cofactor)
+{
+ fe tmp;
+ fe_0(out);
+ fe_ccopy(out, k , (cofactor >> 1) & 1); // bit 1
+ fe_ccopy(out, x , (cofactor >> 0) & 1); // bit 0
+ fe_neg (tmp, out);
+ fe_ccopy(out, tmp, (cofactor >> 2) & 1); // bit 2
+ WIPE_BUFFER(tmp);
+}
+
+// "Fast" dirty ephemeral key
+// We use this one by default.
+//
+// This version works by performing a regular scalar multiplication,
+// then add a low order point. The scalar multiplication is done in
+// Edwards space for more speed (*2 compared to the "small" version).
+// The cost is a bigger binary for programs that don't also sign messages.
+void crypto_x25519_dirty_fast(u8 public_key[32], const u8 secret_key[32])
+{
+ // Compute clean scalar multiplication
+ u8 scalar[32];
+ ge pk;
+ crypto_eddsa_trim_scalar(scalar, secret_key);
+ ge_scalarmult_base(&pk, scalar);
+
+ // Compute low order point
+ fe t1, t2;
+ select_lop(t1, lop_x, sqrtm1, secret_key[0]);
+ select_lop(t2, lop_y, fe_one, secret_key[0] + 2);
+ ge_precomp low_order_point;
+ fe_add(low_order_point.Yp, t2, t1);
+ fe_sub(low_order_point.Ym, t2, t1);
+ fe_mul(low_order_point.T2, t2, t1);
+ fe_mul(low_order_point.T2, low_order_point.T2, D2);
+
+ // Add low order point to the public key
+ ge_madd(&pk, &pk, &low_order_point, t1, t2);
+
+ // Convert to Montgomery u coordinate (we ignore the sign)
+ fe_add(t1, pk.Z, pk.Y);
+ fe_sub(t2, pk.Z, pk.Y);
+ fe_invert(t2, t2);
+ fe_mul(t1, t1, t2);
+
+ fe_tobytes(public_key, t1);
+
+ WIPE_BUFFER(t1); WIPE_CTX(&pk);
+ WIPE_BUFFER(t2); WIPE_CTX(&low_order_point);
+ WIPE_BUFFER(scalar);
+}
+
+///////////////////
+/// Elligator 2 ///
+///////////////////
+static const fe A = {486662};
+
+// Elligator direct map
+//
+// Computes the point corresponding to a representative, encoded in 32
+// bytes (little Endian). Since positive representatives fits in 254
+// bits, The two most significant bits are ignored.
+//
+// From the paper:
+// w = -A / (fe(1) + non_square * r^2)
+// e = chi(w^3 + A*w^2 + w)
+// u = e*w - (fe(1)-e)*(A//2)
+// v = -e * sqrt(u^3 + A*u^2 + u)
+//
+// We ignore v because we don't need it for X25519 (the Montgomery
+// ladder only uses u).
+//
+// Note that e is either 0, 1 or -1
+// if e = 0 u = 0 and v = 0
+// if e = 1 u = w
+// if e = -1 u = -w - A = w * non_square * r^2
+//
+// Let r1 = non_square * r^2
+// Let r2 = 1 + r1
+// Note that r2 cannot be zero, -1/non_square is not a square.
+// We can (tediously) verify that:
+// w^3 + A*w^2 + w = (A^2*r1 - r2^2) * A / r2^3
+// Therefore:
+// chi(w^3 + A*w^2 + w) = chi((A^2*r1 - r2^2) * (A / r2^3))
+// chi(w^3 + A*w^2 + w) = chi((A^2*r1 - r2^2) * (A / r2^3)) * 1
+// chi(w^3 + A*w^2 + w) = chi((A^2*r1 - r2^2) * (A / r2^3)) * chi(r2^6)
+// chi(w^3 + A*w^2 + w) = chi((A^2*r1 - r2^2) * (A / r2^3) * r2^6)
+// chi(w^3 + A*w^2 + w) = chi((A^2*r1 - r2^2) * A * r2^3)
+// Corollary:
+// e = 1 if (A^2*r1 - r2^2) * A * r2^3) is a non-zero square
+// e = -1 if (A^2*r1 - r2^2) * A * r2^3) is not a square
+// Note that w^3 + A*w^2 + w (and therefore e) can never be zero:
+// w^3 + A*w^2 + w = w * (w^2 + A*w + 1)
+// w^3 + A*w^2 + w = w * (w^2 + A*w + A^2/4 - A^2/4 + 1)
+// w^3 + A*w^2 + w = w * (w + A/2)^2 - A^2/4 + 1)
+// which is zero only if:
+// w = 0 (impossible)
+// (w + A/2)^2 = A^2/4 - 1 (impossible, because A^2/4-1 is not a square)
+//
+// Let isr = invsqrt((A^2*r1 - r2^2) * A * r2^3)
+// isr = sqrt(1 / ((A^2*r1 - r2^2) * A * r2^3)) if e = 1
+// isr = sqrt(sqrt(-1) / ((A^2*r1 - r2^2) * A * r2^3)) if e = -1
+//
+// if e = 1
+// let u1 = -A * (A^2*r1 - r2^2) * A * r2^2 * isr^2
+// u1 = w
+// u1 = u
+//
+// if e = -1
+// let ufactor = -non_square * sqrt(-1) * r^2
+// let vfactor = sqrt(ufactor)
+// let u2 = -A * (A^2*r1 - r2^2) * A * r2^2 * isr^2 * ufactor
+// u2 = w * -1 * -non_square * r^2
+// u2 = w * non_square * r^2
+// u2 = u
+void crypto_elligator_map(u8 curve[32], const u8 hidden[32])
+{
+ fe r, u, t1, t2, t3;
+ fe_frombytes_mask(r, hidden, 2); // r is encoded in 254 bits.
+ fe_sq(r, r);
+ fe_add(t1, r, r);
+ fe_add(u, t1, fe_one);
+ fe_sq (t2, u);
+ fe_mul(t3, A2, t1);
+ fe_sub(t3, t3, t2);
+ fe_mul(t3, t3, A);
+ fe_mul(t1, t2, u);
+ fe_mul(t1, t3, t1);
+ int is_square = invsqrt(t1, t1);
+ fe_mul(u, r, ufactor);
+ fe_ccopy(u, fe_one, is_square);
+ fe_sq (t1, t1);
+ fe_mul(u, u, A);
+ fe_mul(u, u, t3);
+ fe_mul(u, u, t2);
+ fe_mul(u, u, t1);
+ fe_neg(u, u);
+ fe_tobytes(curve, u);
+
+ WIPE_BUFFER(t1); WIPE_BUFFER(r);
+ WIPE_BUFFER(t2); WIPE_BUFFER(u);
+ WIPE_BUFFER(t3);
+}
+
+// Elligator inverse map
+//
+// Computes the representative of a point, if possible. If not, it does
+// nothing and returns -1. Note that the success of the operation
+// depends only on the point (more precisely its u coordinate). The
+// tweak parameter is used only upon success
+//
+// The tweak should be a random byte. Beyond that, its contents are an
+// implementation detail. Currently, the tweak comprises:
+// - Bit 1 : sign of the v coordinate (0 if positive, 1 if negative)
+// - Bit 2-5: not used
+// - Bits 6-7: random padding
+//
+// From the paper:
+// Let sq = -non_square * u * (u+A)
+// if sq is not a square, or u = -A, there is no mapping
+// Assuming there is a mapping:
+// if v is positive: r = sqrt(-u / (non_square * (u+A)))
+// if v is negative: r = sqrt(-(u+A) / (non_square * u ))
+//
+// We compute isr = invsqrt(-non_square * u * (u+A))
+// if it wasn't a square, abort.
+// else, isr = sqrt(-1 / (non_square * u * (u+A))
+//
+// If v is positive, we return isr * u:
+// isr * u = sqrt(-1 / (non_square * u * (u+A)) * u
+// isr * u = sqrt(-u / (non_square * (u+A))
+//
+// If v is negative, we return isr * (u+A):
+// isr * (u+A) = sqrt(-1 / (non_square * u * (u+A)) * (u+A)
+// isr * (u+A) = sqrt(-(u+A) / (non_square * u)
+int crypto_elligator_rev(u8 hidden[32], const u8 public_key[32], u8 tweak)
+{
+ fe t1, t2, t3;
+ fe_frombytes(t1, public_key); // t1 = u
+
+ fe_add(t2, t1, A); // t2 = u + A
+ fe_mul(t3, t1, t2);
+ fe_mul_small(t3, t3, -2);
+ int is_square = invsqrt(t3, t3); // t3 = sqrt(-1 / non_square * u * (u+A))
+ if (is_square) {
+ // The only variable time bit. This ultimately reveals how many
+ // tries it took us to find a representable key.
+ // This does not affect security as long as we try keys at random.
+
+ fe_ccopy (t1, t2, tweak & 1); // multiply by u if v is positive,
+ fe_mul (t3, t1, t3); // multiply by u+A otherwise
+ fe_mul_small(t1, t3, 2);
+ fe_neg (t2, t3);
+ fe_ccopy (t3, t2, fe_isodd(t1));
+ fe_tobytes(hidden, t3);
+
+ // Pad with two random bits
+ hidden[31] |= tweak & 0xc0;
+ }
+
+ WIPE_BUFFER(t1);
+ WIPE_BUFFER(t2);
+ WIPE_BUFFER(t3);
+ return is_square - 1;
+}
+
+void crypto_elligator_key_pair(u8 hidden[32], u8 secret_key[32], u8 seed[32])
+{
+ u8 pk [32]; // public key
+ u8 buf[64]; // seed + representative
+ COPY(buf + 32, seed, 32);
+ do {
+ crypto_chacha20_djb(buf, 0, 64, buf+32, zero, 0);
+ crypto_x25519_dirty_fast(pk, buf); // or the "small" version
+ } while(crypto_elligator_rev(buf+32, pk, buf[32]));
+ // Note that the return value of crypto_elligator_rev() is
+ // independent from its tweak parameter.
+ // Therefore, buf[32] is not actually reused. Either we loop one
+ // more time and buf[32] is used for the new seed, or we succeeded,
+ // and buf[32] becomes the tweak parameter.
+
+ crypto_wipe(seed, 32);
+ COPY(hidden , buf + 32, 32);
+ COPY(secret_key, buf , 32);
+ WIPE_BUFFER(buf);
+ WIPE_BUFFER(pk);
+}
+
+///////////////////////
+/// Scalar division ///
+///////////////////////
+
+// Montgomery reduction.
+// Divides x by (2^256), and reduces the result modulo L
+//
+// Precondition:
+// x < L * 2^256
+// Constants:
+// r = 2^256 (makes division by r trivial)
+// k = (r * (1/r) - 1) // L (1/r is computed modulo L )
+// Algorithm:
+// s = (x * k) % r
+// t = x + s*L (t is always a multiple of r)
+// u = (t/r) % L (u is always below 2*L, conditional subtraction is enough)
+static void redc(u32 u[8], u32 x[16])
+{
+ static const u32 k[8] = {
+ 0x12547e1b, 0xd2b51da3, 0xfdba84ff, 0xb1a206f2,
+ 0xffa36bea, 0x14e75438, 0x6fe91836, 0x9db6c6f2,
+ };
+
+ // s = x * k (modulo 2^256)
+ // This is cheaper than the full multiplication.
+ u32 s[8] = {0};
+ FOR (i, 0, 8) {
+ u64 carry = 0;
+ FOR (j, 0, 8-i) {
+ carry += s[i+j] + (u64)x[i] * k[j];
+ s[i+j] = (u32)carry;
+ carry >>= 32;
+ }
+ }
+ u32 t[16] = {0};
+ multiply(t, s, L);
+
+ // t = t + x
+ u64 carry = 0;
+ FOR (i, 0, 16) {
+ carry += (u64)t[i] + x[i];
+ t[i] = (u32)carry;
+ carry >>= 32;
+ }
+
+ // u = (t / 2^256) % L
+ // Note that t / 2^256 is always below 2*L,
+ // So a constant time conditional subtraction is enough
+ remove_l(u, t+8);
+
+ WIPE_BUFFER(s);
+ WIPE_BUFFER(t);
+}
+
+void crypto_x25519_inverse(u8 blind_salt [32], const u8 private_key[32],
+ const u8 curve_point[32])
+{
+ static const u8 Lm2[32] = { // L - 2
+ 0xeb, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,
+ 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
+ };
+ // 1 in Montgomery form
+ u32 m_inv [8] = {
+ 0x8d98951d, 0xd6ec3174, 0x737dcf70, 0xc6ef5bf4,
+ 0xfffffffe, 0xffffffff, 0xffffffff, 0x0fffffff,
+ };
+
+ u8 scalar[32];
+ crypto_eddsa_trim_scalar(scalar, private_key);
+
+ // Convert the scalar in Montgomery form
+ // m_scl = scalar * 2^256 (modulo L)
+ u32 m_scl[8];
+ {
+ u32 tmp[16];
+ ZERO(tmp, 8);
+ load32_le_buf(tmp+8, scalar, 8);
+ mod_l(scalar, tmp);
+ load32_le_buf(m_scl, scalar, 8);
+ WIPE_BUFFER(tmp); // Wipe ASAP to save stack space
+ }
+
+ // Compute the inverse
+ u32 product[16];
+ for (int i = 252; i >= 0; i--) {
+ ZERO(product, 16);
+ multiply(product, m_inv, m_inv);
+ redc(m_inv, product);
+ if (scalar_bit(Lm2, i)) {
+ ZERO(product, 16);
+ multiply(product, m_inv, m_scl);
+ redc(m_inv, product);
+ }
+ }
+ // Convert the inverse *out* of Montgomery form
+ // scalar = m_inv / 2^256 (modulo L)
+ COPY(product, m_inv, 8);
+ ZERO(product + 8, 8);
+ redc(m_inv, product);
+ store32_le_buf(scalar, m_inv, 8); // the *inverse* of the scalar
+
+ // Clear the cofactor of scalar:
+ // cleared = scalar * (3*L + 1) (modulo 8*L)
+ // cleared = scalar + scalar * 3 * L (modulo 8*L)
+ // Note that (scalar * 3) is reduced modulo 8, so we only need the
+ // first byte.
+ add_xl(scalar, scalar[0] * 3);
+
+ // Recall that 8*L < 2^256. However it is also very close to
+ // 2^255. If we spanned the ladder over 255 bits, random tests
+ // wouldn't catch the off-by-one error.
+ scalarmult(blind_salt, scalar, curve_point, 256);
+
+ WIPE_BUFFER(scalar); WIPE_BUFFER(m_scl);
+ WIPE_BUFFER(product); WIPE_BUFFER(m_inv);
+}
+
+////////////////////////////////
+/// Authenticated encryption ///
+////////////////////////////////
+static void lock_auth(u8 mac[16], const u8 auth_key[32],
+ const u8 *ad , size_t ad_size,
+ const u8 *cipher_text, size_t text_size)
+{
+ u8 sizes[16]; // Not secret, not wiped
+ store64_le(sizes + 0, ad_size);
+ store64_le(sizes + 8, text_size);
+ crypto_poly1305_ctx poly_ctx; // auto wiped...
+ crypto_poly1305_init (&poly_ctx, auth_key);
+ crypto_poly1305_update(&poly_ctx, ad , ad_size);
+ crypto_poly1305_update(&poly_ctx, zero , gap(ad_size, 16));
+ crypto_poly1305_update(&poly_ctx, cipher_text, text_size);
+ crypto_poly1305_update(&poly_ctx, zero , gap(text_size, 16));
+ crypto_poly1305_update(&poly_ctx, sizes , 16);
+ crypto_poly1305_final (&poly_ctx, mac); // ...here
+}
+
+void crypto_aead_init_x(crypto_aead_ctx *ctx,
+ u8 const key[32], const u8 nonce[24])
+{
+ crypto_chacha20_h(ctx->key, key, nonce);
+ COPY(ctx->nonce, nonce + 16, 8);
+ ctx->counter = 0;
+}
+
+void crypto_aead_init_djb(crypto_aead_ctx *ctx,
+ const u8 key[32], const u8 nonce[8])
+{
+ COPY(ctx->key , key , 32);
+ COPY(ctx->nonce, nonce, 8);
+ ctx->counter = 0;
+}
+
+void crypto_aead_init_ietf(crypto_aead_ctx *ctx,
+ const u8 key[32], const u8 nonce[12])
+{
+ COPY(ctx->key , key , 32);
+ COPY(ctx->nonce, nonce + 4, 8);
+ ctx->counter = (u64)load32_le(nonce) << 32;
+}
+
+void crypto_aead_write(crypto_aead_ctx *ctx, u8 *cipher_text, u8 mac[16],
+ const u8 *ad, size_t ad_size,
+ const u8 *plain_text, size_t text_size)
+{
+ u8 auth_key[64]; // the last 32 bytes are used for rekeying.
+ crypto_chacha20_djb(auth_key, 0, 64, ctx->key, ctx->nonce, ctx->counter);
+ crypto_chacha20_djb(cipher_text, plain_text, text_size,
+ ctx->key, ctx->nonce, ctx->counter + 1);
+ lock_auth(mac, auth_key, ad, ad_size, cipher_text, text_size);
+ COPY(ctx->key, auth_key + 32, 32);
+ WIPE_BUFFER(auth_key);
+}
+
+int crypto_aead_read(crypto_aead_ctx *ctx, u8 *plain_text, const u8 mac[16],
+ const u8 *ad, size_t ad_size,
+ const u8 *cipher_text, size_t text_size)
+{
+ u8 auth_key[64]; // the last 32 bytes are used for rekeying.
+ u8 real_mac[16];
+ crypto_chacha20_djb(auth_key, 0, 64, ctx->key, ctx->nonce, ctx->counter);
+ lock_auth(real_mac, auth_key, ad, ad_size, cipher_text, text_size);
+ int mismatch = crypto_verify16(mac, real_mac);
+ if (!mismatch) {
+ crypto_chacha20_djb(plain_text, cipher_text, text_size,
+ ctx->key, ctx->nonce, ctx->counter + 1);
+ COPY(ctx->key, auth_key + 32, 32);
+ }
+ WIPE_BUFFER(auth_key);
+ WIPE_BUFFER(real_mac);
+ return mismatch;
+}
+
+void crypto_aead_lock(u8 *cipher_text, u8 mac[16], const u8 key[32],
+ const u8 nonce[24], const u8 *ad, size_t ad_size,
+ const u8 *plain_text, size_t text_size)
+{
+ crypto_aead_ctx ctx;
+ crypto_aead_init_x(&ctx, key, nonce);
+ crypto_aead_write(&ctx, cipher_text, mac, ad, ad_size,
+ plain_text, text_size);
+ crypto_wipe(&ctx, sizeof(ctx));
+}
+
+int crypto_aead_unlock(u8 *plain_text, const u8 mac[16], const u8 key[32],
+ const u8 nonce[24], const u8 *ad, size_t ad_size,
+ const u8 *cipher_text, size_t text_size)
+{
+ crypto_aead_ctx ctx;
+ crypto_aead_init_x(&ctx, key, nonce);
+ int mismatch = crypto_aead_read(&ctx, plain_text, mac, ad, ad_size,
+ cipher_text, text_size);
+ crypto_wipe(&ctx, sizeof(ctx));
+ return mismatch;
+}
+
+#ifdef MONOCYPHER_CPP_NAMESPACE
+}
+#endif
diff --git a/hw/application_fpga/tkey-libs/monocypher/monocypher.h b/hw/application_fpga/tkey-libs/monocypher/monocypher.h
new file mode 100644
index 0000000..765a07f
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/monocypher/monocypher.h
@@ -0,0 +1,321 @@
+// Monocypher version 4.0.2
+//
+// This file is dual-licensed. Choose whichever licence you want from
+// the two licences listed below.
+//
+// The first licence is a regular 2-clause BSD licence. The second licence
+// is the CC-0 from Creative Commons. It is intended to release Monocypher
+// to the public domain. The BSD licence serves as a fallback option.
+//
+// SPDX-License-Identifier: BSD-2-Clause OR CC0-1.0
+//
+// ------------------------------------------------------------------------
+//
+// Copyright (c) 2017-2019, Loup Vaillant
+// All rights reserved.
+//
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the
+// distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// ------------------------------------------------------------------------
+//
+// Written in 2017-2019 by Loup Vaillant
+//
+// To the extent possible under law, the author(s) have dedicated all copyright
+// and related neighboring rights to this software to the public domain
+// worldwide. This software is distributed without any warranty.
+//
+// You should have received a copy of the CC0 Public Domain Dedication along
+// with this software. If not, see
+//
+
+#ifndef MONOCYPHER_H
+#define MONOCYPHER_H
+
+#include
+#include
+
+#ifdef MONOCYPHER_CPP_NAMESPACE
+namespace MONOCYPHER_CPP_NAMESPACE {
+#elif defined(__cplusplus)
+extern "C" {
+#endif
+
+// Constant time comparisons
+// -------------------------
+
+// Return 0 if a and b are equal, -1 otherwise
+int crypto_verify16(const uint8_t a[16], const uint8_t b[16]);
+int crypto_verify32(const uint8_t a[32], const uint8_t b[32]);
+int crypto_verify64(const uint8_t a[64], const uint8_t b[64]);
+
+
+// Erase sensitive data
+// --------------------
+void crypto_wipe(void *secret, size_t size);
+
+
+// Authenticated encryption
+// ------------------------
+void crypto_aead_lock(uint8_t *cipher_text,
+ uint8_t mac [16],
+ const uint8_t key [32],
+ const uint8_t nonce[24],
+ const uint8_t *ad, size_t ad_size,
+ const uint8_t *plain_text, size_t text_size);
+int crypto_aead_unlock(uint8_t *plain_text,
+ const uint8_t mac [16],
+ const uint8_t key [32],
+ const uint8_t nonce[24],
+ const uint8_t *ad, size_t ad_size,
+ const uint8_t *cipher_text, size_t text_size);
+
+// Authenticated stream
+// --------------------
+typedef struct {
+ uint64_t counter;
+ uint8_t key[32];
+ uint8_t nonce[8];
+} crypto_aead_ctx;
+
+void crypto_aead_init_x(crypto_aead_ctx *ctx,
+ const uint8_t key[32], const uint8_t nonce[24]);
+void crypto_aead_init_djb(crypto_aead_ctx *ctx,
+ const uint8_t key[32], const uint8_t nonce[8]);
+void crypto_aead_init_ietf(crypto_aead_ctx *ctx,
+ const uint8_t key[32], const uint8_t nonce[12]);
+
+void crypto_aead_write(crypto_aead_ctx *ctx,
+ uint8_t *cipher_text,
+ uint8_t mac[16],
+ const uint8_t *ad , size_t ad_size,
+ const uint8_t *plain_text, size_t text_size);
+int crypto_aead_read(crypto_aead_ctx *ctx,
+ uint8_t *plain_text,
+ const uint8_t mac[16],
+ const uint8_t *ad , size_t ad_size,
+ const uint8_t *cipher_text, size_t text_size);
+
+
+// General purpose hash (BLAKE2b)
+// ------------------------------
+
+// Direct interface
+void crypto_blake2b(uint8_t *hash, size_t hash_size,
+ const uint8_t *message, size_t message_size);
+
+void crypto_blake2b_keyed(uint8_t *hash, size_t hash_size,
+ const uint8_t *key, size_t key_size,
+ const uint8_t *message, size_t message_size);
+
+// Incremental interface
+typedef struct {
+ // Do not rely on the size or contents of this type,
+ // for they may change without notice.
+ uint64_t hash[8];
+ uint64_t input_offset[2];
+ uint64_t input[16];
+ size_t input_idx;
+ size_t hash_size;
+} crypto_blake2b_ctx;
+
+void crypto_blake2b_init(crypto_blake2b_ctx *ctx, size_t hash_size);
+void crypto_blake2b_keyed_init(crypto_blake2b_ctx *ctx, size_t hash_size,
+ const uint8_t *key, size_t key_size);
+void crypto_blake2b_update(crypto_blake2b_ctx *ctx,
+ const uint8_t *message, size_t message_size);
+void crypto_blake2b_final(crypto_blake2b_ctx *ctx, uint8_t *hash);
+
+
+// Password key derivation (Argon2)
+// --------------------------------
+#define CRYPTO_ARGON2_D 0
+#define CRYPTO_ARGON2_I 1
+#define CRYPTO_ARGON2_ID 2
+
+typedef struct {
+ uint32_t algorithm; // Argon2d, Argon2i, Argon2id
+ uint32_t nb_blocks; // memory hardness, >= 8 * nb_lanes
+ uint32_t nb_passes; // CPU hardness, >= 1 (>= 3 recommended for Argon2i)
+ uint32_t nb_lanes; // parallelism level (single threaded anyway)
+} crypto_argon2_config;
+
+typedef struct {
+ const uint8_t *pass;
+ const uint8_t *salt;
+ uint32_t pass_size;
+ uint32_t salt_size; // 16 bytes recommended
+} crypto_argon2_inputs;
+
+typedef struct {
+ const uint8_t *key; // may be NULL if no key
+ const uint8_t *ad; // may be NULL if no additional data
+ uint32_t key_size; // 0 if no key (32 bytes recommended otherwise)
+ uint32_t ad_size; // 0 if no additional data
+} crypto_argon2_extras;
+
+extern const crypto_argon2_extras crypto_argon2_no_extras;
+
+void crypto_argon2(uint8_t *hash, uint32_t hash_size, void *work_area,
+ crypto_argon2_config config,
+ crypto_argon2_inputs inputs,
+ crypto_argon2_extras extras);
+
+
+// Key exchange (X-25519)
+// ----------------------
+
+// Shared secrets are not quite random.
+// Hash them to derive an actual shared key.
+void crypto_x25519_public_key(uint8_t public_key[32],
+ const uint8_t secret_key[32]);
+void crypto_x25519(uint8_t raw_shared_secret[32],
+ const uint8_t your_secret_key [32],
+ const uint8_t their_public_key [32]);
+
+// Conversion to EdDSA
+void crypto_x25519_to_eddsa(uint8_t eddsa[32], const uint8_t x25519[32]);
+
+// scalar "division"
+// Used for OPRF. Be aware that exponential blinding is less secure
+// than Diffie-Hellman key exchange.
+void crypto_x25519_inverse(uint8_t blind_salt [32],
+ const uint8_t private_key[32],
+ const uint8_t curve_point[32]);
+
+// "Dirty" versions of x25519_public_key().
+// Use with crypto_elligator_rev().
+// Leaks 3 bits of the private key.
+void crypto_x25519_dirty_small(uint8_t pk[32], const uint8_t sk[32]);
+void crypto_x25519_dirty_fast (uint8_t pk[32], const uint8_t sk[32]);
+
+
+// Signatures
+// ----------
+
+// EdDSA with curve25519 + BLAKE2b
+void crypto_eddsa_key_pair(uint8_t secret_key[64],
+ uint8_t public_key[32],
+ uint8_t seed[32]);
+void crypto_eddsa_sign(uint8_t signature [64],
+ const uint8_t secret_key[64],
+ const uint8_t *message, size_t message_size);
+int crypto_eddsa_check(const uint8_t signature [64],
+ const uint8_t public_key[32],
+ const uint8_t *message, size_t message_size);
+
+// Conversion to X25519
+void crypto_eddsa_to_x25519(uint8_t x25519[32], const uint8_t eddsa[32]);
+
+// EdDSA building blocks
+void crypto_eddsa_trim_scalar(uint8_t out[32], const uint8_t in[32]);
+void crypto_eddsa_reduce(uint8_t reduced[32], const uint8_t expanded[64]);
+void crypto_eddsa_mul_add(uint8_t r[32],
+ const uint8_t a[32],
+ const uint8_t b[32],
+ const uint8_t c[32]);
+void crypto_eddsa_scalarbase(uint8_t point[32], const uint8_t scalar[32]);
+int crypto_eddsa_check_equation(const uint8_t signature[64],
+ const uint8_t public_key[32],
+ const uint8_t h_ram[32]);
+
+
+// Chacha20
+// --------
+
+// Specialised hash.
+// Used to hash X25519 shared secrets.
+void crypto_chacha20_h(uint8_t out[32],
+ const uint8_t key[32],
+ const uint8_t in [16]);
+
+// Unauthenticated stream cipher.
+// Don't forget to add authentication.
+uint64_t crypto_chacha20_djb(uint8_t *cipher_text,
+ const uint8_t *plain_text,
+ size_t text_size,
+ const uint8_t key[32],
+ const uint8_t nonce[8],
+ uint64_t ctr);
+uint32_t crypto_chacha20_ietf(uint8_t *cipher_text,
+ const uint8_t *plain_text,
+ size_t text_size,
+ const uint8_t key[32],
+ const uint8_t nonce[12],
+ uint32_t ctr);
+uint64_t crypto_chacha20_x(uint8_t *cipher_text,
+ const uint8_t *plain_text,
+ size_t text_size,
+ const uint8_t key[32],
+ const uint8_t nonce[24],
+ uint64_t ctr);
+
+
+// Poly 1305
+// ---------
+
+// This is a *one time* authenticator.
+// Disclosing the mac reveals the key.
+// See crypto_lock() on how to use it properly.
+
+// Direct interface
+void crypto_poly1305(uint8_t mac[16],
+ const uint8_t *message, size_t message_size,
+ const uint8_t key[32]);
+
+// Incremental interface
+typedef struct {
+ // Do not rely on the size or contents of this type,
+ // for they may change without notice.
+ uint8_t c[16]; // chunk of the message
+ size_t c_idx; // How many bytes are there in the chunk.
+ uint32_t r [4]; // constant multiplier (from the secret key)
+ uint32_t pad[4]; // random number added at the end (from the secret key)
+ uint32_t h [5]; // accumulated hash
+} crypto_poly1305_ctx;
+
+void crypto_poly1305_init (crypto_poly1305_ctx *ctx, const uint8_t key[32]);
+void crypto_poly1305_update(crypto_poly1305_ctx *ctx,
+ const uint8_t *message, size_t message_size);
+void crypto_poly1305_final (crypto_poly1305_ctx *ctx, uint8_t mac[16]);
+
+
+// Elligator 2
+// -----------
+
+// Elligator mappings proper
+void crypto_elligator_map(uint8_t curve [32], const uint8_t hidden[32]);
+int crypto_elligator_rev(uint8_t hidden[32], const uint8_t curve [32],
+ uint8_t tweak);
+
+// Easy to use key pair generation
+void crypto_elligator_key_pair(uint8_t hidden[32], uint8_t secret_key[32],
+ uint8_t seed[32]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // MONOCYPHER_H
diff --git a/hw/application_fpga/tkey-libs/tools/spdx-ensure b/hw/application_fpga/tkey-libs/tools/spdx-ensure
new file mode 100755
index 0000000..28267f4
--- /dev/null
+++ b/hw/application_fpga/tkey-libs/tools/spdx-ensure
@@ -0,0 +1,93 @@
+#!/bin/bash
+# SPDX-FileCopyrightText: 2022 Tillitis AB
+# SPDX-License-Identifier: BSD-2-Clause
+set -eu
+
+# Check for the SPDX tag in all files in the repo. Exit with a non-zero code if
+# some is missing. The missingok arrays below contain files and directories
+# with files where the the tag is not required.
+
+cd "${0%/*}"
+cd ..
+
+tag="SPDX-License-Identifier:"
+
+missingok_dirs=(
+.github/workflows/
+LICENSES/
+)
+
+missingok_files=(
+.clang-format
+.editorconfig
+.gitignore
+LICENSE
+Makefile
+README.md
+README-DIST.txt
+RELEASE.md
+example-app/Makefile
+monocypher/LICENSE
+monocypher/README.md
+blake2s/*
+)
+
+is_missingok() {
+ item="$1"
+ # ok for empty files
+ [[ -f "$item" ]] && [[ ! -s "$item" ]] && return 0
+ for fileok in "${missingok_files[@]}"; do
+ [[ "$item" = "$fileok" ]] && return 0
+ done
+ for dirok in "${missingok_dirs[@]}"; do
+ [[ "$item" =~ ^$dirok ]] && return 0
+ done
+ return 1
+}
+
+printf "* Checking for SPDX tags in %s\n" "$PWD"
+
+mapfile -t repofiles < <(git ls-files || true)
+if [[ -z "${repofiles[*]}" ]]; then
+ printf "* No files in the repo?!\n"
+ exit 1
+fi
+
+failed=0
+
+printed=0
+for fileok in "${missingok_files[@]}"; do
+ [[ -f "$fileok" ]] && continue
+ if (( !printed )); then
+ printf "* Some files in missingok_files are themselves missing:\n"
+ printed=1
+ failed=1
+ fi
+ printf "%s\n" "$fileok"
+done
+
+printed=0
+for dirok in "${missingok_dirs[@]}"; do
+ [[ -d "$dirok" ]] && continue
+ if (( !printed )); then
+ printf "* Some dirs in missingok_dirs are themselves missing:\n"
+ printed=1
+ failed=1
+ fi
+ printf "%s\n" "$dirok"
+done
+
+printed=0
+for file in "${repofiles[@]}"; do
+ is_missingok "$file" && continue
+ if ! grep -q "$tag" "$file"; then
+ if (( !printed )); then
+ printf "* Files missing the SPDX tag:\n"
+ printed=1
+ failed=1
+ fi
+ printf "%s\n" "$file"
+ fi
+done
+
+exit "$failed"
diff --git a/hw/application_fpga/tools/README.md b/hw/application_fpga/tools/README.md
new file mode 100644
index 0000000..d419260
--- /dev/null
+++ b/hw/application_fpga/tools/README.md
@@ -0,0 +1,33 @@
+# Tools
+
+We have developed some tools necessary for the build.
+
+- `app_bin_to_spram_hex.py`: Script used to include a device app in a
+ testbench simulation.
+
+- `b2s`: Compute and print a BLAKE2s digest over a file. Used for the
+ digest of the app in app slot 0 included in the firmware.
+
+- `default_partition.bin`: Default partition table for the flash.
+
+- `load_preloaded_app.sh`: Script to load two copies of the partition
+ table to flash and a pre-loaded to app slot 0 or 1. Needs
+ `default_partition.bin`, generated with `tkeyimage` and the binary
+ of the device app to load. Call like: `./load_preloaded_app 0
+ path/to/binary`.
+
+- `makehex.py`: Used to build hex version of firmware ROM for FPGA
+ bitstream build.
+
+- `patch_uds_udi.py`: Script used to patch in the Unique Device Secret
+ in `data/uds.hex` and the Unique Device Identifier in `data/udi.hex`
+ into the bitstream without having to rebuild the entire bitstream.
+
+- `run_pnr.sh`: Script to run place and route with `nextpnr` in order
+ to find a routing seed that will meet desired timing.
+
+- `tkeyimage`: Utility to create and parse flash images with a TKey
+ filesystem or the partition table
+
+- `tpt/tpt.py`: Utility to create the Unique Device Secret (UDS) and
+ Unique Device Identity (UDI) interactively.
diff --git a/hw/application_fpga/tools/b2s/README.md b/hw/application_fpga/tools/b2s/README.md
new file mode 100644
index 0000000..2a039e0
--- /dev/null
+++ b/hw/application_fpga/tools/b2s/README.md
@@ -0,0 +1,29 @@
+# b2s
+
+The firmware included a BLAKE2s digest of the expected device app in
+the first app slot. The firmware refuses to start the app if the
+computed digest differs from the constant.
+
+To simplify computing the digest, use this tool with the `-c` flag for
+including the digest in a C program:
+
+## Building
+
+`go build`
+
+## Running
+
+```
+./b2s -m b2s -c
+// BLAKE2s digest of b2s
+uint8_t digest[32] = {
+0x17, 0x36, 0xe9, 0x4e, 0xeb, 0x1b, 0xa2, 0x30, 0x89, 0xa9, 0xaa, 0xe, 0xf2, 0x6f, 0x35, 0xb2, 0xa9, 0x89, 0xac, 0x64, 0x63, 0xde, 0x38, 0x60, 0x47, 0x40, 0x91, 0x4e, 0xd7, 0x72, 0xa0, 0x58,
+};
+```
+
+To print the digest in a more user friendly way, leave out the `-c`:
+
+```
+./b2s -m b2s
+1736e94eeb1ba23089a9aa0ef26f35b2a989ac6463de38604740914ed772a058 b2s
+```
diff --git a/hw/application_fpga/tools/b2s/b2s.go b/hw/application_fpga/tools/b2s/b2s.go
new file mode 100644
index 0000000..bfd9afe
--- /dev/null
+++ b/hw/application_fpga/tools/b2s/b2s.go
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: 2025 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+
+ "golang.org/x/crypto/blake2s"
+)
+
+func usage() {
+ fmt.Printf("Usage: %s -m filename [-c]\n", os.Args[0])
+}
+
+func printCDigest(digest [blake2s.Size]byte, fileName string) {
+ fmt.Printf("// BLAKE2s digest of %v\n", fileName)
+ fmt.Printf("const uint8_t digest[32] = {\n")
+
+ for _, n := range digest {
+ fmt.Printf("0x%02x, ", n)
+ }
+
+ fmt.Printf("\n}; \n")
+}
+
+func main() {
+ var messageFile string
+ var forC bool
+
+ flag.StringVar(&messageFile, "m", "", "Specify file containing message.")
+ flag.BoolVar(&forC, "c", false, "Print digest for inclusion in C program.")
+
+ flag.Usage = usage
+ flag.Parse()
+
+ if messageFile == "" {
+ usage()
+ os.Exit(0)
+ }
+
+ message, err := os.ReadFile(messageFile)
+ if err != nil {
+ fmt.Printf("%v\n", err)
+ os.Exit(1)
+ }
+
+ digest := blake2s.Sum256(message)
+
+ if forC {
+ printCDigest(digest, messageFile)
+ } else {
+ fmt.Printf("%x %s\n", digest, messageFile)
+ }
+
+ os.Exit(0)
+}
diff --git a/hw/application_fpga/tools/b2s/go.mod b/hw/application_fpga/tools/b2s/go.mod
new file mode 100644
index 0000000..5c98d9b
--- /dev/null
+++ b/hw/application_fpga/tools/b2s/go.mod
@@ -0,0 +1,9 @@
+module b2s
+
+go 1.23.0
+
+toolchain go1.23.7
+
+require golang.org/x/crypto v0.36.0
+
+require golang.org/x/sys v0.31.0 // indirect
diff --git a/hw/application_fpga/tools/b2s/go.sum b/hw/application_fpga/tools/b2s/go.sum
new file mode 100644
index 0000000..927b255
--- /dev/null
+++ b/hw/application_fpga/tools/b2s/go.sum
@@ -0,0 +1,4 @@
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
diff --git a/hw/application_fpga/tools/default_partition.bin b/hw/application_fpga/tools/default_partition.bin
new file mode 100644
index 0000000..38fa3af
Binary files /dev/null and b/hw/application_fpga/tools/default_partition.bin differ
diff --git a/hw/application_fpga/tools/load_preloaded_app.sh b/hw/application_fpga/tools/load_preloaded_app.sh
new file mode 100755
index 0000000..23186a5
--- /dev/null
+++ b/hw/application_fpga/tools/load_preloaded_app.sh
@@ -0,0 +1,36 @@
+#!/bin/bash -e
+
+# SPDX-FileCopyrightText: 2025 Tillitis AB
+# SPDX-License-Identifier: GPL-2.0-only
+if [ $# != 2 ]
+then
+ echo "Usage: $0 slot_num app_file"
+ echo ""
+ echo "Where slot_num is 0 or 1."
+ exit
+fi
+
+SLOT_NUM="$1"
+APP="$2"
+
+if [ "$SLOT_NUM" = "0" ]; then
+ START_ADDRESS=0x30000
+elif [ "$SLOT_NUM" = "1" ]; then
+ START_ADDRESS=0x50000
+else
+ echo "Invalid slot_num"
+ exit 1
+fi
+
+echo "WARNING: Will install default partition table."
+read -p "Press CTRL-C to abort. Press key to continue." -n1 -s
+
+# Write both copies of the partition table
+tillitis-iceprog -o 128k default_partition.bin
+tillitis-iceprog -o 0xf0000 default_partition.bin
+
+# Erase existing pre loaded app
+tillitis-iceprog -o "$START_ADDRESS" -e 128k
+
+# Write pre loaded app
+tillitis-iceprog -o "$START_ADDRESS" "$APP"
diff --git a/hw/application_fpga/tools/makehex/makehex.py b/hw/application_fpga/tools/makehex.py
similarity index 86%
rename from hw/application_fpga/tools/makehex/makehex.py
rename to hw/application_fpga/tools/makehex.py
index 7f6cb71..a8b4d50 100755
--- a/hw/application_fpga/tools/makehex/makehex.py
+++ b/hw/application_fpga/tools/makehex.py
@@ -1,5 +1,8 @@
#!/usr/bin/env python3
#
+# SPDX-FileCopyrightText: Claire Xenia Wolf
+# SPDX-License-Identifier: CC0-1.0
+
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
diff --git a/hw/application_fpga/tools/reset-tk1 b/hw/application_fpga/tools/reset-tk1
deleted file mode 100755
index 839290f..0000000
--- a/hw/application_fpga/tools/reset-tk1
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/sh
-set -eu
-
-cd "${0%/*}"
-cd ../../production_test
-
-if [ ! -e venv ]; then
- python3 -m venv venv
- . ./venv/bin/activate
- pip3 install -r requirements.txt
-else
- . ./venv/bin/activate
-fi
-
-./reset.py
diff --git a/hw/application_fpga/tools/run_pnr.sh b/hw/application_fpga/tools/run_pnr.sh
index 68f9e70..21a5cb1 100755
--- a/hw/application_fpga/tools/run_pnr.sh
+++ b/hw/application_fpga/tools/run_pnr.sh
@@ -1,5 +1,8 @@
#!/bin/bash
+# SPDX-FileCopyrightText: 2025 Tillitis AB
+# SPDX-License-Identifier: GPL-2.0-only
+
help() {
echo "Usage: $(basename $0) [OPTION]"
echo "Run multiple place and route threads with nextpnr-ice40"
diff --git a/hw/application_fpga/tools/tkeyimage/README.md b/hw/application_fpga/tools/tkeyimage/README.md
new file mode 100644
index 0000000..3c4e417
--- /dev/null
+++ b/hw/application_fpga/tools/tkeyimage/README.md
@@ -0,0 +1,114 @@
+# tkeyimage
+
+A tool to parse or generate partition table or entire filesystems for
+the TKey.
+
+- Parse with `-i file.bin` for "input".
+- Generate with `-o file.bin` for "output".
+
+Add `-f` to parse or generate an entire flash image file.
+
+## Usage
+
+### Inspect a partition table dump
+
+Dump the entire data from flash, then inspect:
+
+```
+$ tillitis-iceprog -R 1M dump.bin
+$ ./tkeyimage -i dump.bin -f
+INFO: main.Flash struct is 1048576 byte long
+Partition Table Storage
+ Partition Table
+ Header
+ Version : 1
+ Preloaded App 0
+ Size : 23796
+ Digest : 00000000000000000000000000000000
+ 00000000000000000000000000000000
+ Signature : 00000000000000000000000000000000
+ 00000000000000000000000000000000
+ 00000000000000000000000000000000
+ 00000000000000000000000000000000
+ Preloaded App 1
+ Size : 264
+ Digest : 96bb4c90603dbbbe09b9a1d7259b5e9e
+ 61bedd89a897105c30c9d4bf66a98d97
+ Signature : ccb60c034e559b8c695f25233b80c245
+ e099316324e1a4e68a14c82d834eee58
+ 5700cd5c29b64e74159a4dbf3fed030a
+ 140e981fb3b6972c125afb4d4497da0a
+ Digest : 4628f142764f724e45e05b20363960967705cfcee8285b2d9d207e04a46e275e
+```
+
+Read only the first copy of the partition table from flash to file,
+then inspect:
+
+```
+$ tillitis-iceprog -o 128k -r partition.bin
+$ ./tkeyimage -i partition.bin
+INFO: main.PartTableStorage struct is 365 byte long
+Partition Table Storage
+ Partition Table
+ Header
+ Version : 1
+ Preloaded App 0
+ Size : 23796
+ Digest : 00000000000000000000000000000000
+ 00000000000000000000000000000000
+ Signature : 00000000000000000000000000000000
+ 00000000000000000000000000000000
+ 00000000000000000000000000000000
+ 00000000000000000000000000000000
+ Preloaded App 1
+ Size : 0
+ Digest : 00000000000000000000000000000000
+ 00000000000000000000000000000000
+ Signature : 00000000000000000000000000000000
+ 00000000000000000000000000000000
+ 00000000000000000000000000000000
+ 00000000000000000000000000000000
+ Digest : 40c6dbb4c8fda561369ec54a907452ae352ccbd736ba7824c4e173fd438b7d7a
+```
+
+### Generate a partition table
+
+If you want to generate just a partition table:
+
+```
+$ ./tkeyimage -o partition.bin
+```
+
+With an app in slot 0, filling in the size in the partition table:
+
+```
+$ ./tkeyimage -o partition.bin -app0 ../../fw/testloadapp/testloadapp.bin
+```
+
+### Generate flash image
+
+The program can also generate an entire flash image for use either
+with real hardware or qemu.
+
+Generate like this:
+
+```
+$ ./tkeyimage -o flash.bin -f -app0 ../../fw/testloadapp/testloadapp.bin
+```
+
+Using `-app0` is mandatory because TKey firmware won't start without
+an app in slot 0.
+
+The qemu args to use to run with `flash.bin` as the flash are:
+
+```
+-drive file=flash.bin,if=mtd,format=raw,index=0
+```
+
+A complete command example:
+
+```
+$ qemu-system-riscv32 -nographic -M tk1-castor,fifo=chrid \
+-bios qemu_firmware.elf -chardev pty,id=chrid -s -d guest_errors \
+-drive file=flash.bin,if=mtd,format=raw,index=0
+```
diff --git a/hw/application_fpga/tools/tkeyimage/go.mod b/hw/application_fpga/tools/tkeyimage/go.mod
new file mode 100644
index 0000000..6ede5b7
--- /dev/null
+++ b/hw/application_fpga/tools/tkeyimage/go.mod
@@ -0,0 +1,7 @@
+module tkeyimage
+
+go 1.23.0
+
+require golang.org/x/crypto v0.36.0
+
+require golang.org/x/sys v0.31.0 // indirect
diff --git a/hw/application_fpga/tools/tkeyimage/go.sum b/hw/application_fpga/tools/tkeyimage/go.sum
new file mode 100644
index 0000000..927b255
--- /dev/null
+++ b/hw/application_fpga/tools/tkeyimage/go.sum
@@ -0,0 +1,4 @@
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
diff --git a/hw/application_fpga/tools/tkeyimage/tkeyimage.go b/hw/application_fpga/tools/tkeyimage/tkeyimage.go
new file mode 100644
index 0000000..d411c5d
--- /dev/null
+++ b/hw/application_fpga/tools/tkeyimage/tkeyimage.go
@@ -0,0 +1,266 @@
+// SPDX-FileCopyrightText: 2025 Tillitis AB
+// SPDX-License-Identifier: BSD-2-Clause
+
+package main
+
+import (
+ "encoding/binary"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+
+ "golang.org/x/crypto/blake2s"
+)
+
+// Maximum allowed size in bytes of a preloaded app.
+const MaxAppSize = (128 * 1024)
+
+// Size in bytes of the partition table binary. Find out with -o and
+// check the file size.
+const PartitionSize = 365
+
+type PreLoadedAppData struct {
+ Size uint32
+ Digest [32]uint8
+ Signature [64]uint8
+}
+
+type Auth struct {
+ Nonce [16]uint8
+ AuthDigest [16]uint8
+}
+
+type AppStorage struct {
+ Status uint8
+ Auth Auth
+}
+
+type PartTable struct {
+ Version uint8
+ PreLoadedAppData [2]PreLoadedAppData
+ AppStorage [4]AppStorage
+}
+
+type PartTableStorage struct {
+ PartTable PartTable
+ Checksum [32]byte
+}
+
+func (p *PartTableStorage) GenChecksum() {
+ buf := make([]byte, 4096)
+ len, err := binary.Encode(buf, binary.LittleEndian, p.PartTable)
+ if err != nil {
+ panic(err)
+ }
+
+ p.Checksum = blake2s.Sum256(buf[:len])
+}
+
+// Name Size Start addr
+// ---- ---- ----
+// Bitstream 128KiB 0x00
+// ---- ---- ----
+// Partition 64KiB 0x20000
+// ---- ---- ----
+// Pre load 0 128KiB 0x30000
+// Pre load 1 128KiB 0x50000
+// ---- ---- ----
+// storage 0 128KiB 0x70000
+// storage 1 128KiB 0x90000
+// storage 2 128KiB 0xB0000
+// storage 3 128KiB 0xD0000
+// ---- ---- ----
+// Partition2 64KiB 0xf0000
+type Flash struct {
+ Bitstream [0x20000]uint8 // Reserved for FPGA bitstream
+ PartitionTable PartTableStorage // 0x20000 partition table
+ PartitionTablePadding [64*1024 - PartitionSize]uint8 // ~64k padding
+ PreLoadedApp0 [0x20000]uint8 // 0x30000
+ PreLoadedApp1 [0x20000]uint8 // 0x50000
+ AppStorage [4][0x20000]uint8 // 0x70000, 4 * 128 storage for apps
+ PartitionTable2 PartTableStorage // 0xf0000 second copy of table
+ PartitionTablePadding2 [64*1024 - PartitionSize]uint8 // ~64k padding
+}
+
+func readStruct[T PartTableStorage | Flash](filename string) T {
+ var s T
+
+ file, err := os.Open(filename)
+ if err != nil {
+ panic(err)
+ }
+
+ if err := binary.Read(file, binary.LittleEndian, &s); err != nil {
+ panic(err)
+ }
+
+ sLen, err := file.Seek(0, io.SeekCurrent)
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Fprintf(os.Stderr, "INFO: %T struct is %d byte long\n", *new(T), sLen)
+
+ return s
+}
+
+func printPartTableStorageCondensed(storage PartTableStorage) {
+ fmt.Printf("Partition Table Storage\n")
+ fmt.Printf(" Partition Table\n")
+ fmt.Printf(" Header\n")
+ fmt.Printf(" Version : %d\n", storage.PartTable.Version)
+
+ for i, appData := range storage.PartTable.PreLoadedAppData {
+ fmt.Printf(" Preloaded App %d\n", i)
+ fmt.Printf(" Size : %d\n", appData.Size)
+ fmt.Printf(" Digest : %x\n", appData.Digest[:16])
+ fmt.Printf(" %x\n", appData.Digest[16:])
+ fmt.Printf(" Signature : %x\n", appData.Signature[:16])
+ fmt.Printf(" %x\n", appData.Signature[16:32])
+ fmt.Printf(" %x\n", appData.Signature[32:48])
+ fmt.Printf(" %x\n", appData.Signature[48:])
+ }
+ fmt.Printf(" Digest : %x\n", storage.Checksum)
+}
+
+func genPartitionFile(outputFilename string, app0Filename string) {
+ partition := newPartTable(app0Filename)
+ partition.GenChecksum()
+
+ storageFile, err := os.Create(outputFilename)
+ if err != nil {
+ panic(err)
+ }
+
+ if err := binary.Write(storageFile, binary.LittleEndian, partition); err != nil {
+ panic(err)
+ }
+}
+
+// newPartTable generates a new partition table suitable for storage.
+// If given app0Filename it also fills in the size of the file.
+//
+// When you're done with filling in the struct, remember to call
+// GenChecksum().
+//
+// It returns the partition table.
+func newPartTable(app0Filename string) PartTableStorage {
+ storage := PartTableStorage{
+ PartTable: PartTable{
+ Version: 1,
+ PreLoadedAppData: [2]PreLoadedAppData{},
+ AppStorage: [4]AppStorage{},
+ },
+ }
+
+ if app0Filename != "" {
+ stat, err := os.Stat(app0Filename)
+ if err != nil {
+ panic(err)
+ }
+ storage.PartTable.PreLoadedAppData[0].Size = uint32(stat.Size())
+ }
+
+ return storage
+}
+
+func memset(s []byte, c byte) {
+ for i := range s {
+ s[i] = c
+ }
+}
+
+func genFlashFile(outputFilename string, app0Filename string) {
+ var flash Flash
+
+ // Set all bits in flash to erased.
+ memset(flash.Bitstream[:], 0xff)
+ // partition0 will be filled in below
+ memset(flash.PartitionTablePadding[:], 0xff)
+ memset(flash.PreLoadedApp0[:], 0xff)
+ memset(flash.PreLoadedApp1[:], 0xff)
+ memset(flash.AppStorage[0][:], 0xff)
+ memset(flash.AppStorage[1][:], 0xff)
+ memset(flash.AppStorage[2][:], 0xff)
+ memset(flash.AppStorage[3][:], 0xff)
+ // partition1 will be filled in below
+ memset(flash.PartitionTablePadding2[:], 0xff)
+
+ partition := newPartTable(app0Filename)
+ partition.GenChecksum()
+
+ flash.PartitionTable = partition
+ flash.PartitionTable2 = partition
+
+ // Read the entire file.
+ appBuf, err := os.ReadFile(app0Filename)
+ if err != nil {
+ panic(err)
+ }
+
+ if len(appBuf) > MaxAppSize {
+ fmt.Printf("max app size is 128 k")
+ os.Exit(1)
+ }
+
+ copy(flash.PreLoadedApp0[:], appBuf)
+
+ storageFile, err := os.Create(outputFilename)
+ if err != nil {
+ panic(err)
+ }
+
+ if err := binary.Write(storageFile, binary.LittleEndian, flash); err != nil {
+ panic(err)
+ }
+}
+
+func main() {
+ var input string
+ var output string
+ var app0 string
+ var flash bool
+
+ flag.StringVar(&input, "i", "", "Input binary file. Cannot be used with -o.")
+ flag.StringVar(&output, "o", "", "Output binary file. Cannot be used with -i. If used with -f, -app0 must also be specified.")
+ flag.StringVar(&app0, "app0", "", "Binary in pre loaded app slot 0. Can be used with -o.")
+ flag.BoolVar(&flash, "f", false, "Treat file as a dump of the entire flash memory.")
+ flag.Parse()
+
+ if len(flag.Args()) > 0 {
+ fmt.Printf("superfluous args\n")
+ flag.Usage()
+ os.Exit(1)
+ }
+
+ if (input == "" && output == "") || (input != "" && output != "") {
+ flag.Usage()
+ os.Exit(1)
+ }
+
+ if input != "" {
+ var storage PartTableStorage
+
+ if flash {
+ storage = readStruct[Flash](input).PartitionTable
+ } else {
+ storage = readStruct[PartTableStorage](input)
+ }
+ printPartTableStorageCondensed(storage)
+ os.Exit(0)
+ }
+
+ if output != "" {
+ if flash {
+ if app0 == "" {
+ fmt.Printf("need -app0 path/to/app\n")
+ os.Exit(1)
+ }
+
+ genFlashFile(output, app0)
+ } else {
+ genPartitionFile(output, app0)
+ }
+ }
+}
diff --git a/hw/application_fpga/tools/tpt/__init__.py b/hw/application_fpga/tools/tpt/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/hw/usb_interface/ch552_fw/LICENSE b/hw/usb_interface/ch552_fw/LICENSE
deleted file mode 100644
index 87b95b2..0000000
--- a/hw/usb_interface/ch552_fw/LICENSE
+++ /dev/null
@@ -1,19 +0,0 @@
-Copyright 2017 WCH
-Device ID modifications copyright 2023 Tillitis AB
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
-the Software, and to permit persons to whom the Software is furnished to do so,
-subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
-FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
-COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
-IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/hw/usb_interface/ch552_fw/LICENSES/GPL-2.0-only.txt b/hw/usb_interface/ch552_fw/LICENSES/GPL-2.0-only.txt
new file mode 100644
index 0000000..17cb286
--- /dev/null
+++ b/hw/usb_interface/ch552_fw/LICENSES/GPL-2.0-only.txt
@@ -0,0 +1,117 @@
+GNU GENERAL PUBLIC LICENSE
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
+
+Preamble
+
+The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too.
+
+When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
+
+We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.
+
+Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and modification follow.
+
+TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.
+
+1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.
+
+2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.
+
+3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.
+
+If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.
+
+4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.
+
+5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.
+
+6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.
+
+7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.
+
+This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.
+
+8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.
+
+9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.
+
+10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.
+
+NO WARRANTY
+
+11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+END OF TERMS AND CONDITIONS
+
+How to Apply These Terms to Your New Programs
+
+If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
+
+ one line to give the program's name and an idea of what it does. Copyright (C) yyyy name of author
+
+ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+signature of Ty Coon, 1 April 1989 Ty Coon, President of Vice
diff --git a/hw/usb_interface/ch552_fw/LICENSES/MIT.txt b/hw/usb_interface/ch552_fw/LICENSES/MIT.txt
new file mode 100644
index 0000000..fc2cf8e
--- /dev/null
+++ b/hw/usb_interface/ch552_fw/LICENSES/MIT.txt
@@ -0,0 +1,18 @@
+MIT License
+
+Copyright (c)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+associated documentation files (the "Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial
+portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
+EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/hw/usb_interface/ch552_fw/Makefile b/hw/usb_interface/ch552_fw/Makefile
index 71d8183..6cf1932 100644
--- a/hw/usb_interface/ch552_fw/Makefile
+++ b/hw/usb_interface/ch552_fw/Makefile
@@ -1,37 +1,87 @@
-TARGET = usb_device_cdc
+#######################################################
-CH554_SDCC=~/ch554_sdcc/
-CHPROG=chprog
+# Toolchain
+CC = sdcc
+OBJCOPY = objcopy
+PACK_HEX = packihx
+CHPROG = chprog
-# Adjust the XRAM location and size to leave space for the USB DMA buffers
-# Buffer layout in XRAM:
-# 0x0000 Ep0Buffer[8]
-# 0x0040 Ep1Buffer[8]
-# 0x0080 EP2Buffer[2*64]
-#
-# This takes a total of 256bytes, so there are 768 bytes left.
-#XRAM_SIZE = 0x0300
-#XRAM_LOC = 0x0100
+#######################################################
-XRAM_SIZE = 0x0400
-XRAM_LOC = 0x0000
+TARGET = usb_device
-FREQ_SYS = 16000000
+OUT_DIR = _out
+
+XRAM_SIZE = 0x0400 # 1 KB on-chip xRAM
+XRAM_LOC = 0x0000 # xRAM area starts at address 0 in the External Data Address Space
+CODE_SIZE = 0x3800 # 14 KB program storage area
+FREQ_SYS = 16000000 # 16 MHz system clock
+
+EXTRA_FLAGS = -DBUILD_CODE
+
+CFLAGS = \
+ -V \
+ -mmcs51 \
+ --model-small \
+ --xram-size $(XRAM_SIZE) \
+ --xram-loc $(XRAM_LOC) \
+ --code-size $(CODE_SIZE) \
+ -Iinc \
+ -DFREQ_SYS=$(FREQ_SYS) \
+ $(EXTRA_FLAGS)
+
+LFLAGS = \
+ $(CFLAGS)
+
+C_FILES = \
+ src/debug.c \
+ src/flash.c \
+ src/gpio.c \
+ src/lib.c \
+ src/main.c \
+ src/print.c
+
+# Create a .rel file for each .c file in $(OUT_DIR)/
+RELS = $(patsubst %.c,$(OUT_DIR)/%.rel,$(C_FILES))
+
+OUT_SUBDIRS = $(sort $(dir $(RELS)))
+
+# Ensure out directory exists
+$(OUT_DIR):
+ mkdir -p $(OUT_SUBDIRS)
+
+$(OUT_DIR)/%.rel: %.c | $(OUT_DIR)
+ $(CC) -c $(CFLAGS) $< -o $@
usb_strings.h: encode_usb_strings.py
./encode_usb_strings.py
-C_FILES = \
- main.c \
- include/debug.c \
- include/print.c
+# Compile the final ihx file
+$(TARGET).ihx: $(RELS)
+ $(CC) $(RELS) $(LFLAGS) -o $(TARGET).ihx
-pre-flash:
+$(TARGET).hex: $(TARGET).ihx
+ $(PACK_HEX) $(TARGET).ihx > $(TARGET).hex
+$(TARGET).bin: $(TARGET).ihx
+ $(OBJCOPY) -I ihex -O binary $(TARGET).ihx $(TARGET).bin
-flash_patched: usb_device_cdc.bin
- ./inject_serial_number.py -i usb_device_cdc.bin -o patched.bin
- ${CHPROG} patched.bin
+flash: $(TARGET).bin
+ $(CHPROG) $(TARGET).bin
+
+flash_patched: $(TARGET).bin
+ ./inject_serial_number.py -i $(TARGET).bin -o patched.bin
+ $(CHPROG) patched.bin
rm patched.bin
-include Makefile.include
+.DEFAULT_GOAL := all
+all: $(TARGET).bin $(TARGET).hex
+
+clean:
+ rm -rf $(OUT_DIR) \
+ $(TARGET).lk \
+ $(TARGET).map \
+ $(TARGET).mem \
+ $(TARGET).ihx \
+ $(TARGET).hex \
+ $(TARGET).bin
diff --git a/hw/usb_interface/ch552_fw/Makefile.include b/hw/usb_interface/ch552_fw/Makefile.include
deleted file mode 100644
index d4f7a8c..0000000
--- a/hw/usb_interface/ch552_fw/Makefile.include
+++ /dev/null
@@ -1,70 +0,0 @@
-#######################################################
-
-# toolchain
-CC = sdcc
-OBJCOPY = objcopy
-PACK_HEX = packihx
-WCHISP ?= wchisptool -g -f
-
-#######################################################
-
-EXTRA_FLAGS = -D BUILD_CODE
-
-FREQ_SYS ?= 24000000
-
-XRAM_SIZE ?= 0x0400
-
-XRAM_LOC ?= 0x0000
-
-CODE_SIZE ?= 0x3800
-
-ROOT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
-
-CFLAGS := -V -mmcs51 --model-small \
- --xram-size $(XRAM_SIZE) --xram-loc $(XRAM_LOC) \
- --code-size $(CODE_SIZE) \
- -I$(ROOT_DIR)include -DFREQ_SYS=$(FREQ_SYS) \
- $(EXTRA_FLAGS)
-
-LFLAGS := $(CFLAGS)
-
-RELS := $(C_FILES:.c=.rel)
-
-print-% : ; @echo $* = $($*)
-
-%.rel : %.c
- $(CC) -c $(CFLAGS) $<
-
-# Note: SDCC will dump all of the temporary files into this one, so strip the paths from RELS
-# For now, get around this by stripping the paths off of the RELS list.
-
-$(TARGET).ihx: $(RELS)
- $(CC) $(notdir $(RELS)) $(LFLAGS) -o $(TARGET).ihx
-
-$(TARGET).hex: $(TARGET).ihx
- $(PACK_HEX) $(TARGET).ihx > $(TARGET).hex
-
-$(TARGET).bin: $(TARGET).ihx
- $(OBJCOPY) -I ihex -O binary $(TARGET).ihx $(TARGET).bin
-
-flash: $(TARGET).bin pre-flash
- $(WCHISP) $(TARGET).bin
-
-.DEFAULT_GOAL := all
-all: $(TARGET).bin $(TARGET).hex
-
-clean:
- rm -f \
- $(notdir $(RELS:.rel=.asm)) \
- $(notdir $(RELS:.rel=.lst)) \
- $(notdir $(RELS:.rel=.mem)) \
- $(notdir $(RELS:.rel=.rel)) \
- $(notdir $(RELS:.rel=.rst)) \
- $(notdir $(RELS:.rel=.sym)) \
- $(notdir $(RELS:.rel=.adb)) \
- $(TARGET).lk \
- $(TARGET).map \
- $(TARGET).mem \
- $(TARGET).ihx \
- $(TARGET).hex \
- $(TARGET).bin
diff --git a/hw/usb_interface/ch552_fw/README.md b/hw/usb_interface/ch552_fw/README.md
index 769657e..cf659a6 100644
--- a/hw/usb_interface/ch552_fw/README.md
+++ b/hw/usb_interface/ch552_fw/README.md
@@ -27,4 +27,38 @@ Flash the firmware to a device:
## Re-programming the firmware
-By design, once the USB to serial firmware is loaded onto the chip, there isn't an intended way to reflash it using only software. However, if 3.3V is applied to the D+ line through a 10K resistor during power-up, then the CH552 will enter bootloader mode, and a new firmware can be programmed onto the chip. Note that the CH552 flash is only guaranteed for a few hundred flash cycles.
+By design, once the USB to serial firmware is loaded onto the chip,
+there isn't an intended way to reflash it using only software.
+However, if 3.3V is applied to the D+ line through a 10K resistor
+during power-up, then the CH552 will enter bootloader mode, and a new
+firmware can be programmed onto the chip.
+
+The Blinkinlabs CH55x Reset Controller can help you do this:
+
+https://shop-nl.blinkinlabs.com/products/ch55x-reset-controller
+
+https://github.com/Blinkinlabs/ch55x_programmer
+
+Note that the CH552 flash is only guaranteed for a few hundred flash
+cycles.
+
+## License
+
+Originally based on reference firmware for the CH552 by WCH released
+under the MIT license:
+
+https://www.wch-ic.com/
+
+The oldest files Copyright 1999.
+
+Much changed and added to by Tillitis.
+
+Check licenses using the reuse tool:
+
+https://github.com/fsfe/reuse-tool
+
+Note that so far you need to specify this directory as root, as in:
+
+```
+$ reuse --root . lint
+```
diff --git a/hw/usb_interface/ch552_fw/REUSE.toml b/hw/usb_interface/ch552_fw/REUSE.toml
new file mode 100644
index 0000000..bb84172
--- /dev/null
+++ b/hw/usb_interface/ch552_fw/REUSE.toml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: 2025 Tillitis AB
+# SPDX-License-Identifier: BSD-2-Clause
+version = 1
+
+[[annotations]]
+path = [
+ ".gitignore",
+ "Makefile",
+ "README.md",
+]
+SPDX-FileCopyrightText = "2022 Tillitis AB "
+SPDX-License-Identifier = "GPL-2.0-only"
diff --git a/hw/usb_interface/ch552_fw/baud rate calculator.ods b/hw/usb_interface/ch552_fw/baud rate calculator.ods
deleted file mode 100644
index e3ec753..0000000
Binary files a/hw/usb_interface/ch552_fw/baud rate calculator.ods and /dev/null differ
diff --git a/hw/usb_interface/ch552_fw/encode_usb_strings.py b/hw/usb_interface/ch552_fw/encode_usb_strings.py
index 151d2f6..cc263e7 100755
--- a/hw/usb_interface/ch552_fw/encode_usb_strings.py
+++ b/hw/usb_interface/ch552_fw/encode_usb_strings.py
@@ -1,4 +1,7 @@
#!/usr/bin/env python3
+# SPDX-FileCopyrightText: 2021 Mullvad VPN AB
+# SPDX-FileCopyrightText: 2022 Tillitis AB
+# SPDX-License-Identifier: GPL-2.0-only
def descriptor_to_string(descriptor):
""" Convert a bytes object containing a USB string descriptor into a python string"""
@@ -20,37 +23,55 @@ def descriptor_to_string(descriptor):
def string_to_descriptor(string):
""" Convert a python string into a bytes object containing a USB string descriptor"""
descriptor = bytearray()
- descriptor.append(0x00) # placeholder for length
- descriptor.append(0x03)
+ descriptor.append(0x00) # Placeholder for length
+ descriptor.append(0x03) # Descriptor type (String)
descriptor.extend(string.encode('utf-16')[2:]) # crop the BOM
- descriptor[0] = len(descriptor)
+ descriptor[0] = len(descriptor) # Set length of this descriptor (in bytes)
return bytes(descriptor)
+def format_descriptor(name, value):
+ descriptor = string_to_descriptor(value)
+ formatted = [
+ 'unsigned char FLASH {}[] = {{ // "{}"'.format(name, value), # Add string as a comment
+ ' {}, // Length of this descriptor (in bytes)'.format(descriptor[0]),
+ ' 0x03, // Descriptor type (String)'
+ ]
+
+ formatted.extend(
+ [
+ ' ' + ', '.join(
+ ["'{}', 0".format(chr(b)) if b != 0x00 else "0x00" for b in descriptor[2 + i:2 + i + 8:2]]
+ ) + ','
+ for i in range(0, len(descriptor[2:]), 8) # 8 bytes = 4 characters
+ ]
+ )
+ formatted.append('};\n')
+ return '\n'.join(formatted)
if __name__ == "__main__":
- manufacturer = 'Tillitis'
- product = 'MTA1-USB-V1'
- serial = "68de5d27-e223-4874-bc76-a54d6e84068f"
+ strings = {
+ "ProdDesc": "Tillitis TKEY-USB-V2",
+ "ManufDesc": "Tillitis",
+ "SerialDesc": "68de5d27-e223-4874-bc76-a54d6e84068f",
+ "CdcCtrlInterfaceDesc": "CDC-Ctrl",
+ "CdcDataInterfaceDesc": "CDC-Data",
+ "FidoInterfaceDesc": "FIDO",
+ "CcidInterfaceDesc": "CCID",
+ "DebugInterfaceDesc": "DEBUG"
+ }
+
+ with open('inc/usb_strings.h', 'w') as f:
+ f.write('// SPDX-FileCopyrightText: 2024 Tillitis AB \n')
+ f.write('// SPDX-License-Identifier: MIT\n')
+ f.write('\n')
+ f.write('#ifndef __USB_STRINGS_H__\n')
+ f.write('#define __USB_STRINGS_H__\n')
+ f.write('\n')
+ f.write('#include "mem.h"\n')
+ f.write('\n')
+
+ for name, value in strings.items():
+ f.write(format_descriptor(name, value) + '\n')
- with open('usb_strings.h', 'w') as f:
- f.write('#ifndef USB_STRINGS\n')
- f.write('#define USB_STRINGS\n')
-
- f.write('unsigned char __code ProdDesc[]={{ // "{}"\n'.format(product))
- f.write(' ')
- f.write(', '.join(['0x{:02x}'.format(i) for i in string_to_descriptor(product)]))
- f.write('\n};\n\n')
-
- f.write('unsigned char __code ManufDesc[]={{ // "{}"\n'.format(manufacturer))
- f.write(' ')
- f.write(', '.join(['0x{:02x}'.format(i) for i in string_to_descriptor(manufacturer)]))
- f.write('\n};\n\n')
-
- f.write('unsigned char __code SerialDesc[]={{ // "{}"\n'.format(serial))
- f.write(' ')
- f.write(', '.join(['0x{:02x}'.format(i) for i in string_to_descriptor(serial)]))
- f.write('\n};\n\n')
-
-
f.write('#endif\n')
diff --git a/hw/usb_interface/ch552_fw/include/ch554.h b/hw/usb_interface/ch552_fw/inc/ch554.h
similarity index 99%
rename from hw/usb_interface/ch552_fw/include/ch554.h
rename to hw/usb_interface/ch552_fw/inc/ch554.h
index 33ebc3d..99af2ac 100644
--- a/hw/usb_interface/ch552_fw/include/ch554.h
+++ b/hw/usb_interface/ch552_fw/inc/ch554.h
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 1999 WCH
+// SPDX-FileCopyrightText: 2022 Tillitis AB
+// SPDX-License-Identifier: MIT
+
/*--------------------------------------------------------------------------
CH554.H
Header file for CH554 microcontrollers.
diff --git a/hw/usb_interface/ch552_fw/include/ch554_usb.h b/hw/usb_interface/ch552_fw/inc/ch554_usb.h
similarity index 96%
rename from hw/usb_interface/ch552_fw/include/ch554_usb.h
rename to hw/usb_interface/ch552_fw/inc/ch554_usb.h
index b9ca5f2..2277adc 100644
--- a/hw/usb_interface/ch552_fw/include/ch554_usb.h
+++ b/hw/usb_interface/ch552_fw/inc/ch554_usb.h
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 1999 WCH
+// SPDX-FileCopyrightText: 2022 Tillitis AB
+// SPDX-License-Identifier: MIT
+
/*--------------------------------------------------------------------------
CH554.H
Header file for CH554 microcontrollers.
@@ -90,6 +94,13 @@ Header file for CH554 microcontrollers.
#define USB_CDC_REQ_TYPE_SET_CONTROL_LINE_STATE 0x22
#endif
+/* USB CCID (Smart Card) class request code */
+#ifndef USB_CCID_REQ_TYPE
+#define USB_CCID_REQ_TYPE_ABORT 0x01
+#define USB_CCID_REQ_TYPE_GET_CLOCK_FREQUENCIES 0x02
+#define USB_CCID_REQ_TYPE_GET_DATA_RATES 0x03
+#endif
+
/* USB request type for hub class request */
#ifndef HUB_GET_HUB_DESCRIPTOR
#define HUB_CLEAR_HUB_FEATURE 0x20
@@ -195,8 +206,9 @@ Header file for CH554 microcontrollers.
#define USB_IDX_SERIAL_STR 0x03
#define USB_IDX_INTERFACE_CDC_CTRL_STR 0x04
#define USB_IDX_INTERFACE_CDC_DATA_STR 0x05
-#define USB_IDX_INTERFACE_FIDO_HID_STR 0x06
-#define USB_IDX_INTERFACE_TKEY_CTRL_STR 0x07
+#define USB_IDX_INTERFACE_FIDO_STR 0x06
+#define USB_IDX_INTERFACE_CCID_STR 0x07
+#define USB_IDX_INTERFACE_DEBUG_STR 0x08
#endif
#ifndef USB_DEVICE_ADDR
diff --git a/hw/usb_interface/ch552_fw/inc/config.h b/hw/usb_interface/ch552_fw/inc/config.h
new file mode 100644
index 0000000..610a474
--- /dev/null
+++ b/hw/usb_interface/ch552_fw/inc/config.h
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: 2025 Tillitis AB
+// SPDX-License-Identifier: MIT
+
+#ifndef __CONFIG_H__
+#define __CONFIG_H__
+
+//#define USE_DEBUG_PRINT /* Enable to print debug messages */
+//#define DEBUG_PRINT_SW /* Enable to print debug messages via FPGA */
+//#define DEBUG_PRINT_HW /* Enable to print debug messages via UART0 */
+
+#define USE_NUM_U8
+//#define USE_NUM_U32
+
+//#define DEBUG_SETUP /* Enable to debug USB setup flow (printStrSetup,printNumU8HexSetup) */
+
+#define USE_NUM_U8HEX
+#define USE_NUM_U16HEX
+//#define USE_NUM_U32HEX
+//#define USE_NEGATIVE_NUMS
+
+#endif
diff --git a/hw/usb_interface/ch552_fw/include/debug.h b/hw/usb_interface/ch552_fw/inc/debug.h
similarity index 94%
rename from hw/usb_interface/ch552_fw/include/debug.h
rename to hw/usb_interface/ch552_fw/inc/debug.h
index ee99780..93d13fb 100644
--- a/hw/usb_interface/ch552_fw/include/debug.h
+++ b/hw/usb_interface/ch552_fw/inc/debug.h
@@ -1,4 +1,8 @@
-#ifndef __DEBUG_H__
+// SPDX-FileCopyrightText: 2017 WCH
+// SPDX-FileCopyrightText: 2025 Tillitis AB
+// SPDX-License-Identifier: MIT
+
+#ifndef __DEBUG_H__
#define __DEBUG_H__
/* Debug */
@@ -8,6 +12,7 @@
#include
#include "ch554.h"
+#include "gpio.h"
// UART1 baud rates
// Setting Actual % error
@@ -29,31 +34,17 @@
//Std 1000000 1000000 0.00%
#ifndef UART0_BAUD
-//#define UART0_BAUD 115200
#define UART0_BAUD 1000000
#endif
#ifndef UART1_BAUD
#define UART1_BAUD 500000
-//#define UART1_BAUD 1000000
#endif
void CfgFsys(void); // CH554 clock selection and configuration
void mDelayuS(uint16_t n); // Delay in units of uS
void mDelaymS(uint16_t n); // Delay in mS
-// Set pin p1.4 and p1.5 to GPIO output mode.
-void gpio_init(void);
-void gpio_set(uint8_t pin);
-void gpio_unset(uint8_t pin);
-uint8_t gpio_get(uint8_t pin);
-
-void gpio_init_p1_4_in(void);
-void gpio_init_p1_5_out(void);
-uint8_t gpio_p1_4_get(void);
-void gpio_p1_5_set(void);
-void gpio_p1_5_unset(void);
-
/*******************************************************************************
* Function Name : CH554UART0Alter()
* Description : Set the alternate pin mappings for UART0 (RX on P1.2, TX on P1.3)
@@ -148,9 +139,10 @@ inline void UART1Setup()
U1SM0 = 0; // UART1 selects 8-bit data bit
U1SMOD = 1; // Fast mode
U1REN = 1; // Enable receiving
- // Should correct for rounding in SBAUD1 calculation
- SBAUD1 = 256 - FREQ_SYS/16/UART1_BAUD; // Calculation for Fast mode
+ uint32_t val = FREQ_SYS/16/UART1_BAUD; // Calculation for Fast mode
+ SBAUD1 = 256 - val; // Calculation for Fast mode
IE_UART1 = 1; // Enable UART1 interrupt
+ IP_EX = bIP_UART1; // Serial port IRQ has high priority
}
/*******************************************************************************
@@ -187,6 +179,8 @@ inline uint8_t CH554UART1RcvByte( )
*******************************************************************************/
inline void CH554UART1SendByte(uint8_t SendDat)
{
+ while(gpio_p1_4_get() == 0)
+ ;
SBUF1 = SendDat; // Query sending, the interrupt mode does not need the following two statements, but TI=0 is required before sending
while (U1TI == 0)
;
diff --git a/hw/usb_interface/ch552_fw/inc/flash.h b/hw/usb_interface/ch552_fw/inc/flash.h
new file mode 100644
index 0000000..dc32189
--- /dev/null
+++ b/hw/usb_interface/ch552_fw/inc/flash.h
@@ -0,0 +1,12 @@
+// SPDX-FileCopyrightText: 2025 Tillitis AB
+// SPDX-License-Identifier: MIT
+
+#ifndef __FLASH_H__
+#define __FLASH_H__
+
+#include
+
+uint8_t write_code_flash(uint16_t address, uint16_t data);
+uint8_t write_data_flash(uint8_t address, uint8_t data);
+
+#endif
diff --git a/hw/usb_interface/ch552_fw/inc/gpio.h b/hw/usb_interface/ch552_fw/inc/gpio.h
new file mode 100644
index 0000000..2890829
--- /dev/null
+++ b/hw/usb_interface/ch552_fw/inc/gpio.h
@@ -0,0 +1,21 @@
+// SPDX-FileCopyrightText: 2024 Tillitis AB
+// SPDX-License-Identifier: MIT
+
+#ifndef __GPIO_H__
+#define __GPIO_H__
+
+#include
+
+// Set pin p1.4 and p1.5 to GPIO output mode.
+void gpio_init(void);
+void gpio_set(uint8_t pin);
+void gpio_unset(uint8_t pin);
+uint8_t gpio_get(uint8_t pin);
+
+void gpio_init_p1_4_in(void);
+void gpio_init_p1_5_out(void);
+uint8_t gpio_p1_4_get(void);
+void gpio_p1_5_set(void);
+void gpio_p1_5_unset(void);
+
+#endif
diff --git a/hw/usb_interface/ch552_fw/inc/io.h b/hw/usb_interface/ch552_fw/inc/io.h
new file mode 100644
index 0000000..3856c6d
--- /dev/null
+++ b/hw/usb_interface/ch552_fw/inc/io.h
@@ -0,0 +1,23 @@
+// SPDX-FileCopyrightText: 2025 Tillitis AB
+// SPDX-License-Identifier: MIT
+
+#ifndef __IO_H__
+#define __IO_H__
+
+enum ioend {
+ IO_NONE = 0x00, // No endpoint
+ IO_UART = 0x01, // Only destination, raw UART access
+ IO_QEMU = 0x02, // Only destination, QEMU debug port
+ IO_CH552 = 0x04, // Internal CH552 control port
+ IO_CDC = 0x08, // CDC "serial" port
+ IO_FIDO = 0x10, // FIDO security token port
+ IO_CCID = 0x20, // CCID "smart card" port
+ IO_DEBUG = 0x40, // Debug port over USB HID
+};
+
+enum ch552cmd {
+ SET_ENDPOINTS = 0x01, // Config USB endpoints on the CH552
+ CH552_CMD_MAX,
+};
+
+#endif
diff --git a/hw/usb_interface/ch552_fw/inc/lib.h b/hw/usb_interface/ch552_fw/inc/lib.h
new file mode 100644
index 0000000..92e7def
--- /dev/null
+++ b/hw/usb_interface/ch552_fw/inc/lib.h
@@ -0,0 +1,26 @@
+// SPDX-FileCopyrightText: 2024 Tillitis AB
+// SPDX-License-Identifier: MIT
+
+#ifndef __LIB_H__
+#define __LIB_H__
+
+#include
+
+#include "config.h"
+
+#ifdef USE_NUM_U8
+int8_t uint8_to_str(uint8_t *buf, uint8_t bufsize, uint8_t n);
+#endif
+
+#ifdef USE_NUM_U32
+int8_t uint32_to_str(uint8_t *buf, uint8_t bufsize, uint32_t n);
+#endif
+
+//uint8_t hex_to_uint8(const char *hex, uint8_t len);
+//uint16_t hex_to_uint16(const char *hex, uint8_t len);
+uint32_t hex_to_uint32(const char *hex, uint8_t len);
+
+uint8_t ascii_hex_char_to_byte(uint8_t c);
+//int ascii_hex_string_to_bytes(uint8_t *hex_str, uint8_t *out_bytes, size_t out_len);
+
+#endif
diff --git a/hw/usb_interface/ch552_fw/include/mem.h b/hw/usb_interface/ch552_fw/inc/mem.h
similarity index 91%
rename from hw/usb_interface/ch552_fw/include/mem.h
rename to hw/usb_interface/ch552_fw/inc/mem.h
index 603d3c8..4d6bba8 100644
--- a/hw/usb_interface/ch552_fw/include/mem.h
+++ b/hw/usb_interface/ch552_fw/inc/mem.h
@@ -1,3 +1,6 @@
+// SPDX-FileCopyrightText: 2024 Tillitis AB
+// SPDX-License-Identifier: MIT
+
#ifndef __MEM_PART_H__
#define __MEM_PART_H__
diff --git a/hw/usb_interface/ch552_fw/inc/print.h b/hw/usb_interface/ch552_fw/inc/print.h
new file mode 100644
index 0000000..589ee6d
--- /dev/null
+++ b/hw/usb_interface/ch552_fw/inc/print.h
@@ -0,0 +1,42 @@
+// SPDX-FileCopyrightText: 2024 Tillitis AB
+// SPDX-License-Identifier: MIT
+
+#ifndef __PRINT_H__
+#define __PRINT_H__
+
+#include
+
+#include "config.h"
+
+void printStr(uint8_t *str);
+void printChar(uint8_t c);
+
+#ifdef DEBUG_SETUP
+#define printStrSetup(str) printStr(str)
+#define printNumU8HexSetup(num) printNumU8Hex(num)
+#else
+#define printStrSetup(str)
+#define printNumU8HexSetup(num)
+#endif
+
+#ifdef USE_NUM_U8
+void printNumU8(uint8_t num);
+#endif
+
+#ifdef USE_NUM_U32
+void printNumU32(uint32_t num);
+#endif
+
+#ifdef USE_NUM_U8HEX
+void printNumU8Hex(uint8_t num);
+#endif
+
+#ifdef USE_NUM_U16HEX
+void printNumU16Hex(uint16_t num);
+#endif
+
+#ifdef USE_NUM_U32HEX
+void printNumU32Hex(uint32_t num);
+#endif
+
+#endif
diff --git a/hw/usb_interface/ch552_fw/include/usb_strings.h b/hw/usb_interface/ch552_fw/inc/usb_strings.h
similarity index 50%
rename from hw/usb_interface/ch552_fw/include/usb_strings.h
rename to hw/usb_interface/ch552_fw/inc/usb_strings.h
index a36d512..d697d4b 100644
--- a/hw/usb_interface/ch552_fw/include/usb_strings.h
+++ b/hw/usb_interface/ch552_fw/inc/usb_strings.h
@@ -1,24 +1,29 @@
+// SPDX-FileCopyrightText: 2024 Tillitis AB
+// SPDX-License-Identifier: MIT
+
#ifndef __USB_STRINGS_H__
#define __USB_STRINGS_H__
#include "mem.h"
-unsigned char FLASH ProdDesc[]={ // "MTA1-USB-V1"
- 24, // Length of this descriptor (in bytes)
+unsigned char FLASH ProdDesc[] = { // "Tillitis TKEY-USB-V2"
+ 42, // Length of this descriptor (in bytes)
0x03, // Descriptor type (String)
- 'M', 0, 'T', 0, 'A', 0, '1', 0,
- '-', 0, 'U', 0, 'S', 0, 'B', 0,
- '-', 0, 'V', 0, '1', 0
+ 'T', 0, 'i', 0, 'l', 0, 'l', 0,
+ 'i', 0, 't', 0, 'i', 0, 's', 0,
+ ' ', 0, 'T', 0, 'K', 0, 'E', 0,
+ 'Y', 0, '-', 0, 'U', 0, 'S', 0,
+ 'B', 0, '-', 0, 'V', 0, '2', 0,
};
-unsigned char FLASH ManufDesc[]={ // "Tillitis"
+unsigned char FLASH ManufDesc[] = { // "Tillitis"
18, // Length of this descriptor (in bytes)
0x03, // Descriptor type (String)
'T', 0, 'i', 0, 'l', 0, 'l', 0,
- 'i', 0, 't', 0, 'i', 0, 's', 0
+ 'i', 0, 't', 0, 'i', 0, 's', 0,
};
-unsigned char FLASH SerialDesc[]={ // "68de5d27-e223-4874-bc76-a54d6e84068f"
+unsigned char FLASH SerialDesc[] = { // "68de5d27-e223-4874-bc76-a54d6e84068f"
74, // Length of this descriptor (in bytes)
0x03, // Descriptor type (String)
'6', 0, '8', 0, 'd', 0, 'e', 0,
@@ -32,33 +37,37 @@ unsigned char FLASH SerialDesc[]={ // "68de5d27-e223-4874-bc76-a54d6e84068f"
'0', 0, '6', 0, '8', 0, 'f', 0,
};
-unsigned char FLASH CdcCtrlInterfaceDesc[] = {
+unsigned char FLASH CdcCtrlInterfaceDesc[] = { // "CDC-Ctrl"
18, // Length of this descriptor (in bytes)
0x03, // Descriptor type (String)
'C', 0, 'D', 0, 'C', 0, '-', 0,
'C', 0, 't', 0, 'r', 0, 'l', 0,
};
-unsigned char FLASH CdcDataInterfaceDesc[] = {
+unsigned char FLASH CdcDataInterfaceDesc[] = { // "CDC-Data"
18, // Length of this descriptor (in bytes)
0x03, // Descriptor type (String)
'C', 0, 'D', 0, 'C', 0, '-', 0,
'D', 0, 'a', 0, 't', 0, 'a', 0,
};
-unsigned char FLASH FidoHidInterfaceDesc[] = {
- 18, // Length of this descriptor (in bytes)
+unsigned char FLASH FidoInterfaceDesc[] = { // "FIDO"
+ 10, // Length of this descriptor (in bytes)
0x03, // Descriptor type (String)
'F', 0, 'I', 0, 'D', 0, 'O', 0,
- '-', 0, 'H', 0, 'I', 0, 'D', 0,
};
-unsigned char FLASH TkeyCtrlInterfaceDesc[] = {
- 20, // Length of this descriptor (in bytes)
+unsigned char FLASH CcidInterfaceDesc[] = { // "CCID"
+ 10, // Length of this descriptor (in bytes)
0x03, // Descriptor type (String)
- 'T', 0, 'K', 0, 'E', 0, 'Y', 0,
- '-', 0, 'C', 0, 't', 0, 'r', 0,
- 'l', 0,
+ 'C', 0, 'C', 0, 'I', 0, 'D', 0,
+};
+
+unsigned char FLASH DebugInterfaceDesc[] = { // "DEBUG"
+ 12, // Length of this descriptor (in bytes)
+ 0x03, // Descriptor type (String)
+ 'D', 0, 'E', 0, 'B', 0, 'U', 0,
+ 'G', 0,
};
#endif
diff --git a/hw/usb_interface/ch552_fw/include/print.h b/hw/usb_interface/ch552_fw/include/print.h
deleted file mode 100644
index f5a4844..0000000
--- a/hw/usb_interface/ch552_fw/include/print.h
+++ /dev/null
@@ -1,42 +0,0 @@
-#ifndef __PRINT_H__
-#define __PRINT_H__
-
-#include
-
-//#define DEBUG_PRINT
-//#define DEBUG_SETUP
-//#define UART_OUT_DEBUG
-//#define USE_NUM_U8
-#define USE_NUM_U32
-//#define USE_NEGATIVE_NUMS
-
-void printStr(uint8_t *str);
-void printChar(uint8_t c);
-
-#ifdef DEBUG_SETUP
-#define printStrSetup(x) printStr(x)
-#define printNumHexSetup(x) printNumHex(x)
-#else
-#define printStrSetup(x)
-#define printNumHexSetup(x)
-#endif
-
-#ifdef USE_NUM_U8
-int8_t uint8_to_str(uint8_t *buf, uint8_t bufsize, uint8_t n);
-#endif
-
-#ifdef USE_NUM_U32
-int8_t uint32_to_str(uint8_t *buf, uint8_t bufsize, uint32_t n);
-#endif
-
-#ifdef USE_NUM_U8
-void printNumU8(uint8_t num);
-#endif
-
-#ifdef USE_NUM_U32
-void printNumU32(uint32_t num);
-#endif
-
-void printNumHex(uint8_t num);
-
-#endif
diff --git a/hw/usb_interface/ch552_fw/inject_serial_number.py b/hw/usb_interface/ch552_fw/inject_serial_number.py
index c3da88f..2d1b3c5 100755
--- a/hw/usb_interface/ch552_fw/inject_serial_number.py
+++ b/hw/usb_interface/ch552_fw/inject_serial_number.py
@@ -1,4 +1,8 @@
#!/usr/bin/env python3
+# SPDX-FileCopyrightText: 2021 Mullvad VPN AB
+# SPDX-FileCopyrightText: 2022 Tillitis AB
+# SPDX-License-Identifier: GPL-2.0-only
+
import uuid
import argparse
import encode_usb_strings
diff --git a/hw/usb_interface/ch552_fw/include/debug.c b/hw/usb_interface/ch552_fw/src/debug.c
similarity index 64%
rename from hw/usb_interface/ch552_fw/include/debug.c
rename to hw/usb_interface/ch552_fw/src/debug.c
index 7add9b6..81ac4df 100644
--- a/hw/usb_interface/ch552_fw/include/debug.c
+++ b/hw/usb_interface/ch552_fw/src/debug.c
@@ -1,4 +1,8 @@
-/********************************** (C) COPYRIGHT *******************************
+// SPDX-FileCopyrightText: 2017 WCH
+// SPDX-FileCopyrightText: 2022 Tillitis AB
+// SPDX-License-Identifier: MIT
+
+/********************************** (C) COPYRIGHT *******************************
* File Name : Debug.C
* Author : WCH
* Version : V1.0
@@ -160,108 +164,3 @@ int getchar(void)
return SBUF;
}
#endif
-
-// Set pin p1.4 and p1.5 to GPIO output mode.
-void gpio_init()
-{
- // p1.4
- P1_MOD_OC &= ~0x10;
- P1_DIR_PU |= 0x10;
-
- // p1.5
- P1_MOD_OC &= ~0x20;
- P1_DIR_PU |= 0x20;
-}
-
-void gpio_set(uint8_t pin)
-{
- switch (pin) {
- case 0x10: // p1.4
- P1 |= 0x10;
- break;
- case 0x20: // p1.5
- P1 |= 0x20;
- break;
- default: // do nothing, unsupported pin.
- break;
- }
-}
-
-void gpio_unset(uint8_t pin)
-{
- switch (pin) {
- case 0x10:
- P1 &= ~0x10;
- break;
- case 0x20:
- P1 &= ~0x20;
- break;
- default: // do nothing, unsupported pin.
- break;
- }
-}
-
-uint8_t gpio_get(uint8_t pin)
-{
- uint8_t ret = 0;
-
- switch (pin) {
- case 0x10: // p1.4
- ret = P1 & 0x10;
- break;
- case 0x20: // p1.5
- ret = P1 & 0x20;
- break;
- default: // do nothing, unsupported pin.
- ret = 0xff;
- break;
- }
-
- return ret;
-}
-
-// Set pin p1.4 to GPIO input mode. (FPGA_CTS)
-void gpio_init_p1_4_in()
-{
- // p1.4
- P1_MOD_OC &= ~0x10; // Output Mode: 0 = Push-pull output, 1 = Open-drain output
- P1_DIR_PU &= ~0x10; // Port Direction Control and Pull-up Enable Register:
- // Push-pull output mode:
- // 0 = Input.
- // 1 = Output
- // Open-drain output mode:
- // 0 = Pull-up resistor disabled
- // 1 = Pull-up resistor enabled
-}
-
-// Read status of pin 1.4
-uint8_t gpio_p1_4_get(void)
-{
- return (P1 & 0x10); // p1.4
-}
-
-// Set pin p1.5 to GPIO output mode. (CH552_CTS)
-void gpio_init_p1_5_out()
-{
- // p1.5
- P1_MOD_OC &= ~0x20; // Output Mode: 0 = Push-pull output, 1 = Open-drain output
- P1_DIR_PU |= 0x20; // Port Direction Control and Pull-up Enable Register:
- // Push-pull output mode:
- // 0 = Input.
- // 1 = Output
- // Open-drain output mode:
- // 0 = Pull-up resistor disabled
- // 1 = Pull-up resistor enabled
-}
-
-// Set p1.5 high
-void gpio_p1_5_set(void)
-{
- P1 |= 0x20; // p1.4
-}
-
-// Set p1.5 low
-void gpio_p1_5_unset(void)
-{
- P1 &= ~0x20;
-}
diff --git a/hw/usb_interface/ch552_fw/src/flash.c b/hw/usb_interface/ch552_fw/src/flash.c
new file mode 100644
index 0000000..edcd0df
--- /dev/null
+++ b/hw/usb_interface/ch552_fw/src/flash.c
@@ -0,0 +1,82 @@
+// SPDX-FileCopyrightText: 2025 Tillitis AB
+// SPDX-License-Identifier: MIT
+
+#include
+
+#include "ch554.h"
+
+uint8_t write_code_flash(uint16_t address, uint16_t data)
+{
+ uint8_t ret = 0;
+
+ SAFE_MOD = 0x55;
+ SAFE_MOD = 0xAA; // Enter Safe mode
+ GLOBAL_CFG |= bCODE_WE; // Enable flash-ROM write
+ SAFE_MOD = 0; // Exit Safe mode
+
+ // Set address
+ ROM_ADDR = address;
+ ROM_DATA = data;
+
+ // Check that write operation address is valid
+ if ((ROM_STATUS & bROM_ADDR_OK) == 0) {
+ ret |= 0x01;
+ }
+
+ if (ROM_CTRL & bROM_ADDR_OK) {
+ E_DIS = 1; // Disable global interrupts to prevent Flash operation from being interrupted
+ ROM_CTRL = ROM_CMD_WRITE; // Write
+ E_DIS = 0; // Enable global interrupts
+ }
+
+ // Check operation command error
+ if (ROM_STATUS & bROM_CMD_ERR) {
+ ret |= 0x02;
+ }
+
+ SAFE_MOD = 0x55;
+ SAFE_MOD = 0xAA; // Enter Safe mode
+ GLOBAL_CFG &= ~bCODE_WE; // Disable flash-ROM write
+ SAFE_MOD = 0; // Exit Safe mode
+
+ return ret;
+}
+
+uint8_t write_data_flash(uint8_t address, uint8_t data)
+{
+ uint8_t ret = 0;
+
+ EA = 0; // Disable global interrupts to prevent Flash operation from being interrupted
+
+ SAFE_MOD = 0x55;
+ SAFE_MOD = 0xAA; // Enter Safe mode
+ GLOBAL_CFG |= bDATA_WE; // Enable Data-Flash write
+ SAFE_MOD = 0; // Exit Safe mode
+
+ ROM_ADDR_H = DATA_FLASH_ADDR >> 8;
+ ROM_ADDR_L = address << 1;
+ ROM_DATA_L = data;
+
+ // Check that write operation address is valid
+ if ((ROM_STATUS & bROM_ADDR_OK) == 0) {
+ ret |= 0x01;
+ }
+
+ if (ROM_CTRL & bROM_ADDR_OK) {
+ ROM_CTRL = ROM_CMD_WRITE; // Write
+ }
+
+ // Check operation command error
+ if (ROM_STATUS & bROM_CMD_ERR) {
+ ret |= 0x02;
+ }
+
+ SAFE_MOD = 0x55;
+ SAFE_MOD = 0xAA; // Enter Safe mode
+ GLOBAL_CFG &= ~bDATA_WE; // Disable Data-Flash write
+ SAFE_MOD = 0; // Exit Safe mode
+
+ EA = 1; // Enable global interrupts
+
+ return ret;
+}
diff --git a/hw/usb_interface/ch552_fw/src/gpio.c b/hw/usb_interface/ch552_fw/src/gpio.c
new file mode 100644
index 0000000..d80186f
--- /dev/null
+++ b/hw/usb_interface/ch552_fw/src/gpio.c
@@ -0,0 +1,111 @@
+// SPDX-FileCopyrightText: 2023 Tillitis AB
+// SPDX-License-Identifier: MIT
+
+#include
+
+#include "ch554.h"
+
+// Set pin p1.4 and p1.5 to GPIO output mode.
+void gpio_init()
+{
+ // p1.4
+ P1_MOD_OC &= ~0x10;
+ P1_DIR_PU |= 0x10;
+
+ // p1.5
+ P1_MOD_OC &= ~0x20;
+ P1_DIR_PU |= 0x20;
+}
+
+void gpio_set(uint8_t pin)
+{
+ switch (pin) {
+ case 0x10: // p1.4
+ P1 |= 0x10;
+ break;
+ case 0x20: // p1.5
+ P1 |= 0x20;
+ break;
+ default: // do nothing, unsupported pin.
+ break;
+ }
+}
+
+void gpio_unset(uint8_t pin)
+{
+ switch (pin) {
+ case 0x10:
+ P1 &= ~0x10;
+ break;
+ case 0x20:
+ P1 &= ~0x20;
+ break;
+ default: // do nothing, unsupported pin.
+ break;
+ }
+}
+
+uint8_t gpio_get(uint8_t pin)
+{
+ uint8_t ret = 0;
+
+ switch (pin) {
+ case 0x10: // p1.4
+ ret = P1 & 0x10;
+ break;
+ case 0x20: // p1.5
+ ret = P1 & 0x20;
+ break;
+ default: // do nothing, unsupported pin.
+ ret = 0xff;
+ break;
+ }
+
+ return ret;
+}
+
+// Set pin p1.4 to GPIO input mode. (FPGA_CTS)
+void gpio_init_p1_4_in()
+{
+ // p1.4
+ P1_MOD_OC &= ~0x10; // Output Mode: 0 = Push-pull output, 1 = Open-drain output
+ P1_DIR_PU &= ~0x10; // Port Direction Control and Pull-up Enable Register:
+ // Push-pull output mode:
+ // 0 = Input.
+ // 1 = Output
+ // Open-drain output mode:
+ // 0 = Pull-up resistor disabled
+ // 1 = Pull-up resistor enabled
+}
+
+// Read status of pin 1.4
+uint8_t gpio_p1_4_get(void)
+{
+ return (P1 & 0x10); // p1.4
+}
+
+// Set pin p1.5 to GPIO output mode. (CH552_CTS)
+void gpio_init_p1_5_out()
+{
+ // p1.5
+ P1_MOD_OC &= ~0x20; // Output Mode: 0 = Push-pull output, 1 = Open-drain output
+ P1_DIR_PU |= 0x20; // Port Direction Control and Pull-up Enable Register:
+ // Push-pull output mode:
+ // 0 = Input.
+ // 1 = Output
+ // Open-drain output mode:
+ // 0 = Pull-up resistor disabled
+ // 1 = Pull-up resistor enabled
+}
+
+// Set pin 1.5 high
+void gpio_p1_5_set(void)
+{
+ P1 |= 0x20;
+}
+
+// Set pin 1.5 low
+void gpio_p1_5_unset(void)
+{
+ P1 &= ~0x20;
+}
diff --git a/hw/usb_interface/ch552_fw/include/print.c b/hw/usb_interface/ch552_fw/src/lib.c
similarity index 51%
rename from hw/usb_interface/ch552_fw/include/print.c
rename to hw/usb_interface/ch552_fw/src/lib.c
index d65f6e4..0c5b77c 100644
--- a/hw/usb_interface/ch552_fw/include/print.c
+++ b/hw/usb_interface/ch552_fw/src/lib.c
@@ -1,28 +1,9 @@
+// SPDX-FileCopyrightText: 2024 Tillitis AB
+// SPDX-License-Identifier: MIT
+
#include
-#include "debug.h"
-#include "print.h"
-
-void printStr(uint8_t *str)
-{
-#ifdef DEBUG_PRINT
- while (*str != 0) {
- CH554UART0SendByte(*str);
- ++str;
- }
-#else
- (void)str;
-#endif
-}
-
-void printChar(uint8_t c)
-{
-#ifdef DEBUG_PRINT
- CH554UART0SendByte(c);
-#else
- (void)c;
-#endif
-}
+#include "config.h"
#ifdef USE_NUM_U8
int8_t uint8_to_str(uint8_t *buf, uint8_t bufsize, uint8_t n)
@@ -132,57 +113,91 @@ int8_t uint32_to_str(uint8_t *buf, uint8_t bufsize, uint32_t n)
}
#endif
-#ifdef USE_NUM_U8
-void printNumU8(uint8_t num)
+#if 0
+uint8_t hex_to_uint8(const char *hex, uint8_t len)
{
-#ifdef DEBUG_PRINT
- uint8_t num_str[4] = { 0 };
- int8_t ret;
- ret = uint8_to_str(num_str, 4, num);
- if (!ret) {
- printStr(num_str);
+ uint8_t val = 0;
+ while (len) {
+ char c = *hex++;
+ val <<= 4;
+ if (c >= '0' && c <= '9') {
+ val |= c - '0';
+ } else if (c >= 'A' && c <= 'F') {
+ val |= c - 'A' + 10;
+ } else if (c >= 'a' && c <= 'f') {
+ val |= c - 'a' + 10;
+ } else {
+ return 0; // Invalid character
+ }
+ len--;
}
-#endif
+ return val;
}
#endif
-#ifdef USE_NUM_U32
-void printNumU32(uint32_t num)
+#if 0
+uint16_t hex_to_uint16(const char *hex, uint8_t len)
{
-#ifdef DEBUG_PRINT
- uint8_t num_str[11] = { 0 };
- int8_t ret;
- ret = uint32_to_str(num_str, 10, num);
- if (!ret) {
- printStr(num_str);
+ uint16_t val = 0;
+ while (len) {
+ char c = *hex++;
+ val <<= 4;
+ if (c >= '0' && c <= '9') {
+ val |= c - '0';
+ } else if (c >= 'A' && c <= 'F') {
+ val |= c - 'A' + 10;
+ } else if (c >= 'a' && c <= 'f') {
+ val |= c - 'a' + 10;
+ } else {
+ return 0; // Invalid character
+ }
+ len--;
}
-#else
- (void)num;
-#endif
+ return val;
}
#endif
-void printNumHex(uint8_t num)
+uint32_t hex_to_uint32(const char *hex, uint8_t len)
{
-#ifdef DEBUG_PRINT
- // High nibble
- uint8_t val = num >> 4;
- if (val <= 9) {
- val = val + '0';
- } else {
- val = (val-10) + 'A';
+ uint32_t val = 0;
+ while (len) {
+ char c = *hex++;
+ val <<= 4;
+ if (c >= '0' && c <= '9') {
+ val |= c - '0';
+ } else if (c >= 'A' && c <= 'F') {
+ val |= c - 'A' + 10;
+ } else if (c >= 'a' && c <= 'f') {
+ val |= c - 'a' + 10;
+ } else {
+ return 0; // Invalid character
+ }
+ len--;
}
- printChar(val);
-
- // Low nibble
- val = num & 0x0F;
- if (val <= 9) {
- val = val + '0';
- } else {
- val = (val-10) + 'A';
- }
- printChar(val);
-#else
- (void)num;
-#endif
+ return val;
}
+
+uint8_t ascii_hex_char_to_byte(uint8_t c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ return 0; // Invalid character, should not happen if input is valid
+}
+
+#if 0
+int ascii_hex_string_to_bytes(uint8_t *hex_str, uint8_t *out_bytes, size_t out_len)
+{
+ if (!hex_str || !out_bytes || out_len < 32)
+ return -1; // Error handling
+
+ for (size_t i = 0; i < 32; i++) {
+ out_bytes[i] = (ascii_hex_char_to_byte(hex_str[i * 2]) << 4) | ascii_hex_char_to_byte(hex_str[i * 2 + 1]);
+ }
+
+ return 0; // Success
+}
+#endif
diff --git a/hw/usb_interface/ch552_fw/main.c b/hw/usb_interface/ch552_fw/src/main.c
similarity index 52%
rename from hw/usb_interface/ch552_fw/main.c
rename to hw/usb_interface/ch552_fw/src/main.c
index e8dd4c7..35c98d7 100644
--- a/hw/usb_interface/ch552_fw/main.c
+++ b/hw/usb_interface/ch552_fw/src/main.c
@@ -1,3 +1,7 @@
+// SPDX-FileCopyrightText: 2017 WCH
+// SPDX-FileCopyrightText: 2022 Tillitis AB