Skip to content
This repository has been archived by the owner on Apr 3, 2023. It is now read-only.

Latest commit

 

History

History
217 lines (158 loc) · 14.4 KB

implementation-details.md

File metadata and controls

217 lines (158 loc) · 14.4 KB

Implementation details

If you're not interested in the details, you can skip ahead to the installation instructions, but it might be worhth giving this section a skim so you can see what's going on and fix things if they break later.

This section refers to the modifications performed by the automatic configuration script in this repo. For details on unlocking the write protect setting and installing linux in the first place, see the installation instructions.

Kernel

The kernel is compiled from Google's ChromiumOS fork of the linux kernel, which enables support for the display backlight controls and the audio hardware.

This approach was heavily inspired by @megabytefisher's brilliant hack, the key insight of which is to compile the chromium fork of the linux kernel with a configuration that's as close to the one used by the stock Pixelbook as possible. Many thanks to @megabytefisher, and I hope that my work will be useful to them as well.

The configuration is copied from @megabytefisher's, with some very small modifications since I'm using a slightly newer version of the kernel (4.4.178 vs 4.4.164).

Swap support

The chromium fork of the kernel does not support swapping to disk by design, to prevent wearing out the flash chips that are commonly used for storage on ChromeOS devices.

It is possible to enable swapping to a portion of ram that's been setup to compress its contents using a kernel feature called zram.

The automatic install will add a /usr/local/sbin/setup-zram-swap.sh script and a zram-swap systemd service that runs the setup script at boot. By default the script will enable zram-based swap of about 1.5x the size of physical RAM. I have no idea if this is a good value or not; the script is a minimally altered version of the chromiumos swap setup script.

The size of the zram swap allocation can be controlled by writing an integer value to /root/.zram-swap. Check the setup script source for valid values.

Hibernation

Hibernation is a power state where the contents of ram are written out to disk, to prevent loss of data in the case of complete power loss. Because this feature relies on a disk-based swap partition or file, it's not possible to enable while running the chromium-flavored kernel.

In practice, the suspend state should last for several days before draining the battery completely, which is good enough for me.

Firmware

Again, this is inspired by @megabytefisher. To enable the audio hardware, we need some firmware files that can be extracted from a Pixelbook recovery image. The automated configuration script will pull all the firmware files from the recovery image and install them to the correct place, and also pull some configuration needed for full audio support using cras.

Audio support

Once we're running the chromium-flavored kernel with the firware files in place, we can access the raw audio hardware, and aplay -l should show some output devices. However, the Pixelbook uses an audio chip that's not well supported by the linux audio "userland" components (ALSA and pulseaudio). As a result, both ALSA and pulseaudio can only playback through the internal speakers, and there's no way to switch to the headphones. Recording is also unsupported out of the box, as none of the capture devices are recognized.

This was a deal-breaker for me, since my hope for this machine was to use it for work, where I often need to participate in video calls.

Background info

Before getting into the solution, let me give a quick overview of how audio on linux works, filtered through my super limited understanding.

At the bottom is the hardware driver, which takes the form of a kernel module, which in our case also requires some special firmware files. Once those are in place, the audio hardware is avalable outside of the kernel. However, the "interface" for using the hardware is extremely low-level and not directly usable by most linux programs and end users.

The layer just above the kernel is ALSA, the Advanced Linux Sound Architecture. This provides a common interface to the many, many kinds of audio drivers exposed by the kernel. It lets you do things like adjust the volume and other parameters exposed by the driver, and provides a high-level API that applications can use to play and record audio.

Above ALSA is pulseaudio, which serves as an "audio server", allowing many "client" applications to all share the same audio hardware, something which is quite difficult with ALSA alone. PulseAudio is used by Gnome and other desktop environments; when you move the volume slider in the Gnome UI, it tells the PulseAudio server to adjust the volume on the current output device. PulseAudio uses ALSA for the acutal playback and recording, although I think it also supports other backends.

How to get "perfect" audio on the pixelbook

The problem with audio on the Pixelbook is that neither ALSA nor PulseAudio are configured to use the specific controls and devices provided by the kernel audio driver. ALSA tries to map the driver interfaces to known configurations, but since there's no special mapping for the Pixelbook chip, it falls back on the default "Analog Stereo" profile, which only supports the internal speakers.

Everything works great on ChromeOS, of course, because Google has put in the engineering work to get everything playing nice with the hardware. However, while ChromeOS uses ALSA, it doesn't use it directly, and it also doesn't use PulseAudio. Instead, Google wrote their own audio server, cras, the Chromium OS Audio Server. This serves the same purpose as PulseAudio but consumes fewer resources.

I spent a little while trying to figure out how to get ALSA and PulseAudio to play nice with the Pixelbook audio driver, but I didn't manage to make any headway.

The breakthrough came when I saw that crouton supports audio by actually compiling cras inside the chroot environment and letting cras talk to ALSA and manage the hardware. I had already discovered that the eve recovery image contained configuration files for cras that are specific to the pixelbook hardware, so I figured that the best shot of getting everything to work would be to get cras in the mix.

I decided to try compiling cras and running it inside the standard linux environment to see if it could make sense of the weird hardware interface provided by the driver and exposed by ALSA. Some hours later, it works!

After compiling cras and running it (with the config files in the right place), the cras_test_client included with cras can switch between speakers & headphones (and also HDMI 1 and HDMI 2, but I haven't tried using those yet). It can also switch between a headset mic and the internal mic, and exposes "loopback" capture devices to record whatever is playing through the output device.

You can also use cras_test_client to test audio playback and recording - to see if things are working try cras_test_client --playback_file /usr/share/sounds/alsa/Front_Left.wav.

Included with cras are some ALSA plugins that create a new virtual ALSA device named cras - if those are in the right place (/usr/lib/x86_64-linux-gnu/alsa-lib/ on Ubuntu), apps that support ALSA can target that device and the audio will be routed to cras, which will then send it back to ALSA, this time targeting the actual hardware devices.

The final piece of the puzzle is PulseAudio, which will let us use the standard Gnome volume controls and basically let the rest of the system pretend that we're using a standard audio setup.

Once again, crouton shows the way - the crouton pulseaudio config routes audio through the special cras ALSA device, which then sends audio to cras, which sends it back to ALSA, which finally sends it to the kernel, where it hits the hardware at last.

Here's a picture of this Rube Goldberg contraption:

                    cras uses ALSA's real audio
                    devices for the hardware         +---------------+
                    exposed by the driver            |               |
                                                     |     cras      |
                               +---------------------+               |
                               |                     |               |
                               v                     +-------|-------+
+--------------+      +--------|------+                      ^  Audio sent to the "cras" ALSA
|              |      |               |                      |  device gets routed to cras,
|    Audio     +<-----+     ALSA      +----------------------+  which sends it back to the
|    Driver    |      |               |                         the real hardware ALSA device
|              |      |               |              +---------------+
+--------------+      +-------|-------+              |               |
                              ^                      |  pulseaudio   |
                              +----------------------+               |
                                                     |               |
                            pulse uses the           +---------------+
                            virtual "cras" ALSA device

Switching audio devices

I was feeling pretty smug when I got audio working through the headphones, but the process of switching devices wasn't very pleasant. Because pulseaudio only sees a single audio device (the virtual cras ALSA device), you can't use the built-in output switching provided by Gnome, etc. What you can do is use the cras_test_client included with cras to tell cras what output and input you want to use.

The basic process works like this:

  1. run cras_test_client with no arguments to get a status report, including the names and IDs of all the input and output devices
  2. find the ID of the output device you want to target (will be two numbers separated by :, e.g. 7:0)
  3. run cras_test_client --select_output $id_from_step_2

This seemed like a job for a hacky script, so I wrote one up. The eve-audio-ctl.py script that gets installed when you run the automated setup script wraps cras_test_client and parses the device IDs, so you can just do e.g. eve-audio-ctl.py -o headphone.

Running eve-audio-ctl.py with no arguments will show a list of available audio devices and indicate which is active.

The final piece of the puzzle is automatically switching inputs when headphones are plugged or unplugged. For this, I used the acpi_listen command, which writes messages to stdout when headphones and microphones are plugged in or removed. If you run eve-audio-ctl.py -j, the script will listen for events and switch to headphones when they're plugged in and speakers when the headphones are unplugged. It will also switch the input to the headset mic if one is plugged, and switch back to the internal mic if removed.

The automatic install also creates a systemd service called eve-headphone-jack-listener, which runs the script automatically in the background, so everything should "just work" as expected without having to explicitly run the script.

Keyboard backlight

When running the chromium-flavored kernel, the keyboard backlight can be controlled by writing an integer value between 0 and 100 to /sys/class/leds/chromeos::kbd_backlight/brightness, e.g. echo 100 > '/sys/class/leds/chromeos::kbd_backlight/brightness'. By default only root has permission to do this, so the automatic install script installs a udev rules file to grant permission to an leds group, and also makes sure the group exists and the main user account is a member.

There's also a eve-keyboard-brightness.sh script installd in /usr/local/bin that you can use to more easily set the brightness:

  • Set brightness to absolute level between 0 and 100: eve-keyboard-brightness.sh 100.
  • Adjust brightness by relative amount: eve-keyboard-brightness.sh +10
    • handy for assigning to a keyboard shortcut

Touchpad

By default, the pixelbook touchpad feels off in non-ChromeOS linux, requiring too much pressure to move the cursor. This can be fixed by fiddling with libinput debug tools and creating a /etc/libinput/local-overrides.quirks file.

An earlier version of this repo included a quirks file to set the sensitivity, but I realized I could do one better by once again building some ChromeOS platform code for vanilla linux.

ChromeOS has its own X11 input driver called cmt (for Chromium Multi Touch), which is why the touchpad feels so much nicer on ChromeOS than most flavors of linux.

I found a fork of the xf86-input-cmt driver by @hugegreenbug, which convinced me to try compiling it. Because @hugegreenbug's repo hasn't been updated in a few years, I decided to make my own patches based on his changes and apply them during the automatic configuration process.

Running the automatic config script will build the input driver and its dependencies and install everything into the right place. On reboot, you should have nice touchpad sensitivity, and you'll be able to scroll much more smoothly.

Remapping non-standard keyboard keys

The Pixelbook keyboard has three non-standard keys, the search key that takes the place of Caps Lock, the "hamburger" key in the upper right, and the Google Assistant key betweeen left control and left alt. The hamburger key is automatically recognized as F13, and search gets mapped to Left Super, but the assistant key is ignored.

The automatic install adds a /lib/udev/hwdb.d/61-eve-keyboard.hwdb file that remaps the assistant key to Right Super. If you'd prefer a different key, or to map the search key to something else, see Running the install script for details on how to customize.