UltraZed 向け Debian GNU/Linux (v2017.3版) の構築(Linux Kernel編)

## はじめに

UltraZed-EG スターターキットに Debian GNU/Linux (v2017.3版) を構築する方法について、具体的な方法をいくつかに分けて説明します。

 * [Introduction](https://qiita.com/ikwzm/items/a4ebcffdd26ab3e8124d)
 * [Boot Loader](https://qiita.com/ikwzm/items/9d2a76f9665759df58e7)
   - [Sample FPGA Design](https://qiita.com/ikwzm/items/61316ecec2fc68be4c96)
   - [FSBL   (First Stage Boot Loader)](https://qiita.com/ikwzm/items/6c345b0ef7fc397d37bf)
   - [PMUFW  (Platform Manager Unit Firmware)](https://qiita.com/ikwzm/items/9835f591a78e83d6b92b)
   - [BL31   (ARM Trusted Firmware Boot Loader state 3-1)](https://qiita.com/ikwzm/items/6d03b7e5b47721df3ae9)
   - [U-Boot](https://qiita.com/ikwzm/items/27e6ab1517d724b7f2eb)
   - [BOOT.BIN](https://qiita.com/ikwzm/items/45520c8722edbfcd3575)
 * __Linux Kernel(この記事)__
 * [Debian9 Root File System](https://qiita.com/ikwzm/items/a0e88798ed29b30fa003)

この記事では、ZynqMP の Linux Kernel について説明をします。

## 注意

Linux Kernel では 起動時に ATF(ARM Trusted Firmware) のバージョンをチェックしています。Debian GNU/Linux (v2017.3版) で構築する Linux Kernel 4.9.0 (xilinx-v2017.3) では ATF(ARM Trusted Firmware) のバージョンは v0.3 である必要があります。これ以外のバージョンでは Kernel が Panic を起こして起動出来ないので注意してください。

## Linux Kernel の構築

### 必要な環境

* gcc-aarch64-linux-gnu

### Linux Kernel 構築環境の準備

次の URL から git clone でリポジトリをダウンロードして v2017.3.0 をチェックアウトします。

 * [https://github.com/ikwzm/ZynqMP-FPGA-Linux](https://github.com/ikwzm/ZynqMP-FPGA-Linux)

```console
shell$ git clone git://github.com/ikwzm/ZynqMP-FPGA-Linux
shell$ cd ZynqMP-FPGA-Linux
shell$ git checkout v2017.3.0
```

### ソースコードのダウンロード

Linux Kernel のソースコードを以下の URL からダウンロードします。今回は Linux のメインラインからではなく、Xilinx が提供してる linux-xlnx を使います。

* [https://github.com/Xilinx/linux-xlnx.git](https://github.com/Xilinx/linux-xlnx.git)

```
shell$ git clone https://github.com/Xilinx/linux-xlnx.git linux-xlnx-v2017.3-zynqmp-fpga
```

### xilinx-v2017.3 をチェックアウト

linux-xlnx の xilinx-v2017.3 というタグをチェックアウトして linux-xlnx-v2017.3-zynqmp-fpga という作業用のブランチを作ります。

```
shell$ cd linux-xlnx-v2017.3-zynqmp-fpga
shell$ git checkout -b linux-xlnx-v2017.3-zynqmp-fpga refs/tags/xilinx-v2017.3
```

### ZynqMP 用の各種ファイルを追加

ZynqMP 用のファイルを追加してコミットします。追加するファイルの詳細は後述します。

```
shell$ patch -p0 < ../files/linux-xlnx-v2017.3-zynqmp-fpga.diff
shell$ git add --update
shell$ git add arch/arm64/boot/dts/xilinx/zynqmp-uz3eg-iocc.dts
shell$ git commit -m "[patch] for linux-xlnx-v2017.3-zynqmp-fpga."
```

### Debian Package を作る際の問題を修正

チェックアウトした xilinx-v2017.3 には、Debain のパッケージを作る際に arm64 用ではなくクロスコンパイルを実行したプラットフォーム用のパッケージが出来てしまう問題があります。そのための修正を追加します。この件の詳細は後述します。

```
shell$ patch -p0 < ../files/linux-xlnx-v2017.3-builddeb.diff
shell$ git add --update
shell$ git commit -m "[fix] build wrong architecture debian package when ARCH=arm64 and cross compile."
shell$ patch -p0 < ../files/linux-xlnx-v2017.3-builddeb2.diff
shell$ git add --update
shell$ git commit -m "[update] scripts/package/builddeb to add tools/include and postinst script to header package"
```

### タグとバージョン番号の設定

```
shell$ git tag -a xilinx-v2017.3-zynqmp-fpga -m "release xilinx-v2017.3-zynqmp-fpga"
shell$ echo 1 > .version
```

### 構築の準備

環境変数を設定します。アーキテクチャ(ARCH)に arm64 を指定します。ここでは、クロスコンパイルに使うツールチェーン(CROSS_COMPILE) に aarch64-linux-gnu-を指定します。この変数は構築する環境にあわせてください。その後、ZynqMP 用のコンフィギュレーション定義ファイルを使って構築の準備をします。

````
shell$ cd linux-xlnx-v2017.3-zynqmp-fpga
shell$ export ARCH=arm64
shell$ export CROSS_COMPILE=aarch64-linux-gnu-
shell$ make xilinx_zynqmp_defconfig
````

### カーネルと Debian パッケージの構築

````
shell$ export DTC_FLAGS=--symbols
shell$ make deb-pkg
````

### 構築したカーネルのイメージとデバイスツリーを target/UltraZed-EG-IOCC/boot にコピー

無事にカーネルのイメージ(arch/arm64/boot/Image) と UltraZed-EG-IOCC 用のデバイスツリー(arch/arm64/boot/dts/xilinx/zynqmp-uz3eg-iocc.dtb) が出来たら、それを target/UltraZed-EG-IOCC/build にコピーします。その際、作ったカーネルのバージョン等をわかりやすくするため名前を変更しておきます。
また、dtc(Device Tree Compiler) を使って、デバイスツリーのバイナリファイル (devicetree-4.9.0-xlnx-v2017.3-fpga-zynqmp-uz3eg-iocc.dtb) をデバイスツリーのソースファイル (devicetree-4.9.0-xlnx-v2017.3-fpga-zynqmp-uz3eg-iocc.dts) に戻しておきます。何かデバイスツリーを変更したい時は、このデバイスツリーソースファイルを修正して dtc でデバイスツリーバイナリファイルにコンパイルしてください。
なお、[「デバイスツリーにシンボル情報を埋め込む」](https://qiita.com/ikwzm/items/03d518b7c46d1ee49943)で説明したように、古いバージョンの dtc では --symbols オプションをサポートしていない場合があります。その場合は Linux Kernel をビルドした時にホスト用に生成された dtc (scripts/dtc/dtc) を使ってコンパイルすると良いでしょう。


```
shell$ cp arch/arm64/boot/Image ../target/UltraZed-EG-IOCC/boot/image-4.9.0-xlnx-v2017.3-fpga
shell$ cp arch/arm64/boot/dts/xilinx/zynqmp-uz3eg-iocc.dtb ../target/UltraZed-EG-IOCC/boot/devicetree-4.9.0-xlnx-v2017.3-fpga-zynqmp-uz3eg-iocc.dtb
shell$ ./scripts/dtc/dtc -I dtb -O dts --symbols -o ../target/UltraZed-EG-IOCC/boot/devicetree-4.9.0-xlnx-v2017.3-fpga-zynqmp-uz3eg-iocc.dts ../target/UltraZed-EG-IOCC/boot/devicetree-4.9.0-xlnx-v2017.3-fpga-zynqmp-uz3eg-iocc.dtb
```

## Linux Kernel の起動

### uEnv.txt

[「UltraZed 向け Debian GNU/Linux (v2017.3版) の構築(U-Boot編)」](https://qiita.com/ikwzm/items/27e6ab1517d724b7f2eb) で構築した U-Boot は、起動の際、ストレージに uEnv.txt があればそれを読み込みます。そして ```uenvcmd``` 変数が定義されていれば、その変数で定義されている内容を実行します。そこで uEnv.txt に、この記事で構築したカーネルのイメージとデバイスツリーを指定して実行するように記述します。


```text:target/UltraZed-EG-IOCC/boot/uEnv.txt
linux_img_load_cmd=fatload mmc 1 $kernel_addr image-4.9.0-xlnx-v2017.3-fpga
linux_dtb_load_cmd=fatload mmc 1 $fdt_addr    devicetree-4.9.0-xlnx-v2017.3-fpga-zynqmp-uz3eg-iocc.dtb
linux_set_boot_cmd=setenv bootargs console=ttyPS0,115200 root=/dev/mmcblk1p2 rw rootwait uio_pdrv_genirq.of_id=generic-uio
linux_boot_img_cmd=booti $kernel_addr - $fdt_addr
uenvcmd=run linux_img_load_cmd && run linux_dtb_load_cmd && run linux_set_boot_cmd && run linux_boot_img_cmd
```

 * ```linux_img_load_cmd``` 変数に ```mmc 1``` (SD-Card の FAT file system)から image-4.9.0-xlnx-v2017.3-fpga をメモリ上の ```$kernel_addr``` で指定されたアドレスにロードするコマンドを記述。
 * ```linux_dtb_load_cmd``` 変数に ```mmc 1``` (SD-Card の FAT file system)から devicetree-4.9.0-xlnx-v2017.3-fpga-zynqmp-uz3eg-iocc.dtb をメモリ上の ```$fdt_addr``` で指定されたアドレスにロードするコマンドを記述。
 * ```linux_set_boot_cmd``` 変数に ```bootargs``` 変数を設定するコマンドを記述。
 * ```bootargs``` には root file system として ```/dev/mmcblk1p2``` (SD-Card の第2パーティション) を指定。
 * ```linux_boot_img_cmd``` 変数に ```booti``` コマンドを使ってメモリ上にロードされたカーネルをブートするコマンドを記述。
 * ```uenvcmd``` 変数に上記各種変数をコマンドとして実行するように記述。

Zynq と異なるのは、ブートするカーネルのイメージが Linux kernel ARM boot executable zImage 形式ではなく、arm64 Linux Image 形式であることです。arm64 用に構築した U-Boot には arm64 Linux Image 形式のイメージをブートするためのコマンド ```booti``` があるので、そちらを使います。

## ファイルの説明

ここでは linux-xlnx を ZynqMP 用に構築する際に新たに追加したファイルおよび修正したファイルの説明をします。

### arch/arm64/configs/xilinx_zynqmp_defconfig

ZynqMP 用のコンフィギュレーション定義ファイルです。基本的には linux-xlnx にすでにある xilinx_zynqmp_defconfig と同じですが、Device Tree Overlay を使うために ```CONFIG_OF_OVERLAY=y```を、さらに Device Tree Overlay を Configuration File System から行うために ```CONFIG_OF_CONFIGFS=y``` を新たに追加しています。

### arch/arm64/boot/dts/xilinx/zynqmp-uz3eg-iocc.dts

UltraZed-EG-IOCC 用のデバイスツリーです。実はこれと同じデバイスツリーを U-Boot のビルド時にも流用しています。

```dts:arch/arm64/boot/dts/xilinx/zynqmp-uz3eg-iocc.dts
/*
*/
/dts-v1/;
/include/ "zynqmp.dtsi"
/include/ "zynqmp-clk-ccf.dtsi"
/ {
	model = "ZynqMP UltraZed-EG IO Carrier Card";
	compatible = "xlnx,zynqmp-uz3eg-iocc", "xlnx,zynqmp";

	chosen {
		bootargs = "earlycon clk_ignore_unused";
		stdout-path = "serial0:115200n8";
	};
	aliases {
		ethernet0 = &gem3;
		serial0 = &uart0;
		serial1 = &uart1;
		spi0 = &qspi;
	};
	memory {
		device_type = "memory";
		reg = <0x0 0x0 0x0 0x80000000>;
	};

	cpus {
	};

	amba_pl: amba_pl@0 {
		#address-cells = <2>;
		#size-cells = <2>;
		compatible = "simple-bus";
		ranges ;
	};
};

&lpd_dma_chan1 {
	status = "okay";
};
&lpd_dma_chan2 {
	status = "okay";
};
&lpd_dma_chan3 {
	status = "okay";
};
&lpd_dma_chan4 {
	status = "okay";
};
&lpd_dma_chan5 {
	status = "okay";
};
&lpd_dma_chan6 {
	status = "okay";
};
&lpd_dma_chan7 {
	status = "okay";
};
&lpd_dma_chan8 {
	status = "okay";
};
&xilinx_ams {
	status = "okay";
};
&xlnx_dp {
	phy-names = "dp-phy0","dp-phy1";
	phys = <&lane3 5 0 3 27000000>,
		<&lane2 5 1 3 27000000>;
	status = "okay";
	xlnx,max-lanes = <2>;
};
&xlnx_dpdma {
	status = "okay";
};
&gem3 {
	status = "okay";
	xlnx,ptp-enet-clock = <0x0>;
	phy-mode = "rgmii-id";
	phy-handle = <&phy9>;
	phy9: phy@9 {
		reg = <0x9>;
		ti,rx-internal-delay = <0x8>;
		ti,tx-internal-delay = <0xa>;
		ti,fifo-depth = <0x1>;
		ti,rxctrl-strap-worka;
	};
};
&fpd_dma_chan1 {
	status = "okay";
};
&fpd_dma_chan2 {
	status = "okay";
};
&fpd_dma_chan3 {
	status = "okay";
};
&fpd_dma_chan4 {
	status = "okay";
};
&fpd_dma_chan5 {
	status = "okay";
};
&fpd_dma_chan6 {
	status = "okay";
};
&fpd_dma_chan7 {
	status = "okay";
};
&fpd_dma_chan8 {
	status = "okay";
};
&gpio {
	emio-gpio-width = <32>;
	gpio-mask-high = <0x0>;
	gpio-mask-low = <0x5600>;
	status = "okay";
};
&gpu {
	status = "okay";
};
&i2c1 {
	clock-frequency = <400000>;
	status = "okay";
};
&pinctrl0 {
	status = "okay";
};
&qspi {
	is-dual = <1>;
	num-cs = <1>;
	status = "okay";
	spi-rx-bus-width = <0x4>;
	spi-tx-bus-width = <0x4>;
	flash@0 {
		compatible = "n25q512a", "micron,m25p80";
		spi-tx-bus-width = <0x1>;
		spi-rx-bus-width = <0x4>;
		reg = <0x0>;
		#address-cells = <0x1>;
		#size-cells = <0x1>;
		spi-max-frequency = <0x66ff300>;

		partition@0x00000000 {
			label = "boot";
			reg = <0x0 0x100000>;
		};

		partition@0x00100000 {
			label = "bootenv";
			reg = <0x100000 0x40000>;
		};

		partition@0x00140000 {
			label = "kernel";
			reg = <0x140000 0x1600000>;
		};
	};
};
&rtc {
	status = "okay";
};
&sata {
	ceva,p0-burst-params = "/bits/ 8 <0x13 0x08 0x4A 0x06>";
	ceva,p0-cominit-params = "/bits/ 8 <0x18 0x40 0x18 0x28>";
	ceva,p0-comwake-params = "/bits/ 8 <0x06 0x14 0x08 0x0E>";
	ceva,p0-retry-params = "/bits/ 16 <0x96A4 0x3FFC>";
	status = "okay";
};
&sdhci0 {
	clock-frequency = <199998000>;
	status = "okay";
	xlnx,mio_bank = <0x0>;
	disable-wp;
};
&sdhci1 {
	clock-frequency = <199998000>;
	status = "okay";
	xlnx,mio_bank = <0x1>;
	max-frequency = <50000000>;
	no-1-8-v;
	disable-wp;
};
&serdes {
	status = "okay";
};
&uart0 {
	device_type = "serial";
	port-number = <0>;
	status = "okay";
	u-boot,dm-pre-reloc ;
};
&uart1 {
	device_type = "serial";
	port-number = <1>;
	status = "okay";
	u-boot,dm-pre-reloc ;
};
&dwc3_0 {
	dr_mode = "host";
	status = "okay";
};
&watchdog0 {
	status = "okay";
};
&ams_ps {
	status = "okay";
};
&ams_pl {
	status = "okay";
};
&xilinx_drm {
	status = "okay";
};
&xlnx_dp_sub {
	status = "okay";
};
&xlnx_dp_snd_pcm0 {
	status = "disabled";
};
&xlnx_dp_snd_pcm1 {
	status = "disabled";
};
&xlnx_dp_snd_card {
	status = "disabled";
};
&xlnx_dp_snd_codec0 {
	status = "disabled";
};
&usb0 {
	status = "okay";
};
```

この Device Tree の詳細について以下の節で説明します。

#### sdhci0 および sdhci1 に disable-wp

これは[「UltraZed で SD Card が書き込み不可になってしまう問題」](https://qiita.com/ikwzm/items/179af88949ae1b5d1460) で説明したとおりです。もともとは Avnet のボード設定ファイルに問題があったのですが、もうすでにそのボード設定ファイルを使ったデザインがあり、もしそのデザインを使って FSBL を作ったら SD Card が書き込み不可になってしまってブートに失敗してしまいます。その予防策として、あえて、Device Tree にも disable-wp を追加しています。

```dts:arch/arm64/boot/dts/xilinx/zynqmp-uz3eg-iocc.dts
&sdhci0 {
	clock-frequency = <199998000>;
	status = "okay";
	xlnx,mio_bank = <0x0>;
	disable-wp;
};
&sdhci1 {
	clock-frequency = <199998000>;
	status = "okay";
	xlnx,mio_bank = <0x1>;
	max-frequency = <50000000>;
	no-1-8-v;
	disable-wp;
};
```

#### Ethernet の Phy の物理アドレス

当初、Phy の物理アドレスは[「UltraZed™-EG SOM Hardware User Guide Version 1.1」](http://zedboard.org/sites/default/files/documentations/5264-UG-AES-ZU3EGES-1-SOM-G-v1-1-V1.pdf)の「2.5.1 Ethernet PHY Strapping Resistors」の記述に基づいて ```<0x01>``` を設定していましたが、認識されませんでした。どうやら物理アドレスは ```<0x09>``` が正しいようです。

```dts:arch/arm64/boot/dts/xilinx/zynqmp-uz3eg-iocc.dts
&gem3 {
	status = "okay";
	xlnx,ptp-enet-clock = <0x0>;
	phy-mode = "rgmii-id";
	phy-handle = <&phy9>;
	phy9: phy@9 {
		reg = <0x9>;
		ti,rx-internal-delay = <0x8>;
		ti,tx-internal-delay = <0xa>;
		ti,fifo-depth = <0x1>;
		ti,rxctrl-strap-worka;
	};
};
```

#### サウンド関係をディセーブル

私が構築した環境では、何故かサウンド関係(xlnx_dp_snd_pcm0、xlnx_dp_snd_pcm1、xlnx_dp_snd_card、xlnx_dp_snd_codec0)を有効にすると、カーネルのブート時に PLL 関連で カーネルがパニックしました。問題の原因はまだ不明ですが、とりあえずサウンド関係は使わないだろうと判断してディセーブルにしています。

```dts:arch/arm64/boot/dts/xilinx/zynqmp-uz3eg-iocc.dts
&xlnx_dp_snd_pcm0 {
	status = "disabled";
};
&xlnx_dp_snd_pcm1 {
	status = "disabled";
};
&xlnx_dp_snd_card {
	status = "disabled";
};
&xlnx_dp_snd_codec0 {
	status = "disabled";
};
```

### scripts/package/builddeb

#### arm64 用のパッケージが出来ない問題

これは Debian のパッケージを作るスクリプトです。チェックアウトした xilinx-v2017.3 にはこのスクリプトファイルに問題があって、Debain のパッケージを作る際に arm64 用ではなくクロスコンパイルを実行したプラットフォーム用のパッケージが出来てしまいます。

具体的には次のような箇所が問題です。

```sh:scripts/package/builddeb
set_debarch() {
	# Attempt to find the correct Debian architecture
	case "$UTS_MACHINE" in
	:
	(中略)
	:
	arm64)
		debarch=arm64 ;;
	:
	(中略)
	:
	*)
		debarch=$(dpkg --print-architecture)
		echo "" >&2
		echo "** ** **  WARNING  ** ** **" >&2
		echo "" >&2
		echo "Your architecture doesn't have it's equivalent" >&2
		echo "Debian userspace architecture defined!" >&2
		echo "Falling back to using your current userspace instead!" >&2
		echo "Please add support for $UTS_MACHINE to ${0} ..." >&2
		echo "" >&2
	esac
```

```$UTS_MACHINE```に定義されたアーキテクチャに基づいて Debian パッケージを作るのですが、ZynqMP の場合、arch/arm64/Makefile によると、```$UTS_MACHINE```は arm64 ではなく aarch64 になります。

したがって scripts/package/builddeb を次のように修正する必要があります。


```sh:scripts/package/builddeb
	aarch64*|arm64*)
		debarch=arm64 ;;
```

このパッチを当てる差分ファイルが files/linux-xlnx-v2017.3-builddeb.diff です。

<details><summary>files/linux-xlnx-v2017.3-builddeb.diff</summary><div>

```diff:files/linux-xlnx-v2017.3-builddeb.diff
diff --git scripts/package/builddeb scripts/package/builddeb
index 8ea9fd2..11d61a2 100755
--- scripts/package/builddeb
+++ scripts/package/builddeb
@@ -51,7 +51,7 @@ set_debarch() {
 		debarch=hppa ;;
 	mips*)
 		debarch=mips$(grep -q CPU_LITTLE_ENDIAN=y $KCONFIG_CONFIG && echo el || true) ;;
-	arm64)
+	aarch64*|arm64*)
 		debarch=arm64 ;;
 	arm*)
 		if grep -q CONFIG_AEABI=y $KCONFIG_CONFIG; then
```

</div></details>

ちなみに、メインラインの方の Linux Kernel にはこの修正がすでに入っています。そのうち linux-xlnx にも反映されると思います。

#### ヘッダーパッケージにホスト用の実行ファイルが含まれてしまう問題

詳細は以下の記事を参照してください。

 * [「クロスコンパイルした Debian GNU/Linux でデバイスドライバをセルフコンパイルする際の注意点」@Qiita](https://qiita.com/ikwzm/items/e31ecb099d0d50cb027c)

このパッチを当てる差分ファイルが files/linux-xlnx-v2017.3-builddeb2.diff です。

<details><summary>files/linux-xlnx-v2017.3-builddeb2.diff</summary><div>

```diff:files/linux-xlnx-v2017.3-builddeb2.diff
diff --git scripts/package/builddeb scripts/package/builddeb
index 11d61a2..c52aa28 100755
--- scripts/package/builddeb
+++ scripts/package/builddeb
@@ -325,7 +325,7 @@ fi
 
 # Build kernel header package
 (cd $srctree; find . -name Makefile\* -o -name Kconfig\* -o -name \*.pl) > "$objtree/debian/hdrsrcfiles"
-(cd $srctree; find arch/*/include include scripts -type f) >> "$objtree/debian/hdrsrcfiles"
+(cd $srctree; find arch/*/include include tools/include scripts -type f) >> "$objtree/debian/hdrsrcfiles"
 (cd $srctree; find arch/$SRCARCH -name module.lds -o -name Kbuild.platforms -o -name Platform) >> "$objtree/debian/hdrsrcfiles"
 (cd $srctree; find $(find arch/$SRCARCH -name include -o -name scripts -type d) -type f) >> "$objtree/debian/hdrsrcfiles"
 if grep -q '^CONFIG_STACK_VALIDATION=y' $KCONFIG_CONFIG ; then
@@ -342,6 +342,15 @@ mkdir -p "$destdir"
 (cd $objtree; cp $KCONFIG_CONFIG $destdir/.config) # copy .config manually to be where it's expected to be
 ln -sf "/usr/src/linux-headers-$version" "$kernel_headers_dir/lib/modules/$version/build"
 rm -f "$objtree/debian/hdrsrcfiles" "$objtree/debian/hdrobjfiles"
+mkdir -m 755 -p "$kernel_headers_dir/DEBIAN"
+cat <<EOF >> $kernel_headers_dir/DEBIAN/postinst
+#!/bin/sh -e
+
+make -C /usr/src/linux-headers-$version scripts
+
+EOF
+
+chmod 755 $kernel_headers_dir/DEBIAN/postinst
 
 cat <<EOF >> debian/control
 
```

</div></detalis>


## 参考

 * [「UltraZed で SD Card が書き込み不可になってしまう問題」@Qiita](https://qiita.com/ikwzm/items/179af88949ae1b5d1460)
 * [「UltraZed™-EG SOM Hardware User Guide Version 1.1」](http://zedboard.org/sites/default/files/documentations/5264-UG-AES-ZU3EGES-1-SOM-G-v1-1-V1.pdf)
 * [「クロスコンパイルした Debian GNU/Linux でデバイスドライバをセルフコンパイルする際の注意点」@Qiita](https://qiita.com/ikwzm/items/e31ecb099d0d50cb027c)