IoT何をいまさら(101) ATSAMD51、ICM、ハッシュ計算をする専用DMAC

Joseph Halfmoon

前々回、トラぶりながらもなんとかDMACを動かしてみました。しかし、SAMD51にはもう一つDMACがあることに気づきました。ICMというものです。ぶっちゃけメモリのある領域のハッシュ値を計算してくれるDMACです。監視対象のメモリが書き換わってしまったら割り込みをかけるなどということも可能。流石近代的。

(実験に使用したソースは末尾に)

ICM = Integrity Check Monitor であります。お馴染みのSecure Hash Algorithm (SHA)により、メモリブロックのハッシュ値をCPUに頼らず、完全ハードでやってくれる周辺回路です。自らメモリの内容を読み取って、ハッシュ値を計算し、結果をメモリの所定の場所に書き込んだり、あるいは既にレジスタにセットしてあるハッシュ値と比較して異なっていたら割り込む、などといった動作が可能です。

対応しているSHAのアルゴリズムは、SHA1, SHA224, SHA256の3種類です。何等かのデータのハッシュ値をハードで計算できるエンジンとしても使えますが、CPUと無関係にメモリ領域のハッシュを定期的に計算しつづける、という仕組みが使えるので、メモリの見張り番として利用することも可能と思われます。ICMという名称からはそちらの方がメインっぽいです。勿論、良からぬ輩にICMそのものを改ざんされないように、Autoで見張っている最中、ICMのレジスタを書き換えようとなどとするとフラグが立ち、割り込みを発生させることも可能です。

SAMD51は、レジスタ全般のロックにも対応するPACという機構もあるので、2つを併用すると簡単には良からぬ輩が忍び込むことは出来ないように思います。

まずはサンプルデータの準備

今回、ICM使ってみるのは初めてなので、一番軽いアルゴリズムである

SHA1

を使って、最小単位を1回だけハッシュ計算してみる、ことにいたしました。しかし、実際にちゃんと計算が出来ているかどうかを観察するのに、入力用のビット列と、それをSHA1でハッシュにした結果が欲しかったです。流石にMicrochip社の人もそう思ったのか、データシートの

26.6.2.1 Message Digest Example

という項目に米国のFIPS 180-4 から引いた計算例が掲げられています。今はこの計算例が英文字3文字だけの極小のサンプルビット列だということが分かるのですが、ついちょっと前までチンプンカンプンでした。SHA1は512ビット(64バイト)ブロックを単位として処理するのですが、「端数ビット」のある末尾のブロックにPaddingという処理をする必要があるからです。今回、RFC3174の日本語訳ドキュメントを読んで、ようやくPaddingの処理を理解し、データシート記載の元の文字列を理解できました。ありがとうございます。

SHA-1 (US Secure Hash Algorithm 1 (SHA1))

結局、データシートのExample(512bit)は、

abc

たった3文字、24ビットのビット列でした。元の文字列が分かったので、PC上のSHA1を計算できるソフトでも同じ結果が出るよね、ということで確認した様子が、先頭のアイキャッチ画像です。タマにお世話になる

Hash Checker

というソフトウエアで計算させていただきました。計算したあと一瞬データシートの例と違うじゃんと慌てましたが、上記のソフトが生成したHEXビット列は最下位バイトから並んでいて、データシートの例はワード(32ビット毎)に上位のバイトレーンから並んでいたための混乱でした。ちゃんとあっています。

サンプルデータを作れるようになったので、データシートの例以外にもいろいろやれば良いのですが、例によってここまでで気力を失ったので、今回は、データシートの例が出来たらいいじゃん、と。ダメな私。

ICMデスクリプタの設定に戸惑う

SAMD51のペリフェラルはモダンで強力な反面、設定項目が多いです。前回もDMACでメモリ上のデスクリプタに手こずりましたが、今回もICMはDMACを内部に含み、デスクリプタを使うのです。ザックリ言うと以下のような感じです。

  • あちこちの領域に飛び飛びに離れたメモリブロックを繋ぎ合わせてハッシュを計算するために、デスクリプタを使う
  • デスクリプタはメモリ上の制御構造であって、処理すべきメモリのアドレス、処理方法、ブロックの大きさ、そして次のデスクリプタへのポインタを含む
  • ICMはこのようなデスクリプタのチェーンを最大4個処理して4つの異なるハッシュ値を生成できる

当然、上記のデスクリプタを制御するためにICM本体内のレジスタを操作することも必要です。

ぶっちゃけハマったのは以下の点です。

デスクリプタにメモリブロックの大きさを指定するフィールドに書き込む値

デスクリプタのこの部分は、Region Control Structure Memberというタイトルがつけられており、構造体メンバ的には、RCTRLというフィールド名でアクセスできます。32ビット長ですが、使用しているのは下16ビットです。今回処理は最小の1ブロック=64バイトです。私はここに1を書いてハマリました。何度計算させても期待の値が出てこないです。いろいろ調べた結果、どうも次のメモリブロックまでハッシュ計算にかかっていることが分かりました。そこでディスクリプタ上のブロックサイズに0を書いてみたら期待通りの値が計算されてきました。1ブロックのときは0って書くのね。データシートはかなり良く見たのだけれど、ハッシュ素人に分かるような記述は無かったです。まあ、答えが出るようになったので良かったケド。

実行結果

末尾のソフトをビルドして、アップロードして、実行結果を仮想シリアル端末で観察したものが以下です。OUT>とかかれた行のうち0から5がSHA1ハッシュ値です。

ATSAMD51 ICM test.
-----Setup descriptors and memory----
Trial: 1
ICM ISR: 0x00010001
ICM UASR: 0x00000000
OUT> 0: 0x363e99a9
OUT> 1: 0x6a810647
OUT> 2: 0x71253eba
OUT> 3: 0x6cc25078
OUT> 4: 0x9dd8d09c
OUT> 5: 0x00000000
OUT> 6: 0x00000000
OUT> 7: 0x00000000
=====END OF TRIAL ==========================================

先頭のアイキャッチ画像で計算した値と合ってますな。(バイトの順番をたどるのがメンドイけど。。。)

毎度ながらSAMD51の周辺の強力さには感銘を受けますが、0は説明不足よ。。。

IoT何をいまさら(100) ATSAMD51、アナログコンパレータACを使ってみる へ戻る

以下のソースは、VSCode上のPlatformIOを使い、SAMD51P19A搭載Wio Terminal をターゲットボード、プラットフォームはArduinoにてビルドし、動作確認しています。

#include <Arduino.h>

#define MAX_ICM_CH (4)
#define HASH_WSIZE (32)
#define BLK_WSIZE (16)

int counter = 0;

IcmDescriptor icmDesc[MAX_ICM_CH] __attribute__ (( aligned (64) ));
volatile uint32_t hashResult[HASH_WSIZE] __attribute__ (( aligned (128) ));
uint32_t hashRegion0[BLK_WSIZE];
uint32_t hashRegion1[BLK_WSIZE];
uint32_t hashRegion2[BLK_WSIZE];

// Setup Message Digest Example
void setupExampleMem() {
  for (int i=0; i < BLK_WSIZE; i++) {
    hashRegion0[i] = 0;
    hashRegion1[i] = 0;
    hashRegion2[i] = 0;
  }
  hashRegion0[0]  = 0x80636261;
  hashRegion0[15] = 0x18000000;
}

void clearHashResult() {
  for (int i=0; i < HASH_WSIZE; i++) {
    hashResult[i] = 0;
  }
}

void printHashResult() {
  for (int i=0; i < 8; i++) {
    Serial.printf("OUT> %d: 0x%08x\r\n", i, hashResult[i]);
  }
}

void setupICMdescriptor() {
  icmDesc[0].RADDR.reg = (uint32_t)hashRegion0;  // Region Start Addresss
  icmDesc[0].RNEXT.reg = 0;                   // No Next
  icmDesc[0].RCTRL.reg = 0;                   // 1 BLOCK (512bit = 64bytes)
  icmDesc[0].RCFG.reg  = 0x02E4;              // SHA1, REC, RHC, EOM=1, NO WRAP, NO COMPARE
}

void setupICM() {
  MCLK->AHBMASK.bit.ICM_ = 1; // Enable CLK_ICM_AHB
  MCLK->APBCMASK.bit.ICM_ = 1; // Enable CLK_ICM_APB
  setupICMdescriptor();
  ICM->CTRL.reg = 0x0006; // Disable ICM & Software RESET
  ICM->CFG.reg = 0;
  ICM->HASH.reg = (uint32_t)hashResult;
  ICM->DSCR.reg = (uint32_t)icmDesc;
  ICM->CTRL.reg = 0x01; // Enable
}

void setup() {
  Serial.begin(9600);
  while(!Serial);
  Serial.printf("ATSAMD51 ICM test.\r\n");
  clearHashResult();
  Serial.printf("-----Setup descriptors and memory----\r\n");
  setupExampleMem();
  setupICM();
}

void loop() {
  Serial.printf("Trial: %d\r\n", ++counter);
  Serial.printf("ICM ISR:  0x%08x\r\n", (uint32_t)ICM->ISR.reg);
  Serial.printf("ICM UASR: 0x%08x\r\n", (uint32_t)ICM->UASR.reg);
  printHashResult();
  Serial.printf("=====END OF TRIAL ==========================================\r\n\r\n");
  delay(10000);
}