LPC11U35にSWDでアクセス(3)

前回はSWD-DPのDPIDRレジスタにアクセスするところまで進めた。ここではSWD-DPの先にあるMEM-APを使って、マイコン内部のAHBバスにアクセスする。

MEM-APとは

MEM-APはAP(アクセスポート)の種類の1つで、メモリ(SRAMFlashなど)やメモリマップドI/Oなど、アドレスを使ってリソースにアクセスするような場合に使われる。普通はメインのバスにつながっているので、MEM-APに接続できればだいたいのことはSWDからもできるようになる。

MEM-APを使う

以下の手順で進めていく。

  1. APを使うための設定をDPレジスタで行う
  2. APレジスタにアクセス
  3. MEM-APを使ってAHBバス(につながっているメモリ)にアクセス

APを使うための準備

はじめに、APにアクセスするにはまずデバッグ回路の電源を入れる必要がある。またエラーフラグが残っていた場合、それらをクリアしておかなくてはならない。これらの操作はDPレジスタで行う。

    write_dp_reg(DP_CTRL, 0x50000000, "CTRL"); // debug component power on
    write_dp_reg(DP_ABORT, 0x1f, "ABORT");; // clear error flags
    do
        read_dp_reg(DP_STAT, "STAT");
    while ((swd_reg & 0xf0000000) != 0xf0000000);

APレジスタにアクセス

SWDからAPレジスタにアクセスする手順は次の通り。アクセスしたいAPレジスタのアドレスをAとすると

  1. DPのSELECT[7:4]に、A[7:4]をセット
  2. A[3:2]を使ってAPアクセス(APnDP = 1にして、他はDPレジスタアクセスと同様)
    • 書き込みの場合、WDATAとして書き込む値を送信する
    • 読み込みの場合、送られてくるRDATAは無視して3に進む
  3. 読み込みの場合、DPのRDBUFFに読み出された値がセットされるのでこれを読む

これも関数にしておく。

int read_ap_reg(uint32_t address, const char *name = NULL)
{
    int ack = swd_reg_access(0, 0, DP_SELECT, address & 0xf0);
    if (ack == SWD_OK)
    {
        ack = swd_reg_access(1, 1, address, 0);
        if (ack == SWD_OK)
            while ((ack = swd_reg_access(0, 1, DP_RDBUFF, 0)) == SWD_WAIT) { }
        if (name != NULL && ack == SWD_OK)
            printf("[AP] %6s  = 0x%08x\n", name, swd_reg);
    }
    return ack;
}

int write_ap_reg(uint32_t address, uint32_t value, const char *name = NULL)
{
    int ack = swd_reg_access(0, 0, DP_SELECT, address & 0xf0);
    if (ack == SWD_OK)
    {
        ack = swd_reg_access(1, 0, address, value);
        if (name != NULL && ack == SWD_OK)
            printf("[AP] %6s <- 0x%08x\n", name, value);
    }
    return ack;
}

この関数を使ってAPのIDRを読み出してみる。IDRにはAPの種類などの情報が含まれているので、どんなAPにアクセスしているのかがわかる。

    read_ap_reg(AP_IDR);
    printf("AP class = 0x%01x (if 0x8, MEM-AP)\n", swd_reg >> 13 & 0xf);
    printf("AP variant = 0x%01x\n", swd_reg >> 4 & 0xf);
    printf("AP type = 0x%01x (if 0x1, AHB bus)\n", swd_reg & 0xf);

結果は…

=== START ===
[DP]  DPIDR  = 0x0bb11477
[DP]   CTRL <- 0x50000000
[DP]  ABORT <- 0x0000001f
[DP]   STAT  = 0xf0000040
[AP]    IDR  = 0x04770021
AP class = 0x8 (if 0x8, MEM-AP)
AP variant = 0x2
AP type = 0x1 (if 0x1, AHB bus)

メモリにアクセス

IDRの情報からMEM-APにアクセスしていることがわかった。そしてMEM-APは内部のAHBバスにつながっているので、この時点でMEM-APを使ってAHBバスを操作できるはず。試しに0x000~0x100番地の値を読み出してみよう。MEM-APを使ってAHBバスのアドレス空間にアクセスする手順は次の通り。

  1. CSWレジスタでアクセス方法を設定
  2. TARレジスタにアクセスするアドレスをセット
  3. DRWレジスタを使ってアクセス
    • DRWへの読み書きがそのままTARが示すアドレスに対する読み書きに変換される

今回の場合はこのようになる。

    puts("configure memory access ...");
    read_ap_reg(AP_CSW);
    write_ap_reg(AP_CSW, swd_reg & 0xffffffc8 | 0x10 | 0x2, "CSW"); // increment single, 32bit

    puts("read 0x00000000 - 0x00000100");
    write_ap_reg(AP_TAR, 0, "TAR");
    for (int i = 0; i < 64; i++)
        read_ap_reg(AP_DRW, "DRW");

インクリメントアクセス(CSW[5:4] = 1)に設定するとDRWにアクセスするたびに自動でアドレスを進めてくれるので、このような連続アクセスにはとても便利。

=== START ===
[DP]  DPIDR  = 0x0bb11477
[DP]   CTRL <- 0x50000000
[DP]  ABORT <- 0x0000001f
[DP]   STAT  = 0xf0000040
[AP]    IDR  = 0x04770021
AP class = 0x8 (if 0x8, MEM-AP)
AP variant = 0x2
AP type = 0x1 (if 0x1, AHB bus)
configure memory access ...
[AP]    CSW <- 0x03000052
read 0x00000000 - 0x00000100
[AP]    TAR <- 0x00000000
[AP]    DRW  = 0x10002000
(以下略)

LPC11U35に書き込んだバイナリと比べてみると、割り込みテーブルが若干変わっている以外は同じ値が読み出せた。割り込みテーブルはOSが実行時に書き換えたと思われる。実際のコードは省くが、同じようにTARにアドレスをセットしてからDRWに書き込みすることでRAMに書き込むこともできる。これでAHBバスへのアクセスは成功した。

ついでに例のアドレスも読み出してみる。

    puts("read CRP option (0x000002fc)");
    write_ap_reg(AP_TAR, 0x000002fc);
    read_ap_reg(AP_DRW);
    printf("CRP option = 0x%08x\n", swd_reg);
read CRP option (0x000002fc)
CRP option = 0x4e697370

これを何とかしたいのだ…。