[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, &current_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, &current_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_