[sw/sca] AES-ECB encrypt simple serial

The AES test program is based on ChipWhisperer's simple serial protocol.
The test is configured to work with the CW305 FPGA board and a
ChipWhisperer capture board.

Currently, the test only supports AES-ECB 128bit key encryption.

Signed-off-by: Miguel Osorio <miguelosorio@google.com>
diff --git a/sw/device/meson.build b/sw/device/meson.build
index 7e21754..04734e7 100644
--- a/sw/device/meson.build
+++ b/sw/device/meson.build
@@ -33,6 +33,7 @@
 subdir('boot_rom')
 subdir('mask_rom')
 subdir('examples')
+subdir('sca')
 subdir('tests')
 subdir('benchmarks')
 subdir('tock')
diff --git a/sw/device/sca/aes_serial/aes_serial.c b/sw/device/sca/aes_serial/aes_serial.c
new file mode 100644
index 0000000..a79e5be
--- /dev/null
+++ b/sw/device/sca/aes_serial/aes_serial.c
@@ -0,0 +1,336 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+
+#include "sw/device/lib/aes.h"
+#include "sw/device/lib/arch/device.h"
+#include "sw/device/lib/base/log.h"
+#include "sw/device/lib/dif/dif_gpio.h"
+#include "sw/device/lib/dif/dif_rv_timer.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/uart.h"
+
+#include "hw/top_earlgrey/sw/autogen/top_earlgrey.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;
+
+// UART input maximum buffer sizes
+static const uint32_t kMaxInputLengthText = 128;
+static const uint32_t kMaxInputLengthBin = 64;
+
+// AES Configuration parameters. These values must match the test driving
+// side.
+static const uint32_t kAesBlockSize = 16;
+static const uint32_t kAesKeySize = 16;
+
+// Simple serial status codes
+typedef enum simple_serial_result {
+  kSimpleSerialOk = 0,
+  kSimpleSerialError = 1
+} simple_serial_result_t;
+
+// Simple serial protocol version 1.1
+static const uint32_t kSimpleSerialProtocolVersion = 1;
+
+// GPIO capture trigger values
+static const uint32_t kGpioCaptureTriggerHigh = 0x08200;
+static const uint32_t kGpioCaptureTriggerLow = 0x00000;
+
+// RV Timer settings
+static const uint32_t kHart = (uint32_t)kTopEarlgreyPlicTargetIbex0;
+static const uint32_t kComparator = 0;
+static const uint64_t kTickFreqHz = 1000 * 1000;  // 1 MHz.
+static const uint64_t kDeadline = 500;            //  500us.
+
+// 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 {                           \
+    if (!(condition)) {          \
+      return kSimpleSerialError; \
+    }                            \
+  } while (false)
+
+/**
+ * Convert `from` binary `to` hex.
+ *
+ * @param from input value in binary format.
+ * @param to   output value in hex format.
+ *
+ * @return kSimpleSerialOk on success, kSimpleSerialError otherwise.
+ */
+static simple_serial_result_t hex_value(char from, char *to) {
+  if (from >= '0' && from <= '9') {
+    *to = from - '0';
+  } else if (from >= 'A' && from <= 'F') {
+    *to = from - 'A' + 10;
+  } else if (from >= 'a' && from <= 'f') {
+    *to = from - 'a' + 10;
+  } else {
+    return kSimpleSerialError;
+  }
+  return kSimpleSerialOk;
+}
+
+/**
+ * Convert `num` bytes `from` hex `to` binary format.
+ *
+ * `from` size is expected to by twice as big as `to`.
+ *
+ * @param from input buffer for hex formatted characters.
+ * @param to   output binary buffer.
+ * @param num  number of characters in output buffer.
+ *
+ * @return kSimpleSerialOk on success, kSimpleSerialError otherwise.
+ */
+static simple_serial_result_t a2b_hex(const char *from, char *to, size_t num) {
+  for (int i = 0; i < num; ++i) {
+    char hi, lo;
+    if (hex_value(from[i * 2], &hi) || hex_value(from[i * 2 + 1], &lo)) {
+      return kSimpleSerialError;
+    }
+    to[i] = ((hi & 0xFF) << 4) | (lo & 0xFF);
+  }
+  return kSimpleSerialOk;
+}
+
+/**
+ * Send simple serial command response via UART.
+ *
+ * @param cmd_tag  Simple serial command tag.
+ * @param data     Response data. Converted to hex format by this function.
+ * @param data_len data length.
+ */
+static void print_cmd_response(const char cmd_tag, const char *data,
+                               size_t data_len) {
+  static const char b2a_hex_values[16] = "0123456789ABCDEF";
+
+  // TODO: Switch to sw/device/lib/base/print.h
+  uart_send_char(cmd_tag);
+  for (int i = 0; i < data_len; ++i) {
+    uart_send_char(b2a_hex_values[data[i] >> 4]);
+    uart_send_char(b2a_hex_values[data[i] & 0xF]);
+  }
+  uart_send_char('\n');
+}
+
+/**
+ * Return number of AES blocks from `plain_text_len`.
+ */
+static size_t get_num_blocks(size_t plain_text_len) {
+  size_t num_blocks = plain_text_len / kAesBlockSize;
+  if ((plain_text_len - (num_blocks * kAesBlockSize)) > 0) {
+    ++num_blocks;
+  }
+  return num_blocks;
+}
+
+/**
+ * Simple serial set AES key command.
+ *
+ * @param aes_cfg AES configuration object.
+ * @param key     AES key.
+ * @param key_len AES key length.
+ *
+ * @return kSimpleSerialOk on success, kSimpleSerialError otherwise.
+ */
+static simple_serial_result_t simple_serial_set_key(const aes_cfg_t *aes_cfg,
+                                                    const char *key_share0,
+                                                    size_t key_len) {
+  // The implementation does not use key shares to simplify AES attacks.
+  static const uint8_t kKeyShare1[32] = {0};
+
+  if (key_len != kAesKeySize) {
+    return kSimpleSerialError;
+  }
+
+  aes_clear();
+  while (!aes_idle()) {
+    ;
+  }
+  aes_init(*aes_cfg);
+
+  aes_key_put(key_share0, kKeyShare1, aes_cfg->key_len);
+  return kSimpleSerialOk;
+}
+
+/*
+ * Simple serial trigger AES encryption command.
+ *
+ * @param aes_cfg        AES configuration object.
+ * @param plain_text     plain text to be encrypted.
+ * @param plain_text_len plain text length.
+ *
+ * @return kSimpleSerialOk on success, kSimpleSerialError otherwise.
+ */
+static simple_serial_result_t simple_serial_trigger_encryption(
+    const char *plain_text, size_t plain_text_len) {
+  if (plain_text_len > kMaxInputLengthBin) {
+    return kSimpleSerialError;
+  }
+
+  char cipher_text[64];
+  size_t num_blocks = get_num_blocks(plain_text_len);
+  if (num_blocks != 1) {
+    return kSimpleSerialError;
+  }
+
+  // start timer to wake up Ibex after AES is done.
+  uint64_t current_time;
+  SS_CHECK(dif_rv_timer_counter_read(&timer, kHart, &current_time) ==
+           kDifRvTimerOk);
+  SS_CHECK(dif_rv_timer_arm(&timer, kHart, kComparator,
+                            current_time + kDeadline) == kDifRvTimerOk);
+  SS_CHECK(dif_rv_timer_counter_set_enabled(
+               &timer, kHart, kDifRvTimerEnabled) == kDifRvTimerOk);
+
+  aes_data_put_wait(plain_text);
+  SS_CHECK(dif_gpio_all_write(&gpio, kGpioCaptureTriggerHigh) == kDifGpioOk);
+
+  aes_manual_trigger();
+  wait_for_interrupt();
+
+  SS_CHECK(dif_gpio_all_write(&gpio, kGpioCaptureTriggerLow) == kDifGpioOk);
+  aes_data_get_wait(cipher_text);
+
+  print_cmd_response('r', cipher_text, plain_text_len);
+  return kSimpleSerialOk;
+}
+
+/**
+ * Handle simple serial command.
+ *
+ * @param aes_cfg AES configuration object.
+ * @param cmd     input command buffer.
+ * @param cmd_len number of characters set in input buffer.
+ */
+static void simple_serial_handle_command(const aes_cfg_t *aes_cfg,
+                                         const char *cmd, size_t cmd_len) {
+  // Data length is half the size of the hex encoded string.
+  const size_t data_len = cmd_len >> 1;
+  if (data_len >= kMaxInputLengthBin) {
+    return;
+  }
+  char data[64] = {0};
+
+  // The simple serial protocol does not expect return status codes for invalid
+  // data, thus it is ok to return early.
+  if (a2b_hex(&cmd[1], data, data_len) == kSimpleSerialError) {
+    return;
+  }
+
+  if (cmd[0] == 'v') {
+    print_cmd_response('z', (const char *)&kSimpleSerialProtocolVersion,
+                       /*data_len=*/1);
+    return;
+  }
+
+  simple_serial_result_t result = kSimpleSerialOk;
+  switch (cmd[0]) {
+    case 'k':
+      result = simple_serial_set_key(aes_cfg, data, data_len);
+      break;
+    case 'p':
+      result = simple_serial_trigger_encryption(data, data_len);
+      break;
+    default:
+      // Unknown command.
+      result = kSimpleSerialError;
+  }
+
+  // This protocol version expects a 1 byte return status.
+  print_cmd_response('z', (const char *)&result, /*data_len=*/1);
+}
+
+int main(int argc, char **argv) {
+  uart_init(kUartBaudrate);
+  base_set_stdout(uart_stdout);
+  pinmux_init();
+
+  irq_global_ctrl(true);
+  irq_timer_ctrl(true);
+
+  CHECK(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) == kDifRvTimerOk);
+  dif_rv_timer_tick_params_t tick_params;
+  CHECK(dif_rv_timer_approximate_tick_params(kClockFreqHz, kTickFreqHz,
+                                             &tick_params) ==
+        kDifRvTimerApproximateTickParamsOk);
+  CHECK(dif_rv_timer_set_tick_params(&timer, kHart, tick_params) ==
+        kDifRvTimerOk);
+  CHECK(dif_rv_timer_irq_enable(&timer, kHart, kComparator,
+                                kDifRvTimerEnabled) == kDifRvTimerOk);
+
+  dif_gpio_config_t gpio_config = {.base_addr =
+                                       mmio_region_from_addr(0x40010000)};
+  CHECK(dif_gpio_init(&gpio_config, &gpio) == kDifGpioOk);
+  CHECK(dif_gpio_output_mode_all_set(&gpio, 0x08200) == kDifGpioOk);
+  CHECK(dif_gpio_all_write(&gpio, kGpioCaptureTriggerLow) == kDifGpioOk);
+
+  aes_cfg_t aes_cfg;
+  aes_cfg.key_len = kAes128;
+  aes_cfg.operation = kAesEnc;
+  aes_cfg.mode = kAesEcb;
+  aes_cfg.manual_operation = true;
+
+  char text[128] = {0};
+  size_t pos = 0;
+  while (true) {
+    if (uart_rcv_char(&text[pos]) == -1) {
+      usleep(50);
+      continue;
+    }
+    if (text[pos] == '\n' || text[pos] == '\r') {
+      // Continue to override line feed (\n) or carriage return (\r). This
+      // way we don't pass empty lines to the handle_command function.
+      if (pos != 0) {
+        simple_serial_handle_command(&aes_cfg, text, pos - 1);
+      }
+      // Reset `pos` for the next command.
+      pos = 0;
+    } else {
+      // Going over the maxium buffer size will result in corrupting the input
+      // buffer. This is okay in this case since we control the script used
+      // to drive the UART input.
+      pos = (pos + 1) % kMaxInputLengthText;
+    }
+  }
+}
+
+void handler_irq_timer(void) {
+  bool irq_flag;
+  CHECK(dif_rv_timer_irq_get(&timer, kHart, kComparator, &irq_flag) ==
+        kDifRvTimerOk);
+  CHECK(irq_flag, "Entered IRQ handler but the expected IRQ flag wasn't set!");
+
+  CHECK(dif_rv_timer_counter_set_enabled(&timer, kHart, kDifRvTimerDisabled) ==
+        kDifRvTimerOk);
+  CHECK(dif_rv_timer_irq_clear(&timer, kHart, kComparator) == kDifRvTimerOk);
+}
diff --git a/sw/device/sca/aes_serial/meson.build b/sw/device/sca/aes_serial/meson.build
new file mode 100644
index 0000000..ecd6c5a
--- /dev/null
+++ b/sw/device/sca/aes_serial/meson.build
@@ -0,0 +1,42 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+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,
+      dif_rv_timer,
+      riscv_crt,
+      sw_lib_aes,
+      sw_lib_base_log,
+      sw_lib_dif_gpio,
+      sw_lib_irq_handlers,
+      sw_lib_irq,
+      sw_lib_mmio,
+      sw_lib_pinmux,
+      sw_lib_runtime_hart,
+      sw_lib_uart,
+    ],
+  )
+
+  aes_serial_embedded = custom_target(
+    'aes_serial_' + device_name,
+    command: make_embedded_target,
+    input: aes_serial_elf,
+    output: make_embedded_target_outputs,
+    build_by_default: true,
+  )
+
+  custom_target(
+    'aes_serial_export_' + device_name,
+    command: export_embedded_target,
+    input: [aes_serial_elf, aes_serial_embedded],
+    output: 'aes_serial_export_' + device_name,
+    build_always_stale: true,
+    build_by_default: true,
+  )
+endforeach
diff --git a/sw/device/sca/meson.build b/sw/device/sca/meson.build
new file mode 100644
index 0000000..9511693
--- /dev/null
+++ b/sw/device/sca/meson.build
@@ -0,0 +1,5 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+subdir('aes_serial')