.. _zephyr-audio-dsp-development-on-chromebooks:

Zephyr Audio DSP Development on Chromebooks
###########################################

The Audio DSP on Intel Chromebooks is configured to use the SOF
"Community" key for firmware signing, and can therefore accept
arbitrary user-developed firmware like Zephyr applications (of which
SOF is one), including the Zephyr samples and test suite.

Initial TGL Chromebook Setup
****************************

(These instructions were written specifically to the Asus Flip CX5
device code named "delbin".  But they should be reasonably applicable
to any recent Intel device.)

Power the device on and connect it to a wireless network.  It will
likely want to download a firmware update (mine did).  Let this finish
first, to ensure you have two working OS images.

Enable Developer Mode
=====================

Power the device off (menu in lower right, or hold the power button
on the side)

Hold Esc + Refresh (the arrow-in-a-circle "reload" key above "3") and
hit the power key to enter recovery mode.  Note: the touchscreen and
pad don't work in recovery mode, use the arrow keys to navigate.

Select "Advanced Options", then "Enable Developer Mode" and confirm
that you really mean it.  Select "Boot from Internal Storage" at the
bootloader screen.  You will see this screen every time the machine
boots now, telling you that the boot is unverified.

Wait while the device does the required data wipe.  My device takes
about 15 minutes to completely write the stateful partition.  On
reboot, select "Boot from Internal Storage" again and set it up
(again) with Google account.

Make a Recovery Drive
=====================

You will at some point wreck your device and need a recovery stick.
Install the Chromebook Recovery Utility from the Google Web Store and
make one.

You can actually do this on any machine (and any OS) with Chrome
installed, but it's easiest on the Chromebook because it knows its
device ID (for example "DELBIN-XHVI D4B-H4D-G4G-Q9A-A9P" for the Asus
Tiger Lake board).  Note that recovery, when it happens, will not
affect developer mode or firmware settings but it **will wipe out the
root filesystem and /usr/local customizations you have made**.  So
plan on a strategy that can tolerate data loss on the device you're
messing with!

Make the root filesystem writable
=================================

For security, ChromeOS signs and cryptographically verifies (using
Linux's dm-verity feature) all access to the read-only root
filesystem.  Mucking with the rootfs (for example, to install modules
for a custom kernel) requires that the dm-verity layer be turned off:

First open a terminal with Ctrl-Alt-T.  Then at the "crosh> " prompt
issue the "shell" command to get a shell running as the "chronos"
user.  Finally (in developer mode) a simple "sudo su -" will get you a
root prompt.

.. code-block:: console

    crosh> shell
    chronos@localhost / $ sudo su -
    localhost ~ #

Now you need to turn of signature verification in the bootloader
(because obviously we'll be breaking whatever signature existed).
Note that signature verification is something done by the ROM
bootloader, not the OS, and this setting is a (developer-mode-only)
directive to that code:

.. code-block:: console

    cros# crossystem dev_boot_signed_only=0

(*Note: for clarity, commands in this document entered at the ChromeOS
core shell will be prefixed with a hostname of cros.*)

Next you disable the validation step:

.. code-block:: console

    cros# /usr/share/vboot/bin/make_dev_ssd.sh --remove_rootfs_verification

**THIS COMMAND WILL FAIL**, give you an error that you are changing
the setting for the entire running system, and suggest an alternative
"--partitions X" argument to use that modifies only the currently used
partition.  Run that modified command, then reboot.

After rebooting, you will notice that your chromebook boots with the
raw storage device (e.g. /dev/nvme0n1p5) mounted as root and not the
"dm-0" verity device, and that the rootfs is read-write.

Note: What this command actually does is modify the command line of
the installed kernel image (it saves a backup in
/mnt/stateful_partition/cros_sign_backups) so that it specifies
"root=<guid>" and not "root=dm-0".  It does seem to leave the other
verity configuration in place though, it just doesn't try to mount the
resulting (now-invalid!) partition.

Metanote: The astute will note that we're probably going to throw this
kernel out, and that we could probably have just edited the command
line of the new kernel instead of flashing and rebooting into this
modified one.  But that's too many balls to juggle at once for me.

Enable ChromeOS SSH
===================

Once you are booted with a writable partition, you can turn on the
built-in ssh server with:

.. code-block:: console

    cros# /usr/libexec/debugd/helpers/dev_features_ssh

By default neither the "chronos" user nor root accounts have
passwords, so unless you want to type a ssh key in by hand, you
probably want to set a password for the first login (before you run
ssh-copy-id, of course):

.. code-block:: console

    cros# passwd

Now ssh into the chromebook and add your key to
``.ssh/authorized_keys`` as you do for any Linux system.

Install Crouton
***************

The Zephyr integration tools require a proper Linux environment and
won't run on ChromeOS's minimal distro.  So we need to install a Linux
personality.  **DO NOT** bother installing the "Linux Development
Environment" (Crostini) from the ChromeOS Developer settings.  This
personality runs inside a VM, where our tools need access to the real
kernel running on the real hardware.  Instead install Crouton
(https://github.com/dnschneid/crouton), which is a community
chroot-based personality that preserves access to the real hardware
sysfs and /dev filesystem.  These instructions install the "cli-extra"
package list, there are X11-enabled ones available too if you prefer
to work on the device screen directly.  See the project page, etc...

At a root shell, grab the installer and run it (note: /usr/local is
the only writable filesystem without noexec, you must place the binary
there for it to run!):

.. code-block:: console

    cros# mkdir -p /usr/local/bin
    cros# curl -L https://github.com/dnschneid/crouton/raw/master/installer/crouton \
                  > /usr/local/bin/crouton
    cros# chmod 755 /usr/local/bin/crouton
    cros# crouton -r focal -t cli-extra

Start the Crouton chroot environment:

.. code-block:: console

    cros# startcli

Now you are typing commands into the Ubuntu environment.  Enable
inbound ssh on Crouton, but on a port other than 22 (which is used for
the native ChromeOS ssh server).  I'm using 222 here (which is easy to
remember, and not a registered port in /etc/services):

.. code-block:: console

    crouton# apt install iptables openssh-server
    crouton# echo "Port 222" >> /etc/ssh/sshd_config
    crouton# mkdir /run/sshd
    crouton# iptables -I INPUT -p tcp --dport 222 -j ACCEPT
    crouton# /usr/sbin/sshd

(*As above: note that we have introduced a hostname of "crouton" to
refer to the separate Linux personality.*)

NOTE: the mkdir, iptables and sshd commands need to be run every time
the chroot is restarted.  You can put them in /etc/rc.local for
convenience.  Crouton doesn't run systemd (because it can't -- it
doesn't own the system!) so Ubuntu services like openssh-server don't
know how to start themselves.

Building and Installing a Custom Kernel
***************************************

On your build host, grab a copy of the ChromeOS kernel tree.  The
shipping device is using a 5.4 kernel, but the 5.10 tree works for me
and seems to have been backporting upstream drivers such that its main
hardware is all quite recent (5-6 weeks behind mainline or so).  We
place it in the home directory here for simplicity:

.. code-block:: console

    dev$ cd $HOME
    dev$ git clone https://chromium.googlesource.com/chromiumos/third_party/kernel
    dev$ cd kernel
    dev$ git checkout chromeos-5.10

(*Once again, we are typing into a different shell.  We introduce the
hostname "dev" here to represent the development machine on which you
are building kernels and Zephyr apps. It is possible to do this on the
chromebook directly, but not advisable.  Remember the discussion above
about requiring a drive wipe on system recovery!*)

Note: you probably have an existing Linux tree somewhere already.  If
you do it's much faster to add this as a remote there and just fetch
the deltas -- ChromeOS tracks upstream closely.

Now you need a .config file.  The Chromebook kernel ships with the
"configs" module built which exposes this in the running kernel.  You
just have to load the module and read the file.

.. code-block:: console

    dev$ cd /path/to/kernel
    dev$ ssh root@cros modprobe configs
    dev$ ssh root@cros zcat /proc/config.gz > .config

You will need to set some custom configuration variables differently
from ChromeOS defaults (you can edit .config directly, or use
menuconfig, etc...):

+ ``CONFIG_HUGETLBFS=y`` - The Zephyr loader tool requires this
+ ``CONFIG_EXTRA_FIRMWARE_DIR=n`` - This refers to a build directory
    in Google's build environment that we will not have.
+ ``CONFIG_SECURITY_LOADPIN=n`` - Pins modules such that they will
    only load from one filesystem.  Annoying restriction for custom
    kernels.
+ ``CONFIG_MODVERSIONS=n`` - Allow modules to be built and installed
    from modified "dirty" build trees.

Now build your kernel just as you would any other:

.. code-block:: console

    dev$ make olddefconfig     # Or otherwise update .config
    dev$ make bzImage modules  # Probably want -j<whatever> for parallel build

The modules you can copy directly to the (now writable) rootfs on the
device.  Note that this filesystem has very limited space (it's
intended to be read only), so the INSTALL_MOD_STRIP=1 is absolutely
required, and you may find you need to regularly prune modules from
older kernels to make space:

.. code-block:: console

    dev$ make INSTALL_MOD_PATH=mods INSTALL_MOD_STRIP=1 modules_install
    dev$ (cd mods/lib/modules; tar cf - .) | ssh root@cros '(cd /lib/modules; tar xfv -)'

Pack and Install ChromeOS Kernel Image
======================================

The kernel bzImage file itself needs to be signed and packaged into a
ChromeOS vboot package and written directly to the kernel partition.
Thankfully the tools to do this are shipped in Debian/Ubuntu
repositories already:

.. code-block:: console

    $ sudo apt install vboot-utils vboot-kernel-utils

Find the current kernel partition on the device.  You can get this by
comparing the "kernel_guid" command line parameter (passed by the
bootloader) with the partition table of the boot drive, for example:

.. code-block:: console

    dev$ KPART=`ssh root@cros 'fdisk -l -o UUID,Device /dev/nvme0n1 | \
                               grep -i $(sed "s/.*kern_guid=//" /proc/cmdline \
                                         | sed "s/ .*//") \
                               | sed "s/.* //"'`
    dev$ echo $KPART
    /dev/nvme0n1p4

Extract the command line from that image into a local file:

.. code-block:: console

    dev$ ssh root@cros vbutil_kernel --verify /dev/$KPART | tail -1 > cmdline.txt

Now you can pack a new kernel image using the vboot tooling.  Most of
these arguments are boilerplate and always the same.  The keys are
there because the boot requires a valid signature, even though as
configured it won't use it.  Note the cannot-actually-be-empty dummy
file passed as a "bootloader", which is a holdover from previous ROM
variants which needed an EFI stub.

.. code-block:: console

    dev$ echo dummy > dummy.efi
    dev$ vbutil_kernel --pack kernel.img --config cmdline.txt \
           --vmlinuz arch/x86_64/boot/bzImage \
           --keyblock /usr/share/vboot/devkeys/kernel.keyblock \
           --signprivate /usr/share/vboot/devkeys/kernel_data_key.vbprivk \
           --version 1 --bootloader dummy.efi --arch x86_64

You can verify this image if you like with "vbutil_kernel --verify".

Now just copy up the file and write it to the partition on the device:

.. code-block:: console

    $ scp kernel.img root@cros:/tmp
    $ ssh root@cros dd if=/tmp/kernel.img of=/dev/nvme0n1p4

Now reboot, and if all goes well you will find yourself running in
your new kernel.

Wifi Firmware Fixup
===================

On the Tiger Lake Chromebook, the /lib/firmware tree is a bit stale
relative to the current 5.10 kernel.  The iwlwifi driver requests a
firmware file that doesn't exist, leading to a device with no network.
It's a simple problem, but a catastrophic drawback if uncorrected.  It
seems to be sufficient just to link the older version to the new name.
(It would probably be better to copy the proper version from
/lib/firmware from a recent kernel.org checkout.):

.. code-block:: console

    cros# cd /lib/firmware
    cros# ln -s iwlwifi-QuZ-a0-hr-b0-62.ucode iwlwifi-QuZ-a0-hr-b0-64.ucode

Build and Run a Zephyr Application
**********************************

Finally, with your new kernel booted, you are ready to run Zephyr
code.

Build rimage Signing Tool
=========================

First download and build a copy of the Sound Open Firmware "rimage"
tool (these instructions put it in your home directory for clarity,
but anywhere is acceptable):

.. code-block:: console

     dev$ cd $HOME
     dev$ git clone https://github.com/thesofproject/rimage
     dev$ cd rimage/
     dev$ git submodule init
     dev$ git submodule update
     dev$ cmake .
     dev$ make

Copy Integration Scripting to Chromebook
========================================

There is a python scripts needed on the device, to be run inside
the Crouton environment installed above.  Copy them:

.. code-block:: console

    dev$ scp soc/intel/intel_adsp/tools/cavstool.py user@crouton:

Then start the service in the Crouton environment:

.. code-block:: console

    crouton$ sudo ./cavstool.py user@crouton:


Build and Sign Zephyr App
=========================

Zephyr applications build conventionally for this platform, and are
signed with "west flash" with just a few extra arguments.  Note that
the key in use for the Tiger Lake DSP is the "3k" key from SOF, not
the original that is used with older hardware.  The output artifact is
a "zephyr.ri" file to be copied to the device.

.. code-block:: console

    dev$ west build -b intel_adsp/cavs25 samples/hello_world
    dev$ west sign --tool-data=~/rimage/config -t ~/rimage/rimage -- \
                -k $ZEPHYR_BASE/../modules/audio/sof/keys/otc_private_key_3k.pem

Run it!
=======

The loader script takes the signed rimage file as its argument.  Once
it reports success, the application begins running immediately and its
console output (in the SOF shared memory trace buffer) can be read by
the logging script.

.. code-block:: console

    dev$ west flash --remote-host crouton
    Hello World! intel_adsp

Misc References
***************

Upstream documentation from which these instructions were drawn:

This page has the best reference for the boot process:

https://www.chromium.org/chromium-os/developer-library/reference/device/disk-format

This is great too, with an eye toward booting things other than ChromeOS:

https://www.chromium.org/chromium-os/developer-information-for-chrome-os-devices/custom-firmware
