[sw/sca] Add side-channel analysis support library (sca)
This change introduces a library that provides various functionality
that is expected to be shared across different SCA targets such as:
- Peripheral initialization (pinmux, UART, GPIO, and timer),
- Trigger handling,
- IRQ handling, and
- Sleeping during encryption operations.
Signed-off-by: Alphan Ulusoy <alphan@google.com>
diff --git a/sw/device/sca/aes_serial/aes_serial.c b/sw/device/sca/aes_serial/aes_serial.c
index 26f2077..a3fb116 100644
--- a/sw/device/sca/aes_serial/aes_serial.c
+++ b/sw/device/sca/aes_serial/aes_serial.c
@@ -3,28 +3,16 @@
// SPDX-License-Identifier: Apache-2.0
#include "sw/device/lib/aes.h"
-#include "sw/device/lib/arch/device.h"
-#include "sw/device/lib/dif/dif_gpio.h"
-#include "sw/device/lib/dif/dif_rv_timer.h"
-#include "sw/device/lib/dif/dif_uart.h"
-#include "sw/device/lib/handler.h"
-#include "sw/device/lib/irq.h"
-#include "sw/device/lib/pinmux.h"
#include "sw/device/lib/runtime/hart.h"
#include "sw/device/lib/runtime/log.h"
#include "sw/device/lib/runtime/print.h"
-
-#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+#include "sw/device/sca/aes_serial/sca.h"
// This program implements the ChipWhisperer simple serial protocol version 1.1.
// Only set key ('k'), encrypt ('p') and version ('v') commands are implemented.
// Encryption is done in AES-ECB-128 mode.
// See https://wiki.newae.com/SimpleSerial for details.
-static dif_gpio_t gpio;
-static dif_rv_timer_t timer;
-static dif_uart_t uart;
-
enum {
kAesKeySize = 16,
/**
@@ -38,16 +26,6 @@
*/
kIbexAesSleepCycles = 200,
/**
- * GPIO capture trigger values.
- */
- kGpioCaptureTriggerHigh = 0x08200,
- kGpioCaptureTriggerLow = 0x00000,
- /**
- * RV timer settings.
- */
- kRvTimerComparator = 0,
- kRvTimerHart = kTopEarlgreyPlicTargetIbex0,
- /**
* UART constants.
*/
kUartMaxRxSizeText = 128,
@@ -58,29 +36,14 @@
kSimpleSerialProtocolVersion = 1,
};
+static const dif_uart_t *uart;
+
// Simple serial status codes
typedef enum simple_serial_result {
kSimpleSerialOk = 0,
kSimpleSerialError = 1
} simple_serial_result_t;
-// TODO: Remove once there is a CHECK version that does not require test
-// library dependencies.
-#define CHECK(condition, ...) \
- do { \
- if (!(condition)) { \
- /* NOTE: because the condition in this if \
- statement can be statically determined, \
- only one of the below string constants \
- will be included in the final binary.*/ \
- if (GET_NUM_VARIABLE_ARGS(_, ##__VA_ARGS__) == 0) { \
- LOG_ERROR("CHECK-fail: " #condition); \
- } else { \
- LOG_ERROR("CHECK-fail: " __VA_ARGS__); \
- } \
- } \
- } while (false)
-
/** Returns kSimpleSerialError if the condition evaluates to false */
#define SS_CHECK(condition) \
do { \
@@ -90,116 +53,6 @@
} while (false)
/**
- * Initializes the UART peripheral.
- */
-static void sca_init_uart() {
- (void)dif_uart_init(
- (dif_uart_params_t){
- .base_addr = mmio_region_from_addr(TOP_EARLGREY_UART_BASE_ADDR),
- },
- &uart);
- (void)dif_uart_configure(&uart, (dif_uart_config_t){
- .baudrate = kUartBaudrate,
- .clk_freq_hz = kClockFreqPeripheralHz,
- .parity_enable = kDifUartToggleDisabled,
- .parity = kDifUartParityEven,
- });
- dif_uart_fifo_reset(&uart, kDifUartFifoResetAll);
- base_uart_stdout(&uart);
-}
-
-/**
- * Initializes the GPIO peripheral.
- */
-static void sca_init_gpio() {
- dif_gpio_params_t gpio_params = {
- .base_addr = mmio_region_from_addr(TOP_EARLGREY_GPIO_BASE_ADDR)};
- (void)dif_gpio_init(gpio_params, &gpio);
- (void)dif_gpio_output_set_enabled_all(&gpio, kGpioCaptureTriggerHigh);
-}
-
-/**
- * Initializes the timer peripheral.
- */
-static void sca_init_timer() {
- (void)dif_rv_timer_init(
- mmio_region_from_addr(TOP_EARLGREY_RV_TIMER_BASE_ADDR),
- (dif_rv_timer_config_t){.hart_count = 1, .comparator_count = 1}, &timer);
- dif_rv_timer_tick_params_t tick_params;
- (void)dif_rv_timer_approximate_tick_params(kClockFreqPeripheralHz,
- kClockFreqCpuHz, &tick_params);
- (void)dif_rv_timer_set_tick_params(&timer, kRvTimerHart, tick_params);
- (void)dif_rv_timer_irq_enable(&timer, kRvTimerHart, kRvTimerComparator,
- kDifRvTimerEnabled);
- irq_timer_ctrl(true);
- irq_global_ctrl(true);
-}
-
-/**
- * Timer IRQ handler.
- *
- * Disables the counter and clears pending interrupts.
- */
-void handler_irq_timer(void) {
- // Return values of below functions are ignored to improve capture
- // performance.
- (void)dif_rv_timer_counter_set_enabled(&timer, kRvTimerHart,
- kDifRvTimerDisabled);
- (void)dif_rv_timer_irq_clear(&timer, kRvTimerHart, kRvTimerComparator);
-}
-
-/**
- * Initializes the peripherals (pinmux, uart, gpio, and timer) used by SCA code.
- */
-static void sca_init(void) {
- pinmux_init();
- sca_init_uart();
- sca_init_gpio();
- sca_init_timer();
-}
-
-/**
- * Sets capture trigger high.
- *
- * The actual trigger signal used for capture is a combination (logical AND) of:
- * - GPIO15 enabled here, and
- * - the busy signal of the AES unit. This signal will go high 40 cycles
- * after aes_manual_trigger() is executed below and remain high until
- * the operation has finished.
- */
-static void sca_set_trigger_high() {
- (void)dif_gpio_write_all(&gpio, kGpioCaptureTriggerHigh);
-}
-
-/**
- * Sets capture trigger low.
- */
-static void sca_set_trigger_low() {
- (void)dif_gpio_write_all(&gpio, kGpioCaptureTriggerLow);
-}
-
-/**
- * Functions called by `sca_call_and_sleep()` must conform to this prototype.
- */
-typedef void (*sca_callee)(void);
-
-static void sca_call_and_sleep(sca_callee callee, uint32_t sleep_cycles) {
- // Start timer to wake Ibex after AES is done.
- uint64_t current_time;
- // Return values of below functions are ignored to improve capture
- // performance.
- (void)dif_rv_timer_counter_read(&timer, kRvTimerHart, ¤t_time);
- (void)dif_rv_timer_arm(&timer, kRvTimerHart, kRvTimerComparator,
- current_time + sleep_cycles);
- (void)dif_rv_timer_counter_set_enabled(&timer, kRvTimerHart,
- kDifRvTimerEnabled);
-
- callee();
-
- wait_for_interrupt();
-}
-
-/**
* Convert `from` binary `to` hex.
*
* @param from input value in binary format.
@@ -375,6 +228,7 @@
int main(void) {
sca_init();
+ sca_get_uart(&uart);
aes_cfg_t aes_cfg;
aes_cfg.key_len = kAes128;
@@ -386,7 +240,7 @@
size_t pos = 0;
while (true) {
size_t chars_read;
- if (dif_uart_bytes_receive(&uart, 1, (uint8_t *)&text[pos], &chars_read) !=
+ if (dif_uart_bytes_receive(uart, 1, (uint8_t *)&text[pos], &chars_read) !=
kDifUartOk ||
chars_read == 0) {
usleep(50);
diff --git a/sw/device/sca/aes_serial/meson.build b/sw/device/sca/aes_serial/meson.build
index 1908428..2b90dcb 100644
--- a/sw/device/sca/aes_serial/meson.build
+++ b/sw/device/sca/aes_serial/meson.build
@@ -2,24 +2,37 @@
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
+sw_sca_aes_serial_sca = declare_dependency(
+ link_with: static_library(
+ 'aes_serial_sca',
+ sources: ['sca.c'],
+ dependencies: [
+ device_lib,
+ sw_lib_dif_gpio,
+ sw_lib_dif_rv_timer,
+ sw_lib_irq,
+ sw_lib_irq_handlers,
+ sw_lib_mmio,
+ sw_lib_pinmux,
+ sw_lib_runtime_hart,
+ sw_lib_runtime_log,
+ ],
+ ),
+)
+
foreach device_name, device_lib : sw_lib_arch_core_devices
aes_serial_elf = executable(
'aes_serial_' + device_name,
sources: ['aes_serial.c'],
name_suffix: 'elf',
dependencies: [
- device_lib,
- sw_lib_dif_rv_timer,
riscv_crt,
sw_lib_aes,
sw_lib_runtime_log,
- sw_lib_dif_gpio,
- sw_lib_irq_handlers,
- sw_lib_irq,
sw_lib_mmio,
- sw_lib_pinmux,
sw_lib_runtime_hart,
sw_lib_dif_uart,
+ sw_sca_aes_serial_sca,
],
)
diff --git a/sw/device/sca/aes_serial/sca.c b/sw/device/sca/aes_serial/sca.c
new file mode 100644
index 0000000..4e5fd8a
--- /dev/null
+++ b/sw/device/sca/aes_serial/sca.c
@@ -0,0 +1,139 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/sca/aes_serial/sca.h"
+#include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/dif/dif_gpio.h"
+#include "sw/device/lib/dif/dif_rv_timer.h"
+#include "sw/device/lib/dif/dif_uart.h"
+#include "sw/device/lib/handler.h"
+#include "sw/device/lib/irq.h"
+#include "sw/device/lib/pinmux.h"
+#include "sw/device/lib/runtime/hart.h"
+#include "sw/device/lib/runtime/print.h"
+
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
+
+/**
+ * Macro for ignoring return values.
+ *
+ * This is needed because ‘(void)expr;` does not work for gcc.
+ */
+#define IGNORE_RESULT(expr) \
+ if (expr) { \
+ }
+
+enum {
+ /**
+ * GPIO capture trigger values.
+ */
+ kGpioCaptureTriggerHigh = 0x08200,
+ kGpioCaptureTriggerLow = 0x00000,
+ /**
+ * RV timer settings.
+ */
+ kRvTimerComparator = 0,
+ kRvTimerHart = kTopEarlgreyPlicTargetIbex0,
+};
+
+static dif_uart_t uart;
+static dif_gpio_t gpio;
+static dif_rv_timer_t timer;
+
+// TODO(alphan): Handle return values as long as they don't affect capture rate.
+
+/**
+ * Initializes the UART peripheral.
+ */
+static void sca_init_uart(void) {
+ IGNORE_RESULT(dif_uart_init(
+ (dif_uart_params_t){
+ .base_addr = mmio_region_from_addr(TOP_EARLGREY_UART_BASE_ADDR),
+ },
+ &uart));
+ IGNORE_RESULT(
+ dif_uart_configure(&uart, (dif_uart_config_t){
+ .baudrate = kUartBaudrate,
+ .clk_freq_hz = kClockFreqPeripheralHz,
+ .parity_enable = kDifUartToggleDisabled,
+ .parity = kDifUartParityEven,
+ }));
+ base_uart_stdout(&uart);
+}
+
+/**
+ * Initializes the GPIO peripheral.
+ */
+static void sca_init_gpio(void) {
+ dif_gpio_params_t gpio_params = {
+ .base_addr = mmio_region_from_addr(TOP_EARLGREY_GPIO_BASE_ADDR)};
+ IGNORE_RESULT(dif_gpio_init(gpio_params, &gpio));
+ IGNORE_RESULT(
+ dif_gpio_output_set_enabled_all(&gpio, kGpioCaptureTriggerHigh));
+}
+
+/**
+ * Initializes the timer peripheral.
+ */
+static void sca_init_timer(void) {
+ IGNORE_RESULT(dif_rv_timer_init(
+ mmio_region_from_addr(TOP_EARLGREY_RV_TIMER_BASE_ADDR),
+ (dif_rv_timer_config_t){.hart_count = 1, .comparator_count = 1}, &timer));
+ dif_rv_timer_tick_params_t tick_params;
+ IGNORE_RESULT(dif_rv_timer_approximate_tick_params(
+ kClockFreqPeripheralHz, kClockFreqCpuHz, &tick_params));
+ IGNORE_RESULT(
+ dif_rv_timer_set_tick_params(&timer, kRvTimerHart, tick_params));
+ IGNORE_RESULT(dif_rv_timer_irq_enable(
+ &timer, kRvTimerHart, kRvTimerComparator, kDifRvTimerEnabled));
+ irq_timer_ctrl(true);
+ irq_global_ctrl(true);
+}
+
+/**
+ * Timer IRQ handler.
+ *
+ * Disables the counter and clears pending interrupts.
+ */
+void handler_irq_timer(void) {
+ // Return values of below functions are ignored to improve capture
+ // performance.
+ IGNORE_RESULT(dif_rv_timer_counter_set_enabled(&timer, kRvTimerHart,
+ kDifRvTimerDisabled));
+ IGNORE_RESULT(
+ dif_rv_timer_irq_clear(&timer, kRvTimerHart, kRvTimerComparator));
+}
+
+void sca_init(void) {
+ pinmux_init();
+ sca_init_uart();
+ sca_init_gpio();
+ sca_init_timer();
+}
+
+void sca_get_uart(const dif_uart_t **uart_out) { *uart_out = &uart; }
+
+void sca_set_trigger_high() {
+ IGNORE_RESULT(dif_gpio_write_all(&gpio, kGpioCaptureTriggerHigh));
+}
+
+void sca_set_trigger_low() {
+ IGNORE_RESULT(dif_gpio_write_all(&gpio, kGpioCaptureTriggerLow));
+}
+
+void sca_call_and_sleep(sca_callee callee, uint32_t sleep_cycles) {
+ // Start timer to wake Ibex after the callee is done.
+ uint64_t current_time;
+ // Return values of below functions are ignored to improve capture
+ // performance.
+ IGNORE_RESULT(dif_rv_timer_counter_read(&timer, kRvTimerHart, ¤t_time));
+ IGNORE_RESULT(dif_rv_timer_arm(&timer, kRvTimerHart, kRvTimerComparator,
+ current_time + sleep_cycles));
+ IGNORE_RESULT(dif_rv_timer_counter_set_enabled(&timer, kRvTimerHart,
+ kDifRvTimerEnabled));
+
+ callee();
+
+ wait_for_interrupt();
+}
diff --git a/sw/device/sca/aes_serial/sca.h b/sw/device/sca/aes_serial/sca.h
new file mode 100644
index 0000000..279ee27
--- /dev/null
+++ b/sw/device/sca/aes_serial/sca.h
@@ -0,0 +1,82 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#ifndef OPENTITAN_SW_DEVICE_SCA_AES_SERIAL_SCA_H_
+#define OPENTITAN_SW_DEVICE_SCA_AES_SERIAL_SCA_H_
+
+#include "sw/device/lib/dif/dif_uart.h"
+
+#include <stdint.h>
+
+/**
+ * @file
+ * @brief Side-channel analysis support library.
+ */
+
+/**
+ * Initializes the peripherals (pinmux, uart, gpio, and timer) used by SCA code.
+ */
+void sca_init(void);
+
+/**
+ * Returns a handle to the intialized UART device.
+ *
+ * @param[out] uart_out Handle to the initialized UART device.
+ */
+void sca_get_uart(const dif_uart_t **uart_out);
+
+/**
+ * Sets capture trigger high.
+ *
+ * The actual trigger signal used for capture is a combination (logical AND) of:
+ * - GPIO15 enabled here, and
+ * - the busy signal of the AES unit. This signal will go high 40 cycles
+ * after aes_manual_trigger() is executed below and remain high until
+ * the operation has finished.
+ */
+void sca_set_trigger_high(void);
+
+/**
+ * Sets capture trigger low.
+ */
+void sca_set_trigger_low(void);
+
+/**
+ * Functions called by `sca_call_and_sleep()` must conform to this prototype.
+ */
+typedef void (*sca_callee)(void);
+
+/**
+ * Calls the given function and puts Ibex to sleep.
+ *
+ * This function can be used to minimize noise while capturing power traces for
+ * side-channel attacks, in which case `callee` would trigger the operation of
+ * interest, typically a crypto operation, on the target device.
+ *
+ * @param callee Function to call before putting Ibex to sleep.
+ * @param sleep_cycles Number of cycles to sleep.
+ */
+void sca_call_and_sleep(sca_callee callee, uint32_t sleep_cycles);
+
+/**
+ * Prints an error message over UART if the given condition evaluates to false.
+ * TODO: Remove once there is a CHECK version that does not require test
+ * library dependencies.
+ */
+#define CHECK(condition, ...) \
+ do { \
+ if (!(condition)) { \
+ /* NOTE: because the condition in this if \
+ statement can be statically determined, \
+ only one of the below string constants \
+ will be included in the final binary.*/ \
+ if (GET_NUM_VARIABLE_ARGS(_, ##__VA_ARGS__) == 0) { \
+ LOG_ERROR("CHECK-fail: " #condition); \
+ } else { \
+ LOG_ERROR("CHECK-fail: " __VA_ARGS__); \
+ } \
+ } \
+ } while (false)
+
+#endif // OPENTITAN_SW_DEVICE_SCA_AES_SERIAL_SCA_H_