Software APIs
otbn.c
1 // Copyright lowRISC contributors (OpenTitan project).
2 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
3 // SPDX-License-Identifier: Apache-2.0
4 
5 #include "sw/device/silicon_creator/lib/drivers/otbn.h"
6 
7 #include <assert.h>
8 #include <stddef.h>
9 #include <stdint.h>
10 
14 #include "sw/device/silicon_creator/lib/drivers/rnd.h"
15 #include "sw/device/silicon_creator/lib/error.h"
16 
18 #include "otbn_regs.h" // Generated.
19 
20 enum {
21  /**
22  * Base address for OTBN.
23  */
25  /**
26  * Highest index of OTBN error bits.
27  */
28  kOtbnErrBitsLast = OTBN_ERR_BITS_FATAL_SOFTWARE_BIT,
29 };
30 
31 /**
32  * Ensures that `offset_bytes` and `len` are valid for a given `mem_size`.
33  */
35 static rom_error_t check_offset_len(uint32_t offset_bytes, size_t num_words,
36  size_t mem_size) {
37  if (num_words > UINT32_MAX / sizeof(uint32_t)) {
38  return kErrorOtbnBadOffsetLen;
39  }
40  uint32_t num_bytes = num_words * sizeof(uint32_t);
41 
42  if (offset_bytes > UINT32_MAX - num_bytes) {
43  return kErrorOtbnBadOffsetLen;
44  }
45  uint32_t adjusted_offset_bytes = offset_bytes + num_bytes;
46 
47  if (adjusted_offset_bytes > mem_size) {
48  return kErrorOtbnBadOffsetLen;
49  }
50 
51  return kErrorOk;
52 }
53 
54 rom_error_t sc_otbn_busy_wait_for_done(void) {
55  uint32_t status = launder32(UINT32_MAX);
56  rom_error_t res = launder32(kErrorOk ^ status);
57  do {
58  status = abs_mmio_read32(kBase + OTBN_STATUS_REG_OFFSET);
59  } while (launder32(status) != kScOtbnStatusIdle &&
60  launder32(status) != kScOtbnStatusLocked);
61  res ^= ~status;
62  if (launder32(res) == kErrorOk) {
63  HARDENED_CHECK_EQ(res, kErrorOk);
64  HARDENED_CHECK_EQ(abs_mmio_read32(kBase + OTBN_STATUS_REG_OFFSET),
65  kScOtbnStatusIdle);
66  return res;
67  }
68  return kErrorOtbnUnavailable;
69 }
70 
71 /**
72  * Helper function for writing to OTBN's DMEM or IMEM.
73  *
74  * @param dest_addr Destination address.
75  * @param src Source buffer.
76  * @param num_words Number of words to copy.
77  */
78 static void sc_otbn_write(uint32_t dest_addr, const uint32_t *src,
79  size_t num_words) {
80  // Start from a random index less than `num_words`.
81  uint32_t i = ((uint64_t)rnd_uint32() * (uint64_t)num_words) >> 32;
82  enum { kStep = 1 };
83  uint32_t iter_cnt = 0, r_iter_cnt = num_words - 1;
84  for (; launder32(iter_cnt) < num_words && launder32(r_iter_cnt) < num_words;
85  ++iter_cnt, --r_iter_cnt) {
86  abs_mmio_write32(dest_addr + i * sizeof(uint32_t), src[i]);
87  i += kStep;
88  if (launder32(i) >= num_words) {
89  i -= num_words;
90  }
91  HARDENED_CHECK_LT(i, num_words);
92  }
93  HARDENED_CHECK_EQ(iter_cnt, num_words);
94  HARDENED_CHECK_EQ(r_iter_cnt, UINT32_MAX);
95 }
96 
98 static rom_error_t sc_otbn_imem_write(size_t num_words, const uint32_t *src,
99  sc_otbn_addr_t dest) {
100  HARDENED_RETURN_IF_ERROR(
101  check_offset_len(dest, num_words, OTBN_IMEM_SIZE_BYTES));
102  sc_otbn_write(kBase + OTBN_IMEM_REG_OFFSET + dest, src, num_words);
103  return kErrorOk;
104 }
105 
106 rom_error_t sc_otbn_dmem_write(size_t num_words, const uint32_t *src,
107  sc_otbn_addr_t dest) {
108  HARDENED_RETURN_IF_ERROR(
109  check_offset_len(dest, num_words, OTBN_DMEM_SIZE_BYTES));
110  sc_otbn_write(kBase + OTBN_DMEM_REG_OFFSET + dest, src, num_words);
111  return kErrorOk;
112 }
113 
114 rom_error_t sc_otbn_dmem_read(size_t num_words, sc_otbn_addr_t src,
115  uint32_t *dest) {
116  HARDENED_RETURN_IF_ERROR(
117  check_offset_len(src, num_words, OTBN_DMEM_SIZE_BYTES));
118  uint32_t i = 0, r = num_words - 1;
119  for (; launder32(i) < num_words && launder32(r) < num_words; ++i, --r) {
120  dest[i] = abs_mmio_read32(kBase + OTBN_DMEM_REG_OFFSET + src +
121  i * sizeof(uint32_t));
122  }
123  HARDENED_CHECK_EQ(i, num_words);
124  HARDENED_CHECK_EQ(r, UINT32_MAX);
125  return kErrorOk;
126 }
127 
128 /**
129  * Helper function for running an OTBN command.
130  *
131  * This function blocks until OTBN is idle.
132  *
133  * @param cmd OTBN command.
134  * @param error Error to return if operation fails.
135  * @return Result of the operation.
136  */
138 static rom_error_t sc_otbn_cmd_run(sc_otbn_cmd_t cmd, rom_error_t error) {
139  enum {
140  kIntrStateDone = (1 << OTBN_INTR_COMMON_DONE_BIT),
141  // Use a bit index that doesn't overlap with error bits.
142  kResDoneBit = 31,
143  };
144  static_assert((UINT32_C(1) << kResDoneBit) > kOtbnErrBitsLast,
145  "kResDoneBit must not overlap with OTBN error bits");
146 
147  abs_mmio_write32(kBase + OTBN_INTR_STATE_REG_OFFSET, kIntrStateDone);
148  abs_mmio_write32(kBase + OTBN_CMD_REG_OFFSET, cmd);
149 
150  rom_error_t res = kErrorOk ^ (UINT32_C(1) << kResDoneBit);
151  uint32_t reg = 0;
152  do {
153  reg = abs_mmio_read32(kBase + OTBN_INTR_STATE_REG_OFFSET);
154  res ^= (uint32_t)bitfield_bit32_read(reg, OTBN_INTR_COMMON_DONE_BIT)
155  << kResDoneBit;
156  } while (launder32(reg) != kIntrStateDone);
157  HARDENED_CHECK_EQ(reg, kIntrStateDone);
158  abs_mmio_write32(kBase + OTBN_INTR_STATE_REG_OFFSET, kIntrStateDone);
159 
160  // Error bits register should be 0 (no errors).
161  uint32_t err_bits = abs_mmio_read32(kBase + OTBN_ERR_BITS_REG_OFFSET);
162  res ^= err_bits;
163 
164  // Status should be kScOtbnStatusIdle; OTBN can also issue a done interrupt
165  // when transitioning to the "locked" state, so it is important to check
166  // the status here.
167  uint32_t status = abs_mmio_read32(kBase + OTBN_STATUS_REG_OFFSET);
168 
169  if (launder32(res) == kErrorOk && launder32(err_bits) == 0 &&
170  launder32(status) == kScOtbnStatusIdle) {
171  HARDENED_CHECK_EQ(res, kErrorOk);
172  HARDENED_CHECK_EQ(err_bits, 0);
173  HARDENED_CHECK_EQ(abs_mmio_read32(kBase + OTBN_STATUS_REG_OFFSET),
174  kScOtbnStatusIdle);
175  return res;
176  }
177  return error;
178 }
179 
180 rom_error_t sc_otbn_execute(void) {
181  // If OTBN is busy, wait for it to be done.
182  HARDENED_RETURN_IF_ERROR(sc_otbn_busy_wait_for_done());
183 
184  // Set software errors to fatal before running the program. Note: the CTRL
185  // register has only this one setting, so we have no need to read the
186  // previous value.
187  sec_mmio_write32(kBase + OTBN_CTRL_REG_OFFSET,
188  1 << OTBN_CTRL_SOFTWARE_ERRS_FATAL_BIT);
189 
190  return sc_otbn_cmd_run(kScOtbnCmdExecute, kErrorOtbnExecutionFailed);
191 }
192 
193 uint32_t sc_otbn_instruction_count_get(void) {
194  return abs_mmio_read32(kBase + OTBN_INSN_CNT_REG_OFFSET);
195 }
196 
197 rom_error_t sc_otbn_imem_sec_wipe(void) {
198  return sc_otbn_cmd_run(kScOtbnCmdSecWipeImem, kErrorOtbnSecWipeImemFailed);
199 }
200 
201 rom_error_t sc_otbn_dmem_sec_wipe(void) {
202  return sc_otbn_cmd_run(kScOtbnCmdSecWipeDmem, kErrorOtbnSecWipeDmemFailed);
203 }
204 
205 /**
206  * Checks if the OTBN application's IMEM and DMEM address parameters are valid.
207  *
208  * IMEM and DMEM ranges must not be "backwards" in memory, with the end address
209  * coming before the start address, and the IMEM range must additionally be
210  * non-empty.
211  *
212  * @param app the OTBN application to check
213  * @return OK if the addresses are valid, otherwise `kErrorOtbnInvalidArgument`.
214  */
216 static rom_error_t check_app_address_ranges(const sc_otbn_app_t app) {
217  if (app.imem_end > app.imem_start &&
218  app.dmem_data_end >= app.dmem_data_start) {
219  HARDENED_CHECK_GT(app.imem_end, app.imem_start);
220  HARDENED_CHECK_GE(app.dmem_data_end, app.dmem_data_start);
221  return kErrorOk;
222  }
223  return kErrorOtbnInvalidArgument;
224 }
225 
226 rom_error_t sc_otbn_load_app(const sc_otbn_app_t app) {
227  HARDENED_RETURN_IF_ERROR(check_app_address_ranges(app));
228 
229  // If OTBN is busy, wait for it to be done.
230  HARDENED_RETURN_IF_ERROR(sc_otbn_busy_wait_for_done());
231 
232  // Wipe memories.
233  HARDENED_RETURN_IF_ERROR(sc_otbn_dmem_sec_wipe());
234  HARDENED_RETURN_IF_ERROR(sc_otbn_imem_sec_wipe());
235 
236  const size_t imem_num_words = (size_t)(app.imem_end - app.imem_start);
237  const size_t data_num_words =
238  (size_t)(app.dmem_data_end - app.dmem_data_start);
239 
240  // IMEM always starts at 0.
241  sc_otbn_addr_t imem_start_addr = 0;
242  HARDENED_RETURN_IF_ERROR(
243  sc_otbn_imem_write(imem_num_words, app.imem_start, imem_start_addr));
244 
245  if (data_num_words > 0) {
246  HARDENED_RETURN_IF_ERROR(sc_otbn_dmem_write(
247  data_num_words, app.dmem_data_start, app.dmem_data_start_addr));
248  }
249  return kErrorOk;
250 }