コラム

Column

ご挨拶

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をビルドします。

ホスト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.bin
zephyr.bin

Zephyrの起動

ボードのスイッチを操作してSDブートするようにし、ボードにSDカードをセットし、ボードの電源を入れます。
U-bootが起動したらオートブートを中断して、以下のコマンドを入力してZephyrアプリケーションのロードと実行を行います。
zynq-uboot> fatload mmc 0 0x0 zephyr.bin
zynq-uboot> go 0x0
Zephyrアプリケーションが以下のメッセージを出力することを確認します。
*** Booting Zephyr OS build v4.1.0-5075-gcf6170cbe605 ***
Hello World! zc702/xc7z010
ZC702にZephyrを移植できました。
zephyr_0001

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 delay
    phy_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 0
    phy_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=y
CONFIG_TEST_RANDOM_GENERATOR=y
CONFIG_NETWORKING=y
CONFIG_NET_L2_ETHERNET=y
CONFIG_NET_IPV4=y
CONFIG_NET_IPV6=n
CONFIG_NET_ARP=y
CONFIG_NET_TCP=y
CONFIG_NET_UDP=y
CONFIG_NET_DHCPV4=y
CONFIG_NET_DHCPV4_OPTION_CALLBACKS=y
CONFIG_DNS_RESOLVER=y
CONFIG_LOG=y
CONFIG_NET_LOG=y
CONFIG_SHELL=y
CONFIG_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.1
28 bytes from 192.168.13.1 to 192.168.13.47: icmp_seq=1 ttl=64 time=0 ms
28 bytes from 192.168.13.1 to 192.168.13.47: icmp_seq=2 ttl=64 time=0 ms
28 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: 1
Interface eth0 (0x39560) (Ethernet) [1]
===================================
Link addr : DA:13:7A:1F:39:AC
MTU       : 1500
Flags     : AUTO_START,IPv4
Device    : ethernet@e000b000 (0x310d4)
Status    : oper=UP, admin=UP, carrier=ON
Ethernet capabilities supported:
        100 Mbits
        Promiscuous mode
Ethernet PHY device: <none> (0)
IPv4 unicast addresses (max 1):
        192.168.13.215/255.255.255.0 DHCP preferred
IPv4 multicast addresses (max 2):
        224.0.0.1
IPv4 gateway : 192.168.13.1
DHCPv4 lease time : 172800
DHCPv4 renew time : 86400
DHCPv4 server     : 192.168.13.1
DHCPv4 requested  : 192.168.13.215
DHCPv4 state      : bound
DHCPv4 attempts   : 1
DHCPv4 state      : bound
警告が出力されたり、「Ethernet PHY device」が「\<none\> (0)」になったりしていますが、Ethernetが動作するようになりました。
zephyr_0002

TLS

最後に簡単なTLSクライアントアプリケーションを実装します。
カーネルコンフィグを変更してTLSを有効化し、ユーザーコードを書けばTLSクライアントアプリケーションを実装できます。

カーネルコンフィグ

アプリケーションのprj.confにネットワークの設定を追加します。
これでBSDソケット互換APIが利用できるようになりますが、このAPIは拡張されていて、簡単にTLSを利用できるようになっています。
CONFIG_MAIN_STACK_SIZE=2048
CONFIG_POSIX_API=y
CONFIG_NET_CONFIG_SETTINGS=y
CONFIG_NET_CONFIG_NEED_IPV4=y
CONFIG_NET_CONFIG_NEED_IPV6=n
CONFIG_NET_CONFIG_MY_IPV4_GW="192.168.13.1"
CONFIG_NET_SOCKETS_SOCKOPT_TLS=y
CONFIG_MBEDTLS_KEY_EXCHANGE_PSK_ENABLED=y
CONFIG_MBEDTLS=y
CONFIG_MBEDTLS_BUILTIN=y
CONFIG_MBEDTLS_ENABLE_HEAP=y
CONFIG_MBEDTLS_HEAP_SIZE=60000
CONFIG_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_0003

最後に

以上になります。
このコラムが、Zephyrの特長や扱い方を理解する助けになりましたら幸いです。

メールマガジン

「ご登録いただいた内容は、弊社の掲げる「個人情報保護方針」に沿って管理し、本件に関するお問い合わせ、お申込み等いただいた内容への対応のために利用する場合がございます。なお、お客様の同意なく目的外の利用や第三者への開示、提供を行うことはございません。」

CONTACT

まずはお気軽に
お問い合わせください!

PAGE TOP