みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

Yocto UEFIセキュアブート

はじめに

Yoctoでx86_64向けのボードを動かす。その際にUEFIセキュアブートも有効にしてみたいと考えた。そこで、meta-efi-secure-bootというレイヤを見つけた。

実機はROCK Pi X V1.4を使用する。

UEFIセキュアブート

UEFIセキュアブートは秘密鍵を使用して署名されたバイナリを、実行時に署名が正しいかどうかを検証して、正しい場合にのみ起動できるようになっている。検証に使用する公開鍵はNVRAMに保存されている。

NVRAMに保存される鍵は下記のようなものがある。

役割 主な所持者
Platform Kay (PK) KEKの変更に必要 HWベンダ
Key Exchange Key (KEK) db/dbxの変更に必要 OSベンダ
db/dbx 実際のバイナリの検証に使用

UEFIファームウェアから起動されるプログラムはdb/dbxによって検証され、正しいと確認が取れた場合にのみ実行可能となる。dbは署名データベースdbxは失効した署名データベースとなっている。dbにある署名と一致する場合は正しいバイナリ、dbxにある署名と一致する場合は不正なバイナリと判断される。

f:id:mickey_happygolucky:20210309091436p:plain
db/dbx

shimについて

UEFIセキュアブートでは実際のバイナリの署名を検証するのはdb/dbxだが、そのdb/dbxに鍵を登録するにはKEK秘密鍵が必要になる。

KEKはPKによって署名されているが、PKの秘密鍵はHWベンダしか持っておらず、UEFIを使用する場合は大概PCのHWとなり、HWベンダとしてはWindowsが起動できれば良いため、KEK秘密鍵マイクロソフトが持っているという状況が多い。

Linuxディストリビューションを起動するためにはカーネルを起動するためのブートローダであるgrubに対してdbの署名をしてもらう必要があるが、dbの署名をすることができるのはKEK秘密鍵を持っているマイクロソフトである。オープンソースであるgrubをビルドするたびにいちいちマイクロソフトに署名してもらうのは現実的ではない。

そこでshimという小さいブートローダ作成し、shimにしてマイクロソフトに署名してもらうようにした。shimからはdb/dbxの署名を検証し次のブートローダを起動するという機能しか持たせずビルドされる機会は少ないように作られている。 shimからgrubを起動し、従来のgrubからカーネルを起動するという流れにすることで、UEFIセキュアブートの環境でもLinuxが起動できるようになった。

shimから署名をチェックしないでなんでも起動できるようにしてしまうとセキュアブートの意味がなくなるので、shim以降はMachine Owner Key(MOK)という署名鍵でバイナリを検証する。 MOKの証明書の公開鍵はNVRAMではなくshimに埋め込まれている。

非PEのValidation

shimはPE形式のバイナリに対してのみ検証を行う。grub.cfgやカーネル、initrdなどの非PEのファイルに対して検証を行うために、 meta-efi-secure-bootではSELoaderを導入ししている。

SELoaderはshimに埋め込まれた証明書でバイナリを検証する。

ブートシーケンス

UEFIファームウェアはデフォルトの状態だと、EFI/BOOT/bootx64.efiを探して実行しようとする。そのため、最初に実行したいブートローダbootx64.efiにリネームしておく。 ここではshimbootx64.efiとして配置されている。

shimからgrubを直接実行しても問題はないが、shimは前述の通りPE形式のファイルしか認証できないため、shimの次にSELoaderを実行しSELoaderからgrubが実行されるようになっている。

まとめると次のような順序でプログラムが実行される。

  1. UEFIファームウェア
  2. shim
  3. SELoader
  4. grub
  5. Linux Kenrel

UEFIファームウェア(BIOS)の設定

最終的にカーネルが起動した時の設定を下記に示す。

鍵は全て、meta-efi-secure-bootが提供しているサンプルの鍵を使用している。

Aptio Setup Utility(American Megatrends, Inc.)

Securityのタブ

Key Value
System Mode User
Secure Boot Active
Vendor Keys Not Active
Secure Boot [Enabled]
Secure Boot Mode [Custom]

Key Management

Key Value
Provison Factory Default Keys [Disabled]
Secure Boot variable Size Key# Key source
Platform Key(PK) 843 1 Custom *
Key Exchange Keys 823 1 Custom *
Authorized Signatures 821 1 Custom *
Forbidden Signatures 813 1 Custom *
Authorized TimeStamps 0 0

meta-efi-secure-bootの挙動

boot-menuの変更

検証環境ではboot-menu.incの内容が実機とあっていないので、grubメニューでeを押してメニューの内容を変更する必要があった。

その場合はユーザーとパスワードが聞かれるので下記のように入力する必要がある。

User Password
root root

これはpassword.incによって設定される。

ブートパーティション

カーネルが起動した時のブートパーティションのファイル構成を下記に示す。

.
├── EFI
│   └── BOOT
│       ├── Hash2DxeCrypto.efi
│       ├── LockDown.efi
│       ├── Pkcs7VerifyDxe.efi
│       ├── SELoaderx64.efi
│       ├── boot-menu.inc
│       ├── boot-menu.inc.p7b
│       ├── bootx64.efi
│       ├── efi-secure-boot.inc
│       ├── efi-secure-boot.inc.p7b
│       ├── grub.cfg
│       ├── grub.cfg.p7b
│       ├── grubenv
│       ├── grubx64.efi
│       ├── mmx64.efi
│       ├── password.inc
│       └── password.inc.p7b
├── bzImage
└── bzImage.p7b

カスタムキーのProvision

カスタムキーをNVRAMに一括に登録する機能としてLockDown.efiがある。このアプリケーションは通常Setupモードで実行されることになる。

カスタムキー非登録時には非セキュアな状態で実行されるgrubによって、自動的にLockDown.efiが実行されるようになっている。

これが自動的に実行されない場合はUEFI設定画面からUEFIシェルを起動し、手動で実行する。

UEFIシェルでのコマンドの例を下記に示す。

> fs0:
> cd EFI\BOOT
> LockDown.efi

meta-efi-secure-bootのビルド

meta-efi-secure-bootを使用してROCK Pi Xを起動する環境を作成する手順を示す。いくつか修正する必要があったため独自のレイヤを作成している。

gatesgarthブランチを使用する。

pokyの取得

$ git clone git://git.yoctoproject.org/poky -b gatesgarth

環境変数を読み込む。

$ source poky/oe-init-build-env build

この時点でbuildディレクトリに移動される。

必要なレイヤをダウンロードする。

$ bitbake-layers layerindex-fetch meta-oe
$ bitbake-layers layerindex-fetch meta-efi-secure-boot -b master

meta-efi-secure-boot-helperの取得

ワークアラウンドのために作成したレイヤをダウンロードする。

$ cd ../poky
$ git clone https://github.com/mickey-happygolucky/meta-efi-secure-boot-helper.git
$ cd -
$ bitbake-layers add-layer ../poky/meta-efi-secure-boot-helper

local.confの修正

conf/local.confの下の方に下記の内容を追加する。

MACHINE = "genericx86-64"
DL_DIR = "${TOPDIR}/../downloads"

# systemd
DISTRO_FEATURES_append = " systemd pam"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

# secure boot
#SIGNING_MODEL = "user"

INITRAMFS_IMAGE = "secure-core-image-initramfs"
DISTRO_FEATURES_append = " efi-secure-boot"

IMAGE_EFI_BOOT_FILES_append = " \
        ${KERNEL_IMAGETYPE}.p7b \
        Hash2DxeCrypto.efi;EFI/BOOT/Hash2DxeCrypto.efi \
        LockDown.efi;EFI/BOOT/LockDown.efi \
        Pkcs7VerifyDxe.efi;EFI/BOOT/Pkcs7VerifyDxe.efi \
        SELoaderx64.efi;EFI/BOOT/SELoaderx64.efi \
        boot-menu.inc;EFI/BOOT/boot-menu.inc \
        boot-menu.inc.p7b;EFI/BOOT/boot-menu.inc.p7b \
        bootx64.efi;EFI/BOOT/bootx64.efi \
        efi-secure-boot.inc;EFI/BOOT/efi-secure-boot.inc \
        efi-secure-boot.inc.p7b;EFI/BOOT/efi-secure-boot.inc.p7b \
        grub.cfg;EFI/BOOT/grub.cfg \
        grub.cfg.p7b;EFI/BOOT/grub.cfg.p7b \
        grubenv;EFI/BOOT/grubenv \
        grubx64.efi;EFI/BOOT/grubx64.efi \
        mmx64.efi;EFI/BOOT/mmx64.efi \
        password.inc;EFI/BOOT/password.inc \
        password.inc.p7b;EFI/BOOT/password.inc.p7b \
"

ビルド

secure-core-minimal-imageをビルドする。

$ bitbake secure-core-minimal-image

書き込み

bmaptoolで書き込む。メディアはUSBメモリスティックか、SDカードを使用する。

SDカードを使用する場合はオンボードのカードスロットではなく、USB接続のカードリーダを使用する必要がある。

メディアが自動でマウントされている場合はアンマウントする。

$ sudo umount /media/${USER}/*

下記のコマンドで書き込む。

$ cd tmp/deploy/images/genericx86-64/
$ sudo bmaptool copy secure-core-minimal-image-genericx86-64.wic /dev/sda

UEFIファームウェア(BIOS)の設定

ボードにはUSBでキーボード、HDMIでモニタを接続しておく。

UEFIファームウェア(BIOS)の設定画面で下記のことを実行する。

  1. セキュアブートを有効化
  2. 設定されている鍵を全て削除
  3. LockDown.efiの実行

次回からセキュアな環境でgrubが起動されるようになる。

meta-efi-secure-boot-helperの内容

構成

設定ファイルを上書きしたいのでプライオリティは20と高めに設定している。

.
├── COPYING.MIT
├── README.md
├── conf
│   └── layer.conf
├── recipes-bsp
│   └── grub
│       ├── grub-efi
│       │   └── boot-menu.inc
│       └── grub-efi_2.04.bbappend
└── recipes-devtools
    └── sbsigntool
        ├── sbsigntool
        │   └── resolve_efi_dir.patch
        └── sbsigntool_git.bbappend

追加したレシピは下記の2つ。

  1. grub-efi
  2. sbsigntool

grub-efi

recipes-bsp/grub/grub-efi_2.04.bbappendで署名されていないgrubのバイナリがインストールされるのを抑制した。

FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"

# avoid the installing grubx64.efi which does not certified.
do_deploy() {
}

recipes-bsp/grub/grub-efi/boot-menu.incgrubメニューの内容を修正した。

menuentry "Sample EFI boot" --unrestricted {
    savedefault
    set fallback=1
    linux /bzImage root=/dev/sda2 ro rootwait
}

sbsigntool

そのままではビルドに失敗するため、recipes-devtools/sbsigntool/sbsigntool_git.bbappendで依存関係とパッチを追加した。

SRC_URI += "file://resolve_efi_dir.patch;subdir=git"

FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"
DEPENDS += "binutils-native"

recipes-devtools/sbsigntool/sbsigntool/resolve_efi_dir.patchはクロスコンパイルに対応している。

diff --git a/configure.ac b/configure.ac
index 4ffb68f..a77f94d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -71,7 +71,7 @@ AM_CONDITIONAL(TEST_BINARY_FORMAT, [ test "$EFI_ARCH" = "arm" -o "$EFI_ARCH" = "
 # no consistent view of where gnu-efi should dump the efi stuff, so find it
 ##
 for path in /lib /lib64 /usr/lib /usr/lib64 /usr/lib32 /lib/efi /lib64/efi /usr/lib/efi /usr/lib64/efi /usr/lib/gnuefi /usr/lib64/gnuefi ; do
-    if test -e $path/crt0-efi-$EFI_ARCH.o; then
+    if test -e $base_prefix$path/crt0-efi-$EFI_ARCH.o; then
        CRTPATH=$path
     fi
 done
@@ -79,7 +79,7 @@ if test -z "$CRTPATH"; then
    AC_MSG_ERROR([cannot find the gnu-efi crt path])
 fi
 
-EFI_CPPFLAGS="-I/usr/include/efi -I/usr/include/efi/$EFI_ARCH \
+EFI_CPPFLAGS="-I$base_prefix/usr/include/efi -I$base_prefix/usr/include/efi/$EFI_ARCH \
  -DEFI_FUNCTION_WRAPPER"
 CPPFLAGS_save="$CPPFLAGS"
 CPPFLAGS="$CPPFLAGS $EFI_CPPFLAGS"

まとめ

Yoctoでx86のボードをちゃんと動かしたのは初めて。せっかくなのでUEFIセキュアブートを有効化してみた。

起動しない時に何が原因かを探るのが難しいが意外とドキュメントは見つかるように感じた。

参照

Yocto bitbake-layers layerindex-fetch

はじめに

bitbake-layersコマンドの便利なサブコマンドを見つけた(認識した)。

レイヤ間の依存関係

bitbakeで取り扱うレイヤではlayer.confに下記のようにLAYERDEPENDSを記述することで、依存関係を定義することができる。

LAYERDEPENDS_gnome-layer = "core openembedded-layer networking-layer"

しかし、bitbake-layers add-layerをする場合、依存関係の順番を間違えると面倒なことになる。

$ bitbake-layers add-layer ../poky/meta-openembedded/meta-gnome

一見問題なく追加できるが、次回以降bitbake-layersのコマンドを実行すると下記のようにエラーが発生する。

$ bitbake-layers show-layers
NOTE: Starting bitbake server...
Traceback (most recent call last):
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/bin/bitbake-layers", line 95, in <module>
    ret = main()
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/bin/bitbake-layers", line 63, in main
    tinfoil.prepare(True)
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/lib/bb/tinfoil.py", line 414, in prepare
    self.run_command('parseConfiguration')
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/lib/bb/tinfoil.py", line 470, in run_command
    raise TinfoilCommandFailed(result[1])
bb.tinfoil.TinfoilCommandFailed: Traceback (most recent call last):
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/lib/bb/command.py", line 74, in runCommand
    result = command_method(self, commandline)
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/lib/bb/command.py", line 275, in parseConfiguration
    command.cooker.parseConfiguration()
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/lib/bb/cooker.py", line 433, in parseConfiguration
    self.handleCollections(self.data.getVar("BBFILE_COLLECTIONS"))
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/lib/bb/cooker.py", line 1225, in handleCollections
    raise CollectionError("Errors during parsing layer configuration")
bb.cooker.CollectionError: Errors during parsing layer configuration

これはmeta-gnomeが依存しているレイヤがないため発生するのだが、この状態になるとテキストエディタなどでbblayers.confを修正する必要がある。

meta-gnomeに記載されている依存関係は下記。

  1. core
  2. openembedded-layer
  3. networking-layer

しかしここで厄介なのが、networking-layerがmeta-pythonに依存しているためそちらも記述しなければならない。 このようなネストした依存関係がある場合には、1つずつ追加していくのが面倒となってくる。

bitbake-layers layerindex-fetch

レイヤ間の依存関係をプログラム的にチェックできるのであれば、追加する前にエラーにするか、自動的に追加するなどしてほしいと思っていた。 しかし、layerindex-fetchサブコマンドを使用すると自動的に依存関係を解決して、必要であればレイヤのダウンロードまでおこなってくれる。

$ bitbake-layers layerindex-fetch meta-gnome
NOTE: Starting bitbake server...
Loading https://layers.openembedded.org/layerindex/api/;branch=dunfell...
Layer                                              Git repository (branch)                                 Subdirectory
=============================================================================================================================
local:HEAD:openembedded-core                       git://git.yoctoproject.org/poky.git (dunfell)           meta
  required by: meta-gnome meta-networking meta-python meta-oe
layers.openembedded.org:dunfell:meta-oe            git://git.openembedded.org/meta-openembedded (dunfell)  meta-oe
  required by: meta-python meta-networking meta-gnome
layers.openembedded.org:dunfell:meta-python        git://git.openembedded.org/meta-openembedded (dunfell)  meta-python
  required by: meta-networking
layers.openembedded.org:dunfell:meta-networking    git://git.openembedded.org/meta-openembedded (dunfell)  meta-networking
  required by: meta-gnome
layers.openembedded.org:dunfell:meta-gnome         git://git.openembedded.org/meta-openembedded (dunfell)  meta-gnome
Cloning into '/home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded'...
remote: Counting objects: 148262, done.
remote: Compressing objects: 100% (49118/49118), done.
remote: Total 148262 (delta 92673), reused 147741 (delta 92348)
Receiving objects: 100% (148262/148262), 39.27 MiB | 3.67 MiB/s, done.
Resolving deltas: 100% (92673/92673), done.
Adding layer "meta-oe" (/home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-oe) to conf/bblayers.conf
Adding layer "meta-python" (/home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-python) to conf/bblayers.conf
Adding layer "meta-networking" (/home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-networking) to conf/bblayers.conf
Adding layer "meta-gnome" (/home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-gnome) to conf/bblayers.conf

レイヤの設定状況を確認する。

$ bitbake-layers show-layers
NOTE: Starting bitbake server...
layer                 path                                      priority
==========================================================================
meta                  /home/mickey/work/yocto/x86-dunfell/layers/poky/meta  5
meta-poky             /home/mickey/work/yocto/x86-dunfell/layers/poky/meta-poky  5
meta-yocto-bsp        /home/mickey/work/yocto/x86-dunfell/layers/poky/meta-yocto-bsp  5
meta-oe               /home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-oe  6
meta-python           /home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-python  7
meta-networking       /home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-networking  5
meta-gnome            /home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-gnome  7

meta-gnomeが依存する全てのレイヤが追加されている。

自動的にダウンロードできるレイヤについて

レイヤのダウロードにはhttps://layers.openembedded.org/layerindex/api/というWeb APIを使用している。

そのため、OpenEmbedded Layer Indexに登録されているもののみが対象となっていると推測される。

既存の設定について

layerindex-fetchでは既に追加してあるレイヤに関しては削除されないため、ローカルで開発中のレイヤなどがBBLAYERSに既にある場合でも問題なく使用することができる。

ブランチ

下記のようにすることで、ダウンロードする際に使用するブランチを指定することができる。

$ bitbake-layers layerindex-fetch meta-gnome -b dunfell

ブランチを指定しない場合は、現在チェックアウトしているpokyのブランチがデフォルトとなる。

例えば、pokyがgatesgarthである時にブランチを指定せずにlayerindex-fetchを実行するとgatesgarthブランチが選択される。

また、レイヤ側がmasterブランチしか持っていない場合などに、pokyがdunfellだった場合ではデフォルト指定ではエラーになる。

$ bitbake-layers layerindex-fetch meta-efi-secure-boot
NOTE: Starting bitbake server...
Loading https://layers.openembedded.org/layerindex/api/;branch=dunfell...
ERROR: Layer "meta-efi-secure-boot" not found in layer index

そのような場合にはブランチを明示する必要がある。

$ bitbake-layers layerindex-fetch meta-efi-secure-boot -b master

まとめ

bitbake-layers layerindex-fetchは便利。

Yocto meta-gnomeでpolkitのエラー

はじめに

デフォルトの設定でmeta-gnomeをビルドするとエラーが発生する。

エラーの内容

gnome-control-centerとsystmedがそれぞれで/usr/share/polkit-1/rules.dをインストールしようとしてコンフリクトしている。

Error: Transaction test error:
  file /usr/share/polkit-1/rules.d conflicts between attempted installs of gnome-control-center-3.36.4-r0.cortexa7t2hf_neon_vfpv4 and systemd-1:246.9-r0.cortexa7t2hf_neon_vfpv4

設定ファイル自体がかぶってエラーになるならともかく、格納場所であるディレクトリがかぶってエラーになってしまうのはどうかと思う。

原因

meta-gnomeはmeta-openembeddedの持ち物なので、こんな基本的なところがメンテナンスされないことはないだろうと思いつつ調べていくと、ここで似たようなエラーメッセージが引っかかった。

これはPC(サーバ)用のLinux上でrpmの処理中に似たような状況になっている。

これはbitbakeのエラーではなくRPMの制限なのかと推測した。

回避策

local.confに下記を追加し、パッケージシステムをrpmからdebにする。

PACKAGE_CLASSES = "package_deb"

まとめ

meta-gnomeを使う場合はパッケージシステムはRPM以外にする。

Raspberry Pi Pico Systick割り込みを使ってLチカをFlashに書く

はじめに

前回作ったプログラムをFlashから実行できるようにしてみる。

RESUSについて追記(2021/3/10)

Flash対応

Makefile

diff -Narubp blink_ram_int_systick/Makefile blink_flash_int_systick/Makefile
--- blink_ram_int_systick/Makefile    2021-02-27 13:21:52.160613474 +0900
+++ blink_flash_int_systick/Makefile  2021-02-28 07:45:26.010697915 +0900
@@ -7,20 +7,26 @@ ASMOPT = $(MCPU) -g
 COPT = $(MCPU) -ffreestanding -g -O0
 LOPT = -nostdlib -nostartfiles
 
-all: ram
+all: led.uf2
 
 clean:
    rm -f *.o
    rm -f *.elf
    rm -f *.list
+   rm -f *.uf2
    rm -f *~
 
 start.o: start.S
    $(CROSS_COMPILE)as $(ASMOPT) start.S -o start.o
 
+boot2.o: boot2/bs2_default_padded_checksummed.S
+   $(CROSS_COMPILE)as $(ASMOPT) boot2/bs2_default_padded_checksummed.S -o boot2.o
+
 main.o: main.c
    $(CROSS_COMPILE)gcc $(COPT) -fpic -mthumb -c main.c -o main.o
 
-ram: start.o main.o
-  $(CROSS_COMPILE)ld $(LOPT) start.o main.o -T memmap_ram.ld  -o led.elf 
+led.elf: start.o boot2.o main.o
+   $(CROSS_COMPILE)ld $(LOPT) start.o boot2.o main.o -T memmap.ld  -o led.elf 
 
+led.uf2: led.elf
+   elf2uf2 led.elf led.uf2

Flashに書き込みできるようにビルド済みのBoot Stage2のリンクとelf2uf2でuf2に変換できるようにする。

リンカスクリプト

リンカスクリプトmemmap_ram.ldではなくmemmap.ldにリネームして下記のように変更する。

diff -Narubp blink_ram_int_systick/memmap_ram.ld blink_flash_int_systick/memmap_ram.ld
--- blink_ram_int_systick/memmap_ram.ld   2021-02-27 13:21:52.160613474 +0900
+++ blink_flash_int_systick/memmap_ram.ld 2021-02-28 08:24:07.117410109 +0900
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: MIT */
 MEMORY
 {
+    FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 2048k
       RAM(rwx) : ORIGIN =  0x20000000, LENGTH = 256k
 }
 
@@ -8,11 +9,22 @@ ENTRY(reset)
 
 SECTIONS
 {
-    .text : {
-        *(.vectors)
-      *(.text*)
-    } > RAM
+    /* 
+       boot2 section is for embed the precompiled boot2.
+       this code from memmap_default.ld in pico-sdk
+    */
+    .boot2 : {
+        __boot2_start__ = .;
+        KEEP(*(.boot2))
+        __boot2_end__ = .;
+    } > FLASH
 
+    ASSERT(__boot2_end__ - __boot2_start__ == 256,
+        "ERROR: Pico second stage bootloader must be 256 bytes in size")
+    .text : {
+        KEEP (*(.vectors))
+       KEEP(*(.text*))
+    } > FLASH
     .bss : {
         __bss_start__ = .;
    *(.bss*)

スタートアップ

ベクターテーブルはBoot Stage2で設定されるので、VTORの設定は削除。

diff -Narubp blink_ram_int_systick/start.S blink_flash_int_systick/start.S
--- blink_ram_int_systick/start.S 2021-02-27 13:21:52.160613474 +0900
+++ blink_flash_int_systick/start.S   2021-02-28 07:51:40.922563814 +0900
@@ -28,10 +28,6 @@ __vectors:
    .thumb_func
    .global reset
 reset:
-  ldr r1, =0xE000ED08 /* VTOR */
-  ldr r0, =__vectors
-  str r0, [r1]
-
    ldr r0, =0x20001000
    mov sp, r0
    bl init_bss

実行(失敗)

ビルドして出来上がったled.uf2をPicoに書き込む。

LEDがチカチカするが波形取ってみると下記のようになる。

f:id:mickey_happygolucky:20210228155147p:plain
Lチカ(失敗)

本来は100msになっているべきWidthが223.6msになっている。

XOSC

RP2040 Datasheetからこの辺りのことを調べてみる。

「2.4.5.1.1. SysTick timer」によると、watchdogブロックのクロックを使用する。

The SysTick timer uses a 1μs pulse as a clock enable. This is generated in the watchdog block as timer_tick.

SysTickはクロックソースをSYST_CSRのCLKSOURCEによって選択できるが、Lチカプログラムでは外部クロック(watchdogのクロック)を使用するようにしている。

「4.7.2 Tick generation」によると、1usのtickを得るために使用しているclk_tickclk_refから作られているらしく、正確なリファレンスクロックを得るには、Crystal Oscillator(XOSC)を動かす必要があるらしい。

The watchdog reference clock, clk_tick, is driven from clk_ref. Ideally clk_ref will be configured to use the Crystal Oscillator (Section 2.16) so that it provides an accurate reference clock.

「2.7. Boot Sequence」によるとclk_refは最初Ring Oscillator(ROSC)によって、低周波数(6.5MHz)で駆動しているとのこと。

The Ring Oscillator (Section 2.17) is started, providing a clock source to the clock generators. clk_sys and clk_ref are now running at a relatively low frequency (typically 6.5MHz).

Pico SDKを使用している場合は、xosc_init()によってこれらが正しく設定された状態でプログラムが実行される。

RAMバージョンで問題なく動作していたのは、Flashに書き込まれていたプログラムがpico-examplesのblinkerで、Pico SDKによってXOSCの初期化が行われていたからではないかと推測される。

レジスタ設定の確認

XOSCのレジスタ

XORCレジスタで設定する。

f:id:mickey_happygolucky:20210228155305p:plain

f:id:mickey_happygolucky:20210228155324p:plain

f:id:mickey_happygolucky:20210228155343p:plain

クロックソース

CLOCKSレジスタで設定する。

f:id:mickey_happygolucky:20210228155358p:plain

f:id:mickey_happygolucky:20210228155411p:plain

XOSC初期化処理の追加

XOSCを初期化しclk_sysのクロックソースをROSCからXOSCに変更する。

regs.h

regs.hを下記のように修正する。

diff --git a/blink_flash_int_systick/regs.h b/blink_flash_int_systick/regs.h
index 2d650ec..51792c4 100644
--- a/blink_flash_int_systick/regs.h
+++ b/blink_flash_int_systick/regs.h
@@ -34,5 +34,13 @@
 #define SYSTICK_RVR (0xE000E014)
 #define SYSTICK_CVR (0xE000E018)
 
+#define XOSC_BASE (0x40024000)
+#define XOSC_CTRL (XOSC_BASE+0x00)
+#define XOSC_STATUS (XOSC_BASE+0x04)
+#define XOSC_STARTUP (XOSC_BASE+0x0c)
+
+#define CLOCKS_BASE (0x40008000)
+#define CLOCKS_REF_CTRL (CLOCKS_BASE+0x30)
+#define CLOCKS_SYS_CTRL (CLOCKS_BASE+0x3c)
 
 #endif //REGS_H

XOSC関連とclk_ref、clc_sysのコントロールレジスタを追加。

main

main.cを下記のように修正する。

diff --git a/blink_flash_int_systick/main.c b/blink_flash_int_systick/main.c
index 19539f9..6553c63 100644
--- a/blink_flash_int_systick/main.c
+++ b/blink_flash_int_systick/main.c
@@ -24,6 +24,26 @@ void init_bss() {
         }
 }
 
+void init_xosc() {
+        write_reg(XOSC_CTRL, 0xaa0); /* 1_15MHZ */
+        write_reg(XOSC_STARTUP, 47); /* (((12 * MHZ) / 1000) + 128) / 256 = 47 */
+        write_reg_op(XOSC_CTRL, 0xfab << 12, OP_SET); /* ENABLE */
+
+        /* Wait for XOSSC to be stable */
+        while (!(read_reg(XOSC_STATUS) & 0x80000000)) ;
+}
+
+void init_clocks() {
+        init_xosc();
+
+        /* source of clk_ref to xosc */
+        write_reg_op(CLOCKS_REF_CTRL, 0x2, OP_SET);
+
+        /* reset the source of clk_sys to clk_ref which references xosc */
+        write_reg(CLOCKS_SYS_CTRL, 0x0);
+}
+
+
 void init_systick() {
         write_reg(SYSTICK_RVR, 1000-1); /* Systick interrupt will issue per 1ms. */
         write_reg(SYSTICK_CVR, 0);

XOSCの初期化処理と、clk_refのクロックソースをXOSCに変更。 XOSCを参照するようにしたclk_refを、clk_sysのクロックソースとして再設定している。

RESUS

クロックの初期化処理を追加したのに、実際に波形を取ってみると思ったとおりに動かない(パルス幅が広くなる)ことがある。

これは初期化の最中にRESUSという機能が働いた結果おかしい動作になっているっぽいことがわかった。

RESUSはRP2040が持っているセーフティの機能で、clk_sysが止まってしまった場合、clk_refを使用して無理やり動かすというものらしい。よってclk_refまで止まるとRESUSでもどうにもできなくなるとのこと。

それはそれとして、クロックの初期化で一度clk_sysを止めるのでその際にRESUSが動いたままだと挙動がおかしくなることがあるらしい。そのためクロックの初期化をする前にRESUSを止めておく必要がある。

main.cのinit_clocks()を下記のように修正する。

void init_clocks() {
        /*  Disable resus that may be enabled from previous software */
        write_reg(CLOCKS_CLK_SYS_RESUS_CTRL, 0);

        init_xosc();

        /* source of clk_ref to xosc */
        write_reg_op(CLOCKS_REF_CTRL, 0x2, OP_SET);

        /* reset the source of clk_sys to clk_ref which references xosc */
        write_reg(CLOCKS_SYS_CTRL, 0x0);
}

CLOCKS_CLK_SYS_RESUS_CTRLの定義をrefs.hに追加する。

#define CLOCKS_CLK_SYS_RESUS_CTRL (CLOCKS_BASE+0x78)

スタートアップ

start.Sを下記のように修正する。

diff --git a/blink_flash_int_systick/start.S b/blink_flash_int_systick/start.S
index b39ba02..0eb8efc 100644
--- a/blink_flash_int_systick/start.S
+++ b/blink_flash_int_systick/start.S
@@ -31,6 +31,7 @@ reset:
        ldr r0, =0x20001000
        mov sp, r0
        bl init_bss
+       bl init_clocks
        bl main
        b hang

init_clocksを呼び出すように変更。

実行

ビルドして出来上がったled.uf2をPicoに書き込む。

LEDがチカチカするが波形取ってみると下記のようになる。

f:id:mickey_happygolucky:20210228155224p:plain
Lチカ

パルス幅が100msになっている。

まとめ

LチカをFlashに書くと、クロック周りの初期化をきちんとしないと正しいタイミングで点滅しない。

参考

Raspberry Pi Pico Systick割り込みを使ってLチカをする

はじめに

SysTick割り込みを使用してLチカをする。

2021-02-26 Raspberry Pi Pico Systickを使ってLチカを正確にするのコードをベースに作業する。

SysTick割り込み

SysTick割り込みはm0+のコアで用意されている割り込みで、この割り込みが発生するとベクターテーブルに飛んでくる。

ここによるとベクターテーブルは下記のような構造になっている。

ベクターテーブル

ベクターテーブルは通常0x00000000に配置されCPUのリセットがかかったり割り込みが入ったりした場合に参照される様になっているが、VTORレジスタにアドレスを設定することで任意の場所に配置することができる。

今回はPicoのSRAM上にベクターテーブルを配置するようにする。

ベクターテーブルと割り込みハンドラの追加

ベクターテーブルとSysTickの割り込みハンドラとなるisr_systickを追加する。

スタートアップ

start.Sを下記のように変更する。

--- a/blink_ram_int_systick/start.S
+++ b/blink_ram_int_systick/start.S
@@ -2,10 +2,36 @@
    .cpu cortex-m0plus
    .thumb

+/* vector table */
+   .section .vectors, "ax"
+   .align 2
+   .global __vectors
+__vectors:
+.word 0x20001000
+.word reset
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word hang
+.word isr_systick
+
 /* reset handler */
    .thumb_func
    .global reset
 reset:
+   ldr r1, =0xE000ED08 /* VTOR */
+   ldr r0, =__vectors
+   str r0, [r1]
+
    ldr r0, =0x20001000
    mov sp, r0
    bl main

ベクターテーブルを__vectorsとして定義する。 Systick割り込みハンドラの位置にisr_systickを登録している。他の割り込みは使用しないのでisr_systickまでしか定義していない。

reset関数の先頭でVTORレジスタ__vectorsのアドレスを設定している。

リンカスクリプト

memmap_ram.ldを下記の様に修正する。

--- a/blink_ram_int_systick/memmap_ram.ld
+++ b/blink_ram_int_systick/memmap_ram.ld
@@ -9,6 +9,7 @@ ENTRY(reset)
 SECTIONS
 {
     .text : {
+        *(.vectors)
        *(.text*)
     } > RAM
 }

.vectorセクションがRAMに配置されるようにする。

main

main.cを下記のように修正する。

--- a/blink_ram_int_systick/main.c
+++ b/blink_ram_int_systick/main.c
@@ -18,6 +18,11 @@ void init_systick() {
         write_reg(SYSTICK_CSR, 0x1);
 }

+static uint32_t tick_count = 0;
+void isr_systick() {
+        ++tick_count;
+}
+
 // us is up to max of 24bit (0xffffff)
 void delay_us(uint32_t us) {
         write_reg(SYSTICK_RVR, us);

Systickの割り込みハンドラとなるisr_systickを追加する。

割り込みが発生するたびにtick_countをインクリメントする。

ディレイ処理の改造

前回はマイクロ秒単位で作成したディレイ関数をミリ秒単位に変更する。

main.c

diff --git a/blink_ram_int_systick/main.c b/blink_ram_int_systick/main.c
index 830f1e4..77d1309 100644
--- a/blink_ram_int_systick/main.c
+++ b/blink_ram_int_systick/main.c
@@ -15,7 +15,9 @@ void write_reg_op(uint32_t addr, uint32_t value, uint32_t op) {
 }

 void init_systick() {
-        write_reg(SYSTICK_CSR, 0x1);
+        write_reg(SYSTICK_RVR, 1000-1); /* Systick interrupt will issue per 1ms. */
+        write_reg(SYSTICK_CVR, 0);
+        write_reg(SYSTICK_CSR, 0x3); /* TICKINT=1, ENABLE=1 */
 }

 static uint32_t tick_count = 0;
@@ -23,16 +25,10 @@ void isr_systick() {
         ++tick_count;
 }

-// us is up to max of 24bit (0xffffff)
-void delay_us(uint32_t us) {
-        write_reg(SYSTICK_RVR, us);
-        write_reg(SYSTICK_CVR, 0);
-
-        // wait for 0 to RVR
-        while (!read_reg(SYSTICK_CVR)) __asm__ __volatile__("");
-
-        // wait for RVR counting down to 0.
-        while (read_reg(SYSTICK_CVR)) __asm__ __volatile__("");
+void delay_ms(uint32_t ms) {
+        uint32_t expire = tick_count + ms;
+        while (tick_count != expire)
+                __asm__ __volatile__("");
 }

 int main(void) {
@@ -66,7 +62,7 @@ int main(void) {
         // Blink
         write_reg(SIO_GPIO_OE_SET, (1ul<<25));
         while (1) {
-                delay_us(100*1000); //100ms
+                delay_ms(100); //100ms
                 write_reg(SIO_GPIO_OUT_XOR, (1ul<<25));
         }
         return 0;

RVRを1000-1に設定することで、割り込みの周期を1ミリ秒にしている。

BSS領域を使えるようにする

変数tick_countはスタティックな広域変数となっている。0で初期化しているため、コンパイラによって.bssセクションに配置される様になっている。

$ readelf -s led.elf | grep tick_count
25:    21: 20000224     4 NOTYPE  LOCAL  DEFAULT    2 tick_count
$ readelf -S led.elf | grep bss
7:  [ 2] .bss              NOBITS          20000224 000278 000004 00  WA  0   0  4

余談だが初期値を0以外にすると.dataセクションに配置される。

現在は.bssセクションはリンカスクリプトでもケアしていないので、下記のようにCのコード上0に初期化していても実際にはおかしな値が設定されている。

static uint32_t tick_count = 0;

これを正しく動作させるためには下記の様な修正をする必要がある。

  • リンカスクリプトを修正し.bssセクションが正しくRAM上に配置されるようにする。
  • mainが実装されるまでに0で初期化する。

リンカスクリプト

リンカスクリプトを下記のように修正する。

diff --git a/blink_ram_int_systick/memmap_ram.ld b/blink_ram_int_systick/memmap_ram.ld
index 97530d9..c16e1ea 100644
--- a/blink_ram_int_systick/memmap_ram.ld
+++ b/blink_ram_int_systick/memmap_ram.ld
@@ -12,4 +12,11 @@ SECTIONS
         *(.vectors)
        *(.text*)
     } > RAM
+
+    .bss : {
+        __bss_start__ = .;
+       *(.bss*)
+       . = ALIGN(4);
+       __bss_end__ = .;
+    } > RAM
 }

__bss_start____bss_end__はロケーションカウンタと呼ばれるもので、リンク後にその位置をプログラムから参照できるようにするもの。ここでは.bssセクションの先頭と末尾の位置を保存している。

main

一般的にはBSS領域の初期化などの処理はスタートアップのアセンブリで実装されることが多いが、ここではC言語からロケーションカウンタを参照し0で初期化する。

diff --git a/blink_ram_int_systick/main.c b/blink_ram_int_systick/main.c
index 77d1309..19539f9 100644
--- a/blink_ram_int_systick/main.c
+++ b/blink_ram_int_systick/main.c
@@ -14,6 +14,16 @@ void write_reg_op(uint32_t addr, uint32_t value, uint32_t op) {
         write_reg(addr | op, value);
 }
 
+void init_bss() {
+        extern uint32_t __bss_start__;
+        extern uint32_t __bss_end__;
+        uint32_t *p = &__bss_start__;
+        uint32_t *end = &__bss_end__;
+        while (p != end) {
+                *p++ = 0;
+        }
+}
+
 void init_systick() {
         write_reg(SYSTICK_RVR, 1000-1); /* Systick interrupt will issue per 1ms. */
         write_reg(SYSTICK_CVR, 0);

スタートアップ

スタートアップからinit_bssを呼び出す。

diff --git a/blink_ram_int_systick/start.S b/blink_ram_int_systick/start.S
index 793d991..8419f1b 100644
--- a/blink_ram_int_systick/start.S
+++ b/blink_ram_int_systick/start.S
@@ -34,6 +34,7 @@ reset:
 
        ldr r0, =0x20001000
        mov sp, r0
+       bl init_bss
        bl main
        b hang

波形を見る

f:id:mickey_happygolucky:20210227132845p:plain
Lチカ

こちらも100msのパルス幅になっている。

まとめ

Systickの割り込みを使用してLチカを行った。

ベクターテーブルの場所はVTORレジスタによって適切に設定する必要がある。 また今回は実装の都合上、tick_countをBSSに置いたためBSSの初期化なども行う必要があった。

今回の成果物はここに置いてある。

参考

*Cortex-M0+ Devices Generic User Guide

Raspberry Pi Pico Systickを使ってLチカを正確にする

はじめに

「PicoでベアメタルのLチカ(RAMバージョン) その2」として。

これまで作ってきたLチカは、適当な値をforでビジーループするだけだったので、タイミングはいい加減だった。

Cortex-M0+ではコアにSysTickを持っていて、これは難しい設定することもなく使用可能なので、これを使ってLチカのタイミングを正確にする。

SysTickはPico SDKの中では基本的には使用されていない。

SysTick

RP2040 Datasheetの「2.4.5.1.1. SysTick timer」によると、

The SysTick timer uses a 1μs pulse as a clock enable. This is generated in the watchdog block as timer_tick.

watchdogのtimer_tickで生成される1usのパルスを使用するとのこと。

SYST_CSRCLKSOURCEの初期値が0になっており外部のクロックを使用するとのこと。これがtimer_tickのことかと思われる。

f:id:mickey_happygolucky:20210226084257p:plain

f:id:mickey_happygolucky:20210226084308p:plain

WATCHDOGのTICKレジスタRUNNINGENABLEが動いているので、timer_tickの1usのパルスは初期状態で動作している。

f:id:mickey_happygolucky:20210226084323p:plain

f:id:mickey_happygolucky:20210226084334p:plain

Lチカ(RAMバージョン)を改造

Lチカ(RAMバージョン)をSysTickを使用するように変更してみる。

regs.h

regs.hにSYSTICK関連のレジスタ定義を追加する。

diff --git a/blink_ram/regs.h b/blink_ram/regs.h
index dc0b11b..2d650ec 100644
--- a/blink_ram/regs.h
+++ b/blink_ram/regs.h
@@ -30,5 +30,9 @@
 
 #define IO_GPIO25_CTRL (IO_BANK0_BASE + IO_BANK_GPIO25_CTRL)
 
+#define SYSTICK_CSR (0xE000E010)
+#define SYSTICK_RVR (0xE000E014)
+#define SYSTICK_CVR (0xE000E018)
+
 
 #endif //REGS_H

main.c

main.cinit_systick()delay_us()を追加する。

初期状態ではSysTickでは動作していないので、init_systick()で動かすようにする。その際CLKSOURCEは0に設定し、timer_tickを使用するようにする。

delay_us()では、RVRに任意のディレイ時間をus単位で設定する。次にCVRに0を設定しRVRにリセットされるまで待つ。 次に、CVRが0になるまで待つことで設定した時間の分だけビジーループする。

diff --git a/blink_ram/main.c b/blink_ram/main.c
index 47f4935..f4b4135 100644
--- a/blink_ram/main.c
+++ b/blink_ram/main.c
@@ -14,6 +14,22 @@ void write_reg_op(uint32_t addr, uint32_t value, uint32_t op) {
         write_reg(addr | op, value);
 }
 
+void init_systick() {
+        write_reg(SYSTICK_CSR, 0x1);
+}
+
+// us is up to max of 24bit (0xffffff)
+void delay_us(uint32_t us) {
+        write_reg(SYSTICK_RVR, us);
+        write_reg(SYSTICK_CVR, 0);
+
+        // wait for 0 to RVR
+        while (!read_reg(SYSTICK_CVR)) __asm__ __volatile__("");
+
+        // wait for RVR counting down to 0.
+        while (read_reg(SYSTICK_CVR)) __asm__ __volatile__("");
+}
+
 int main(void) {
 
         /////////////////////
@@ -39,10 +55,13 @@ int main(void) {
         write_reg(IO_GPIO25_CTRL, 0x5);
         uint32_t v = read_reg(IO_GPIO25_CTRL);
 
+        // SysTick init
+        init_systick();
+
         // Blink
         write_reg(SIO_GPIO_OE_SET, (1ul<<25));
         while (1) {
-                for (int i = 100000; i != 0; i--) ;
+                delay_us(100*1000); //100ms
                 write_reg(SIO_GPIO_OUT_XOR, (1ul<<25));
         }
         return 0;

波形を見る

f:id:mickey_happygolucky:20210226091039p:plain
Lチカ

ちゃんと100ms幅になっている。

まとめ

今回のような単純な使い方では、SysTickは24ビットしかないのであまり長い時間待たせられない。

例えば割り込みを使用してTickカウンタを作って、その差分で待つようにすれば24ビットの壁は超えられると思うが、ちゃんとしたタイマー処理を使用したい場合は、やはりタイマーを使用するほうが良いと思われる。

Raspberry Pi Pico J-LlinkでOpenOCD

はじめに

J-Link EDUが手元にあるので、OpenOCDでPicoのデバッグができないか試してみた。

接続

下記のように接続する。

Pico ARM-JTAG-SWD
SWDIO SWDIO(7)
SWDCLK SWDCLK(9)
GND GND(4,6,8,10,12,14,16,18,20)
VSYS VCC(1)

PlatformIOのJ-LINK

設定ファイル

${PICO_SDK_PATH}/../openocd/tcl/pico-jlink.cfgを下記の内容で作成する。

source [find interface/jlink.cfg]
transport select swd

source [find target/rp2040.cfg]
adapter speed 4000

アダプタをSWDモードに設定し、アダプタのスピードを設定する。

スピードを設定する際にadapter_khzを使うとadapter speedを使えと怒られる。

OpenOCDの実行(失敗)

下記のコマンドでOpenOCDを実行する。

$ cd ${PICO_SDK_PATH}/../openocd
$ src/openocd -f pico-jlink.cfg -s tcl

下記のようなエラーになる。

Open On-Chip Debugger 0.10.0+dev-geb22ace-dirty (2021-02-24-18:16)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : RP2040 Flash Bank Command
DEPRECATED! use 'adapter speed' not 'adapter_khz'
adapter speed: 4000 kHz

Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : J-Link V10 compiled Oct 22 2019 16:28:15
Info : Hardware version: 10.10
Info : VTarget = 4.808 V
Info : clock speed 4000 kHz
Error: Sequence 4 not supported.
Info : DAP init failed


Error: Sequence 3 not supported.
Error: Sequence 4 not supported.

エラーの箇所

src/jtag/drivers/jlink.cの下記の関数でエラーになっている。

static int jlink_swd_switch_seq(enum swd_special_seq seq)
{
    const uint8_t *s;
    unsigned int s_len;

    switch (seq) {
        case LINE_RESET:
            LOG_DEBUG("SWD line reset");
            s = swd_seq_line_reset;
            s_len = swd_seq_line_reset_len;
            break;
        case JTAG_TO_SWD:
            LOG_DEBUG("JTAG-to-SWD");
            s = swd_seq_jtag_to_swd;
            s_len = swd_seq_jtag_to_swd_len;
            break;
        case SWD_TO_JTAG:
            LOG_DEBUG("SWD-to-JTAG");
            s = swd_seq_swd_to_jtag;
            s_len = swd_seq_swd_to_jtag_len;
            break;
        default:
            LOG_ERROR("Sequence %d not supported.", seq);
            return ERROR_FAIL;
    }

    jlink_queue_data_out(s, s_len);

    return ERROR_OK;
}

このseqが3、4の場合にエラーになっている。

enum swd_special_seq {
    LINE_RESET,
    JTAG_TO_SWD,
    SWD_TO_JTAG,
    SWD_TO_DORMANT,
    DORMANT_TO_SWD,
}

SWD_TO_DORMANTDORMANT_TO_SWDに未対応ということらしい。

調べてみると、既にPR(Rp2040 jlink #19)は出されている。

patchは2つあり、下記の修正はドンピシャの内容となっている。

From ef234fa045dfae30e4557db0867d4720f46bbbd3 Mon Sep 17 00:00:00 2001
From: Liam Fraser <liam@raspberrypi.com>
Date: Sun, 24 Jan 2021 08:59:02 +0000
Subject: [PATCH] Add DORMANT_TO_SWD and SWD_TO_DORMANT sequences to jlink
 driver

---
 src/jtag/drivers/jlink.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/src/jtag/drivers/jlink.c b/src/jtag/drivers/jlink.c
index ae8ce49c6..5b2c1ea89 100644
--- a/src/jtag/drivers/jlink.c
+++ b/src/jtag/drivers/jlink.c
@@ -2149,6 +2149,16 @@ static int jlink_swd_switch_seq(enum swd_special_seq seq)
            s = swd_seq_swd_to_jtag;
            s_len = swd_seq_swd_to_jtag_len;
            break;
+       case DORMANT_TO_SWD:
+           LOG_DEBUG("DORMANT-to-SWD");
+           s = swd_seq_dormant_to_swd;
+           s_len = swd_seq_dormant_to_swd_len;
+           break;
+       case SWD_TO_DORMANT:
+           LOG_DEBUG("SWD-to-DORMANT");
+           s = swd_seq_swd_to_dormant;
+           s_len = swd_seq_swd_to_dormant_len;
+           break;
        default:
            LOG_ERROR("Sequence %d not supported.", seq);
            return ERROR_FAIL;

もう一つの方はack待ちが不要なコマンドに対応するものらしい。

From 7e5ea1861a118120a78dac1fd812f1bdcaedc0cc Mon Sep 17 00:00:00 2001
From: graham sanderson <graham.sanderson@gmail.com>
Date: Mon, 25 Jan 2021 12:09:03 -0600
Subject: [PATCH] Add handling of no-ack commands to jlink driver

Change-Id: I4f7b0dad37bc68cde168962d86e53d7f5ea1cad7
---
 src/jtag/drivers/jlink.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/jtag/drivers/jlink.c b/src/jtag/drivers/jlink.c
index 5b2c1ea89..e2537b78f 100644
--- a/src/jtag/drivers/jlink.c
+++ b/src/jtag/drivers/jlink.c
@@ -2002,6 +2002,8 @@ struct pending_scan_result {
    void *buffer;
    /** Offset in the destination buffer */
    unsigned buffer_offset;
+   /** true if the command has nmo acknowledgement */
+   bool no_ack;
 };
 
 #define MAX_PENDING_SCAN_RESULTS 256
@@ -2195,7 +2197,9 @@ static int jlink_swd_run_queue(void)
    }
 
    for (i = 0; i < pending_scan_results_length; i++) {
-      int ack = buf_get_u32(tdo_buffer, pending_scan_results_buffer[i].first, 3);
+       int ack = pending_scan_results_buffer[i].no_ack ?
+               SWD_ACK_OK :
+               buf_get_u32(tdo_buffer, pending_scan_results_buffer[i].first, 3);
 
        if (ack != SWD_ACK_OK) {
            LOG_DEBUG("SWD ack not OK: %d %s", ack,
@@ -2259,6 +2263,9 @@ static void jlink_swd_queue_cmd(uint8_t cmd, uint32_t *dst, uint32_t data, uint3
 
        jlink_queue_data_out(data_parity_trn, 32 + 1);
    }
+   pending_scan_results_buffer[pending_scan_results_length].no_ack =
+           (0 == ((cmd ^ swd_cmd(false, false, DP_TARGETSEL)) &
+                         (SWD_CMD_APnDP|SWD_CMD_RnW|SWD_CMD_A32)));
 
    pending_scan_results_length++;

Checkに引っかかっておりマージされないらしい。

パッチの適用

ローカルのソースコードにこれらの変更を適用してみる。手元ではとりあえず手で修正した。

$ make -j $(nproc)

ビルドが通った。

OpenOCDの実行(成功)

再度実行してみる

$ cd ${PICO_SDK_PATH}/../openocd
$ src/openocd -f pico-jlink.cfg -s tcl
Open On-Chip Debugger 0.10.0+dev-geb22ace-dirty (2021-02-24-18:16)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : Hardware thread awareness created
Info : Hardware thread awareness created
Info : RP2040 Flash Bank Command
DEPRECATED! use 'adapter speed' not 'adapter_khz'
adapter speed: 4000 kHz

Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : J-Link V10 compiled Oct 22 2019 16:28:15
Info : Hardware version: 10.10
Info : VTarget = 4.825 V
Info : clock speed 4000 kHz
Info : SWD DPIDR 0x0bc12477
Info : SWD DLPIDR 0x00000001
Info : SWD DPIDR 0x0bc12477
Info : SWD DLPIDR 0x10000001
Info : rp2040.core0: hardware has 4 breakpoints, 2 watchpoints
Info : rp2040.core1: hardware has 4 breakpoints, 2 watchpoints
Info : starting gdb server for rp2040.core0 on 3333
Info : Listening on port 3333 for gdb connections

問題なく起動した。

blinkを実行

pico-examplesのblinkを実行してみる。

OpenOCDとは別のターミナルで下記を実行する。

$ cd ${PICO_EXAMPLES_PATH}/build/blink
$ gdb-multiarch blink.elf
(gdb) target remote :3333
(gdb) mon reset init
(gdb) load
(gdb) c

LEDがチカチカした。

f:id:mickey_happygolucky:20210224193102g:plain
Lチカ

まとめ

J-Link対応のためのPRは既に出されているが 2021/2/24時点ではマージされていない。

修正内容自体は正しそうなので、自分でこれらの修正を取り込むか、PR用のブランチを使用すればJ-Link自体は使用できそう。

CIがしくじっているだけのような気もするので、もうすぐ取り込まれるかも。