Zephyrのセキュア通信を試す
Wind River
コラム
Zephyrとは
Zephyrは、もとはウインドリバー・システムズによって開発されたIoT向けのRTOS(リアルタイムオペレーティングシステム)です。
現在はLinux Foundationによってオープンソースで開発が進められています。
軽量で、x86、arm、RISC-Vなどのさまざまなアーキテクチャに対応し、リアルタイム機能を持ち、さまざまなプロトコルスタックを実装し、セキュリティを重視していることなどを特長とします。
本コラムでは、ZYNQ-7000 ZC702評価ボードにZephyrを移植し、セキュアなプロトコル(TLS)を使って外部に接続するまでの手順を説明します。
開発環境
| システム構成要素 | 詳細 |
| ホストOS | Ubuntu 23.10 |
| ターゲットOS | Zephyr v4.1.0-4540-gf77e258cb9c2 |
| ターゲットボード | ZYNQ-7000 EPP ZC702 Evalution Kit REV 1.1 |
Zephyrのビルド
公式のガイドを参考にしながら、ZC702向けのZephyrをビルドします。
- https://docs.zephyrproject.org/latest/develop/getting_started/index.html
- https://docs.zephyrproject.org/latest/hardware/porting/board_porting.html
ホストOSのアップデート
以下のコマンドでホストOSをアップデートします。
$ sudo apt update -y$ sudo apt upgrade -y
依存関係のインストール
以下のコマンドで必要なパッケージをホストOSにインストールします。
$ sudo apt install --no-install-recommends -y git cmake ninja-build gperf \ccache dfu-util device-tree-compiler wget \python3-dev python3-pip python3-setuptools python3-tk python3-wheel xz-utils file \make gcc gcc-multilib g++-multilib libsdl2-dev libmagic1$ cmake --version$ python3 --version$ dtc --version
Zephyrの入手とPython依存関係のインストール
以下のコマンドでZephyrを入手し、必要なPythonパッケージをインストールします。
$ cd ~$ mkdir zephyrproject$ cd zephyrproject$ sudo apt install python3-venv$ python3 -m venv ~/zephyrproject/.venv$ source ~/zephyrproject/.venv/bin/activate$ pip install west$ west init ~/zephyrproject$ west update$ west zephyr-export$ west packages pip --install
Zephyr SDKのインストール
以下のコマンドでZephyr SDKをインストールします。
Zephyr SDKには、Zephyrのビルドに必要なコンパイラ、アセンブラ、リンカーなどが含まれています。
$ cd ~/zephyrproject/zephyr$ west sdk install
ボードディレクトリの作成
ZCU702向けのボードディレクトリを作成します。
ボードディレクトリとは、各開発ボード向けの設定ファイルをまとめたディレクトリのことです。
今回は、ZCU702と同じSoCを搭載したDigilent Zyboのボードディレクトリをベースにします。
$ mkdir -p boards/xilinx$ cp -r boards/digilent/zybo boards/xilinx/zc702$ cd boards/xilinx/zc702$ mv Kconfig.zybo Kconfig.zc702$ mv zybo.dts zc702.dts$ mv zybo.yaml zc702.yaml$ mv zybo_defconfig zc702_defconfig$ mv zybo-pinctrl.dtsi zc702-pinctrl.dtsi$ find . -type f -exec sed -i 's/digilent/xilinx/g' {} +$ find . -type f -exec sed -i 's/Digilent/Xilinx/g' {} +$ find . -type f -exec sed -i 's/DIGILENT/XILINX/g' {} +$ find . -type f -exec sed -i 's/zybo/zc702/g' {} +$ find . -type f -exec sed -i 's/Zybo/ZC702/g' {} +$ find . -type f -exec sed -i 's/ZYBO/ZC702/g' {} +
DTSの変更
zc702.dtsは、ボードのハードウェア構成を定義したファイルです。
テキストエディタでLEDとUARTの設定をZC702向けに変更します。
変更前 :
leds {compatible = "gpio-leds";ld_mio: led_mio {gpios = <&psgpio_bank0 7 GPIO_ACTIVE_HIGH>;label = "LD_MIO";};};
&uart1 {status = "okay";current-speed = <115200>;clock-frequency = <100000000>;pinctrl-0 = <&pinctrl_uart1_default>;pinctrl-names = "default";};
変更後 :
leds {compatible = "gpio-leds";ld_mio: led_mio {gpios = <&psgpio_bank0 10 GPIO_ACTIVE_HIGH>; /* <- ここを変えた */label = "LD_MIO";};};
&uart1 {status = "okay";current-speed = <115200>;clock-frequency = <50000000>; /* <- ここを変えた */pinctrl-0 = <&pinctrl_uart1_default>;pinctrl-names = "default";};
サンプルのビルド
以下のコマンドでサンプルアプリケーションをビルドします。
$ cd ~/zephyrproject/zephyr$ west build -p always -b zc702 samples/hello_world
~/zephyrproject/zephyr/build/zephyr/以下にzephyr.binが作成されたことを確認します。
これがZephyrアプリケーションのバイナリになります。
$ ls ~/zephyrproject/zephyr/build/zephyr/
U-Bootの準備
Zynq-7000ではZephyrアプリケーションを実行する前にSoCの初期化をする必要があります。
ですので、SoCの初期化を行うプログラムを準備します。
今回は以下のXilinxのWikiにあるビルド済みのU-bootを使います。
$ cd ~$ wget https://xilinx-wiki.atlassian.net/wiki/download/attachments/18841732/zynq-ubuntu-core-12.10-core-armhf-boot.tar.xz$ tar Jxfv zynq-ubuntu-core-12.10-core-armhf-boot.tar.xz
展開したファイルにboot.binがあることを確認します。
これが最初にロードされるバイナリになります。
$ ls boot
SDカードの準備
SDカードをFAT32形式でフォーマットし、以下の準備したファイルを書き込みます。
boot.binzephyr.bin
Zephyrの起動
ボードのスイッチを操作してSDブートするようにし、ボードにSDカードをセットし、ボードの電源を入れます。
U-bootが起動したらオートブートを中断して、以下のコマンドを入力してZephyrアプリケーションのロードと実行を行います。
zynq-uboot> fatload mmc 0 0x0 zephyr.binzynq-uboot> go 0x0
Zephyrアプリケーションが以下のメッセージを出力することを確認します。
*** Booting Zephyr OS build v4.1.0-5075-gcf6170cbe605 ***Hello World! zc702/xc7z010
ZC702にZephyrを移植できました。
Ethernet
続いてEthernetを有効にしていきます。
以下のZYNQのデータシートなどを参考にしながらプロジェクトに変更を加えていきます。
GEMのドライバ (eth_xlnx_gem.c)
Zephyr v4.1.0のGEM(Gigabit Ethernet MAC)のドライバ(eth_xlnx_gem.c)は、そのままではZC702では動かないのでソースコードを変更します。
(1) eth_xlnx_gem_configure_clocks() の分周比計算処理を変更します。
div0 = 8;div1 = 5;
(2) eth_xlnx_gem_configure_clocks() のレジスタ書込の処理を変更します。
// CLK_CTRL READ{mem_addr_t phys_addr = 0xF8000140;mem_addr_t addr = 0;device_map((mm_reg_t *)&addr, phys_addr, 0x100, K_MEM_CACHE_NONE);clk_ctrl_reg = sys_read32(addr);}clk_ctrl_reg &= ~((ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR_MASK <<ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR0_SHIFT) |(ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR_MASK <<ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR1_SHIFT));clk_ctrl_reg |= ((div0 & ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR_MASK) <<ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR0_SHIFT) |((div1 & ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR_MASK) <<ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR1_SHIFT);// SLCR UNLOCK{mem_addr_t phys_addr = 0xF8000008;mem_addr_t addr = 0;device_map((mm_reg_t *)&addr, phys_addr, 0x100, K_MEM_CACHE_NONE);sys_write32(0xDF0D, addr);}// CLK_CTRL WRITE{mem_addr_t phys_addr = 0xF8000140;mem_addr_t addr = 0;device_map((mm_reg_t *)&addr, phys_addr, 0x100, K_MEM_CACHE_NONE);sys_write32(clk_ctrl_reg, addr);}// SLCR LOCK{mem_addr_t phys_addr = 0xF8000004;mem_addr_t addr = 0;device_map((mm_reg_t *)&addr, phys_addr, 0x100, K_MEM_CACHE_NONE);sys_write32(0x767B, addr);}
PHYのドライバ (phy_xlnx_gem.c)
Zephyr v4.1.0には、ZC702に搭載されたPHY(Marvell Alaska 88E1116R)のドライバがありません。
既存のPHYドライバのソースコード(phy_xlnx_gem.c)に88E1116R固有の処理を追加することでこれを補います。
(1) phy_xlnx_gem_supported_devs配列に88E1116Rの要素を追加します。
{.phy_id = 0x01410e40,.phy_id_mask = PHY_MRVL_PHY_ID_MODEL_MASK,.api = &phy_xlnx_gem_marvell_alaska_api,.identifier = "Marvell Alaska 88E1116R"},
(2) phy_xlnx_gem_marvell_alaska_cfg()に88E1116R固有の処理を追加します。
// Change to page 2: Configure RGMII TX/RX delayphy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, PHY_MRVL_COPPER_PAGE_SWITCH_REGISTER, 0x0002);phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,0x15, 0x1070);// Change to page 3: Configure LED control (optional)phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, PHY_MRVL_COPPER_PAGE_SWITCH_REGISTER, 0x0003);phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr,0x10, 0x021e);// Return to default page 0phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, PHY_MRVL_COPPER_PAGE_SWITCH_REGISTER, 0x0000);
DTS
zc702.dtsにGEMの記述を追加します。
&gem0 {status = "okay";clock-frequency = <100000000>;mdc-divider = <XLNX_GEM_MDC_DIVIDER_32>;local-mac-address = [da 13 7a 1f 39 ac];init-mdio-phy;/delete-property/ discard-rx-fcs;/delete-property/ unicast-hash;/delete-property/ full-duplex;// amba-ahb-burst-length = <XLNX_GEM_AMBA_AHB_BURST_INCR4>;};
カーネルコンフィグ
アプリケーションのprj.confにネットワーク(とシェル)の設定を追加します。
CONFIG_ETH_DRIVER=yCONFIG_TEST_RANDOM_GENERATOR=yCONFIG_NETWORKING=yCONFIG_NET_L2_ETHERNET=yCONFIG_NET_IPV4=yCONFIG_NET_IPV6=nCONFIG_NET_ARP=yCONFIG_NET_TCP=yCONFIG_NET_UDP=yCONFIG_NET_DHCPV4=yCONFIG_NET_DHCPV4_OPTION_CALLBACKS=yCONFIG_DNS_RESOLVER=yCONFIG_LOG=yCONFIG_NET_LOG=yCONFIG_SHELL=yCONFIG_NET_SHELL=y
リンクアップ
リビルドしたアプリケーションをロードすると、ドライバが動作してEthernetがリンクアップします。
リンクアップすると以下のようなログが出力されます。
[00:00:03.012,000] <inf> eth_xlnx_gem: ethernet@e000b000 link up, 100 MBit/s
ICMP
Zephyrのシェルを使ってpingを実行できます。
$ net ipv4 gateway 1 192.168.13.1$ net ipv4 add 1 192.168.13.47 255.255.255.0$ net ping 192.168.13.1
実行すると以下のようなログが出力されます。
PING 192.168.13.128 bytes from 192.168.13.1 to 192.168.13.47: icmp_seq=1 ttl=64 time=0 ms28 bytes from 192.168.13.1 to 192.168.13.47: icmp_seq=2 ttl=64 time=0 ms28 bytes from 192.168.13.1 to 192.168.13.47: icmp_seq=3 ttl=64 time=0 ms
DHCP
Zephyrのシェルを使ってDHCPを実行できます。
$ net ipv4 del 1 192.168.13.47$ net dhcpv4 client start 1$ net iface
実行すると以下のようなログが出力されます。
[00:00:45.867,000] <wrn> net_dhcpv4: DHCP server provided more DNS servers than can be saved[00:00:45.867,000] <wrn> net_dhcpv4: DHCP server provided more DNS servers than can be saved[00:00:45.867,000] <inf> net_dhcpv4: Received: 192.168.13.215
Default interface: 1Interface eth0 (0x39560) (Ethernet) [1]===================================Link addr : DA:13:7A:1F:39:ACMTU : 1500Flags : AUTO_START,IPv4Device : ethernet@e000b000 (0x310d4)Status : oper=UP, admin=UP, carrier=ONEthernet capabilities supported:100 MbitsPromiscuous modeEthernet PHY device: <none> (0)IPv4 unicast addresses (max 1):192.168.13.215/255.255.255.0 DHCP preferredIPv4 multicast addresses (max 2):224.0.0.1IPv4 gateway : 192.168.13.1DHCPv4 lease time : 172800DHCPv4 renew time : 86400DHCPv4 server : 192.168.13.1DHCPv4 requested : 192.168.13.215DHCPv4 state : boundDHCPv4 attempts : 1DHCPv4 state : bound
警告が出力されたり、「Ethernet PHY device」が「\<none\> (0)」になったりしていますが、Ethernetが動作するようになりました。
TLS
最後に簡単なTLSクライアントアプリケーションを実装します。
カーネルコンフィグを変更してTLSを有効化し、ユーザーコードを書けばTLSクライアントアプリケーションを実装できます。
カーネルコンフィグ
アプリケーションのprj.confにネットワークの設定を追加します。
これでBSDソケット互換APIが利用できるようになりますが、このAPIは拡張されていて、簡単にTLSを利用できるようになっています。
CONFIG_MAIN_STACK_SIZE=2048CONFIG_POSIX_API=yCONFIG_NET_CONFIG_SETTINGS=yCONFIG_NET_CONFIG_NEED_IPV4=yCONFIG_NET_CONFIG_NEED_IPV6=nCONFIG_NET_CONFIG_MY_IPV4_GW="192.168.13.1"CONFIG_NET_SOCKETS_SOCKOPT_TLS=yCONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED=yCONFIG_MBEDTLS=yCONFIG_MBEDTLS_BUILTIN=yCONFIG_MBEDTLS_ENABLE_HEAP=yCONFIG_MBEDTLS_HEAP_SIZE=60000CONFIG_MBEDTLS_SSL_MAX_CONTENT_LEN=2048
アプリケーションソースコード
自己署名証明書を使うサーバーに接続するアプリケーションソースコードを書きます。
#include <stdio.h>#include <zephyr/kernel.h>#include <zephyr/logging/log.h>#include <zephyr/net/socket.h>#include <zephyr/net/tls_credentials.h>LOG_MODULE_REGISTER(simple_tls, LOG_LEVEL_DBG);int main(void){int ok = 0;int socket_ = -1;// ソケットを作成する{socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TLS_1_2);if (socket_ < 0){LOG_ERR("ERROR : socket : %d\n", errno);goto EXIT;}// 自己署名証明書のサーバーに接続するint verify = TLS_PEER_VERIFY_NONE;if (setsockopt(socket_, SOL_TLS, TLS_PEER_VERIFY, &verify, sizeof(verify)) < 0){LOG_ERR("ERROR : setsockopt TLS_PEER_VERIFY : %d\n", errno);goto EXIT;}}// 接続する{struct sockaddr_in server = { 0 };server.sin_family = AF_INET;server.sin_port = htons(12345);inet_pton(AF_INET, "192.168.13.48", &server.sin_addr);if (connect(socket_, (struct sockaddr *)&server, sizeof(server)) < 0){LOG_ERR("ERROR : connect : %d\n", errno);goto EXIT;}}// 送信する{char const * const message = "Hello from client";if (send(socket_, message, strlen(message) + 1, 0) < 0){LOG_ERR("ERROR : send : %d\n", errno);goto EXIT;}}// 受信するwhile (1){char message[128] = { 0 };int const return_value = recv(socket_, message, sizeof(message) - 1, 0);if (return_value < 0){LOG_ERR("ERROR : recv : %d\n", errno);goto EXIT;}if (return_value == 0){break;}else{LOG_INF("%s", message);continue;}}ok = 1;EXIT:// ソケットをクローズするif (socket_ != -1){if (close(socket_) < 0){LOG_ERR("ERROR : close : %d\n", errno);goto EXIT;}}LOG_INF("%s\n", ok ? "OK" : "NG");return 0;}
実行
リビルドしたアプリケーションを実行すると、ユーザーが作成したアプリケーションコードが実行される前に、Zephyrが自動で諸々のネットワークの設定を行ってくれます。
*** Booting Zephyr OS build v4.1.0-5075-gcf6170cbe605 ***[00:00:00.011,000] <wrn> net_sock_tls: No entropy device on the system, TLS communication is insecure![00:00:00.011,000] <inf> net_config: Initializing network[00:00:00.011,000] <inf> net_config: Waiting interface 1 (0x51930) to be up...[00:00:03.012,000] <inf> eth_xlnx_gem: ethernet@e000b000 link up, 100 MBit/s[00:00:03.012,000] <inf> net_config: Interface 1 (0x51930) coming up[00:00:03.012,000] <inf> net_config: Running dhcpv4 client...[00:00:05.013,000] <wrn> net_dhcpv4: DHCP server provided more DNS servers than can be saved[00:00:05.014,000] <wrn> net_dhcpv4: DHCP server provided more DNS servers than can be saved[00:00:05.014,000] <inf> net_dhcpv4: Received: 192.168.13.215[00:00:05.014,000] <inf> net_config: IPv4 address: 192.168.13.215[00:00:05.014,000] <inf> net_config: Lease time: 172800 seconds[00:00:05.014,000] <inf> net_config: Subnet: 255.255.255.0[00:00:05.014,000] <inf> net_config: Router: 192.168.13.1[00:00:05.072,000] <inf> simple_tls: Hello from server[00:00:05.072,000] <inf> simple_tls: OK
ユーザーアプリケーションが実行され、TLSでメッセージの送受信ができました。
最後に
以上になります。
このコラムが、Zephyrの特長や扱い方を理解する助けになりましたら幸いです。
また弊社ではZepher開発・運用支援サービスを提供しておりますので、こちらも参考にしてください。
詳細資料をご希望の方には送付させていただきますので、以下フォームよりメールアドレスをご登録ください。
[2025年06月11日 時点]





