Compare commits

..

154 commits
cth-1 ... main

Author SHA1 Message Date
Jonas Thörnblad
a0f699aea5
Update USB product descriptor for Castor 2025-06-27 15:01:29 +02:00
Jonas Thörnblad
62adf4da71
Update USB VID and PID for Castor
VID has been wrong for some reason, therefore changed to the
correct value 0x1209.

New PID to differentiate Castor from Bellatrix.
2025-06-27 15:01:29 +02:00
Jonas Thörnblad
1a904e8857
Fix allowed_app_digest formatting
Fix formating of BLAKE2s digest of app allowed to start from
flash slot 0.
2025-06-27 15:01:29 +02:00
Jonas Thörnblad
03d96c3e96
tool: Fix b2s BLAKE2s digest zero padding 2025-06-24 18:18:17 +02:00
Jonas Thörnblad
c0b3c80620
Add make targets for building CH552 firmware with podman 2025-06-24 17:27:21 +02:00
Mikael Ågren
460d310c73
fw: Fix qemu_firmware build warnings 2025-06-19 08:50:13 +02:00
Michael Cardell Widerkrantz
f4f8c9e6c6
doc: Update Building & flashing docs
- Point out where to find tools.

- Add some description that we assume a Linux dist.

- Move the make targets around: make flash builds and flashes the
  entire thing so someone who just want to exactly that can use it
  right away.

- Explain what kind of hardware the USB controller is.

- Add a description on how to be able to use chprog without being
  root when running chprog.
2025-05-30 18:07:29 +02:00
Sasko Simonovski
ed9395c832
doc: Describe how to test alpha release in release notes
Adds information and link on how to test alpha release.
2025-05-28 11:56:22 +02:00
Mikael Ågren
888f18e5fe
doc: Refer to CH55x Reset Controller silk screen labels in flash docs 2025-05-26 07:59:50 +02:00
Michael Cardell Widerkrantz
24ef7b412b
doc: Add description on how to build and flash USB controller firmware 2025-05-23 15:43:44 +02:00
Michael Cardell Widerkrantz
f5d2cfef15
doc: Mention the tkeyimage tool in firmware README 2025-05-23 14:12:36 +02:00
Michael Cardell Widerkrantz
9a93da087d
doc: Document how to flash with filesystem 2025-05-23 14:12:36 +02:00
Michael Cardell Widerkrantz
916c37eab9
doc: Update release notes
Co-authored-by: Mikael Ågren <mikael@tillitis.se>
2025-05-23 14:05:58 +02:00
Michael Cardell Widerkrantz
29e5888482
doc: Remove old toolchain setup text
Use current information in Developer Handbook,
https://dev.tillitis.se/ instead of relying on this old description.
2025-05-23 14:05:58 +02:00
Mikael Ågren
e8acc7aee2
build: Flash partition table when running make prog_flash
Changes make targets:

- prog_flash in hw/application/Makefile which flashes only the
  bitstream is renamed to prog_flash_bs.

- prog_flash in hw/application/Makefile is modified to flash the
  bitstream, the testloadapp.bin in app slot 0, and the partition table.

- flash in contrib/Makefile is modified to use prog_flash from
  hw/application/Makefile
2025-05-22 15:24:45 +02:00
Mikael Ågren
4172db8dfb
build: Do not use sudo when running tillitis-iceprog 2025-05-22 15:06:39 +02:00
Mikael Ågren
7b1c1e5076
testapp: Update to 24 MHz clock 2025-05-22 09:31:54 +02:00
Michael Cardell Widerkrantz
1fec28ff0d
doc: Complete copyright and licenses
- Point out licensing terms in docs.
- Add missing SPDX tags
- Update the SPDX checker to check all the files we want to check.
- Include spdx-ensure in CI.
2025-05-22 09:31:54 +02:00
Michael Cardell Widerkrantz
8f9c706b9e
doc: Correct the GPL file
Import file

https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt

verbatim since it clearly says in the license that we are now allowed
to change it. I think it might have been a cut and paste from

https://www.gnu.org/licenses/old-licenses/gpl-2.0.html

before?
2025-05-22 09:31:54 +02:00
Mikael Ågren
07e487733b
build: Check app code formatting from application_fpga/Makefile 2025-05-22 09:31:54 +02:00
Michael Cardell Widerkrantz
ba17a2b29e
build: Use only one Makefile for apps
- Use one common Makefile for all test device apps.
- Use a single copy of syscall.[Sh].
- Update docs for building.
2025-05-22 09:31:50 +02:00
Michael Cardell Widerkrantz
6e3034c3ce
build: Move .clang-format to top level 2025-05-21 09:44:17 +02:00
Michael Cardell Widerkrantz
e302910f4d
Remove superfluous file __init__.py 2025-05-20 17:41:18 +02:00
Michael Cardell Widerkrantz
13641cb18b
build: Move test applications and the defaultapp
Instead of having the test apps under fw we create a new directory for
them.
2025-05-20 17:37:58 +02:00
Michael Cardell Widerkrantz
69940d2c64
doc: Link to firmware docs from TKey hardware design 2025-05-20 17:28:01 +02:00
Michael Cardell Widerkrantz
4ec58ce04c
tools: Prune and document tools
Add a tools/README.md.

Remove:

- create_flash_image.py: role now overtaken by tkeyimage which does
  more.
- reset-tk1: Leftover from when production_test was removed.

Rename:

- makehex.py: Moved up one level.
2025-05-20 17:12:46 +02:00
Michael Cardell Widerkrantz
a1f37d17c9
tool: Rename partition_table to tkeyimage 2025-05-20 13:50:55 +02:00
Michael Cardell Widerkrantz
fab126b695
tool: Add docs to partition_table
Co-authored-by: Mikael Ågren <mikael@tillitis.se>
2025-05-20 13:50:55 +02:00
Michael Cardell Widerkrantz
9a17aa6bdb
tool: Add SPDX tag to partition_table 2025-05-20 13:50:55 +02:00
Michael Cardell Widerkrantz
0d6e1d9ba5
fw: Add debug print when reading partition table fails 2025-05-20 13:50:55 +02:00
Michael Cardell Widerkrantz
ea29843037
tool: Make partition_table able to produce flash file
Usage:

  ./partition_table -o flash.bin -f -app0 path/to/app

Will produce a flash.bin that can be used for qemu.

- Adjust the size of the partition table.
- Refactor and rename genPartitionFile().
2025-05-20 13:50:51 +02:00
Mikael Ågren
6afdc114b8
Update binary hashes for bitstream 2025-05-20 11:59:44 +02:00
Mikael Ågren
2556f61f5a
fpga: Bump tk1 core version to 6 2025-05-20 11:27:07 +02:00
Mikael Ågren
b144cdfbdb
fpga: Use Castor specific VID/PID in UDI
Allows an app to determine which type of device it is running on.

- Reserve vendor ID 0x7357 for people using Unlocked.
- Use Castor product ID.
- Serial number is just nonsense, as before.
2025-05-20 11:25:54 +02:00
Michael Cardell Widerkrantz
8965fea947
Reset USB controller endpoints when starting
When starting, reset the USB controller to only enable the USB CDC
endpoint and the internal command channel. If the app resets firmware,
but had differend endpoints enabled, we want to go back to a known
state.
2025-05-16 17:09:13 +02:00
Mikael Ågren
daa7807c0f
Update binary hashes for bitstream & firmware 2025-05-15 16:15:14 +02:00
Mikael Ågren
53bc2d5fa0
fw: Update flash_write_data() to handle sizes larger than 4096 bytes 2025-05-15 16:13:30 +02:00
Mikael Ågren
5a9b77806f
fw: Return 0 on sys_alloc success, -1 on error
It is left to the app to keep track of whether it has had access to the
allocated area before.
2025-05-15 16:13:30 +02:00
Mikael Ågren
887883c8db
fw: Allow last storage area sector to be erased 2025-05-15 16:13:29 +02:00
Mikael Ågren
2dce9828ea
Update binary hashes for bitstream & firmware 2025-05-15 14:14:52 +02:00
Mikael Ågren
a2b77ec348
fw: Return reset() return value in TK1_SYSCALL_RESET 2025-05-15 14:14:39 +02:00
Mikael Ågren
9a3b4b9dca
fw: Use sizeof(resetinfo->app_digest) instead of hardcoded value 2025-05-15 14:14:38 +02:00
Michael Cardell Widerkrantz
48108cb3a2
fw: Build qemu_firmware with different linker script
The qemu_firmware is too large for the real hardware's 8k of ROM. The
emulator, however, has lots of ROM. Use a different linker script for
to reflect this.
2025-05-15 14:03:04 +02:00
Michael Cardell Widerkrantz
e935195846
fw: Add syscall TK1_SYSCALL_GET_APP_DATA
Add a new syscall to enable an app to get the data left for it by the
previous app in chain.

- Change testloadapp to leave some data for the next app to read.
- Call system call with:

  uint8_t next_app_data[RESET_DATA_SIZE];

  syscall(TK1_SYSCALL_GET_APP_DATA, (uint32_t)next_app_data, 0, 0);
2025-05-15 14:03:04 +02:00
Jonas Thörnblad
14e4cd09c9
ch552: Fix FIDO data copy
Fix potential out of bounds write.
2025-05-07 10:39:10 +02:00
Jonas Thörnblad
ec9ef31140
doc: Fix endpoint info 2025-05-07 10:22:29 +02:00
Michael Cardell Widerkrantz
6745c56851
Update binary hashes for bitstream & firmware 2025-05-06 17:52:14 +02:00
Michael Cardell Widerkrantz
fea9df790d
fw/docs: Correct documentation
Co-authored-by: Mikael Ågren <mikael@tillitis.se>
2025-05-06 17:52:10 +02:00
Michael Cardell Widerkrantz
8cf2cd08b7
fw/defaultapp: Introduce simple default app
To retain the default behaviour from Bellatrix, we introduce a simple
default app. If used on flash app slot 0 we get the same behaviour as
in Bellatrix, that is, waiting for an app from the client.
2025-05-06 17:52:09 +02:00
Michael Cardell Widerkrantz
d83d659284
fw: Remove use of timer in flash operations
Since we want to keep the user of the timer to the device apps, remove
the use of the timer for implementing a delay when writing to flash.
Let's try without any delay what so ever, just busylooping the query
to the chip.
2025-05-06 17:52:09 +02:00
Michael Cardell Widerkrantz
4f4de4a07d
fw: Harmonize comment style 2025-05-06 17:52:09 +02:00
Michael Cardell Widerkrantz
f373ad3f68
fw: Introduce reset()
- New function reset.c:reset(). Move code from syscall handler switch
  to this function.

- Rename resetinfo.h to reset.h.
2025-05-06 17:52:05 +02:00
Michael Cardell Widerkrantz
9d1bbffbaa
fw: Remove unneeded variable
Instead of assigning error to a variable, just include the function
returning the error in the if case.
2025-04-29 22:00:54 +02:00
Michael Cardell Widerkrantz
0692dddbae
fw: Simplify error return codes
Since callees doesn't differentiate between different errors, we have
no list of what different error codes mean, just return -1 on all
errors.
2025-04-29 22:00:51 +02:00
Mikael Ågren
15a350da1e
fw: Set LED colors
- Set LED color to white when firmware has initialized
- Set LED color to black when changing state to loading
- Set LED color to blue when starting testloadapp
- Update mgmt app allowed digest since testloadapp changed
2025-04-29 21:58:50 +02:00
Mikael Ågren
edbcdb111f
fw: Update default partition table 2025-04-29 21:58:50 +02:00
Michael Cardell Widerkrantz
3e8ff9671c
fw/tools: Change partition checksum to vanilla BLAKE2s
Instead of using 16 byte BLAKE2s with a dummy key, use plain vanilla
unkeyed 32 byte BLAKE2s for partition checksum.

Co-authored-by: Mikael Ågren <mikael@tillitis.se>
2025-04-29 21:58:50 +02:00
Michael Cardell Widerkrantz
66ea8df1d9
fw: Rename partition digest to checksum
- Rename functions, defines, et c to indicate that it's a checksum
  over the partition, not necessarily a cryptographic hash digest even
  though we use a version of BLAKE2s.

- Add comments describing where the checksum is stored and what it is
  used for.

Co-authored-by: Mikael Ågren <mikael@tillitis.se>
2025-04-29 21:54:06 +02:00
Michael Cardell Widerkrantz
106a7a5613
fw: Check flash app length to be within limits
Complain if the pre-loaded app on flash is larger than app RAM.

Co-authored-by: Mikael Ågren <mikael@tillitis.se>
2025-04-29 21:53:36 +02:00
Michael Cardell Widerkrantz
49d5a26a77
fw: Check syscall arg pointers to be in app RAM
When we pass pointers in system calls these pointers should point to
app RAM, not any other parts of the memory map, and especially not to
memory like FW_RAM that is only available in in a higher privilege
mode.

Co-authored-by: Mikael Ågren <mikael@tillitis.se>
2025-04-29 21:53:24 +02:00
Michael Cardell Widerkrantz
632b6d8fc7
fw: Limit flash offsets to be within sane limits
Limit flash offsets passed to syscalls. Be sure to check the limits
before doing any form of calculation with the passed values.

Co-authored-by: Mikael Ågren <mikael@tillitis.se>
2025-04-28 15:21:10 +02:00
Mikael Ågren
506b4c8269
doc: Add ERASE_DATA syscall 2025-04-24 16:03:21 +02:00
Mikael Ågren
9c1bb53d7a
fw: Add ERASE_DATA syscall
Erase one or more flash sectors in app storage areas
2025-04-24 16:03:20 +02:00
Michael Cardell Widerkrantz
a9d3dd7242
testapp: Use tkey-libs crt0 and linker script 2025-04-24 16:03:05 +02:00
Michael Cardell Widerkrantz
3be9e8ab19
doc: Update release notes with filesystem things 2025-04-24 16:03:05 +02:00
Michael Cardell Widerkrantz
d7ddae42d0
doc: Update firmware README
- Describe all the new functionality.
- Revise text.
2025-04-24 16:03:05 +02:00
Mikael Ågren
18773cdcf2
fw: Use globbing for FMTFILES 2025-04-24 16:03:04 +02:00
Michael Cardell Widerkrantz
25f3300964
fw: Change splint config
- Now uses at least some of the standard libraries like stdlib.h,
  stddef.h, et cetera.

- Include LIBDIR headers.

- Disregard some warnings globally.
2025-04-24 16:03:04 +02:00
Michael Cardell Widerkrantz
c1902c0955
fw: Rename FIRMWARE_SOURCES, use globbing
The symbol is only used for the check targets (with clangd and splint)
and doesn't include all the source files in the firmware. Let's just
use globbing instead.
2025-04-24 16:03:04 +02:00
Mikael Ågren
528f997681
tool: Add script to load pre-loaded app into flash 2025-04-24 16:03:03 +02:00
Michael Cardell Widerkrantz
73ea180b2a
tool: Add default_partition.bin
Add default partition table

Partition table built with `./partition_table/partition_table -o
default_partition.bin --app0 ../fw/testloadapp/testloadapp.bin`
2025-04-24 16:03:03 +02:00
Michael Cardell Widerkrantz
6324da2c90
tool: Introduce b2s tool to help compute BLAKE2s digests 2025-04-24 16:03:03 +02:00
Mikael Ågren
7511e98abe
tool: Add tool to inspect and create partition table binaries 2025-04-24 16:03:02 +02:00
Mikael Ågren
8c091d9719
tool: Add tool to create a flash image containing a preloaded app at slot 0 2025-04-24 16:03:02 +02:00
Michael Cardell Widerkrantz
ce97682758
testloadapp: Add app for testing preloaded app functionality
Co-authored-by: Mikael Ågren <mikael@tillitis.se>
2025-04-24 16:03:02 +02:00
Jonas Thörnblad
e37985938d
reset_test: Add resetinfo testapp
Co-authored-by: Mikael Ågren <mikael@tillitis.se>
Co-authored-by: Michael Cardell Widerkrantz <mc@tillitis.se>
2025-04-24 16:03:01 +02:00
Mikael Ågren
c5c6230664
fw: Replace custom picorv32 instructions when building for qemu 2025-04-24 16:03:01 +02:00
Mikael Ågren
49c06d78d1
testapp: Call storage syscalls
Calls
- TK1_SYSCALL_ALLOC_AREA
- TK1_SYSCALL_WRITE_DATA
- TK1_SYSCALL_READ_DATA
- TK1_SYSCALL_DEALLOC_AREA
2025-04-24 16:03:01 +02:00
Michael Cardell Widerkrantz
2c1c05f180
fw: Add pre loaded flash app and flash data storage
- Add per app flash storage
  - Adds four data areas. An app can allocate an area. Once allocated
    the area is tied to the CDI of the app and can only be
    read/written/deallocated by the same app.
- Add two pre loaded app slots to flash
  - Load an app from the first slot at boot. The app digest must match a
    specific digest specified in firmware.
  - Optionally load an app from the second slot
- Add a resetinfo area in FW_RAM which is used to signal an app's intent
  of resetting the system and, optionally, pass data to firmware or the
  next app in a bootchain.

Co-authored-by: Jonas Thörnblad <jonas@tillitis.se>
Co-authored-by: Mikael Ågren <mikael@tillitis.se>
Co-authored-by: Daniel Jobson <jobson@tillitis.se>
2025-04-24 16:02:34 +02:00
Michael Cardell Widerkrantz
4841b1b127
fw: Use BLAKE2s functions from tkey-libs
Instead of using the firmware's own copy of BLAKE2s functions, use the
functions from tkey-libs.
2025-04-24 09:10:55 +02:00
Michael Cardell Widerkrantz
1be5140850
tkey-libs: Optimize for size
Optimize tkey-libs for size to fit firmware in ROM
2025-04-24 09:10:55 +02:00
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
75b028505f 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
166 changed files with 14869 additions and 2831 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,29 @@ jobs:
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: check indentation in firmware C code
- name: check formatting on Verilog and C
working-directory: hw/application_fpga
run: |
make -C fw/tk1 checkfmt
make -C fw/testfw checkfmt
make checkfmt
- name: check for SPDX tags
run: ./LICENSES/spdx-ensure
check-firmware:
runs-on: ubuntu-latest
container:
image: ghcr.io/tillitis/tkey-builder:5rc1
steps:
- name: checkout
uses: actions/checkout@v4
with:
# fetch-depth: 0
persist-credentials: false
- name: fix
# https://github.com/actions/runner-images/issues/6775
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: run static analysis on firmware C code
working-directory: hw/application_fpga
@ -44,7 +62,7 @@ jobs:
check-verilog:
runs-on: ubuntu-latest
container:
image: ghcr.io/tillitis/tkey-builder:4
image: ghcr.io/tillitis/tkey-builder:5rc1
steps:
- name: checkout
uses: actions/checkout@v4
@ -64,7 +82,7 @@ jobs:
build-usb-firmware:
runs-on: ubuntu-latest
container:
image: ghcr.io/tillitis/tkey-builder:4
image: ghcr.io/tillitis/tkey-builder:5rc1
steps:
- name: checkout
uses: actions/checkout@v4
@ -86,7 +104,7 @@ jobs:
commit_sha: ${{ github.sha }}
runs-on: ubuntu-latest
container:
image: ghcr.io/tillitis/tkey-builder:4
image: ghcr.io/tillitis/tkey-builder:5rc1
steps:
- name: checkout
uses: actions/checkout@v4
@ -115,7 +133,7 @@ jobs:
needs: build-bitstream
runs-on: ubuntu-latest
container:
image: ghcr.io/tillitis/tkey-builder:4
image: ghcr.io/tillitis/tkey-builder:5rc1
steps:
- name: Checkout
uses: actions/checkout@v4
@ -133,8 +151,3 @@ jobs:
- name: check matching hashes for firmware.bin & application_fpga.bin
working-directory: hw/application_fpga
run: make check-binary-hashes
# TODO? first deal with hw/boards/ and hw/production_test/
# - name: check for SPDX tags
# run: ./LICENSES/spdx-ensure

9
.gitignore vendored
View file

@ -28,6 +28,12 @@
/testbench_verilator*
/check.smt2
/check.vcd
/hw/application_fpga/tkey-libs/libblake2s.a
/hw/application_fpga/tkey-libs/libcommon.a
/hw/application_fpga/tkey-libs/libcrt0.a
/hw/application_fpga/tkey-libs/libmonocypher.a
/hw/application_fpga/tools/tkeyimage/tkeyimage
/hw/application_fpga/tools/b2s/b2s
synth.json
synth.txt
synth.v
@ -40,6 +46,7 @@ verilated/
*.o
*.asc
*.bin
!/hw/application_fpga/tools/default_partition.bin
*.elf
*.map
*.tmp
@ -49,3 +56,5 @@ verilated/
.*.swp
*.sch-bak
*.sim
compile_commands.json
.cache

View file

@ -2,11 +2,13 @@
## Main license
Unless otherwise noted, the project sources are licensed under the
terms and conditions of the "GNU General Public License v2.0 only".
Unless otherwise noted, the project sources are copyright Tillitis AB,
but you can redistribute it and/or modify it under the terms of the
GNU General Public License version 2 as published by the Free Software
Foundation. See `gpl-2.0.txt`.
The `LICENSES/` directory contains copies of the license texts used by
the sources included in the project source tree.
This directory contains copies of the license texts used by the
sources included in the project source tree.
## SPDX
@ -19,3 +21,21 @@ The current set of valid, predefined SPDX identifiers can be found on
the SPDX License List at:
https://spdx.org/licenses/
## Notable imported projects
- ch552 firmware: `hw/usb_interface/ch552_fw/`
Originally by WCH under MIT. Much changed by Tillitis.
- picorv32: `hw/application_fpga/core/picorv32`
From https://github.com/YosysHQ/picorv32
ISC.
- PicoRV32 custom ops: `hw/application_fpga/fw/tk1/picorv32/`
- tkey-libs: `hw/application_fpga/tkey-libs/`
BSD2. From https://github.com/tillitis/tkey-libs

338
LICENSES/gpl-2.0.txt Normal file
View file

@ -0,0 +1,338 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
<https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <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, see <https://www.gnu.org/licenses/>.
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 Moe Ghoul>, 1 April 1989
Moe Ghoul, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View file

@ -1,245 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your freedom to share and
change it. By contrast, the GNU General Public License is intended to guarantee your
freedom to share and change free software--to make sure the software is free for all
its users. This General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to using it.
(Some other Free Software Foundation software is covered by the GNU Lesser General
Public License instead.) You can apply it to your programs, too.
When we speak of free software, we are referring to freedom, not price. Our General
Public Licenses are designed to make sure that you have the freedom to distribute
copies of free software (and charge for this service if you wish), that you receive
source code or can get it if you want it, that you can change the software or use
pieces of it in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid anyone to deny you
these rights or to ask you to surrender the rights. These restrictions translate to
certain responsibilities for you if you distribute copies of the software, or if you
modify it.
For example, if you distribute copies of such a program, whether gratis or for a
fee, you must give the recipients all the rights that you have. You must make sure
that they, too, receive or can get the source code. And you must show them these
terms so they know their rights.
We protect your rights with two steps: (1) copyright the software, and (2) offer you
this license which gives you legal permission to copy, distribute and/or modify the
software.
Also, for each author's protection and ours, we want to make certain that everyone
understands that there is no warranty for this free software. If the software is
modified by someone else and passed on, we want its recipients to know that what
they have is not the original, so that any problems introduced by others will not
reflect on the original authors' reputations.
Finally, any free program is threatened constantly by software patents. We wish to
avoid the danger that redistributors of a free program will individually obtain
patent licenses, in effect making the program proprietary. To prevent this, we have
made it clear that any patent must be licensed for everyone's free use or not
licensed at all.
The precise terms and conditions for copying, distribution and modification follow.
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains a notice placed
by the copyright holder saying it may be distributed under the terms of this General
Public License. The "Program", below, refers to any such program or work, and a
"work based on the Program" means either the Program or any derivative work under
copyright law: that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another language.
(Hereinafter, translation is included without limitation in the term
"modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not covered by this
License; they are outside its scope. The act of running the Program is not
restricted, and the output from the Program is covered only if its contents
constitute a work based on the Program (independent of having been made by running
the Program). Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and appropriately publish
on each copy an appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any warranty; and
give any other recipients of the Program a copy of this License along with the
Program.
You may charge a fee for the physical act of transferring a copy, and you may at
your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion of it, thus
forming a work based on the Program, and copy and distribute such modifications or
work under the terms of Section 1 above, provided that you also meet all of these
conditions:
a) You must cause the modified files to carry prominent notices stating that you
changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in whole or in
part contains or is derived from the Program or any part thereof, to be licensed
as a whole at no charge to all third parties under the terms of this License.
c) If the modified program normally reads commands interactively when run, you
must cause it, when started running for such interactive use in the most
ordinary way, to print or display an announcement including an appropriate
copyright notice and a notice that there is no warranty (or else, saying that
you provide a warranty) and that users may redistribute the program under these
conditions, and telling the user how to view a copy of this License. (Exception:
if the Program itself is interactive but does not normally print such an
announcement, your work based on the Program is not required to print an
announcement.)
These requirements apply to the modified work as a whole. If identifiable sections
of that work are not derived from the Program, and can be reasonably considered
independent and separate works in themselves, then this License, and its terms, do
not apply to those sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based on the
Program, the distribution of the whole must be on the terms of this License, whose
permissions for other licensees extend to the entire whole, and thus to each and
every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest your rights to
work written entirely by you; rather, the intent is to exercise the right to control
the distribution of derivative or collective works based on the Program.
In addition, mere aggregation of another work not based on the Program with the
Program (or with a work based on the Program) on a volume of a storage or
distribution medium does not bring the other work under the scope of this License.
3. You may copy and distribute the Program (or a work based on it, under Section 2)
in object code or executable form under the terms of Sections 1 and 2 above provided
that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable source code,
which must be distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three years, to give
any third party, for a charge no more than your cost of physically performing
source distribution, a complete machine-readable copy of the corresponding
source code, to be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer to distribute
corresponding source code. (This alternative is allowed only for noncommercial
distribution and only if you received the program in object code or executable
form with such an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for making
modifications to it. For an executable work, complete source code means all the
source code for all modules it contains, plus any associated interface definition
files, plus the scripts used to control compilation and installation of the
executable. However, as a special exception, the source code distributed need not
include anything that is normally distributed (in either source or binary form) with
the major components (compiler, kernel, and so on) of the operating system on which
the executable runs, unless that component itself accompanies the executable.
If distribution of executable or object code is made by offering access to copy from
a designated place, then offering equivalent access to copy the source code from the
same place counts as distribution of the source code, even though third parties are
not compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program except as
expressly provided under this License. Any attempt otherwise to copy, modify,
sublicense or distribute the Program is void, and will automatically terminate your
rights under this License. However, parties who have received copies, or rights,
from you under this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not signed it.
However, nothing else grants you permission to modify or distribute the Program or
its derivative works. These actions are prohibited by law if you do not accept this
License. Therefore, by modifying or distributing the Program (or any work based on
the Program), you indicate your acceptance of this License to do so, and all its
terms and conditions for copying, distributing or modifying the Program or works
based on it.
6. Each time you redistribute the Program (or any work based on the Program), the
recipient automatically receives a license from the original licensor to copy,
distribute or modify the Program subject to these terms and conditions. You may not
impose any further restrictions on the recipients' exercise of the rights granted
herein. You are not responsible for enforcing compliance by third parties to this
License.
7. If, as a consequence of a court judgment or allegation of patent infringement or
for any other reason (not limited to patent issues), conditions are imposed on you
(whether by court order, agreement or otherwise) that contradict the conditions of
this License, they do not excuse you from the conditions of this License. If you
cannot distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may not
distribute the Program at all. For example, if a patent license would not permit
royalty-free redistribution of the Program by all those who receive copies directly
or indirectly through you, then the only way you could satisfy both it and this
License would be to refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under any particular
circumstance, the balance of the section is intended to apply and the section as a
whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any patents or other
property right claims or to contest validity of any such claims; this section has
the sole purpose of protecting the integrity of the free software distribution
system, which is implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed through that system
in reliance on consistent application of that system; it is up to the author/donor
to decide if he or she is willing to distribute software through any other system
and a licensee cannot impose that choice.
This section is intended to make thoroughly clear what is believed to be a
consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in certain countries
either by patents or by copyrighted interfaces, the original copyright holder who
places the Program under this License may add an explicit geographical distribution
limitation excluding those countries, so that distribution is permitted only in or
among countries not thus excluded. In such case, this License incorporates the
limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions of the
General Public License from time to time. Such new versions will be similar in
spirit to the present version, but may differ in detail to address new problems or
concerns.
Each version is given a distinguishing version number. If the Program specifies a
version number of this License which applies to it and "any later version", you have
the option of following the terms and conditions either of that version or of any
later version published by the Free Software Foundation. If the Program does not
specify a version number of this License, you may choose any version ever published
by the Free Software Foundation.
10. If you wish to incorporate parts of the Program into other free programs whose
distribution conditions are different, write to the author to ask for permission.
For software which is copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our decision will be
guided by the two goals of preserving the free status of all derivatives of our free
software and of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE
PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN
WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS"
WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH
YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY
SERVICING, REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY
COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM
AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE
OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE
WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

View file

@ -16,34 +16,73 @@ LICENSES/
doc/
hw/application_fpga/core/picorv32/
hw/application_fpga/core/uart/
hw/application_fpga/fw/tk1/blake2s/
)
missingok_files=(
.editorconfig
.gitattributes
.gitignore
.clang-format
README.md
contrib/99-tillitis.rules
contrib/Dockerfile
contrib/Makefile
dco.md
contrib/verible.sha512
hw/application_fpga/README.md
hw/application_fpga/core/clk_reset_gen/README.md
hw/application_fpga/core/fw_ram/README.md
hw/application_fpga/core/ram/README.md
hw/application_fpga/core/rom/README.md
hw/application_fpga/core/tk1/tb/udi.hex
hw/application_fpga/core/uds/README.md
hw/application_fpga/fw/README.md
hw/application_fpga/fw/tk1/picorv32/README.md
hw/application_fpga/apps/Makefile
hw/application_fpga/apps/README.md
hw/application_fpga/application_fpga.bin.sha256
hw/application_fpga/config.vlt
hw/application_fpga/core/timer/README.md
hw/application_fpga/core/tk1/README.md
hw/application_fpga/core/touch_sense/README.md
hw/application_fpga/core/trng/README.md
hw/application_fpga/core/uds/README.txt
hw/application_fpga/data/udi.hex
hw/application_fpga/data/uds.hex
hw/application_fpga/firmware.bin.sha512
hw/application_fpga/fw/.clang-format
hw/application_fpga/fw/testfw/Makefile
hw/application_fpga/fw/tk1/Makefile
hw/application_fpga/tools/makehex/makehex.py
hw/application_fpga/tools/reset-tk1
hw/application_fpga/tools/tpt/README.md
hw/usb_interface/ch552_fw/.gitignore
hw/usb_interface/ch552_fw/LICENSES/GPL-2.0-only.txt
hw/usb_interface/ch552_fw/LICENSES/MIT.txt
hw/usb_interface/ch552_fw/Makefile
hw/usb_interface/ch552_fw/README.md
# tkey-libs is assumed to be REUSE compliant
hw/application_fpga/tkey-libs/LICENSE
hw/application_fpga/tkey-libs/LICENSES/BSD-2-Clause.txt
hw/application_fpga/tkey-libs/LICENSES/CC0-1.0.txt
hw/application_fpga/tkey-libs/Makefile
hw/application_fpga/tkey-libs/README-DIST.txt
hw/application_fpga/tkey-libs/README.md
hw/application_fpga/tkey-libs/RELEASE.md
hw/application_fpga/tkey-libs/blake2s/LICENSE
hw/application_fpga/tkey-libs/blake2s/Makefile
hw/application_fpga/tkey-libs/blake2s/blake2s.c
hw/application_fpga/tkey-libs/blake2s/blake2s.h
hw/application_fpga/tkey-libs/blake2s/blake2s_test.c
hw/application_fpga/tkey-libs/example-app/Makefile
hw/application_fpga/tkey-libs/monocypher/LICENSE
hw/application_fpga/tkey-libs/monocypher/README.md
hw/application_fpga/tools/README.md
hw/application_fpga/tools/b2s/README.md
hw/application_fpga/tools/b2s/go.mod
hw/application_fpga/tools/b2s/go.sum
hw/application_fpga/tools/default_partition.bin
hw/application_fpga/tools/tkeyimage/README.md
hw/application_fpga/tools/tkeyimage/go.mod
hw/application_fpga/tools/tkeyimage/go.sum
)
is_missingok() {

158
README.md
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
@ -37,9 +40,17 @@ software and hardware should be.
## Licensing
Unless otherwise noted, the project sources are copyright Tillitis AB.
but you can redistribute it and/or modify it under the terms of the
GNU General Public License version 2 as published by the Free Software
Foundation.
See [LICENSES](./LICENSES/README.md) for more information about
the projects' licenses.
Each imported project is typically kept in its own directory with its
own LICENSE file.
## Repositories
This repository contains the FPGA design, the source of the
@ -56,17 +67,138 @@ releases.
The TKey PCB [KiCad](https://www.kicad.org/) design files are kept in
a separate repository:
https://github.com/tillitis/tk1-pcba
<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 & flashing
These instructions assume you're using a Linux distribution. Most of
them also assume you're using our OCI image
[tkey-builder](https://ghcr.io/tillitis/tkey-builder). If you want to
run native tools, look in `contrib/Dockerfile` and
`contrib/buildtools.sh` for the tools and versions to use.
### FPGA
You need a [TKey
Unlocked](https://shop.tillitis.se/products/tkey-not-provisioned), a
[the TP1 TKey Programmer
board](https://shop.tillitis.se/products/tkey-dev-kit), and probably a
[Blinkinlabs CH55x Reset
Controller](https://shop-nl.blinkinlabs.com/products/ch55x-reset-controller)
to use this on real hardware.
Building is probably easiest using make and Podman.
To build everything and then flash the resulting bitstream with the
testloadapp in app slot 0 and the partition table copies in one go,
place the TKey Unlocked in the TP1, then do:
```
cd contrib
make flash
```
This uses the make target `prog_flash` in
`hw/application_fpga/Makefile` behind the scenes, but mounts your TP1
device into the container.
To see all targets:
```
cd contrib
make
```
See the [Tillitis Developer Handbook](https://dev.tillitis.se) for
more.
### USB Controller
The TKey uses a WCH CH552 chip as a USB controller. It has its own
firmware.
Build:
```
cd contrib
make run
cd hw/usb_interface/ch552_fw
make
```
To flash the controller with new firmware you need hardware like the
[Blinkinlabs CH55x Reset
Controller](https://shop-nl.blinkinlabs.com/products/ch55x-reset-controller)
and a USB-A to USB-C converter.
[Reset Controller source](https://github.com/Blinkinlabs/ch55x_programmer).
You also need [chprog](https://github.com/ole00/chprog).
The bootloader identifies itself as USB VID 4348, PID 55e0. To be able
to access it and run `chprog` without root you need to allow your user
to access it. Place `contrib/99-tillitis.rules` in `/etc/udev/rules.d`
and run `udevadm control --reload`. Now you can add your user to the
`dialout` group and access it.
1. Connect the Reset Controller to your computer through "DUT\_IN"/"PC".
2. Connect the TKey to "DUT\_OUT"/"DUT".
3. Press the "Bootloader" button.
4. Run `make flash_patched` in `hw/usb_interface/ch552_fw` outside of
a container.
## Updating and working with tkey-libs
A copy of [tkey-libs](https://github.com/tillitis/tkey-libs) is kept
in `hw/application_fpga/tkey-libs`. This is mostly to avoid the
subtleties of Git submodules.
If you want to change something in tkey-libs, always change in the
upstream library at:
https://github.com/tillitis/tkey-libs
You can build with an out-of-tree copy if you set `LIBDIR`, for
example:
```
make LIBDIR=~/git/tkey-libs firmware.elf
```
When it's time to update the in-tree tkey-lib first tag the upstream
repo with an `fw` prefix, like `fw-1` even if it already has an
official version tag.
Easiest is probably to just remove the tkey-libs directory and then
git clone the desired tag. Use the entire repo, but remove the .-files
like `.git`, `.github`, et cetera. Something like:
```
$ rm -rf tkey-libs
$ git clone git@github.com:tillitis/tkey-libs.git
$ cd tkey-libs
$ git checkout fw-3
```
Note that you need to change the optimization flag in the tkey-libs'
Makefile to `-Os`.
## Measured boot
The key behind guaranteeing security even as a general computer is the
@ -104,3 +236,15 @@ deterministically generate any cryptographic keys it needs.
The TKey unconditional measured boot is inspired by, but not exactly
the same as part of [TCG
DICE](https://trustedcomputinggroup.org/work-groups/dice-architectures/).
# Current Work in Progress in this repository
We are updating the FPGA and firmware on TKey as part of the Castor
release. This update will simplify 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,16 +5,18 @@
BUILDIMAGE=tkey-builder-local
# default image used when running a container
IMAGE=ghcr.io/tillitis/tkey-builder:4
IMAGE=ghcr.io/tillitis/tkey-builder:5rc1
all:
@echo "Targets:"
@echo "run Run a shell using image '$(IMAGE)' (Podman)"
@echo "run-make Build the FPGA bitstream using image '$(IMAGE)' (Podman)"
@echo "run-make-ch552 Build the CH552 firmware using image '$(IMAGE)' (Podman)"
@echo "run-tb Run all the testbenches using image '$(IMAGE)' (Podman)"
@echo "run-make-no-clean Like run-make but without cleaning first, useful for iterative firmware dev"
@echo "run-make-ch552-no-clean Like run-make-ch552 but without cleaning first, useful for iterative firmware dev"
@echo "run-make-clean_fw Like run-make but cleans only firmware"
@echo "flash Program the SPI flash on the TKey - needs an existing bitstream"
@echo "flash Build bitstream and testloadapp.bin then program them and the partition table onto the TKey SPI flash"
@echo "pull Pull down the image '$(IMAGE)' (Podman)"
@echo "build-image Build a toolchain image named '$(BUILDIMAGE)' (Podman)"
@echo " A newly built image can be used like: make IMAGE=$(BUILDIMAGE) run"
@ -23,16 +25,20 @@ 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 \
$(IMAGE) make clean application_fpga.bin
run-make-ch552:
podman run --rm --mount type=bind,source="`pwd`/../hw/usb_interface/ch552_fw",target=/build -w /build -it \
$(IMAGE) make clean usb_device.bin
run-tb:
podman run --rm --mount type=bind,source="`pwd`/../hw/application_fpga",target=/build -w /build -it \
$(IMAGE) make clean_tb tb
@ -45,6 +51,10 @@ run-make-no-clean:
podman run --rm --mount type=bind,source="`pwd`/../hw/application_fpga",target=/build -w /build -it \
$(IMAGE) make application_fpga.bin
run-make-ch552-no-clean:
podman run --rm --mount type=bind,source="`pwd`/../hw/usb_interface/ch552_fw",target=/build -w /build -it \
$(IMAGE) make usb_device.bin
run-make-clean_fw:
podman run --rm --mount type=bind,source="`pwd`/../hw/application_fpga",target=/build -w /build -it \
$(IMAGE) make clean_fw application_fpga.bin
@ -54,7 +64,7 @@ flash:
--device /dev/bus/usb/$(lsusb | grep -m 1 1209:8886 | awk '{ printf "%s/%s", $2, substr($4,1,3) }') \
--mount type=bind,source="`pwd`/../hw/application_fpga",target=/build \
-w /build \
-it $(IMAGE) tillitis-iceprog /build/application_fpga.bin
-it $(IMAGE) make prog_flash
pull:

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,205 @@
Descriptions of the tagged TKey releases.
## Upcoming release: Castor
Overview of changes since [TK1-24.03
Bellatrix](https://github.com/tillitis/tillitis-key1/releases/tag/TK1-24.03)
for the Castor milestone so far.
Main branch in the repository contains Castor design and specifically
the tag `TK1-Castor-alpha-1` reflects the upcoming release.
**Note well**: BREAKING CHANGE! Older device apps WILL NOT WORK.
The introduction of the USB Mode Protocol between the programs running
on the PicoRV32 CPU and the CH552 means that device apps that have not
been changed to use the protocol will not have any way to communicate
with the outside world.
### How to test TK1-Castor-alpha
To test this alpha release you will need:
- Tkey Unlocked
- TKey Programmer Board
- CH55x Reset Controller from [Blinkinlabs](https://shop-nl.blinkinlabs.com/products/ch55x-reset-controller)
Read
[here](https://github.com/tillitis/tillitis-key1/blob/main/README.md#building--flashing)
for instructions.
### General
- Split repo:
- tk1, mta1-usb-dev, mta-usb-v1 and mta1-library moves to
<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
- Make Security Monitor memory access checks more complete.
- Add SPI main controller mainly to access the flash chip.
- Add system reset API. Device apps can reset the FPGA and restart
the firmware.
- Increase clock frequence to 24 MHz.
- Increase UART baudrate to 500,000.
- Fix UART baudrate counter issues noticable at higher baudrates.
- Fix missing clock cycles in timer core.
- Remove the UART runtime configuration API.
- Several minor clean ups of design and testbench.
- Add hardware clear to send (CTS) signals for communication between
UART and CH552.
- Increase size of ROM and FW\_RAM.
- Make ROM non-executable in app mode.
- Remove MMIO address for access to the firmware blake2s() function
from apps.
- Automatically leave firmware mode when execution leaves ROM and
remove the now unnecessary APP\_MODE\_CTRL register.
- Change UDS read protection: When execution leaves ROM the first
time, UDS is hardware protected from reads. The already existing
protection that UDS is protected after the first read is also still
available.
- Introduce interrupt handler for hardware-based privilege raising and
automatically privelege lowering for system calls.
### Firmware
- At startup, fill RAM with random data using the xorwow PRNG, seeded
by TRNG.
- Add support for the new USB Mode Protocol to communicate with
different USB endpoints in the USB controller.
- Support a filesystem on flash: There's space for two pre-loaded
apps and four storage areas for device apps.
A typical use is that app slot 0 will contain a loader app for
verified boot and app slot 1 contains the app to be verified.
- Automatically start an app in flash app slot 0 after power cycle and
when instructed to by reset intentions.
The automatically started app is trusted by the firmware by
including an app digest in the firmware ROM. This means we extend
the user's trust in the firmware to the first app, but only if it's
measured to the correct digest by the firmware. Anything else is a
hard error which halts the CPU.
- Support chaining of apps through soft resets, including support for
verifying that the next app is the expected one (exact measured
digest the previous app expected), and leaving data for the next app
to use.
- Add a system call mechanism and system calls. See [firmware's
README](../hw/application_fpga/fw/README.md) for documentation, but
its probably easier to use the the syscall wrappers in libsyscall in
[tkey-libs](https://github.com/tillitis/tkey-libs) if you're writing
in C.
- Harmonize with [tkey-libs](https://github.com/tillitis/tkey-libs).
Import tkey-libs to this repo for convenience.
- Rewrite test firmware to work with the new leaving ROM-scenario.
Introduce a separate `testapp` for the app mode parts.
### Device apps
Introduce some device apps mostly for testing.
- `reset_test`: Test the different types of soft reset.
- `testapp`: Tests in app mode that used to live in `testfw`.
- `testloadapp`: A simple loader app for management and verification
of a second app.
- `defaultapp`: An app that immediately resets the TKey to load an app
from the client, just like earlier releases.
### CH552 firmware
- Use the new CTS signals for communication over the UART.
- Add support for two HID endpoints (security token and our debug
HID).
- Add support for CCID endpoint.
- Add a protocol to communicate with the different endpoints: CDC,
CCID, FIDO, debug.
- Change USB frame sending from a software timer to instead be
controlled by the USB Controller Protocol.
Note that to update the CH552 firmware you will need something like
the Blinkinlabs CH55x Reset Controller:
<https://shop-nl.blinkinlabs.com/products/ch55x-reset-controller>
<https://github.com/Blinkinlabs/ch55x_programmer>
### Tooling
- Add tools to parse and generate partition tables and flash images.
- Add tool to compute a print a BLAKE2s digest, optionally as C code.
### tkey-builder
- New versions of:
- clang (18.1.3, part of ubuntu 24.04)
- icestorm (commit 738af822905fdcf0466e9dd784b9ae4b0b34987f
)
- yosys (0.45)
- nextpnr (0.7) + extra patches for RNG seed handling and early exit
- iverilog (v12)
- verilator (v5.028)
- verible (v0.0-3795)
- cocotb (v1.9.1)
- Remove TKey Programmer (TP) toolchain:
- gcc-arm-none-eabi: Used for the TKey Programmer firmware, now
moved to it's own repo.
- libnewlib-arm-none-eabi
- libstdc++-arm-none-eabi-newlib
- pico-sdk
TP1 is now in <https://github.com/tillitis/tp1>
- Remove Go compiler support.
- Introduce buildtools.sh for building upstream tools for inclusion
in the image.
### Docs
- All docs now in READMEs close to the design or code.
- Protocol docs moved to [the Developer
Handbook](https://dev.tillitis.se/)
[repo](https://github.com/tillitis/dev-tillitis)
## TK1-24.03
@ -19,6 +218,7 @@ the following digest:
```
### FPGA
- Security Monitor now prevents access to RAM outside of the physical
memory. If it detects an access outside of the RAM address space, it
will halt the CPU.
@ -30,6 +230,7 @@ the following digest:
- Complete testbenches and add 9 tests for the FPGA cores.
### Firmware
- Protect zeroisation against compiler optimisation by using
secure_wipe(), fixing a memset() that was removed during
compilation.
@ -44,31 +245,35 @@ the following digest:
- Fix warnings from splint.
### TP1
- New plastic clip o and update of BOM.
- Build TP1 firmware in CI.
### CH552
- Fixed a bug where a byte of data could in some rare circumstances be
dropped, causing a client app to hang.
- General clean-up of code, translated all comments to English.
### TK1
- New injection moulded plastic case
### tkey-builder
- Updated to version 3. Bumping Ubuntu to 23.10, Yosys to 0.36 and
nextpnr to 0.6.
- Updated to version 4. Bumping pico-sdk to 1.5.1, adding clang-tidy
and splint.
### Docs
- Fixing broken links, cleaning up docs and READMEs.
- Clarify warm boot attack mitigations and scope for Bellatrix in
threat model.
For full change log [see](https://github.com/tillitis/tillitis-key1/compare/TK1-23.03.2...TK1-24.03)
## TK1-23.03.2
This is the official release of the "Bellatrix" version of the
@ -98,8 +303,8 @@ This bug fix release contains the following changes:
- Change the firmware protocol max frame size back to 128 bytes
- Correct a bug with the reading out of UDS
## TK1-23.03
This is the official release of the "Bellatrix" version of
the Tillitis TKey device. This version is ready for general
use.
@ -113,7 +318,6 @@ shasum -a256 application_fpga.bin
f11d6b0f57c5405598206dcfea284008413391a2c51f124a2e2ae8600cb78f0b application_fpga.bin
```
### New and improved functionality
- (ALL) The TKey HW design, FW, protocol and first applications has
@ -177,10 +381,9 @@ f11d6b0f57c5405598206dcfea284008413391a2c51f124a2e2ae8600cb78f0b application_fp
- (TOOLS) There is now a version of iceprog able to write to the FPGA
bitstream to the NVCM and lock the NVCM from external access
### Bugs fixed
- No known bugs have been fixed. Numerous issues has been closed.
- No known bugs have been fixed. Numerous issues has been closed.
### Limitations
@ -188,7 +391,6 @@ f11d6b0f57c5405598206dcfea284008413391a2c51f124a2e2ae8600cb78f0b application_fp
cryptographically secure. It his however randomized every time
a TKey device is powered up.
## engineering-release-2
### New and improved functionality

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

@ -1,149 +0,0 @@
# Toolchain setup
**NOTE:** Documentation migrated to dev.tillitis.se, this is kept for
history. This is likely to be outdated.
Here are instructions for setting up the tools required to build the
project. Tested on Ubuntu 22.10.
## General development environment
The following is intended to be a complete list of the packages that
are required for doing all of the following:
- building and developing [TKey device and client
apps](https://github.com/tillitis/tillitis-key1-apps)
- building our [QEMU machine](https://github.com/tillitis/qemu/tree/tk1)
(useful for apps dev)
- building and developing firmware and FPGA gateware (which also
requires building the toolchain below)
```
sudo apt install build-essential clang lld llvm bison flex libreadline-dev \
gawk tcl-dev libffi-dev git mercurial graphviz \
xdot pkg-config python3 libftdi-dev \
python3-dev libeigen3-dev \
libboost-dev libboost-filesystem-dev \
libboost-thread-dev libboost-program-options-dev \
libboost-iostreams-dev cmake libusb-1.0-0-dev \
ninja-build libglib2.0-dev libpixman-1-dev \
golang clang-format \
gcc-arm-none-eabi libnewlib-arm-none-eabi \
libstdc++-arm-none-eabi-newlib
```
## Device permissions
To allow sudo-less programming, you can install a udev rule that will
assign the tkey programmer, and also an unprogrammed CH552, to the
dialout group. You will also need to add your user to this group:
```
sudo cp contrib/99-tillitis.rules /etc/udev/rules.d
sudo udevadm control --reload-rules
sudo usermod -aG dialout ${USER}
```
To apply the new group, log out and then log back in.
You can check the device permissions to determine if the group was
successfully applied. First, use lsusb to find the location of the
programmer:
```
lsusb -d 1209:8886
Bus 001 Device 023: ID 1209:8886 Generic TP-1
```
Then, you can check the permissions by using the bus and device
number reported above. Note that this pair is ephemeral and may
change after every device insertion:
```
ls -l /dev/bus/usb/001/023
crw-rw---- 1 root dialout 189, 22 Feb 16 14:58 /dev/bus/usb/001/023
```
## Gateware: Yosys/Icestorm toolchain
If the LED of your TKey is steady white when you plug it, then the
firmware is running and it's already usable! If you want to develop
TKey apps, then only the above general development environment is
needed.
Compiling and installing Yosys and friends is only needed if your TKey
is not already running the required firmware and FPGA gateware, or if
you want to do development on these components.
These steps are used to build and install the
[icestorm](http://bygone.clairexen.net/icestorm/) toolchain. The
binaries are installed in `/usr/local`. Note that if you have or
install other versions of these tools locally, they could conflict
(case in point: `yosys` installed on MacOS using brew).
git clone https://github.com/YosysHQ/icestorm
cd icestorm
git checkout d20a5e9001f46262bf0cef220f1a6943946e421d
make -j$(nproc)
sudo make install
cd ..
# Custom iceprog for the RPi 2040-based programmer (will be upstreamed).
# Note: install dependencies for building tillitis-iceprog on Ubuntu:
# sudo apt install libftdi-dev libusb-1.0-0-dev
git clone -b interfaces https://github.com/tillitis/icestorm tillitis--icestorm
cd tillitis--icestorm/iceprog
make
sudo make PROGRAM_PREFIX=tillitis- install
cd ../..
git clone https://github.com/YosysHQ/yosys
cd yosys
git checkout yosys-0.26
make -j$(nproc)
sudo make install
cd ..
git clone https://github.com/YosysHQ/nextpnr
cd nextpnr
git checkout nextpnr-0.5
cmake -DARCH=ice40 -DCMAKE_INSTALL_PREFIX=/usr/local .
make -j$(nproc)
sudo make install
cd ..
References:
* http://bygone.clairexen.net/icestorm/
## Firmware: riscv toolchain
The TKey implements a [picorv32](https://github.com/YosysHQ/picorv32)
soft core CPU, which is a RISC-V microcontroller with the C
instructions and Zmmul extension, multiply without divide
(RV32ICZmmul). You can read
[more](https://www.sifive.com/blog/all-aboard-part-1-compiler-args)
about it.
The project uses the LLVM/Clang suite and version 15 or later is
required. As of writing Ubuntu 22.10 has version 15 packaged. You may
be able to get it installed on older Ubuntu and Debian using the
instructions on https://apt.llvm.org/ . There are also binary releases
here: https://github.com/llvm/llvm-project/releases
References:
* https://github.com/YosysHQ/picorv32
If your available `objcopy` and `size` commands is anything other than
the default `llvm-objcopy` and `llvm-size` define `OBJCOPY` and `SIZE`
to whatever they're called on your system before calling `make`.
## CH552 USB to Serial firmware
The USB to Serial firmware runs on the CH552 microcontroller, and
provides a USB CDC profile which should work with the default drivers
on all major operating systems. MTA1-USB-V1 and TK-1 devices come
with the CH552 microcontroller pre-programmed.
Toolchain setup and build instructions for this firmware are detailed
in the
[ch552_fw directory](../hw/usb_interface/ch552_fw/README.md)

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 \
@ -58,8 +60,12 @@ CFLAGS = \
-Wall \
-Wpedantic \
-Wno-language-extension-token \
-Wextra \
-flto \
-g
-g \
-I $(LIBDIR)/include \
-I $(LIBDIR) \
-I $(LIBDIR)/blake2s
AS = clang
@ -67,7 +73,8 @@ ASFLAGS = \
-target riscv32-unknown-none-elf \
-march=rv32iczmmul \
-mabi=ilp32 \
-mno-relax
-mno-relax \
-I $(LIBDIR)/include
ICE40_SIM_CELLS = $(shell yosys-config --datdir/ice40/cells_sim.v)
@ -113,36 +120,30 @@ PICORV32_SRCS = \
$(P)/core/picorv32/rtl/picorv32.v
FIRMWARE_DEPS = \
$(P)/fw/tk1_mem.h \
$(P)/fw/tk1/types.h \
$(P)/fw/tk1/lib.h \
$(P)/fw/tk1/proto.h \
$(P)/fw/tk1/assert.h \
$(P)/fw/tk1/led.h
$(P)/fw/tk1/proto.h
FIRMWARE_OBJS = \
$(P)/fw/tk1/main.o \
$(P)/fw/tk1/start.o \
$(P)/fw/tk1/proto.o \
$(P)/fw/tk1/lib.o \
$(P)/fw/tk1/assert.o \
$(P)/fw/tk1/led.o \
$(P)/fw/tk1/blake2s/blake2s.o
$(P)/fw/tk1/syscall_enable.o \
$(P)/fw/tk1/syscall_handler.o \
$(P)/fw/tk1/spi.o \
$(P)/fw/tk1/flash.o \
$(P)/fw/tk1/storage.o \
$(P)/fw/tk1/partition_table.o \
$(P)/fw/tk1/auth_app.o \
$(P)/fw/tk1/rng.o \
$(P)/fw/tk1/reset.o \
$(P)/fw/tk1/preload_app.o \
$(P)/fw/tk1/mgmt_app.o
FIRMWARE_SOURCES = \
$(P)/fw/tk1/main.c \
$(P)/fw/tk1/proto.c \
$(P)/fw/tk1/lib.c \
$(P)/fw/tk1/assert.c \
$(P)/fw/tk1/led.c \
$(P)/fw/tk1/blake2s/blake2s.c
CHECK_SOURCES = \
$(P)/fw/tk1/*.[ch]
TESTFW_OBJS = \
$(P)/fw/testfw/main.o \
$(P)/fw/testfw/start.o \
$(P)/fw/tk1/proto.o \
$(P)/fw/tk1/lib.o \
$(P)/fw/tk1/blake2s/blake2s.o
$(P)/fw/testfw/start.o
#-------------------------------------------------------------------
# All: Complete build of HW and FW.
@ -155,7 +156,10 @@ all: application_fpga.bin
# incorrect BRAM_FW_SIZE
# -------------------------------------------------------------------
%_size_mismatch: %.elf phony_explicit
@test $$($(SIZE) $< | awk 'NR==2{print $$4}') -le $$(( 32 / 8 * $(BRAM_FW_SIZE) )) \
@test $$(( \
$$($(SIZE) -A $< | grep text | awk 'NR==1{print $$2}') + \
$$($(SIZE) -A $< | grep text | awk 'NR==2{print $$2}') \
)) -le $$(( 32 / 8 * $(BRAM_FW_SIZE) )) \
|| { printf "The 'BRAM_FW_SIZE' variable needs to be increased\n"; \
[[ $< =~ testfw ]] && printf "Note that testfw fits if built with -Os\n"; \
false; }
@ -176,21 +180,39 @@ secret:
# Firmware generation.
# Included in the bitstream.
#-------------------------------------------------------------------
LDFLAGS = -T $(P)/fw/tk1/firmware.lds
LDFLAGS = \
-T $(P)/fw/tk1/firmware.lds \
-Wl,--cref,-M \
-L $(LIBDIR) -lcommon -lblake2s
QEMU_LDFLAGS = \
-T $(P)/fw/tk1/qemu_firmware.lds \
-Wl,--cref,-M \
-L $(LIBDIR) -lcommon -lblake2s
# Common libraries the firmware and testfw depend on. See
# https://github.com/tillitis/tkey-libs/
.PHONY: tkey-libs
tkey-libs:
make -C $(LIBDIR)
$(FIRMWARE_OBJS): $(FIRMWARE_DEPS)
$(TESTFW_OBJS): $(FIRMWARE_DEPS)
firmware.elf: $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds
$(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@
#firmware.elf: CFLAGS += -DTKEY_DEBUG
firmware.elf: tkey-libs $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds
$(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map
simfirmware.elf: CFLAGS += -DSIMULATION
simfirmware.elf: $(FIRMWARE_OBJS) $(P)/fw/tk1/firmware.lds
$(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@
$(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map
qemu_firmware.elf: CFLAGS += -DQEMU_CONSOLE
qemu_firmware.elf: firmware.elf
mv firmware.elf qemu_firmware.elf
qemu_firmware.elf: CFLAGS += -DQEMU_DEBUG
qemu_firmware.elf: ASFLAGS += -DQEMU_DEBUG
qemu_firmware.elf: CFLAGS += -DQEMU_SYSCALL
qemu_firmware.elf: ASFLAGS += -DQEMU_SYSCALL
qemu_firmware.elf: tkey-libs $(FIRMWARE_OBJS) $(P)/fw/tk1/qemu_firmware.lds
$(CC) $(CFLAGS) $(FIRMWARE_OBJS) $(QEMU_LDFLAGS) -o $@ > $(basename $@).map
# Create compile_commands.json for clangd and LSP
.PHONY: clangd
@ -201,12 +223,12 @@ compile_commands.json:
.PHONY: check
check:
clang-tidy -header-filter=.* -checks=cert-* $(FIRMWARE_SOURCES) -- $(CFLAGS)
clang-tidy -header-filter=.* -checks=cert-* $(CHECK_SOURCES) -- $(CFLAGS)
.PHONY: splint
splint:
splint \
-nolib \
+unixlib \
-predboolint \
+boolint \
-nullpass \
@ -217,21 +239,27 @@ splint:
-unreachable \
-unqualifiedtrans \
-fullinitblock \
$(FIRMWARE_SOURCES)
+gnuextensions \
-fixedformalarray \
-mustfreeonly \
-I $(LIBDIR)/include \
-I $(LIBDIR) \
-I $(LIBDIR)/blake2s \
$(CHECK_SOURCES)
testfw.elf: $(TESTFW_OBJS) $(P)/fw/tk1/firmware.lds
$(CC) $(CFLAGS) $(TESTFW_OBJS) $(LDFLAGS) -o $@
testfw.elf: tkey-libs $(TESTFW_OBJS) $(P)/fw/tk1/firmware.lds
$(CC) $(CFLAGS) $(TESTFW_OBJS) $(LDFLAGS) -o $@ > $(basename $@).map
# Generate a fake BRAM file that will be filled in later after place-n-route
bram_fw.hex:
$(ICESTORM_PATH)icebram -v -g 32 $(BRAM_FW_SIZE) > $@
firmware.hex: firmware.bin firmware_size_mismatch
python3 $(P)/tools/makehex/makehex.py $< $(BRAM_FW_SIZE) > $@
python3 $(P)/tools/makehex.py $< $(BRAM_FW_SIZE) > $@
simfirmware.hex: simfirmware.bin simfirmware_size_mismatch
python3 $(P)/tools/makehex/makehex.py $< $(BRAM_FW_SIZE) > $@
python3 $(P)/tools/makehex.py $< $(BRAM_FW_SIZE) > $@
testfw.hex: testfw.bin testfw_size_mismatch
python3 $(P)/tools/makehex/makehex.py $< $(BRAM_FW_SIZE) > $@
python3 $(P)/tools/makehex.py $< $(BRAM_FW_SIZE) > $@
.PHONY: check-binary-hashes
check-binary-hashes:
@ -241,9 +269,6 @@ check-binary-hashes:
sha256sum -c application_fpga.bin.sha256
%.bin: %.elf
$(SIZE) $<
@test "$$($(SIZE) $< | awk 'NR==2{print $$2, $$3}')" = "0 0" \
|| { printf "Non-empty data or bss section!\n"; false; }
$(OBJCOPY) --input-target=elf32-littleriscv --output-target=binary $< $@
chmod -x $@
@ -261,7 +286,8 @@ LINT_FLAGS = \
-Wno-WIDTHEXPAND \
-Wno-UNOPTFLAT \
--timescale 1ns/1ns \
-DNO_ICE40_DEFAULT_ASSIGNMENTS
-DNO_ICE40_DEFAULT_ASSIGNMENTS \
-Wno-GENUNNAMED
lint: $(FPGA_VERILOG_SRCS) \
$(SIM_VERILOG_SRCS) \
@ -303,6 +329,9 @@ fmt: $(FPGA_VERILOG_SRCS) $(SIM_VERILOG_SRCS) $(VERILATOR_VERILOG_SRCS) $(VERILO
# Temporary fix using grep, since the verible with --verify flag only returns
# error if the last file is malformatted.
checkfmt: $(FPGA_VERILOG_SRCS) $(SIM_VERILOG_SRCS) $(VERILATOR_VERILOG_SRCS) $(VERILOG_SRCS)
make -C fw/tk1 checkfmt
make -C fw/testfw checkfmt
make -C apps checkfmt
$(FORMAT) $(CHECK_FORMAT_FLAGS) $^ 2>&1 | \
grep "Needs formatting" && exit 1 || true
.PHONY: checkfmt
@ -353,8 +382,7 @@ tb:
YOSYS_FLAG ?=
synth.json: $(FPGA_VERILOG_SRCS) $(VERILOG_SRCS) $(PICORV32_SRCS) bram_fw.hex \
$(P)/data/uds.hex $(P)/data/udi.hex
synth.json: $(FPGA_VERILOG_SRCS) $(VERILOG_SRCS) $(PICORV32_SRCS) bram_fw.hex
$(YOSYS_PATH)yosys \
-v3 \
-l synth.txt \
@ -368,7 +396,7 @@ synth.json: $(FPGA_VERILOG_SRCS) $(VERILOG_SRCS) $(PICORV32_SRCS) bram_fw.hex \
application_fpga_par.json: synth.json $(P)/data/$(PIN_FILE)
$(NEXTPNR_PATH)nextpnr-ice40 \
-l application_fpga_par.txt \
--seed 9106179903728618585 \
--seed 18160564147838858264 \
--freq $(TARGET_FREQ) \
--ignore-loops \
--up5k \
@ -444,17 +472,23 @@ tb_application_fpga: $(SIM_VERILOG_SRCS) \
#-------------------------------------------------------------------
prog_flash: check-hardware application_fpga.bin
sudo tillitis-iceprog application_fpga.bin
tillitis-iceprog application_fpga.bin
make -C apps
(cd tools && ./load_preloaded_app.sh 0 ../apps/testloadapp.bin)
.PHONY: prog_flash
prog_flash_bs: check-hardware application_fpga.bin
tillitis-iceprog application_fpga.bin
.PHONY: prog_flash_bs
prog_flash_testfw: check-hardware application_fpga_testfw.bin
sudo tillitis-iceprog application_fpga_testfw.bin
tillitis-iceprog application_fpga_testfw.bin
.PHONY: prog_flash_testfw
check-hardware:
@sudo tillitis-iceprog -t >/dev/null 2>&1 || \
@tillitis-iceprog -t >/dev/null 2>&1 || \
{ echo "Programmer not plugged in or not accessible"; false; }
@if sudo tillitis-iceprog -t 2>&1 | grep -qi "^flash.id:\( 0x\(00\|ff\)\)\{4\}"; then \
@if tillitis-iceprog -t 2>&1 | grep -qi "^flash.id:\( 0x\(00\|ff\)\)\{4\}"; then \
echo "No USB stick in the programmer?"; false; else true; fi
.PHONY: check-hardware
@ -478,18 +512,20 @@ clean: clean_sim clean_fw clean_tb
rm -f lint_issues.txt
rm -f tools/tpt/*.hex
rm -rf tools/tpt/__pycache__
make -C apps clean
.PHONY: clean
clean_fw:
rm -f firmware.{elf,elf.map,bin,hex}
rm -f firmware.{elf,map,bin,hex}
rm -f $(FIRMWARE_OBJS)
rm -f testfw.{elf,elf.map,bin,hex}
rm -f testfw.{elf,map,bin,hex}
rm -f $(TESTFW_OBJS)
rm -f qemu_firmware.elf
make -C tkey-libs clean
.PHONY: clean_fw
clean_sim:
rm -f simfirmware.{elf,elf.map,bin,hex}
rm -f simfirmware.{elf,map,bin,hex}
rm -f tb_application_fpga_sim.fst
rm -f tb_application_fpga_sim.fst.hier
rm -f tb/output_spram*.hex
@ -525,7 +561,8 @@ help:
@echo "tb_application_fpga Build testbench simulation for the design"
@echo "lint Run lint on Verilog source files."
@echo "tb Run all testbenches"
@echo "prog_flash Program device flash with FGPA bitstream including firmware (using the RPi Pico-based programmer)."
@echo "prog_flash Program device flash with FGPA bitstream (including firmware), partition table, and testloadapp.bin (using the RPi Pico-based programmer)."
@echo "prog_flash_bs Program device flash with FGPA bitstream including firmware (using the RPi Pico-based programmer)."
@echo "prog_flash_testfw Program device flash as above, but with testfw."
@echo "clean Delete all generated files."
@echo "clean_fw Delete only generated files for firmware. Useful for fw devs."

View file

@ -1,4 +1,4 @@
# TKey hardware design
## TKey hardware design
![The Application FPGA block diagram](../../doc/images/application_fpga_block_diagram.png)
@ -11,16 +11,18 @@ The design top level is in `rtl/application_fpga.v`. It contains
instances of all cores as well as the memory system.
The memory system allows the CPU to access cores in different ways
given the current execution mode. There are two execution modes -
firmware and application. Basically, in application mode the access is
more restrictive.
given the current execution mode. There are three execution modes -
firmware, application and system call. Each mode give access to a
different set of resources. Where app mode is the most restrictive and
firmware mode is the least restrictive.
The rest of the components are under `cores`. They typically have
their own `README.md` file documenting them and their API in detail.
The rest of the components are under `cores`, except the firmware,
which is under `fw/tk1`. They typically have their own `README.md`
file documenting them and their API in detail.
Hardware functions with APIs, assets, and input/output are memory
mapped starting at base address `0xc000_0000`. For specific offsets
and bitmasks, see the file `fw/tk1_mem.h`.
and bitmasks, see the file `tk1_mem.h` in tkey-libs.
Rough memory map:
@ -34,8 +36,14 @@ Rough memory map:
| UART | 0xc3 |
| Touch | 0xc4 |
| FW\_RAM | 0xd0 |
| Syscall | 0xe1 |
| TK1 | 0xff |
## Firmware
Firmware is kept in ROM. See the [Firmware implementation
notes](fw/README.md).
## `clk_reset_gen`
Generator for system clock and system reset.
@ -96,6 +104,54 @@ hours, days) there is also a 32 bit prescaler.
The timer is available to use by firmware and applications.
## Syscall
System call trigger area. A 32-bit write to address 0xe1000000 will
trigger interrupt 31, which in turn triggers a system call in the
firmware.
## Interrupts
Triggering an interrupt will cause the CPU to execute the interrupt
handler att address 0x10.
The interrupt handler is shared by all PicoRV32 interrupts but only
interrupt 31 is enabled on the Tkey. Register `x4` can be inspected to
determine the interrupt source. Each interrupt source is assigned one
bit in x4. Triggered interrupts have their bit set to `1`.
| *Source* | *x4 Bit* |
|----------|----------|
| Syscall | 31 |
The return address is located in register `x3`. Calling the PicoRV32
specific instruction `retirq` exits the interrupt handler and clears
the interrupt source.
No registers are stored/restored when entering/exiting the interrupt
handler. It is up to the software to store/restore as necessary.
Interrupts can be enabled/disabled using the PicoRV32 specific
`maskirq` instruction.
## Restricted resources
The following table shows resource availablility for each execution
mode:
| *Execution Mode* | *ROM* | *FW RAM* | *SPI* | *UDS* |
|------------------|-------|----------|-------|-------|
| Firmware mode | r/x | r/w | r/w | r/w* |
| Syscall | r/x | r/w | r/w | i |
| Application mode | r | i | i | i |
Legend:
r = readable
w = writeable
x = executable
i = invisible
* = UDS words are readable only once in firmware mode.
## `tk1`
See [tk1 README](core/tk1/README.md) for details.
@ -107,7 +163,6 @@ Contains:
- RGB LED control.
- General purpose input/output (GPIO) pin control.
- Application introspection: start address and size of binary.
- BLAKE2s function access.
- Compound Device Identity (CDI).
- Unique Device Identity (UDI).
- RAM memory protection.
@ -135,13 +190,13 @@ should make it infeasible to improve asset extraction by observing
multiple memory dumps from the same TKey device. The attack should
also not directly scale to multiple TKey devices.
The RAM address and data scrambling is done in de RAM core.
The RAM address and data scrambling is done in the RAM core.
The memory protection is setup by the firmware. Access to the memory
protection controls is disabled for applications. Before the memory
protecetion is enabled, the RAM is filled with randomised data using
Xorwow. So during boot the firmware perform the following steps to
setup the memory protection:
Xorwow. During boot the firmware perform the following steps to setup
the memory protection:
1. Get a random 32-bit value from the TRNG to use as data state for
Xorwow.

View file

@ -1 +1 @@
8b09a7b2c9b864711e19f85de2785c8ea52f454207943c13bb17fff1ce095711 application_fpga.bin
dfba361c83337c6bac2364d1fd8f115eeb7feeae5faabbdf0713c79b44bbd78d application_fpga.bin

View file

@ -0,0 +1,123 @@
P := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
LIBDIR ?= ../tkey-libs
OBJCOPY ?= llvm-objcopy
CC = clang
CFLAGS = \
-target riscv32-unknown-none-elf \
-march=rv32iczmmul \
-mabi=ilp32 \
-mcmodel=medany \
-static \
-std=gnu99 \
-Os \
-ffast-math \
-fno-common \
-fno-builtin-printf \
-fno-builtin-putchar \
-fno-builtin-memcpy \
-nostdlib \
-mno-relax \
-Wall \
-Wpedantic \
-Wno-language-extension-token \
-Werror \
-flto \
-g \
-I $(LIBDIR)/include \
-I $(LIBDIR) \
-I include \
-I ../
AS = clang
ASFLAGS = \
-target riscv32-unknown-none-elf \
-march=rv32iczmmul \
-mabi=ilp32 \
-mno-relax
LDFLAGS = \
-T $(LIBDIR)/app.lds \
-L $(LIBDIR) -lcrt0 -lcommon -lmonocypher -lblake2s
.PHONY: all
all: defaultapp.bin reset_test.bin testapp.bin testloadapp.bin
# Turn elf into bin for device
%.bin: %.elf
$(OBJCOPY) --input-target=elf32-littleriscv --output-target=binary $^ $@
chmod a-x $@
.PHONY: tkey-libs
tkey-libs:
make -C $(LIBDIR)
OBJS=syscall.o
# syscall.o: syscall.S
# $(CC) $(CFLAGS) $(DEFAULTAPP_OBJS) $(LDFLAGS) -o $@
# defaultapp
DEFAULTAPP_FMTFILES = *.[ch]
DEFAULTAPP_OBJS = \
$(P)/defaultapp/main.o
defaultapp.elf: tkey-libs $(OBJS) $(DEFAULTAPP_OBJS)
$(CC) $(CFLAGS) $(OBJS) $(DEFAULTAPP_OBJS) $(LDFLAGS) -o $@
# reset_test
RESET_TEST_FMTFILES = *.[ch]
RESET_TEST_OBJS = \
$(P)/reset_test/main.o
reset_test.elf: tkey-libs $(RESET_TEST_OBJS)
$(CC) $(CFLAGS) $(OBJS) $(RESET_TEST_OBJS) $(LDFLAGS) -o $@
# testapp
TESTAPP_OBJS = \
$(P)/testapp/main.o
testapp.elf: tkey-libs $(TESTAPP_OBJS)
$(CC) $(CFLAGS) $(OBJS) $(TESTAPP_OBJS) $(LDFLAGS) -o $@
# testloadapp
TESTLOADAPP_OBJS = \
$(P)/testloadapp/main.o
testloadapp.elf: tkey-libs $(TESTLOADAPP_OBJS)
$(CC) $(CFLAGS) $(OBJS) $(TESTLOADAPP_OBJS) $(LDFLAGS) -o $@
.PHONY: fmt
fmt:
clang-format --dry-run --ferror-limit=0 defaultapp/*.[ch]
clang-format --verbose -i defaultapp/*.[ch]
clang-format --dry-run --ferror-limit=0 reset_test/*.[ch]
clang-format --verbose -i reset_test/*.[ch]
clang-format --dry-run --ferror-limit=0 testapp/*.[ch]
clang-format --verbose -i testapp/*.[ch]
clang-format --dry-run --ferror-limit=0 testloadapp/*.[ch]
clang-format --verbose -i testloadapp/*.[ch]
.PHONY: checkfmt
checkfmt:
clang-format --dry-run --ferror-limit=0 defaultapp/*.[ch]
clang-format --dry-run --ferror-limit=0 reset_test/*.[ch]
clang-format --dry-run --ferror-limit=0 testapp/*.[ch]
clang-format --dry-run --ferror-limit=0 testloadapp/*.[ch]
.PHONY: clean
clean:
rm -f *.elf *.bin $(OBJS) $(DEFAULTAPP_OBJS) $(RESET_TEST_OBJS) \
$(TESTAPP_OBJS) $(TESTLOADAPP_OBJS)

View file

@ -0,0 +1,36 @@
# Test applications
- `defaultapp`: Immediately resets the TKey with the intention to
start an app from the client, replicating the behaviour of earlier
generations.
- `testapp`: Runs through a couple of tests that are now impossible
to do in the `testfw`.
- `reset_test`: Interactively test different reset scenarios.
- `testloadapp`: Interactively test management app things like
installing an app (hardcoded for a small happy blinking app, see
`blink.h` for the entire binary!) and to test verified boot.
## Build
```
$ make
```
will build all the .elf and .bin files on the top level.
## Use
Use `tkey-runapp` from
[tkey-devtools](https://github.com/tillitis/tkey-devtools) to load the
apps:
```
$ tkey-runapp testapp.bin
```
All of these test apps are controlled through the USB CDC, typically
by running picocom or similar terminal program, like:
```
$ picocom /dev/ttyACM1
```

View file

@ -0,0 +1,18 @@
// Copyright (C) 2025 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <fw/tk1/reset.h>
#include <fw/tk1/syscall_num.h>
#include <syscall.h>
#include <tkey/debug.h>
#include <tkey/led.h>
int main(void)
{
struct reset rst = {0};
led_set(LED_BLUE);
rst.type = START_CLIENT;
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
}

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, uint32_t arg2, uint32_t arg3);
#endif

View file

@ -0,0 +1,158 @@
// Copyright (C) 2022, 2023 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <fw/tk1/proto.h>
#include <fw/tk1/reset.h>
#include <fw/tk1/syscall_num.h>
#include <stdint.h>
#include <syscall.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>
// Converts a single hex character to its integer value
static uint8_t hex_char_to_byte(uint8_t c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
return 0; // Invalid character, should not happen if input is valid
}
// Converts a 64-char hex string into a 32-byte array
int hex_string_to_bytes(uint8_t *hex_str, uint8_t *out_bytes, size_t out_len)
{
if (!hex_str || !out_bytes || out_len < 32)
return -1; // Error handling
for (size_t i = 0; i < 32; i++) {
out_bytes[i] = (hex_char_to_byte(hex_str[i * 2]) << 4) |
hex_char_to_byte(hex_str[i * 2 + 1]);
}
return 0; // Success
}
//---------------------------------------
#define BUFSIZE 32
int main(void)
{
uint8_t available = 0;
uint8_t cmdbuf[BUFSIZE] = {0};
enum ioend endpoint = IO_NONE;
led_set(LED_BLUE);
struct reset rst = {0};
while (1) {
puts(IO_CDC, "reset_test: Waiting for command\r\n");
memset(cmdbuf, 0, BUFSIZE);
// Wait for data
if (readselect(IO_CDC, &endpoint, &available) < 0) {
assert(1 == 2);
}
if (read(IO_CDC, cmdbuf, BUFSIZE, available) < 0) {
// read failed! I/O broken? Just redblink.
assert(1 == 2);
}
led_set(LED_BLUE | LED_RED);
switch (cmdbuf[0]) {
case '1':
// Reset into default state
rst.type = START_DEFAULT;
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
break;
case '2':
// Reset and load app from client
rst.type = START_CLIENT;
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
break;
case '3':
// Reset and load app from second flash slot
rst.type = START_FLASH1;
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
break;
case '4': {
// Reset and load app from client with verification
// using an invalid digest.
//
// Should cause firmware to refuse to start app.
uint8_t string[] = "0123456789abcdef0123456789abcdef012"
"3456789abcdef0123456789abcdef";
rst.type = START_CLIENT_VER;
hex_string_to_bytes(string, (uint8_t *)&rst.app_digest,
sizeof(rst.app_digest));
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
} break;
case '5': {
// Reset and load app from client with verification
// using a digest matching the example app (blue.bin)
// from tkey-libs
uint8_t tkeylibs_example_app_digest[] =
"96bb4c90603dbbbe09b9a1d7259b5e9e61bedd89a897105c30"
"c9d4bf66a98d97";
rst.type = START_CLIENT_VER;
hex_string_to_bytes(tkeylibs_example_app_digest,
(uint8_t *)&rst.app_digest,
sizeof(rst.app_digest));
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
} break;
case '6': {
// Reset and load app from second flash slot with
// verification using an invalid digest.
//
// Should cause firmware to refuse to start app.
uint8_t string[] = "0123456789abcdef0123456789abcdef012"
"3456789abcdef0123456789abcdef";
rst.type = START_FLASH1_VER;
hex_string_to_bytes(string, (uint8_t *)&rst.app_digest,
sizeof(rst.app_digest));
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
} break;
case '7': {
// Reset and load app from second flash slot with
// verification using a digest matching the example app
// (blue.bin) from tkey-libs
//
// Blue.bin has to be present on flash in the second
// preloaded app slot (slot 1).
uint8_t tkeylibs_example_app_digest[] =
"96bb4c90603dbbbe09b9a1d7259b5e9e61bedd89a897105c30"
"c9d4bf66a98d97";
rst.type = START_FLASH1_VER;
hex_string_to_bytes(tkeylibs_example_app_digest,
(uint8_t *)&rst.app_digest,
sizeof(rst.app_digest));
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
} break;
default:
break;
}
}
}

View file

@ -0,0 +1,85 @@
// SPDX-FileCopyrightText: 2024 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: BSD-2-Clause
#include "../fw/tk1/picorv32/custom_ops.S"
.section ".text"
.globl syscall
syscall:
// Save registers to stack
addi sp, sp, -32*4
sw x0, 0*4(sp)
sw x1, 1*4(sp)
// x2 (sp) is assumed to be preserved by the interrupt handler.
sw x3, 3*4(sp)
sw x4, 4*4(sp)
sw x5, 5*4(sp)
sw x6, 6*4(sp)
sw x7, 7*4(sp)
sw x8, 8*4(sp)
sw x9, 9*4(sp)
// x10 (a0) will contain syscall return value. And should not be saved.
sw x11, 11*4(sp)
sw x12, 12*4(sp)
sw x13, 13*4(sp)
sw x14, 14*4(sp)
sw x15, 15*4(sp)
sw x16, 16*4(sp)
sw x17, 17*4(sp)
sw x18, 18*4(sp)
sw x19, 19*4(sp)
sw x20, 20*4(sp)
sw x21, 21*4(sp)
sw x22, 22*4(sp)
sw x23, 23*4(sp)
sw x24, 24*4(sp)
sw x25, 25*4(sp)
sw x26, 26*4(sp)
sw x27, 27*4(sp)
sw x28, 28*4(sp)
sw x29, 29*4(sp)
sw x30, 30*4(sp)
sw x31, 31*4(sp)
// Trigger syscall interrupt
li t1, 0xe1000000 // Syscall interrupt trigger address
sw zero, 0(t1) // Trigger interrupt
// Restore registers from stack
lw x0, 0*4(sp)
lw x1, 1*4(sp)
// x2 (sp) is assumed to be preserved by the interrupt handler.
lw x3, 3*4(sp)
lw x4, 4*4(sp)
lw x5, 5*4(sp)
lw x6, 6*4(sp)
lw x7, 7*4(sp)
lw x8, 8*4(sp)
lw x9, 9*4(sp)
// x10 (a0) contains syscall return value. And should not be destroyed.
lw x11, 11*4(sp)
lw x12, 12*4(sp)
lw x13, 13*4(sp)
lw x14, 14*4(sp)
lw x15, 15*4(sp)
lw x16, 16*4(sp)
lw x17, 17*4(sp)
lw x18, 18*4(sp)
lw x19, 19*4(sp)
lw x20, 20*4(sp)
lw x21, 21*4(sp)
lw x22, 22*4(sp)
lw x23, 23*4(sp)
lw x24, 24*4(sp)
lw x25, 25*4(sp)
lw x26, 26*4(sp)
lw x27, 27*4(sp)
lw x28, 28*4(sp)
lw x29, 29*4(sp)
lw x30, 30*4(sp)
lw x31, 31*4(sp)
addi sp, sp, 32*4
ret

View file

@ -0,0 +1,295 @@
// Copyright (C) 2022, 2023 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <fw/tk1/proto.h>
#include <fw/tk1/reset.h>
#include <fw/tk1/syscall_num.h>
#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 "syscall.h"
#define USBMODE_PACKET_SIZE 64
// clang-format off
volatile uint32_t *tk1name0 = (volatile uint32_t *)TK1_MMIO_TK1_NAME0;
volatile uint32_t *tk1name1 = (volatile uint32_t *)TK1_MMIO_TK1_NAME1;
volatile uint32_t *tk1version = (volatile uint32_t *)TK1_MMIO_TK1_VERSION;
volatile uint32_t *uds = (volatile uint32_t *)TK1_MMIO_UDS_FIRST;
volatile uint32_t *cdi = (volatile uint32_t *)TK1_MMIO_TK1_CDI_FIRST;
volatile uint32_t *udi = (volatile uint32_t *)TK1_MMIO_TK1_UDI_FIRST;
volatile uint8_t *fw_ram = (volatile uint8_t *)TK1_MMIO_FW_RAM_BASE;
volatile uint32_t *system_reset = (volatile uint32_t *)TK1_MMIO_TK1_SYSTEM_RESET;
volatile uint32_t *timer = (volatile uint32_t *)TK1_MMIO_TIMER_TIMER;
volatile uint32_t *timer_prescaler = (volatile uint32_t *)TK1_MMIO_TIMER_PRESCALER;
volatile uint32_t *timer_status = (volatile uint32_t *)TK1_MMIO_TIMER_STATUS;
volatile uint32_t *timer_ctrl = (volatile uint32_t *)TK1_MMIO_TIMER_CTRL;
volatile uint32_t *trng_status = (volatile uint32_t *)TK1_MMIO_TRNG_STATUS;
volatile uint32_t *trng_entropy = (volatile uint32_t *)TK1_MMIO_TRNG_ENTROPY;
// clang-format on
#define UDS_WORDS 8
#define UDI_WORDS 2
#define CDI_WORDS 8
void puthexn(uint8_t *p, int n)
{
for (int i = 0; i < n; i++) {
puthex(IO_CDC, p[i]);
}
}
void reverseword(uint32_t *wordp)
{
*wordp = ((*wordp & 0xff000000) >> 24) | ((*wordp & 0x00ff0000) >> 8) |
((*wordp & 0x0000ff00) << 8) | ((*wordp & 0x000000ff) << 24);
}
uint32_t wait_timer_tick(uint32_t last_timer)
{
uint32_t newtimer;
for (;;) {
newtimer = *timer;
if (newtimer != last_timer) {
return newtimer;
}
}
}
void failmsg(char *s)
{
puts(IO_CDC, "FAIL: ");
puts(IO_CDC, s);
puts(IO_CDC, "\r\n");
}
int main(void)
{
uint8_t in = 0;
uint8_t available = 0;
enum ioend endpoint = IO_NONE;
led_set(LED_BLUE);
// Wait for terminal program and a character to be typed
if (readselect(IO_CDC, &endpoint, &available) < 0) {
// readselect failed! I/O broken? Just redblink.
assert(1 == 2);
}
if (read(IO_CDC, &in, 1, 1) < 0) {
// read failed! I/O broken? Just redblink.
assert(1 == 2);
}
puts(IO_CDC, "\r\nI'm testapp on:");
// Output the TK1 core's NAME0 and NAME1
uint32_t name;
wordcpy_s(&name, 1, (void *)tk1name0, 1);
reverseword(&name);
write(IO_CDC, (const uint8_t *)&name, 4);
puts(IO_CDC, " ");
wordcpy_s(&name, 1, (void *)tk1name1, 1);
reverseword(&name);
write(IO_CDC, (const uint8_t *)&name, 4);
puts(IO_CDC, "\r\n");
puts(IO_CDC, "Version: ");
putinthex(IO_CDC, *tk1version);
puts(IO_CDC, "\r\n");
uint32_t zeros[8];
memset(zeros, 0, 8 * 4);
int anyfailed = 0;
uint32_t uds_local[UDS_WORDS];
uint32_t udi_local[UDI_WORDS];
// Should NOT be able to read from UDS in app-mode.
wordcpy_s(uds_local, UDS_WORDS, (void *)uds, UDS_WORDS);
if (!memeq(uds_local, zeros, UDS_WORDS * 4)) {
failmsg("Read from UDS in app-mode");
anyfailed = 1;
}
// Should NOT be able to read from UDI in app-mode.
wordcpy_s(udi_local, UDI_WORDS, (void *)udi, UDI_WORDS);
if (!memeq(udi_local, zeros, UDI_WORDS * 4)) {
failmsg("Read from UDI in app-mode");
anyfailed = 1;
}
// But a syscall to get parts of UDI should be able to run
int vidpid = syscall(TK1_SYSCALL_GET_VIDPID, 0, 0, 0);
if (vidpid != 0x073570c0) {
failmsg("Expected VID/PID to be 0x073570c0");
anyfailed = 1;
}
puts(IO_CDC, "\r\nAllocating storage area...");
if (syscall(TK1_SYSCALL_ALLOC_AREA, 0, 0, 0) != 0) {
failmsg("Failed to allocate storage area");
}
puts(IO_CDC, "done.\r\n");
puts(IO_CDC, "\r\nWriting to storage area...");
uint8_t out_data[14] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
if (syscall(TK1_SYSCALL_WRITE_DATA, 0, (uint32_t)out_data,
sizeof(out_data)) != 0) {
failmsg("Failed to write to storage area");
}
puts(IO_CDC, "done.\r\n");
puts(IO_CDC, "\r\nReading data from storage area...");
uint8_t in_data[14] = {0};
if (syscall(TK1_SYSCALL_READ_DATA, 0, (uint32_t)in_data,
sizeof(in_data)) != 0) {
failmsg("Failed to write to storage area");
}
if (!memeq(in_data, out_data, sizeof(in_data))) {
failmsg("Failed to read back data from storage area");
anyfailed = 1;
}
puts(IO_CDC, "done.\r\n");
puts(IO_CDC, "\r\nErasing written data from storage area...");
if (syscall(TK1_SYSCALL_ERASE_DATA, 0, 4096, 0) != 0) {
failmsg("Failed to erase storage area");
}
puts(IO_CDC, "done.\r\n");
puts(IO_CDC, "\r\nVerify erased storage area data...");
if (syscall(TK1_SYSCALL_READ_DATA, 0, (uint32_t)in_data,
sizeof(in_data)) != 0) {
failmsg("Failed to write to storage area");
}
uint8_t check_data[sizeof(in_data)] = {0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff};
if (!memeq(in_data, check_data, sizeof(check_data))) {
failmsg("Failed to read back data from storage area");
anyfailed = 1;
}
puts(IO_CDC, "done.\r\n");
puts(IO_CDC, "\r\nDeallocating storage area...");
if (syscall(TK1_SYSCALL_DEALLOC_AREA, 0, 0, 0) != 0) {
failmsg("Failed to deallocate storage area");
}
puts(IO_CDC, "done.\r\n");
uint32_t cdi_local[CDI_WORDS];
uint32_t cdi_local2[CDI_WORDS];
wordcpy_s(cdi_local, CDI_WORDS, (void *)cdi, CDI_WORDS);
// Write to CDI should NOT have any effect in app mode.
wordcpy_s((void *)cdi, CDI_WORDS, zeros, CDI_WORDS);
wordcpy_s(cdi_local2, CDI_WORDS, (void *)cdi, CDI_WORDS);
if (!memeq(cdi_local, cdi_local2, CDI_WORDS * 4)) {
failmsg("Write to CDI in app-mode");
anyfailed = 1;
}
// Should NOT be able to reset Tkey from app mode
puts(IO_CDC, "\r\nTesting system reset...");
*system_reset = 1;
puts(IO_CDC, "done.\r\n");
// Test FW_RAM.
*fw_ram = 0x21;
if (*fw_ram == 0x21) {
failmsg("Write and read FW RAM in app-mode");
anyfailed = 1;
}
puts(IO_CDC, "\r\nTesting timer... 3");
// Matching clock at 24 MHz, giving us timer in seconds
*timer_prescaler = 24 * 1000000;
// Test timer expiration after 1s
*timer = 1;
// Start the timer
*timer_ctrl = (1 << TK1_MMIO_TIMER_CTRL_START_BIT);
while (*timer_status & (1 << TK1_MMIO_TIMER_STATUS_RUNNING_BIT)) {
}
// Now timer has expired and is ready to run again
puts(IO_CDC, " 2");
// Test to interrupt a timer - and reads from timer register
// Starting 10s timer and interrupting it in 3s...
*timer = 10;
*timer_ctrl = (1 << TK1_MMIO_TIMER_CTRL_START_BIT);
uint32_t last_timer = 10;
for (int i = 0; i < 3; i++) {
last_timer = wait_timer_tick(last_timer);
}
// Stop the timer
*timer_ctrl = (1 << TK1_MMIO_TIMER_CTRL_STOP_BIT);
puts(IO_CDC, " 1. done.\r\n");
if (*timer_status & (1 << TK1_MMIO_TIMER_STATUS_RUNNING_BIT)) {
failmsg("Timer didn't stop");
anyfailed = 1;
}
if (*timer != 10) {
failmsg("Timer didn't reset to 10");
anyfailed = 1;
}
// Check and display test results.
puts(IO_CDC, "\r\n--> ");
if (anyfailed) {
puts(IO_CDC, "Some test FAILED!\r\n");
} else {
puts(IO_CDC, "All tests passed.\r\n");
}
puts(IO_CDC, "\r\nHere are 256 bytes from the TRNG:\r\n");
for (int j = 0; j < 8; j++) {
for (int i = 0; i < 8; i++) {
while ((*trng_status &
(1 << TK1_MMIO_TRNG_STATUS_READY_BIT)) == 0) {
}
uint32_t rnd = *trng_entropy;
puthexn((uint8_t *)&rnd, 4);
puts(IO_CDC, " ");
}
puts(IO_CDC, "\r\n");
}
puts(IO_CDC, "\r\n");
puts(IO_CDC, "Now echoing what you type...Type + to reset device\r\n");
for (;;) {
if (readselect(IO_CDC, &endpoint, &available) < 0) {
// readselect failed! I/O broken? Just redblink.
assert(1 == 2);
}
if (read(IO_CDC, &in, 1, 1) < 0) {
// read failed! I/O broken? Just redblink.
assert(1 == 2);
}
if (in == '+') {
struct reset rst;
memset(&rst, 0, sizeof(rst));
rst.type = START_DEFAULT;
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
}
write(IO_CDC, &in, 1);
}
}

View file

@ -0,0 +1,31 @@
// Copyright (C) 2025 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef BLINK_APP_H
#define BLINK_APP_H
uint8_t blink[] = {
0x81, 0x40, 0x01, 0x41, 0x81, 0x41, 0x01, 0x42, 0x81, 0x42, 0x01, 0x43,
0x81, 0x43, 0x01, 0x44, 0x81, 0x44, 0x01, 0x45, 0x81, 0x45, 0x01, 0x46,
0x81, 0x46, 0x01, 0x47, 0x81, 0x47, 0x01, 0x48, 0x81, 0x48, 0x01, 0x49,
0x81, 0x49, 0x01, 0x4a, 0x81, 0x4a, 0x01, 0x4b, 0x81, 0x4b, 0x01, 0x4c,
0x81, 0x4c, 0x01, 0x4d, 0x81, 0x4d, 0x01, 0x4e, 0x81, 0x4e, 0x01, 0x4f,
0x81, 0x4f, 0x37, 0x01, 0x02, 0x40, 0x41, 0x11, 0x17, 0x05, 0x00, 0x00,
0x13, 0x05, 0x45, 0x0c, 0x97, 0x05, 0x00, 0x00, 0x93, 0x85, 0xc5, 0x0b,
0x63, 0x57, 0xb5, 0x00, 0x23, 0x20, 0x05, 0x00, 0x11, 0x05, 0xe3, 0x4d,
0xb5, 0xfe, 0x97, 0x00, 0x00, 0x00, 0xe7, 0x80, 0xa0, 0x00, 0x00, 0x00,
0x41, 0x11, 0x37, 0x05, 0x00, 0xff, 0x11, 0x48, 0xe1, 0x66, 0x13, 0x86,
0xf6, 0x69, 0x93, 0x86, 0x06, 0x6a, 0x09, 0x47, 0x85, 0x47, 0x23, 0x22,
0x05, 0x03, 0x02, 0xc2, 0x92, 0x45, 0x63, 0x68, 0xb6, 0x00, 0x92, 0x45,
0x85, 0x05, 0x2e, 0xc2, 0x92, 0x45, 0xe3, 0xec, 0xd5, 0xfe, 0x58, 0xd1,
0x02, 0xc4, 0xa2, 0x45, 0x63, 0x68, 0xb6, 0x00, 0xa2, 0x45, 0x85, 0x05,
0x2e, 0xc4, 0xa2, 0x45, 0xe3, 0xec, 0xd5, 0xfe, 0x5c, 0xd1, 0x02, 0xc6,
0xb2, 0x45, 0xe3, 0x66, 0xb6, 0xfc, 0xb2, 0x45, 0x85, 0x05, 0x2e, 0xc6,
0xb2, 0x45, 0xe3, 0xec, 0xd5, 0xfe, 0x75, 0xbf, 0x19, 0xca, 0x2a, 0x96,
0xaa, 0x86, 0x03, 0xc7, 0x05, 0x00, 0x23, 0x80, 0xe6, 0x00, 0x85, 0x06,
0x85, 0x05, 0xe3, 0x9a, 0xc6, 0xfe, 0x82, 0x80, 0x11, 0xca, 0x0a, 0x06,
0x2a, 0x96, 0xaa, 0x86, 0x98, 0x41, 0x98, 0xc2, 0x91, 0x06, 0x91, 0x05,
0xe3, 0x9c, 0xc6, 0xfe, 0x82, 0x80, 0x01, 0xca, 0x2a, 0x96, 0xaa, 0x86,
0x23, 0x80, 0xb6, 0x00, 0x85, 0x06, 0xe3, 0x9d, 0xc6, 0xfe, 0x82, 0x80};
#endif

View file

@ -0,0 +1,228 @@
// Copyright (C) 2025 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <blake2s/blake2s.h>
#include <fw/tk1/reset.h>
#include <fw/tk1/syscall_num.h>
#include <monocypher/monocypher-ed25519.h>
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/debug.h>
#include <tkey/led.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include "blink.h"
#include "syscall.h"
// clang-format off
static volatile uint32_t *cdi = (volatile uint32_t *) TK1_MMIO_TK1_CDI_FIRST;
// clang-format on
int install_app(uint8_t secret_key[64])
{
uint8_t app_digest[32];
uint8_t app_signature[64];
size_t app_size = sizeof(blink);
int ret = 0;
ret = syscall(TK1_SYSCALL_PRELOAD_DELETE, 0, 0, 0);
if (ret != 0) {
puts(IO_CDC, "couldn't delete preloaded app. error: 0x");
putinthex(IO_CDC, ret);
puts(IO_CDC, "\r\n");
return -1;
}
ret = syscall(TK1_SYSCALL_PRELOAD_STORE, 0, (uint32_t)blink,
sizeof(blink));
if (ret != 0) {
puts(IO_CDC, "couldn't store app, error: 0x");
putinthex(IO_CDC, ret);
puts(IO_CDC, "\r\n");
return -1;
}
puts(IO_CDC, "blink: ");
putinthex(IO_CDC, (uint32_t)blink);
puts(IO_CDC, "\r\n");
puts(IO_CDC, "blink[0]: ");
putinthex(IO_CDC, blink[0]);
puts(IO_CDC, "\r\n");
puts(IO_CDC, "sizeof(blink): ");
putinthex(IO_CDC, sizeof(blink));
puts(IO_CDC, "\r\n");
if (blake2s(app_digest, 32, NULL, 0, blink, sizeof(blink)) != 0) {
puts(IO_CDC, "couldn't compute digest\r\n");
return -1;
}
crypto_ed25519_sign(app_signature, secret_key, app_digest,
sizeof(app_digest));
puts(IO_CDC, "app_digest:\r\n");
hexdump(IO_CDC, app_digest, sizeof(app_digest));
puts(IO_CDC, "\r\n");
puts(IO_CDC, "app_signature:\r\n");
hexdump(IO_CDC, app_signature, sizeof(app_signature));
puts(IO_CDC, "\r\n");
puts(IO_CDC, "secret_key:\r\n");
hexdump(IO_CDC, secret_key, 64);
puts(IO_CDC, "\r\n");
ret = syscall(TK1_SYSCALL_PRELOAD_STORE_FIN, app_size,
(uint32_t)app_digest, (uint32_t)app_signature);
if (ret != 0) {
puts(IO_CDC, "couldn't finalize storing app, error:");
putinthex(IO_CDC, ret);
puts(IO_CDC, "\r\n");
return -1;
}
return 0;
}
int verify(uint8_t pubkey[32])
{
uint8_t app_digest[32];
uint8_t app_signature[64];
int ret = 0;
// pubkey we already have
// read signature
// read digest
ret = syscall(TK1_SYSCALL_PRELOAD_GET_DIGSIG, (uint32_t)app_digest,
(uint32_t)app_signature, 0);
if (ret != 0) {
puts(IO_CDC, "couldn't get digsig, error:");
putinthex(IO_CDC, ret);
puts(IO_CDC, "\r\n");
return -1;
}
puts(IO_CDC, "app_digest:\r\n");
hexdump(IO_CDC, app_digest, sizeof(app_digest));
puts(IO_CDC, "\r\n");
puts(IO_CDC, "app_signature:\r\n");
hexdump(IO_CDC, app_signature, sizeof(app_signature));
puts(IO_CDC, "\r\n");
puts(IO_CDC, "pubkey:\r\n");
hexdump(IO_CDC, pubkey, 32);
puts(IO_CDC, "\r\n");
puts(IO_CDC, "Checking signature...\r\n");
if (crypto_ed25519_check(app_signature, pubkey, app_digest,
sizeof(app_digest)) != 0) {
puts(IO_CDC, "signature check failed\r\n");
return -1;
}
puts(IO_CDC, "Resetting into pre loaded app (slot 2)...\r\n");
// syscall reset flash1_ver with app_digest
struct reset rst;
rst.type = START_FLASH1_VER;
memcpy_s(rst.app_digest, sizeof(rst.app_digest), app_digest,
sizeof(app_digest));
memset(rst.next_app_data, 0, sizeof(rst.next_app_data));
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, 0, 0);
return -2;
}
void reset_from_client(void)
{
struct reset rst = {0};
rst.type = START_CLIENT;
// Give the next in chain something to look at.
memset(rst.next_app_data, 17, sizeof(rst.next_app_data));
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, sizeof(rst.next_app_data),
0);
}
int main(void)
{
uint8_t secret_key[64];
uint8_t pubkey[32];
enum ioend endpoint;
uint8_t available;
uint8_t in = 0;
led_set(LED_BLUE);
// Generate a key pair from CDI
crypto_ed25519_key_pair(secret_key, pubkey, (uint8_t *)cdi);
if (readselect(IO_CDC, &endpoint, &available) < 0) {
// readselect failed! I/O broken? Just redblink.
assert(1 == 2);
}
if (read(IO_CDC, &in, 1, 1) < 0) {
// read failed! I/O broken? Just redblink.
assert(1 == 2);
}
puts(IO_CDC, "Hello from testloadapp! 0 = install app in slot 1, 1 = "
"verify app, 2 == load app from client\r\n");
for (;;) {
if (readselect(IO_CDC, &endpoint, &available) < 0) {
// readselect failed! I/O broken? Just redblink.
assert(1 == 2);
}
if (read(IO_CDC, &in, 1, 1) < 0) {
// read failed! I/O broken? Just redblink.
assert(1 == 2);
}
switch (in) {
case '0':
if (install_app(secret_key) < 0) {
puts(IO_CDC, "Failed to install app\r\n");
} else {
puts(IO_CDC, "Installed app!\r\n");
}
break;
case '1':
if (verify(pubkey) < 0) {
puts(IO_CDC, "Failed to verify app\r\n");
} else {
puts(IO_CDC, "Verified app!\r\n");
}
break;
case '2':
reset_from_client();
break;
default:
break;
}
}
}

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;
@ -102,11 +101,12 @@ module tk1 #(
localparam TK1_NAME0 = 32'h746B3120; // "tk1 "
localparam TK1_NAME1 = 32'h6d6b6466; // "mkdf"
localparam TK1_VERSION = 32'h00000005;
localparam TK1_VERSION = 32'h00000006;
localparam FW_RAM_FIRST = 32'hd0000000;
localparam FW_RAM_LAST = 32'hd00007ff;
localparam FW_RAM_LAST = 32'hd0000fff; // 4 KB
localparam FW_ROM_LAST = 32'h00001fff;
//----------------------------------------------------------------
// Registers including update variables and write enable.
@ -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,2 +1,2 @@
00010203
073570c0
04050607

View file

@ -1 +1 @@
39d5aee11b8553544ba9171f83fbe6f5b7546a15c70d03325e72a2b0ca86c8f7a2b5b6bf121d1d3ffc84a502a2a1a6f3ea140d1424cd424336e055be2f394f83 firmware.bin
4be2767d5ddd30b5422f4b58075365cb6d988259e88ffa14d6d243560b289f54eaf0c351e9d744cff8ec3a18b1830f3925a86f36bd2096c12eccce25ed44993c firmware.bin

View file

@ -1,35 +1,44 @@
# Firmware
# Firmware implementation notes
## Introduction
This text is an introduction to, a requirement specification of,
and some implementation notes of the TKey firmware. It also gives a
few hint on developing and debugging the firmware.
This text is specific for the firmware. For a more general description
on how to implement device apps, see [the TKey Developer
Handbook](https://dev.tillitis.se/).
This text is specific for the firmware, the piece of software in TKey
ROM. For a more general description on how to implement device apps,
see [the TKey Developer Handbook](https://dev.tillitis.se/).
## Definitions
- Firmware - software in ROM responsible for loading applications. The
firmware is included as part of the FPGA bitstream and not
replacable on a usual consumer TKey.
- Device application or app - software supplied by the client which is
received, loaded, measured, and started by the firmware.
- Firmware: Software in ROM responsible for loading, measuring,
starting applications, and providing system calls. The firmware is
included as part of the FPGA bitstream and not replacable on a usual
consumer TKey.
- Client: Software running on a computer or a mobile phone the TKey is
inserted into.
- Device application or app: Software supplied by the client or from
flash that runs on the TKey.
## CPU modes and firmware
The TKey has two modes of software operation: firmware mode and
application mode. The TKey always starts in firmware mode and starts
the firmware. When the firmware is about to start the application it
switches to a more constrained environment, the application mode.
application mode. The TKey always starts in firmware mode when it
starts the firmware. When the application starts the hardware
automatically switches to a more constrained environment: the
application mode.
The TKey hardware cores are memory mapped. Firmware has complete
access, except that the UDS is readable only once. The memory map is
constrained when running in application mode, e.g. FW\_RAM and UDS
isn't readable, and several other hardware addresses are either not
readable or not writable for the application.
The TKey hardware cores are memory mapped but the memory access is
different depending on mode. Firmware has complete access, except that
the Unique Device Secret (UDS) words are readable only once even in
firmware mode. The memory map is constrained when running in
application mode, e.g. FW\_RAM and UDS isn't readable, and several
other hardware addresses are either not readable or not writable for
the application.
When doing system calls from a device app the context switches back to
firmware mode. However, the UDS is still not available, protected by
two measures: 1) the UDS words can only be read out once and have
already been read by firmware when measuring the app, and, 2) the UDS
is protected by hardware after the execution leaves ROM for the first
time.
See the table in [the Developer
Handbook](https://dev.tillitis.se/memory/) for an overview about the
@ -37,171 +46,441 @@ memory access control.
## Communication
The firmware communicates to the host via the
`UART_{RX,TX}_{STATUS,DATA}` registers, using the framing protocol
described in the [Framing
Protocol](https://dev.tillitis.se/protocol/).
The firmware communicates with the client using the
`UART_{RX,TX}_{STATUS,DATA}` registers. On top of that is uses three
protocols: The USB Mode protocol, the TKey framing protocol, and the
firmware's own protocol.
To communicate between the CPU and the CH552 USB controller it uses an
internal protocol, used only within the TKey, which we call the USB
Mode Protocol. It is used in both directions.
| *Name* | *Size* | *Comment* |
|----------|-----------|------------------------------------|
| Endpoint | 1B | Origin or destination USB endpoint |
| Length | 1B | Number of bytes following |
| Payload | See above | Actual data from or to firmware |
The different endpoints:
| *Name* | *Value* | *Comment* |
|--------|---------|----------------------------------------------------------------------|
| CH552 | 0x04 | USB controller control |
| CDC | 0x08 | USB CDC-ACM, a serial port on the client. |
| FIDO | 0x10 | A USB FIDO security token device, useful for FIDO-type applications. |
| CCID | 0x20 | USB CCID, a port for emulating a smart card |
| DEBUG | 0x40 | A USB HID special debug pipe. Useful for debug prints. |
You can turn on and off different endpoints dynamically by sending
commands to the `CH552` control endpoint. When the TKey starts only
the `CH552` and the `CDC` endpoints are active. To change this, send a
command to `CH552` in this form:
| *Name* | *Size* | *Comment* |
|----------|--------|-------------------------------|
| Command | 1B | Command to the CH552 firmware |
| Argument | 1B | Data for the command |
Commands:
| *Name* | *Value* | *Argument* |
|------------------|---------|----------------------|
| Enable endpoints | 0x01 | Bitmask of endpoints |
On top of the USB Mode Protocol is [the TKey Framing
Protocol](https://dev.tillitis.se/protocol/) which is described in the
Developer Handbook.
The firmware uses a protocol on top of this framing layer which is
used to bootstrap the application. All commands are initiated by the
used to load a device application. All commands are initiated by the
client. All commands receive a reply. See [Firmware
protocol](#firmware-protocol) for specific details.
protocol](http://dev.tillitis.se/protocol/#firmware-protocol) in the
Dev Handbook for specific details.
## Memory constraints
- ROM: 6 kByte.
- FW\_RAM: 2 kByte.
- RAM: 128 kByte.
| *Name* | *Size* | *FW mode* | *App mode* |
|---------|-----------|-----------|------------|
| ROM | 8 kByte | r-x | r |
| FW\_RAM | 4 kByte* | rw- | - |
| RAM | 128 kByte | rwx | rwx |
* FW\_RAM is divided into the following areas:
- fw stack: 3000 bytes.
- resetinfo: 256 bytes.
- .data and .bss: 840 bytes.
## Firmware behaviour
The purpose of the firmware is to load, measure, and start an
application received from the client over the USB/UART.
The purpose of the firmware is to:
1. Load, measure, and start an application received from the client
over the USB/UART or from one of two flash app slots.
2. Provide functionality to run only app's with specific BLAKE2s
digests.
3. Provide system calls to access the filesystem and get other data.
The firmware binary is part of the FPGA bitstream as the initial
values of the Block RAMs used to construct the `FW_ROM`. The `FW_ROM`
start address is located at `0x0000_0000` in the CPU memory map, which
is the CPU reset vector.
values of the Block RAMs used to construct the ROM. The ROM is located
at `0x0000_0000`. This is also the CPU reset vector.
### Reset type
When the TKey is started or resetted it can load an app from different
sources. We call this the reset type. Reset type is located in the
resetinfo part of FW\_RAM. The different reset types loads and start
an app from:
1. Flash slot 0 (default): `FLASH0` with a specific app hash defined
in a constant in firmware.
2. Flash slot 1: `FLASH1`.
3. Flash slot 0 with a specific app hash left from previous app:
`FLASH0_VER`
4. Flash slot 1 with a specific app hash left from previous app:
`FLASH1_VER`.
5. Client: `CLIENT`.
6. Client with a specific app hash left from previous app:
`CLIENT_VER`.
### Firmware state machine
This is the state diagram of the firmware. There are only four states.
Change of state occur when we receive specific I/O or a fatal error
occurs.
```mermaid
stateDiagram-v2
S1: initial
S2: loading
S3: running
SE: failed
S0: INITIAL
S1: WAITCOMMAND
S2: LOADING
S3: LOAD_FLASH
S4: LOAD_FLASH_MGMT
S5: START
SE: FAIL
[*] --> S1
[*] --> S0
S1 --> S1: Commands
S0 --> S1
S0 --> S4: Default
S0 --> S3
S0 --> SE: Unknown reset type
S1 --> S1: Other commands
S1 --> S2: LOAD_APP
S1 --> SE: Error
S2 --> S2: LOAD_APP_DATA
S2 --> S3: Last block received
S2 --> S5: Last block received
S2 --> SE: Error
S3 --> [*]
S3 --> S5
S3 --> SE
S4 --> S5
S4 --> SE
SE --> [*]
S5 --> [*]
```
States:
- `initial` - At start. Allows the commands `NAME_VERSION`, `GET_UDI`,
`LOAD_APP`.
- `loading` - Expect application data. Allows only the command
`LOAD_APP_DATA`.
- `run` - Computes CDI and starts the application. Allows no commands.
- `fail` - Stops waiting for commands, flashes LED forever. Allows no
commands.
- *INITIAL*: Transitions to next state through reset type left in
`FW_RAM`.
- *WAITCOMMAND*: Waiting for initial commands from client. Allows the
commands `NAME_VERSION`, `GET_UDI`, `LOAD_APP`.
- *LOADING*: Expecting application data from client. Allows only the
command `LOAD_APP_DATA` to continue loading the device app.
- *LOAD_FLASH*: Loading an app from flash. Allows no commands.
- *LOAD_FLASH_MGMT*: Loading an app from flash and registering it as a
prospective managment app. Allows no commands.
- *START*: Computes CDI. Possibly verifies app. Starts the
application. Does not return to firmware. Allows no commands.
- *FAIL* - Halts CPU. Allows no commands.
Commands in state `initial`:
Allowed data in state *INITIAL*:
| *reset type* | *next state* |
|--------------|-------------------|
| `FLASH0` | *LOAD_FLASH_MGMT* |
| `FLASH1` | *LOAD_FLASH* |
| `FLASH0_VER` | *LOAD_FLASH* |
| `FLASH1_VER` | *LOAD_FLASH* |
| `CLIENT` | *WAITCOMMAND* |
| `CLIENT_VER` | *WAITCOMMAND* |
| unknown | *FAIL* |
I/O in state *LOAD_FLASH*:
| *I/O* | *next state* |
|--------------------|--------------|
| Last app data read | *START* |
I/O in state *LOAD_FLASH_MGMT*:
| *I/O* | *next state* |
|--------------------|--------------|
| Last app data read | *START* |
Commands in state *WAITCOMMAND*:
| *command* | *next state* |
|-----------------------|--------------|
|-----------------------|--------------------------------------------|
| `FW_CMD_NAME_VERSION` | unchanged |
| `FW_CMD_GET_UDI` | unchanged |
| `FW_CMD_LOAD_APP` | `loading` |
| `FW_CMD_LOAD_APP` | *LOADING* or unchanged on invalid app size |
| | |
Commands in state `loading`:
Commands in state *LOADING*:
| *command* | *next state* |
|------------------------|----------------------------------|
| `FW_CMD_LOAD_APP_DATA` | unchanged or `run` on last chunk |
|------------------------|------------------------------------|
| `FW_CMD_LOAD_APP_DATA` | unchanged or *START* on last chunk |
No other states allows commands.
See [Firmware protocol in the Dev
Handbook](http://dev.tillitis.se/protocol/#firmware-protocol) for the
definition of the specific commands and their responses.
State changes from "initial" to "loading" when receiving `LOAD_APP`,
which also sets the size of the number of data blocks to expect. After
that we expect several `LOAD_APP_DATA` commands until the last block
is received, when state is changed to "running".
Plain text explanation of the states:
In "running", the loaded device app is measured, the Compound Device
Identifier (CDI) is computed, we do some cleanup of firmware data
structures, flip to application mode, and finally start the app, which
ends the firmware state machine.
- *INITIAL*: Start here. Check the `FW_RAM` for the `resetinfo` type
for what to do next.
The device app is now running in application mode. There is no other
means of getting back from application mode to firmware mode than
resetting/power cycling the device. Note that ROM is still accessible
in the memory map, so it's still possible to execute firmware code in
application mode, but with no privileged access.
For type `FLASH0` transition to *LOAD_FLASH_MGMT* because the app in
slot 0 is considered a special management app. For all other types
beginning with `FLASH*` transition to *LOAD_FLASH* to load an
ordinary app from flash.
Firmware loads the application at the start of RAM (`0x4000_0000`). It
uses the special FW\_RAM for its own stack.
For type `CLIENT*` transitionto *WAITCOMMAND* to expect a device app
from the client.
When the firmware starts it clears all FW\_RAM, then sets up a stack
there before jumping to `main()`.
If type is unknown, error out.
- *LOAD_FLASH*: Load device app from flash into RAM, app slot taken
from context. Compute a BLAKE2s digest over the entire app.
Transition to *START*.
- *LOAD_FLASH_MGMT*: Load device app from flash into RAM, app slot
always 0. Compute a BLAKE2s digest over the entire app. Register the
app as a prospective management app. Transition to *START*.
- *WAITCOMMAND*: Wait for commands from the client. Transition to
*LOADING* on `LOAD_APP` command, which also sets the size of the
number of data blocks to expect.
- *LOADING*: Wait for several `LOAD_APP_DATA` commands until the last
block is received, then transition to *START*.
- *START*: Compute the Compound Device Identifier (CDI). If we have a
registered verification digest, verify that the app we are about to
start is indeed the correct app. This also means that a prospective
management app is now verified.
Clean up firmware data structures, enable the system calls, and
start the app, which ends the firmware state machine. Hardware
guarantees that we leave firmware mode automatically when the
program counter leaves ROM.
- *FAIL*: Execute an illegal instruction which traps the CPU. Hardware
detects a trapped CPU and blinks the status LED in red until power
loss. No further instructions are executed.
After leaving *START* the device app is now running in application
mode. We can, however, return to firmware mode (excepting access to
the UDS) by doing system calls. Note that ROM is still readable, but
is now hardware protected from execution, except through the system
call mechanism.
If during this whole time any commands are received which are not
allowed in the current state, or any errors occur, we enter the *FAIL*
state.
### Golden path from start to default app
Firmware will load the device application at the start of RAM
(`0x4000_0000`) from either flash or from the client through the UART.
Firmware is using a part of the FW\_RAM for its own stack.
When reset is released, the CPU starts executing the firmware. It
begins 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,
except the part reserved for the resetinfo area, sets up a stack for
itself there, and then jumps to `main()`. Also included in the
assembly part of firmware is an interrupt handler for the system
calls, but the handler is not yet enabled.
Beginning at `main()` it 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).
Typical expected use scenario:
Firmware then proceeds to:
1. The client sends the `FW_CMD_LOAD_APP` command with the size of
1. Read the partition table from flash and store in FW\_RAM.
2. Reset the CH552 USB controller to a known state, only allowing the
CDC USB endpoint and the internal command channel between the CPU
and the CH552.
3. Check the special resetinfo area in FW\_RAM for reset type. Type
zero means default behaviour, load from flash app slot 0, expecting
the app there to have a specific hardcoded BLAKE2s digest.
4. Load app data from flash slot 0 into RAM.
5. Compute a BLAKE2s digest of the loaded app.
6. Compare the computed digest against the allowed app digest
hardcoded in the firmware. If it's not equal, halt CPU.
7. [Start the device app](#start-the-device-app).
### Start the device app
1. Check if there is a verification digest left from the previous app
in the resetinfo. If it is, compare with the loaded app's already
computed digest. Halt CPU if different.
2. Compute the Compound Device Identifier
([CDI]((#compound-device-identifier-computation))) by doing a
BLAKE2s using the Unique Device Secret (UDS), the application
digest, and any User Supplied Secret (USS) digest already received.
3. Write the start address of the device app, currently `0x4000_0000`,
to `APP_ADDR` and the size of the loaded binary to `APP_SIZE` to
let the device application know where it is loaded and how large it
is, if it wants to relocate in RAM.
4. Clear the stack part of `FW_RAM`.
5. Enable system call interrupt handler.
6. Start the application by jumping to the contents of `APP_ADDR`.
Hardware automatically switch from firmware mode to application
mode. In this mode some memory access is restricted, e.g. some
addresses are inaccessible (`UDS`), and some are switched from
read/write to read-only (see [the memory
map](https://dev.tillitis.se/memory/)).
### Management app, chaining apps and verified boot
Normally, the TKey measures a device app and mixes it together with
the Unique Device Secret in hardware to produce the [Compound Device
Identifier]((#compound-device-identifier-computation)). The CDI can
then be used for creating key material. However, since any key
material created like this will change if the app is changed even the
slightest, this make it hard to upgrade apps and keep the key
material.
This is where a combination of measured boot and verified boot comes
in!
To support verified boot the firmware supports reset types with
verification. This means that the firmware will load an app as usual
either from flash or from the client, but before starting the app it
will verify the new app's computed digest with a verification digest.
The verification digest can either be stored in the firmware itself or
left to it from a previous app, a verified boot loader app.
Such a verified boot loader app:
- Might be loaded from either flash or client.
- Typically includes a security policy, for instance a public key and
code to check a crytographic signature.
- Can be specifically trusted by firmware to be able to do filesystem
management to be able to update an app slot on flash. Add the app's
digest to `allowed_app_digest` in `mgmt_app.c` to allow it to use
`PRELOAD_DELETE`, `PRELOAD_STORE`, `PRELOAD_STORE_FIN`, and
`PRELOAD_GET_DIGSIG`.
It works like this:
- The app reads a digest of the next app in the chain and the
signature over the digest from either the filesystem (syscall
`PRELOAD_GET_DIGSIG`) or sent from the client.
- If the signature provided over the digest is verified against the
public key the app use the system call `RESET` with the reset type
set to `START_FLASH0_VER`, `START_FLASH1_VER`, or `START_CLIENT_VER`
depending on where it wants the next app to start from. It also
sends the now verified app digest to the firmware in the same system
call.
- The key is reset and firmware starts again. It checks:
1. The reset type. Start from client or a slot in the filesystem?
2. The expected digest of the next app.
- Firmware loads the app from the expected source.
- Firmware refuses to start if the loaded app has a different digest.
- If the app was allowed to start it can now use something
deterministic left for it in resetinfo by the verified boot loader
app as a seed for it's key material and no longer use CDI for the
purpose.
We propose that a loader app can derive the seed for the next app by
creating a shared secret, perhaps something as easy as:
```
secret = blake2s(cdi, "name-of-next-app")
```
The loader shares the secret with the next app by putting it in the
part of `resetinfo` that is reserved for inter-app communication.
The next app can now use the secret as a seed for it's own key
material. Depending on the app's behaviour and the number of keys it
needs it can derive more keys, for instance by having nonces stored on
its flash area and doing:
```
secret1 = blake2s(secret, nonce1)
secret2 = blake2s(secret, nonce2)
...
```
Now it can create many secrets deterministically, as long as there is
some space left on flash for the nonces and all of them can be traced
to the measured identity of the loader app, giving all the features of
the measured boot system.
### App loaded from client
The default is always to start from a verified app in flash slot
0. To be able to load an app from the client you have to send
something to the app to reset the TKey with a reset type of
`START_CLIENT` or `START_CLIENT_VER`.
After reset, firmware will:
1. Wait for data coming in through the UART.
2. The client sends the `FW_CMD_LOAD_APP` command with the size of
the device app and the optional 32 byte hash of the user-supplied
secret as arguments and gets a `FW_RSP_LOAD_APP` back. After
using this it's not possible to restart the loading of an
application.
2. If the the client receive a sucessful response, it will send
multiple `FW_CMD_LOAD_APP_DATA` commands, together containing the
full application.
3. On a sucessful response, the client will send multiple
`FW_CMD_LOAD_APP_DATA` commands, together containing the full
application.
3. On receiving`FW_CMD_LOAD_APP_DATA` commands the firmware places
4. On receiving`FW_CMD_LOAD_APP_DATA` commands the firmware places
the data into `0x4000_0000` and upwards. The firmware replies
with a `FW_RSP_LOAD_APP_DATA` response to the client for each
received block except the last data block.
4. When the final block of the application image is received with a
5. When the final block of the application image is received with a
`FW_CMD_LOAD_APP_DATA`, the firmware measure the application by
computing a BLAKE2s digest over the entire application. Then
firmware send back the `FW_RSP_LOAD_APP_DATA_READY` response
containing the digest.
5. The Compound Device Identifier
([CDI]((#compound-device-identifier-computation))) is then
computed by doing a new BLAKE2s using the Unique Device Secret
(UDS), the application digest, and any User Supplied Secret
(USS).
6. The start address of the device app, currently `0x4000_0000`, is
written to `APP_ADDR` and the size of the binary to `APP_SIZE` to
let the device application know where it is loaded and how large
it is, if it wants to relocate in RAM.
7. The firmware now clears the special `FW_RAM` where it keeps it
stack. After this it performs no more function calls and uses no
more automatic variables.
8. Firmware starts the application by first switching to from
firmware mode to application mode by writing to the `SYSTEM_MODE_CTRL`
register. In this mode the MMIO region is restricted, e.g. some
registers are removed (`UDS`), and some are switched from
read/write to read-only (see [the memory
map](https://dev.tillitis.se/memory/)).
Then the firmware jumps to what is in `APP_ADDR` which starts the
application.
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.
6. [Start the device app](#start-the-device-app).
### User-supplied Secret (USS)
@ -221,65 +500,231 @@ CDI = blake2s(UDS, blake2s(app), USS)
In an ideal world, software would never be able to read UDS at all and
we would have a BLAKE2s function in hardware that would be the only
thing able to read the UDS. Unfortunately, we couldn't fit a BLAKE2s
implementation in the FPGA at this time.
implementation in the FPGA.
The firmware instead does the CDI computation using the special
firmware-only `FW_RAM` which is invisible after switching to app mode.
We keep the entire firmware stack in `FW_RAM` and clear it just before
switching to app mode just in case.
We keep the entire firmware stack in `FW_RAM` and clear the stack just
before switching to app mode just in case.
We sleep for a random number of cycles before reading out the UDS,
call `blake2s_update()` with it and then immediately call
`blake2s_update()` again with the program digest, destroying the UDS
stored in the internal context buffer. UDS should now not be in
`FW_RAM` anymore. We can read UDS only once per power cycle so UDS
should now not be available to firmware at all.
should now not be available even to firmware.
Then we continue with the CDI computation by updating with an optional
USS and then finalizing the hash, storing the resulting digest in
USS digest and finalizing the hash, storing the resulting digest in
`CDI`.
We also clear the entire `FW_RAM` where the stack lived, including the
BLAKE2s context with the UDS, very soon after that, just before
jumping to the application.
### System calls
### Firmware services
The firmware exposes a BLAKE2s function through a function pointer
located in MMIO `BLAKE2S` (see [memory
map](system_description.md#memory-mapped-hardware-functions)) with the
with function signature:
```c
int blake2s(void *out, unsigned long outlen, const void *key,
unsigned long keylen, const void *in, unsigned long inlen,
blake2s_ctx *ctx);
The firmware provides a system call mechanism through the use of the
PicoRV32 interrupt handler. They are triggered by writing to the
trigger address: 0xe1000000. It's typically done with a function
signature like this:
```
where `blake2s_ctx` is:
```c
typedef struct {
uint8_t b[64]; // input buffer
uint32_t h[8]; // chained state
uint32_t t[2]; // total number of bytes
size_t c; // pointer for b[]
size_t outlen; // digest size
} blake2s_ctx;
int syscall(uint32_t number, uint32_t arg1, uint32_t arg2,
uint32_t arg3);
```
The `libcommon` library in
[tkey-libs](https://github.com/tillitis/tkey-libs/)
has a wrapper for using this function called `blake2s()` which needs
to be maintained if you do any changes to the firmware call.
Arguments are system call number and up to 6 generic arguments passed
to the system call handler. The caller should place the system call
number in the a0 register and the arguments in registers a1 to a7
according to the RISC-V calling convention. The caller is responsible
for saving and restoring registers.
The syscall handler returns execution on the next instruction after
the store instruction to the trigger address. The return value from
the syscall is now available in x10 (a0).
The syscall numbers are kept in `syscall_num.h`. The syscalls are
handled in `syscall_handler()` in `syscall_handler.c`.
#### `RESET`
```
struct reset {
uint32_t type; // Reset type
uint8_t app_digest[32]; // Digest of next app in chain to verify
uint8_t next_app_data[220]; // Data to leave around for next app
};
struct reset rst;
uint32_t len; // Length of data in next_app_data.
syscall(TK1_SYSCALL_RESET, (uint32_t)&rst, len, 0);
```
Resets the TKey. Does not return.
You can pass data to the firmware about the reset type `type` and a
digest that the next app must have. You can also leave some data to
the next app in the chain in `next_app_data`.
The types of reset are defined in `reset.h`:
| *Name* | *Comment* |
|--------------------|------------------------------------------------|
| `START_FLASH0` | Load next app from flash slot 0 |
| `START_FLASH1` | Load next app from flash slot 1 |
| `START_FLASH0_VER` | Load next app from flash slot 0, but verify it |
| `START_FLASH1_VER` | Load next app from flash slot 1, but verify it |
| `START_CLIENT` | Load next app from client |
| `START_CLIENT_VER` | Load next app from client |
#### `ALLOC_AREA`
```
syscall(TK1_SYSCALL_ALLOC_AREA, 0, 0, 0);
```
Allocate a flash area for the current app. Returns 0 on success.
#### `DEALLOC_AREA`
```
syscall(TK1_SYSCALL_DEALLOC_AREA, 0, 0, 0);
```
Free an already allocated flash area for the current app. Returns 0 on
success.
#### `WRITE_DATA`
```
uint32_t offset = 0;
uint8_t buf[17];
syscall(TK1_SYSCALL_WRITE_DATA, offset, (uint32_t)buf, sizeof(buf))
```
Write data in `buf` to the app's flash area at byte `offset` within
the area. Returns 0 on success.
At most 4096 bytes can be written at once and `offset` must be a
multiple of 4096 bytes.
#### `READ_DATA`
```
uint32_t offset = 0;
uint8_t buf[17];
syscall(TK1_SYSCALL_READ_DATA, offset, (uint32_t)buf, sizeof(buf);
```
Read into `buf` at byte `offset` from the app's flash area.
#### `ERASE_DATA`
```
uint32_t offset = 0;
uint32_t size = 4096;
syscall(TK1_SYSCALL_ERASE_DATA, offset, size, 0);
```
Erase `size` bytes from `offset` within the area. Returns 0 on
success.
Both `size` and `offset` must be a multiple of 4096 bytes.
#### `PRELOAD_DELETE`
```
syscall(TK1_SYSCALL_PRELOAD_DELETE, 0, 0, 0);
```
Delete the app in flash slot 1. Returns 0 on success. Only available
for the verified management app.
#### `PRELOAD_STORE`
```
uint8_t *appbinary;
uint32_t offset;
uint32_t size;
syscall(TK1_SYSCALL_PRELOAD_STORE, offset, (uint32_t)appbinary,
size);
```
Store an app, or possible just a block of an app, from the `appbinary`
buffer in flash slot 1 at byte `offset`.
If you can't fit your entire app in the buffer, call `PRELOAD_STORE`
many times as you receive the binary from the client. Returns 0 on
success.
At most 4096 bytes can be written at once and `offset` must be a
multiple of 4096 bytes.
Only available for the verified management app.
#### `PRELOAD_STORE_FIN`
```
uint8_t app_digest[32];
uint8_t app_signature[64];
size_t app_size;
syscall(TK1_SYSCALL_PRELOAD_STORE_FIN, app_size,
(uint32_t)app_digest, (uint32_t)app_signature)
```
Finalize storing of an app where the complete binary size is
`app_size` in flash slot 1. Returns 0 on success. Only available for
the verified management app.
Compute a BLAKE2s hash digest over the entire binary. Pass the result
in `app_digest`.
Sign `app_digest` with your Ed25519 private key and pass the
resulting signature in `app_signature`.
#### `PRELOAD_GET_DIGSIG`
```
uint8_t app_digest[32];
uint8_t app_signature[64];
syscall(TK1_SYSCALL_PRELOAD_GET_DIGSIG, (uint32_t)app_digest,
(uint32_t)app_signature, 0);
```
Copies the digest and signature of app in flash slot 1 to `app_digest`
and `app_signature`. Returns 0 on success. Only available for the
verified management app.
#### `STATUS`
```
syscall(TK1_SYSCALL_PRELOAD_STATUS, 0, 0, 0);
```
Returns filesystem status. Non-zero when problems have been detected,
so far only that the first copy of the partition table didn't pass
checks.
#### `GET_VIDPID`
```
syscall(TK1_SYSCALL_PRELOAD_STATUS, 0, 0, 0);
```
Returns Vendor and Product ID. Notably the serial number is not
returned, so a device app can't identify what particular TKey it is
running on.
## Developing firmware
Standing in `hw/application_fpga/` you can run `make firmware.elf` to
build just the firmware. You don't need all the FPGA development
tools. See [the Developer Handbook](https://dev.tillitis.se/tools/)
for the tools you need. The easiest is probably to use your OCI image,
for the tools you need. The easiest is probably to use our OCI image,
`ghcr.io/tillitis/tkey-builder`.
[Our version of qemu](https://dev.tillitis.se/tools/#qemu-emulator) is
@ -287,11 +732,93 @@ also useful for debugging the firmware. You can attach GDB, use
breakpoints, et cetera.
There is a special make target for QEMU: `qemu_firmware.elf`, which
sets `-DQEMU_CONSOLE`, so you can use plain debug prints using the
helper functions in `lib.c` like `htif_puts()` `htif_putinthex()`
`htif_hexdump()` and friends. Note that these functions are only
usable in qemu and that you might need to `make clean` before
building, if you have already built before.
sets `-DQEMU_DEBUG`, so you can debug prints using the `debug_*()`
functions. Note that these functions are only usable in QEMU and that
you might need to `make clean` before building, if you have already
built before.
To build a flash image file suitable for use with qemu, use the
`tools/tkeyimage` program. See its documentation.
If you want debug prints to show up on the special TKey HID debug
endpoint instead, define `-DTKEY_DEBUG`. This might mean you can't fit
the firmware in the ROM space available, however. You will get a
warning if it doesn't fit. In that case, just use explicit
`puts(IO_DEBUG, ...)` or `puts(IO_CDC, ...)` and so on.
Note that if you use `TKEY_DEBUG` you *must* have something listening
on the corresponding HID device. It's usually the last HID device
created. On Linux, for instance, this means the last reported hidraw
in `dmesg` is the one you should do `cat /dev/hidrawX` on.
### tkey-libs
Most of the utility functions that the firmware use lives in
`tkey-libs`. The canonical place where you can find tkey-libs is at:
https://github.com/tillitis/tkey-libs
but we have vendored it in for firmware use in `../tkey-libs`. See top
README for how to update.
### Preparing the filesystem
The TKey supports a simple filesystem. This filesystem must be
initiated before starting for the first time. You need a [TKey
Programmer Board](https://shop.tillitis.se/products/tkey-dev-kit) for
this part.
If you just want to build and flash the bitstream, the testloadapp in
app slot 0, and the partition table copies in one go, place the TKey
Unlocked in the TP1, then:
Using Podman, from the top level directory:
```
cd contrib
make flash
```
Using native tools:
```
cd hw/application_fpga
make prog_flash
```
If you want to prepare the filesystem yourself:
1. Choose your pre-loaded app. You *must* have a pre-loaded app, for
example `testloadapp`. Build it with the OCI image we use. The
binary needs to produce the BLAKE2s digest in `allowed_app_digest`
`tk1/mgmt_app.c`.
2. Write the filesystem to flash:
```
$ cd ../tools
$ ./load_preloaded_app.sh 0 ../fw/testloadapp/testloadapp.bin
```
If you want to use a different pre-loaded app you have to
1. Check the BLAKE2s digest of the app. You can use `tools/b2s` to
compute it.
2. Update the `allowed_app_digest` in `tk1/mgmt_app.c`.
3. Create a new `default_partition.bin` using the
`tools/tkeyimage`, typically:
```
$ tkeyimage -app0 path/to/your/app.bin -o default_partition.bin
```
4. Flash the filesystem image:
```
$ ./load_preloaded_app.sh 0 path/to/your/app.bin
```
### Test firmware
@ -304,5 +831,9 @@ terminal program to the serial port device, even if it's running in
qemu. It waits for you to type a character before starting the tests.
It needs to be compiled with `-Os` instead of `-O2` in `CFLAGS` in the
ordinary `application_fpga/Makefile` to be able to fit in the 6 kByte
ROM.
ordinary `application_fpga/Makefile` to be able to fit in ROM.
### Test apps
There are a couple of test apps, see `../apps`.

View file

@ -1,5 +1,5 @@
# Uses ../.clang-format
FMTFILES=main.c
FMTFILES=*.[ch]
.PHONY: fmt
fmt:
clang-format --dry-run --ferror-limit=0 $(FMTFILES)

View file

@ -1,13 +1,16 @@
/*
* Copyright (C) 2022, 2023 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
// Copyright (C) 2022, 2023 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stddef.h>
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/io.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include "../tk1/blake2s/blake2s.h"
#include "../tk1/lib.h"
#include "../tk1/proto.h"
#include "../tk1/types.h"
#include "../tk1_mem.h"
#define USBMODE_PACKET_SIZE 64
// clang-format off
volatile uint32_t *tk1name0 = (volatile uint32_t *)TK1_MMIO_TK1_NAME0;
@ -15,80 +18,27 @@ volatile uint32_t *tk1name1 = (volatile uint32_t *)TK1_MMIO_TK1_NAME1;
volatile uint32_t *uds = (volatile uint32_t *)TK1_MMIO_UDS_FIRST;
volatile uint32_t *cdi = (volatile uint32_t *)TK1_MMIO_TK1_CDI_FIRST;
volatile uint32_t *udi = (volatile uint32_t *)TK1_MMIO_TK1_UDI_FIRST;
volatile uint32_t *system_mode_ctrl = (volatile uint32_t *)TK1_MMIO_TK1_SYSTEM_MODE_CTRL;
volatile uint8_t *fw_ram = (volatile uint8_t *)TK1_MMIO_FW_RAM_BASE;
volatile uint32_t *system_reset = (volatile uint32_t *)TK1_MMIO_TK1_SYSTEM_RESET;
volatile uint32_t *timer = (volatile uint32_t *)TK1_MMIO_TIMER_TIMER;
volatile uint32_t *timer_prescaler = (volatile uint32_t *)TK1_MMIO_TIMER_PRESCALER;
volatile uint32_t *timer_status = (volatile uint32_t *)TK1_MMIO_TIMER_STATUS;
volatile uint32_t *timer_ctrl = (volatile uint32_t *)TK1_MMIO_TIMER_CTRL;
volatile uint32_t *trng_status = (volatile uint32_t *)TK1_MMIO_TRNG_STATUS;
volatile uint32_t *trng_entropy = (volatile uint32_t *)TK1_MMIO_TRNG_ENTROPY;
volatile uint32_t *fw_blake2s_addr = (volatile uint32_t *)TK1_MMIO_TK1_BLAKE2S;
// clang-format on
#define UDS_WORDS 8
#define UDI_WORDS 2
#define CDI_WORDS 8
void *memcpy(void *dest, const void *src, size_t n)
{
uint8_t *src_byte = (uint8_t *)src;
uint8_t *dest_byte = (uint8_t *)dest;
for (int i = 0; i < n; i++) {
dest_byte[i] = src_byte[i];
}
return dest;
}
void puts(char *reason)
{
for (char *c = reason; *c != '\0'; c++) {
writebyte(*c);
}
}
void putsn(char *p, int n)
{
for (int i = 0; i < n; i++) {
writebyte(p[i]);
}
}
void puthex(uint8_t c)
{
unsigned int upper = (c >> 4) & 0xf;
unsigned int lower = c & 0xf;
writebyte(upper < 10 ? '0' + upper : 'a' - 10 + upper);
writebyte(lower < 10 ? '0' + lower : 'a' - 10 + lower);
}
void puthexn(uint8_t *p, int n)
{
for (int i = 0; i < n; i++) {
puthex(p[i]);
puthex(IO_CDC, p[i]);
}
}
void hexdump(void *buf, int len)
{
uint8_t *byte_buf = (uint8_t *)buf;
for (int i = 0; i < len; i++) {
puthex(byte_buf[i]);
if (i % 2 == 1) {
writebyte(' ');
}
if (i != 1 && i % 16 == 1) {
puts("\r\n");
}
}
puts("\r\n");
}
void reverseword(uint32_t *wordp)
{
*wordp = ((*wordp & 0xff000000) >> 24) | ((*wordp & 0x00ff0000) >> 8) |
@ -124,19 +74,19 @@ int check_fwram_zero_except(unsigned int offset, uint8_t expected_val)
if (i == offset) {
if (val != expected_val) {
failed_now = 1;
puts(" wrong value at: ");
puts(IO_CDC, " wrong value at: ");
}
} else {
if (val != 0) {
failed_now = 1;
puts(" not zero at: ");
puts(IO_CDC, " not zero at: ");
}
}
if (failed_now) {
failed = 1;
reverseword(&addr);
puthexn((uint8_t *)&addr, 4);
puts("\r\n");
puts(IO_CDC, "\r\n");
}
}
return failed;
@ -144,19 +94,17 @@ int check_fwram_zero_except(unsigned int offset, uint8_t expected_val)
void failmsg(char *s)
{
puts("FAIL: ");
puts(s);
puts("\r\n");
puts(IO_CDC, "FAIL: ");
puts(IO_CDC, s);
puts(IO_CDC, "\r\n");
}
int main(void)
{
// Function pointer to blake2s()
volatile int (*fw_blake2s)(void *, unsigned long, const void *,
unsigned long, const void *, unsigned long,
blake2s_ctx *);
uint8_t in = 0;
uint8_t available = 0;
enum ioend endpoint = IO_NONE;
uint8_t in;
// Hard coded test UDS in ../../data/uds.hex
// clang-format off
uint32_t uds_test[8] = {
@ -172,19 +120,27 @@ int main(void)
// clang-format on
// Wait for terminal program and a character to be typed
in = readbyte();
if (readselect(IO_CDC, &endpoint, &available) < 0) {
// readselect failed! I/O broken? Just redblink.
assert(1 == 2);
}
puts("\r\nI'm testfw on:");
if (read(IO_CDC, &in, 1, 1) < 0) {
// read failed! I/O broken? Just redblink.
assert(1 == 2);
}
puts(IO_CDC, "\r\nI'm testfw on:");
// Output the TK1 core's NAME0 and NAME1
uint32_t name;
wordcpy_s(&name, 1, (void *)tk1name0, 1);
reverseword(&name);
putsn((char *)&name, 4);
puts(" ");
write(IO_CDC, (const uint8_t *)&name, 4);
puts(IO_CDC, " ");
wordcpy_s(&name, 1, (void *)tk1name1, 1);
reverseword(&name);
putsn((char *)&name, 4);
puts("\r\n");
write(IO_CDC, (const uint8_t *)&name, 4);
puts(IO_CDC, "\r\n");
uint32_t zeros[8];
memset(zeros, 0, 8 * 4);
@ -200,11 +156,11 @@ int main(void)
anyfailed = 1;
}
puts("\r\nUDS: ");
puts(IO_CDC, "\r\nUDS: ");
for (int i = 0; i < UDS_WORDS * 4; i++) {
puthex(((uint8_t *)uds_local)[i]);
puthex(IO_CDC, ((uint8_t *)uds_local)[i]);
}
puts("\r\n");
puts(IO_CDC, "\r\n");
if (!memeq(uds_local, uds_test, UDS_WORDS * 4)) {
failmsg("UDS not equal to test UDS");
anyfailed = 1;
@ -247,7 +203,7 @@ int main(void)
}
// Test FW_RAM.
puts("\r\nTesting FW_RAM (takes 15s on hw)...\r\n");
puts(IO_CDC, "\r\nTesting FW_RAM (takes 50s on hw)...\r\n");
for (unsigned int i = 0; i < TK1_MMIO_FW_RAM_SIZE; i++) {
zero_fwram();
*(volatile uint8_t *)(TK1_MMIO_FW_RAM_BASE + i) = 0x42;
@ -257,60 +213,7 @@ int main(void)
}
}
uint32_t sw = *system_mode_ctrl;
if (sw != 0) {
failmsg("system_mode_ctrl is not 0 in fw mode");
anyfailed = 1;
}
// Store function pointer to blake2s() so it's reachable from app
*fw_blake2s_addr = (uint32_t)blake2s;
// Turn on application mode.
// -------------------------
*system_mode_ctrl = 1;
sw = *system_mode_ctrl;
if (sw != 0xffffffff) {
failmsg("system_mode_ctrl is not 0xffffffff in app mode");
anyfailed = 1;
}
// Should NOT be able to read from UDS in app-mode.
wordcpy_s(uds_local, UDS_WORDS, (void *)uds, UDS_WORDS);
if (!memeq(uds_local, zeros, UDS_WORDS * 4)) {
failmsg("Read from UDS in app-mode");
anyfailed = 1;
}
// Should NOT be able to read from UDI in app-mode.
wordcpy_s(udi_local, UDI_WORDS, (void *)udi, UDI_WORDS);
if (!memeq(udi_local, zeros, UDI_WORDS * 4)) {
failmsg("Read from UDI in app-mode");
anyfailed = 1;
}
uint32_t cdi_local[CDI_WORDS];
uint32_t cdi_local2[CDI_WORDS];
wordcpy_s(cdi_local, CDI_WORDS, (void *)cdi, CDI_WORDS);
// Write to CDI should NOT have any effect in app mode.
wordcpy_s((void *)cdi, CDI_WORDS, zeros, CDI_WORDS);
wordcpy_s(cdi_local2, CDI_WORDS, (void *)cdi, CDI_WORDS);
if (!memeq(cdi_local, cdi_local2, CDI_WORDS * 4)) {
failmsg("Write to CDI in app-mode");
anyfailed = 1;
}
// Test FW_RAM.
*fw_ram = 0x21;
if (*fw_ram == 0x21) {
failmsg("Write and read FW RAM in app-mode");
anyfailed = 1;
}
puts("\r\nTesting timer... 3");
puts(IO_CDC, "\r\nTesting timer... 3");
// Matching clock at 24 MHz, giving us timer in seconds
*timer_prescaler = 24 * 1000000;
@ -321,7 +224,7 @@ int main(void)
while (*timer_status & (1 << TK1_MMIO_TIMER_STATUS_RUNNING_BIT)) {
}
// Now timer has expired and is ready to run again
puts(" 2");
puts(IO_CDC, " 2");
// Test to interrupt a timer - and reads from timer register
// Starting 10s timer and interrupting it in 3s...
@ -334,7 +237,7 @@ int main(void)
// Stop the timer
*timer_ctrl = (1 << TK1_MMIO_TIMER_CTRL_STOP_BIT);
puts(" 1. done.\r\n");
puts(IO_CDC, " 1. done.\r\n");
if (*timer_status & (1 << TK1_MMIO_TIMER_STATUS_RUNNING_BIT)) {
failmsg("Timer didn't stop");
@ -346,41 +249,16 @@ int main(void)
anyfailed = 1;
}
// Testing the blake2s MMIO in app mode
fw_blake2s = (volatile int (*)(void *, unsigned long, const void *,
unsigned long, const void *,
unsigned long, blake2s_ctx *)) *
fw_blake2s_addr;
char msg[17] = "dldlkjsdkljdslsdj";
uint32_t digest0[8];
uint32_t digest1[8];
blake2s_ctx b2s_ctx;
blake2s(&digest0[0], 32, NULL, 0, &msg, 17, &b2s_ctx);
fw_blake2s(&digest1[0], 32, NULL, 0, &msg, 17, &b2s_ctx);
puts("\r\ndigest #0: \r\n");
hexdump((uint8_t *)digest0, 32);
puts("digest #1: \r\n");
hexdump((uint8_t *)digest1, 32);
if (!memeq(digest0, digest1, 32)) {
failmsg("Digests not the same");
anyfailed = 1;
}
// Check and display test results.
puts("\r\n--> ");
puts(IO_CDC, "\r\n--> ");
if (anyfailed) {
puts("Some test FAILED!\r\n");
puts(IO_CDC, "Some test FAILED!\r\n");
} else {
puts("All tests passed.\r\n");
puts(IO_CDC, "All tests passed.\r\n");
}
puts("\r\nHere are 256 bytes from the TRNG:\r\n");
puts(IO_CDC, "\r\nHere are 256 bytes from the TRNG:\r\n");
for (int j = 0; j < 8; j++) {
for (int i = 0; i < 8; i++) {
while ((*trng_status &
@ -388,15 +266,28 @@ int main(void)
}
uint32_t rnd = *trng_entropy;
puthexn((uint8_t *)&rnd, 4);
puts(" ");
puts(IO_CDC, " ");
}
puts("\r\n");
puts(IO_CDC, "\r\n");
}
puts("\r\n");
puts(IO_CDC, "\r\n");
puts("Now echoing what you type...\r\n");
puts(IO_CDC, "Now echoing what you type...Type + to reset device\r\n");
for (;;) {
in = readbyte(); // blocks
writebyte(in);
if (readselect(IO_CDC, &endpoint, &available) < 0) {
// readselect failed! I/O broken? Just redblink.
assert(1 == 2);
}
if (read(IO_CDC, &in, 1, 1) < 0) {
// read failed! I/O broken? Just redblink.
assert(1 == 2);
}
if (in == '+') {
*system_reset = 1;
}
write(IO_CDC, &in, 1);
}
}

View file

@ -1,7 +1,5 @@
/*
* Copyright (C) 2022, 2023 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
// Copyright (C) 2022, 2023 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
.section ".text.init"
.globl _start
@ -38,7 +36,7 @@ _start:
li x30,0
li x31,0
/* Clear all RAM */
// Clear all RAM
li a0, 0x40000000 // TK1_RAM_BASE
li a1, 0x40020000 // TK1_RAM_BASE + TK1_RAM_SIZE
clear:
@ -46,9 +44,9 @@ clear:
addi a0, a0, 4
blt a0, a1, clear
/*
* For testfw we init stack at top of RAM
*/
// NOTE WELL
// For testfw we init stack at top of RAM
//
li sp, 0x40020000 // TK1_RAM_BASE + TK1_RAM_SIZE
call main

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

@ -0,0 +1,77 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdbool.h>
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include "auth_app.h"
#include "blake2s/blake2s.h"
#include "partition_table.h"
#include "rng.h"
static volatile uint32_t *cdi = (volatile uint32_t *)TK1_MMIO_TK1_CDI_FIRST;
// Calculates the authentication digest based on a supplied nonce and
// the CDI. Requires that the CDI is already calculated and stored
static void calculate_auth_digest(uint8_t *nonce, uint8_t *auth_digest)
{
assert(nonce != NULL);
assert(auth_digest != NULL);
blake2s_ctx ctx = {0};
// Generate a 16 byte authentication digest
int blake2err = blake2s_init(&ctx, 16, NULL, 0);
assert(blake2err == 0);
blake2s_update(&ctx, (const void *)cdi, 32);
blake2s_update(&ctx, nonce, 16);
blake2s_final(&ctx, auth_digest);
}
// Generates a 16 byte nonce
static void generate_nonce(uint32_t *nonce)
{
assert(nonce != NULL);
for (uint8_t i = 0; i < 4; i++) {
nonce[i] = rng_get_word();
}
return;
}
// Returns the authentication digest and random nonce. Requires that
// the CDI is already calculated and stored
void auth_app_create(struct auth_metadata *auth_table)
{
assert(auth_table != NULL);
uint8_t nonce[16] = {0};
uint8_t auth_digest[16] = {0};
generate_nonce((uint32_t *)nonce);
calculate_auth_digest(nonce, auth_digest);
memcpy_s(auth_table->authentication_digest, 16, auth_digest, 16);
memcpy_s(auth_table->nonce, 16, nonce, 16);
return;
}
bool auth_app_authenticate(struct auth_metadata *auth_table)
{
assert(auth_table != NULL);
uint8_t auth_digest[16] = {0};
calculate_auth_digest(auth_table->nonce, auth_digest);
if (memeq(auth_digest, auth_table->authentication_digest, 16)) {
return true;
}
return false;
}

View file

@ -0,0 +1,14 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef AUTH_APP_H
#define AUTH_APP_H
#include "partition_table.h"
#include <stdbool.h>
void auth_app_create(struct auth_metadata *auth_table);
bool auth_app_authenticate(struct auth_metadata *auth_table);
#endif

View file

@ -1,11 +0,0 @@
# blake2s
A Blake2s implementation taken from Joachim Strömbergson's
https://github.com/secworks/blake2s
Specifically from
https://github.com/secworks/blake2s/tree/master/src/model
Minor local changes for build purposes.

View file

@ -6,9 +6,14 @@
OUTPUT_ARCH("riscv")
ENTRY(_start)
/* Define stack size */
STACK_SIZE = 3000;
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

@ -0,0 +1,229 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/tk1_mem.h>
#include "flash.h"
#include "spi.h"
#define PAGE_SIZE 256
static bool flash_is_busy(void);
static void flash_wait_busy(void);
static void flash_write_enable(void);
static bool flash_is_busy(void)
{
uint8_t tx_buf = READ_STATUS_REG_1;
uint8_t rx_buf = {0x00};
assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, &rx_buf,
sizeof(rx_buf)) == 0);
if (rx_buf & (1 << STATUS_REG_BUSY_BIT)) {
return true;
}
return false;
}
// Blocking until !busy
static void flash_wait_busy(void)
{
while (flash_is_busy())
;
}
static void flash_write_enable(void)
{
uint8_t tx_buf = WRITE_ENABLE;
assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
}
void flash_write_disable(void)
{
uint8_t tx_buf = WRITE_DISABLE;
assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
}
void flash_sector_erase(uint32_t address)
{
uint8_t tx_buf[4] = {0x00};
tx_buf[0] = SECTOR_ERASE;
tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF;
tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF;
/* tx_buf[3] is within a sector, and hence does not make a difference */
flash_write_enable();
assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
flash_wait_busy();
}
void flash_block_32_erase(uint32_t address)
{
uint8_t tx_buf[4] = {0x00};
tx_buf[0] = BLOCK_ERASE_32K;
tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF;
tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF;
tx_buf[3] = (address >> ADDR_BYTE_1_BIT) & 0xFF;
flash_write_enable();
assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
flash_wait_busy();
}
// 64 KiB block erase, only cares about address bits 16 and above.
void flash_block_64_erase(uint32_t address)
{
uint8_t tx_buf[4] = {0x00};
tx_buf[0] = BLOCK_ERASE_64K;
tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF;
/* tx_buf[2] and tx_buf[3] is within a block, and hence does not make a
* difference */
flash_write_enable();
assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
flash_wait_busy();
}
void flash_release_powerdown(void)
{
uint8_t tx_buf[4] = {0x00};
tx_buf[0] = RELEASE_POWER_DOWN;
assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
}
void flash_powerdown(void)
{
uint8_t tx_buf = POWER_DOWN;
assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, NULL, 0) == 0);
}
void flash_read_manufacturer_device_id(uint8_t *device_id)
{
assert(device_id != NULL);
uint8_t tx_buf[4] = {0x00};
tx_buf[0] = READ_MANUFACTURER_ID;
assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, device_id, 2) ==
0);
}
void flash_read_jedec_id(uint8_t *jedec_id)
{
assert(jedec_id != NULL);
uint8_t tx_buf = READ_JEDEC_ID;
assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, jedec_id, 3) ==
0);
}
void flash_read_unique_id(uint8_t *unique_id)
{
assert(unique_id != NULL);
uint8_t tx_buf[5] = {0x00};
tx_buf[0] = READ_UNIQUE_ID;
assert(spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, unique_id, 8) ==
0);
}
void flash_read_status(uint8_t *status_reg)
{
assert(status_reg != NULL);
uint8_t tx_buf = READ_STATUS_REG_1;
assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg, 1) ==
0);
tx_buf = READ_STATUS_REG_2;
assert(spi_transfer(&tx_buf, sizeof(tx_buf), NULL, 0, status_reg + 1,
1) == 0);
}
int flash_read_data(uint32_t address, uint8_t *dest_buf, size_t size)
{
if (dest_buf == NULL) {
return -1;
}
uint8_t tx_buf[4] = {0x00};
tx_buf[0] = READ_DATA;
tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF;
tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF;
tx_buf[3] = (address >> ADDR_BYTE_1_BIT) & 0xFF;
return spi_transfer(tx_buf, sizeof(tx_buf), NULL, 0, dest_buf, size);
}
// Only handles writes where the least significant byte of the start address is
// zero.
int flash_write_data(uint32_t address, uint8_t *data, size_t size)
{
if (data == NULL) {
return -1;
}
if (size <= 0) {
return -1;
}
if (address % 256 != 0) {
return -1;
}
size_t left = size;
uint8_t *p_data = data;
size_t n_bytes = 0;
// Page Program allows 1-256 bytes of a page to be written. A page is
// 256 bytes. Behavior when writing past the end of a page is device
// specific.
//
// We set the address LSByte to 0 and only write 256 bytes or less in
// each transfer.
uint8_t tx_buf[4] = {
PAGE_PROGRAM, /* tx_buf[0] */
(address >> ADDR_BYTE_3_BIT) & 0xFF, /* tx_buf[1] */
(address >> ADDR_BYTE_2_BIT) & 0xFF, /* tx_buf[2] */
0x00, /* tx_buf[3] */
};
while (left > 0) {
if (left >= PAGE_SIZE) {
n_bytes = PAGE_SIZE;
} else {
n_bytes = left;
}
flash_write_enable();
if (spi_transfer(tx_buf, sizeof(tx_buf), p_data, n_bytes, NULL,
0) != 0) {
return -1;
}
left -= n_bytes;
p_data += n_bytes;
address += n_bytes;
tx_buf[1] = (address >> ADDR_BYTE_3_BIT) & 0xFF;
tx_buf[2] = (address >> ADDR_BYTE_2_BIT) & 0xFF;
flash_wait_busy();
}
return 0;
}

View file

@ -0,0 +1,55 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef TKEY_FLASH_H
#define TKEY_FLASH_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define WRITE_ENABLE 0x06
#define WRITE_DISABLE 0x04
#define READ_STATUS_REG_1 0x05
#define READ_STATUS_REG_2 0x35
#define WRITE_STATUS_REG 0x01
#define PAGE_PROGRAM 0x02
#define SECTOR_ERASE 0x20
#define BLOCK_ERASE_32K 0x52
#define BLOCK_ERASE_64K 0xD8
#define CHIP_ERASE 0xC7
#define POWER_DOWN 0xB9
#define READ_DATA 0x03
#define RELEASE_POWER_DOWN 0xAB
#define READ_MANUFACTURER_ID 0x90
#define READ_JEDEC_ID 0x9F
#define READ_UNIQUE_ID 0x4B
#define ENABLE_RESET 0x66
#define RESET 0x99
#define ADDR_BYTE_3_BIT 16
#define ADDR_BYTE_2_BIT 8
#define ADDR_BYTE_1_BIT 0
#define STATUS_REG_BUSY_BIT 0
#define STATUS_REG_WEL_BIT 1
void flash_write_disable(void);
void flash_sector_erase(uint32_t address);
void flash_block_32_erase(uint32_t address);
void flash_block_64_erase(uint32_t address);
void flash_release_powerdown(void);
void flash_powerdown(void);
void flash_read_manufacturer_device_id(uint8_t *device_id);
void flash_read_jedec_id(uint8_t *jedec_id);
void flash_read_unique_id(uint8_t *unique_id);
void flash_read_status(uint8_t *status_reg);
int flash_read_data(uint32_t address, uint8_t *dest_buf, size_t size);
int flash_write_data(uint32_t address, uint8_t *data, size_t size);
#endif

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

@ -1,19 +1,26 @@
/*
* Copyright (C) 2022, 2023 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
// Copyright (C) 2022, 2023 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include "../tk1_mem.h"
#include "assert.h"
#include "blake2s/blake2s.h"
#include "lib.h"
#include <blake2s/blake2s.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/debug.h>
#include <tkey/led.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include "mgmt_app.h"
#include "partition_table.h"
#include "preload_app.h"
#include "proto.h"
#include "reset.h"
#include "state.h"
#include "types.h"
#include "syscall_enable.h"
// clang-format off
static volatile uint32_t *uds = (volatile uint32_t *)TK1_MMIO_UDS_FIRST;
static volatile uint32_t *system_mode_ctrl = (volatile uint32_t *)TK1_MMIO_TK1_SYSTEM_MODE_CTRL;
static volatile uint32_t *name0 = (volatile uint32_t *)TK1_MMIO_TK1_NAME0;
static volatile uint32_t *name1 = (volatile uint32_t *)TK1_MMIO_TK1_NAME1;
static volatile uint32_t *ver = (volatile uint32_t *)TK1_MMIO_TK1_VERSION;
@ -21,7 +28,6 @@ static volatile uint32_t *udi = (volatile uint32_t *)TK1_MMIO_TK1_U
static volatile uint32_t *cdi = (volatile uint32_t *)TK1_MMIO_TK1_CDI_FIRST;
static volatile uint32_t *app_addr = (volatile uint32_t *)TK1_MMIO_TK1_APP_ADDR;
static volatile uint32_t *app_size = (volatile uint32_t *)TK1_MMIO_TK1_APP_SIZE;
static volatile uint32_t *fw_blake2s_addr = (volatile uint32_t *)TK1_MMIO_TK1_BLAKE2S;
static volatile uint32_t *trng_status = (volatile uint32_t *)TK1_MMIO_TRNG_STATUS;
static volatile uint32_t *trng_entropy = (volatile uint32_t *)TK1_MMIO_TRNG_ENTROPY;
static volatile uint32_t *timer = (volatile uint32_t *)TK1_MMIO_TIMER_TIMER;
@ -30,15 +36,21 @@ static volatile uint32_t *timer_status = (volatile uint32_t *)TK1_MMIO_TIMER
static volatile uint32_t *timer_ctrl = (volatile uint32_t *)TK1_MMIO_TIMER_CTRL;
static volatile uint32_t *ram_addr_rand = (volatile uint32_t *)TK1_MMIO_TK1_RAM_ADDR_RAND;
static volatile uint32_t *ram_data_rand = (volatile uint32_t *)TK1_MMIO_TK1_RAM_DATA_RAND;
static volatile struct reset *resetinfo = (volatile struct reset *)TK1_MMIO_RESETINFO_BASE;
// clang-format on
struct partition_table_storage part_table_storage;
// Context for the loading of a TKey program
struct context {
uint32_t left; // Bytes left to receive
uint8_t digest[32]; // Program digest
uint8_t *loadaddr; // Where we are currently loading a TKey program
uint8_t use_uss; // Use USS?
bool use_uss; // Use USS?
uint8_t uss[32]; // User Supplied Secret, if any
uint8_t flash_slot; // App is loaded from flash slot number
/*@null@*/ volatile uint8_t
*ver_digest; // Verify loaded app against this digest
};
static void print_hw_version(void);
@ -53,34 +65,38 @@ static enum state initial_commands(const struct frame_header *hdr,
static enum state loading_commands(const struct frame_header *hdr,
const uint8_t *cmd, enum state state,
struct context *ctx);
static void run(const struct context *ctx);
#if !defined(SIMULATION)
static uint32_t xorwow(uint32_t state, uint32_t acc);
#endif
static void scramble_ram(void);
static int compute_app_digest(uint8_t *digest);
static int load_flash_app(struct partition_table *part_table,
uint8_t digest[32], uint8_t slot);
static enum state start_where(struct context *ctx);
static void print_hw_version(void)
{
htif_puts("Hello, I'm firmware with");
htif_puts(" tk1_name0:");
htif_putinthex(*name0);
htif_puts(" tk1_name1:");
htif_putinthex(*name1);
htif_puts(" tk1_version:");
htif_putinthex(*ver);
htif_lf();
debug_puts("Hello, I'm firmware with");
debug_puts(" tk1_name0:");
debug_putinthex(*name0);
debug_puts(" tk1_name1:");
debug_putinthex(*name1);
debug_puts(" tk1_version:");
debug_putinthex(*ver);
debug_lf();
}
static void print_digest(uint8_t *md)
{
htif_puts("The app digest:\n");
debug_puts("The app digest:\n");
for (int j = 0; j < 4; j++) {
for (int i = 0; i < 8; i++) {
htif_puthex(md[i + 8 * j]);
debug_puthex(md[i + 8 * j]);
(void)md;
}
htif_lf();
debug_lf();
}
htif_lf();
debug_lf();
}
static uint32_t rnd_word(void)
@ -141,6 +157,7 @@ static void compute_cdi(const uint8_t *digest, const uint8_t use_uss,
static void copy_name(uint8_t *buf, const size_t bufsiz, const uint32_t word)
{
assert(bufsiz >= 4);
assert(buf != NULL);
buf[0] = word >> 24;
buf[1] = word >> 16;
@ -152,20 +169,20 @@ static enum state initial_commands(const struct frame_header *hdr,
const uint8_t *cmd, enum state state,
struct context *ctx)
{
uint8_t rsp[CMDLEN_MAXBYTES] = {0};
uint8_t rsp[CMDSIZE] = {0};
switch (cmd[0]) {
case FW_CMD_NAME_VERSION:
htif_puts("cmd: name-version\n");
debug_puts("cmd: name-version\n");
if (hdr->len != 1) {
// Bad length
state = FW_STATE_FAIL;
break;
}
copy_name(rsp, CMDLEN_MAXBYTES, *name0);
copy_name(&rsp[4], CMDLEN_MAXBYTES - 4, *name1);
wordcpy_s(&rsp[8], CMDLEN_MAXBYTES / 4 - 2, (void *)ver, 1);
copy_name(rsp, CMDSIZE, *name0);
copy_name(&rsp[4], CMDSIZE - 4, *name1);
wordcpy_s(&rsp[8], CMDSIZE - 8, (void *)ver, 1);
fwreply(*hdr, FW_RSP_NAME_VERSION, rsp);
// still initial state
@ -174,7 +191,7 @@ static enum state initial_commands(const struct frame_header *hdr,
case FW_CMD_GET_UDI: {
uint32_t udi_words[2];
htif_puts("cmd: get-udi\n");
debug_puts("cmd: get-udi\n");
if (hdr->len != 1) {
// Bad length
state = FW_STATE_FAIL;
@ -183,7 +200,7 @@ static enum state initial_commands(const struct frame_header *hdr,
rsp[0] = STATUS_OK;
wordcpy_s(&udi_words, 2, (void *)udi, 2);
memcpy_s(&rsp[1], CMDLEN_MAXBYTES - 1, &udi_words, 2 * 4);
memcpy_s(&rsp[1], CMDSIZE - 1, &udi_words, 2 * 4);
fwreply(*hdr, FW_RSP_GET_UDI, rsp);
// still initial state
break;
@ -192,7 +209,7 @@ static enum state initial_commands(const struct frame_header *hdr,
case FW_CMD_LOAD_APP: {
uint32_t local_app_size;
htif_puts("cmd: load-app(size, uss)\n");
debug_puts("cmd: load-app(size, uss)\n");
if (hdr->len != 128) {
// Bad length
state = FW_STATE_FAIL;
@ -203,9 +220,9 @@ static enum state initial_commands(const struct frame_header *hdr,
local_app_size =
cmd[1] + (cmd[2] << 8) + (cmd[3] << 16) + (cmd[4] << 24);
htif_puts("app size: ");
htif_putinthex(local_app_size);
htif_lf();
debug_puts("app size: ");
debug_putinthex(local_app_size);
debug_lf();
if (local_app_size == 0 || local_app_size > TK1_APP_MAX_SIZE) {
rsp[0] = STATUS_BAD;
@ -219,10 +236,10 @@ static enum state initial_commands(const struct frame_header *hdr,
// Do we have a USS at all?
if (cmd[5] != 0) {
// Yes
ctx->use_uss = TRUE;
ctx->use_uss = true;
memcpy_s(ctx->uss, 32, &cmd[6], 32);
} else {
ctx->use_uss = FALSE;
ctx->use_uss = false;
}
rsp[0] = STATUS_OK;
@ -233,14 +250,16 @@ static enum state initial_commands(const struct frame_header *hdr,
ctx->left = *app_size;
led_set(LED_BLACK);
state = FW_STATE_LOADING;
break;
}
default:
htif_puts("Got unknown firmware cmd: 0x");
htif_puthex(cmd[0]);
htif_lf();
debug_puts("Got unknown firmware cmd: 0x");
debug_puthex(cmd[0]);
debug_lf();
state = FW_STATE_FAIL;
break;
}
@ -252,12 +271,12 @@ static enum state loading_commands(const struct frame_header *hdr,
const uint8_t *cmd, enum state state,
struct context *ctx)
{
uint8_t rsp[CMDLEN_MAXBYTES] = {0};
uint8_t rsp[CMDSIZE] = {0};
uint32_t nbytes = 0;
switch (cmd[0]) {
case FW_CMD_LOAD_APP_DATA:
htif_puts("cmd: load-app-data\n");
debug_puts("cmd: load-app-data\n");
if (hdr->len != 128) {
// Bad length
state = FW_STATE_FAIL;
@ -276,29 +295,25 @@ static enum state loading_commands(const struct frame_header *hdr,
ctx->left -= nbytes;
if (ctx->left == 0) {
blake2s_ctx b2s_ctx = {0};
int blake2err = 0;
htif_puts("Fully loaded ");
htif_putinthex(*app_size);
htif_lf();
debug_puts("Fully loaded ");
debug_putinthex(*app_size);
debug_lf();
// Compute Blake2S digest of the app,
// storing it for FW_STATE_RUN
blake2err = blake2s(&ctx->digest, 32, NULL, 0,
(const void *)TK1_RAM_BASE,
*app_size, &b2s_ctx);
blake2err = compute_app_digest(ctx->digest);
assert(blake2err == 0);
print_digest(ctx->digest);
// And return the digest in final
// response
rsp[0] = STATUS_OK;
memcpy_s(&rsp[1], CMDLEN_MAXBYTES - 1, &ctx->digest,
32);
memcpy_s(&rsp[1], CMDSIZE - 1, &ctx->digest, 32);
fwreply(*hdr, FW_RSP_LOAD_APP_DATA_READY, rsp);
state = FW_STATE_RUN;
state = FW_STATE_START;
break;
}
@ -308,9 +323,9 @@ static enum state loading_commands(const struct frame_header *hdr,
break;
default:
htif_puts("Got unknown firmware cmd: 0x");
htif_puthex(cmd[0]);
htif_lf();
debug_puts("Got unknown firmware cmd: 0x");
debug_puthex(cmd[0]);
debug_lf();
state = FW_STATE_FAIL;
break;
}
@ -318,24 +333,22 @@ static enum state loading_commands(const struct frame_header *hdr,
return state;
}
static void run(const struct context *ctx)
static void jump_to_app(void)
{
/* Start of app is always at the beginning of RAM */
*app_addr = TK1_RAM_BASE;
// CDI = hash(uds, hash(app), uss)
compute_cdi(ctx->digest, ctx->use_uss, ctx->uss);
htif_puts("Flipping to app mode!\n");
htif_puts("Jumping to ");
htif_putinthex(*app_addr);
htif_lf();
debug_puts("Flipping to app mode!\n");
debug_puts("Jumping to ");
debug_putinthex(*app_addr);
debug_lf();
// Clear the firmware stack
// clang-format off
#ifndef S_SPLINT_S
asm volatile(
"li a0, 0xd0000000;" // FW_RAM
"li a1, 0xd0000800;" // End of 2 KB FW_RAM (just past the end)
"la a0, _sstack;"
"la a1, _estack;"
"loop:;"
"sw zero, 0(a0);"
"addi a0, a0, 4;"
@ -344,13 +357,10 @@ static void run(const struct context *ctx)
#endif
// clang-format on
// Flip over to application mode
*system_mode_ctrl = 1;
// XXX Firmware stack now no longer available
// Don't use any function calls!
syscall_enable();
// Jump to app - doesn't return
// Hardware is responsible for switching to app mode
// clang-format off
#ifndef S_SPLINT_S
asm volatile(
@ -366,6 +376,29 @@ static void run(const struct context *ctx)
__builtin_unreachable();
}
static int load_flash_app(struct partition_table *part_table,
uint8_t digest[32], uint8_t slot)
{
if (slot >= N_PRELOADED_APP) {
return -1;
}
if (preload_load(part_table, slot) == -1) {
return -1;
}
*app_size = part_table->pre_app_data[slot].size;
if (*app_size > TK1_APP_MAX_SIZE) {
return -1;
}
int digest_err = compute_app_digest(digest);
assert(digest_err == 0);
print_digest(digest);
return 0;
}
#if !defined(SIMULATION)
static uint32_t xorwow(uint32_t state, uint32_t acc)
{
@ -400,31 +433,105 @@ static void scramble_ram(void)
*ram_data_rand = rnd_word();
}
/* Computes the blake2s digest of the app loaded into RAM */
static int compute_app_digest(uint8_t *digest)
{
return blake2s(digest, 32, NULL, 0, (const void *)TK1_RAM_BASE,
*app_size);
}
static enum state start_where(struct context *ctx)
{
assert(ctx != NULL);
debug_puts("resetinfo->type: ");
debug_putinthex(resetinfo->type);
debug_lf();
debug_puts(" ->app_digest: \n");
debug_hexdump((void *)resetinfo->app_digest, RESET_DIGEST_SIZE);
debug_lf();
debug_puts(" ->next_app_data: \n");
debug_hexdump((void *)resetinfo->next_app_data, RESET_DATA_SIZE);
debug_lf();
// Where do we start?
switch (resetinfo->type) {
case START_DEFAULT:
// fallthrough
case START_FLASH0:
ctx->flash_slot = 0;
ctx->ver_digest = mgmt_app_allowed_digest();
return FW_STATE_LOAD_FLASH_MGMT;
case START_FLASH1:
ctx->flash_slot = 1;
ctx->ver_digest = NULL;
return FW_STATE_LOAD_FLASH;
case START_FLASH0_VER:
ctx->flash_slot = 0;
ctx->ver_digest = resetinfo->app_digest;
return FW_STATE_LOAD_FLASH;
case START_FLASH1_VER:
ctx->flash_slot = 1;
ctx->ver_digest = resetinfo->app_digest;
return FW_STATE_LOAD_FLASH;
case START_CLIENT:
ctx->ver_digest = NULL;
return FW_STATE_WAITCOMMAND;
case START_CLIENT_VER:
ctx->ver_digest = resetinfo->app_digest;
return FW_STATE_WAITCOMMAND;
default:
debug_puts("Unknown startfrom\n");
return FW_STATE_FAIL;
}
}
int main(void)
{
struct context ctx = {0};
struct frame_header hdr = {0};
uint8_t cmd[CMDLEN_MAXBYTES] = {0};
uint8_t cmd[CMDSIZE] = {0};
enum state state = FW_STATE_INITIAL;
print_hw_version();
// Let the app know the function adddress for blake2s()
*fw_blake2s_addr = (uint32_t)blake2s;
/*@-mustfreeonly@*/
/* Yes, splint, this points directly to RAM and we don't care
* about freeing anything was pointing to 0x0 before.
*/
ctx.loadaddr = (uint8_t *)TK1_RAM_BASE;
/*@+mustfreeonly@*/
ctx.use_uss = FALSE;
uint8_t mode = 0;
uint8_t mode_bytes_left = 0;
ctx.use_uss = false;
scramble_ram();
if (part_table_read(&part_table_storage) != 0) {
// Couldn't read partition table
debug_puts("Couldn't read partition table\n");
assert(1 == 2);
}
// Reset the USB controller to only enable the USB CDC
// endpoint and the internal command channel.
config_endpoints(IO_CDC | IO_CH552);
led_set(LED_WHITE);
#if defined(SIMULATION)
run(&ctx);
#endif
@ -432,18 +539,23 @@ int main(void)
for (;;) {
switch (state) {
case FW_STATE_INITIAL:
if (readcommand(&hdr, cmd, state, &mode,
&mode_bytes_left) == -1) {
state = start_where(&ctx);
break;
case FW_STATE_WAITCOMMAND:
if (readcommand(&hdr, cmd, state) == -1) {
state = FW_STATE_FAIL;
break;
}
debug_puts("cmd: \n");
debug_hexdump(cmd, hdr.len);
state = initial_commands(&hdr, cmd, state, &ctx);
break;
case FW_STATE_LOADING:
if (readcommand(&hdr, cmd, state, &mode,
&mode_bytes_left) == -1) {
if (readcommand(&hdr, cmd, state) == -1) {
state = FW_STATE_FAIL;
break;
}
@ -451,22 +563,66 @@ int main(void)
state = loading_commands(&hdr, cmd, state, &ctx);
break;
case FW_STATE_RUN:
run(&ctx);
break; // This is never reached!
case FW_STATE_LOAD_FLASH:
if (load_flash_app(&part_table_storage.table,
ctx.digest, ctx.flash_slot) < 0) {
debug_puts("Couldn't load app from flash\n");
state = FW_STATE_FAIL;
break;
}
state = FW_STATE_START;
break;
case FW_STATE_LOAD_FLASH_MGMT:
if (load_flash_app(&part_table_storage.table,
ctx.digest, ctx.flash_slot) < 0) {
debug_puts("Couldn't load app from flash\n");
state = FW_STATE_FAIL;
break;
}
if (mgmt_app_init(ctx.digest) != 0) {
state = FW_STATE_FAIL;
break;
}
state = FW_STATE_START;
break;
case FW_STATE_START:
// CDI = hash(uds, hash(app), uss)
compute_cdi(ctx.digest, ctx.use_uss, ctx.uss);
if (ctx.ver_digest != NULL) {
print_digest(ctx.digest);
if (!memeq(ctx.digest, (void *)ctx.ver_digest,
sizeof(ctx.digest))) {
debug_puts("Digests do not match\n");
state = FW_STATE_FAIL;
break;
}
}
(void)memset((void *)resetinfo->app_digest, 0,
sizeof(resetinfo->app_digest));
jump_to_app();
break; // Not reached
case FW_STATE_FAIL:
// fallthrough
default:
htif_puts("firmware state 0x");
htif_puthex(state);
htif_lf();
debug_puts("firmware state 0x");
debug_puthex(state);
debug_lf();
assert(1 == 2);
break; // Not reached
}
}
/*@ -compdestroy @*/
/* We don't care about memory leaks here. */
// We don't care about memory leaks here.
return (int)0xcafebabe;
}

View file

@ -0,0 +1,46 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdbool.h>
#include <stdint.h>
#include <tkey/io.h>
#include <tkey/lib.h>
#include "mgmt_app.h"
// Lock down what app can start from flash slot 0.
//
// To update this, compute the BLAKE2s digest of the app.bin
// clang-format off
static const uint8_t allowed_app_digest[32] = {
0x85, 0x29, 0xe3, 0x25, 0xf5, 0x8d, 0x53, 0x5f,
0xe1, 0x2a, 0x77, 0x92, 0xe7, 0xdc, 0x4b, 0x4d,
0x01, 0x85, 0x17, 0xca, 0xfd, 0x54, 0x83, 0xb3,
0xbb, 0x28, 0x4f, 0xa1, 0x98, 0x5f, 0x9e, 0x56,
};
// clang-format on
static uint8_t current_app_digest[32];
int mgmt_app_init(uint8_t app_digest[32])
{
if (app_digest == NULL) {
return -1;
}
memcpy_s(current_app_digest, sizeof(current_app_digest), app_digest,
32);
return 0;
}
// Authenticate an management app
bool mgmt_app_authenticate(void)
{
return memeq(current_app_digest, allowed_app_digest, 32) != 0;
}
uint8_t *mgmt_app_allowed_digest(void)
{
return (uint8_t *)allowed_app_digest;
}

View file

@ -0,0 +1,14 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef MGMT_APP_H
#define MGMT_APP_H
#include <stdbool.h>
#include <stdint.h>
int mgmt_app_init(uint8_t app_digest[32]);
bool mgmt_app_authenticate(void);
uint8_t *mgmt_app_allowed_digest(void);
#endif

View file

@ -0,0 +1,104 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/lib.h>
#include "blake2s/blake2s.h"
#include "flash.h"
#include "partition_table.h"
#include "proto.h"
static enum part_status part_status;
enum part_status part_get_status(void)
{
return part_status;
}
static void part_checksum(struct partition_table *part_table,
uint8_t *out_digest, size_t out_len);
// part_digest computes a checksum over the partition table to detect
// flash problems
static void part_checksum(struct partition_table *part_table,
uint8_t *out_digest, size_t out_len)
{
int blake2err = 0;
assert(part_table != NULL);
assert(out_digest != NULL);
blake2err = blake2s(out_digest, out_len, NULL, 0, part_table,
sizeof(struct partition_table));
assert(blake2err == 0);
}
// part_table_read reads and verifies the partition table storage,
// first trying slot 0, then slot 1 if slot 0 does not verify.
//
// It stores the partition table in storage.
//
// Returns negative values on errors.
int part_table_read(struct partition_table_storage *storage)
{
uint32_t offset[2] = {
ADDR_PARTITION_TABLE_0,
ADDR_PARTITION_TABLE_1,
};
uint8_t check_digest[PART_CHECKSUM_SIZE] = {0};
if (storage == NULL) {
return -1;
}
flash_release_powerdown();
(void)memset(storage, 0x00, sizeof(*storage));
for (int i = 0; i < 2; i++) {
if (flash_read_data(offset[i], (uint8_t *)storage,
sizeof(*storage)) != 0) {
return -1;
}
part_checksum(&storage->table, check_digest,
sizeof(check_digest));
if (memeq(check_digest, storage->checksum,
sizeof(check_digest))) {
if (i == 1) {
part_status = PART_SLOT0_INVALID;
}
return 0;
}
}
return -1;
}
int part_table_write(struct partition_table_storage *storage)
{
uint32_t offset[2] = {
ADDR_PARTITION_TABLE_0,
ADDR_PARTITION_TABLE_1,
};
if (storage == NULL) {
return -1;
}
part_checksum(&storage->table, storage->checksum,
sizeof(storage->checksum));
for (int i = 0; i < 2; i++) {
flash_sector_erase(offset[i]);
if (flash_write_data(offset[i], (uint8_t *)storage,
sizeof(*storage)) != 0) {
return -1;
}
}
return 0;
}

View file

@ -0,0 +1,112 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef PARTITION_TABLE_H
#define PARTITION_TABLE_H
#include <stdint.h>
// ---- Flash ---- ----
// name size start addr
// ---- ---- ----
// bitstream 128KiB 0x00
// ---- ---- ----
// Partition 64KiB 0x20000
// ---- ---- ----
// Pre load 1 128KiB 0x30000
// Pre load 2 128KiB 0x50000
// ---- ---- ----
// storage 1 128KiB 0x70000
// storage 2 128KiB 0x90000
// storage 3 128KiB 0xB0000
// storage 4 128KiB 0xD0000
// ---- ---- ----
// Partition2 64KiB 0xf0000
// To simplify all blocks are aligned with the 64KiB blocks on the
// W25Q80DL flash.
#define PART_TABLE_VERSION 1
#define ADDR_BITSTREAM 0UL
#define SIZE_BITSTREAM 0x20000UL // 128KiB
#define ADDR_PARTITION_TABLE_0 (ADDR_BITSTREAM + SIZE_BITSTREAM)
#define ADDR_PARTITION_TABLE_1 0xf0000
#define SIZE_PARTITION_TABLE \
0x10000UL // 64KiB, 60 KiB reserved, 2 flash pages (2 x 4KiB) for the
// partition table
#define N_PRELOADED_APP 2
#define ADDR_PRE_LOADED_APP_0 (ADDR_PARTITION_TABLE_0 + SIZE_PARTITION_TABLE)
#define SIZE_PRE_LOADED_APP 0x20000UL // 128KiB
#define ADDR_STORAGE_AREA \
(ADDR_PRE_LOADED_APP_0 + (N_PRELOADED_APP * SIZE_PRE_LOADED_APP))
#define SIZE_STORAGE_AREA 0x20000UL // 128KiB
#define N_STORAGE_AREA 4
#define PART_CHECKSUM_SIZE 32
enum part_status {
PART_SLOT0_INVALID = 1,
};
// Partition Table
// ----------------------------------------------------------------------
// - Table header
// - 1 bytes Version
//
// - Pre-loaded device app 1
// - 4 bytes length.
// - 32 bytes digest.
// - 64 bytes signature.
//
// - Pre-loaded device app 2
// - 4 bytes length.
// - 32 bytes digest.
// - 64 bytes signature.
//
// - Device app storage area
// - 1 byte status.
// - 16 bytes random nonce.
// - 16 bytes authentication tag.
//
// - Checksum over the above
struct auth_metadata {
uint8_t nonce[16];
uint8_t authentication_digest[16];
} __attribute__((packed));
struct pre_loaded_app_metadata {
uint32_t size;
uint8_t digest[32];
uint8_t signature[64];
} __attribute__((packed));
struct app_storage_area {
uint8_t status;
struct auth_metadata auth;
} __attribute__((packed));
struct table_header {
uint8_t version;
} __attribute__((packed));
struct partition_table {
struct table_header header;
struct pre_loaded_app_metadata pre_app_data[N_PRELOADED_APP];
struct app_storage_area app_storage[N_STORAGE_AREA];
} __attribute__((packed));
struct partition_table_storage {
struct partition_table table;
uint8_t checksum[PART_CHECKSUM_SIZE]; // Helps detect flash problems
} __attribute__((packed));
enum part_status part_get_status(void);
int part_table_read(struct partition_table_storage *storage);
int part_table_write(struct partition_table_storage *storage);
#endif

View file

@ -0,0 +1,12 @@
# PicoRV32
## custom_ops.S
We have imported the custom Custom PicoRV32 instructions in
`custom_ops.S` from https://github.com/YosysHQ/picorv32/ tag v1.0,
commit 6d145b708d5dfa4caa3445bc599927cebc3291d8.
Upstream path is `picorv32/firmware/custom_ops.S`.
The picorv32 firmware is public domain, which we chose to mark as
CC0-1.0.

View file

@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: Claire Xenia Wolf <claire@yosyshq.com>
// SPDX-License-Identifier: CC0-1.0
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
#define regnum_q0 0
#define regnum_q1 1
#define regnum_q2 2
#define regnum_q3 3
#define regnum_x0 0
#define regnum_x1 1
#define regnum_x2 2
#define regnum_x3 3
#define regnum_x4 4
#define regnum_x5 5
#define regnum_x6 6
#define regnum_x7 7
#define regnum_x8 8
#define regnum_x9 9
#define regnum_x10 10
#define regnum_x11 11
#define regnum_x12 12
#define regnum_x13 13
#define regnum_x14 14
#define regnum_x15 15
#define regnum_x16 16
#define regnum_x17 17
#define regnum_x18 18
#define regnum_x19 19
#define regnum_x20 20
#define regnum_x21 21
#define regnum_x22 22
#define regnum_x23 23
#define regnum_x24 24
#define regnum_x25 25
#define regnum_x26 26
#define regnum_x27 27
#define regnum_x28 28
#define regnum_x29 29
#define regnum_x30 30
#define regnum_x31 31
#define regnum_zero 0
#define regnum_ra 1
#define regnum_sp 2
#define regnum_gp 3
#define regnum_tp 4
#define regnum_t0 5
#define regnum_t1 6
#define regnum_t2 7
#define regnum_s0 8
#define regnum_s1 9
#define regnum_a0 10
#define regnum_a1 11
#define regnum_a2 12
#define regnum_a3 13
#define regnum_a4 14
#define regnum_a5 15
#define regnum_a6 16
#define regnum_a7 17
#define regnum_s2 18
#define regnum_s3 19
#define regnum_s4 20
#define regnum_s5 21
#define regnum_s6 22
#define regnum_s7 23
#define regnum_s8 24
#define regnum_s9 25
#define regnum_s10 26
#define regnum_s11 27
#define regnum_t3 28
#define regnum_t4 29
#define regnum_t5 30
#define regnum_t6 31
// x8 is s0 and also fp
#define regnum_fp 8
#define r_type_insn(_f7, _rs2, _rs1, _f3, _rd, _opc) \
.word (((_f7) << 25) | ((_rs2) << 20) | ((_rs1) << 15) | ((_f3) << 12) | ((_rd) << 7) | ((_opc) << 0))
#define picorv32_getq_insn(_rd, _qs) \
r_type_insn(0b0000000, 0, regnum_ ## _qs, 0b100, regnum_ ## _rd, 0b0001011)
#define picorv32_setq_insn(_qd, _rs) \
r_type_insn(0b0000001, 0, regnum_ ## _rs, 0b010, regnum_ ## _qd, 0b0001011)
#define picorv32_retirq_insn() \
r_type_insn(0b0000010, 0, 0, 0b000, 0, 0b0001011)
#define picorv32_maskirq_insn(_rd, _rs) \
r_type_insn(0b0000011, 0, regnum_ ## _rs, 0b110, regnum_ ## _rd, 0b0001011)
#define picorv32_waitirq_insn(_rd) \
r_type_insn(0b0000100, 0, 0, 0b100, regnum_ ## _rd, 0b0001011)
#define picorv32_timer_insn(_rd, _rs) \
r_type_insn(0b0000101, 0, regnum_ ## _rs, 0b110, regnum_ ## _rd, 0b0001011)

View file

@ -0,0 +1,224 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <tkey/debug.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include "flash.h"
#include "mgmt_app.h"
#include "partition_table.h"
#include "preload_app.h"
static uint32_t slot_to_start_address(uint8_t slot)
{
return ADDR_PRE_LOADED_APP_0 + slot * SIZE_PRE_LOADED_APP;
}
// Loads a preloaded app from flash to app RAM
int preload_load(struct partition_table *part_table, uint8_t from_slot)
{
if (part_table == NULL) {
return -1;
}
if (from_slot >= N_PRELOADED_APP) {
return -1;
}
// Check for a valid app in flash
if (part_table->pre_app_data[from_slot].size == 0 &&
part_table->pre_app_data[from_slot].size <= TK1_APP_MAX_SIZE) {
return -1;
}
uint8_t *loadaddr = (uint8_t *)TK1_RAM_BASE;
// Read from flash, straight into RAM
int ret = flash_read_data(slot_to_start_address(from_slot), loadaddr,
part_table->pre_app_data[from_slot].size);
return ret;
}
// preload_store stores chunks of an app in app slot to_slot. data is a buffer
// of size size to be written at byte offset in the slot. offset needs to be
// kept and updated between each call. offset must be a multiple of 256.
//
// When all data has been written call preload_store_finalize() with the last
// parameters.
//
// Returns 0 on success.
int preload_store(struct partition_table *part_table, uint32_t offset,
uint8_t *data, size_t size, uint8_t to_slot)
{
if (part_table == NULL || data == NULL) {
return -1;
}
if (to_slot >= N_PRELOADED_APP) {
return -1;
}
// Check if we are allowed to store
if (!mgmt_app_authenticate()) {
return -1;
}
// Check for a valid app in flash, bale out if it already
// exists
if (part_table->pre_app_data[to_slot].size != 0) {
return -1;
}
if (offset > SIZE_PRE_LOADED_APP) {
return -1;
}
if (size > SIZE_PRE_LOADED_APP) {
return -1;
}
if ((offset + size) > SIZE_PRE_LOADED_APP) {
// Writing outside of area
return -1;
}
uint32_t address = slot_to_start_address(to_slot) + offset;
debug_puts("preload_store: write to addr: ");
debug_putinthex(address);
debug_lf();
return flash_write_data(address, data, size);
}
int preload_store_finalize(struct partition_table_storage *part_table_storage,
size_t app_size, uint8_t app_digest[32],
uint8_t app_signature[64], uint8_t to_slot)
{
struct partition_table *part_table = &part_table_storage->table;
if (part_table == NULL) {
return -1;
}
// Allow data to point only to app RAM
if (app_digest < (uint8_t *)TK1_RAM_BASE ||
app_digest >= (uint8_t *)(TK1_RAM_BASE + TK1_RAM_SIZE)) {
return -1;
}
if (app_signature < (uint8_t *)TK1_RAM_BASE ||
app_signature >= (uint8_t *)(TK1_RAM_BASE + TK1_RAM_SIZE)) {
return -1;
}
if (to_slot >= N_PRELOADED_APP) {
return -1;
}
// Check if we are allowed to store
if (!mgmt_app_authenticate()) {
return -1;
}
// Check for a valid app in flash, bale out if it already
// exists
if (part_table->pre_app_data[to_slot].size != 0) {
return -1;
}
if (app_size == 0 || app_size > SIZE_PRE_LOADED_APP) {
return -1;
}
part_table->pre_app_data[to_slot].size = app_size;
memcpy_s(part_table->pre_app_data[to_slot].digest,
sizeof(part_table->pre_app_data[to_slot].digest), app_digest,
32);
memcpy_s(part_table->pre_app_data[to_slot].signature,
sizeof(part_table->pre_app_data[to_slot].signature),
app_signature, 64);
debug_puts("preload_*_final: size: ");
debug_putinthex(app_size);
debug_lf();
if (part_table_write(part_table_storage) != 0) {
return -1;
}
return 0;
}
int preload_delete(struct partition_table_storage *part_table_storage,
uint8_t slot)
{
struct partition_table *part_table = &part_table_storage->table;
if (part_table_storage == NULL) {
return -1;
}
if (slot >= N_PRELOADED_APP) {
return -1;
}
// Check if we are allowed to delete
if (!mgmt_app_authenticate()) {
return -1;
}
// Check for a valid app in flash
if (part_table->pre_app_data[slot].size == 0) {
// Nothing to do.
return 0;
}
part_table->pre_app_data[slot].size = 0;
(void)memset(part_table->pre_app_data[slot].digest, 0,
sizeof(part_table->pre_app_data[slot].digest));
(void)memset(part_table->pre_app_data[slot].signature, 0,
sizeof(part_table->pre_app_data[slot].signature));
if (part_table_write(part_table_storage) != 0) {
return -1;
}
// Assumes the area is 64 KiB block aligned
flash_block_64_erase(
slot_to_start_address(slot)); // Erase first 64 KB block
flash_block_64_erase(slot_to_start_address(slot) +
0x10000); // Erase first 64 KB block
return 0;
}
int preload_get_digsig(struct partition_table *part_table,
uint8_t app_digest[32], uint8_t app_signature[64],
uint8_t slot)
{
if (part_table == NULL || app_digest == NULL || app_signature == NULL) {
return -1;
}
if (slot >= N_PRELOADED_APP) {
return -1;
}
// Check if we are allowed to read
if (!mgmt_app_authenticate()) {
return -1;
}
memcpy_s(app_digest, 32, part_table->pre_app_data[slot].digest,
sizeof(part_table->pre_app_data[slot].digest));
memcpy_s(app_signature, 64, part_table->pre_app_data[slot].signature,
sizeof(part_table->pre_app_data[slot].signature));
return 0;
}

View file

@ -0,0 +1,24 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef PRELOAD_APP_H
#define PRELOAD_APP_H
#include "partition_table.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
int preload_load(struct partition_table *part_table, uint8_t from_slot);
int preload_store(struct partition_table *part_table, uint32_t offset,
uint8_t *data, size_t size, uint8_t to_slot);
int preload_store_finalize(struct partition_table_storage *part_table_storage,
size_t app_size, uint8_t app_digest[32],
uint8_t app_signature[64], uint8_t to_slot);
int preload_delete(struct partition_table_storage *part_table_storage,
uint8_t slot);
int preload_get_digsig(struct partition_table *part_table,
uint8_t app_digest[32], uint8_t app_signature[64],
uint8_t slot);
#endif

View file

@ -1,29 +1,20 @@
/*
* Copyright (C) 2022, 2023 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
// Copyright (C) 2022, 2023 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <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>
#include "proto.h"
#include "../tk1_mem.h"
#include "assert.h"
#include "led.h"
#include "lib.h"
#include "state.h"
#include "types.h"
// clang-format off
static volatile uint32_t *can_rx = (volatile uint32_t *)TK1_MMIO_UART_RX_STATUS;
static volatile uint32_t *rx = (volatile uint32_t *)TK1_MMIO_UART_RX_DATA;
static volatile uint32_t *can_tx = (volatile uint32_t *)TK1_MMIO_UART_TX_STATUS;
static volatile uint32_t *tx = (volatile uint32_t *)TK1_MMIO_UART_TX_DATA;
// clang-format on
static uint8_t genhdr(uint8_t id, uint8_t endpoint, uint8_t status,
enum cmdlen len);
static int parseframe(uint8_t b, struct frame_header *hdr);
static void write(uint8_t *buf, size_t nbytes);
static int read(uint8_t *buf, size_t bufsize, size_t nbytes, uint8_t *mode,
uint8_t *mode_bytes_left);
static size_t bytelen(enum cmdlen cmdlen);
static uint8_t genhdr(uint8_t id, uint8_t endpoint, uint8_t status,
@ -32,29 +23,59 @@ static uint8_t genhdr(uint8_t id, uint8_t endpoint, uint8_t status,
return (id << 5) | (endpoint << 3) | (status << 2) | len;
}
int readcommand(struct frame_header *hdr, uint8_t *cmd, int state,
uint8_t *mode, uint8_t *mode_bytes_left)
int readcommand(struct frame_header *hdr, uint8_t *cmd, int state)
{
uint8_t in = 0;
uint8_t available = 0;
enum ioend endpoint = IO_NONE;
set_led((state == FW_STATE_LOADING) ? LED_BLACK : LED_WHITE);
in = readbyte(mode, mode_bytes_left);
led_set((state == FW_STATE_LOADING) ? LED_BLACK : LED_WHITE);
if (parseframe(in, hdr) == -1) {
htif_puts("Couldn't parse header\n");
debug_puts("readcommand\n");
if (readselect(IO_CDC, &endpoint, &available) < 0) {
return -1;
}
(void)memset(cmd, 0, CMDLEN_MAXBYTES);
// Now we know the size of the cmd frame, read it all
if (read(cmd, CMDLEN_MAXBYTES, hdr->len, mode, mode_bytes_left) != 0) {
htif_puts("read: buffer overrun\n");
if (read(IO_CDC, &in, 1, 1) == -1) {
return -1;
}
debug_puts("read 1 byte\n");
if (parseframe(in, hdr) == -1) {
debug_puts("Couldn't parse header\n");
return -1;
}
debug_puts("parseframe succeeded\n");
(void)memset(cmd, 0, CMDSIZE);
// Now we know the size of the cmd frame, read it all
uint8_t n = 0;
while (n < hdr->len) {
// Wait for something to be available
if (readselect(IO_CDC, &endpoint, &available) < 0) {
return -1;
}
// Read as much as is available of what we expect
available = available > hdr->len ? hdr->len : available;
assert(n < CMDSIZE);
int n_bytes_read =
read(IO_CDC, &cmd[n], CMDSIZE - n, available);
if (n_bytes_read < 0) {
return -1;
}
n += n_bytes_read;
}
// Is it for us?
if (hdr->endpoint != DST_FW) {
htif_puts("Message not meant for us\n");
debug_puts("Message not meant for us\n");
return -1;
}
@ -86,6 +107,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 +131,23 @@ void fwreply(struct frame_header hdr, enum fwcmd rspcode, uint8_t *buf)
break;
default:
htif_puts("fwreply(): Unknown response code: 0x");
htif_puthex(rspcode);
htif_lf();
debug_puts("fwreply(): Unknown response code: 0x");
debug_puthex(rspcode);
debug_lf();
return;
}
nbytes = bytelen(len);
// Mode Protocol Header
writebyte(MODE_CDC);
writebyte(2);
// Frame Protocol Header
writebyte(genhdr(hdr.id, hdr.endpoint, 0x0, len));
frame[0] = genhdr(hdr.id, hdr.endpoint, 0x0, len);
// App protocol header
frame[1] = rspcode;
// FW protocol header
writebyte(rspcode);
nbytes--;
// Payload
memcpy(&frame[2], buf, nbytes - 1);
while (nbytes > 0) {
// Limit transfers to 64 bytes (2 byte header + 62 byte data) to
// fit in a single USB frame.
size_t tx_count = nbytes > 62 ? 62 : nbytes;
// Mode Protocol Header
writebyte(MODE_CDC);
writebyte(tx_count & 0xff);
// Data
write(buf, tx_count);
nbytes -= tx_count;
buf += tx_count;
}
}
void writebyte(uint8_t b)
{
for (;;) {
if (*can_tx) {
*tx = b;
return;
}
}
}
static void write(uint8_t *buf, size_t nbytes)
{
for (int i = 0; i < nbytes; i++) {
writebyte(buf[i]);
}
}
uint8_t readbyte_(void)
{
for (;;) {
if (*can_rx) {
uint32_t b = *rx;
return b;
}
}
}
uint8_t readbyte(uint8_t *mode, uint8_t *mode_bytes_left)
{
if (*mode_bytes_left == 0) {
*mode = readbyte_();
if (*mode != MODE_CDC) {
htif_puts("We only support MODE_CDC\n");
} else {
*mode_bytes_left = readbyte_();
}
}
uint8_t b = readbyte_();
*mode_bytes_left -= 1;
return b;
}
static int read(uint8_t *buf, size_t bufsize, size_t nbytes, uint8_t *mode,
uint8_t *mode_bytes_left)
{
if (nbytes > bufsize) {
return -1;
}
for (int n = 0; n < nbytes; n++) {
buf[n] = readbyte(mode, mode_bytes_left);
}
return 0;
write(IO_CDC, frame, 1 + nbytes);
}
// bytelen returns the number of bytes a cmdlen takes

View file

@ -1,19 +1,12 @@
/*
* Copyright (C) 2022, 2023 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
// Copyright (C) 2022, 2023 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include "types.h"
#include <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 +21,7 @@ enum cmdlen {
LEN_128
};
#define CMDLEN_MAXBYTES 128
#define CMDSIZE 128
// clang-format off
enum fwcmd {
@ -57,9 +50,6 @@ struct frame_header {
};
/*@ -exportlocal @*/
void writebyte(uint8_t b);
uint8_t readbyte(uint8_t *mode, uint8_t *mode_bytes_left);
void fwreply(struct frame_header hdr, enum fwcmd rspcode, uint8_t *buf);
int readcommand(struct frame_header *hdr, uint8_t *cmd, int state,
uint8_t *mode, uint8_t *mode_bytes_left);
int readcommand(struct frame_header *hdr, uint8_t *cmd, int state);
#endif

View file

@ -0,0 +1,87 @@
/*
* Copyright (C) 2022, 2023 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
OUTPUT_ARCH("riscv")
ENTRY(_start)
/* Define stack size */
STACK_SIZE = 3000;
MEMORY
{
ROM (rx) : ORIGIN = 0x00000000, LENGTH = 128k
FWRAM (rw) : ORIGIN = 0xd0000000, LENGTH = 0xF00 /* 3840 B */
RESETINFO (rw) : ORIGIN = 0xd0000F00, LENGTH = 0x100 /* 256 B (part of FW_RAM area) */
RAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x20000 /* 128 KB */
}
SECTIONS
{
.text.init :
{
*(.text.init)
} >ROM
.htif :
{
. = ALIGN(0x00000000);
*(.htif)
} >ROM
.text :
{
. = ALIGN(4);
_stext = .;
*(.text) /* .text sections (code) */
*(.text*) /* .text* sections (code) */
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */
*(.srodata) /* .srodata sections (constants, strings, etc.) */
*(.srodata*) /* .srodata* sections (constants, strings, etc.) */
. = ALIGN(4);
_etext = .;
} >ROM
.stack (NOLOAD) :
{
. = ALIGN(16);
_sstack = .;
. += STACK_SIZE;
. = ALIGN(16);
_estack = .;
} >FWRAM
.data :
{
. = ALIGN(4);
_sdata = .;
*(.data) /* .data sections */
*(.data*) /* .data* sections */
*(.sdata) /* .sdata sections */
*(.sdata*) /* .sdata* sections */
. = ALIGN(4);
_edata = .;
} >FWRAM AT>ROM
_sidata = LOADADDR(.data);
/* Uninitialized data section */
.bss :
{
. = ALIGN(4);
_sbss = .;
*(.bss)
*(.bss*)
*(.sbss)
*(.sbss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
} >FWRAM
}
_sfwram = ORIGIN(FWRAM);
_efwram = ORIGIN(FWRAM) + LENGTH(FWRAM);
_sresetinfo = ORIGIN(RESETINFO);
_eresetinfo = ORIGIN(RESETINFO) + LENGTH(RESETINFO);

View file

@ -0,0 +1,59 @@
// Copyright (C) 2025 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include "reset.h"
// clang-format off
static volatile uint32_t *system_reset = (volatile uint32_t *)TK1_MMIO_TK1_SYSTEM_RESET;
static volatile struct reset *resetinfo = (volatile struct reset *)TK1_MMIO_RESETINFO_BASE;
// clang-format on
int reset(struct reset *userreset, size_t nextlen)
{
if ((uint32_t)userreset < TK1_RAM_BASE ||
(uint32_t)userreset >= TK1_RAM_BASE + TK1_RAM_SIZE) {
return -1;
}
if (nextlen > sizeof(resetinfo->next_app_data)) {
return -1;
}
(void)memset((void *)resetinfo, 0, sizeof(*resetinfo));
resetinfo->type = userreset->type;
memcpy((void *)resetinfo->app_digest, userreset->app_digest,
sizeof(resetinfo->app_digest));
memcpy((void *)resetinfo->next_app_data, userreset->next_app_data,
nextlen);
// Do the actual reset.
*system_reset = 1;
// Should not be reached.
assert(1 == 2);
__builtin_unreachable();
}
int reset_data(uint8_t *next_app_data)
{
if ((uint32_t)next_app_data < TK1_RAM_BASE ||
(uint32_t)next_app_data >= TK1_RAM_BASE + TK1_RAM_SIZE) {
return -1;
}
if ((uint32_t)next_app_data + RESET_DATA_SIZE >
TK1_RAM_BASE + TK1_RAM_SIZE) {
return -1;
}
memcpy(next_app_data, (void *)resetinfo->next_app_data,
RESET_DATA_SIZE);
return 0;
}

View file

@ -0,0 +1,33 @@
// Copyright (C) 2025 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef TKEY_RESET_H
#define TKEY_RESET_H
#include <stddef.h>
#include <stdint.h>
#define TK1_MMIO_RESETINFO_BASE 0xd0000f00
#define TK1_MMIO_RESETINFO_SIZE 0x100
#define RESET_DIGEST_SIZE 32
#define RESET_DATA_SIZE 220
enum reset_start {
START_DEFAULT = 0, // Probably cold boot
START_FLASH0 = 1,
START_FLASH1 = 2,
START_FLASH0_VER = 3,
START_FLASH1_VER = 4,
START_CLIENT = 5,
START_CLIENT_VER = 6,
};
struct reset {
enum reset_start type;
uint8_t app_digest[RESET_DIGEST_SIZE];
uint8_t next_app_data[RESET_DATA_SIZE];
};
int reset(struct reset *userreset, size_t nextlen);
int reset_data(uint8_t *next_app_data);
#endif

View file

@ -0,0 +1,28 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include "rng.h"
#include <tkey/tk1_mem.h>
#include <stdint.h>
// clang-format off
static volatile uint32_t *trng_status = (volatile uint32_t *)TK1_MMIO_TRNG_STATUS;
static volatile uint32_t *trng_entropy = (volatile uint32_t *)TK1_MMIO_TRNG_ENTROPY;
// clang-format on
uint32_t rng_get_word(void)
{
while ((*trng_status & (1 << TK1_MMIO_TRNG_STATUS_READY_BIT)) == 0) {
}
return *trng_entropy;
}
uint32_t rng_xorwow(uint32_t state, uint32_t acc)
{
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
state += acc;
return state;
}

View file

@ -0,0 +1,11 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef RNG_H
#define RNG_H
#include <stdint.h>
uint32_t rng_get_word(void);
uint32_t rng_xorwow(uint32_t state, uint32_t acc);
#endif

View file

@ -0,0 +1,100 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include "spi.h"
#include <tkey/assert.h>
#include <tkey/tk1_mem.h>
#include <stddef.h>
#include <stdint.h>
// clang-format off
static volatile uint32_t *spi_en = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x200);
static volatile uint32_t *spi_xfer = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x204);
static volatile uint32_t *spi_data = (volatile uint32_t *)(TK1_MMIO_TK1_BASE | 0x208);
// clang-format on
static int spi_ready(void);
static void spi_enable(void);
static void spi_disable(void);
static void spi_write(uint8_t *cmd, size_t size);
static void spi_read(uint8_t *buf, size_t size);
// Returns non-zero when the SPI-master is ready, and zero if not
// ready. This can be used to check if the SPI-master is available
// in the hardware.
static int spi_ready(void)
{
return *spi_xfer;
}
static void spi_enable(void)
{
*spi_en = 1;
}
static void spi_disable(void)
{
*spi_en = 0;
}
static void spi_write(uint8_t *cmd, size_t size)
{
assert(cmd != NULL);
for (size_t i = 0; i < size; i++) {
while (!spi_ready()) {
}
*spi_data = cmd[i];
*spi_xfer = 1;
}
while (!spi_ready()) {
}
}
static void spi_read(uint8_t *buf, size_t size)
{
assert(buf != NULL);
while (!spi_ready()) {
}
for (size_t i = 0; i < size; i++) {
*spi_data = 0x00;
*spi_xfer = 1;
// wait until spi master is done
while (!spi_ready()) {
}
buf[i] = (*spi_data & 0xff);
}
}
// Function to both read and write data to the connected SPI flash.
int spi_transfer(uint8_t *cmd, size_t cmd_size, uint8_t *tx_buf, size_t tx_size,
uint8_t *rx_buf, size_t rx_size)
{
if (cmd == NULL || cmd_size == 0) {
return -1;
}
spi_enable();
spi_write(cmd, cmd_size);
if (tx_buf != NULL && tx_size != 0) {
spi_write(tx_buf, tx_size);
}
if (rx_buf != NULL && rx_size != 0) {
spi_read(rx_buf, rx_size);
}
spi_disable();
return 0;
}

View file

@ -0,0 +1,13 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef TKEY_SPI_H
#define TKEY_SPI_H
#include <stddef.h>
#include <stdint.h>
int spi_transfer(uint8_t *cmd, size_t cmd_size, uint8_t *tx_buf, size_t tx_size,
uint8_t *rx_buf, size_t rx_size);
#endif

View file

@ -1,11 +1,117 @@
/*
* Copyright (C) 2022, 2023 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
// Copyright (C) 2022-2025 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <tkey/tk1_mem.h>
#ifdef QEMU_SYSCALL
#define picorv32_retirq_insn(...) \
mv ra, x3; \
ret
#else
#include "picorv32/custom_ops.S" // PicoRV32 custom instructions
#endif
#define illegal_insn() .word 0
// Variables in bss
.lcomm irq_ret_addr, 4
.lcomm app_sp, 4
.section ".text.init"
.globl _start
_start:
j init
// IRQ handler
.=0x10
irq_handler:
// PicoRV32 stores the IRQ bitmask in x4.
// If bit 31 is 1: IRQ31 was triggered.
li t4, (1 << 31)
beq x4, t4, irq_source_ok
unexpected_irq_source:
illegal_insn()
j unexpected_irq_source
irq_source_ok:
// Save interrupt return address (x3)
la t0, irq_ret_addr
sw x3, 0(t0)
// Save app stack pointer. App is responsible for saving the rest of
// the registers.
la t0, app_sp
sw sp, 0(t0)
// Setup firmware stack pointer
la sp, _estack
// Run syscall handler
call syscall_handler
// Restore app stack pointer
la t0, app_sp
lw sp, 0(t0)
// Restore interrupt return address (x3)
la t0, irq_ret_addr
lw x3, 0(t0)
// Verify that interrupt return address (x3) is in app RAM
li t0, TK1_RAM_BASE // 0x40000000
blt x3, t0, x3_invalid
li t0, TK1_RAM_BASE + TK1_RAM_SIZE // 0x40020000
bge x3, t0, x3_invalid
j x3_valid
x3_invalid:
illegal_insn()
j x3_invalid
x3_valid:
// Remove data left over from the syscall handling
mv x0, zero
mv x1, zero
// x2 (sp) is assumed to be preserved by the interrupt handler
// x3 (interrupt return address) need to be preserved
mv x4, zero
mv x5, zero
mv x6, zero
mv x7, zero
mv x8, zero
mv x9, zero
// x10 (a0) contains syscall return value. And should not be destroyed.
mv x11, zero
mv x12, zero
mv x13, zero
mv x14, zero
mv x15, zero
mv x16, zero
mv x17, zero
mv x18, zero
mv x19, zero
mv x20, zero
mv x21, zero
mv x22, zero
mv x23, zero
mv x24, zero
mv x25, zero
mv x26, zero
mv x27, zero
mv x28, zero
mv x29, zero
mv x30, zero
mv x31, zero
picorv32_retirq_insn() // Return from interrupt
// Init
.=0x100
init:
li x1, 0
li x2, 0
li x3, 0
@ -38,18 +144,25 @@ _start:
li x30,0
li x31,0
/* Clear FW_RAM */
li a0, 0xd0000000 // TK1_MMIO_FW_RAM_BASE
li a1, 0xd0000800 // TK1_MMIO_FW_RAM_BASE + TK1_MMIO_FW_RAM_SIZE
// Clear FW_RAM
la a0, _sfwram
la a1, _efwram
clear:
sw zero, 0(a0)
addi a0, a0, 4
blt a0, a1, clear
/*
* Init stack at top of fw_ram.
*/
li sp, 0xd0000800 // 2 kiB (TK1_MMIO_FW_RAM_SIZE)
// Zero-init bss section
la a0, _sbss
la a1, _ebss
loop_init_bss:
sw zero, 0(a0)
addi a0, a0, 4
blt a0, a1, loop_init_bss
// Init stack
la sp, _estack
call main

View file

@ -1,15 +1,16 @@
/*
* Copyright (C) 2023 - Tillitis AB
* SPDX-License-Identifier: GPL-2.0-only
*/
// Copyright (C) 2023 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef STATE_H
#define STATE_H
enum state {
FW_STATE_INITIAL,
FW_STATE_WAITCOMMAND,
FW_STATE_LOADING,
FW_STATE_RUN,
FW_STATE_LOAD_FLASH,
FW_STATE_LOAD_FLASH_MGMT,
FW_STATE_START,
FW_STATE_FAIL,
FW_STATE_MAX,
};

View file

@ -0,0 +1,317 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <tkey/debug.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include "auth_app.h"
#include "flash.h"
#include "partition_table.h"
#include "storage.h"
// Returns the index of the first empty area.
//
// Returns -1 on errors.
static int get_first_empty(struct partition_table *part_table)
{
if (part_table == NULL) {
return -1;
}
for (uint8_t i = 0; i < N_STORAGE_AREA; i++) {
if (part_table->app_storage[i].status == 0x00) {
return i;
}
}
return -1;
}
static int index_to_address(int index, uint32_t *address)
{
if (address == NULL) {
return -1;
}
if ((index < 0) || (index >= N_STORAGE_AREA)) {
return -1;
}
*address = ADDR_STORAGE_AREA + index * SIZE_STORAGE_AREA;
return 0;
}
// Returns the index of the area an app has allocated.
//
// Returns -1 on errors.
static int storage_get_area(struct partition_table *part_table)
{
if (part_table == NULL) {
return -1;
}
for (uint8_t i = 0; i < N_STORAGE_AREA; i++) {
if (part_table->app_storage[i].status != 0x00) {
if (auth_app_authenticate(
&part_table->app_storage[i].auth)) {
return i;
}
}
}
return -1;
}
// Allocate a new area for an app. Returns zero on success.
int storage_allocate_area(struct partition_table_storage *part_table_storage)
{
if (part_table_storage == NULL) {
return -1;
}
struct partition_table *part_table = &part_table_storage->table;
if (storage_get_area(part_table) != -1) {
/* Already has an area */
return 0;
}
int index = get_first_empty(part_table);
if (index < 0) {
/* No empty slot */
return -1;
}
uint32_t start_address = 0;
if (index_to_address(index, &start_address) != 0) {
return -1;
}
// Allocate the empty index found
// Erase area first
// Assumes the area is 64 KiB block aligned
flash_block_64_erase(start_address); // Erase first 64 KB block
flash_block_64_erase(start_address +
0x10000); // Erase second 64 KB block
// Write partition table lastly
part_table->app_storage[index].status = 0x01;
auth_app_create(&part_table->app_storage[index].auth);
if (part_table_write(part_table_storage) != 0) {
return -1;
}
return 0;
}
// Dealloacate a previously allocated storage area.
//
// Returns zero on success, and non-zero on errors.
int storage_deallocate_area(struct partition_table_storage *part_table_storage)
{
if (part_table_storage == NULL) {
return -1;
}
struct partition_table *part_table = &part_table_storage->table;
int index = storage_get_area(part_table);
if (index < 0) {
// No area to deallocate
return -1;
}
uint32_t start_address = 0;
if (index_to_address(index, &start_address) != 0) {
return -1;
}
// Erase area first
// Assumes the area is 64 KiB block aligned
flash_block_64_erase(start_address); // Erase first 64 KB block
flash_block_64_erase(start_address +
0x10000); // Erase second 64 KB block
// Clear partition table lastly
part_table->app_storage[index].status = 0;
(void)memset(part_table->app_storage[index].auth.nonce, 0x00,
sizeof(part_table->app_storage[index].auth.nonce));
(void)memset(
part_table->app_storage[index].auth.authentication_digest, 0x00,
sizeof(part_table->app_storage[index].auth.authentication_digest));
if (part_table_write(part_table_storage) != 0) {
return -1;
}
return 0;
}
// Erases sector. Offset of a sector to begin erasing, must be a
// multiple of the sector size. Size to erase in bytes, must be a
// multiple of the sector size.
//
// Returns zero on success, negative error code on failure
int storage_erase_sector(struct partition_table *part_table, uint32_t offset,
size_t size)
{
if (part_table == NULL) {
return -1;
}
int index = storage_get_area(part_table);
if (index == -1) {
// No allocated area
return -1;
}
uint32_t start_address = 0;
if (index_to_address(index, &start_address) != 0) {
return -1;
}
if (offset > SIZE_STORAGE_AREA) {
return -1;
}
// Cannot only erase entire sectors
if (offset % 4096 != 0) {
return -1;
}
// Cannot erase less than one sector
if (size < 4096 || size > SIZE_STORAGE_AREA || size % 4096 != 0) {
return -1;
}
if ((offset + size) > SIZE_STORAGE_AREA) {
return -1;
}
uint32_t address = start_address + offset;
debug_puts("storage: erase addr: ");
debug_putinthex(address);
debug_lf();
for (size_t i = 0; i < size; i += 4096) {
flash_sector_erase(address);
address += 4096;
}
return 0;
}
// Writes the specified data to the offset inside of the allocated area.
// Assumes area has been erased before hand. Offset must be a multiple of 256.
//
// Returns zero on success.
int storage_write_data(struct partition_table *part_table, uint32_t offset,
uint8_t *data, size_t size)
{
if (part_table == NULL) {
return -1;
}
// Allow data to point only to app RAM
if (data < (uint8_t *)TK1_RAM_BASE ||
data >= (uint8_t *)(TK1_RAM_BASE + TK1_RAM_SIZE)) {
return -1;
}
int index = storage_get_area(part_table);
if (index == -1) {
// No allocated area
return -1;
}
uint32_t start_address = 0;
if (index_to_address(index, &start_address) != 0) {
return -1;
}
if (offset > SIZE_STORAGE_AREA) {
return -1;
}
if (size > SIZE_STORAGE_AREA) {
return -1;
}
if ((offset + size) > SIZE_STORAGE_AREA) {
// Writing outside of area
return -1;
}
uint32_t address = start_address + offset;
debug_puts("storage: write to addr: ");
debug_putinthex(address);
debug_lf();
return flash_write_data(address, data, size);
}
// Reads size bytes of data at the specified offset inside of the
// allocated area.
//
// Only read limit is the size of the allocated area.
//
// Returns zero on success.
int storage_read_data(struct partition_table *part_table, uint32_t offset,
uint8_t *data, size_t size)
{
if (part_table == NULL) {
return -1;
}
// Allow data to point only to app RAM
if (data < (uint8_t *)TK1_RAM_BASE ||
data >= (uint8_t *)(TK1_RAM_BASE + TK1_RAM_SIZE)) {
return -1;
}
int index = storage_get_area(part_table);
if (index == -1) {
// No allocated area
return -1;
}
uint32_t start_address = 0;
if (index_to_address(index, &start_address) != 0) {
return -1;
}
if (offset > SIZE_STORAGE_AREA) {
return -1;
}
if (size > 4096) {
return -1;
}
if ((offset + size) > SIZE_STORAGE_AREA) {
// Reading outside of area
return -1;
}
uint32_t address = start_address + offset;
debug_puts("storage: read from addr: ");
debug_putinthex(address);
debug_lf();
return flash_read_data(address, data, size);
}

View file

@ -0,0 +1,22 @@
// Copyright (C) 2024 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef STORAGE_H
#define STORAGE_H
#include "partition_table.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
int storage_deallocate_area(struct partition_table_storage *part_table_storage);
int storage_allocate_area(struct partition_table_storage *part_table_storage);
int storage_erase_sector(struct partition_table *part_table, uint32_t offset,
size_t size);
int storage_write_data(struct partition_table *part_table, uint32_t offset,
uint8_t *data, size_t size);
int storage_read_data(struct partition_table *part_table, uint32_t offset,
uint8_t *data, size_t size);
#endif

View file

@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2025 Tillitis AB <tillitis.se>
// SPDX-License-Identifier: GPL-2.0-only
#ifdef QEMU_SYSCALL
#define picorv32_maskirq_insn(...)
#else
#include "../tk1/picorv32/custom_ops.S"
#endif
.section ".text"
.globl syscall_enable
syscall_enable:
// Enable syscall IRQ
li t0, 0x7fffffff // IRQ31 mask
picorv32_maskirq_insn(zero, t0) // Enable IRQs
ret

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,112 @@
// Copyright (C) 2025 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#include <stdint.h>
#include <tkey/assert.h>
#include <tkey/debug.h>
#include <tkey/lib.h>
#include <tkey/tk1_mem.h>
#include "partition_table.h"
#include "preload_app.h"
#include "reset.h"
#include "storage.h"
#include "syscall_num.h"
// clang-format off
static volatile uint32_t *udi = (volatile uint32_t *)TK1_MMIO_TK1_UDI_FIRST;
// clang-format on
extern struct partition_table_storage part_table_storage;
extern uint8_t part_status;
int32_t syscall_handler(uint32_t number, uint32_t arg1, uint32_t arg2,
uint32_t arg3)
{
switch (number) {
case TK1_SYSCALL_RESET:
return reset((struct reset *)arg1, (size_t)arg2);
break;
case TK1_SYSCALL_ALLOC_AREA:
if (storage_allocate_area(&part_table_storage) < 0) {
debug_puts("couldn't allocate storage area\n");
return -1;
}
return 0;
case TK1_SYSCALL_DEALLOC_AREA:
if (storage_deallocate_area(&part_table_storage) < 0) {
debug_puts("couldn't deallocate storage area\n");
return -1;
}
return 0;
case TK1_SYSCALL_WRITE_DATA:
if (storage_write_data(&part_table_storage.table, arg1,
(uint8_t *)arg2, arg3) < 0) {
debug_puts("couldn't write storage area\n");
return -1;
}
return 0;
case TK1_SYSCALL_READ_DATA:
if (storage_read_data(&part_table_storage.table, arg1,
(uint8_t *)arg2, arg3) < 0) {
debug_puts("couldn't read storage area\n");
return -1;
}
return 0;
case TK1_SYSCALL_ERASE_DATA:
if (storage_erase_sector(&part_table_storage.table, arg1,
arg2) < 0) {
debug_puts("couldn't erase storage area\n");
return -1;
}
return 0;
case TK1_SYSCALL_GET_VIDPID:
// UDI is 2 words: VID/PID & serial. Return just the
// first word. Serial is kept secret to the device
// app.
return udi[0];
case TK1_SYSCALL_PRELOAD_DELETE:
return preload_delete(&part_table_storage, 1);
case TK1_SYSCALL_PRELOAD_STORE:
// arg1 offset
// arg2 data
// arg3 size
// always using slot 1
return preload_store(&part_table_storage.table, arg1,
(uint8_t *)arg2, arg3, 1);
case TK1_SYSCALL_PRELOAD_STORE_FIN:
// arg1 app_size
// arg2 app_digest
// arg3 app_signature
// always using slot 1
return preload_store_finalize(&part_table_storage, arg1,
(uint8_t *)arg2, (uint8_t *)arg3,
1);
case TK1_SYSCALL_PRELOAD_GET_DIGSIG:
return preload_get_digsig(&part_table_storage.table,
(uint8_t *)arg1, (uint8_t *)arg2, 1);
case TK1_SYSCALL_STATUS:
return part_get_status();
case TK1_SYSCALL_GET_APP_DATA:
// arg1 next_app_data
return reset_data((uint8_t *)arg1);
default:
assert(1 == 2);
}
assert(1 == 2);
return -1; // This should never run
}

View file

@ -0,0 +1,24 @@
// Copyright (C) 2025 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
#ifndef TKEY_SYSCALL_NUM_H
#define TKEY_SYSCALL_NUM_H
enum syscall_num {
TK1_SYSCALL_RESET = 1,
TK1_SYSCALL_ALLOC_AREA = 2,
TK1_SYSCALL_DEALLOC_AREA = 3,
TK1_SYSCALL_WRITE_DATA = 4,
TK1_SYSCALL_READ_DATA = 5,
TK1_SYSCALL_ERASE_DATA = 6,
TK1_SYSCALL_GET_VIDPID = 7,
TK1_SYSCALL_PRELOAD_STORE = 8,
TK1_SYSCALL_PRELOAD_STORE_FIN = 9,
TK1_SYSCALL_PRELOAD_DELETE = 10,
TK1_SYSCALL_PRELOAD_GET_DIGSIG = 11,
TK1_SYSCALL_REG_MGMT = 12,
TK1_SYSCALL_STATUS = 13,
TK1_SYSCALL_GET_APP_DATA = 14,
};
#endif

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 -Os -ffast-math -fno-common \
-fno-builtin-printf -fno-builtin-putchar -nostdlib -mno-relax -flto \
-Wall -Werror=implicit-function-declaration \
-I $(INCLUDE) -I .
AS = clang
AR = llvm-ar
ASFLAGS = -target riscv32-unknown-none-elf -march=rv32iczmmul -mabi=ilp32 \
-mcmodel=medany -mno-relax
LDFLAGS=-T app.lds -L libcommon/ -lcommon -L libcrt0/ -lcrt0
.PHONY: all
all: libcrt0.a libcommon.a libmonocypher.a libblake2s.a
IMAGE=ghcr.io/tillitis/tkey-builder:4
podman:
podman run --rm --mount type=bind,source=$(CURDIR),target=/src \
-w /src -it $(IMAGE) make -j
.PHONY: check
check:
clang-tidy -header-filter=.* -checks=cert-* libcommon/*.c -- $(CFLAGS)
# C runtime library
libcrt0.a: libcrt0/crt0.o
$(AR) -qc $@ libcrt0/crt0.o
# Common C functions
LIBOBJS=libcommon/assert.o libcommon/led.o libcommon/lib.o \
libcommon/proto.o libcommon/touch.o libcommon/io.o
libcommon.a: $(LIBOBJS)
$(AR) -qc $@ $(LIBOBJS)
$(LIBOBJS): include/tkey/assert.h include/tkey/led.h \
include/tkey/lib.h include/tkey/proto.h include/tkey/tk1_mem.h \
include/tkey/touch.h include/tkey/debug.h
# Monocypher
MONOOBJS=monocypher/monocypher.o monocypher/monocypher-ed25519.o
libmonocypher.a: $(MONOOBJS)
$(AR) -qc $@ $(MONOOBJS)
$MONOOBJS: monocypher/monocypher-ed25519.h monocypher/monocypher.h
# blake2s
B2OBJS=blake2s/blake2s.o
libblake2s.a: $(B2OBJS)
$(AR) -qc $@ $(B2OBJS)
$B2OBJS: blake2s/blake2s.h
LIBS=libcrt0.a libcommon.a
.PHONY: clean
clean:
rm -f $(LIBS) $(LIBOBJS) libcrt0/crt0.o
rm -f libmonocypher.a $(MONOOBJS)
rm -f libblake2s.a $(B2OBJS)
# Create compile_commands.json for clangd and LSP
.PHONY: clangd
clangd: compile_commands.json
compile_commands.json:
$(MAKE) clean
bear -- make all
# Uses ../.clang-format
FMTFILES=include/tkey/*.h libcommon/*.c
.PHONY: fmt
fmt:
clang-format --dry-run --ferror-limit=0 $(FMTFILES)
clang-format --verbose -i $(FMTFILES)
.PHONY: checkfmt
checkfmt:
clang-format --dry-run --ferror-limit=0 --Werror $(FMTFILES)
.PHONY: update-mem-include
update-mem-include:
cp -af ../tillitis-key1/hw/application_fpga/fw/tk1_mem.h \
include/tkey/tk1_mem.h
echo "Remember to update header include guard!"

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

@ -3,22 +3,22 @@
// blake2s.c
// ---------
//
// A simple blake2s Reference Implementation.
// A simple BLAKE2s reference implementation.
//
// See LICENSE for license terms.
// See README.md in the repo root for info about source code origin.
//======================================================================
#include "../types.h"
#include "../lib.h"
#include <stdint.h>
#include "blake2s.h"
// Dummy printf() for verbose mode
static void printf(const char *format, ...)
{
}
#define VERBOSE 0
#define SHOW_V 0
#define SHOW_M_WORDS 0
#if VERBOSE || SHOW_V || SHOW_M_WORDS
#include <stdio.h>
#endif
// Cyclic right rotation.
#ifndef ROTR32
@ -41,6 +41,7 @@ static const uint32_t blake2s_iv[8] = {
};
#if VERBOSE || SHOW_V
//------------------------------------------------------------------
//------------------------------------------------------------------
void print_v(uint32_t *v) {
@ -71,24 +72,25 @@ void print_ctx(blake2s_ctx *ctx) {
printf("\n");
}
#endif
//------------------------------------------------------------------
// B2S_G macro redefined as a G function.
// Allows us to output intermediate values for debugging.
//------------------------------------------------------------------
void G(uint32_t *v, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, uint32_t y) {
if (VERBOSE) {
#if VERBOSE
printf("G started.\n");
}
#endif
if (SHOW_V) {
#if SHOW_V
printf("v before processing:\n");
print_v(&v[0]);
}
#endif
if (SHOW_M_WORDS) {
#if SHOW_M_WORDS
printf("x: 0x%08x, y: 0x%08x\n", x, y);
}
#endif
v[a] = v[a] + v[b] + x;
v[d] = ROTR32(v[d] ^ v[a], 16);
@ -99,14 +101,14 @@ void G(uint32_t *v, uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t x,
v[c] = v[c] + v[d];
v[b] = ROTR32(v[b] ^ v[c], 7);
if (SHOW_V) {
#if SHOW_V
printf("v after processing:\n");
print_v(&v[0]);
}
#endif
if (VERBOSE) {
#if VERBOSE
printf("G completed.\n\n");
}
#endif
}
@ -131,9 +133,9 @@ static void blake2s_compress(blake2s_ctx *ctx, int last)
int i;
uint32_t v[16], m[16];
if (VERBOSE) {
#if VERBOSE
printf("blake2s_compress started.\n");
}
#endif
// init work variables
for (i = 0; i < 8; i++) {
@ -143,9 +145,9 @@ static void blake2s_compress(blake2s_ctx *ctx, int last)
// low 32 bits of offset
// high 32 bits
if (VERBOSE) {
#if VERBOSE
printf("t[0]: 0x%08x, t[1]: 0x%08x\n", ctx->t[0], ctx->t[1]);
}
#endif
v[12] ^= ctx->t[0];
v[13] ^= ctx->t[1];
@ -159,52 +161,52 @@ static void blake2s_compress(blake2s_ctx *ctx, int last)
m[i] = B2S_GET32(&ctx->b[4 * i]);
}
if (VERBOSE) {
#if VERBOSE
printf("v before G processing:\n");
print_v(&v[0]);
}
#endif
// Ten rounds of the G function applied on rows, diagonal.
for (i = 0; i < 10; i++) {
if (VERBOSE) {
#if VERBOSE
printf("Round %02d:\n", (i + 1));
printf("Row processing started.\n");
}
#endif
G(&v[0], 0, 4, 8, 12, m[sigma[i][ 0]], m[sigma[i][ 1]]);
G(&v[0], 1, 5, 9, 13, m[sigma[i][ 2]], m[sigma[i][ 3]]);
G(&v[0], 2, 6, 10, 14, m[sigma[i][ 4]], m[sigma[i][ 5]]);
G(&v[0], 3, 7, 11, 15, m[sigma[i][ 6]], m[sigma[i][ 7]]);
if (VERBOSE) {
#if VERBOSE
printf("Row processing completed.\n");
printf("Diagonal processing started.\n");
}
#endif
G(&v[0], 0, 5, 10, 15, m[sigma[i][ 8]], m[sigma[i][ 9]]);
G(&v[0], 1, 6, 11, 12, m[sigma[i][10]], m[sigma[i][11]]);
G(&v[0], 2, 7, 8, 13, m[sigma[i][12]], m[sigma[i][13]]);
G(&v[0], 3, 4, 9, 14, m[sigma[i][14]], m[sigma[i][15]]);
if (VERBOSE) {
#if VERBOSE
printf("Diagonal processing completed.\n");
printf("\n");
}
#endif
}
if (VERBOSE) {
#if VERBOSE
printf("v after G processing:\n");
print_v(&v[0]);
}
#endif
// Update the hash state.
for (i = 0; i < 8; ++i) {
ctx->h[i] ^= v[i] ^ v[i + 8];
}
if (VERBOSE) {
#if VERBOSE
printf("blake2s_compress completed.\n");
}
#endif
}
@ -218,11 +220,11 @@ int blake2s_init(blake2s_ctx *ctx, size_t outlen,
{
size_t i;
if (VERBOSE) {
#if VERBOSE
printf("blake2s_init started.\n");
printf("Context before blake2s_init processing:\n");
print_ctx(ctx);
}
#endif
if (outlen == 0 || outlen > 32 || keylen > 32)
return -1; // illegal parameters
@ -243,11 +245,11 @@ int blake2s_init(blake2s_ctx *ctx, size_t outlen,
ctx->c = 64; // at the end
}
if (VERBOSE) {
#if VERBOSE
printf("Context after blake2s_init processing:\n");
print_ctx(ctx);
printf("blake2s_init completed.\n");
}
#endif
return 0;
}
@ -261,11 +263,11 @@ void blake2s_update(blake2s_ctx *ctx,
{
size_t i;
if (VERBOSE) {
#if VERBOSE
printf("blake2s_update started.\n");
printf("Context before blake2s_update processing:\n");
print_ctx(ctx);
}
#endif
for (i = 0; i < inlen; i++) {
if (ctx->c == 64) { // buffer full ?
@ -278,11 +280,11 @@ void blake2s_update(blake2s_ctx *ctx,
ctx->b[ctx->c++] = ((const uint8_t *) in)[i];
}
if (VERBOSE) {
#if VERBOSE
printf("Context after blake2s_update processing:\n");
print_ctx(ctx);
printf("blake2s_update completed.\n");
}
#endif
}
@ -294,11 +296,11 @@ void blake2s_final(blake2s_ctx *ctx, void *out)
{
size_t i;
if (VERBOSE) {
#if VERBOSE
printf("blake2s_final started.\n");
printf("Context before blake2s_final processing:\n");
print_ctx(ctx);
}
#endif
ctx->t[0] += ctx->c; // mark last block offset
@ -321,11 +323,11 @@ void blake2s_final(blake2s_ctx *ctx, void *out)
(ctx->h[i >> 2] >> (8 * (i & 3))) & 0xFF;
}
if (VERBOSE) {
#if VERBOSE
printf("Context after blake2s_final processing:\n");
print_ctx(ctx);
printf("blake2s_final completed.\n");
}
#endif
}
@ -334,15 +336,16 @@ void blake2s_final(blake2s_ctx *ctx, void *out)
//------------------------------------------------------------------
int blake2s(void *out, size_t outlen,
const void *key, size_t keylen,
const void *in, size_t inlen,
blake2s_ctx *ctx)
const void *in, size_t inlen)
{
if (blake2s_init(ctx, outlen, key, keylen))
blake2s_ctx ctx;
if (blake2s_init(&ctx, outlen, key, keylen))
return -1;
blake2s_update(ctx, in, inlen);
blake2s_update(&ctx, in, inlen);
blake2s_final(ctx, out);
blake2s_final(&ctx, out);
return 0;
}

View file

@ -1,10 +1,18 @@
//======================================================================
//
// blake2s.h
// ---------
// BLAKE2s Hashing Context and API Prototypes
//
// See LICENSE for license terms.
// See README.md in the repo root for info about source code origin.
//======================================================================
#ifndef BLAKE2S_H
#define BLAKE2S_H
#include "../types.h"
#include <stdint.h>
#include <stddef.h>
// state context
typedef struct {
@ -32,8 +40,6 @@ void blake2s_final(blake2s_ctx *ctx, void *out);
// All-in-one convenience function.
int blake2s(void *out, size_t outlen, // return buffer for digest
const void *key, size_t keylen, // optional secret key
const void *in, size_t inlen, // data to be hashed
blake2s_ctx *ctx);
const void *in, size_t inlen); // data to be hashed
#endif

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

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