Skip to content

Commit 673988c

Browse files
committed
[otbn] Add compression option
Use LZ4 to add a compression option in otbn_binary (activated by setting compress = True). If this is set, the imem and dmem of the otbn application are compressed before they are stored. By default, binaries are not compressed. Change the otbn.c and otbn.h in the cryptolib to use LZ4 from lib/base in order to decompress the loaded imem and dmem from an application. The compression option is therefore mandatory in the cryptolib (lib/crypto), however, the applications from silicon_creator (using silicon_creator/lib/drivers/otbn.c/h) still use the uncompressed version (and will not work with the compressed version). This option saves the cryptolib ~7000bytes while increasing ~400bytes to load LZ4.c while providing backwards compatability for the boot. Concerning the CRC: The CRC for the imem/dmem is calculated before compression. For loading to OTBN, the OTBN is given the CRC of the uncompressed payload before and checks itself the CRC of what it receives. I.e., the CRC still provides end-2-end protection. Signed-off-by: Siemen Dhooghe <sdhooghe@google.com>
1 parent d3b1d62 commit 673988c

15 files changed

Lines changed: 461 additions & 196 deletions

File tree

rules/otbn.bzl

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,20 @@ def _otbn_binary(ctx, additional_srcs = []):
9191

9292
deps = [f for dep in ctx.attr.deps for f in dep.files.to_list()]
9393

94+
script_args = [
95+
"--app-name={}".format(ctx.attr.name),
96+
"--archive",
97+
"--no-assembler",
98+
"--out-dir={}".format(elf.dirname),
99+
]
100+
101+
# Add the compress flag if the BUILD file requested it
102+
if getattr(ctx.attr, "compress", False):
103+
script_args.append("--compress")
104+
105+
# Add the input files
106+
script_args += [o.path for o in ([obj] + deps)]
107+
94108
# Run the otbn_build.py script to link object files from the sources and
95109
# dependencies.
96110
ctx.actions.run(
@@ -106,12 +120,7 @@ def _otbn_binary(ctx, additional_srcs = []):
106120
"RV32_TOOL_LD": ctx.executable._riscv32_ld.path,
107121
"RV32_TOOL_OBJCOPY": ctx.executable._riscv32_objcopy.path,
108122
},
109-
arguments = [
110-
"--app-name={}".format(ctx.attr.name),
111-
"--archive",
112-
"--no-assembler",
113-
"--out-dir={}".format(elf.dirname),
114-
] + [o.path for o in ([obj] + deps)],
123+
arguments = script_args,
115124
executable = ctx.executable._wrapper,
116125
)
117126

@@ -375,6 +384,7 @@ otbn_binary = rv_rule(
375384
"srcs": attr.label_list(allow_files = True),
376385
"deps": attr.label_list(providers = [DefaultInfo]),
377386
"args": attr.string_list(),
387+
"compress": attr.bool(default = False),
378388
"_riscv32_ar": attr.label(
379389
default = Label("@lowrisc_rv32imcb_toolchain//:bin/riscv32-unknown-elf-ar"),
380390
allow_single_file = True,

sw/device/lib/crypto/drivers/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ cc_library(
387387
"//sw/device/lib/base:bitfield",
388388
"//sw/device/lib/base:crc32",
389389
"//sw/device/lib/base:hardened",
390+
"//sw/device/lib/base:lz4",
390391
"//sw/device/lib/base:random_order",
391392
"//sw/device/lib/crypto/impl:status",
392393
],

sw/device/lib/crypto/drivers/otbn.c

Lines changed: 76 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "sw/device/lib/base/bitfield.h"
99
#include "sw/device/lib/base/crc32.h"
1010
#include "sw/device/lib/base/hardened.h"
11+
#include "sw/device/lib/base/lz4.h"
1112
#include "sw/device/lib/base/macros.h"
1213
#include "sw/device/lib/base/random_order.h"
1314
#include "sw/device/lib/base/status.h"
@@ -283,79 +284,114 @@ status_t otbn_set_ctrl_software_errs_fatal(bool enable) {
283284
}
284285

285286
/**
286-
* Checks if the OTBN application's IMEM and DMEM address parameters are valid.
287+
* Checks if the OTBN application's compressed IMEM and DMEM address parameters
288+
* are valid.
287289
*
288290
* This function checks the following properties:
289-
* - IMEM and DMEM ranges must not be "backwards" in memory, with the end
290-
* address coming before the start address.
291-
* - The IMEM range must be non-empty.
291+
* - IMEM and DMEM compressed pointer ranges must not be "backwards" in memory.
292+
* - The uncompressed IMEM range must be non-empty.
292293
*
293294
* @param app the OTBN application to check
294295
* @return `OTCRYPTO_OK` if checks pass, otherwise `OTCRYPTO_BAD_ARGS`.
295296
*/
296297
static status_t check_app_address_ranges(const otbn_app_t *app) {
297-
// IMEM must not be backwards or empty.
298-
if (app->imem_end <= app->imem_start) {
298+
// Compressed IMEM must not be backwards, and uncompressed size must not
299+
// be empty.
300+
if (app->imem_compressed_end <= app->imem_compressed_start ||
301+
app->imem_uncompressed_words == 0) {
299302
// COVERAGE (SW ERR) This is an internal function, we only provide it valid
300303
// inputs.
301304
return OTCRYPTO_BAD_ARGS;
302305
}
303-
HARDENED_CHECK_LT(app->imem_start, app->imem_end);
306+
HARDENED_CHECK_LT((uintptr_t)app->imem_compressed_start,
307+
(uintptr_t)app->imem_compressed_end);
308+
HARDENED_CHECK_GT(app->imem_uncompressed_words, 0);
304309

305-
// DMEM data section must not be backwards.
306-
if (app->dmem_data_end < app->dmem_data_start) {
310+
// Compressed DMEM must not be backwards.
311+
if (app->dmem_compressed_end < app->dmem_compressed_start) {
307312
return OTCRYPTO_BAD_ARGS;
308313
}
309-
HARDENED_CHECK_LE(app->dmem_data_start, app->dmem_data_end);
314+
HARDENED_CHECK_LE((uintptr_t)app->dmem_compressed_start,
315+
(uintptr_t)app->dmem_compressed_end);
310316

311317
return OTCRYPTO_OK;
312318
}
313319

314320
status_t otbn_load_app(const otbn_app_t app) {
315321
HARDENED_TRY(check_app_address_ranges(&app));
316322

323+
// Ensure that the total uncompressed IMEM section fits in IMEM.
324+
// IMEM always starts at offset 0.
325+
HARDENED_TRY(
326+
check_offset_len(0, app.imem_uncompressed_words, kOtbnIMemSizeBytes));
327+
328+
// Ensure that the total uncompressed data section fits in DMEM.
329+
HARDENED_TRY(check_offset_len(app.dmem_data_start_addr,
330+
app.dmem_uncompressed_words,
331+
kOtbnDMemSizeBytes));
332+
317333
// Ensure OTBN is idle.
318334
HARDENED_TRY(otbn_assert_idle());
319335

320-
const size_t imem_num_words = (size_t)(app.imem_end - app.imem_start);
321-
const size_t data_num_words =
322-
(size_t)(app.dmem_data_end - app.dmem_data_start);
323-
324336
HARDENED_TRY(otbn_imem_sec_wipe());
325337
HARDENED_TRY(otbn_dmem_sec_wipe());
326338

327339
// Reset the LOAD_CHECKSUM register.
328340
abs_mmio_write32(kBase + OTBN_LOAD_CHECKSUM_REG_OFFSET, 0);
329341

330-
// Ensure that the IMEM section fits in IMEM and the data section fits in
331-
// DMEM.
332-
HARDENED_TRY(check_offset_len(app.dmem_data_start_addr, data_num_words,
333-
kOtbnDMemSizeBytes));
334-
335-
// Write to IMEM. Always starts at zero on the OTBN side.
336-
otbn_addr_t imem_offset = 0;
337-
HARDENED_TRY(
338-
check_offset_len(imem_offset, imem_num_words, kOtbnIMemSizeBytes));
339-
uint32_t imem_start_addr = kBase + OTBN_IMEM_REG_OFFSET + imem_offset;
340-
uint32_t i = 0;
341-
for (; launder32(i) < imem_num_words; i++) {
342-
HARDENED_CHECK_LT(i, imem_num_words);
343-
abs_mmio_write32(imem_start_addr + i * sizeof(uint32_t), app.imem_start[i]);
342+
// A 256-byte stack buffer for chunked decompression.
343+
uint32_t local_chunk_buf[256 / sizeof(uint32_t)];
344+
345+
// IMEM decompression and loading
346+
const uint8_t *src = app.imem_compressed_start;
347+
uint32_t mmio_addr = kBase + OTBN_IMEM_REG_OFFSET;
348+
349+
while (src < app.imem_compressed_end) {
350+
// Parse the 4-byte chunk header as unsigned 32-bit integers
351+
uint32_t comp_len = (uint32_t)(src[0] | (src[1] << 8));
352+
uint32_t uncomp_len = (uint32_t)(src[2] | (src[3] << 8));
353+
src += 4;
354+
355+
if (LZ4_decompress((const char *)src, (char *)local_chunk_buf,
356+
(int)comp_len,
357+
(int)sizeof(local_chunk_buf)) != (int)uncomp_len) {
358+
return OTCRYPTO_FATAL_ERR;
359+
}
360+
361+
// Write to IMEM. Always starts at zero on the OTBN side.
362+
uint32_t words = uncomp_len / (uint32_t)sizeof(uint32_t);
363+
for (uint32_t i = 0; launder32(i) < words; i++) {
364+
abs_mmio_write32(mmio_addr + i * sizeof(uint32_t), local_chunk_buf[i]);
365+
}
366+
367+
src += comp_len;
368+
mmio_addr += uncomp_len;
344369
}
345-
HARDENED_CHECK_EQ(i, imem_num_words);
346370

347-
// Write the data portion to DMEM.
348-
otbn_addr_t data_offset = app.dmem_data_start_addr;
349-
HARDENED_TRY(
350-
check_offset_len(data_offset, data_num_words, kOtbnDMemSizeBytes));
351-
uint32_t data_start_addr = kBase + OTBN_DMEM_REG_OFFSET + data_offset;
352-
i = 0;
353-
for (; launder32(i) < data_num_words; i++) {
354-
HARDENED_CHECK_LT(i, data_num_words);
355-
abs_mmio_write32(data_start_addr + i * sizeof(uint32_t),
356-
app.dmem_data_start[i]);
371+
// DMEM decompression and loading
372+
src = app.dmem_compressed_start;
373+
mmio_addr = kBase + OTBN_DMEM_REG_OFFSET + app.dmem_data_start_addr;
374+
375+
while (src < app.dmem_compressed_end) {
376+
uint32_t comp_len = (uint32_t)(src[0] | (src[1] << 8));
377+
uint32_t uncomp_len = (uint32_t)(src[2] | (src[3] << 8));
378+
src += 4;
379+
380+
if (LZ4_decompress((const char *)src, (char *)local_chunk_buf,
381+
(int)comp_len,
382+
(int)sizeof(local_chunk_buf)) != (int)uncomp_len) {
383+
return OTCRYPTO_FATAL_ERR;
384+
}
385+
386+
// Write the data portion to DMEM.
387+
uint32_t words = uncomp_len / (uint32_t)sizeof(uint32_t);
388+
for (uint32_t i = 0; launder32(i) < words; i++) {
389+
abs_mmio_write32(mmio_addr + i * sizeof(uint32_t), local_chunk_buf[i]);
390+
}
391+
392+
src += comp_len;
393+
mmio_addr += uncomp_len;
357394
}
358-
HARDENED_CHECK_EQ(i, data_num_words);
359395

360396
// Ensure that the checksum matches expectations.
361397
uint32_t checksum = abs_mmio_read32(kBase + OTBN_LOAD_CHECKSUM_REG_OFFSET);

sw/device/lib/crypto/drivers/otbn.h

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -67,36 +67,52 @@ typedef uint32_t otbn_addr_t;
6767
*/
6868
typedef struct otbn_app {
6969
/**
70-
* Start of OTBN instruction memory in the embedded program.
70+
* Start of the compressed OTBN instruction memory in the embedded program.
7171
*
7272
* This pointer references Ibex's memory.
7373
*/
74-
const uint32_t *imem_start;
74+
const uint8_t *imem_compressed_start;
7575
/**
76-
* The first word after OTBN instruction memory in the embedded program.
76+
* The first byte after the compressed OTBN instruction memory.
7777
*
7878
* This pointer references Ibex's memory.
7979
*
80-
* This address satifies `imem_start < imem_end`.
80+
* This address satisfies `imem_compressed_start <= imem_compressed_end`.
8181
*/
82-
const uint32_t *imem_end;
82+
const uint8_t *imem_compressed_end;
8383
/**
84-
* Start of initialized OTBN data in the embedded program.
84+
* The expected size of the uncompressed instruction memory, in 32-bit words.
85+
*
86+
* This defines exactly how many words the host CPU will write to the OTBN
87+
* IMEM registers after decompressing the payload.
88+
*/
89+
const size_t imem_uncompressed_words;
90+
91+
/**
92+
* Start of the compressed initialized OTBN data in the embedded program.
8593
*
8694
* This pointer references Ibex's memory.
8795
*
88-
* Data in between `dmem_data_start` and `dmem_data_end` will be copied to
89-
* OTBN at app load time.
96+
* Data between `dmem_compressed_start` and `dmem_compressed_end` will be
97+
* decompressed and copied to OTBN at app load time.
9098
*/
91-
const uint32_t *dmem_data_start;
99+
const uint8_t *dmem_compressed_start;
92100
/**
93-
* The first word after initialized OTBN data in the embedded program.
101+
* The first byte after the compressed initialized OTBN data.
94102
*
95103
* This pointer references Ibex's memory.
96104
*
97-
* Should satisfy `dmem_data_start <= dmem_data_end`.
105+
* Should satisfy `dmem_compressed_start <= dmem_compressed_end`.
98106
*/
99-
const uint32_t *dmem_data_end;
107+
const uint8_t *dmem_compressed_end;
108+
/**
109+
* The expected size of the uncompressed initialized data, in 32-bit words.
110+
*
111+
* This defines exactly how many words the host CPU will write to the OTBN
112+
* DMEM registers after decompressing the payload.
113+
*/
114+
const size_t dmem_uncompressed_words;
115+
100116
/**
101117
* Start of initialized data section in OTBN's DMEM.
102118
*
@@ -166,22 +182,25 @@ typedef struct otbn_app {
166182
/**
167183
* Makes an embedded OTBN application image available for use.
168184
*
169-
* Make symbols available that indicate the start and the end of instruction
170-
* and data memory regions, as they are stored in the device memory.
185+
* Makes symbols available that indicate the start and the end of the compressed
186+
* instruction and data memory regions, their uncompressed sizes, and the
187+
* expected checksum for integrity verification.
171188
*
172189
* Use this macro instead of manually declaring the symbols as symbol names
173190
* might change.
174191
*
175192
* @param app_name Name of the application to load, which is typically the
176193
* name of the main (assembly) source file.
177194
*/
178-
#define OTBN_DECLARE_APP_SYMBOLS(app_name) \
179-
OTBN_DECLARE_SYMBOL_PTR(app_name, _imem_start); \
180-
OTBN_DECLARE_SYMBOL_PTR(app_name, _imem_end); \
181-
OTBN_DECLARE_SYMBOL_PTR(app_name, _dmem_data_start); \
182-
OTBN_DECLARE_SYMBOL_PTR(app_name, _dmem_data_end); \
183-
OTBN_DECLARE_SYMBOL_ADDR(app_name, _dmem_data_start); \
184-
OTBN_DECLARE_SYMBOL_ADDR(app_name, _checksum)
195+
#define OTBN_DECLARE_APP_SYMBOLS(app_name) \
196+
extern const uint8_t OTBN_SYMBOL_PTR(app_name, imem_compressed_start)[]; \
197+
extern const uint8_t OTBN_SYMBOL_PTR(app_name, imem_compressed_end)[]; \
198+
extern const uint8_t OTBN_SYMBOL_PTR(app_name, dmem_compressed_start)[]; \
199+
extern const uint8_t OTBN_SYMBOL_PTR(app_name, dmem_compressed_end)[]; \
200+
OTBN_DECLARE_SYMBOL_ADDR(app_name, _dmem_data_start); \
201+
OTBN_DECLARE_SYMBOL_ADDR(app_name, _checksum); \
202+
OTBN_DECLARE_SYMBOL_ADDR(app_name, imem_uncompressed_bytes); \
203+
OTBN_DECLARE_SYMBOL_ADDR(app_name, dmem_uncompressed_bytes)
185204

186205
/**
187206
* Initializes the OTBN application information structure.
@@ -190,17 +209,27 @@ typedef struct otbn_app {
190209
* through `OTBN_DECLARE_APP_SYMBOLS()`, use this macro to initialize an
191210
* `otbn_app_t` struct with those symbols.
192211
*
212+
* This macro automatically handles the mapping of the compressed payload
213+
* pointers and converts the uncompressed byte counts provided by the linker
214+
* into the 32-bit word counts expected by the OTBN loader.
215+
*
193216
* @param app_name Name of the application to load.
194217
* @see OTBN_DECLARE_APP_SYMBOLS()
195218
*/
196-
#define OTBN_APP_T_INIT(app_name) \
197-
((otbn_app_t){ \
198-
.imem_start = OTBN_SYMBOL_PTR(app_name, _imem_start), \
199-
.imem_end = OTBN_SYMBOL_PTR(app_name, _imem_end), \
200-
.dmem_data_start = OTBN_SYMBOL_PTR(app_name, _dmem_data_start), \
201-
.dmem_data_end = OTBN_SYMBOL_PTR(app_name, _dmem_data_end), \
202-
.dmem_data_start_addr = OTBN_ADDR_T_INIT(app_name, _dmem_data_start), \
203-
.checksum = OTBN_ADDR_T_INIT(app_name, _checksum), \
219+
#define OTBN_APP_T_INIT(app_name) \
220+
((otbn_app_t){ \
221+
.imem_compressed_start = \
222+
OTBN_SYMBOL_PTR(app_name, imem_compressed_start), \
223+
.imem_compressed_end = OTBN_SYMBOL_PTR(app_name, imem_compressed_end), \
224+
.imem_uncompressed_words = \
225+
OTBN_ADDR_T_INIT(app_name, imem_uncompressed_bytes) / 4, \
226+
.dmem_compressed_start = \
227+
OTBN_SYMBOL_PTR(app_name, dmem_compressed_start), \
228+
.dmem_compressed_end = OTBN_SYMBOL_PTR(app_name, dmem_compressed_end), \
229+
.dmem_uncompressed_words = \
230+
OTBN_ADDR_T_INIT(app_name, dmem_uncompressed_bytes) / 4, \
231+
.dmem_data_start_addr = OTBN_ADDR_T_INIT(app_name, _dmem_data_start), \
232+
.checksum = OTBN_ADDR_T_INIT(app_name, _checksum), \
204233
})
205234

206235
/**

0 commit comments

Comments
 (0)