Вступление
Я давненько подписан на TDMLab вконтакте и под одним из постов про новый отечественный микроконтроллер возник вопрос про аппаратный криптографический ускоритель данного МК. Так получилось что у меня на руках есть отладочная плата на К1921ВГ015 и я как раз работаю с криптографией в том числе. Поэтому решил написать небольшой тест и представить общественности, потому что многие относятся настороженно к отечественным микроконтроллерам (как и я по началу), а зря.
О микроконтроллере
К1921ВГ015 это серьезный микроконтроллер на архитектуре RISC-V и ядром CloudBEAR с внушительным набором периферии. Тут есть и USB-FS, QSPI, CAN и аналоговые компараторы и естественно весь стандартный набор вроде I2C и всяких таймеров и конечно блок CRYPTO, о котором мы поговорим подробнее. Однако максимальная частота ядра составляет всего 50 МГц, что по современным меркам не так уж много. Так же стоит упомянуть некую гигантоманию производителя, чип поставляется в 128-ногом QFN корпусе, что-то компактное из него сделать вряд ли получится. Впрочем я немного отвлекся.
Микроконтроллер имеет развитую систему тактирования ядра и периферийных блоков, тут есть и PLL и SysTick и RTC и можно достаточно гибко все настраивать через регистры управления синхронизацией RCU. Подробнее про первый запуск можно почитать в этой статье. А сейчас перейдем непосредственно к криптографии.
Для аппаратного ускорения криптографических операций нам завезли три блока.
Хэш-процессор - поддерживает алгоритмы SHA-1, SHA-224, SHA-256, MD5 и HMAC
TRNG (true random number generator) - генератор истинно случайных чисел, в качестве энтропии использует фазовое дрожание цифрового сигнала (jitter)
CRYPTO - собственно блок шифрования/дешифрования, о нем немного подробнее ниже
Поддерживаются следующие алгоритмы:
AES-128 (из стандарта FIPS PUB 197). Размер блока 128 бит, ключа – 128 бит;
AES-256 (из стандарта FIPS PUB 197). Размер блока 128 бит, ключа – 256 бит;
Кузнечик (из ГОСТ 34.12-2018). Размер блока 128 бит, ключа – 256 бит;
Магма (из ГОСТ 34.12-2018). Размер блока 64 бит, ключа – 256 бит.
Так же для выполнения криптографических операций поддерживаются следующие режимы:
ECB – режим электронной кодовой книги (режим простой замены из ГОСТ 34.13-2018);
CBC – режим простой замены с зацеплением с параметром z = 1 ГОСТ 34.13-2018);
CTR – режим гаммирования с параметром z = 1 из (ГОСТ 34.13-2018).
GCM – режим счётчика с аутентификацией Галуа
Методика теста
Тест проводился на стандартной отладочной плате КФДЛ.441461.029 с максимальной частотой ядра, которая для этого процессора составляет 50 МГц. Режим кодирования ECB.
Тест состоит из пяти фаз, длительность которых можно измерить по высокому уровню на ножке PB0. Первые три фазы - аппаратное шифрование, последние две софтверная реализация для сравнения, после чего длительная пауза примерно 10 мс.
Импульс 1 - Единичный 64-битный ECB блок
Импульс 2 - 256 блоков, CPU polled
Импульс 3 - Ротация ключей
Импульс 4 - Единичный блок (софтовая реализация, референс [3])
Импульс 5 - 256 блоков, софтовая реализация
Импульс 6 - 256 блоков, конвейр DMA
Длительность импульсов лучше всего смотреть осциллографом. Так же можно подключиться к UART0, 115200 и посмотреть усредненное значение. Для усреднения используется алгоритм EMA (ALPHA 0.1). Важно: все вычисления и вывод в отладочный порт производятся вне окна измерений.
Во время просмотра осциллограм я заметил некую странность, импульс соответствующий программному вычислению 256 блоков был слишком длинным, на порядок длинней чем программынй расчет одного блока * 256. Оказалось что я переиспользовал функцию для единичного блока, в которой раундовые ключи рассчитывались каждый раз для всех 256 блоков. Это пришлось исправить для получения более корректных результатов. К сожалению в этот момент у меня уже не было доступа к осциллографу и пришлось сделать подсчет тактов процессора с выводом в UART. Тем не менее это дает объективную оценку и в каком то смысле даже удобнее.
Нюансы
Пожалуй отдельно стоит упомянуть настройку DMA, так как для людей привыкших к HAL от ST или той же CMSIS настройка будет не привычная. Для большинства периферии при настройке ее работы с использованием DMA.
В CMSIS у нас есть отдельные структуры для настройки каждого канала DMA, например:
void DMA_Init(void) { DMA1_Channel1->CCR |= DMA_CCR1_MEM2MEM; /*Memory to memory transfer enable ((uint16_t)0x4000)*/ DMA1_Channel1->CCR &= ~DMA_CCR1_CIRC; // And so on }
У НИИЭТ же подход немного другой. Для настройки DMA используются так называемые управляющие структуры, основная и альтернативная. Эти структуры содержат идентичные поля и по сути альтернативная структура нужна для ускорения работы в некоторых режимах (пока DMA использует данные основной структуры, ядро может обновить данные в альтернативной, а потом просто передать указатель на новую структуру). Нюанс этого подхода заключается в том что при использовании хотя бы одного канала DMA в оперативной памяти нужно выделить место сразу под всю управляющую структуру, а это 1024 байта [1, 10.1]. Благо оперативной памяти нам завезли прилично, целых 256 Кб.
/** @brief DMA main config data structure init in ram **/ DMA_CtrlData_TypeDef DMA_CONFIGDATA attribute((aligned(1024))); /** @brief Send string buffer to uart0 via DMA ch8 (default) @param data string buffer ptr @param len string buffer length **/ void debug_uart_write(const uint8_t data, uint16_t len) { if( ( data == NULL ) || ( len == 0U ) ) { return; } while(DMA->ENSET_bit.CH8 ); DMA->BASEPTR = (uint32_t)(&DMA_CONFIGDATA); // Fill primary data struct DMA_CONFIGDATA.PRM_DATA.CH[8].SRC_DATA_END_PTR = (uint32_t )(data + len -1); DMA_CONFIGDATA.PRM_DATA.CH[8].CHANNEL_CFG_bit.SRC_SIZE = DMA_CHANNEL_CFG_SRC_SIZE_Byte; DMA_CONFIGDATA.PRM_DATA.CH[8].CHANNEL_CFG_bit.SRC_INC = DMA_CHANNEL_CFG_SRC_INC_Byte; DMA_CONFIGDATA.PRM_DATA.CH[8].DST_DATA_END_PTR = (uint32_t)(&UART0->DR); DMA_CONFIGDATA.PRM_DATA.CH[8].CHANNEL_CFG_bit.DST_SIZE = DMA_CHANNEL_CFG_DST_SIZE_Byte; DMA_CONFIGDATA.PRM_DATA.CH[8].CHANNEL_CFG_bit.DST_INC = DMA_CHANNEL_CFG_DST_INC_None; DMA_CONFIGDATA.PRM_DATA.CH[8].CHANNEL_CFG_bit.R_POWER = 0x02; DMA_CONFIGDATA.PRM_DATA.CH[8].CHANNEL_CFG_bit.N_MINUS_1 = len - 1; DMA_CONFIGDATA.PRM_DATA.CH[8].CHANNEL_CFG_bit.CYCLE_CTRL = DMA_CHANNEL_CFG_CYCLE_CTRL_Basic; DMA->ENSET_bit.CH8 = 1; DMA->CFG_bit.MASTEREN = 1; }
Однако, это касается системного модуля DMA (DMA SYS). У данного микроконтроллера есть еще 2 зарезервированных модуля DMA CRYPTO и DMA USB. Важно отметить что это не просто 2 зарезервированных канала DMA SYS, а два полноценных мастера шины, напрямую соединенных со своей периферией и шиной AHB. В связи с этим для управления этими модулями используются свои дескрипторы, отличные от рассмотренного выше. Вот как настраивается DMA CRYPTO:
static CRYPTO_DMA_DESCR_TypeDef crypto_dma_descr attribute((aligned(16))); /** @brief Blocking BENCH_BLOCK_COUNT-block Magma/ECB/Encrypt burst, driven entirely by CRYPTO's descriptor engine -- one CPU write starts the whole burst, vs. prvBenchBurst()'s per-block register writes. **/ static void prvCryptoDmaBurst(void) { prvWaitReady(); crypto_dma_descr.CONTROL = 0u; crypto_dma_descr.CONTROL_bit.UPDATE_KEY = 0u; crypto_dma_descr.CONTROL_bit.LAST_DESCRIPTOR = 1u; crypto_dma_descr.CONTROL_bit.DIRECTION = CRYPTO_CONTROL_DIRECTION_Encrypt; crypto_dma_descr.CONTROL_bit.ALGORITHM = CRYPTO_CONTROL_ALGORITHM_Magma; crypto_dma_descr.CONTROL_bit.MODE = CRYPTO_CONTROL_MODE_ECB; crypto_dma_descr.CONTROL_bit.GCM_PHASE = 0u; crypto_dma_descr.CONTROL_bit.INTERRUPT_ENABLE = 0u; crypto_dma_descr.CONTROL_bit.BLOCKS_COUNT = BENCH_BLOCK_COUNT - 1u; / datasheet: COUNT + 1 blocks processed. / crypto_dma_descr.SRC_ADDR = (uint32_t)crypto_dma_src_buf; crypto_dma_descr.DST_ADDR = (uint32_t)crypto_dma_dst_buf; crypto_dma_descr.NEXT_DESCR = 0u; / LAST_DESCRIPTOR set -- not read by the state machine. / CRYPTO->BASE_DESCRIPTOR = (uint32_t)&crypto_dma_descr; CRYPTO->DMA_CONTROL = CRYPTO_DMA_CONTROL_START_Msk; / WORDS_SWAP=0, BYTES_SWAP=0, START=1, one write. */ while (CRYPTO->STATUS & CRYPTO_STATUS_DMA_ACTIVE_Msk) {} prvWaitReady(); }
Принцип работы здесь следующий: дескриптор состоит из 16 байт первые 4 из которых это конфигурационное слово контроллера DMA, затем стартовый адрес источника, стартовый адрес назначения и адрес следующего дескриптора (если есть). Таким образом мы можем задавать разные режимы работы и области копирования добавлением новых дескрипторов в связный список, которые будут выполнены последовательно без участия ядра вовсе, ну кроме первоначальной настройки. Что как мне кажется довольно удобно и может ускорить потоковые операции в реальном времени.
Результаты
Усредненные значения:
Pulse | Description | Duration |
Pulse 1 | HW single | 0.9 us |
Pulse 2 | HW burst/256 | 179.6 us |
Pulse 3 | HW key sched | 2.5 us |
Pulse 4 | SW single | 94.2 us |
Pulse 5 | SW burst/256 | 24041.1 us |
Pulse 6 | HW DMA stream/256 | 99.1 us |
Заключение
Итак мы видим что аппаратная обработка 256 блоков с использованием конвейера DMA занимает всего 99 микросекунд, против 24 миллисекунд при софтовой реализации алгоритма, что бесконечно медленно! Таким образом аппаратное ускорение дает х242 прирост скорости или примерно 20,7 МБ/с против 85,1 КБ/с пропускной способности.
Стало быть применение аппаратного шифрования в связке с DMA позволяет получить очень короткие пики активности и, учитывая продвинутую систему управления питанием и тактированием, вполне экономичный низкопотребляющий режим работы в сочетании с высокоскоростным каналом передачи данных. Это может найти применение например в современных приборах учета или пультах ОПС, которым все чаще нужны закрытые каналы связи в соответствии с требованиями последних ГОСТов.
Источники
[2] ГОСТ 34.12-2018

