みつきんのメモ

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

raspberrypi3 yocto2.1

やや時間が経ってしまったがyoctoの2.1がリリースされた。 コードネームは「Krogoth」

RPi3で動作を確認してみる。

作業ディレクトリ

今回は「~/work/yocto/rpi」で作業を行う。 また、ソースコードなどダウンロードしたデータを格納するディレクトリを「~/work/yocto/downloads」とする。

Krogothブランチの取得

下記のコマンドでpokyとmeta-raspberrypiを取得し、krogothブランチをチェックアウトする。

$ git clone git://git.yoctoproject.org/poky.git
$ cd poky
$ git checkout -b krogoth origin/krogoth
$ git clone git://git.yoctoproject.org/meta-raspberrypi

meta-raspberrypiはまだkrogothブランチが切られていないようなので、masterブランチを使用する。

環境変数を設定

下記を実行してbitbakeの実行に必要な環境変数を設定する。

$ cd ~/work/yocto/rpi
$ source poky/oe-init-build-env build

このコマンドを実行すると、buildディレクトリへ自動的に移動される。

confディレクトリ以下に、local.confとbblayers.confが生成される。

bblayers.conf

bblayers.confにmeta-raspberrypiの行を追加し、bitbakeのビルド対象に含める。 他のレイヤをビルド対象に追加する場合も、ここに追加する。

BBLAYERS ?= " \
  /home/mickey/work/yocto/rpi/poky/meta \
  /home/mickey/work/yocto/rpi/poky/meta-poky \
  /home/mickey/work/yocto/rpi/poky/meta-yocto-bsp \
  /home/mickey/work/yocto/rpi/poky/meta-raspberrypi \
  "

また、フルパス表記なので、「/home/mickey」の部分は適宜自分の環境に合わせる必要がある。

local.conf

RPi3向けのイメージをビルドするための設定をlocal.confの上の方に下記のように追加する。

MACHINE ?= "raspberrypi3"
BB_NUMBER_THREADS = "6"
PARALLEL_MAKE = "-j 6"
GPU_MEM = "128"
DL_DIR ?= "${HOME}/work/yocto/downloads"

イメージ作成

bitbakeを実行してイメージを作成する。

$ bitbake rpi-basic-image

イメージの書き込み

meta-raspberrypiではrpi-sdimgとしてSDカードのイメージが生成されるので、これをddコマンドで書き込む。

$ sudo dd if=./tmp/deploy/images/raspberrypi3/rpi-basic-image-raspberrypi3.rpi-sdimg of=/dev/sdb bs=40M

/dev/sdbは環境によってはsdbだったりsdcだったりするので適宜変更する。 内蔵HDDの追加など行ってなければ大抵はsdbとなる。

動作確認

動作した!

f:id:mickey_happygolucky:20160422004541j:plain

ただし起動時間がものすごい長い。不安になるほど長かった。

起動時間の対処

ddで書き込んだあとのSDのraspberrypiパーティションにある「cmdline.txt」を下記のように変更する。

-dwc_otg.lpm_enable=0 console=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait debug 
+dwc_otg.lpm_enable=0 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait debug 

「console=ttyAMA0,115200」の部分を削除する。 コンソールの出力先が変わるため、カーネルの出力なども表示されるようになってしまうが、起動時間は劇的に改善する。

pokyでWindowsの共有フォルダをマウントできない

pokyでWindowsの共有フォルダをマウントできないケースがあった。 同じフォルダをUbuntuマシンからmountすると成功するためWindows側の設定は問題なさそう。

例えば下記のような感じだ。

mount -t cifs -o username=mickey //192.168.21.XX/Music /mnt/music
Password for mickey@//192.168.21.XX/Music:  *****
mount error(13): Permission denied
Refer to the mount.cifs(8) manual page (e.g. man mount.cifs)

ユーザーもパスワード正しいのにPermission Deniedでエラーになる。

原因がわかったのでメモ。

犯人はpam

local.confで下記を追加してbitbakeしなおしたイメージからmountを行うと無事マウントできた。

DISTRO_FEATURES_append = " pam"

(こんどこそ)犯人はsec=ntlm

$ mount -t cifs -ouser=mickey,sec=ntlm //192.168.XX.XX/Share /mnt/nas

ここに答えがあった。

ユーザーもパスワードもあっているのに、何故かCIFSのマウントに失敗する場合は、これを疑ってみると良いかもしれない。

raspberrypi3のオンボードのbluetoothをRaspbian以外のOSで使用する方法

poky(yocto)などのRaspbian以外のOSでRPi3のオンボードbluetoothを使用したいと思うケースは多いと思う。 しかし、現時点(20160329)でこれらのOSではおそらくbluetoothはそのままでは機能しない。

(少なくともmeta-raspberrypiの)カーネルからデバイスが見えないためだ。

今回はRaspbianの中身を覗き、何が必要かを解析したのでメモ。

ライセンスが不明瞭なので、商用利用や作成したイメージの二次配布などの際には気をつけること。

bleutoothが動作するまでに必要なこと

  • cmdline.txtからttyAMA0を削除
  • serial-getty@ttyAMA0.serviceのマスク
  • pi-bluetoothに同梱されているhciuart.serviceのインストール
  • bluez-firmwareに同梱されているファームウェア(BCM43430A1.hcd)のインストール
  • bluez5へのパッチ適用

cmdline.txtの編集

RPiでUARTを使用するためにはttyAMA0をconsole指定からはずさなければならない。 しかし、systemd環境では少し注意する必要がある。

SDのBootパーティションにある、cmdline.txtを下記の様に編集する。

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 fsck.repair=yes rootwait

もしdebugの定義があるなら外したほうが無難かもしれない。

serial-getty@ttyAMA0.serviceのマスク

これをしないと、hciattachが見かけ上成功しても、実際にはデバイスにアクセスできない。

$ systemctl mask serial-getty@ttyAMA0.service

pi-bluetoothに同梱されているhciuart.serviceのインストール

pi-bluetoothで必要なファイルは「/lib/systemd/system/hciuart.service」

「/usr/share/doc/pi-bluetooth/copyright」によると、このライセンスはBSD-3-Clauseなので、二次使用は特に問題なさそう。

[Unit]
Description=Configure Bluetooth Modems connected by UART
ConditionPathIsDirectory=/proc/device-tree/soc/gpio@7e200000/bt_pins
Before=bluetooth.service
After=dev-ttyAMA0.device

[Service]
Type=forking
ExecStart=/usr/bin/hciattach /dev/ttyAMA0 bcm43xx 921600 noflow -

[Install]
WantedBy=multi-user.target

主な処理としては、hciattachを実行する。 これと同等の処理があれば、このファイルそのものはなくても良い。

bluez-firmwareに同梱されているファームウェア(BCM43430A1.hcd)のインストール

bluez-firmwareの中で必要なファイルは「/lib/firmware/BCM43430A1.hcd」 このライセンスはプロプライエタリなもののようだが、取り扱いについてはここで議論されている。

BCM43430A1.hcdはバイナリの為、自分でコンパイルすることができないので、基本的にはRaspbianのイメージからコピーしてくるしか無さそう。

こちらはハッシュも一致している。 おそらくRaspbianから抜き出したものだろう。 ライセンスについて明示したファイルは見当たらないが。。。

これを利用するには下記のようにする。

$ wget https://github.com/OpenELEC/misc-firmware/raw/master/firmware/brcm/BCM43430A1.hcd

先述の通り、これはファイルそのものが必要となる。

bluez5へのパッチの適用

raspbianのbluez5のパッケージは下記の4つのパッチが適用されている。 upstreamからソースを取得する場合や他のディストリビューションのパッケージを使用する場合は、 これらの修正を適用する必要がある。

  • 0050-bcm43xx-Add-bcm43xx-3wire-variant.patch
  • 0051-bcm43xx-The-UART-speed-must-be-reset-after-the-firmw.patch
  • 0052-Increase-firmware-load-timeout-to-30s.patch
  • 0053-Move-43xx-firmware-to-lib-firmware.patch

このパッチがそのまま当たらない場合は、upstreamの5.37に適用できる形してくれているものがここにあるので参考にすると良い。

bluetooth起動手順

上記をすべて適用した環境で次の手順を実行すると、bleutoothが使用できるようになる。

$ systemctl start hciuart
$ systemctl start bluetooth

この時点でrfkill listを実行してhci0が見えれば成功。

$ rfkill list
rfkill list
0: phy0: wlan
    Soft blocked: yes
    Hard blocked: no
1: hci0: bluetooth
    Soft blocked: yes
    Hard blocked: no

blockされている場合は下記のようにしてunblockする。

$ rfkill unblock 1

トラブルシューティング

いくつかハマった点についてメモしておく。

controllerのBDアドレスがすべてAAになる

BCM43430A1.hcdが/lib/firmwareにない場合は、下記のように表示される。

$ bluetoothctl
[NEW] Controller AA:AA:AA:AA:AA:AA raspberrypi3 [default]

BDアドレスが全てAAになった場合は、BCM43430A1.hcdの置き場所を確認すると良い。

bluetoothctlが無反応

bluetoothctl起動後に何を入力しても反応しない場合は下記の2点が考えられる。

  1. bluetoothdが起動していない。
  2. 権限が足りない。

前者の場合はsystemctl start bluetoothでbluetoothdを起動すれば良い。

後者の場合はsuやsudoでスーパーユーザー権限で実行すれば良い。

yocto master(20160323時点)でsystemd-networkdでエラー

先日、jethroでsystemd-networkdを有効化する方法を紹介したが、このままmasterに適用しても、systemd-networkdの起動に失敗する。

$ systemctl status systemd-networkd
* systemd-networkd.service - Network Service
   Loaded: loaded (/lib/systemd/system/systemd-networkd.service; enabled; vendor preset: enabled)
   Active: inactive (dead) (Result: exit-code) since Tue 2016-03-22 06:21:40 UTC; 2min 22s ago
     Docs: man:systemd-networkd.service(8)
  Process: 176 ExecStart=/lib/systemd/systemd-networkd (code=exited, status=1/FAILURE)
 Main PID: 176 (code=exited, status=1/FAILURE)
   Status: "Shutting down..."

Mar 22 06:21:40 raspberrypi3 systemd[1]: systemd-networkd.service: About to execute: /lib/systemd/systemd-networkd
Mar 22 06:21:40 raspberrypi3 systemd[1]: Starting Network Service...
Mar 22 06:21:40 raspberrypi3 systemd[1]: systemd-networkd.service: Main process exited, code=exited, status=1/FAILURE
Mar 22 06:21:40 raspberrypi3 systemd[1]: Failed to start Network Service.
Mar 22 06:21:40 raspberrypi3 systemd[1]: systemd-networkd.service: Unit entered failed state.
Mar 22 06:21:40 raspberrypi3 systemd[1]: systemd-networkd.service: Failed with result 'exit-code'.
Mar 22 06:21:40 raspberrypi3 systemd[1]: systemd-networkd.service: Service has no hold-off time, scheduling restart.
Mar 22 06:21:40 raspberrypi3 systemd[1]: Stopped Network Service.
Mar 22 06:21:40 raspberrypi3 systemd[1]: systemd-networkd.service: Start request repeated too quickly.
Mar 22 06:21:40 raspberrypi3 systemd[1]: Failed to start Network Service.

status=1/FAILUREとなっており、起動に失敗している事がわかる。 ifconfigしてみても、loしかいない。

$ ifconfig
lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1%1996203728/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

systemd-networkdをstartしてみる

試しにsystemd-networkdを手動で開始してみる。

$ systemctl start systemd-networkd.service
Job for systemd-networkd.service failed because the control process exited with error code.
See "systemctl status systemd-networkd.service" and "journalct -xe" for details.

詳細な情報を得るには、「systemctl status」や「journelctl -xe」を実行するようにアドバイスされるので、 journalctlを実行してみる。

$ journalctl -xe -u systemd-networkd.service
-- Logs begin at Tue 2016-03-22 06:21:34 UTC, end at Tue 2016-03-22 06:30:53 UTC. --
Mar 22 06:21:38 raspberrypi3 systemd[1]: systemd-networkd.service: Changed dead -> start
Mar 22 06:21:38 raspberrypi3 systemd[1]: systemd-networkd.service: Got notification message from PID 151 (STOPPING=1, STATUS=Shutting down...)
Mar 22 06:21:38 raspberrypi3 systemd[1]: systemd-networkd.service: Child 151 belongs to systemd-networkd.service
Mar 22 06:21:38 raspberrypi3 systemd[1]: systemd-networkd.service: Main process exited, code=exited, status=1/FAILURE
Mar 22 06:21:38 raspberrypi3 systemd[1]: systemd-networkd.service: Changed start -> failed
Mar 22 06:21:38 raspberrypi3 systemd[1]: systemd-networkd.service: Job systemd-networkd.service/start finished, result=failed
Mar 22 06:21:38 raspberrypi3 systemd[1]: Failed to start Network Service.
-- Subject: Unit systemd-networkd.service has failed
...(長いので省略)...

気になるログが見つかった。

Mar 22 06:28:07 raspberrypi3 systemd-networkd[199]: Cannot resolve user name systemd-network: No such process

systemdのソースを解析

まずは該当のログを出力している箇所を探す。

$ cd ~/rpi3/build_systemd/tmp/work/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/systemd/1_229+gitAUTOINC+714c62b463-r0/git/src
$ grep -r 'Cannot resolve user name' .
./resolve/resolved.c:                log_error_errno(r, "Cannot resolve user name %s: %m", user);
./network/networkd-netdev-tuntap.c:                        return log_netdev_error_errno(netdev, r, "Cannot resolve user name %s: %m", t->user_name);
./network/networkd.c:                log_error_errno(r, "Cannot resolve user name %s: %m", user);
./timesync/timesyncd.c:                log_error_errno(r, "Cannot resolve user name %s: %m", user);

./network/networkd.cがそれっぽい。

~/rpi3/build_systemd/tmp/work/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/systemd/1_229+gitAUTOINC+714c62b463-r0/git/src/network/networkd.c

int main(int argc, char *argv[]) {
...
        const char *user = "systemd-network";
...
        r = get_user_creds(&user, &uid, &gid, NULL, NULL);
        if (r < 0) {
                log_error_errno(r, "Cannot resolve user name %s: %m", user);
                goto out;
        }
...

systemd-networkというユーザーが見つからない場合は、エラー処理に回るようになっている様子。

ユーザーsystemd-networkを追加

試しに実行環境でsystemd-networkというユーザーを追加してみる。

$ useradd --system systemd-network

再起動後、ifconfigを実行。

$ ifconfig
eth0      Link encap:Ethernet  HWaddr B8:27:EB:BA:FD:E3  
          inet addr:192.168.21.99  Bcast:192.168.21.255  Mask:255.255.255.0
          inet6 addr: fe80::ba27:ebff:feba:fde3%1995753168/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:77 errors:0 dropped:0 overruns:0 frame:0
          TX packets:41 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:10266 (10.0 KiB)  TX bytes:6606 (6.4 KiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1%1995753168/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

eth0がwired.networkの設定になっている。

yoctoでの対処

手っ取り早くlocal.confで対処見てみる。

USERADD_PARAM_pn-systemd += "--system systemd-network"
PACKAGECONFIG_pn-systemd += " networkd"

別途、/etc/systemd/network/wired.networkは作成する必要があるが、これでsystemd-networkdが起動されるようになる。

masterブランチを使えるならばconnmanのほうが便利なので無理にsystemd-networkdを使う必要は無いだろう。

raspberrypi3でyocto

技適取得済みのRPi3が入手できた。

f:id:mickey_happygolucky:20160323001355j:plainf:id:mickey_happygolucky:20160323001404j:plain

箱に技適マークが確認できる。

f:id:mickey_happygolucky:20160323001430j:plain

基板はこんな感じ。

meta-raspberrypiもmasterであればRPi3に対応しているようなので動かしてみる。

作業ディレクトリの作成

作業ディレクトリは~/rpi3とする。

$ mkdir ~/rpi3 && cd ~/rpi3

ベース環境の取得

$ git clone git://git.yoctoproject.org/poky.git
$ cd poky
$ git clone git://git.yoctoproject.org/meta-raspberrypi

先述の通り、現時点ではmasterブランチで作業。

oe-init-build-envの読み込み

下記を実行する。

$ cd ~/rpi3
$ source poky/oe-init-build-env

次のことが行われる。

  • ロスコンパイルに必要な環境変数の読み込み
  • buildディレクトリの作成とカレントディレクトリの移動
  • conf/bblayers.confの生成
  • conf/local.confの生成
  • conf/templateconf.cfgの生成

templateconf.cfgは特に存在を意識する必要はない。

conf/bblayers.confの修正

bblayers.confを修正し、meta-raspberrypiをbitbakeのビルド対象に追加する。

BBLAYERS ?= " \
  /home/mickey/rpi3/poky/meta \
  /home/mickey/rpi3/poky/meta-poky \
  /home/mickey/rpi3/poky/meta-yocto-bsp \
  /home/mickey/rpi3/poky/meta-raspberrypi \
  "

conf/local.confの修正

RPi3向けのイメージをビルドするための設定をlocal.confの上の方に記述する。

MACHINE ?= "raspberrypi3"
BB_NUMBER_THREADS = "8"
PARALLEL_MAKE = "-j 8"
GPU_MEM = "128"
DL_DIR ?= "${TOPDIR}/../downloads"

DL_DIRは特に必要ないが、ダウンロードしたファイルを格納するdownloadsディレクトリが buildの1つ上の階層にできるように指定している。

bitbake実行

bitbakeを実行し、RPi3向けのイメージを作成する。

$ bitbake rpi-basic-image

動作確認

ddで書き込んで、実機で動作確認。

$ sudo dd if=./tmp/deploy/images/raspberrypi3/rpi-basic-image-raspberrypi3.rpi-sdimg of=/dev/sdb bs=4M

無事起動した。

f:id:mickey_happygolucky:20160323001518j:plain

yocto(jethro)でsystemd-networkdを有効化する

systemdを組み込んだだけだと、ネットワークインターフェイスが有効化されない。 systemdにはネットワークインターフェイスの設定を行うsystemd-networkdが存在するが、 yoctoのsystemdのレシピではデフォルトでは組み込まれない。

connmanを追加する方法もあるが、こちらはjethroではNFSやCIFSをブート時にマウントする際に問題があるため、systemd-networkdを有効化する方法を調査した。

systemd-networkdの有効化

local.confに下記を追加する。

PACKAGECONFIG_pn-systemd += " networkd"

ネットワークインターフェイスの設定

connmanと異なり、自動的にDHCPが有効になったりはしないので、設定ファイルを作成する必要がある。

DHCP

DHCPにより動的にIPアドレスを割り当てるには「/etc/systemd/network/wired.network」を下記の内容で作成する。

[Match]
Name=eth0

[Network]
DHCP=ipv4

スタティックIPアドレス

IPアドレスを静的に割り当てるには「/etc/systemd/network/wired.network」を下記の内容で作成する。

[Match]
Name=eth0

[Network]
Address=192.168.1.10/24

動作確認

$ systemctl status systemd-networkd.service
● systemd-networkd.service - Network Service
   Loaded: loaded (/lib/systemd/system/systemd-networkd.service; enabled; vendor preset: enabled)
   Active: inactive (dead) since Tue 2016-03-15 13:22:52 UTC; 7min ago
     Docs: man:systemd-networkd.service(8)
  Process: 142 ExecStart=/lib/systemd/systemd-networkd (code=exited, status=0/SUCCESS)
 Main PID: 142 (code=exited, status=0/SUCCESS)
   Status: "Shutting down..."

Mar 15 13:22:52 raspberrypi2 systemd[1]: systemd-networkd.service changed ru...m
Mar 15 13:22:52 raspberrypi2 systemd-networkd[142]: Got message type=signal s...
Mar 15 13:22:52 raspberrypi2 systemd-networkd[142]: Got message type=signal s...
Mar 15 13:22:52 raspberrypi2 systemd[1]: Got notification message for unit s...e
Mar 15 13:22:52 raspberrypi2 systemd[1]: systemd-networkd.service: Got notif...)
Mar 15 13:22:52 raspberrypi2 systemd[1]: systemd-networkd.service: got STOPP...1
Mar 15 13:22:52 raspberrypi2 systemd[1]: Child 142 belongs to systemd-networ...e
Mar 15 13:22:52 raspberrypi2 systemd[1]: systemd-networkd.service: main proc...S
Mar 15 13:22:52 raspberrypi2 systemd[1]: systemd-networkd.service changed st...d
Mar 15 13:22:52 raspberrypi2 systemd[1]: systemd-networkd.service: cgroup is...y
Hint: Some lines were ellipsized, use -l to show in full.

ネットワークインターフェイスの設定を行ったあとstatus=0/SUCCESSで終了している。

systemd-networkd.serviceを確認すると、下記のようになっているため正しい動作といえる。

[Service]
...
Restart=on-failure
...

ifconfigで確認すると、wired.networkの通りに設定されている。

$ ifconfig
eth0      Link encap:Ethernet  HWaddr B8:27:EB:A2:C7:F2  
          inet addr:192.168.21.100  Bcast:192.168.21.255  Mask:255.255.255.0
          inet6 addr: fe80::ba27:ebff:fea2:c7f2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:1183 errors:0 dropped:0 overruns:0 frame:0
          TX packets:191 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:67116 (65.5 KiB)  TX bytes:32256 (31.5 KiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:6240 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6240 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:474240 (463.1 KiB)  TX bytes:474240 (463.1 KiB)

connman バージョン1.30以前の落とし穴

システム起動時にNFSやCIFSでリモートのディレクトリをマウントしたい場合などは 確実にネットワークがオンラインになっている必要がある。

ネットワークがオンラインになる前にマウントを試みても失敗するためだ。

systemdでネットワークインターフェイスがオンラインになるまで待ち合わせるには、 network-online.targetが使用できる。 しかしそれはsystemd-networkdで管理されているものにしか有効ではない。

つまり、connmanで起動されたネットワークインターフェイスは待ち合わせしないため、CIFSのマウントがネットワークインターフェイスの起動完了よりも先に実行されてしまうケースがある。

connmanにも同様のconnman-wait-online.serviceが提供されるが、これは1.31からとなる。 そのため1.30以前のconnmanではネットワークインターフェイスの待ち合わせができない。

ちなみにyocto環境ではjethroは1.30となり待ち合わせできない。 masterでは1.31になっているため、connman-wait-online.serviceが使用できる。