Compare commits

..

73 Commits
cth-1 ... main

Author SHA1 Message Date
Mikael Ågren
353d7e9f50
tkey-libs: Import tag fw-4 of tkey-libs
- Use tag fw-4 from https://github.com/tillitis/tkey-libs/
2025-04-23 15:13:28 +02:00
Jonas Thörnblad
f75620720f
ch552: Clean up debugging of USB stack 2025-04-17 10:27:57 +02:00
Jonas Thörnblad
fb1269b06e
ch552: Fix various USB stack things, add check for IO_CH552 command range.
- Move handling of returning data from USB_GET_DESCRIPTOR request.
- Fix correct size of ActiveCfgDesc descriptor.
- Add missing check for limiting total length in USB_CDC_REQ_TYPE_GET_LINE_CODING.
- Add missing break in DeviceInterrupt to follow coding style.
- Check command range for IO_CH552.
- Add some comments.
2025-04-17 10:27:57 +02:00
Jonas Thörnblad
770acc9b38
ch552: Add CCID (Smart Card) support 2025-04-17 10:27:56 +02:00
Michael Cardell Widerkrantz
d0c049cdba
fw/ch552: Document new dynamic endpoint functionality
The CH552 firmware has an added functionality to control the USB
controller dynamically, turning on and off endpoints with a small
protocol.

Since most of the documentation for the already lives in the ordinary
firmware README, add this documentation there, too.
2025-04-07 11:00:32 +02:00
Michael Cardell Widerkrantz
7eca72c2c3
ch552: Add SPDX tags
- Add SPDX tags to source files.
- Make REUSE compliant.
- Add reuse.toml configuration.
2025-04-07 11:00:31 +02:00
Jonas Thörnblad
d43585ee1a
ch552: Add functionality to dynamically control USB endpoints
- Make it possible to enable and disable endpoints on demand
- Add internal FPGA<->CH552 communication channel (IO_CH552)
- Reorder IO endpoint numbering
- Rename endpoint from TKEYCTRL to DEBUG and update related variables
- Rename endpoint from HID to FIDO and update related variables
2025-04-07 11:00:31 +02:00
Jonas Thörnblad
d94387a9e7
ch552: Split functionality into separate files
- Move GPIO functions to gpio.c and gpio.h
- Move string handling functions for debug to lib.c and lib.h
- Add config.h and move config defines there
- Add debug print functionality via FPGA with framing protocol
- Add Flash Code and Flash Data functions to flash.c and flash.h
2025-04-07 11:00:11 +02:00
Jonas Thörnblad
7de49d2021
ch552: Reorganize file directory structure
- Move source files to separate directory.
- Move include files to separate directory.
- Add specific output direcory for intermediate files.
2025-03-24 14:12:45 +01:00
Jonas Thörnblad
8d8f4c7faf
ch552: Misc. cleanup
- Merge Makefile and Makefile.include into Makefile
  - Format structure
  - Remove unused variables, targets etc.

- Add missing check if it is ok to send data to the FPGA.

- Remove 'baud rate calculator.ods'

- Update encode_usb_strings.py to generate strings for
  CdcCtrlInterfaceDesc, CdcDataInterfaceDesc,
  FidoHidInterfaceDesc, TkeyCtrlInterfaceDesc.
  Also store generated strings in UTF-16 instead of hex.

- Update usb_strings.h to match new encode_usb_strings.py
  output.

- Remove unused struct SetupReqBuf.
2025-03-14 09:49:26 +01:00
Michael Cardell Widerkrantz
33f14122ad
doc: Add note about building 2025-03-13 11:07:47 +01:00
Michael Cardell Widerkrantz
435b1f9d29
build: Update binary digests 2025-03-13 11:07:47 +01:00
Michael Cardell Widerkrantz
16a9e8c367
fw: Import tkey-libs fw-2
This is an import of the fw-2 tag of tkey-libs.

We import the entire tkey-libs repo minus dot files into the
tillitis-key1 repo to make it very simple not to make mistakes
regarding which firmware tag depends on which tkey-libs tag,
especially considering locking down with NVCM.

Please see README for information about developing with another
tkey-libs or how to import future tkey-libs.

Since tkey-libs is now a part of the repo we also add tkey-libs to the
clean_fw target.
2025-03-13 11:07:47 +01:00
Michael Cardell Widerkrantz
3dbc31f54c
fw: Move tk1_mem.h to tkey-libs
From now on the canonical home of the tk1_mem.h header file describing
the memory map of the TKey lives in tkey-libs:

https://github.com/tillitis/tkey-libs
2025-03-13 11:07:47 +01:00
Michael Cardell Widerkrantz
cd1a089763
fw: Build with tkey-libs
Build firmware, testfw and testapp using tkey-libs:

  https://github.com/tillitis/tkey-libs

In an effort not to have more or less identical code maintained in two
places, use tkey-libs when developing firmware, testfw and the
firmware testapp, too.

You can place the Git directory directly under hw/application_fpga
and then an ordinary make should work.

Or build with:

  make LIBDIR=/path/to/tkey-libs

Co-authored-by: Mikael Ågren <mikael@tillitis.se>
2025-03-13 11:07:36 +01:00
Sasko Simonovski
1d5d721f1e
README: Added section about work in progress. 2025-03-07 15:24:27 +01:00
Mikael Ågren
a41360917a
build: Update digests of firmware and bitstream 2025-02-27 14:35:23 +01:00
Mikael Ågren
b524cd0d6e
fpga: Update next-pnr seed to reach 24 MHz 2025-02-27 14:35:23 +01:00
Michael Cardell Widerkrantz
ad62f6e48f
doc: Update release notes about syscall mechanism 2025-02-27 14:35:22 +01:00
Mikael Ågren
c52442b54c
doc: Update documentation about syscalls
- Revise firmware implementation notes
- Document how to do fw syscalls
  - Document how to trigger a syscall function in the firmware, how to
    pass arguments, what the caller is responsible for and what is
    returned.
- Describe hardware syscall implementation
  - how the syscall interrupts are triggered,
  - the hardware privilege escalation,
  - the UDS protection.

Co-authored-by: Daniel Jobson <jobson@tillitis.se>
Co-authored-by: Michael Cardell Widerkrantz <mc@tillitis.se>
2025-02-27 14:35:22 +01:00
Mikael Ågren
7554787678
fpga: Add extra access control on UDS
Restrict access to UDS when we have exited firmware the first time.

Co-authored-by: Michael Cardell Widerkrantz <mc@tillitis.se>
2025-02-27 14:35:22 +01:00
Mikael Ågren
77fc5cf578
fpga: Only allow system reset in firmware mode and syscalls 2025-02-27 14:29:07 +01:00
Mikael Ågren
9e317666d3
fpga/fw: Remove SYSTEM_MODE_CTRL register 2025-02-27 14:29:07 +01:00
Michael Cardell Widerkrantz
df04fd56dd
fpga/fw: Introduce syscall TK1_SYSCALL_GET_VIDPID
Introduce new syscall TK1_SYSCALL_GET_VIDPID to get Vendor ID and
Product ID from the protected Unique Device Identification number.

UDI is protected from device apps to protect the serial number, so
apps won't know the exact TKey they are running on other than the CDI.
It may, however, be important to know what *kind* of TKey they are
running on, so we want to expose the Vendor ID and Product ID.

- fpga: Allow UDI to be read when doing syscalls.
- Add the new syscall to firmware.
- Add test to testapp directly after negative test of reading UDI to
  read out VID/PID through a syscall.
2025-02-27 14:29:07 +01:00
Mikael Ågren
13f40561ab
testapp: Call reset syscall 2025-02-27 14:29:06 +01:00
Mikael Ågren
4ba164732d
testapp: Add syscalls 2025-02-27 14:29:06 +01:00
Mikael Ågren
fed9354fe9
testfw/testapp: Break out tests running in app mode into separate app
App mode can no longer be controlled from software. So the tests have to
run from firmware RAM.
2025-02-27 14:27:12 +01:00
Mikael Ågren
d82c3a706e
fw: Add syscalls
Adds:
- SYSCALL_RESET
- SYSCALL_SET_LED

Co-authored-by: Michael Cardell Widerkrantz <mc@tillitis.se>
2025-02-27 14:27:05 +01:00
Mikael Ågren
969df46315
tb: Test ROM execution protection 2025-02-27 14:20:40 +01:00
Mikael Ågren
0ee971e38c
tb: Expand existing tests with access checks in app mode and syscalls
Checks availability of:
- CDI
- UDI
- RAM
- SPI
2025-02-27 14:20:39 +01:00
Mikael Ågren
9c0311cdfc
tb: Fix broken tb_tk1 tests
Fixing tests that broke when adding interrupt based syscalls
- Removing the blake2s test since the blake2s registers are removed.
- Instead of writing to ADDR_SYSTEM_MODE_CTRL, app mode is now entered
  automatically when executing outside of ROM.
- The SPI loop-back test need to clean up after the previous test. We
  reset the memory bus to a known idle state. We also reset the DUT to
  make the SPI master visible.
2025-02-27 14:20:39 +01:00
Daniel Jobson
d1abaad5da
fpga: Deny access to the SPI master in app mode
Co-authored-by: Mikael Ågren <mikael@tillitis.se>
2025-02-27 14:20:39 +01:00
Mikael Ågren
4363637afa
fpga: Trap when executing from ROM in app mode
Only allow executing from ROM when in one of the following execution
contexts:
- Firmware mode
- Syscall

Co-authored-by: Daniel Jobson <jobson@tillitis.se>
2025-02-27 14:20:38 +01:00
Michael Cardell Widerkrantz
5eb020275b
fpga/fw/testfw: Remove Blake2s register
Since the introduction of the syscall mechanism we don't allow
execution in ROM anymore so it's impossible to call the firmware's
blake2s() function.

Co-authored-by: Mikael Ågren <mikael@tillitis.se>
2025-02-27 14:20:38 +01:00
Daniel Jobson
24ef39b739
fpga: Automatically control app_mode in hardware
Instead of manually switching to app mode using the APP_MODE register,
app mode will be enabled when the CPU fetches an instruction outside of
firmware ROM.

Co-authored-by: Mikael Ågren <mikael@tillitis.se>
2025-02-27 14:20:38 +01:00
Mikael Ågren
97de5e68fd
fpga/fw: Rename system_mode to app_mode
Rename `system_mode` to `app_mode` as to not confuse it with syscall or
firmware mode. When `app_mode` is `1`/`true` we are in app mode.
2025-02-27 14:20:37 +01:00
Mikael Ågren
19ae709c81
fpga: Add syscall interrupt
Add syscall interrupt to be used for syscalls. The interrupt is
triggered by writing to an address in the 0xe1000000-0xe1ffffff

The PicoRV32 core is configured to use its minimal, non RISCV-standard,
interrupt implementation.
2025-02-27 14:20:28 +01:00
Mikael Ågren
dd48b77047
tb: Check security monitor read access protection 2025-02-26 13:38:28 +01:00
Mikael Ågren
03c0ca7c86
tb: Display errors in tb_tk1 even if DEBUG is 0
Always display errors to make them easy to find and troubleshoot.
2025-02-26 11:16:23 +01:00
Mikael Ågren
b1047b3618
tb: Write data only once per call to write_word() in tb_tk1
Keep WE and CS high for one clock cycle instead of two. To avoid writing
the same address twice.
2025-02-26 11:16:23 +01:00
Mikael Ågren
0b829cc9ee
.gitignore: compile_commands.json and .cache 2025-02-26 11:16:18 +01:00
Jonas Thörnblad
46ef63ee2d
ch552: Misc. fixes and cleanup
- Move copying of TKEYCTRL data from UartRxBuf to TkeyCtrlRxBuf to align
  with previous code.

- Remove obsolete UartRxBufOverflow variable.

- Add missing Endpoint4 handling for USB bus reset.

- Fix more robust uart_byte_count() calculation.

- Fix baudrate fast mode calculation to get rid of compiler warning.

- Fix assignment of bUD_PD_DIS to UDEV_CTRL.

- Cleanup comments.
2025-02-25 14:40:26 +01:00
Jonas Thörnblad
0b75d25431
ch552: Fix race condition
- Move "EndpointXUploadBusy = 1" to before USB transfer is started to fix
  race with USB transfer complete interrupt.
2025-02-25 14:36:03 +01:00
Jonas Thörnblad
8f2f312531
fpga/fw: Resize ROM and FW_RAM, add RESETINFO partition
In order to be able to leave data for firmware signalling the
intention with a reset or to leave data for the next app in a chain of
apps, we introduce a part of FW_RAM that can be used to store this
data. In order to do this, we:

- Change size of ROM from 6 KB to 8 KB.
- Change size of FW_RAM, from 2 KB to 4 KB.
- Add RESETINFO memory partition inside FW_RAM.
- Add generation of map file.
- Change CFLAGS from using -O2 to using -Os.
- Update address ranges for valid access to ROM and FW_RAM.
- Move stack to be located before data+bss and the RESETINFO data
  above them. This also means we introduce hardware stack overflow
  protection through the Security Monitor.
- Revise firmware README to the new use of FW_RAM.
2025-02-21 11:15:34 +01:00
Michael Cardell Widerkrantz
3126a9c51e
doc: Revise threat model for spelling
- Spell out Chaos Communication Congress.
- Correct spealling of weaknesses.
2025-02-18 09:40:52 +01:00
Michael Cardell Widerkrantz
9a301403e1
doc: Update copyright notice on CH552 fw 2025-02-13 13:49:29 +01:00
Michael Cardell Widerkrantz
de32c58355
doc: Note in CH552 fw where to find CH55x Reset Controller 2025-02-12 14:09:20 +01:00
Michael Cardell Widerkrantz
b7ce031bd6
doc: Revise release notes
- Make it even clearer that legacy device apps WILL NOT WORK.
- Add helpful links to the CH55x Reset Controller, both where to buy
  one and source repo.
2025-02-12 14:09:16 +01:00
Michael Cardell Widerkrantz
d2c7fb0ba9
doc: Update firmware README to include USB Mode Protocol
+ minor link and typo fixes.
2025-02-11 15:21:02 +01:00
Michael Cardell Widerkrantz
179c13e9bf
build: Update digests of firmware and bitstream 2025-02-11 14:40:01 +01:00
Michael Cardell Widerkrantz
050e0f2673
fpga: Format Verilog 2025-02-11 14:37:29 +01:00
Michael Cardell Widerkrantz
aedd6102ea
testfw: Add support for USB Mode Protocol 2025-02-11 14:10:57 +01:00
Michael Cardell Widerkrantz
f68414c4aa
ci: Include Verilog formatting check in CI
- Change checkfmt make target to run both Verilog formatting check and
  C code formatting check.

- Make check formatting it's own job in the CI.
2025-02-11 13:50:08 +01:00
Michael Cardell Widerkrantz
75ad033e03
build: Add -Wno-GENUNNAMED to LINT_FLAGS
For ages we have had a comment saying:

  For Verilator 5.019 -Wno-GENUNNAMED needs to be added to LINT_FLAGS for the
  cell library.

With the new tkey-builder we have 5.028, so it's time to apply this flag.
2025-02-11 13:50:08 +01:00
Michael Cardell Widerkrantz
05bb999759
build/ci: Use new tkey-builder
Use the release candidate for tkey-builder:5
2025-02-11 13:50:07 +01:00
Michael Cardell Widerkrantz
81ac7bffa0
podman/docker: Run bash as login shell
To get bash to source /etc/profile and get the goodness of
/etc/profile/bash_completion.sh, run bash as a login shell.
2025-02-11 13:50:07 +01:00
Michael Cardell Widerkrantz
bb18d5b9e9
toolchain: Introduce buildtools.sh script
Instead of repeated RUNs in Dockerfile, move the entire build of
specific tools to a script.

- Make commands more shell script-like.
- icestorm: Make sure we checkout the right commit.
- Add checks for the right digest for all git clones, so no history
  has been changed.
- Add digest file and check for the downloaded tarball.
2025-02-11 13:50:07 +01:00
Michael Cardell Widerkrantz
8ed16fff6a
docs: Add Castor release notes so far
Breaking change! The introduction of the USB Controller Protocol means
we have a breaking change that makes device apps unable to
communicate.
2025-02-11 13:50:06 +01:00
Jonas Thörnblad
c292595ee3
ch552: Raise UART IRQ priority and tune USB polling period
Set UART1 IRQ to high priority to not miss any incoming bytes
and tune USB polling period (bInterval).
2025-02-11 13:50:06 +01:00
Jonas Thörnblad
361890042a
ch552: Update USB polling period
Update USB polling period (bInterval) for CDC, HID and TKEYCTRL
endpoints.
2025-02-11 13:50:06 +01:00
Jonas Thörnblad
5029eb1d39
ch552: Fix CDC configuration problem on Windows
Fix CDC configuration problem on Windows when we have a composite
device (multiple different Device Classes). Add "Interface Association
Descriptor" to make it work.
2025-02-11 13:50:05 +01:00
Jonas Thörnblad
04ec938200
ch552: Add new USB debug pipe (TKEYCTRL)
Make the CH552 present a new HID endpoint used for debug data.
2025-02-11 13:50:05 +01:00
Jonas Thörnblad
bfc43093ec
fpga: Fix bitrate counter bug
Fix off-by-one UART bitrate counter value that will make the RX
sampling and TX sending drift. The impact gets higher as the baudrate
increases and the bitrate counter value gets smaller.
2025-02-11 13:50:05 +01:00
Jonas Thörnblad
07dc20e4e1
fpga/testfw: Update clock frequency to 24 MHz
Reconfigure the baudrate to keep 500 kbaud.

Correct a forgotten test in testfw that wasn't updated the last time
frequency was raised in commit
75b028505f0d6dc685d37b84d73ddb9db5ee7ea2 in June 17, 2024.
2025-02-11 13:50:04 +01:00
Jonas Thörnblad
0a634c76da
ch552: Use the new hardware CTS signals for UART access
- Use CTS signals to let the FPGA and CH552 signal each other that
    it is OK send UART data.
  - Update the CH552 rx and frame handling logic.
  - Fix minor spelling errors and indentation
2025-02-11 13:50:04 +01:00
Jonas Thörnblad
ab4ef5fdf9
fpga: Introduce CTS signals for UART
Add incoming and outgoing CTS (Clear To Send) signals for the FPGA to
let the CH552 and FPGA signal each other that it is OK to send UART
data. The CTS signals indicate "OK to send" if high. If an incoming
CTS signal goes low, the receiver of that signal should immediatly
stop sending UART data.
2025-02-11 13:50:04 +01:00
Mikael Ågren
f3706dcfcc
fpga: Increase UART baud rate to 500k 2025-02-11 13:50:03 +01:00
Mikael Ågren
a0c031eb25
fw: Minimal CDC implementation of new framing protocol
Throwing away mode and length from incoming data. Adding mode and
length to outgoing data.

Splitting responses into frames small enough for the USB<->UART
transceiver to handle.
2025-02-11 13:50:03 +01:00
Mikael Ågren
1b9bbc4eba
ch552: Wrap accesses to UART output buffers 2025-02-06 16:20:34 +01:00
Jonas Thörnblad
b443359e9c
ch552: Add USB HID and protocol support over UART
- Add USB HID support.
- Introduce a small protocol to distinguish between CDC and HID data
  sent over the UART.
- Add some debug printing.
- Cleanup of code and formatting.
2025-02-06 16:20:34 +01:00
Jonas Thörnblad
90fca5d3dd
ch552: Move usb_strings.h to the include directory 2025-02-06 16:20:34 +01:00
Jonas Thörnblad
0af82ee566
fpga/fw: Extend checks for invalid memory accesses
- Extend hardware checks for invalid memory accesses to include
  checking more address space.

- In fw include file: fix two typos for memory ranges that relates to
  above that fortunately have no impact on functionality.
2025-02-06 16:16:46 +01:00
Michael Cardell Widerkrantz
a5ed3cfaa9
Build: Don't depend on uds.hex and udi.hex
synth.json shouldn't depend on uds.hex and udi.hex because that
triggers a complete rebuild of the bitstream if the UDI or UDS are
changed.

Instead, we want only the application_fpga.asc to depend on them, so
we can patch in the UDS and UDI with tools/patch_uds_udi.py in an
existing application_fpga_par.json.
2025-01-20 14:48:53 +01:00
120 changed files with 11213 additions and 2175 deletions

View File

@ -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,26 @@ 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
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 +59,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 +79,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 +101,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 +130,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

6
.gitignore vendored
View File

@ -28,6 +28,10 @@
/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
synth.json
synth.txt
synth.v
@ -49,3 +53,5 @@ verilated/
.*.swp
*.sch-bak
*.sim
compile_commands.json
.cache

View File

@ -2,6 +2,9 @@
# Tillitis TKey
Read about current work in progress
[here](#current-work-in-progress-in-this-repository).
![TK1 PCB](doc/images/tkey-open-lid.png) *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: <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/>
All of the TKey software, firmware, FPGA Verilog code, schematics and
PCB design files are open source, just like all trustworthy security
@ -56,17 +59,71 @@ releases.
The TKey PCB [KiCad](https://www.kicad.org/) design files are kept in
a separate repository:
https://github.com/tillitis/tk1-pcba
<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
<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
Building is probably easiest using make and Podman. Do this to see all
targets:
```
cd contrib
make
```
Build the entire FPGA bitstream, which includes the firmware, using
Podman:
```
cd contrib
make run-make
```
See the [Tillitis Developer Handbook](https://dev.tillitis.se) for
more.
## 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.
## Measured boot
The key behind guaranteeing security even as a general computer is the
@ -104,3 +161,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 TKeys 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).

View File

@ -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"

View File

@ -5,7 +5,7 @@
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:"
@ -23,11 +23,11 @@ all:
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 \

134
contrib/buildtools.sh Executable file
View File

@ -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

1
contrib/verible.sha512 Normal file
View File

@ -0,0 +1 @@
3e997b8cd494556fa107b96a6daa4e51133208a85b3c0250c0223d7194aedbc98b3f5213fedaee083edd96c317e50057aa9cdc0517197f4638be3834133e2c9f /src/verible.tar.gz

View File

@ -2,6 +2,134 @@
Descriptions of the tagged TKey releases.
## Upcoming release: Castor
Overview of changes since TK TK1-24.03 for the Castor milestone so
far.
**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.
### General
- Split repo:
- tk1, mta1-usb-dev, mta-usb-v1 and mta1-library moves to
https://github.com/tillitis/tk1-pcba
- tp1, mta1-usb-programmer, mta1-library and KiCad-RP Pico moves to
https://github.com/tillitis/tp1
For full change log [see](https://github.com/tillitis/tillitis-key1/compare/TK1-23.03.2...coming-tag)
### FPGA
- Security Monitor memory access checks are now more complete.
- Add SPI main controller mainly to access flash.
- Add system reset API. Device apps can reset the system and restart
the firmware. The FPGA is not reset.
- Increase clock frequence to 24 MHz.
- Increase UART baudrate to 500,000.
- Fix missing clock cycles in timer core.
- Remove the UART runtime configuration API.
- Several clean ups and testbench changes.
- Make Verilator simulation work again.
- 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 support 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.
- Add extra protection of UDS: When execution leaves ROM the first
time, UDS is hardware protected from reading, as well as already
existing UDS protection after first read and UDS being unreadable in
app mode.
- Introduce interrupt handler for hardware-based privilege raising 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 endpoints.
- Introduce a system call mechanism and the first syscalls: RESET,
SET\_LED, GET\_VIDPID.
### CH552
- Use the new CTS signals for communication over the UART.
- Add support for two HID endpoints.
- Add protocol to communicate with the three different endpoints: CDC,
HID, 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:
https://shop-nl.blinkinlabs.com/products/ch55x-reset-controller
https://github.com/Blinkinlabs/ch55x_programmer
### 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 https://github.com/tillitis/tp1
- Remove Go compiler support.
### 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

View File

@ -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

View File

@ -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 \
@ -59,7 +61,9 @@ CFLAGS = \
-Wpedantic \
-Wno-language-extension-token \
-flto \
-g
-g \
-I $(LIBDIR)/include \
-I $(LIBDIR)
AS = clang
@ -67,7 +71,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 +118,25 @@ 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/blake2s/blake2s.o \
$(P)/fw/tk1/syscall_enable.o \
$(P)/fw/tk1/syscall_handler.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
$(P)/fw/tk1/blake2s/blake2s.c \
$(P)/fw/tk1/syscall_handler.c
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 +149,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,19 +173,29 @@ 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
# 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: CFLAGS += -DQEMU_DEBUG
qemu_firmware.elf: firmware.elf
mv firmware.elf qemu_firmware.elf
@ -219,8 +226,8 @@ splint:
-fullinitblock \
$(FIRMWARE_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:
@ -241,9 +248,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 +265,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 +308,8 @@ 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
$(FORMAT) $(CHECK_FORMAT_FLAGS) $^ 2>&1 | \
grep "Needs formatting" && exit 1 || true
.PHONY: checkfmt
@ -353,8 +360,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 +374,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 9416596747216415304 \
--freq $(TARGET_FREQ) \
--ignore-loops \
--up5k \
@ -481,15 +487,16 @@ clean: clean_sim clean_fw clean_tb
.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

View File

@ -11,16 +11,17 @@ 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.
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,6 +35,7 @@ Rough memory map:
| UART | 0xc3 |
| Touch | 0xc4 |
| FW\_RAM | 0xd0 |
| Syscall | 0xe1 |
| TK1 | 0xff |
## `clk_reset_gen`
@ -96,6 +98,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 +157,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 +184,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.

View File

@ -1 +1 @@
8b09a7b2c9b864711e19f85de2785c8ea52f454207943c13bb17fff1ce095711 application_fpga.bin
3d88184d4d636878d7d891e0360413b47f288eea2227435f0bfa2aad6e956f24 application_fpga.bin

View File

@ -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.

View File

@ -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,10 +34,14 @@ 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;
//----------------------------------------------------------------
@ -45,66 +49,257 @@ module fw_ram (
//----------------------------------------------------------------
assign read_data = tmp_read_data;
assign ready = ready_reg;
assign system_mode_cs = cs && ~system_mode;
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
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
else begin
2'b00: begin
bank0 = 1'h1;
tmp_read_data = mem_read_data0;
end
endcase
end
end

View File

@ -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

View File

@ -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;
@ -105,8 +104,9 @@ module tk1 #(
localparam TK1_VERSION = 32'h00000005;
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.
@ -114,8 +114,8 @@ module tk1 #(
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;
@ -187,7 +184,8 @@ module tk1 #(
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;
@ -199,7 +197,6 @@ module tk1 #(
assign system_reset = system_reset_reg;
//----------------------------------------------------------------
// Module instance.
//----------------------------------------------------------------
@ -250,7 +247,7 @@ module tk1 #(
//----------------------------------------------------------------
always @(posedge clk) begin : reg_update
if (!reset_n) begin
system_mode_reg <= 1'h0;
fw_startup_done_reg <= 1'h0;
led_reg <= 3'h6;
gpio1_reg <= 2'h0;
gpio2_reg <= 2'h0;
@ -258,7 +255,6 @@ module tk1 #(
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;
@ -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
if (!app_mode) begin
system_reset_new = 1'h1;
end
if (address == ADDR_BLAKE2S) begin
if (!system_mode_reg) begin
blake2s_addr_we = 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,16 +589,22 @@ module tk1 #(
end
if (address == ADDR_SPI_EN) begin
if (!app_mode) begin
spi_enable_vld = 1'h1;
end
end
if (address == ADDR_SPI_XFER) begin
if (!app_mode) begin
spi_start = 1'h1;
end
end
if (address == ADDR_SPI_DATA) begin
if (!app_mode) begin
spi_tx_data_vld = 1'h1;
end
end
end
else begin
@ -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,27 +636,27 @@ 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
if (!app_mode) begin
tmp_read_data[0] = spi_ready;
end
end
if (address == ADDR_SPI_DATA) begin
if (!app_mode) begin
tmp_read_data[7 : 0] = spi_rx_data;
end
end
end
end

View File

@ -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_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 (DEBUG) begin
$display("--- Reading 0x%08x from 0x%02x.", read_data, address);
end
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
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("");

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1 +1 @@
39d5aee11b8553544ba9171f83fbe6f5b7546a15c70d03325e72a2b0ca86c8f7a2b5b6bf121d1d3ffc84a502a2a1a6f3ea140d1424cd424336e055be2f394f83 firmware.bin
77e6c9e519bce27f7be1a38c10839875ac8c75c71bef540f11569e821638af85a3e724089702a1c17af4c9caa06461ef38a3b8ad5bc5cb01ecf1f3107771708f firmware.bin

View File

@ -1,35 +1,43 @@
# 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, and
starting applications. 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 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,21 +45,70 @@ 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* |
|--------|---------|----------------------------------------------------------------------|
| CCID | 0x08 | USB CCID, a port for emulating a smart card |
| CH552 | 0x10 | USB controller control |
| DEBUG | 0x20 | A USB HID special debug pipe. Useful for debug prints. |
| CDC | 0x40 | USB CDC-ACM, a serial port on the client. |
| FIDO | 0x80 | A USB FIDO security token device, useful for FIDO-type applications. |
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 |
| Payload | 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 bootstrap an 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: 3824 bytes.
- resetinfo: 256 bytes.
- rest is available for .data and .bss.
## Firmware behaviour
@ -61,7 +118,7 @@ application received from the client over the USB/UART.
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.
is also the CPU reset vector.
### Firmware state machine
@ -125,29 +182,30 @@ is received, when state is changed to "running".
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.
structures, enable the system calls, and finally start the app, which
ends the firmware state machine. Hardware guarantees that we leave
firmware mode automatically when the program counter leaves ROM.
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.
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.
### Golden path
Firmware loads the application at the start of RAM (`0x4000_0000`). It
uses the special FW\_RAM for its own stack.
When the firmware starts it clears all FW\_RAM, then sets up a stack
there before jumping to `main()`.
use a part of the special FW\_RAM for its own stack.
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().
begins in `start.S` by clearing all CPU registers, clears all FW\_RAM,
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 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.
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). It then waits for
data coming in through the UART.
Typical expected use scenario:
@ -176,32 +234,30 @@ Typical expected use scenario:
([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).
(USS) digest already received.
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.
7. The firmware now clears the part of the special `FW_RAM` where it
keeps it stack.
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
8. The interrupt handler for system calls is enabled.
9. Firmware starts the application by jumping to the contents of
`APP_ADDR`. Hardware automatically switches 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/)).
Then the firmware jumps to what is in `APP_ADDR` which starts the
application.
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
"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.
### User-supplied Secret (USS)
@ -225,61 +281,58 @@ implementation in the FPGA at this time.
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.
### Firmware 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);
```
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 upto 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).
To add or change syscalls, see the `syscall_handler()` in
`syscall_handler.c`.
Currently supported syscalls:
| *Name* | *Number* | *Argument* | *Description* |
|-------------|----------|------------|----------------------------------|
| RESET | 1 | Unused | Reset the TKey |
| SET\_LED | 10 | Colour | Set the colour of the status LED |
| GET\_VIDPID | 12 | Unused | Get Vendor and Product ID |
## 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 +340,28 @@ 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.
If you want debug prints to show up on the special TKey HID debug
endpoint instead, define `-DTKEY_DEBUG`.
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.
### Test firmware

View File

@ -0,0 +1,76 @@
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 \
-O2 \
-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)
AS = clang
ASFLAGS = \
-target riscv32-unknown-none-elf \
-march=rv32iczmmul \
-mabi=ilp32 \
-mno-relax
LDFLAGS = \
-T $(P)/app.lds \
-L $(LIBDIR) -lcommon
.PHONY: all
all: testapp.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)
TESTAPP_FMTFILES = \
$(P)/main.c \
$(P)/syscall.h
TESTAPP_OBJS = \
$(P)/main.o \
$(P)/crt0.o \
$(P)/syscall.o
testapp.elf: tkey-libs $(TESTAPP_OBJS)
$(CC) $(CFLAGS) $(TESTAPP_OBJS) $(LDFLAGS) -o $@
.PHONY: fmt
fmt:
clang-format --dry-run --ferror-limit=0 $(TESTAPP_FMTFILES)
clang-format --verbose -i $(TESTAPP_FMTFILES)
.PHONY: checkfmt
checkfmt:
clang-format --dry-run --ferror-limit=0 --Werror $(TESTAPP_FMTFILES)
.PHONY: clean
clean:
rm -f testapp.bin testapp.elf $(TESTAPP_OBJS)

View File

@ -0,0 +1,64 @@
/*
* SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
* 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 */
}

View File

@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
// 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

View File

@ -0,0 +1,231 @@
/*
* Copyright (C) 2022, 2023 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/io.h>
#include <tkey/led.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include "../tk1/proto.h"
#include "../tk1/syscall_num.h"
#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 *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");
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);
if (vidpid != 0x00010203) {
failmsg("Expected VID/PID to be 0x00010203");
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;
}
// 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 21 MHz, giving us timer in seconds
*timer_prescaler = 21 * 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 == '+') {
syscall(TK1_SYSCALL_RESET, 0);
}
write(IO_CDC, &in, 1);
}
}

View File

@ -0,0 +1,85 @@
// SPDX-FileCopyrightText: 2024 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#include "../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

View File

@ -0,0 +1,11 @@
// SPDX-FileCopyrightText: 2024 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#include <stdint.h>
#ifndef TKEY_APP_SYSCALL_H
#define TKEY_APP_SYSCALL_H
int syscall(uint32_t number, uint32_t arg1);
#endif

View File

@ -3,11 +3,16 @@
* SPDX-License-Identifier: GPL-2.0-only
*/
#include "../tk1/blake2s/blake2s.h"
#include "../tk1/lib.h"
#include <stddef.h>
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/io.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.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 +20,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 +76,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 +96,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 +122,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 +158,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 +205,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 +215,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 +226,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 +239,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 +251,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 +268,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);
}
}

View File

@ -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)

View File

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

View File

@ -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

View File

@ -6,8 +6,8 @@
// A simple blake2s Reference Implementation.
//======================================================================
#include "../types.h"
#include "../lib.h"
#include <stdint.h>
#include "blake2s.h"
// Dummy printf() for verbose mode

View File

@ -4,7 +4,8 @@
#ifndef BLAKE2S_H
#define BLAKE2S_H
#include "../types.h"
#include <stdint.h>
#include <stddef.h>
// state context
typedef struct {

View File

@ -6,9 +6,14 @@
OUTPUT_ARCH("riscv")
ENTRY(_start)
/* Define stack size */
STACK_SIZE = 0xEF0; /* 3824 B */
MEMORY
{
ROM (rx) : ORIGIN = 0x00000000, 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 */
}
@ -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 */
. = 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);

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -3,17 +3,21 @@
* SPDX-License-Identifier: GPL-2.0-only
*/
#include "../tk1_mem.h"
#include "assert.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/debug.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include "blake2s/blake2s.h"
#include "lib.h"
#include "proto.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 +25,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;
@ -37,7 +40,7 @@ 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
};
@ -61,26 +64,26 @@ static void scramble_ram(void);
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]);
}
htif_lf();
debug_lf();
}
htif_lf();
debug_lf();
}
static uint32_t rnd_word(void)
@ -152,20 +155,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 +177,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 +186,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 +195,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 +206,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 +222,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;
@ -238,9 +241,9 @@ static enum state initial_commands(const struct frame_header *hdr,
}
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 +255,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;
@ -279,9 +282,9 @@ static enum state loading_commands(const struct frame_header *hdr,
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
@ -294,8 +297,7 @@ static enum state loading_commands(const struct frame_header *hdr,
// 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;
@ -308,9 +310,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;
}
@ -325,17 +327,17 @@ static void run(const struct context *ctx)
// 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 +346,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(
@ -404,24 +403,18 @@ 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();
@ -432,18 +425,19 @@ int main(void)
for (;;) {
switch (state) {
case FW_STATE_INITIAL:
if (readcommand(&hdr, cmd, state, &mode,
&mode_bytes_left) == -1) {
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;
}
@ -458,9 +452,9 @@ int main(void)
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
}

View File

@ -0,0 +1,7 @@
# PicoRV32
## custom_ops.S
Custom PicoRV32 instructions are located in `custom_ops.S`.
`custom_ops.S` is imported from upstream PicoRV32 commit:
YosysHQ/picorv32@70f3c33

View File

@ -0,0 +1,102 @@
// 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)

View File

@ -3,27 +3,20 @@
* SPDX-License-Identifier: GPL-2.0-only
*/
#include "proto.h"
#include "../tk1_mem.h"
#include "assert.h"
#include "led.h"
#include "lib.h"
#include "state.h"
#include "types.h"
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/debug.h>
#include <tkey/io.h>
#include <tkey/led.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.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
#include "proto.h"
#include "state.h"
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 +25,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;
}
@ -86,6 +109,7 @@ 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)
uint8_t frame[1 + 128]; // Frame header + longest response
switch (rspcode) {
case FW_RSP_NAME_VERSION:
@ -109,94 +133,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

View File

@ -3,17 +3,12 @@
* SPDX-License-Identifier: GPL-2.0-only
*/
#include "types.h"
#include <stddef.h>
#include <stdint.h>
#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 +23,7 @@ enum cmdlen {
LEN_128
};
#define CMDLEN_MAXBYTES 128
#define CMDSIZE 128
// clang-format off
enum fwcmd {
@ -57,9 +52,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

View File

@ -1,11 +1,111 @@
/*
* Copyright (C) 2022, 2023 - Tillitis AB
* Copyright (C) 2022-2025 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
#include <tkey/tk1_mem.h>
#include "picorv32/custom_ops.S" // PicoRV32 custom instructions
#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
@ -39,17 +139,24 @@ _start:
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
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

View File

@ -0,0 +1,12 @@
#include "../tk1/picorv32/custom_ops.S"
.section ".text"
.globl syscall_enable
syscall_enable:
/* Enable syscall IRQ */
li t0, 0x7fffffff // IRQ31 mask
picorv32_maskirq_insn(zero, t0) // Enable IRQs
ret

View File

@ -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

View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2025 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/led.h>
#include "../tk1/syscall_num.h"
// clang-format off
static volatile uint32_t *system_reset = (volatile uint32_t *)TK1_MMIO_TK1_SYSTEM_RESET;
static volatile uint32_t *udi = (volatile uint32_t *)TK1_MMIO_TK1_UDI_FIRST;
// clang-format on
int32_t syscall_handler(uint32_t number, uint32_t arg1)
{
switch (number) {
case TK1_SYSCALL_RESET:
*system_reset = 1;
return 0;
case TK1_SYSCALL_SET_LED:
led_set(arg1);
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];
default:
assert(1 == 2);
}
assert(1 == 2);
return -1; // This should never run
}

View File

@ -0,0 +1,13 @@
// 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_SET_LED = 10,
TK1_SYSCALL_GET_VIDPID = 12,
};
#endif

View File

@ -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

View File

@ -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;
@ -171,7 +180,12 @@ module application_fpga (
.CATCH_MISALIGN (0),
.COMPRESSED_ISA (1),
.ENABLE_FAST_MUL (1),
.BARREL_SHIFTER (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),
@ -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;

View File

@ -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;
@ -182,7 +191,12 @@ module application_fpga_sim (
.CATCH_MISALIGN (0),
.COMPRESSED_ISA (1),
.ENABLE_FAST_MUL (1),
.BARREL_SHIFTER (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),
@ -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";

View File

@ -0,0 +1,26 @@
BSD 2-Clause License
Copyright 2022 Tillitis AB <tillitis.se>
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.

View File

@ -0,0 +1,24 @@
Copyright 2022 Tillitis AB <tillitis.se>
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.

View File

@ -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.

View File

@ -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 -O2 -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!"

View File

@ -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.

View File

@ -0,0 +1,147 @@
[![ci](https://github.com/tillitis/tkey-libs/actions/workflows/ci.yaml/badge.svg?branch=main&event=push)](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.

View File

@ -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 <tkey/led.h>`.
- 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.

View File

@ -0,0 +1,39 @@
# SPDX-FileCopyrightText: 2024 Tillitis AB <tillitis.se>
# SPDX-License-Identifier: BSD-2-Clause
version = 1
[[annotations]]
path = ".github/workflows/*"
SPDX-FileCopyrightText = "2022 Tillitis AB <tillitis.se>"
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 <tillitis.se>"
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"

View File

@ -0,0 +1,64 @@
/*
* SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
* 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 */
}

View File

@ -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
<http://creativecommons.org/publicdomain/zero/1.0/>

View File

@ -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

View File

@ -0,0 +1,354 @@
//======================================================================
//
// blake2s.c
// ---------
//
// A simple BLAKE2s reference implementation.
//
// See LICENSE for license terms.
// See README.md in the repo root for info about source code origin.
//======================================================================
#include <stdint.h>
#include "blake2s.h"
#define VERBOSE 0
#define SHOW_V 0
#define SHOW_M_WORDS 0
#if VERBOSE || SHOW_V || SHOW_M_WORDS
#include <stdio.h>
#endif
// Cyclic right rotation.
#ifndef ROTR32
#define ROTR32(x, y) (((x) >> (y)) ^ ((x) << (32 - (y))))
#endif
// Little-endian byte access.
#define B2S_GET32(p) \
(((uint32_t) ((uint8_t *) (p))[0]) ^ \
(((uint32_t) ((uint8_t *) (p))[1]) << 8) ^ \
(((uint32_t) ((uint8_t *) (p))[2]) << 16) ^ \
(((uint32_t) ((uint8_t *) (p))[3]) << 24))
// Initialization Vector.
static const uint32_t blake2s_iv[8] = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
};
#if VERBOSE || SHOW_V
//------------------------------------------------------------------
//------------------------------------------------------------------
void print_v(uint32_t *v) {
printf("0x%08x, 0x%08x, 0x%08x, 0x%08x\n", v[0], v[1], v[2], v[3]);
printf("0x%08x, 0x%08x, 0x%08x, 0x%08x\n", v[4], v[5], v[6], v[7]);
printf("0x%08x, 0x%08x, 0x%08x, 0x%08x\n", v[8], v[9], v[10], v[11]);
printf("0x%08x, 0x%08x, 0x%08x, 0x%08x\n", v[12], v[13], v[14], v[15]);
printf("\n");
}
//------------------------------------------------------------------
// print_ctx()
// Print the contents of the context data structure.
//------------------------------------------------------------------
void print_ctx(blake2s_ctx *ctx) {
printf("Chained state (h):\n");
printf("0x%08x, 0x%08x, 0x%08x, 0x%08x, ",
ctx->h[0], ctx->h[1], ctx->h[2], ctx->h[3]);
printf("0x%08x, 0x%08x, 0x%08x, 0x%08x",
ctx->h[4], ctx->h[5], ctx->h[6], ctx->h[7]);
printf("\n");
printf("Byte counter (t):\n");
printf("0x%08x, 0x%08x", ctx->t[0], ctx->t[1]);
printf("\n");
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
printf("G started.\n");
#endif
#if SHOW_V
printf("v before processing:\n");
print_v(&v[0]);
#endif
#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);
v[c] = v[c] + v[d];
v[b] = ROTR32(v[b] ^ v[c], 12);
v[a] = v[a] + v[b] + y;
v[d] = ROTR32(v[d] ^ v[a], 8);
v[c] = v[c] + v[d];
v[b] = ROTR32(v[b] ^ v[c], 7);
#if SHOW_V
printf("v after processing:\n");
print_v(&v[0]);
#endif
#if VERBOSE
printf("G completed.\n\n");
#endif
}
//------------------------------------------------------------------
// Compression function. "last" flag indicates last block.
//------------------------------------------------------------------
static void blake2s_compress(blake2s_ctx *ctx, int last)
{
const uint8_t sigma[10][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}
};
int i;
uint32_t v[16], m[16];
#if VERBOSE
printf("blake2s_compress started.\n");
#endif
// init work variables
for (i = 0; i < 8; i++) {
v[i] = ctx->h[i];
v[i + 8] = blake2s_iv[i];
}
// low 32 bits of offset
// high 32 bits
#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];
// last block flag set ?
if (last) {
v[14] = ~v[14];
}
// get little-endian words
for (i = 0; i < 16; i++) {
m[i] = B2S_GET32(&ctx->b[4 * i]);
}
#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
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
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
printf("Diagonal processing completed.\n");
printf("\n");
#endif
}
#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
printf("blake2s_compress completed.\n");
#endif
}
//------------------------------------------------------------------
// Initialize the hashing context "ctx" with optional key "key".
// 1 <= outlen <= 32 gives the digest size in bytes.
// Secret key (also <= 32 bytes) is optional (keylen = 0).
//------------------------------------------------------------------
int blake2s_init(blake2s_ctx *ctx, size_t outlen,
const void *key, size_t keylen) // (keylen=0: no key)
{
size_t i;
#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
for (i = 0; i < 8; i++) // state, "param block"
ctx->h[i] = blake2s_iv[i];
ctx->h[0] ^= 0x01010000 ^ (keylen << 8) ^ outlen;
ctx->t[0] = 0; // input count low word
ctx->t[1] = 0; // input count high word
ctx->c = 0; // pointer within buffer
ctx->outlen = outlen;
for (i = keylen; i < 64; i++) // zero input block
ctx->b[i] = 0;
if (keylen > 0) {
blake2s_update(ctx, key, keylen);
ctx->c = 64; // at the end
}
#if VERBOSE
printf("Context after blake2s_init processing:\n");
print_ctx(ctx);
printf("blake2s_init completed.\n");
#endif
return 0;
}
//------------------------------------------------------------------
// Add "inlen" bytes from "in" into the hash.
//------------------------------------------------------------------
void blake2s_update(blake2s_ctx *ctx,
const void *in, size_t inlen) // data bytes
{
size_t i;
#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 ?
ctx->t[0] += ctx->c; // add counters
if (ctx->t[0] < ctx->c) // carry overflow ?
ctx->t[1]++; // high word
blake2s_compress(ctx, 0); // compress (not last)
ctx->c = 0; // counter to zero
}
ctx->b[ctx->c++] = ((const uint8_t *) in)[i];
}
#if VERBOSE
printf("Context after blake2s_update processing:\n");
print_ctx(ctx);
printf("blake2s_update completed.\n");
#endif
}
//------------------------------------------------------------------
// Generate the message digest (size given in init).
// Result placed in "out".
//------------------------------------------------------------------
void blake2s_final(blake2s_ctx *ctx, void *out)
{
size_t i;
#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
// carry overflow
// high word
if (ctx->t[0] < ctx->c) {
ctx->t[1]++;
}
// fill up with zeros
// final block flag = 1
while (ctx->c < 64) {
ctx->b[ctx->c++] = 0;
}
blake2s_compress(ctx, 1);
// little endian convert and store
for (i = 0; i < ctx->outlen; i++) {
((uint8_t *) out)[i] =
(ctx->h[i >> 2] >> (8 * (i & 3))) & 0xFF;
}
#if VERBOSE
printf("Context after blake2s_final processing:\n");
print_ctx(ctx);
printf("blake2s_final completed.\n");
#endif
}
//------------------------------------------------------------------
// Convenience function for all-in-one computation.
//------------------------------------------------------------------
int blake2s(void *out, size_t outlen,
const void *key, size_t keylen,
const void *in, size_t inlen)
{
blake2s_ctx ctx;
if (blake2s_init(&ctx, outlen, key, keylen))
return -1;
blake2s_update(&ctx, in, inlen);
blake2s_final(&ctx, out);
return 0;
}
//======================================================================
//======================================================================

View File

@ -0,0 +1,45 @@
//======================================================================
//
// 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 <stdint.h>
#include <stddef.h>
// state context
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;
// Initialize the hashing context "ctx" with optional key "key".
// 1 <= outlen <= 32 gives the digest size in bytes.
// Secret key (also <= 32 bytes) is optional (keylen = 0).
int blake2s_init(blake2s_ctx *ctx, size_t outlen,
const void *key, size_t keylen); // secret key
// Add "inlen" bytes from "in" into the hash.
void blake2s_update(blake2s_ctx *ctx, // context
const void *in, size_t inlen); // data to be hashed
// Generate the message digest (size given in init).
// Result placed in "out".
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
#endif

View File

@ -0,0 +1,138 @@
//======================================================================
//
// blake2s_test.c
// --------------
//
//======================================================================
#include <stdio.h>
#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
//======================================================================

View File

@ -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)

View File

@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2023 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#include <stdint.h>
#include <tkey/led.h>
#include <tkey/tk1_mem.h>
#include <tkey/debug.h>
#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);
}
}

View File

@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#ifndef TKEY_ASSERT_H
#define TKEY_ASSERT_H
#include <tkey/io.h>
#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

View File

@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2023 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#ifndef TKEY_DEBUG_H
#define TKEY_DEBUG_H
#include <stdint.h>
#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

View File

@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2025 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#include <stddef.h>
#include <stdint.h>
#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

View File

@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#ifndef TKEY_LED_H
#define TKEY_LED_H
#include <stdint.h>
#include <tkey/tk1_mem.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
uint32_t led_get(void);
void led_set(uint32_t ledvalue);
void led_flash_forever(uint32_t ledvalue);
#endif

View File

@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#ifndef TKEY_LIB_H
#define TKEY_LIB_H
#include <stddef.h>
#include <stdint.h>
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

View File

@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#include <stddef.h>
#include <stdint.h>
#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

View File

@ -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 <tillitis.se>
* 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,16 +15,18 @@
#define TKEY_TK1_MEM_H
/*
The canonical location of this file is in:
https://github.com/tillitis/tkey-libs
Under:
include/tkey/tk1_mem.h
The contents are mostly derived from the Verilog code in
https://github.com/tillitis/tillitis-key1
/hw/application_fpga/fw/tk1_mem.h
The contents are derived from the Verilog code. For use by QEMU model,
firmware, and apps.
Memory map
Top level prefix, the first 2 bits in a 32-bit address:
@ -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

View File

@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: 2023 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#ifndef TKEY_TOUCH_H
#define TKEY_TOUCH_H
#include <stdbool.h>
#include <stdint.h>
#include <tkey/tk1_mem.h>
// 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

View File

@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#include <tkey/assert.h>
#include <tkey/io.h>
#include <tkey/lib.h>
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();
}

View File

@ -0,0 +1,382 @@
// SPDX-FileCopyrightText: 2025 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/debug.h>
#include <tkey/lib.h>
#include <tkey/proto.h>
#include <tkey/tk1_mem.h>
// 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);
}

View File

@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#include <stdint.h>
#include <tkey/led.h>
// 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;
}
}

View File

@ -0,0 +1,101 @@
// SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
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);
}

View File

@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#include <stddef.h>
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/debug.h>
#include <tkey/proto.h>
#include <tkey/tk1_mem.h>
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;
}

View File

@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: 2023 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#include <stdbool.h>
#include <tkey/debug.h>
#include <tkey/led.h>
#include <tkey/touch.h>
// 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;
}

View File

@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
// 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

View File

@ -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
<http://creativecommons.org/publicdomain/zero/1.0/>

View File

@ -0,0 +1,5 @@
# Monocypher
A ed25519 implementation from https://github.com/LoupVaillant/Monocypher
Small changes made for building.

View File

@ -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
// <https://creativecommons.org/publicdomain/zero/1.0/>
#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

View File

@ -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
// <https://creativecommons.org/publicdomain/zero/1.0/>
#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

File diff suppressed because it is too large Load Diff

View File

@ -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
// <https://creativecommons.org/publicdomain/zero/1.0/>
#ifndef MONOCYPHER_H
#define MONOCYPHER_H
#include <stddef.h>
#include <stdint.h>
#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

View File

@ -0,0 +1,93 @@
#!/bin/bash
# SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
# 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"

View File

@ -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.

View File

@ -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

View File

@ -0,0 +1,18 @@
MIT License
Copyright (c) <year> <copyright holders>
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.

View File

@ -1,37 +1,87 @@
TARGET = usb_device_cdc
#######################################################
CH554_SDCC=~/ch554_sdcc/
# 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

View File

@ -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

View File

@ -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
```

View File

@ -0,0 +1,12 @@
# SPDX-FileCopyrightText: 2025 Tillitis AB <tillitis.se>
# SPDX-License-Identifier: BSD-2-Clause
version = 1
[[annotations]]
path = [
".gitignore",
"Makefile",
"README.md",
]
SPDX-FileCopyrightText = "2022 Tillitis AB <tillitis.se>"
SPDX-License-Identifier = "GPL-2.0-only"

View File

@ -1,4 +1,7 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2021 Mullvad VPN AB <mullvad.se>
# SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
# 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,52 @@ 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": "MTA1-USB-V1",
"ManufDesc": "Tillitis",
"SerialDesc": "68de5d27-e223-4874-bc76-a54d6e84068f",
"CdcCtrlInterfaceDesc": "CDC-Ctrl",
"CdcDataInterfaceDesc": "CDC-Data",
"FidoInterfaceDesc": "FIDO",
"CcidInterfaceDesc": "CCID",
"DebugInterfaceDesc": "DEBUG"
}
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')
with open('inc/usb_strings.h', 'w') as f:
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')
f.write('#endif\n')

View File

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 1999 WCH <wch-ic.com>
// SPDX-FileCopyrightText: 2022 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: MIT
/*--------------------------------------------------------------------------
CH554.H
Header file for CH554 microcontrollers.

Some files were not shown because too many files have changed in this diff Show More