summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--pkgs/applications/virtualization/crosvm/upstream-info.json23
-rw-r--r--pkgs/development/libraries/wlroots/0001-backend-wayland-downgrade-to-wl_compositor-v3.patch84
-rw-r--r--pkgs/development/libraries/wlroots/0002-util-support-virtio_wl-shm-allocation.patch282
-rw-r--r--pkgs/development/libraries/wlroots/default.nix9
-rw-r--r--pkgs/os-specific/linux/chromium-os/common-mk/0001-common-mk-don-t-leak-source-absolute-paths.patch207
-rw-r--r--pkgs/os-specific/linux/chromium-os/common-mk/0002-common-mk-.gn-don-t-hardcode-env-path.patch23
-rw-r--r--pkgs/os-specific/linux/chromium-os/common-mk/default.nix122
-rw-r--r--pkgs/os-specific/linux/chromium-os/crosvm/VIRTIO_NET_F_MAC.patch278
-rw-r--r--pkgs/os-specific/linux/chromium-os/crosvm/default-seccomp-policy-dir.diff (renamed from pkgs/applications/virtualization/crosvm/default-seccomp-policy-dir.diff)0
-rw-r--r--pkgs/os-specific/linux/chromium-os/crosvm/default.nix (renamed from pkgs/applications/virtualization/crosvm/default.nix)58
-rw-r--r--pkgs/os-specific/linux/chromium-os/dbus-properties/default.nix9
-rw-r--r--pkgs/os-specific/linux/chromium-os/default.nix64
-rw-r--r--pkgs/os-specific/linux/chromium-os/libqmi/default.nix28
-rw-r--r--pkgs/os-specific/linux/chromium-os/minigbm/default.nix28
-rw-r--r--pkgs/os-specific/linux/chromium-os/modem-manager/default.nix33
-rw-r--r--pkgs/os-specific/linux/chromium-os/modem-manager/next.nix25
-rw-r--r--pkgs/os-specific/linux/chromium-os/modp_b64/default.nix23
-rw-r--r--pkgs/os-specific/linux/chromium-os/protofiles/default.nix34
-rw-r--r--pkgs/os-specific/linux/chromium-os/sommelier/0005-sommelier-don-t-leak-source-absolute-paths.patch25
-rw-r--r--pkgs/os-specific/linux/chromium-os/sommelier/0006-Revert-Revert-vm_tools-sommelier-Switch-to-the-stabl.patch1840
-rw-r--r--pkgs/os-specific/linux/chromium-os/sommelier/default.nix37
-rwxr-xr-xpkgs/os-specific/linux/chromium-os/update.py (renamed from pkgs/applications/virtualization/crosvm/update.py)84
-rw-r--r--pkgs/os-specific/linux/chromium-os/upstream-info.json72
-rw-r--r--pkgs/os-specific/linux/chromium-os/vm_protos/0003-common-mk-add-goproto_library-source_relative-opt.patch48
-rw-r--r--pkgs/os-specific/linux/chromium-os/vm_protos/0004-vm_tools-proto-set-go_package-correctly.patch102
-rw-r--r--pkgs/os-specific/linux/chromium-os/vm_protos/default.nix41
-rw-r--r--pkgs/os-specific/linux/kernel-headers/default.nix12
-rw-r--r--pkgs/os-specific/linux/kernel/linux-cros.nix38
-rw-r--r--pkgs/os-specific/linux/spectrum/default.nix18
-rw-r--r--pkgs/os-specific/linux/spectrum/linux/vm.nix25
-rw-r--r--pkgs/os-specific/linux/spectrum/rootfs/default.nix58
-rw-r--r--pkgs/os-specific/linux/spectrum/rootfs/etc/group3
-rw-r--r--pkgs/os-specific/linux/spectrum/rootfs/etc/passwd3
-rw-r--r--pkgs/os-specific/linux/spectrum/rootfs/etc/wayfire/wf-shell-defaults.ini2
-rw-r--r--pkgs/os-specific/linux/spectrum/rootfs/rc-services.nix26
-rw-r--r--pkgs/os-specific/linux/spectrum/rootfs/services.nix28
-rw-r--r--pkgs/os-specific/linux/spectrum/rootfs/stage1.nix44
-rw-r--r--pkgs/os-specific/linux/spectrum/spectrum-vm/default.nix35
-rwxr-xr-xpkgs/os-specific/linux/spectrum/spectrum-vm/spectrum-vm.in71
-rw-r--r--pkgs/os-specific/linux/spectrum/testhost/default.nix218
-rw-r--r--pkgs/os-specific/linux/spectrum/vm/app/default.nix58
-rw-r--r--pkgs/os-specific/linux/spectrum/vm/comp/default.nix73
-rw-r--r--pkgs/os-specific/linux/spectrum/vm/default.nix9
-rw-r--r--pkgs/os-specific/linux/spectrum/vm/net/default.nix167
-rw-r--r--pkgs/servers/rust-9p/default.nix34
-rw-r--r--pkgs/tools/networking/mktuntap/default.nix22
-rw-r--r--pkgs/top-level/all-packages.nix19
47 files changed, 4453 insertions, 89 deletions
diff --git a/pkgs/applications/virtualization/crosvm/upstream-info.json b/pkgs/applications/virtualization/crosvm/upstream-info.json
deleted file mode 100644
index bb9cc8841b4..00000000000
--- a/pkgs/applications/virtualization/crosvm/upstream-info.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-  "version": "81.12871.0.0-rc1",
-  "components": {
-    "chromiumos/platform/crosvm": {
-      "url": "https://chromium.googlesource.com/chromiumos/platform/crosvm",
-      "rev": "8b8c01e1ad31718932491e4aee63f56109a138e2",
-      "date": "2020-01-25T02:28:10+00:00",
-      "sha256": "1qmf1k06pwynh15c3nr9m6v90z2pkk930xniwvlvbvnazrk4rllg",
-      "fetchSubmodules": false,
-      "deepClone": false,
-      "leaveDotGit": false
-    },
-    "chromiumos/third_party/adhd": {
-      "url": "https://chromium.googlesource.com/chromiumos/third_party/adhd",
-      "rev": "f361d5b02623274723bff251dafa1e2a2887b013",
-      "date": "2020-01-23T18:37:46+00:00",
-      "sha256": "1p8iwjwgmcgmzri03ik2jaid8l0ch0bzn6z9z64dix1hlrvrlliw",
-      "fetchSubmodules": false,
-      "deepClone": false,
-      "leaveDotGit": false
-    }
-  }
-}
diff --git a/pkgs/development/libraries/wlroots/0001-backend-wayland-downgrade-to-wl_compositor-v3.patch b/pkgs/development/libraries/wlroots/0001-backend-wayland-downgrade-to-wl_compositor-v3.patch
new file mode 100644
index 00000000000..e6d934419f0
--- /dev/null
+++ b/pkgs/development/libraries/wlroots/0001-backend-wayland-downgrade-to-wl_compositor-v3.patch
@@ -0,0 +1,84 @@
+From fae720321c2e8e4d57c9c6554d4f025b1808747c Mon Sep 17 00:00:00 2001
+From: Alyssa Ross <hi@alyssa.is>
+Date: Tue, 25 Aug 2020 17:34:48 +0000
+Subject: [PATCH 1/2] backend/wayland: downgrade to wl_compositor v3
+
+Sommelier does not support v4.
+
+Rather than calculating exact surface-relative damage regions instead
+of the previous buffer-relative ones, just damage the whole surface.
+It'll do for now.
+---
+ backend/wayland/backend.c |  6 +++++-
+ backend/wayland/output.c  | 11 +++++++++--
+ include/backend/wayland.h |  1 +
+ 3 files changed, 15 insertions(+), 3 deletions(-)
+
+diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c
+index 771f4405..fc798292 100644
+--- a/backend/wayland/backend.c
++++ b/backend/wayland/backend.c
+@@ -210,7 +210,7 @@ static void registry_global(void *data, struct wl_registry *registry,
+ 
+ 	if (strcmp(iface, wl_compositor_interface.name) == 0) {
+ 		wl->compositor = wl_registry_bind(registry, name,
+-			&wl_compositor_interface, 4);
++			&wl_compositor_interface, wl_compositor_version());
+ 	} else if (strcmp(iface, wl_seat_interface.name) == 0) {
+ 		struct wl_seat *wl_seat = wl_registry_bind(registry, name,
+ 			&wl_seat_interface, 5);
+@@ -373,6 +373,10 @@ bool wlr_backend_is_wl(struct wlr_backend *b) {
+ 	return b->impl == &backend_impl;
+ }
+ 
++uint32_t wl_compositor_version(void) {
++	return getenv("SOMMELIER_VERSION") ? 3 : 4;
++}
++
+ static void handle_display_destroy(struct wl_listener *listener, void *data) {
+ 	struct wlr_wl_backend *wl =
+ 		wl_container_of(listener, wl, local_display_destroy);
+diff --git a/backend/wayland/output.c b/backend/wayland/output.c
+index 542185ce..5c5ff93d 100644
+--- a/backend/wayland/output.c
++++ b/backend/wayland/output.c
+@@ -319,7 +319,9 @@ static bool output_commit(struct wlr_output *wlr_output) {
+ 
+ 		wl_surface_attach(output->surface, buffer->wl_buffer, 0, 0);
+ 
+-		if (damage == NULL) {
++		if (wl_compositor_version() < 4) {
++			wl_surface_damage(output->surface, 0, 0, INT32_MAX, INT32_MAX);
++		} else if (damage == NULL) {
+ 			wl_surface_damage_buffer(output->surface,
+ 				0, 0, INT32_MAX, INT32_MAX);
+ 		} else {
+@@ -381,7 +383,12 @@ static bool output_set_cursor(struct wlr_output *wlr_output,
+ 		}
+ 
+ 		wl_surface_attach(surface, buffer->wl_buffer, 0, 0);
+-		wl_surface_damage_buffer(surface, 0, 0, INT32_MAX, INT32_MAX);
++
++		if (wl_compositor_version() < 4)
++			wl_surface_damage(surface, 0, 0, INT32_MAX, INT32_MAX);
++		else
++			wl_surface_damage_buffer(surface, 0, 0, INT32_MAX, INT32_MAX);
++
+ 		wl_surface_commit(surface);
+ 	} else {
+ 		wl_surface_attach(surface, NULL, 0, 0);
+diff --git a/include/backend/wayland.h b/include/backend/wayland.h
+index 5d69c248..344dffb1 100644
+--- a/include/backend/wayland.h
++++ b/include/backend/wayland.h
+@@ -131,6 +131,7 @@ struct wlr_wl_input_device *create_wl_input_device(
+ bool create_wl_seat(struct wl_seat *wl_seat, struct wlr_wl_backend *wl);
+ void destroy_wl_seats(struct wlr_wl_backend *wl);
+ void destroy_wl_buffer(struct wlr_wl_buffer *buffer);
++uint32_t wl_compositor_version(void);
+ 
+ extern const struct wl_seat_listener seat_listener;
+ 
+-- 
+2.31.1
+
diff --git a/pkgs/development/libraries/wlroots/0002-util-support-virtio_wl-shm-allocation.patch b/pkgs/development/libraries/wlroots/0002-util-support-virtio_wl-shm-allocation.patch
new file mode 100644
index 00000000000..cd25aedf35a
--- /dev/null
+++ b/pkgs/development/libraries/wlroots/0002-util-support-virtio_wl-shm-allocation.patch
@@ -0,0 +1,282 @@
+From 8dc7c4e472f6c23e20aee6a7041d260344ffd918 Mon Sep 17 00:00:00 2001
+From: Alyssa Ross <hi@alyssa.is>
+Date: Sat, 23 May 2020 03:42:33 +0000
+Subject: [PATCH 2/2] util: support virtio_wl shm allocation
+
+---
+ include/util/virtio_wl.h     | 14 ++++++
+ include/util/virtio_wl_shm.h |  8 +++
+ util/meson.build             |  2 +
+ util/shm.c                   | 12 +++++
+ util/virtio_wl.c             | 96 ++++++++++++++++++++++++++++++++++++
+ util/virtio_wl_shm.c         | 65 ++++++++++++++++++++++++
+ 6 files changed, 197 insertions(+)
+ create mode 100644 include/util/virtio_wl.h
+ create mode 100644 include/util/virtio_wl_shm.h
+ create mode 100644 util/virtio_wl.c
+ create mode 100644 util/virtio_wl_shm.c
+
+diff --git a/include/util/virtio_wl.h b/include/util/virtio_wl.h
+new file mode 100644
+index 00000000..ae5c19b5
+--- /dev/null
++++ b/include/util/virtio_wl.h
+@@ -0,0 +1,14 @@
++#ifndef UTIL_VIRTIO_WL_H
++#define UTIL_VIRTIO_WL_H
++
++struct virtwl_ioctl_txn;
++
++int virtio_wl_connect(const char *name, uint32_t flags);
++
++int32_t virtio_wl_sendmsg(int sockfd, struct virtwl_ioctl_txn *ioctl_txn);
++int32_t virtio_wl_send(int sockfd, const void *buf, uint32_t len);
++
++int32_t virtio_wl_recvmsg(int sockfd, struct virtwl_ioctl_txn *ioctl_txn);
++int32_t virtio_wl_recv(int sockfd, void *buf, uint32_t len);
++
++#endif
+diff --git a/include/util/virtio_wl_shm.h b/include/util/virtio_wl_shm.h
+new file mode 100644
+index 00000000..d9f9f045
+--- /dev/null
++++ b/include/util/virtio_wl_shm.h
+@@ -0,0 +1,8 @@
++#ifndef UTIL_VIRTIO_WL_SHM_H
++#define UTIL_VIRTIO_WL_SHM_H
++
++#include <stddef.h>
++
++int allocate_virtio_wl_shm_file(size_t size);
++
++#endif
+diff --git a/util/meson.build b/util/meson.build
+index 5e31cbbe..a39cc9bd 100644
+--- a/util/meson.build
++++ b/util/meson.build
+@@ -7,5 +7,7 @@ wlr_files += files(
+ 	'signal.c',
+ 	'time.c',
+ 	'token.c',
++	'virtio_wl.c',
++	'virtio_wl_shm.c',
+ )
+ 
+diff --git a/util/shm.c b/util/shm.c
+index f7c7303e..d8110904 100644
+--- a/util/shm.c
++++ b/util/shm.c
+@@ -2,11 +2,14 @@
+ #include <errno.h>
+ #include <fcntl.h>
+ #include <string.h>
++#include <stdlib.h>
+ #include <sys/mman.h>
+ #include <time.h>
+ #include <unistd.h>
+ #include <wlr/config.h>
++#include <wlr/util/log.h>
+ #include "util/shm.h"
++#include "util/virtio_wl_shm.h"
+ 
+ static void randname(char *buf) {
+ 	struct timespec ts;
+@@ -19,6 +22,11 @@ static void randname(char *buf) {
+ }
+ 
+ int create_shm_file(void) {
++	if (getenv("WLR_VIRTIO_WL")) {
++		wlr_log(WLR_ERROR, "cannot use create_shm_file with virtio_wl");
++		return -1;
++	}
++
+ 	int retries = 100;
+ 	do {
+ 		char name[] = "/wlroots-XXXXXX";
+@@ -37,6 +45,10 @@ int create_shm_file(void) {
+ }
+ 
+ int allocate_shm_file(size_t size) {
++	if (getenv("WLR_VIRTIO_WL")) {
++		return allocate_virtio_wl_shm_file(size);
++	}
++
+ 	int fd = create_shm_file();
+ 	if (fd < 0) {
+ 		return -1;
+diff --git a/util/virtio_wl.c b/util/virtio_wl.c
+new file mode 100644
+index 00000000..e7ee58ac
+--- /dev/null
++++ b/util/virtio_wl.c
+@@ -0,0 +1,96 @@
++#define _POSIX_C_SOURCE 200809L
++
++#include <fcntl.h>
++#include <linux/virtwl.h>
++#include <stdint.h>
++#include <string.h>
++#include <sys/ioctl.h>
++#include <stdlib.h>
++#include <unistd.h>
++#include "util/virtio_wl.h"
++
++// This is essentially vendored reusable library code, so I consider
++// it exempt from the wlroots style guide. :)
++
++int virtio_wl_connect(const char *name, uint32_t flags)
++{
++	static int wl_fd = -1;
++	if (wl_fd < 0)
++		wl_fd = open("/dev/wl0", O_RDWR | O_CLOEXEC);
++	if (wl_fd < 0)
++		return wl_fd;
++
++	struct virtwl_ioctl_new new_ctx = {
++		.type = name ? VIRTWL_IOCTL_NEW_CTX_NAMED : VIRTWL_IOCTL_NEW_CTX,
++		.fd = -1,
++		.flags = flags,
++	};
++	// Device assumes name 32 bytes long if not null terminated.
++#pragma GCC diagnostic push
++#pragma GCC diagnostic ignored "-Wstringop-truncation"
++	if (name)
++		strncpy(new_ctx.name, name, sizeof(new_ctx.name));
++#pragma GCC diagnostic pop
++
++	if (ioctl(wl_fd, VIRTWL_IOCTL_NEW, &new_ctx))
++		return -1;
++
++	return new_ctx.fd;
++}
++
++int32_t virtio_wl_sendmsg(int sockfd, struct virtwl_ioctl_txn *ioctl_txn)
++{
++	int r = ioctl(sockfd, VIRTWL_IOCTL_SEND, ioctl_txn);
++	if (!r)
++		r = ioctl_txn->len > INT32_MAX ? INT32_MAX : ioctl_txn->len;
++	return r;
++}
++
++int32_t virtio_wl_send(int sockfd, const void *buf, uint32_t len)
++{
++	struct virtwl_ioctl_txn *ioctl_txn = malloc(sizeof(*ioctl_txn) + len);
++	if (!ioctl_txn)
++		return -1;
++
++	for (size_t i = 0; i < VIRTWL_SEND_MAX_ALLOCS; i++)
++		ioctl_txn->fds[i] = -1;
++
++	ioctl_txn->len = len;
++	memcpy((uint8_t *)ioctl_txn + sizeof(*ioctl_txn), buf, len);
++
++	int r = virtio_wl_sendmsg(sockfd, ioctl_txn);
++
++	free(ioctl_txn);
++	return r;
++}
++
++int32_t virtio_wl_recvmsg(int sockfd, struct virtwl_ioctl_txn *ioctl_txn)
++{
++	if (ioctl(sockfd, VIRTWL_IOCTL_RECV, ioctl_txn))
++		return -1;
++
++	return ioctl_txn->len > INT32_MAX ? INT32_MAX : ioctl_txn->len;
++}
++
++int32_t virtio_wl_recv(int sockfd, void *buf, uint32_t len)
++{
++	struct virtwl_ioctl_txn *ioctl_txn = malloc(sizeof(*ioctl_txn) + len);
++	if (!ioctl_txn)
++		return -1;
++
++	ioctl_txn->len = len;
++
++	int rv = virtio_wl_recvmsg(sockfd, ioctl_txn);
++	if (rv < 0)
++		goto cleanup;
++
++	memcpy(buf, (uint8_t *)ioctl_txn + sizeof(*ioctl_txn), ioctl_txn->len);
++
++	for (size_t i = 0; i < VIRTWL_SEND_MAX_ALLOCS; i++)
++		if (ioctl_txn->fds[i] >= 0)
++			close(ioctl_txn->fds[i]);
++
++ cleanup:
++	free(ioctl_txn);
++	return rv;
++}
+diff --git a/util/virtio_wl_shm.c b/util/virtio_wl_shm.c
+new file mode 100644
+index 00000000..b2109310
+--- /dev/null
++++ b/util/virtio_wl_shm.c
+@@ -0,0 +1,65 @@
++#include <assert.h>
++#include <errno.h>
++#include <linux/virtwl.h>
++#include <stdint.h>
++#include <stdlib.h>
++#include <string.h>
++#include "util/virtio_wl.h"
++#include "util/virtio_wl_shm.h"
++
++// This is essentially vendored reusable library code, so I consider
++// it exempt from the wlroots style guide. :)
++
++int allocate_virtio_wl_shm_file(size_t size)
++{
++	static const size_t NAME_SIZE = 224;
++	static const char *NAME = "wlroots";
++
++	int r;
++	uint8_t *message = NULL;
++	struct virtwl_ioctl_txn *ioctl_txn = NULL;
++
++	int conn = virtio_wl_connect("__crosvm_memfd", 0);
++	if (conn < 0)
++		return conn;
++
++	message = calloc(NAME_SIZE + 8, 1);
++	if (!message) {
++		r = -1;
++		goto cleanup;
++	}
++	strcpy((char *)message, NAME);
++
++	// Encode size as 64-bit little-endian unsigned integer.
++	for (uint8_t i = 0; i < 8; i++)
++		message[NAME_SIZE + i] = (uint8_t)((uint64_t)size >> (8 * i));
++
++	if ((r = virtio_wl_send(conn, message, NAME_SIZE + 8)) < 0)
++		 goto cleanup;
++
++	int32_t len = 1;
++	if (!(ioctl_txn = malloc(sizeof(*ioctl_txn) + len))) {
++		r = -1;
++		goto cleanup;
++	}
++	ioctl_txn->len = len;
++
++	if ((r = virtio_wl_recvmsg(conn, ioctl_txn)) < 0)
++		goto cleanup;
++
++	if (((uint8_t *)ioctl_txn + sizeof(*ioctl_txn))[0]) {
++		// We don't actually know why we didn't get the
++		// memory, but out of memory is a reasonable guess.
++		errno = ENOMEM;
++		r = -1;
++		goto cleanup;
++	}
++
++	r = ioctl_txn->fds[0];
++	assert(r >= 0);
++
++ cleanup:
++	free(message);
++	free(ioctl_txn);
++	return r;
++}
+-- 
+2.31.1
+
diff --git a/pkgs/development/libraries/wlroots/default.nix b/pkgs/development/libraries/wlroots/default.nix
index c22999dd6f0..53117338c13 100644
--- a/pkgs/development/libraries/wlroots/default.nix
+++ b/pkgs/development/libraries/wlroots/default.nix
@@ -1,7 +1,7 @@
 { lib, stdenv, fetchFromGitHub, meson, ninja, pkg-config, wayland-scanner
 , libGL, wayland, wayland-protocols, libinput, libxkbcommon, pixman
 , xcbutilwm, libX11, libcap, xcbutilimage, xcbutilerrors, mesa
-, libpng, ffmpeg, xcbutilrenderutil, xwayland, seatd
+, libpng, ffmpeg, xcbutilrenderutil, xwayland, seatd, linuxHeaders
 }:
 
 stdenv.mkDerivation rec {
@@ -15,6 +15,11 @@ stdenv.mkDerivation rec {
     sha256 = "1sshp3lvlkl1i670kxhwsb4xzxl8raz6769kqvgmxzcb63ns9ay1";
   };
 
+  patches = [
+    ./0001-backend-wayland-downgrade-to-wl_compositor-v3.patch
+    ./0002-util-support-virtio_wl-shm-allocation.patch
+  ];
+
   # $out for the library and $examples for the example programs (in examples):
   outputs = [ "out" "examples" ];
 
@@ -25,7 +30,7 @@ stdenv.mkDerivation rec {
   buildInputs = [
     libGL wayland wayland-protocols libinput libxkbcommon pixman
     xcbutilwm libX11 libcap xcbutilimage xcbutilerrors mesa
-    libpng ffmpeg xcbutilrenderutil xwayland seatd
+    libpng ffmpeg xcbutilrenderutil xwayland seatd linuxHeaders
   ];
 
   postFixup = ''
diff --git a/pkgs/os-specific/linux/chromium-os/common-mk/0001-common-mk-don-t-leak-source-absolute-paths.patch b/pkgs/os-specific/linux/chromium-os/common-mk/0001-common-mk-don-t-leak-source-absolute-paths.patch
new file mode 100644
index 00000000000..c2e33dbde66
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/common-mk/0001-common-mk-don-t-leak-source-absolute-paths.patch
@@ -0,0 +1,207 @@
+From ae0c98ed2715c685b0cb97ac6e5d65101168b625 Mon Sep 17 00:00:00 2001
+From: Alyssa Ross <hi@alyssa.is>
+Date: Sun, 24 Nov 2019 16:56:11 +0000
+Subject: [PATCH 1/6] common-mk: don't leak source-absolute paths
+
+Source-absolute paths like //vm_tools/whatever were being leaked to
+subprocesses, which of course didn't know how to understand them.
+With this patch, source-absolute paths are only used to tell GN the
+outputs, and normal Unix paths are passed to subprocesses.
+---
+ common-mk/external_dependencies/BUILD.gn    |  3 ++-
+ common-mk/mojom_bindings_generator.gni      | 12 ++++++------
+ common-mk/mojom_type_mappings_generator.gni |  2 +-
+ common-mk/pkg_config.gni                    |  3 ++-
+ common-mk/proto_library.gni                 | 19 ++++++++++---------
+ 5 files changed, 21 insertions(+), 18 deletions(-)
+
+diff --git a/common-mk/external_dependencies/BUILD.gn b/common-mk/external_dependencies/BUILD.gn
+index 2581d31ac8..2ead288006 100644
+--- a/common-mk/external_dependencies/BUILD.gn
++++ b/common-mk/external_dependencies/BUILD.gn
+@@ -45,6 +45,7 @@ genxml2cpp("dbus-proxies") {
+ action("cloud_policy_proto_generator") {
+   policy_resources_dir = "${sysroot}/usr/share/policy_resources"
+   proto_out_dir = "${target_gen_dir}/proto"
++  cloud_policy_protobuf_dir = rebase_path(proto_out_dir)
+   policy_tools_dir = "${sysroot}/usr/share/policy_tools"
+ 
+   script = "${policy_tools_dir}/generate_policy_source.py"
+@@ -54,7 +55,7 @@ action("cloud_policy_proto_generator") {
+   ]
+   outputs = [ "${proto_out_dir}/cloud_policy.proto" ]
+   args = [
+-    "--cloud-policy-protobuf=${proto_out_dir}/cloud_policy.proto",
++    "--cloud-policy-protobuf=${cloud_policy_protobuf_dir}/cloud_policy.proto",
+     "--chrome-version-file=${policy_resources_dir}/VERSION",
+     "--target-platform=chromeos",
+     "--policy-templates-file=${policy_resources_dir}/policy_templates.json",
+diff --git a/common-mk/mojom_bindings_generator.gni b/common-mk/mojom_bindings_generator.gni
+index 038c20ed36..205d7d3037 100644
+--- a/common-mk/mojom_bindings_generator.gni
++++ b/common-mk/mojom_bindings_generator.gni
+@@ -100,7 +100,7 @@ template("generate_mojom_bindings_gen") {
+     args = [
+       "mkdir",
+       "-p",
+-      mojo_templates_dir,
++      rebase_path(mojo_templates_dir),
+     ]
+   }
+ 
+@@ -116,7 +116,7 @@ template("generate_mojom_bindings_gen") {
+     args = [
+       "--use_bundled_pylibs",
+       "-o",
+-      mojo_templates_dir,
++      rebase_path(mojo_templates_dir),
+       "precompile",
+     ]
+     deps = [ ":${mojo_templates_dir_action_name}" ]
+@@ -143,7 +143,7 @@ template("generate_mojom_bindings_gen") {
+     args = [
+       "--mojom-file-list={{response_file_name}}",
+       "--output-root",
+-      _mojo_output_base,
++      rebase_path(_mojo_output_base),
+       "--input-root",
+       mojo_root,  # Mojo depth.
+       "--input-root",
+@@ -188,11 +188,11 @@ template("generate_mojom_bindings_gen") {
+              mojom_bindings_generator,
+              "--use_bundled_pylibs",
+              "--output_dir",
+-             _mojo_output_base,
++             rebase_path(_mojo_output_base),
+              "generate",
+              "--filelist={{response_file_name}}",
+              "--bytecode_path",
+-             mojo_templates_dir,
++             rebase_path(mojo_templates_dir),
+              "-I",
+              mojo_root,  # Mojo include path.
+              "-d",
+@@ -216,7 +216,7 @@ template("generate_mojom_bindings_gen") {
+       foreach(typemap, invoker.typemaps) {
+         args += [
+           "--typemap",
+-          typemap,
++          rebase_path(typemap),
+         ]
+       }
+     }
+diff --git a/common-mk/mojom_type_mappings_generator.gni b/common-mk/mojom_type_mappings_generator.gni
+index ff09397111..959a8b6ca9 100644
+--- a/common-mk/mojom_type_mappings_generator.gni
++++ b/common-mk/mojom_type_mappings_generator.gni
+@@ -21,7 +21,7 @@ template("generate_mojom_type_mappings") {
+     outputs = [ "$target_gen_dir/${target_name}_typemapping" ]
+     args = [
+              "--output",
+-             "$target_gen_dir/${target_name}_typemapping",
++             rebase_path("$target_gen_dir/${target_name}_typemapping"),
+            ] + rebase_path(sources, root_build_dir)
+   }
+ }
+diff --git a/common-mk/pkg_config.gni b/common-mk/pkg_config.gni
+index b2c58845d4..31d3f4ab51 100644
+--- a/common-mk/pkg_config.gni
++++ b/common-mk/pkg_config.gni
+@@ -81,7 +81,8 @@ template("generate_pkg_config") {
+     if (!defined(output_name)) {
+       output_name = name
+     }
+-    outputs = [ "${target_out_dir}/${output_name}.pc" ]
++    lib_path = "${target_out_dir}/${output_name}.pc"
++    outputs = [ lib_path ]
+ 
+     script = "//common-mk/generate-pc.py"
+     args = [ "--output" ] + rebase_path(outputs) + [ "--name=" + name ]
+diff --git a/common-mk/proto_library.gni b/common-mk/proto_library.gni
+index e64aedabe0..fb9fb4231d 100644
+--- a/common-mk/proto_library.gni
++++ b/common-mk/proto_library.gni
+@@ -56,7 +56,7 @@ template("proto_library") {
+ 
+     cc_dir = "${root_gen_dir}/${proto_out_dir}"
+     proto_in_dir = rebase_path(proto_in_dir)
+-    proto_out_dir = rebase_path(proto_out_dir)
++    proto_out_dir = rebase_path(cc_dir)
+ 
+     proto_lib_dirs = [
+       proto_in_dir,
+@@ -96,10 +96,9 @@ template("proto_library") {
+     }
+     outputs = []
+     if (gen_python) {
+-      python_dir = "${root_gen_dir}/${proto_out_dir}/py"
+       args += [
+         "--python_out",
+-        "${python_dir}",
++        "${proto_out_dir}/py",
+       ]
+       foreach(source, sources) {
+         source = rebase_path(source, proto_in_dir)
+@@ -109,19 +108,19 @@ template("proto_library") {
+     }
+     if (gen_grpc) {
+       if (gen_grpc_gmock) {
+-        args += [ "--grpc_out=generate_mock_code=true:${cc_dir}" ]
++        args += [ "--grpc_out=generate_mock_code=true:${proto_out_dir}" ]
+         foreach(source, sources) {
+           source = rebase_path(source, proto_in_dir)
+           source = string_replace(source, ".proto", "")
+           outputs += [ "${cc_dir}/${source}_mock.grpc.pb.h" ]
+         }
+       } else {
+-        args += [ "--grpc_out=${cc_dir}" ]
++        args += [ "--grpc_out=${proto_out_dir}" ]
+       }
+       grpc_cpp_plugin = "/usr/bin/grpc_cpp_plugin"
+       args += [
+         "--plugin=protoc-gen-grpc=${grpc_cpp_plugin}",
+-        "--cpp_out=${gen_cpp_mode}${cc_dir}",
++        "--cpp_out=${gen_cpp_mode}${proto_out_dir}",
+       ]
+       foreach(source, sources) {
+         source = rebase_path(source, proto_in_dir)
+@@ -135,7 +134,7 @@ template("proto_library") {
+       }
+     }
+     if (!gen_grpc && !gen_python) {
+-      args += [ "--cpp_out=${gen_cpp_mode}${cc_dir}" ]
++      args += [ "--cpp_out=${gen_cpp_mode}${proto_out_dir}" ]
+       foreach(source, sources) {
+         source = rebase_path(source, proto_in_dir)
+         source = string_replace(source, ".proto", "")
+@@ -238,7 +237,9 @@ template("goproto_library") {
+     # otherwise file descriptor var name will conflict.
+     # cf) https://github.com/golang/protobuf/issues/109
+ 
++    cc_dir = "${root_gen_dir}/${proto_out_dir}"
+     proto_in_dir = rebase_path(invoker.proto_in_dir)
++    proto_out_dir = rebase_path(cc_dir)
+ 
+     # Build protoc command line to run.
+     script = "//common-mk/file_generator_wrapper.py"
+@@ -278,7 +279,7 @@ template("goproto_library") {
+       "--go_out",
+ 
+       # go_out_prefix can be empty, so we can always add a colon here.
+-      "${go_out_prefix}:${root_gen_dir}/${proto_out_dir}",
++      "${go_out_prefix}:${proto_out_dir}",
+     ]
+     foreach(source, sources) {
+       args += [ rebase_path(source) ]
+@@ -288,7 +289,7 @@ template("goproto_library") {
+     outputs = []
+     foreach(source, invoker.sources) {
+       name = get_path_info(source, "name")
+-      outputs += [ "${root_gen_dir}/${proto_out_dir}/${name}.pb.go" ]
++      outputs += [ "${cc_dir}/${name}.pb.go" ]
+     }
+   }
+ }
+-- 
+2.32.0
+
diff --git a/pkgs/os-specific/linux/chromium-os/common-mk/0002-common-mk-.gn-don-t-hardcode-env-path.patch b/pkgs/os-specific/linux/chromium-os/common-mk/0002-common-mk-.gn-don-t-hardcode-env-path.patch
new file mode 100644
index 00000000000..a6ac5b1e9ac
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/common-mk/0002-common-mk-.gn-don-t-hardcode-env-path.patch
@@ -0,0 +1,23 @@
+From 7d33bcd724ec79d00281c2752f9642be25782370 Mon Sep 17 00:00:00 2001
+From: Alyssa Ross <hi@alyssa.is>
+Date: Sun, 24 Nov 2019 17:20:46 +0000
+Subject: [PATCH 2/6] common-mk: .gn: don't hardcode env path
+
+This is needlessly non-portable.
+---
+ .gn | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/.gn b/.gn
+index e7dba8c91c..e29fcd61ee 100644
+--- a/.gn
++++ b/.gn
+@@ -7,4 +7,4 @@ root = "//common-mk/gn_root/:"
+ 
+ # This makes all scripts run by gn respect the shebang setting of the script.
+ # Otherwise, the default is to always use `python`.
+-script_executable = "/usr/bin/env"
++script_executable = "env"
+-- 
+2.32.0
+
diff --git a/pkgs/os-specific/linux/chromium-os/common-mk/default.nix b/pkgs/os-specific/linux/chromium-os/common-mk/default.nix
new file mode 100644
index 00000000000..d617401138f
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/common-mk/default.nix
@@ -0,0 +1,122 @@
+{ stdenv, lib, fetchFromGitiles, upstreamInfo, gn, pkgconfig, python3, ninja
+# , libchrome
+}:
+
+{ platformSubdir
+
+# Mandatory, unlike in mkDerivation, because Google doesn't provide
+# install tasks and just does that in their ebuilds.
+, installPhase
+
+# src allows an out-of-tree (i.e., out-of-platform2) package to be
+# built with common-mk.  patches will be applied to `src` -- to patch
+# platform2 itself use platform2Patches.
+, src ? null, platform2Patches ? []
+
+# gnArgs allows structured data (attribute sets) to be serialized and
+# passed to gn, unlike gnFlags provided by gn's setupHook, which is a
+# flat list of strings.
+, gnArgs ? {}, gnFlags ? [], use ? {}
+
+, postUnpack ? "", prePatch ? "", postPatch ? ""
+, nativeBuildInputs ? []
+, meta ? {}
+, ... } @ args:
+
+let
+  platform2 = fetchFromGitiles upstreamInfo.components."src/platform2";
+
+  attrsToGnList = lib.mapAttrsToList (name: value: "${name}=${toGn value}");
+
+  toGn = value:
+    if lib.isAttrs value then
+      "{${lib.concatStringsSep " " (attrsToGnList value)}}"
+    else
+      builtins.toJSON value;
+in
+
+stdenv.mkDerivation ({
+  pname = lib.last (lib.splitString "/" platformSubdir);
+  inherit (upstreamInfo) version;
+
+  srcs = [ platform2 ] ++ lib.optional (src != null) src;
+  sourceRoot = "platform2";
+
+  postUnpack = lib.optionalString (src != null) ''
+    ln -s ../${src.name} $sourceRoot/${platformSubdir}
+    chmod -R +w ${src.name}
+  '' + postUnpack;
+
+  prePatch = ''
+    pushd ${platformSubdir}
+  '' + prePatch;
+
+  postPatch = ''
+    popd
+    ${lib.concatMapStrings (patch: ''
+      echo applying patch ${patch}
+      patch -p1 < ${patch} 
+    '') ([
+      ./0001-common-mk-don-t-leak-source-absolute-paths.patch
+      ./0002-common-mk-.gn-don-t-hardcode-env-path.patch
+    ] ++ platform2Patches)}
+
+    patchShebangs common-mk
+  '' + (lib.optionalString (!stdenv.cc.isClang) ''
+    substituteInPlace common-mk/BUILD.gn \
+        --replace '"-Wno-c99-designator",' "" \
+        --replace '"-Wstring-compare",' "" \
+        --replace '"-Wstring-plus-int",' "" \
+        --replace '"-Wxor-used-as-pow",' "" \
+        --replace '"-Wunreachable-code-return",' ""
+  '') + postPatch;
+
+  nativeBuildInputs = [ gn pkgconfig python3 ninja ] ++ nativeBuildInputs;
+
+  gnFlags = (attrsToGnList ({
+    ar = "ar";
+    cc = "cc";
+    cxx = "c++";
+    # libbase_ver = libchrome.version;
+    libdir = placeholder "out";
+    pkg_config = "pkg-config";
+    platform2_root = ".";
+    platform_subdir = platformSubdir;
+    use = {
+      amd64 = stdenv.targetPlatform.isx86_64;
+      arm = stdenv.targetPlatform.isAarch32 || stdenv.targetPlatform.isAarch64;
+      asan = false;
+      coverage = false;
+      cros_host = false;
+      crypto = false;
+      dbus = false;
+      device_mapper = false;
+      fuzzer = false;
+      mojo = false;
+      profiling = false;
+      tcmalloc = false;
+      test = false;
+      timers = false;
+      udev = false;
+    } // use;
+  } // gnArgs)) ++ gnFlags;
+
+  passthru.updateScript = ../update.py;
+
+  meta = {
+    homepage =
+      if src == null then
+        "${platform2.meta.homepage}/+/HEAD/${platformSubdir}"
+      else
+        src.meta.homepage;
+    platform = lib.platforms.linux;
+  } // lib.optionalAttrs (src == null) {
+    license = lib.licenses.bsd3;
+  } // meta;
+} // (builtins.removeAttrs args [
+  "src"
+  "gnArgs" "gnFlags" "use"
+  "postUnpack" "prePatch" "postPatch"
+  "nativeBuildInputs"
+  "meta"
+]))
diff --git a/pkgs/os-specific/linux/chromium-os/crosvm/VIRTIO_NET_F_MAC.patch b/pkgs/os-specific/linux/chromium-os/crosvm/VIRTIO_NET_F_MAC.patch
new file mode 100644
index 00000000000..80cab56b1f8
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/crosvm/VIRTIO_NET_F_MAC.patch
@@ -0,0 +1,278 @@
+From 2b5a83bd9e2c1c9642773c1daf785d03f95f33a3 Mon Sep 17 00:00:00 2001
+From: Alyssa Ross <hi@alyssa.is>
+Date: Sun, 27 Sep 2020 15:34:02 +0000
+Subject: [PATCH crosvm v3] crosvm: support setting guest MAC from tap-fd
+
+This adds a mac= option to crosvm's --tap-fd option.  The virtio-net
+driver in the guest will read the desired MAC from virtio
+configuration space.
+
+See the documentation for VIRTIO_NET_F_MAC in the Virtio spec[1].
+
+[1]: https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.html
+
+Thanks-to: Puck Meerburg <puck@puckipedia.com>
+Reviewed-by: Cole Helbling <cole.e.helbling@outlook.com>
+Message-Id: <20210517185700.3591932-1-hi@alyssa.is>
+---
+ devices/src/virtio/net.rs | 20 ++++++++++--
+ src/crosvm.rs             |  8 +++--
+ src/linux.rs              | 19 +++++++-----
+ src/main.rs               | 64 +++++++++++++++++++++++++++++++--------
+ 4 files changed, 88 insertions(+), 23 deletions(-)
+
+diff --git a/devices/src/virtio/net.rs b/devices/src/virtio/net.rs
+index b88dc44ae..b7489eb2b 100644
+--- a/devices/src/virtio/net.rs
++++ b/devices/src/virtio/net.rs
+@@ -419,6 +419,7 @@ where
+ }
+ 
+ pub struct Net<T: TapT> {
++    mac_address: Option<MacAddress>,
+     queue_sizes: Box<[u16]>,
+     workers_kill_evt: Vec<Event>,
+     kill_evts: Vec<Event>,
+@@ -439,6 +440,7 @@ where
+         ip_addr: Ipv4Addr,
+         netmask: Ipv4Addr,
+         mac_addr: MacAddress,
++        guest_mac_addr: Option<MacAddress>,
+         vq_pairs: u16,
+     ) -> Result<Net<T>, NetError> {
+         let multi_queue = vq_pairs > 1;
+@@ -450,12 +452,17 @@ where
+ 
+         tap.enable().map_err(NetError::TapEnable)?;
+ 
+-        Net::from(base_features, tap, vq_pairs)
++        Net::with_tap(base_features, tap, vq_pairs, guest_mac_addr)
+     }
+ 
+     /// Creates a new virtio network device from a tap device that has already been
+     /// configured.
+-    pub fn from(base_features: u64, tap: T, vq_pairs: u16) -> Result<Net<T>, NetError> {
++    pub fn with_tap(
++        base_features: u64,
++        tap: T,
++        vq_pairs: u16,
++        mac_address: Option<MacAddress>,
++    ) -> Result<Net<T>, NetError> {
+         let taps = tap.into_mq_taps(vq_pairs).map_err(NetError::TapOpen)?;
+ 
+         // This would also validate a tap created by Self::new(), but that's a good thing as it
+@@ -488,7 +495,12 @@ where
+             workers_kill_evt.push(worker_kill_evt);
+         }
+ 
++        if mac_address.is_some() {
++            avail_features |= 1 << virtio_net::VIRTIO_NET_F_MAC;
++        }
++
+         Ok(Net {
++            mac_address,
+             queue_sizes: vec![QUEUE_SIZE; (vq_pairs * 2 + 1) as usize].into_boxed_slice(),
+             workers_kill_evt,
+             kill_evts,
+@@ -503,6 +515,10 @@ where
+         let vq_pairs = self.queue_sizes.len() as u16 / 2;
+ 
+         VirtioNetConfig {
++            mac: self
++                .mac_address
++                .map(|m| m.octets())
++                .unwrap_or_else(Default::default),
+             max_vq_pairs: Le16::from(vq_pairs),
+             // Other field has meaningful value when the corresponding feature
+             // is enabled, but all these features aren't supported now.
+diff --git a/src/crosvm.rs b/src/crosvm.rs
+index eededc02e..62b3019db 100644
+--- a/src/crosvm.rs
++++ b/src/crosvm.rs
+@@ -191,6 +191,10 @@ impl Default for SharedDir {
+     }
+ }
+ 
++pub struct TapFdOption {
++    pub mac: Option<net_util::MacAddress>,
++}
++
+ /// Aggregate of all configurable options for a running VM.
+ pub struct Config {
+     pub kvm_device_path: PathBuf,
+@@ -217,7 +221,7 @@ pub struct Config {
+     pub mac_address: Option<net_util::MacAddress>,
+     pub net_vq_pairs: Option<u16>,
+     pub vhost_net: bool,
+-    pub tap_fd: Vec<RawFd>,
++    pub tap_fd: BTreeMap<RawFd, TapFdOption>,
+     pub cid: Option<u64>,
+     pub wayland_socket_paths: BTreeMap<String, PathBuf>,
+     pub wayland_dmabuf: bool,
+@@ -291,7 +295,7 @@ impl Default for Config {
+             mac_address: None,
+             net_vq_pairs: None,
+             vhost_net: false,
+-            tap_fd: Vec::new(),
++            tap_fd: BTreeMap::new(),
+             cid: None,
+             #[cfg(feature = "gpu")]
+             gpu_parameters: None,
+diff --git a/src/linux.rs b/src/linux.rs
+index ba2d28f96..e9601478a 100644
+--- a/src/linux.rs
++++ b/src/linux.rs
+@@ -60,8 +60,8 @@ use vm_memory::{GuestAddress, GuestMemory};
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+ use crate::gdb::{gdb_thread, GdbStub};
+ use crate::{
+-    Config, DiskOption, Executable, SharedDir, SharedDirKind, TouchDeviceOption, VhostUserFsOption,
+-    VhostUserOption,
++    Config, DiskOption, Executable, SharedDir, SharedDirKind, TapFdOption, TouchDeviceOption,
++    VhostUserFsOption, VhostUserOption,
+ };
+ use arch::{
+     self, LinuxArch, RunnableLinuxVm, SerialHardware, SerialParameters, VcpuAffinity,
+@@ -763,7 +763,11 @@ fn create_balloon_device(cfg: &Config, tube: Tube) -> DeviceResult {
+     })
+ }
+ 
+-fn create_tap_net_device(cfg: &Config, tap_fd: RawDescriptor) -> DeviceResult {
++fn create_tap_net_device(
++    cfg: &Config,
++    tap_fd: RawDescriptor,
++    options: &TapFdOption,
++) -> DeviceResult {
+     // Safe because we ensure that we get a unique handle to the fd.
+     let tap = unsafe {
+         Tap::from_raw_descriptor(
+@@ -779,7 +783,8 @@ fn create_tap_net_device(cfg: &Config, tap_fd: RawDescriptor) -> DeviceResult {
+         vq_pairs = 1;
+     }
+     let features = virtio::base_features(cfg.protected_vm);
+-    let dev = virtio::Net::from(features, tap, vq_pairs).map_err(Error::NetDeviceNew)?;
++    let dev =
++        virtio::Net::with_tap(features, tap, vq_pairs, options.mac).map_err(Error::NetDeviceNew)?;
+ 
+     Ok(VirtioDeviceStub {
+         dev: Box::new(dev),
+@@ -814,7 +819,7 @@ fn create_net_device(
+         .map_err(Error::VhostNetDeviceNew)?;
+         Box::new(dev) as Box<dyn VirtioDevice>
+     } else {
+-        let dev = virtio::Net::<Tap>::new(features, host_ip, netmask, mac_address, vq_pairs)
++        let dev = virtio::Net::<Tap>::new(features, host_ip, netmask, mac_address, None, vq_pairs)
+             .map_err(Error::NetDeviceNew)?;
+         Box::new(dev) as Box<dyn VirtioDevice>
+     };
+@@ -1445,8 +1450,8 @@ fn create_virtio_devices(
+     devs.push(create_balloon_device(cfg, balloon_device_tube)?);
+ 
+     // We checked above that if the IP is defined, then the netmask is, too.
+-    for tap_fd in &cfg.tap_fd {
+-        devs.push(create_tap_net_device(cfg, *tap_fd)?);
++    for (tap_fd, options) in &cfg.tap_fd {
++        devs.push(create_tap_net_device(cfg, *tap_fd, options)?);
+     }
+ 
+     if let (Some(host_ip), Some(netmask), Some(mac_address)) =
+diff --git a/src/main.rs b/src/main.rs
+index ab62f2543..e1188a86c 100644
+--- a/src/main.rs
++++ b/src/main.rs
+@@ -20,13 +20,15 @@ use arch::{
+     set_default_serial_parameters, Pstore, SerialHardware, SerialParameters, SerialType,
+     VcpuAffinity,
+ };
+-use base::{debug, error, getpid, info, kill_process_group, reap_child, syslog, warn};
++use base::{
++    debug, error, getpid, info, kill_process_group, reap_child, syslog, warn, RawDescriptor,
++};
+ #[cfg(feature = "direct")]
+ use crosvm::DirectIoOption;
+ use crosvm::{
+     argument::{self, print_help, set_arguments, Argument},
+-    platform, BindMount, Config, DiskOption, Executable, GidMap, SharedDir, TouchDeviceOption,
+-    VhostUserFsOption, VhostUserOption, DISK_ID_LEN,
++    platform, BindMount, Config, DiskOption, Executable, GidMap, SharedDir, TapFdOption,
++    TouchDeviceOption, VhostUserFsOption, VhostUserOption, DISK_ID_LEN,
+ };
+ #[cfg(feature = "gpu")]
+ use devices::virtio::gpu::{GpuMode, GpuParameters};
+@@ -1460,17 +1462,55 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
+         }
+         "vhost-net" => cfg.vhost_net = true,
+         "tap-fd" => {
+-            cfg.tap_fd.push(
+-                value
+-                    .unwrap()
+-                    .parse()
+-                    .map_err(|_| argument::Error::InvalidValue {
++            let mut components = value.unwrap().split(',');
++
++            let fd: RawDescriptor =
++                components
++                    .next()
++                    .and_then(|x| x.parse().ok())
++                    .ok_or_else(|| argument::Error::InvalidValue {
+                         value: value.unwrap().to_owned(),
+                         expected: String::from(
+                             "this value for `tap-fd` must be an unsigned integer",
+                         ),
+-                    })?,
+-            );
++                    })?;
++
++            let mut mac = None;
++            for c in components {
++                let mut kv = c.splitn(2, '=');
++                let (kind, value) = match (kv.next(), kv.next()) {
++                    (Some(kind), Some(value)) => (kind, value),
++                    _ => {
++                        return Err(argument::Error::InvalidValue {
++                            value: c.to_owned(),
++                            expected: String::from("option must be of the form `kind=value`"),
++                        })
++                    }
++                };
++                match kind {
++                    "mac" => {
++                        mac = Some(value.parse().map_err(|_| argument::Error::InvalidValue {
++                            value: value.to_owned(),
++                            expected: String::from(
++                                "`mac` needs to be in the form \"XX:XX:XX:XX:XX:XX\"",
++                            ),
++                        })?)
++                    }
++                    _ => {
++                        return Err(argument::Error::InvalidValue {
++                            value: kind.to_owned(),
++                            expected: String::from("unrecognized option"),
++                        })
++                    }
++                }
++            }
++            if cfg.tap_fd.contains_key(&fd) {
++                return Err(argument::Error::TooManyArguments(format!(
++                    "TAP FD already used: '{}'",
++                    name
++                )));
++            }
++            cfg.tap_fd.insert(fd, TapFdOption { mac });
+         }
+         #[cfg(feature = "gpu")]
+         "gpu" => {
+@@ -1907,8 +1947,8 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa
+           Argument::value("plugin-gid-map-file", "PATH", "Path to the file listing supplemental GIDs that should be mapped in plugin jail.  Can be given more than once."),
+           Argument::flag("vhost-net", "Use vhost for networking."),
+           Argument::value("tap-fd",
+-                          "fd",
+-                          "File descriptor for configured tap device. A different virtual network card will be added each time this argument is given."),
++                          "FD[,mac=MAC]",
++                          "File descriptor for configured tap device. A different virtual network card will be added each time this argument is given. MAC is the MAC address that will be set in the guest."),
+           #[cfg(feature = "gpu")]
+           Argument::flag_or_value("gpu",
+                                   "[width=INT,height=INT]",
+-- 
+2.31.1
+
diff --git a/pkgs/applications/virtualization/crosvm/default-seccomp-policy-dir.diff b/pkgs/os-specific/linux/chromium-os/crosvm/default-seccomp-policy-dir.diff
index f1aa50ee102..f1aa50ee102 100644
--- a/pkgs/applications/virtualization/crosvm/default-seccomp-policy-dir.diff
+++ b/pkgs/os-specific/linux/chromium-os/crosvm/default-seccomp-policy-dir.diff
diff --git a/pkgs/applications/virtualization/crosvm/default.nix b/pkgs/os-specific/linux/chromium-os/crosvm/default.nix
index 697741e21f1..25fa4e2d937 100644
--- a/pkgs/applications/virtualization/crosvm/default.nix
+++ b/pkgs/os-specific/linux/chromium-os/crosvm/default.nix
@@ -1,26 +1,22 @@
-{ stdenv, lib, rustPlatform, fetchgit, runCommand, symlinkJoin
-, pkg-config, minijail, dtc, libusb1, libcap, linux
+{ stdenv, lib, rustPlatform, fetchFromGitiles, upstreamInfo
+, pkg-config, minigbm, minijail, wayland, wayland-protocols, dtc, libusb1
+, libcap, linux
 }:
 
 let
-
-  upstreamInfo = with builtins; fromJSON (readFile ./upstream-info.json);
-
   arch = with stdenv.hostPlatform;
     if isAarch64 then "arm"
     else if isx86_64 then "x86_64"
     else throw "no seccomp policy files available for host platform";
 
-  crosvmSrc = fetchgit {
-    inherit (upstreamInfo.components."chromiumos/platform/crosvm")
-      url rev sha256 fetchSubmodules;
-  };
-
-  adhdSrc = fetchgit {
-    inherit (upstreamInfo.components."chromiumos/third_party/adhd")
-      url rev sha256 fetchSubmodules;
-  };
-
+  getSrc = path: fetchFromGitiles upstreamInfo.components.${path};
+  srcs = lib.genAttrs [
+    "src/aosp/external/minijail"
+    "src/platform/crosvm"
+    "src/platform2"
+    "src/third_party/adhd"
+    "src/third_party/rust-vmm/vhost"
+  ] getSrc;
 in
 
   rustPlatform.buildRustPackage rec {
@@ -30,34 +26,30 @@ in
     unpackPhase = ''
       runHook preUnpack
 
-      mkdir -p chromiumos/platform chromiumos/third_party
-
-      pushd chromiumos/platform
-      unpackFile ${crosvmSrc}
-      mv ${crosvmSrc.name} crosvm
-      popd
-
-      pushd chromiumos/third_party
-      unpackFile ${adhdSrc}
-      mv ${adhdSrc.name} adhd
-      popd
+      ${lib.concatStringsSep "\n" (lib.mapAttrsToList (path: src: ''
+        mkdir -p ${dirOf path}
+        pushd ${dirOf path}
+        unpackFile ${src}
+        popd
+      '') srcs)}
 
       chmod -R u+w -- "$sourceRoot"
 
       runHook postUnpack
     '';
 
-    sourceRoot = "chromiumos/platform/crosvm";
+    sourceRoot = "src/platform/crosvm";
 
     patches = [
       ./default-seccomp-policy-dir.diff
+      ./VIRTIO_NET_F_MAC.patch
     ];
 
-    cargoSha256 = "0aax0slg59afbyn3ygswwap2anv11k6sr9hfpysb4f8rvymvx7hd";
+    cargoSha256 = "1yhxw19niqwipi1fbrskrpvhs915lrs8sdcpknmqd9izq67r3a06";
 
-    nativeBuildInputs = [ pkg-config ];
+    nativeBuildInputs = [ pkg-config wayland ];
 
-    buildInputs = [ dtc libcap libusb1 minijail ];
+    buildInputs = [ dtc libcap libusb1 minigbm minijail wayland wayland-protocols ];
 
     postPatch = ''
       sed -i "s|/usr/share/policy/crosvm/|$out/share/policy/|g" \
@@ -78,9 +70,9 @@ in
         "${linux}/${stdenv.hostPlatform.linux-kernel.target}";
 
     passthru = {
-      inherit adhdSrc;
-      src = crosvmSrc;
-      updateScript = ./update.py;
+      inherit srcs;
+      src = srcs.${sourceRoot};
+      updateScript = ../update.py;
     };
 
     meta = with lib; {
diff --git a/pkgs/os-specific/linux/chromium-os/dbus-properties/default.nix b/pkgs/os-specific/linux/chromium-os/dbus-properties/default.nix
new file mode 100644
index 00000000000..e95ef4a9aa8
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/dbus-properties/default.nix
@@ -0,0 +1,9 @@
+{ runCommand, chromiumos-overlay }:
+
+runCommand "dbus-properties" {
+  passthru.updateScript = ../update.py;
+} ''
+  mkdir -p $out/share/dbus-1/interfaces
+  cp ${chromiumos-overlay}/sys-apps/dbus/files/org.freedesktop.DBus.Properties.xml \
+      $out/share/dbus-1/interfaces
+''
diff --git a/pkgs/os-specific/linux/chromium-os/default.nix b/pkgs/os-specific/linux/chromium-os/default.nix
new file mode 100644
index 00000000000..efdf600756f
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/default.nix
@@ -0,0 +1,64 @@
+{ lib, newScope, fetchFromGitiles, symlinkJoin
+, kernelPatches, libqmi, linux_5_4, makeLinuxHeaders, modemmanager
+}:
+
+let
+  self = with self; {
+    callPackage = newScope self;
+
+    upstreamInfo = lib.importJSON ./upstream-info.json;
+
+    chromiumos-overlay = (fetchFromGitiles
+      upstreamInfo.components."src/third_party/chromiumos-overlay") // {
+        passthru.updateScript = ./update.py;
+      };
+
+    common-mk = callPackage ./common-mk { };
+
+    crosvm = callPackage ./crosvm { };
+
+    dbus-properties = callPackage ./dbus-properties { };
+
+    dbus-interfaces = symlinkJoin {
+      name = "dbus-interfaces";
+      paths = [ dbus-properties self.modemmanager modemmanager-next ];
+      passthru.updateScript = ./update.py;
+    };
+
+    libqmi = callPackage ./libqmi {
+      inherit libqmi;
+    };
+
+    linux_5_4 = callPackage ../kernel/linux-cros.nix {
+      kernelPatches =
+        lib.remove kernelPatches.rtl8761b_support linux_5_4.kernelPatches;
+    };
+
+    linux = self.linux_5_4;
+
+    linuxHeaders = (makeLinuxHeaders {
+      inherit (linux) version src patches;
+    });
+
+    minigbm = callPackage ./minigbm { };
+
+    modemmanager = callPackage ./modem-manager {
+      inherit modemmanager;
+    };
+
+    modemmanager-next = callPackage ./modem-manager/next.nix {
+      inherit modemmanager;
+    };
+
+    modp_b64 = callPackage ./modp_b64 { };
+
+    protofiles = callPackage ./protofiles { };
+
+    sommelier = callPackage ./sommelier { };
+
+    vm_protos = callPackage ./vm_protos { };
+  };
+
+in self // (with self; {
+  inherit (upstreamInfo) version;
+})
diff --git a/pkgs/os-specific/linux/chromium-os/libqmi/default.nix b/pkgs/os-specific/linux/chromium-os/libqmi/default.nix
new file mode 100644
index 00000000000..b96b5224b57
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/libqmi/default.nix
@@ -0,0 +1,28 @@
+{ libqmi, lib, fetchFromGitiles, upstreamInfo
+, autoreconfHook, autoconf-archive, gtk-doc, docbook-xsl-nons
+}:
+
+libqmi.overrideAttrs (
+  { configureFlags ? [], nativeBuildInputs ? [], passthru ? {}, meta ? {}, ... }:
+  {
+    pname = "libqmi-unstable";
+    version = "2019-12-16";
+
+    src = fetchFromGitiles upstreamInfo.components."src/third_party/libqmi";
+
+    nativeBuildInputs = nativeBuildInputs ++
+      [ autoreconfHook autoconf-archive gtk-doc docbook-xsl-nons ];
+
+    # ModemManager tests fail with QRTR in Chromium OS 91.
+    # Will hopefully be fixed in CrOS 92.
+    configureFlags = configureFlags ++ [ "--enable-gtk-doc" "--disable-qrtr" ];
+
+    passthru = passthru // {
+      updateScript = ../update.py;
+    };
+
+    meta = with lib; meta // {
+      maintainers = with maintainers; [ qyliss ];
+    };
+  }
+)
diff --git a/pkgs/os-specific/linux/chromium-os/minigbm/default.nix b/pkgs/os-specific/linux/chromium-os/minigbm/default.nix
new file mode 100644
index 00000000000..6c532a4deb2
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/minigbm/default.nix
@@ -0,0 +1,28 @@
+{ stdenv, lib, fetchFromGitiles, upstreamInfo, pkg-config, libdrm }:
+
+stdenv.mkDerivation {
+  name = "minigbm";
+  inherit (upstreamInfo) version;
+
+  src = fetchFromGitiles upstreamInfo.components."src/platform/minigbm";
+
+  nativeBuildInputs = [ pkg-config ];
+  buildInputs = [ libdrm ];
+
+  patchPhase = ''
+    substituteInPlace Makefile --replace /usr/include $out/include
+  '';
+
+  buildFlags = [ "ECHO=echo" "PKG_CONFIG=pkg-config" ];
+  installFlags = [ "LIBDIR=$(out)/lib" ];
+
+  enableParallelBuilding = true;
+
+  meta = with lib; {
+    description = "GBM implementation for Chromium";
+    homepage = "https://chromium.googlesource.com/chromiumos/platform/minigbm/";
+    maintainers = with maintainers; [ qyliss ];
+    license = licenses.bsd3;
+    platforms = platforms.linux;
+  };
+}
diff --git a/pkgs/os-specific/linux/chromium-os/modem-manager/default.nix b/pkgs/os-specific/linux/chromium-os/modem-manager/default.nix
new file mode 100644
index 00000000000..f1d6cbdd465
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/modem-manager/default.nix
@@ -0,0 +1,33 @@
+{ modemmanager, lib, fetchFromGitiles
+, autoreconfHook, libtool, intltool, libxslt, dbus_glib, chromiumos-overlay
+}:
+
+modemmanager.overrideAttrs (
+  { pname, nativeBuildInputs ? [], buildInputs ? [], postInstall ? "", meta ? {}
+  , ... }:
+  {
+    pname = "${pname}-chromiumos-unstable";
+    version = "2012-04-10";
+
+    src = fetchFromGitiles {
+      url = "https://chromium.googlesource.com/chromiumos/third_party/modemmanager";
+      rev = "657324d1abfd446b0319e4c51bd30cf4967eccf4";
+      sha256 = "12wlak8zx914zix4vv5a8sl0nyi58v7593h4gjchgv3i8ysgj9ah";
+    };
+
+    patches = [];
+
+    nativeBuildInputs = nativeBuildInputs ++ [ autoreconfHook libtool intltool libxslt ];
+    buildInputs = buildInputs ++ [ dbus_glib ];
+
+    preAutoreconf = ''
+      intltoolize
+    '';
+
+    NIX_CFLAGS_COMPILE = [ "-Wno-error" ];
+
+    meta = with lib; meta // {
+      maintainers = with maintainers; [ qyliss ];
+    };
+  }
+)
diff --git a/pkgs/os-specific/linux/chromium-os/modem-manager/next.nix b/pkgs/os-specific/linux/chromium-os/modem-manager/next.nix
new file mode 100644
index 00000000000..d008470b682
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/modem-manager/next.nix
@@ -0,0 +1,25 @@
+{ modemmanager, lib, fetchFromGitiles, upstreamInfo
+, autoreconfHook, autoconf-archive, gtk-doc, libqmi, libxslt
+}:
+
+(modemmanager.override { inherit libqmi; }).overrideAttrs (
+  { pname, nativeBuildInputs ? [], passthru ? {}, meta ? {}, ... }:
+  {
+    pname = "${pname}-chromiumos-next-unstable";
+    version = "2019-10-17";
+
+    src = fetchFromGitiles
+      upstreamInfo.components."src/third_party/modemmanager-next";
+
+    nativeBuildInputs = nativeBuildInputs ++
+      [ autoreconfHook autoconf-archive gtk-doc libxslt ];
+
+    passthru = passthru // {
+      updateScript = ../update.py;
+    };
+
+    meta = with lib; meta // {
+      maintainers = with maintainers; [ qyliss ];
+    };
+  }
+)
diff --git a/pkgs/os-specific/linux/chromium-os/modp_b64/default.nix b/pkgs/os-specific/linux/chromium-os/modp_b64/default.nix
new file mode 100644
index 00000000000..557769336ce
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/modp_b64/default.nix
@@ -0,0 +1,23 @@
+{ common-mk, lib, fetchFromGitiles, upstreamInfo }:
+
+common-mk {
+  platformSubdir = "modp_b64";
+
+  src = fetchFromGitiles upstreamInfo.components."src/third_party/modp_b64";
+
+  installPhase = ''
+    mkdir -p $out/lib
+    install -m 0644 libmodp_b64.a $out/lib
+
+    mkdir $out/include
+    cp -r ../../modp_b64/modp_b64 $out/include
+  '';
+
+  meta = with lib; {
+    description = "High performance base64 encoder/decoder";
+    homepage = "https://github.com/client9/stringencoders";
+    license = licenses.bsd3;
+    maintainers = with maintainers; [ qyliss ];
+    platform = platforms.all;
+  };
+}
diff --git a/pkgs/os-specific/linux/chromium-os/protofiles/default.nix b/pkgs/os-specific/linux/chromium-os/protofiles/default.nix
new file mode 100644
index 00000000000..de8ecbb6963
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/protofiles/default.nix
@@ -0,0 +1,34 @@
+{ stdenv, lib, fetchFromGitiles, chromiumos-overlay, python2 }:
+
+stdenv.mkDerivation rec {
+  pname = "protofiles";
+  version = "0.0.36";
+
+  src = fetchFromGitiles {
+    url = "https://chromium.googlesource.com/chromium/src/components/policy";
+    rev = "72e354e16600a8999c85528147dcf762f31a4b78";
+    sha256 = "11v7n8d0ma426ba3i6q82k0vj0m5l1hx49waffivplpn0c92bm94";
+  };
+
+  buildInputs = [ python2 ];
+
+  installPhase = ''
+    mkdir -p $out/include/proto $out/share/protofiles \
+        $out/share/policy_resources $out/share/policy_tools
+
+    install -m 0644 proto/*.proto $out/include/proto
+    ln -s $out/include/proto/*.proto $out/share/protofiles
+    install -m 0644 resources/policy_templates.json $out/share/policy_resources
+    install -m 0644 ${chromiumos-overlay}/chromeos-base/protofiles/files/VERSION \
+      $out/share/policy_resources
+
+    install tools/generate_policy_source.py $out/share/policy_tools
+  '';
+
+  meta = with lib; {
+    inherit (src.meta) homepage;
+    license = licenses.bsd3;
+    maintainers = with maintainers; [ qyliss ];
+    platform = platforms.all;
+  };
+}
diff --git a/pkgs/os-specific/linux/chromium-os/sommelier/0005-sommelier-don-t-leak-source-absolute-paths.patch b/pkgs/os-specific/linux/chromium-os/sommelier/0005-sommelier-don-t-leak-source-absolute-paths.patch
new file mode 100644
index 00000000000..d40ff8f022c
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/sommelier/0005-sommelier-don-t-leak-source-absolute-paths.patch
@@ -0,0 +1,25 @@
+From 04bdfd44bbaa9f619d3ff03cad3273c46493396e Mon Sep 17 00:00:00 2001
+From: Alyssa Ross <hi@alyssa.is>
+Date: Sun, 1 Dec 2019 17:04:04 +0000
+Subject: [PATCH 5/6] sommelier: don't leak source-absolute paths
+
+---
+ vm_tools/sommelier/wayland_protocol.gni | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/vm_tools/sommelier/wayland_protocol.gni b/vm_tools/sommelier/wayland_protocol.gni
+index f894adf81d..28bb5a006b 100644
+--- a/vm_tools/sommelier/wayland_protocol.gni
++++ b/vm_tools/sommelier/wayland_protocol.gni
+@@ -42,7 +42,7 @@ template("wayland_protocol_library") {
+         "wayland-scanner",
+         g.subcommand,
+         "{{source}}",
+-        output_file,
++        rebase_path(output_file),
+       ]
+     }
+   }
+-- 
+2.32.0
+
diff --git a/pkgs/os-specific/linux/chromium-os/sommelier/0006-Revert-Revert-vm_tools-sommelier-Switch-to-the-stabl.patch b/pkgs/os-specific/linux/chromium-os/sommelier/0006-Revert-Revert-vm_tools-sommelier-Switch-to-the-stabl.patch
new file mode 100644
index 00000000000..c7b1eeafc0d
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/sommelier/0006-Revert-Revert-vm_tools-sommelier-Switch-to-the-stabl.patch
@@ -0,0 +1,1840 @@
+From e97193872755e44aae51dd88e9323d8a069a40ca Mon Sep 17 00:00:00 2001
+From: Alyssa Ross <hi@alyssa.is>
+Date: Fri, 2 Apr 2021 17:55:55 +0000
+Subject: [PATCH 6/6] Revert "Revert "vm_tools: sommelier: Switch to the stable
+ version of xdg-shell""
+
+This reverts commit 32050c0ea6c00c16999915856b40a6a6b8b41bb9.
+---
+ vm_tools/sommelier/BUILD.gn                   |   2 +-
+ vm_tools/sommelier/meson.build                |   2 +-
+ ...dg-shell-unstable-v6.xml => xdg-shell.xml} | 435 +++++++++++++-----
+ vm_tools/sommelier/sommelier-xdg-shell.cc     | 217 +++++----
+ vm_tools/sommelier/sommelier.cc               | 210 +++++----
+ vm_tools/sommelier/sommelier.h                |   8 +-
+ 6 files changed, 535 insertions(+), 339 deletions(-)
+ rename vm_tools/sommelier/protocol/{xdg-shell-unstable-v6.xml => xdg-shell.xml} (71%)
+
+diff --git a/vm_tools/sommelier/BUILD.gn b/vm_tools/sommelier/BUILD.gn
+index 52df0e3403..fe543138ef 100644
+--- a/vm_tools/sommelier/BUILD.gn
++++ b/vm_tools/sommelier/BUILD.gn
+@@ -49,7 +49,7 @@ wayland_protocol_library("sommelier-protocol") {
+     "protocol/relative-pointer-unstable-v1.xml",
+     "protocol/text-input-unstable-v1.xml",
+     "protocol/viewporter.xml",
+-    "protocol/xdg-shell-unstable-v6.xml",
++    "protocol/xdg-shell.xml",
+   ]
+ }
+ 
+diff --git a/vm_tools/sommelier/meson.build b/vm_tools/sommelier/meson.build
+index 088e3e173f..86bcced912 100644
+--- a/vm_tools/sommelier/meson.build
++++ b/vm_tools/sommelier/meson.build
+@@ -58,7 +58,7 @@ wl_protocols = [
+     'protocol/relative-pointer-unstable-v1.xml',
+     'protocol/text-input-unstable-v1.xml',
+     'protocol/viewporter.xml',
+-    'protocol/xdg-shell-unstable-v6.xml',
++    'protocol/xdg-shell.xml',
+ ]
+ 
+ wl_outs = []
+diff --git a/vm_tools/sommelier/protocol/xdg-shell-unstable-v6.xml b/vm_tools/sommelier/protocol/xdg-shell.xml
+similarity index 71%
+rename from vm_tools/sommelier/protocol/xdg-shell-unstable-v6.xml
+rename to vm_tools/sommelier/protocol/xdg-shell.xml
+index 1c0f92452b..f7377a713c 100644
+--- a/vm_tools/sommelier/protocol/xdg-shell-unstable-v6.xml
++++ b/vm_tools/sommelier/protocol/xdg-shell.xml
+@@ -1,11 +1,13 @@
+ <?xml version="1.0" encoding="UTF-8"?>
+-<protocol name="xdg_shell_unstable_v6">
++<protocol name="xdg_shell">
+ 
+   <copyright>
+     Copyright © 2008-2013 Kristian Høgsberg
+     Copyright © 2013      Rafael Antognolli
+     Copyright © 2013      Jasper St. Pierre
+     Copyright © 2010-2013 Intel Corporation
++    Copyright © 2015-2017 Samsung Electronics Co., Ltd
++    Copyright © 2015-2017 Red Hat Inc.
+ 
+     Permission is hereby granted, free of charge, to any person obtaining a
+     copy of this software and associated documentation files (the "Software"),
+@@ -27,18 +29,19 @@
+     DEALINGS IN THE SOFTWARE.
+   </copyright>
+ 
+-  <interface name="zxdg_shell_v6" version="1">
++  <interface name="xdg_wm_base" version="3">
+     <description summary="create desktop-style surfaces">
+-      xdg_shell allows clients to turn a wl_surface into a "real window"
+-      which can be dragged, resized, stacked, and moved around by the
+-      user. Everything about this interface is suited towards traditional
+-      desktop environments.
++      The xdg_wm_base interface is exposed as a global object enabling clients
++      to turn their wl_surfaces into windows in a desktop environment. It
++      defines the basic functionality needed for clients and the compositor to
++      create windows that can be dragged, resized, maximized, etc, as well as
++      creating transient windows such as popup menus.
+     </description>
+ 
+     <enum name="error">
+       <entry name="role" value="0" summary="given wl_surface has another role"/>
+       <entry name="defunct_surfaces" value="1"
+-	     summary="xdg_shell was destroyed before children"/>
++	     summary="xdg_wm_base was destroyed before children"/>
+       <entry name="not_the_topmost_popup" value="2"
+ 	     summary="the client tried to map or destroy a non-topmost popup"/>
+       <entry name="invalid_popup_parent" value="3"
+@@ -50,11 +53,11 @@
+     </enum>
+ 
+     <request name="destroy" type="destructor">
+-      <description summary="destroy xdg_shell">
+-	Destroy this xdg_shell object.
++      <description summary="destroy xdg_wm_base">
++	Destroy this xdg_wm_base object.
+ 
+-	Destroying a bound xdg_shell object while there are surfaces
+-	still alive created by this xdg_shell object instance is illegal
++	Destroying a bound xdg_wm_base object while there are surfaces
++	still alive created by this xdg_wm_base object instance is illegal
+ 	and will result in a protocol error.
+       </description>
+     </request>
+@@ -65,7 +68,7 @@
+ 	surfaces relative to some parent surface. See the interface description
+ 	and xdg_surface.get_popup for details.
+       </description>
+-      <arg name="id" type="new_id" interface="zxdg_positioner_v6"/>
++      <arg name="id" type="new_id" interface="xdg_positioner"/>
+     </request>
+ 
+     <request name="get_xdg_surface">
+@@ -82,14 +85,14 @@
+ 	See the documentation of xdg_surface for more details about what an
+ 	xdg_surface is and how it is used.
+       </description>
+-      <arg name="id" type="new_id" interface="zxdg_surface_v6"/>
++      <arg name="id" type="new_id" interface="xdg_surface"/>
+       <arg name="surface" type="object" interface="wl_surface"/>
+     </request>
+ 
+     <request name="pong">
+       <description summary="respond to a ping event">
+ 	A client must respond to a ping event with a pong request or
+-	the client may be deemed unresponsive. See xdg_shell.ping.
++	the client may be deemed unresponsive. See xdg_wm_base.ping.
+       </description>
+       <arg name="serial" type="uint" summary="serial of the ping event"/>
+     </request>
+@@ -98,7 +101,7 @@
+       <description summary="check if the client is alive">
+ 	The ping event asks the client if it's still alive. Pass the
+ 	serial specified in the event back to the compositor by sending
+-	a "pong" request back with the specified serial. See xdg_shell.ping.
++	a "pong" request back with the specified serial. See xdg_wm_base.pong.
+ 
+ 	Compositors can use this to determine if the client is still
+ 	alive. It's unspecified what will happen if the client doesn't
+@@ -106,13 +109,13 @@
+ 	try to respond in a reasonable amount of time.
+ 
+ 	A compositor is free to ping in any way it wants, but a client must
+-	always respond to any xdg_shell object it created.
++	always respond to any xdg_wm_base object it created.
+       </description>
+       <arg name="serial" type="uint" summary="pass this to the pong request"/>
+     </event>
+   </interface>
+ 
+-  <interface name="zxdg_positioner_v6" version="1">
++  <interface name="xdg_positioner" version="3">
+     <description summary="child surface positioner">
+       The xdg_positioner provides a collection of rules for the placement of a
+       child surface relative to a parent surface. Rules can be defined to ensure
+@@ -162,13 +165,13 @@
+ 	Specify the anchor rectangle within the parent surface that the child
+ 	surface will be placed relative to. The rectangle is relative to the
+ 	window geometry as defined by xdg_surface.set_window_geometry of the
+-	parent surface. The rectangle must be at least 1x1 large.
++	parent surface.
+ 
+ 	When the xdg_positioner object is used to position a child surface, the
+ 	anchor rectangle may not extend outside the window geometry of the
+ 	positioned child's parent surface.
+ 
+-	If a zero or negative size is set the invalid_input error is raised.
++	If a negative size is set the invalid_input error is raised.
+       </description>
+       <arg name="x" type="int" summary="x position of anchor rectangle"/>
+       <arg name="y" type="int" summary="y position of anchor rectangle"/>
+@@ -176,63 +179,54 @@
+       <arg name="height" type="int" summary="height of anchor rectangle"/>
+     </request>
+ 
+-    <enum name="anchor" bitfield="true">
+-      <entry name="none" value="0"
+-	     summary="the center of the anchor rectangle"/>
+-      <entry name="top" value="1"
+-	     summary="the top edge of the anchor rectangle"/>
+-      <entry name="bottom" value="2"
+-	     summary="the bottom edge of the anchor rectangle"/>
+-      <entry name="left" value="4"
+-	     summary="the left edge of the anchor rectangle"/>
+-      <entry name="right" value="8"
+-	     summary="the right edge of the anchor rectangle"/>
++    <enum name="anchor">
++      <entry name="none" value="0"/>
++      <entry name="top" value="1"/>
++      <entry name="bottom" value="2"/>
++      <entry name="left" value="3"/>
++      <entry name="right" value="4"/>
++      <entry name="top_left" value="5"/>
++      <entry name="bottom_left" value="6"/>
++      <entry name="top_right" value="7"/>
++      <entry name="bottom_right" value="8"/>
+     </enum>
+ 
+     <request name="set_anchor">
+-      <description summary="set anchor rectangle anchor edges">
+-	Defines a set of edges for the anchor rectangle. These are used to
+-	derive an anchor point that the child surface will be positioned
+-	relative to. If two orthogonal edges are specified (e.g. 'top' and
+-	'left'), then the anchor point will be the intersection of the edges
+-	(e.g. the top left position of the rectangle); otherwise, the derived
+-	anchor point will be centered on the specified edge, or in the center of
+-	the anchor rectangle if no edge is specified.
+-
+-	If two parallel anchor edges are specified (e.g. 'left' and 'right'),
+-	the invalid_input error is raised.
++      <description summary="set anchor rectangle anchor">
++	Defines the anchor point for the anchor rectangle. The specified anchor
++	is used derive an anchor point that the child surface will be
++	positioned relative to. If a corner anchor is set (e.g. 'top_left' or
++	'bottom_right'), the anchor point will be at the specified corner;
++	otherwise, the derived anchor point will be centered on the specified
++	edge, or in the center of the anchor rectangle if no edge is specified.
+       </description>
+       <arg name="anchor" type="uint" enum="anchor"
+-	   summary="bit mask of anchor edges"/>
++	   summary="anchor"/>
+     </request>
+ 
+-    <enum name="gravity" bitfield="true">
+-      <entry name="none" value="0"
+-	     summary="center over the anchor edge"/>
+-      <entry name="top" value="1"
+-	     summary="position above the anchor edge"/>
+-      <entry name="bottom" value="2"
+-	     summary="position below the anchor edge"/>
+-      <entry name="left" value="4"
+-	     summary="position to the left of the anchor edge"/>
+-      <entry name="right" value="8"
+-	     summary="position to the right of the anchor edge"/>
++    <enum name="gravity">
++      <entry name="none" value="0"/>
++      <entry name="top" value="1"/>
++      <entry name="bottom" value="2"/>
++      <entry name="left" value="3"/>
++      <entry name="right" value="4"/>
++      <entry name="top_left" value="5"/>
++      <entry name="bottom_left" value="6"/>
++      <entry name="top_right" value="7"/>
++      <entry name="bottom_right" value="8"/>
+     </enum>
+ 
+     <request name="set_gravity">
+       <description summary="set child surface gravity">
+ 	Defines in what direction a surface should be positioned, relative to
+-	the anchor point of the parent surface. If two orthogonal gravities are
+-	specified (e.g. 'bottom' and 'right'), then the child surface will be
+-	placed in the specified direction; otherwise, the child surface will be
+-	centered over the anchor point on any axis that had no gravity
+-	specified.
+-
+-	If two parallel gravities are specified (e.g. 'left' and 'right'), the
+-	invalid_input error is raised.
++	the anchor point of the parent surface. If a corner gravity is
++	specified (e.g. 'bottom_right' or 'top_left'), then the child surface
++	will be placed towards the specified gravity; otherwise, the child
++	surface will be centered over the anchor point on any axis that had no
++	gravity specified.
+       </description>
+       <arg name="gravity" type="uint" enum="gravity"
+-	   summary="bit mask of gravity directions"/>
++	   summary="gravity direction"/>
+     </request>
+ 
+     <enum name="constraint_adjustment" bitfield="true">
+@@ -252,7 +246,7 @@
+       <entry name="none" value="0">
+ 	<description summary="don't move the child surface when constrained">
+ 	  Don't alter the surface position even if it is constrained on some
+-	  axis, for example partially outside the edge of a monitor.
++	  axis, for example partially outside the edge of an output.
+ 	</description>
+       </entry>
+       <entry name="slide_x" value="1">
+@@ -304,6 +298,10 @@
+ 	  surface is constrained, the gravity is 'bottom' and the anchor is
+ 	  'bottom', change the gravity to 'top' and the anchor to 'top'.
+ 
++	  The adjusted position is calculated given the original anchor
++	  rectangle and offset, but with the new flipped anchor and gravity
++	  values.
++
+ 	  If the adjusted position also ends up being constrained, the resulting
+ 	  position of the flip_y adjustment will be the one before the
+ 	  adjustment.
+@@ -359,9 +357,49 @@
+       <arg name="x" type="int" summary="surface position x offset"/>
+       <arg name="y" type="int" summary="surface position y offset"/>
+     </request>
++
++    <!-- Version 3 additions -->
++
++    <request name="set_reactive" since="3">
++      <description summary="continuously reconstrain the surface">
++	When set reactive, the surface is reconstrained if the conditions used
++	for constraining changed, e.g. the parent window moved.
++
++	If the conditions changed and the popup was reconstrained, an
++	xdg_popup.configure event is sent with updated geometry, followed by an
++	xdg_surface.configure event.
++      </description>
++    </request>
++
++    <request name="set_parent_size" since="3">
++      <description summary="">
++	Set the parent window geometry the compositor should use when
++	positioning the popup. The compositor may use this information to
++	determine the future state the popup should be constrained using. If
++	this doesn't match the dimension of the parent the popup is eventually
++	positioned against, the behavior is undefined.
++
++	The arguments are given in the surface-local coordinate space.
++      </description>
++      <arg name="parent_width" type="int"
++	   summary="future window geometry width of parent"/>
++      <arg name="parent_height" type="int"
++	   summary="future window geometry height of parent"/>
++    </request>
++
++    <request name="set_parent_configure" since="3">
++      <description summary="set parent configure this is a response to">
++	Set the serial of a xdg_surface.configure event this positioner will be
++	used in response to. The compositor may use this information together
++	with set_parent_size to determine what future state the popup should be
++	constrained using.
++      </description>
++      <arg name="serial" type="uint"
++	   summary="serial of parent configure event"/>
++    </request>
+   </interface>
+ 
+-  <interface name="zxdg_surface_v6" version="1">
++  <interface name="xdg_surface" version="3">
+     <description summary="desktop user interface surface base interface">
+       An interface that may be implemented by a wl_surface, for
+       implementations that provide a desktop-style user interface.
+@@ -388,11 +426,25 @@
+       manipulate a buffer prior to the first xdg_surface.configure call must
+       also be treated as errors.
+ 
+-      For a surface to be mapped by the compositor, the following conditions
+-      must be met: (1) the client has assigned a xdg_surface based role to the
+-      surface, (2) the client has set and committed the xdg_surface state and
+-      the role dependent state to the surface and (3) the client has committed a
+-      buffer to the surface.
++      After creating a role-specific object and setting it up, the client must
++      perform an initial commit without any buffer attached. The compositor
++      will reply with an xdg_surface.configure event. The client must
++      acknowledge it and is then allowed to attach a buffer to map the surface.
++
++      Mapping an xdg_surface-based role surface is defined as making it
++      possible for the surface to be shown by the compositor. Note that
++      a mapped surface is not guaranteed to be visible once it is mapped.
++
++      For an xdg_surface to be mapped by the compositor, the following
++      conditions must be met:
++      (1) the client has assigned an xdg_surface-based role to the surface
++      (2) the client has set and committed the xdg_surface state and the
++	  role-dependent state to the surface
++      (3) the client has committed a buffer to the surface
++
++      A newly-unmapped surface is considered to have met condition (1) out
++      of the 3 required conditions for mapping a surface if its role surface
++      has not been destroyed.
+     </description>
+ 
+     <enum name="error">
+@@ -416,20 +468,23 @@
+ 	See the documentation of xdg_toplevel for more details about what an
+ 	xdg_toplevel is and how it is used.
+       </description>
+-      <arg name="id" type="new_id" interface="zxdg_toplevel_v6"/>
++      <arg name="id" type="new_id" interface="xdg_toplevel"/>
+     </request>
+ 
+     <request name="get_popup">
+       <description summary="assign the xdg_popup surface role">
+-	This creates an xdg_popup object for the given xdg_surface and gives the
+-	associated wl_surface the xdg_popup role.
++	This creates an xdg_popup object for the given xdg_surface and gives
++	the associated wl_surface the xdg_popup role.
++
++	If null is passed as a parent, a parent surface must be specified using
++	some other protocol, before committing the initial state.
+ 
+ 	See the documentation of xdg_popup for more details about what an
+ 	xdg_popup is and how it is used.
+       </description>
+-      <arg name="id" type="new_id" interface="zxdg_popup_v6"/>
+-      <arg name="parent" type="object" interface="zxdg_surface_v6"/>
+-      <arg name="positioner" type="object" interface="zxdg_positioner_v6"/>
++      <arg name="id" type="new_id" interface="xdg_popup"/>
++      <arg name="parent" type="object" interface="xdg_surface" allow-null="true"/>
++      <arg name="positioner" type="object" interface="xdg_positioner"/>
+     </request>
+ 
+     <request name="set_window_geometry">
+@@ -442,6 +497,11 @@
+ 	The window geometry is double buffered, and will be applied at the
+ 	time wl_surface.commit of the corresponding wl_surface is called.
+ 
++	When maintaining a position, the compositor should treat the (x, y)
++	coordinate of the window geometry as the top left corner of the window.
++	A client changing the (x, y) window geometry coordinate should in
++	general not alter the position of the window.
++
+ 	Once the window geometry of the surface is set, it is not possible to
+ 	unset it, and it will remain the same until set_window_geometry is
+ 	called again, even if a new subsurface or buffer is attached.
+@@ -511,36 +571,57 @@
+       </description>
+       <arg name="serial" type="uint" summary="serial of the configure event"/>
+     </event>
++
+   </interface>
+ 
+-  <interface name="zxdg_toplevel_v6" version="1">
++  <interface name="xdg_toplevel" version="3">
+     <description summary="toplevel surface">
+       This interface defines an xdg_surface role which allows a surface to,
+       among other things, set window-like properties such as maximize,
+       fullscreen, and minimize, set application-specific metadata like title and
+       id, and well as trigger user interactive operations such as interactive
+       resize and move.
++
++      Unmapping an xdg_toplevel means that the surface cannot be shown
++      by the compositor until it is explicitly mapped again.
++      All active operations (e.g., move, resize) are canceled and all
++      attributes (e.g. title, state, stacking, ...) are discarded for
++      an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to
++      the state it had right after xdg_surface.get_toplevel. The client
++      can re-map the toplevel by perfoming a commit without any buffer
++      attached, waiting for a configure event and handling it as usual (see
++      xdg_surface description).
++
++      Attaching a null buffer to a toplevel unmaps the surface.
+     </description>
+ 
+     <request name="destroy" type="destructor">
+       <description summary="destroy the xdg_toplevel">
+-	Unmap and destroy the window. The window will be effectively
+-	hidden from the user's point of view, and all state like
+-	maximization, fullscreen, and so on, will be lost.
++	This request destroys the role surface and unmaps the surface;
++	see "Unmapping" behavior in interface section for details.
+       </description>
+     </request>
+ 
+     <request name="set_parent">
+       <description summary="set the parent of this surface">
+-	Set the "parent" of this surface. This window should be stacked
+-	above a parent. The parent surface must be mapped as long as this
+-	surface is mapped.
++	Set the "parent" of this surface. This surface should be stacked
++	above the parent surface and all other ancestor surfaces.
+ 
+ 	Parent windows should be set on dialogs, toolboxes, or other
+ 	"auxiliary" surfaces, so that the parent is raised when the dialog
+ 	is raised.
++
++	Setting a null parent for a child window removes any parent-child
++	relationship for the child. Setting a null parent for a window which
++	currently has no parent is a no-op.
++
++	If the parent is unmapped then its children are managed as
++	though the parent of the now-unmapped parent has become the
++	parent of this surface. If no parent exists for the now-unmapped
++	parent then the children are managed as though they have no
++	parent surface.
+       </description>
+-      <arg name="parent" type="object" interface="zxdg_toplevel_v6" allow-null="true"/>
++      <arg name="parent" type="object" interface="xdg_toplevel" allow-null="true"/>
+     </request>
+ 
+     <request name="set_title">
+@@ -573,6 +654,9 @@
+ 	For example, "org.freedesktop.FooViewer" where the .desktop file is
+ 	"org.freedesktop.FooViewer.desktop".
+ 
++	Like other properties, a set_app_id request can be sent after the
++	xdg_toplevel has been mapped to update the property.
++
+ 	See the desktop-entry specification [0] for more details on
+ 	application identifiers and how they relate to well-known D-Bus
+ 	names and .desktop files.
+@@ -676,7 +760,7 @@
+       </description>
+       <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
+       <arg name="serial" type="uint" summary="the serial of the user event"/>
+-      <arg name="edges" type="uint" summary="which edge or corner is being dragged"/>
++      <arg name="edges" type="uint" enum="resize_edge" summary="which edge or corner is being dragged"/>
+     </request>
+ 
+     <enum name="state">
+@@ -693,12 +777,18 @@
+ 	<description summary="the surface is maximized">
+ 	  The surface is maximized. The window geometry specified in the configure
+ 	  event must be obeyed by the client.
++
++	  The client should draw without shadow or other
++	  decoration outside of the window geometry.
+ 	</description>
+       </entry>
+       <entry name="fullscreen" value="2" summary="the surface is fullscreen">
+ 	<description summary="the surface is fullscreen">
+-	  The surface is fullscreen. The window geometry specified in the configure
+-	  event must be obeyed by the client.
++	  The surface is fullscreen. The window geometry specified in the
++	  configure event is a maximum; the client cannot resize beyond it. For
++	  a surface to cover the whole fullscreened area, the geometry
++	  dimensions must be obeyed by the client. For more details, see
++	  xdg_toplevel.set_fullscreen.
+ 	</description>
+       </entry>
+       <entry name="resizing" value="3" summary="the surface is being resized">
+@@ -716,6 +806,30 @@
+ 	  keyboard or pointer focus.
+ 	</description>
+       </entry>
++      <entry name="tiled_left" value="5" since="2">
++	<description summary="the surface is tiled">
++	  The window is currently in a tiled layout and the left edge is
++	  considered to be adjacent to another part of the tiling grid.
++	</description>
++      </entry>
++      <entry name="tiled_right" value="6" since="2">
++	<description summary="the surface is tiled">
++	  The window is currently in a tiled layout and the right edge is
++	  considered to be adjacent to another part of the tiling grid.
++	</description>
++      </entry>
++      <entry name="tiled_top" value="7" since="2">
++	<description summary="the surface is tiled">
++	  The window is currently in a tiled layout and the top edge is
++	  considered to be adjacent to another part of the tiling grid.
++	</description>
++      </entry>
++      <entry name="tiled_bottom" value="8" since="2">
++	<description summary="the surface is tiled">
++	  The window is currently in a tiled layout and the bottom edge is
++	  considered to be adjacent to another part of the tiling grid.
++	</description>
++      </entry>
+     </enum>
+ 
+     <request name="set_max_size">
+@@ -805,12 +919,11 @@
+ 	Maximize the surface.
+ 
+ 	After requesting that the surface should be maximized, the compositor
+-	will respond by emitting a configure event with the "maximized" state
+-	and the required window geometry. The client should then update its
+-	content, drawing it in a maximized state, i.e. without shadow or other
+-	decoration outside of the window geometry. The client must also
+-	acknowledge the configure when committing the new content (see
+-	ack_configure).
++	will respond by emitting a configure event. Whether this configure
++	actually sets the window maximized is subject to compositor policies.
++	The client must then update its content, drawing in the configured
++	state. The client must also acknowledge the configure when committing
++	the new content (see ack_configure).
+ 
+ 	It is up to the compositor to decide how and where to maximize the
+ 	surface, for example which output and what region of the screen should
+@@ -818,6 +931,10 @@
+ 
+ 	If the surface was already maximized, the compositor will still emit
+ 	a configure event with the "maximized" state.
++
++	If the surface is in a fullscreen state, this request has no direct
++	effect. It may alter the state the surface is returned to when
++	unmaximized unless overridden by the compositor.
+       </description>
+     </request>
+ 
+@@ -826,13 +943,13 @@
+ 	Unmaximize the surface.
+ 
+ 	After requesting that the surface should be unmaximized, the compositor
+-	will respond by emitting a configure event without the "maximized"
+-	state. If available, the compositor will include the window geometry
+-	dimensions the window had prior to being maximized in the configure
+-	request. The client must then update its content, drawing it in a
+-	regular state, i.e. potentially with shadow, etc. The client must also
+-	acknowledge the configure when committing the new content (see
+-	ack_configure).
++	will respond by emitting a configure event. Whether this actually
++	un-maximizes the window is subject to compositor policies.
++	If available and applicable, the compositor will include the window
++	geometry dimensions the window had prior to being maximized in the
++	configure event. The client must then update its content, drawing it in
++	the configured state. The client must also acknowledge the configure
++	when committing the new content (see ack_configure).
+ 
+ 	It is up to the compositor to position the surface after it was
+ 	unmaximized; usually the position the surface had before maximizing, if
+@@ -840,24 +957,63 @@
+ 
+ 	If the surface was already not maximized, the compositor will still
+ 	emit a configure event without the "maximized" state.
++
++	If the surface is in a fullscreen state, this request has no direct
++	effect. It may alter the state the surface is returned to when
++	unmaximized unless overridden by the compositor.
+       </description>
+     </request>
+ 
+     <request name="set_fullscreen">
+-      <description summary="set the window as fullscreen on a monitor">
++      <description summary="set the window as fullscreen on an output">
+ 	Make the surface fullscreen.
+ 
+-	You can specify an output that you would prefer to be fullscreen.
+-	If this value is NULL, it's up to the compositor to choose which
+-	display will be used to map this surface.
++	After requesting that the surface should be fullscreened, the
++	compositor will respond by emitting a configure event. Whether the
++	client is actually put into a fullscreen state is subject to compositor
++	policies. The client must also acknowledge the configure when
++	committing the new content (see ack_configure).
++
++	The output passed by the request indicates the client's preference as
++	to which display it should be set fullscreen on. If this value is NULL,
++	it's up to the compositor to choose which display will be used to map
++	this surface.
+ 
+ 	If the surface doesn't cover the whole output, the compositor will
+ 	position the surface in the center of the output and compensate with
+-	black borders filling the rest of the output.
++	with border fill covering the rest of the output. The content of the
++	border fill is undefined, but should be assumed to be in some way that
++	attempts to blend into the surrounding area (e.g. solid black).
++
++	If the fullscreened surface is not opaque, the compositor must make
++	sure that other screen content not part of the same surface tree (made
++	up of subsurfaces, popups or similarly coupled surfaces) are not
++	visible below the fullscreened surface.
+       </description>
+       <arg name="output" type="object" interface="wl_output" allow-null="true"/>
+     </request>
+-    <request name="unset_fullscreen" />
++
++    <request name="unset_fullscreen">
++      <description summary="unset the window as fullscreen">
++	Make the surface no longer fullscreen.
++
++	After requesting that the surface should be unfullscreened, the
++	compositor will respond by emitting a configure event.
++	Whether this actually removes the fullscreen state of the client is
++	subject to compositor policies.
++
++	Making a surface unfullscreen sets states for the surface based on the following:
++	* the state(s) it may have had before becoming fullscreen
++	* any state(s) decided by the compositor
++	* any state(s) requested by the client while the surface was fullscreen
++
++	The compositor may include the previous window geometry dimensions in
++	the configure event, if applicable.
++
++	The client must also acknowledge the configure when committing the new
++	content (see ack_configure).
++      </description>
++    </request>
+ 
+     <request name="set_minimized">
+       <description summary="set the window as minimized">
+@@ -913,7 +1069,7 @@
+     </event>
+   </interface>
+ 
+-  <interface name="zxdg_popup_v6" version="1">
++  <interface name="xdg_popup" version="3">
+     <description summary="short-lived, popup surfaces for menus">
+       A popup surface is a short-lived, temporary surface. It can be used to
+       implement for example menus, popovers, tooltips and other similar user
+@@ -931,21 +1087,12 @@
+       surface of their own is clicked should dismiss the popup using the destroy
+       request.
+ 
+-      The parent surface must have either the xdg_toplevel or xdg_popup surface
+-      role.
+-
+       A newly created xdg_popup will be stacked on top of all previously created
+       xdg_popup surfaces associated with the same xdg_toplevel.
+ 
+       The parent of an xdg_popup must be mapped (see the xdg_surface
+       description) before the xdg_popup itself.
+ 
+-      The x and y arguments passed when creating the popup object specify
+-      where the top left of the popup should be placed, relative to the
+-      local surface coordinates of the parent surface. See
+-      xdg_surface.get_popup. An xdg_popup must intersect with or be at least
+-      partially adjacent to its parent surface.
+-
+       The client must call wl_surface.commit on the corresponding wl_surface
+       for the xdg_popup state to take effect.
+     </description>
+@@ -1023,6 +1170,11 @@
+ 	The x and y arguments represent the position the popup was placed at
+ 	given the xdg_positioner rule, relative to the upper left corner of the
+ 	window geometry of the parent surface.
++
++	For version 2 or older, the configure event for an xdg_popup is only
++	ever sent once for the initial configuration. Starting with version 3,
++	it may be sent again if the popup is setup with an xdg_positioner with
++	set_reactive requested, or in response to xdg_popup.reposition requests.
+       </description>
+       <arg name="x" type="int"
+ 	   summary="x position relative to parent surface window geometry"/>
+@@ -1040,5 +1192,58 @@
+       </description>
+     </event>
+ 
++    <!-- Version 3 additions -->
++
++    <request name="reposition" since="3">
++      <description summary="recalculate the popup's location">
++	Reposition an already-mapped popup. The popup will be placed given the
++	details in the passed xdg_positioner object, and a
++	xdg_popup.repositioned followed by xdg_popup.configure and
++	xdg_surface.configure will be emitted in response. Any parameters set
++	by the previous positioner will be discarded.
++
++	The passed token will be sent in the corresponding
++	xdg_popup.repositioned event. The new popup position will not take
++	effect until the corresponding configure event is acknowledged by the
++	client. See xdg_popup.repositioned for details. The token itself is
++	opaque, and has no other special meaning.
++
++	If multiple reposition requests are sent, the compositor may skip all
++	but the last one.
++
++	If the popup is repositioned in response to a configure event for its
++	parent, the client should send an xdg_positioner.set_parent_configure
++	and possibly a xdg_positioner.set_parent_size request to allow the
++	compositor to properly constrain the popup.
++
++	If the popup is repositioned together with a parent that is being
++	resized, but not in response to a configure event, the client should
++	send a xdg_positioner.set_parent_size request.
++      </description>
++      <arg name="positioner" type="object" interface="xdg_positioner"/>
++      <arg name="token" type="uint" summary="reposition request token"/>
++    </request>
++
++    <event name="repositioned" since="3">
++      <description summary="signal the completion of a repositioned request">
++	The repositioned event is sent as part of a popup configuration
++	sequence, together with xdg_popup.configure and lastly
++	xdg_surface.configure to notify the completion of a reposition request.
++
++	The repositioned event is to notify about the completion of a
++	xdg_popup.reposition request. The token argument is the token passed
++	in the xdg_popup.reposition request.
++
++	Immediately after this event is emitted, xdg_popup.configure and
++	xdg_surface.configure will be sent with the updated size and position,
++	as well as a new configure serial.
++
++	The client should optionally update the content of the popup, but must
++	acknowledge the new popup configuration for the new position to take
++	effect. See xdg_surface.ack_configure for details.
++      </description>
++      <arg name="token" type="uint" summary="reposition request token"/>
++    </event>
++
+   </interface>
+ </protocol>
+diff --git a/vm_tools/sommelier/sommelier-xdg-shell.cc b/vm_tools/sommelier/sommelier-xdg-shell.cc
+index 91744a67e3..45b8eeb93e 100644
+--- a/vm_tools/sommelier/sommelier-xdg-shell.cc
++++ b/vm_tools/sommelier/sommelier-xdg-shell.cc
+@@ -7,37 +7,37 @@
+ #include <assert.h>
+ #include <stdlib.h>
+ 
+-#include "xdg-shell-unstable-v6-client-protocol.h"  // NOLINT(build/include_directory)
+-#include "xdg-shell-unstable-v6-server-protocol.h"  // NOLINT(build/include_directory)
++#include "xdg-shell-client-protocol.h"  // NOLINT(build/include_directory)
++#include "xdg-shell-server-protocol.h"  // NOLINT(build/include_directory)
+ 
+ struct sl_host_xdg_shell {
+   struct sl_context* ctx;
+   struct wl_resource* resource;
+-  struct zxdg_shell_v6* proxy;
++  struct xdg_wm_base* proxy;
+ };
+ 
+ struct sl_host_xdg_surface {
+   struct sl_context* ctx;
+   struct wl_resource* resource;
+-  struct zxdg_surface_v6* proxy;
++  struct xdg_surface* proxy;
+ };
+ 
+ struct sl_host_xdg_toplevel {
+   struct sl_context* ctx;
+   struct wl_resource* resource;
+-  struct zxdg_toplevel_v6* proxy;
++  struct xdg_toplevel* proxy;
+ };
+ 
+ struct sl_host_xdg_popup {
+   struct sl_context* ctx;
+   struct wl_resource* resource;
+-  struct zxdg_popup_v6* proxy;
++  struct xdg_popup* proxy;
+ };
+ 
+ struct sl_host_xdg_positioner {
+   struct sl_context* ctx;
+   struct wl_resource* resource;
+-  struct zxdg_positioner_v6* proxy;
++  struct xdg_positioner* proxy;
+ };
+ 
+ static void sl_xdg_positioner_destroy(struct wl_client* client,
+@@ -53,7 +53,7 @@ static void sl_xdg_positioner_set_size(struct wl_client* client,
+       static_cast<sl_host_xdg_positioner*>(wl_resource_get_user_data(resource));
+   double scale = host->ctx->scale;
+ 
+-  zxdg_positioner_v6_set_size(host->proxy, width / scale, height / scale);
++  xdg_positioner_set_size(host->proxy, width / scale, height / scale);
+ }
+ 
+ static void sl_xdg_positioner_set_anchor_rect(struct wl_client* client,
+@@ -72,7 +72,7 @@ static void sl_xdg_positioner_set_anchor_rect(struct wl_client* client,
+   x2 = (x + width) / scale;
+   y2 = (y + height) / scale;
+ 
+-  zxdg_positioner_v6_set_anchor_rect(host->proxy, x1, y1, x2 - x1, y2 - y1);
++  xdg_positioner_set_anchor_rect(host->proxy, x1, y1, x2 - x1, y2 - y1);
+ }
+ 
+ static void sl_xdg_positioner_set_anchor(struct wl_client* client,
+@@ -81,7 +81,7 @@ static void sl_xdg_positioner_set_anchor(struct wl_client* client,
+   struct sl_host_xdg_positioner* host =
+       static_cast<sl_host_xdg_positioner*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_positioner_v6_set_anchor(host->proxy, anchor);
++  xdg_positioner_set_anchor(host->proxy, anchor);
+ }
+ 
+ static void sl_xdg_positioner_set_gravity(struct wl_client* client,
+@@ -90,7 +90,7 @@ static void sl_xdg_positioner_set_gravity(struct wl_client* client,
+   struct sl_host_xdg_positioner* host =
+       static_cast<sl_host_xdg_positioner*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_positioner_v6_set_gravity(host->proxy, gravity);
++  xdg_positioner_set_gravity(host->proxy, gravity);
+ }
+ 
+ static void sl_xdg_positioner_set_constraint_adjustment(
+@@ -100,8 +100,7 @@ static void sl_xdg_positioner_set_constraint_adjustment(
+   struct sl_host_xdg_positioner* host =
+       static_cast<sl_host_xdg_positioner*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_positioner_v6_set_constraint_adjustment(host->proxy,
+-                                               constraint_adjustment);
++  xdg_positioner_set_constraint_adjustment(host->proxy, constraint_adjustment);
+ }  // NOLINT(whitespace/indent)
+ 
+ static void sl_xdg_positioner_set_offset(struct wl_client* client,
+@@ -112,24 +111,23 @@ static void sl_xdg_positioner_set_offset(struct wl_client* client,
+       static_cast<sl_host_xdg_positioner*>(wl_resource_get_user_data(resource));
+   double scale = host->ctx->scale;
+ 
+-  zxdg_positioner_v6_set_offset(host->proxy, x / scale, y / scale);
++  xdg_positioner_set_offset(host->proxy, x / scale, y / scale);
+ }
+ 
+-static const struct zxdg_positioner_v6_interface
+-    sl_xdg_positioner_implementation = {
+-        sl_xdg_positioner_destroy,
+-        sl_xdg_positioner_set_size,
+-        sl_xdg_positioner_set_anchor_rect,
+-        sl_xdg_positioner_set_anchor,
+-        sl_xdg_positioner_set_gravity,
+-        sl_xdg_positioner_set_constraint_adjustment,
+-        sl_xdg_positioner_set_offset};
++static const struct xdg_positioner_interface sl_xdg_positioner_implementation =
++    {sl_xdg_positioner_destroy,
++     sl_xdg_positioner_set_size,
++     sl_xdg_positioner_set_anchor_rect,
++     sl_xdg_positioner_set_anchor,
++     sl_xdg_positioner_set_gravity,
++     sl_xdg_positioner_set_constraint_adjustment,
++     sl_xdg_positioner_set_offset};
+ 
+ static void sl_destroy_host_xdg_positioner(struct wl_resource* resource) {
+   struct sl_host_xdg_positioner* host =
+       static_cast<sl_host_xdg_positioner*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_positioner_v6_destroy(host->proxy);
++  xdg_positioner_destroy(host->proxy);
+   wl_resource_set_user_data(resource, NULL);
+   free(host);
+ }
+@@ -148,20 +146,20 @@ static void sl_xdg_popup_grab(struct wl_client* client,
+   struct sl_host_seat* host_seat =
+       static_cast<sl_host_seat*>(wl_resource_get_user_data(seat_resource));
+ 
+-  zxdg_popup_v6_grab(host->proxy, host_seat->proxy, serial);
++  xdg_popup_grab(host->proxy, host_seat->proxy, serial);
+ }  // NOLINT(whitespace/indent)
+ 
+-static const struct zxdg_popup_v6_interface sl_xdg_popup_implementation = {
++static const struct xdg_popup_interface sl_xdg_popup_implementation = {
+     sl_xdg_popup_destroy, sl_xdg_popup_grab};
+ 
+ static void sl_xdg_popup_configure(void* data,
+-                                   struct zxdg_popup_v6* xdg_popup,
++                                   struct xdg_popup* xdg_popup,
+                                    int32_t x,
+                                    int32_t y,
+                                    int32_t width,
+                                    int32_t height) {
+   struct sl_host_xdg_popup* host =
+-      static_cast<sl_host_xdg_popup*>(zxdg_popup_v6_get_user_data(xdg_popup));
++      static_cast<sl_host_xdg_popup*>(xdg_popup_get_user_data(xdg_popup));
+   double scale = host->ctx->scale;
+   int32_t x1, y1, x2, y2;
+ 
+@@ -170,25 +168,24 @@ static void sl_xdg_popup_configure(void* data,
+   x2 = (x + width) * scale;
+   y2 = (y + height) * scale;
+ 
+-  zxdg_popup_v6_send_configure(host->resource, x1, y1, x2 - x1, y2 - y1);
++  xdg_popup_send_configure(host->resource, x1, y1, x2 - x1, y2 - y1);
+ }
+ 
+-static void sl_xdg_popup_popup_done(void* data,
+-                                    struct zxdg_popup_v6* xdg_popup) {
++static void sl_xdg_popup_popup_done(void* data, struct xdg_popup* xdg_popup) {
+   struct sl_host_xdg_popup* host =
+-      static_cast<sl_host_xdg_popup*>(zxdg_popup_v6_get_user_data(xdg_popup));
++      static_cast<sl_host_xdg_popup*>(xdg_popup_get_user_data(xdg_popup));
+ 
+-  zxdg_popup_v6_send_popup_done(host->resource);
++  xdg_popup_send_popup_done(host->resource);
+ }
+ 
+-static const struct zxdg_popup_v6_listener sl_xdg_popup_listener = {
++static const struct xdg_popup_listener sl_xdg_popup_listener = {
+     sl_xdg_popup_configure, sl_xdg_popup_popup_done};
+ 
+ static void sl_destroy_host_xdg_popup(struct wl_resource* resource) {
+   struct sl_host_xdg_popup* host =
+       static_cast<sl_host_xdg_popup*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_popup_v6_destroy(host->proxy);
++  xdg_popup_destroy(host->proxy);
+   wl_resource_set_user_data(resource, NULL);
+   free(host);
+ }
+@@ -208,8 +205,7 @@ static void sl_xdg_toplevel_set_parent(struct wl_client* client,
+                             wl_resource_get_user_data(parent_resource))
+                       : NULL;
+ 
+-  zxdg_toplevel_v6_set_parent(host->proxy,
+-                              host_parent ? host_parent->proxy : NULL);
++  xdg_toplevel_set_parent(host->proxy, host_parent ? host_parent->proxy : NULL);
+ }
+ 
+ static void sl_xdg_toplevel_set_title(struct wl_client* client,
+@@ -218,7 +214,7 @@ static void sl_xdg_toplevel_set_title(struct wl_client* client,
+   struct sl_host_xdg_toplevel* host =
+       static_cast<sl_host_xdg_toplevel*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_toplevel_v6_set_title(host->proxy, title);
++  xdg_toplevel_set_title(host->proxy, title);
+ }
+ 
+ static void sl_xdg_toplevel_set_app_id(struct wl_client* client,
+@@ -227,7 +223,7 @@ static void sl_xdg_toplevel_set_app_id(struct wl_client* client,
+   struct sl_host_xdg_toplevel* host =
+       static_cast<sl_host_xdg_toplevel*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_toplevel_v6_set_app_id(host->proxy, app_id);
++  xdg_toplevel_set_app_id(host->proxy, app_id);
+ }
+ 
+ static void sl_xdg_toplevel_show_window_menu(struct wl_client* client,
+@@ -243,7 +239,7 @@ static void sl_xdg_toplevel_show_window_menu(struct wl_client* client,
+           ? static_cast<sl_host_seat*>(wl_resource_get_user_data(seat_resource))
+           : NULL;
+ 
+-  zxdg_toplevel_v6_show_window_menu(
++  xdg_toplevel_show_window_menu(
+       host->proxy, host_seat ? host_seat->proxy : NULL, serial, x, y);
+ }  // NOLINT(whitespace/indent)
+ 
+@@ -258,8 +254,7 @@ static void sl_xdg_toplevel_move(struct wl_client* client,
+           ? static_cast<sl_host_seat*>(wl_resource_get_user_data(seat_resource))
+           : NULL;
+ 
+-  zxdg_toplevel_v6_move(host->proxy, host_seat ? host_seat->proxy : NULL,
+-                        serial);
++  xdg_toplevel_move(host->proxy, host_seat ? host_seat->proxy : NULL, serial);
+ }  // NOLINT(whitespace/indent)
+ 
+ static void sl_xdg_toplevel_resize(struct wl_client* client,
+@@ -274,8 +269,8 @@ static void sl_xdg_toplevel_resize(struct wl_client* client,
+           ? static_cast<sl_host_seat*>(wl_resource_get_user_data(seat_resource))
+           : NULL;
+ 
+-  zxdg_toplevel_v6_resize(host->proxy, host_seat ? host_seat->proxy : NULL,
+-                          serial, edges);
++  xdg_toplevel_resize(host->proxy, host_seat ? host_seat->proxy : NULL, serial,
++                      edges);
+ }  // NOLINT(whitespace/indent)
+ 
+ static void sl_xdg_toplevel_set_max_size(struct wl_client* client,
+@@ -285,7 +280,7 @@ static void sl_xdg_toplevel_set_max_size(struct wl_client* client,
+   struct sl_host_xdg_toplevel* host =
+       static_cast<sl_host_xdg_toplevel*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_toplevel_v6_set_max_size(host->proxy, width, height);
++  xdg_toplevel_set_max_size(host->proxy, width, height);
+ }
+ 
+ static void sl_xdg_toplevel_set_min_size(struct wl_client* client,
+@@ -295,7 +290,7 @@ static void sl_xdg_toplevel_set_min_size(struct wl_client* client,
+   struct sl_host_xdg_toplevel* host =
+       static_cast<sl_host_xdg_toplevel*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_toplevel_v6_set_min_size(host->proxy, width, height);
++  xdg_toplevel_set_min_size(host->proxy, width, height);
+ }
+ 
+ static void sl_xdg_toplevel_set_maximized(struct wl_client* client,
+@@ -303,7 +298,7 @@ static void sl_xdg_toplevel_set_maximized(struct wl_client* client,
+   struct sl_host_xdg_toplevel* host =
+       static_cast<sl_host_xdg_toplevel*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_toplevel_v6_set_maximized(host->proxy);
++  xdg_toplevel_set_maximized(host->proxy);
+ }
+ 
+ static void sl_xdg_toplevel_unset_maximized(struct wl_client* client,
+@@ -311,7 +306,7 @@ static void sl_xdg_toplevel_unset_maximized(struct wl_client* client,
+   struct sl_host_xdg_toplevel* host =
+       static_cast<sl_host_xdg_toplevel*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_toplevel_v6_unset_maximized(host->proxy);
++  xdg_toplevel_unset_maximized(host->proxy);
+ }
+ 
+ static void sl_xdg_toplevel_set_fullscreen(
+@@ -325,8 +320,8 @@ static void sl_xdg_toplevel_set_fullscreen(
+                             wl_resource_get_user_data(output_resource))
+                       : NULL;
+ 
+-  zxdg_toplevel_v6_set_fullscreen(host->proxy,
+-                                  host_output ? host_output->proxy : NULL);
++  xdg_toplevel_set_fullscreen(host->proxy,
++                              host_output ? host_output->proxy : NULL);
+ }  // NOLINT(whitespace/indent)
+ 
+ static void sl_xdg_toplevel_unset_fullscreen(struct wl_client* client,
+@@ -334,7 +329,7 @@ static void sl_xdg_toplevel_unset_fullscreen(struct wl_client* client,
+   struct sl_host_xdg_toplevel* host =
+       static_cast<sl_host_xdg_toplevel*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_toplevel_v6_unset_fullscreen(host->proxy);
++  xdg_toplevel_unset_fullscreen(host->proxy);
+ }
+ 
+ static void sl_xdg_toplevel_set_minimized(struct wl_client* client,
+@@ -342,47 +337,47 @@ static void sl_xdg_toplevel_set_minimized(struct wl_client* client,
+   struct sl_host_xdg_toplevel* host =
+       static_cast<sl_host_xdg_toplevel*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_toplevel_v6_set_minimized(host->proxy);
++  xdg_toplevel_set_minimized(host->proxy);
+ }
+ 
+-static const struct zxdg_toplevel_v6_interface sl_xdg_toplevel_implementation =
+-    {sl_xdg_toplevel_destroy,          sl_xdg_toplevel_set_parent,
+-     sl_xdg_toplevel_set_title,        sl_xdg_toplevel_set_app_id,
+-     sl_xdg_toplevel_show_window_menu, sl_xdg_toplevel_move,
+-     sl_xdg_toplevel_resize,           sl_xdg_toplevel_set_max_size,
+-     sl_xdg_toplevel_set_min_size,     sl_xdg_toplevel_set_maximized,
+-     sl_xdg_toplevel_unset_maximized,  sl_xdg_toplevel_set_fullscreen,
+-     sl_xdg_toplevel_unset_fullscreen, sl_xdg_toplevel_set_minimized};
++static const struct xdg_toplevel_interface sl_xdg_toplevel_implementation = {
++    sl_xdg_toplevel_destroy,          sl_xdg_toplevel_set_parent,
++    sl_xdg_toplevel_set_title,        sl_xdg_toplevel_set_app_id,
++    sl_xdg_toplevel_show_window_menu, sl_xdg_toplevel_move,
++    sl_xdg_toplevel_resize,           sl_xdg_toplevel_set_max_size,
++    sl_xdg_toplevel_set_min_size,     sl_xdg_toplevel_set_maximized,
++    sl_xdg_toplevel_unset_maximized,  sl_xdg_toplevel_set_fullscreen,
++    sl_xdg_toplevel_unset_fullscreen, sl_xdg_toplevel_set_minimized};
+ 
+ static void sl_xdg_toplevel_configure(void* data,
+-                                      struct zxdg_toplevel_v6* xdg_toplevel,
++                                      struct xdg_toplevel* xdg_toplevel,
+                                       int32_t width,
+                                       int32_t height,
+                                       struct wl_array* states) {
+   struct sl_host_xdg_toplevel* host = static_cast<sl_host_xdg_toplevel*>(
+-      zxdg_toplevel_v6_get_user_data(xdg_toplevel));
++      xdg_toplevel_get_user_data(xdg_toplevel));
+   double scale = host->ctx->scale;
+ 
+-  zxdg_toplevel_v6_send_configure(host->resource, width * scale, height * scale,
+-                                  states);
++  xdg_toplevel_send_configure(host->resource, width * scale, height * scale,
++                              states);
+ }
+ 
+ static void sl_xdg_toplevel_close(void* data,
+-                                  struct zxdg_toplevel_v6* xdg_toplevel) {
++                                  struct xdg_toplevel* xdg_toplevel) {
+   struct sl_host_xdg_toplevel* host = static_cast<sl_host_xdg_toplevel*>(
+-      zxdg_toplevel_v6_get_user_data(xdg_toplevel));
++      xdg_toplevel_get_user_data(xdg_toplevel));
+ 
+-  zxdg_toplevel_v6_send_close(host->resource);
++  xdg_toplevel_send_close(host->resource);
+ }
+ 
+-static const struct zxdg_toplevel_v6_listener sl_xdg_toplevel_listener = {
++static const struct xdg_toplevel_listener sl_xdg_toplevel_listener = {
+     sl_xdg_toplevel_configure, sl_xdg_toplevel_close};
+ 
+ static void sl_destroy_host_xdg_toplevel(struct wl_resource* resource) {
+   struct sl_host_xdg_toplevel* host =
+       static_cast<sl_host_xdg_toplevel*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_toplevel_v6_destroy(host->proxy);
++  xdg_toplevel_destroy(host->proxy);
+   wl_resource_set_user_data(resource, NULL);
+   free(host);
+ }
+@@ -403,14 +398,14 @@ static void sl_xdg_surface_get_toplevel(struct wl_client* client,
+ 
+   host_xdg_toplevel->ctx = host->ctx;
+   host_xdg_toplevel->resource =
+-      wl_resource_create(client, &zxdg_toplevel_v6_interface, 1, id);
++      wl_resource_create(client, &xdg_toplevel_interface, 1, id);
+   wl_resource_set_implementation(
+       host_xdg_toplevel->resource, &sl_xdg_toplevel_implementation,
+       host_xdg_toplevel, sl_destroy_host_xdg_toplevel);
+-  host_xdg_toplevel->proxy = zxdg_surface_v6_get_toplevel(host->proxy);
+-  zxdg_toplevel_v6_set_user_data(host_xdg_toplevel->proxy, host_xdg_toplevel);
+-  zxdg_toplevel_v6_add_listener(host_xdg_toplevel->proxy,
+-                                &sl_xdg_toplevel_listener, host_xdg_toplevel);
++  host_xdg_toplevel->proxy = xdg_surface_get_toplevel(host->proxy);
++  xdg_toplevel_set_user_data(host_xdg_toplevel->proxy, host_xdg_toplevel);
++  xdg_toplevel_add_listener(host_xdg_toplevel->proxy, &sl_xdg_toplevel_listener,
++                            host_xdg_toplevel);
+ }
+ 
+ static void sl_xdg_surface_get_popup(struct wl_client* client,
+@@ -421,7 +416,7 @@ static void sl_xdg_surface_get_popup(struct wl_client* client,
+   struct sl_host_xdg_surface* host =
+       static_cast<sl_host_xdg_surface*>(wl_resource_get_user_data(resource));
+   struct sl_host_xdg_surface* host_parent = static_cast<sl_host_xdg_surface*>(
+-      wl_resource_get_user_data(parent_resource));
++      parent_resource ? wl_resource_get_user_data(parent_resource) : NULL);
+   struct sl_host_xdg_positioner* host_positioner =
+       static_cast<sl_host_xdg_positioner*>(
+           wl_resource_get_user_data(positioner_resource));
+@@ -431,15 +426,16 @@ static void sl_xdg_surface_get_popup(struct wl_client* client,
+ 
+   host_xdg_popup->ctx = host->ctx;
+   host_xdg_popup->resource =
+-      wl_resource_create(client, &zxdg_popup_v6_interface, 1, id);
++      wl_resource_create(client, &xdg_popup_interface, 1, id);
+   wl_resource_set_implementation(host_xdg_popup->resource,
+                                  &sl_xdg_popup_implementation, host_xdg_popup,
+                                  sl_destroy_host_xdg_popup);
+-  host_xdg_popup->proxy = zxdg_surface_v6_get_popup(
+-      host->proxy, host_parent->proxy, host_positioner->proxy);
+-  zxdg_popup_v6_set_user_data(host_xdg_popup->proxy, host_xdg_popup);
+-  zxdg_popup_v6_add_listener(host_xdg_popup->proxy, &sl_xdg_popup_listener,
+-                             host_xdg_popup);
++  host_xdg_popup->proxy = xdg_surface_get_popup(
++      host->proxy, host_parent ? host_parent->proxy : NULL,
++      host_positioner->proxy);
++  xdg_popup_set_user_data(host_xdg_popup->proxy, host_xdg_popup);
++  xdg_popup_add_listener(host_xdg_popup->proxy, &sl_xdg_popup_listener,
++                         host_xdg_popup);
+ }  // NOLINT(whitespace/indent)
+ 
+ static void sl_xdg_surface_set_window_geometry(struct wl_client* client,
+@@ -458,7 +454,7 @@ static void sl_xdg_surface_set_window_geometry(struct wl_client* client,
+   x2 = (x + width) / scale;
+   y2 = (y + height) / scale;
+ 
+-  zxdg_surface_v6_set_window_geometry(host->proxy, x1, y1, x2 - x1, y2 - y1);
++  xdg_surface_set_window_geometry(host->proxy, x1, y1, x2 - x1, y2 - y1);
+ }
+ 
+ static void sl_xdg_surface_ack_configure(struct wl_client* client,
+@@ -467,31 +463,31 @@ static void sl_xdg_surface_ack_configure(struct wl_client* client,
+   struct sl_host_xdg_surface* host =
+       static_cast<sl_host_xdg_surface*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_surface_v6_ack_configure(host->proxy, serial);
++  xdg_surface_ack_configure(host->proxy, serial);
+ }
+ 
+-static const struct zxdg_surface_v6_interface sl_xdg_surface_implementation = {
++static const struct xdg_surface_interface sl_xdg_surface_implementation = {
+     sl_xdg_surface_destroy, sl_xdg_surface_get_toplevel,
+     sl_xdg_surface_get_popup, sl_xdg_surface_set_window_geometry,
+     sl_xdg_surface_ack_configure};
+ 
+ static void sl_xdg_surface_configure(void* data,
+-                                     struct zxdg_surface_v6* xdg_surface,
++                                     struct xdg_surface* xdg_surface,
+                                      uint32_t serial) {
+   struct sl_host_xdg_surface* host = static_cast<sl_host_xdg_surface*>(
+-      zxdg_surface_v6_get_user_data(xdg_surface));
++      xdg_surface_get_user_data(xdg_surface));
+ 
+-  zxdg_surface_v6_send_configure(host->resource, serial);
++  xdg_surface_send_configure(host->resource, serial);
+ }
+ 
+-static const struct zxdg_surface_v6_listener sl_xdg_surface_listener = {
++static const struct xdg_surface_listener sl_xdg_surface_listener = {
+     sl_xdg_surface_configure};
+ 
+ static void sl_destroy_host_xdg_surface(struct wl_resource* resource) {
+   struct sl_host_xdg_surface* host =
+       static_cast<sl_host_xdg_surface*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_surface_v6_destroy(host->proxy);
++  xdg_surface_destroy(host->proxy);
+   wl_resource_set_user_data(resource, NULL);
+   free(host);
+ }
+@@ -513,13 +509,12 @@ static void sl_xdg_shell_create_positioner(struct wl_client* client,
+ 
+   host_xdg_positioner->ctx = host->ctx;
+   host_xdg_positioner->resource =
+-      wl_resource_create(client, &zxdg_positioner_v6_interface, 1, id);
++      wl_resource_create(client, &xdg_positioner_interface, 1, id);
+   wl_resource_set_implementation(
+       host_xdg_positioner->resource, &sl_xdg_positioner_implementation,
+       host_xdg_positioner, sl_destroy_host_xdg_positioner);
+-  host_xdg_positioner->proxy = zxdg_shell_v6_create_positioner(host->proxy);
+-  zxdg_positioner_v6_set_user_data(host_xdg_positioner->proxy,
+-                                   host_xdg_positioner);
++  host_xdg_positioner->proxy = xdg_wm_base_create_positioner(host->proxy);
++  xdg_positioner_set_user_data(host_xdg_positioner->proxy, host_xdg_positioner);
+ }
+ 
+ static void sl_xdg_shell_get_xdg_surface(struct wl_client* client,
+@@ -536,15 +531,15 @@ static void sl_xdg_shell_get_xdg_surface(struct wl_client* client,
+ 
+   host_xdg_surface->ctx = host->ctx;
+   host_xdg_surface->resource =
+-      wl_resource_create(client, &zxdg_surface_v6_interface, 1, id);
++      wl_resource_create(client, &xdg_surface_interface, 1, id);
+   wl_resource_set_implementation(host_xdg_surface->resource,
+                                  &sl_xdg_surface_implementation,
+                                  host_xdg_surface, sl_destroy_host_xdg_surface);
+   host_xdg_surface->proxy =
+-      zxdg_shell_v6_get_xdg_surface(host->proxy, host_surface->proxy);
+-  zxdg_surface_v6_set_user_data(host_xdg_surface->proxy, host_xdg_surface);
+-  zxdg_surface_v6_add_listener(host_xdg_surface->proxy,
+-                               &sl_xdg_surface_listener, host_xdg_surface);
++      xdg_wm_base_get_xdg_surface(host->proxy, host_surface->proxy);
++  xdg_surface_set_user_data(host_xdg_surface->proxy, host_xdg_surface);
++  xdg_surface_add_listener(host_xdg_surface->proxy, &sl_xdg_surface_listener,
++                           host_xdg_surface);
+   host_surface->has_role = 1;
+ }
+ 
+@@ -554,30 +549,30 @@ static void sl_xdg_shell_pong(struct wl_client* client,
+   struct sl_host_xdg_shell* host =
+       static_cast<sl_host_xdg_shell*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_shell_v6_pong(host->proxy, serial);
++  xdg_wm_base_pong(host->proxy, serial);
+ }
+ 
+-static const struct zxdg_shell_v6_interface sl_xdg_shell_implementation = {
++static const struct xdg_wm_base_interface sl_xdg_shell_implementation = {
+     sl_xdg_shell_destroy, sl_xdg_shell_create_positioner,
+     sl_xdg_shell_get_xdg_surface, sl_xdg_shell_pong};
+ 
+ static void sl_xdg_shell_ping(void* data,
+-                              struct zxdg_shell_v6* xdg_shell,
++                              struct xdg_wm_base* xdg_shell,
+                               uint32_t serial) {
+   struct sl_host_xdg_shell* host =
+-      static_cast<sl_host_xdg_shell*>(zxdg_shell_v6_get_user_data(xdg_shell));
++      static_cast<sl_host_xdg_shell*>(xdg_wm_base_get_user_data(xdg_shell));
+ 
+-  zxdg_shell_v6_send_ping(host->resource, serial);
++  xdg_wm_base_send_ping(host->resource, serial);
+ }
+ 
+-static const struct zxdg_shell_v6_listener sl_xdg_shell_listener = {
++static const struct xdg_wm_base_listener sl_xdg_shell_listener = {
+     sl_xdg_shell_ping};
+ 
+ static void sl_destroy_host_xdg_shell(struct wl_resource* resource) {
+   struct sl_host_xdg_shell* host =
+       static_cast<sl_host_xdg_shell*>(wl_resource_get_user_data(resource));
+ 
+-  zxdg_shell_v6_destroy(host->proxy);
++  xdg_wm_base_destroy(host->proxy);
+   wl_resource_set_user_data(resource, NULL);
+   free(host);
+ }
+@@ -591,17 +586,17 @@ static void sl_bind_host_xdg_shell(struct wl_client* client,
+       static_cast<sl_host_xdg_shell*>(malloc(sizeof(*host)));
+   assert(host);
+   host->ctx = ctx;
+-  host->resource = wl_resource_create(client, &zxdg_shell_v6_interface, 1, id);
++  host->resource = wl_resource_create(client, &xdg_wm_base_interface, 1, id);
+   wl_resource_set_implementation(host->resource, &sl_xdg_shell_implementation,
+                                  host, sl_destroy_host_xdg_shell);
+-  host->proxy = static_cast<zxdg_shell_v6*>(
++  host->proxy = static_cast<xdg_wm_base*>(
+       wl_registry_bind(wl_display_get_registry(ctx->display),
+-                       ctx->xdg_shell->id, &zxdg_shell_v6_interface, 1));
+-  zxdg_shell_v6_set_user_data(host->proxy, host);
+-  zxdg_shell_v6_add_listener(host->proxy, &sl_xdg_shell_listener, host);
++                       ctx->xdg_shell->id, &xdg_wm_base_interface, 1));
++  xdg_wm_base_set_user_data(host->proxy, host);
++  xdg_wm_base_add_listener(host->proxy, &sl_xdg_shell_listener, host);
+ }
+ 
+ struct sl_global* sl_xdg_shell_global_create(struct sl_context* ctx) {
+-  return sl_global_create(ctx, &zxdg_shell_v6_interface, 1, ctx,
++  return sl_global_create(ctx, &xdg_wm_base_interface, 1, ctx,
+                           sl_bind_host_xdg_shell);
+ }
+diff --git a/vm_tools/sommelier/sommelier.cc b/vm_tools/sommelier/sommelier.cc
+index 2833e52a61..8f53b3b65b 100644
+--- a/vm_tools/sommelier/sommelier.cc
++++ b/vm_tools/sommelier/sommelier.cc
+@@ -39,7 +39,7 @@
+ #include "relative-pointer-unstable-v1-client-protocol.h"  // NOLINT(build/include_directory)
+ #include "text-input-unstable-v1-client-protocol.h"  // NOLINT(build/include_directory)
+ #include "viewporter-client-protocol.h"  // NOLINT(build/include_directory)
+-#include "xdg-shell-unstable-v6-client-protocol.h"  // NOLINT(build/include_directory)
++#include "xdg-shell-client-protocol.h"  // NOLINT(build/include_directory)
+ 
+ #define errno_assert(rv)                                          \
+   {                                                               \
+@@ -372,13 +372,13 @@ void sl_sync_point_destroy(struct sl_sync_point* sync_point) {
+ }
+ 
+ static void sl_internal_xdg_shell_ping(void* data,
+-                                       struct zxdg_shell_v6* xdg_shell,
++                                       struct xdg_wm_base* xdg_shell,
+                                        uint32_t serial) {
+   TRACE_EVENT("shell", "sl_internal_xdg_shell_ping");
+-  zxdg_shell_v6_pong(xdg_shell, serial);
++  xdg_wm_base_pong(xdg_shell, serial);
+ }
+ 
+-static const struct zxdg_shell_v6_listener sl_internal_xdg_shell_listener = {
++static const struct xdg_wm_base_listener sl_internal_xdg_shell_listener = {
+     sl_internal_xdg_shell_ping};
+ 
+ static void sl_send_configure_notify(struct sl_window* window) {
+@@ -549,8 +549,8 @@ int sl_process_pending_configure_acks(struct sl_window* window,
+   }
+ 
+   if (window->xdg_surface) {
+-    zxdg_surface_v6_ack_configure(window->xdg_surface,
+-                                  window->pending_config.serial);
++    xdg_surface_ack_configure(window->xdg_surface,
++                              window->pending_config.serial);
+   }
+   window->pending_config.serial = 0;
+ 
+@@ -567,11 +567,12 @@ void sl_commit(struct sl_window* window, struct sl_host_surface* host_surface) {
+   }
+ }
+ 
+-static void sl_internal_xdg_surface_configure(
+-    void* data, struct zxdg_surface_v6* xdg_surface, uint32_t serial) {
++static void sl_internal_xdg_surface_configure(void* data,
++                                              struct xdg_surface* xdg_surface,
++                                              uint32_t serial) {
+   TRACE_EVENT("surface", "sl_internal_xdg_surface_configure");
+   struct sl_window* window =
+-      static_cast<sl_window*>(zxdg_surface_v6_get_user_data(xdg_surface));
++      static_cast<sl_window*>(xdg_surface_get_user_data(xdg_surface));
+ 
+   window->next_config.serial = serial;
+   if (!window->pending_config.serial) {
+@@ -589,18 +590,18 @@ static void sl_internal_xdg_surface_configure(
+   }
+ }
+ 
+-static const struct zxdg_surface_v6_listener sl_internal_xdg_surface_listener =
+-    {sl_internal_xdg_surface_configure};
++static const struct xdg_surface_listener sl_internal_xdg_surface_listener = {
++    sl_internal_xdg_surface_configure};
+ 
+ static void sl_internal_xdg_toplevel_configure(
+     void* data,
+-    struct zxdg_toplevel_v6* xdg_toplevel,
++    struct xdg_toplevel* xdg_toplevel,
+     int32_t width,
+     int32_t height,
+     struct wl_array* states) {
+   TRACE_EVENT("other", "sl_internal_xdg_toplevel_configure");
+   struct sl_window* window =
+-      static_cast<sl_window*>(zxdg_toplevel_v6_get_user_data(xdg_toplevel));
++      static_cast<sl_window*>(xdg_toplevel_get_user_data(xdg_toplevel));
+   int activated = 0;
+   uint32_t* state;
+   int i = 0;
+@@ -630,24 +631,24 @@ static void sl_internal_xdg_toplevel_configure(
+ 
+   window->allow_resize = 1;
+   sl_array_for_each(state, states) {
+-    if (*state == ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN) {
++    if (*state == XDG_TOPLEVEL_STATE_FULLSCREEN) {
+       window->allow_resize = 0;
+       window->next_config.states[i++] =
+           window->ctx->atoms[ATOM_NET_WM_STATE_FULLSCREEN].value;
+     }
+-    if (*state == ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED) {
++    if (*state == XDG_TOPLEVEL_STATE_MAXIMIZED) {
+       window->allow_resize = 0;
+       window->next_config.states[i++] =
+           window->ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_VERT].value;
+       window->next_config.states[i++] =
+           window->ctx->atoms[ATOM_NET_WM_STATE_MAXIMIZED_HORZ].value;
+     }
+-    if (*state == ZXDG_TOPLEVEL_V6_STATE_ACTIVATED) {
++    if (*state == XDG_TOPLEVEL_STATE_ACTIVATED) {
+       activated = 1;
+       window->next_config.states[i++] =
+           window->ctx->atoms[ATOM_NET_WM_STATE_FOCUSED].value;
+     }
+-    if (*state == ZXDG_TOPLEVEL_V6_STATE_RESIZING)
++    if (*state == XDG_TOPLEVEL_STATE_RESIZING)
+       window->allow_resize = 0;
+   }
+ 
+@@ -662,11 +663,11 @@ static void sl_internal_xdg_toplevel_configure(
+   window->next_config.states_length = i;
+ }
+ 
+-static void sl_internal_xdg_toplevel_close(
+-    void* data, struct zxdg_toplevel_v6* xdg_toplevel) {
++static void sl_internal_xdg_toplevel_close(void* data,
++                                           struct xdg_toplevel* xdg_toplevel) {
+   TRACE_EVENT("other", "sl_internal_xdg_toplevel_close");
+   struct sl_window* window =
+-      static_cast<sl_window*>(zxdg_toplevel_v6_get_user_data(xdg_toplevel));
++      static_cast<sl_window*>(xdg_toplevel_get_user_data(xdg_toplevel));
+   xcb_client_message_event_t event = {};
+   event.response_type = XCB_CLIENT_MESSAGE;
+   event.format = 32;
+@@ -679,21 +680,20 @@ static void sl_internal_xdg_toplevel_close(
+                  XCB_EVENT_MASK_NO_EVENT, (const char*)&event);
+ }
+ 
+-static const struct zxdg_toplevel_v6_listener
+-    sl_internal_xdg_toplevel_listener = {sl_internal_xdg_toplevel_configure,
+-                                         sl_internal_xdg_toplevel_close};
++static const struct xdg_toplevel_listener sl_internal_xdg_toplevel_listener = {
++    sl_internal_xdg_toplevel_configure, sl_internal_xdg_toplevel_close};
+ 
+ static void sl_internal_xdg_popup_configure(void* data,
+-                                            struct zxdg_popup_v6* xdg_popup,
++                                            struct xdg_popup* xdg_popup,
+                                             int32_t x,
+                                             int32_t y,
+                                             int32_t width,
+                                             int32_t height) {}
+ 
+ static void sl_internal_xdg_popup_done(void* data,
+-                                       struct zxdg_popup_v6* zxdg_popup_v6) {}
++                                       struct xdg_popup* xdg_popup) {}
+ 
+-static const struct zxdg_popup_v6_listener sl_internal_xdg_popup_listener = {
++static const struct xdg_popup_listener sl_internal_xdg_popup_listener = {
+     sl_internal_xdg_popup_configure, sl_internal_xdg_popup_done};
+ 
+ static void sl_window_set_wm_state(struct sl_window* window, int state) {
+@@ -765,15 +765,15 @@ void sl_window_update(struct sl_window* window) {
+       window->aura_surface = NULL;
+     }
+     if (window->xdg_toplevel) {
+-      zxdg_toplevel_v6_destroy(window->xdg_toplevel);
++      xdg_toplevel_destroy(window->xdg_toplevel);
+       window->xdg_toplevel = NULL;
+     }
+     if (window->xdg_popup) {
+-      zxdg_popup_v6_destroy(window->xdg_popup);
++      xdg_popup_destroy(window->xdg_popup);
+       window->xdg_popup = NULL;
+     }
+     if (window->xdg_surface) {
+-      zxdg_surface_v6_destroy(window->xdg_surface);
++      xdg_surface_destroy(window->xdg_surface);
+       window->xdg_surface = NULL;
+     }
+     window->realized = 0;
+@@ -849,11 +849,11 @@ void sl_window_update(struct sl_window* window) {
+   }
+ 
+   if (!window->xdg_surface) {
+-    window->xdg_surface = zxdg_shell_v6_get_xdg_surface(
+-        ctx->xdg_shell->internal, host_surface->proxy);
+-    zxdg_surface_v6_set_user_data(window->xdg_surface, window);
+-    zxdg_surface_v6_add_listener(window->xdg_surface,
+-                                 &sl_internal_xdg_surface_listener, window);
++    window->xdg_surface = xdg_wm_base_get_xdg_surface(ctx->xdg_shell->internal,
++                                                      host_surface->proxy);
++    xdg_surface_set_user_data(window->xdg_surface, window);
++    xdg_surface_add_listener(window->xdg_surface,
++                             &sl_internal_xdg_surface_listener, window);
+   }
+ 
+   if (ctx->aura_shell) {
+@@ -888,50 +888,46 @@ void sl_window_update(struct sl_window* window) {
+   // window is closed.
+   if (ctx->xwayland || !parent) {
+     if (!window->xdg_toplevel) {
+-      window->xdg_toplevel = zxdg_surface_v6_get_toplevel(window->xdg_surface);
+-      zxdg_toplevel_v6_set_user_data(window->xdg_toplevel, window);
+-      zxdg_toplevel_v6_add_listener(window->xdg_toplevel,
+-                                    &sl_internal_xdg_toplevel_listener, window);
++      window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface);
++      xdg_toplevel_set_user_data(window->xdg_toplevel, window);
++      xdg_toplevel_add_listener(window->xdg_toplevel,
++                                &sl_internal_xdg_toplevel_listener, window);
+     }
+     if (parent)
+-      zxdg_toplevel_v6_set_parent(window->xdg_toplevel, parent->xdg_toplevel);
++      xdg_toplevel_set_parent(window->xdg_toplevel, parent->xdg_toplevel);
+     if (window->name)
+-      zxdg_toplevel_v6_set_title(window->xdg_toplevel, window->name);
++      xdg_toplevel_set_title(window->xdg_toplevel, window->name);
+     if (window->size_flags & P_MIN_SIZE) {
+-      zxdg_toplevel_v6_set_min_size(window->xdg_toplevel,
+-                                    window->min_width / ctx->scale,
+-                                    window->min_height / ctx->scale);
++      xdg_toplevel_set_min_size(window->xdg_toplevel,
++                                window->min_width / ctx->scale,
++                                window->min_height / ctx->scale);
+     }
+     if (window->size_flags & P_MAX_SIZE) {
+-      zxdg_toplevel_v6_set_max_size(window->xdg_toplevel,
+-                                    window->max_width / ctx->scale,
+-                                    window->max_height / ctx->scale);
++      xdg_toplevel_set_max_size(window->xdg_toplevel,
++                                window->max_width / ctx->scale,
++                                window->max_height / ctx->scale);
+     }
+     if (window->maximized) {
+-      zxdg_toplevel_v6_set_maximized(window->xdg_toplevel);
++      xdg_toplevel_set_maximized(window->xdg_toplevel);
+     }
+   } else if (!window->xdg_popup) {
+-    struct zxdg_positioner_v6* positioner;
++    struct xdg_positioner* positioner;
+ 
+-    positioner = zxdg_shell_v6_create_positioner(ctx->xdg_shell->internal);
++    positioner = xdg_wm_base_create_positioner(ctx->xdg_shell->internal);
+     assert(positioner);
+-    zxdg_positioner_v6_set_anchor(
+-        positioner,
+-        ZXDG_POSITIONER_V6_ANCHOR_TOP | ZXDG_POSITIONER_V6_ANCHOR_LEFT);
+-    zxdg_positioner_v6_set_gravity(
+-        positioner,
+-        ZXDG_POSITIONER_V6_GRAVITY_BOTTOM | ZXDG_POSITIONER_V6_GRAVITY_RIGHT);
+-    zxdg_positioner_v6_set_anchor_rect(
+-        positioner, (window->x - parent->x) / ctx->scale,
+-        (window->y - parent->y) / ctx->scale, 1, 1);
++    xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT);
++    xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);
++    xdg_positioner_set_anchor_rect(positioner,
++                                   (window->x - parent->x) / ctx->scale,
++                                   (window->y - parent->y) / ctx->scale, 1, 1);
+ 
+-    window->xdg_popup = zxdg_surface_v6_get_popup(
+-        window->xdg_surface, parent->xdg_surface, positioner);
+-    zxdg_popup_v6_set_user_data(window->xdg_popup, window);
+-    zxdg_popup_v6_add_listener(window->xdg_popup,
+-                               &sl_internal_xdg_popup_listener, window);
++    window->xdg_popup = xdg_surface_get_popup(window->xdg_surface,
++                                              parent->xdg_surface, positioner);
++    xdg_popup_set_user_data(window->xdg_popup, window);
++    xdg_popup_add_listener(window->xdg_popup, &sl_internal_xdg_popup_listener,
++                           window);
+ 
+-    zxdg_positioner_v6_destroy(positioner);
++    xdg_positioner_destroy(positioner);
+   }
+ 
+   if ((window->size_flags & (US_POSITION | P_POSITION)) && parent &&
+@@ -1332,7 +1328,7 @@ static void sl_registry_handler(void* data,
+       data_device_manager->host_global =
+           sl_data_device_manager_global_create(ctx);
+     }
+-  } else if (strcmp(interface, "zxdg_shell_v6") == 0) {
++  } else if (strcmp(interface, "xdg_wm_base") == 0) {
+     struct sl_xdg_shell* xdg_shell =
+         static_cast<sl_xdg_shell*>(malloc(sizeof(struct sl_xdg_shell)));
+     assert(xdg_shell);
+@@ -1343,10 +1339,10 @@ static void sl_registry_handler(void* data,
+     assert(!ctx->xdg_shell);
+     ctx->xdg_shell = xdg_shell;
+     if (ctx->xwayland) {
+-      xdg_shell->internal = static_cast<zxdg_shell_v6*>(
+-          wl_registry_bind(registry, id, &zxdg_shell_v6_interface, 1));
+-      zxdg_shell_v6_add_listener(xdg_shell->internal,
+-                                 &sl_internal_xdg_shell_listener, NULL);
++      xdg_shell->internal = static_cast<xdg_wm_base*>(
++          wl_registry_bind(registry, id, &xdg_wm_base_interface, 1));
++      xdg_wm_base_add_listener(xdg_shell->internal,
++                               &sl_internal_xdg_shell_listener, NULL);
+     } else {
+       xdg_shell->host_global = sl_xdg_shell_global_create(ctx);
+     }
+@@ -1479,7 +1475,7 @@ static void sl_registry_remover(void* data,
+     if (ctx->xdg_shell->host_global)
+       sl_global_destroy(ctx->xdg_shell->host_global);
+     if (ctx->xdg_shell->internal)
+-      zxdg_shell_v6_destroy(ctx->xdg_shell->internal);
++      xdg_wm_base_destroy(ctx->xdg_shell->internal);
+     free(ctx->xdg_shell);
+     ctx->xdg_shell = NULL;
+     return;
+@@ -1656,11 +1652,11 @@ static void sl_destroy_window(struct sl_window* window) {
+   }
+ 
+   if (window->xdg_popup)
+-    zxdg_popup_v6_destroy(window->xdg_popup);
++    xdg_popup_destroy(window->xdg_popup);
+   if (window->xdg_toplevel)
+-    zxdg_toplevel_v6_destroy(window->xdg_toplevel);
++    xdg_toplevel_destroy(window->xdg_toplevel);
+   if (window->xdg_surface)
+-    zxdg_surface_v6_destroy(window->xdg_surface);
++    xdg_surface_destroy(window->xdg_surface);
+   if (window->aura_surface)
+     zaura_surface_destroy(window->aura_surface);
+ 
+@@ -2124,15 +2120,15 @@ static void sl_handle_configure_request(struct sl_context* ctx,
+   // that matching contents will arrive.
+   if (window->xdg_toplevel) {
+     if (window->pending_config.serial) {
+-      zxdg_surface_v6_ack_configure(window->xdg_surface,
+-                                    window->pending_config.serial);
++      xdg_surface_ack_configure(window->xdg_surface,
++                                window->pending_config.serial);
+       window->pending_config.serial = 0;
+       window->pending_config.mask = 0;
+       window->pending_config.states_length = 0;
+     }
+     if (window->next_config.serial) {
+-      zxdg_surface_v6_ack_configure(window->xdg_surface,
+-                                    window->next_config.serial);
++      xdg_surface_ack_configure(window->xdg_surface,
++                                window->next_config.serial);
+       window->next_config.serial = 0;
+       window->next_config.mask = 0;
+       window->next_config.states_length = 0;
+@@ -2253,23 +2249,23 @@ static void sl_handle_configure_notify(struct sl_context* ctx,
+ static uint32_t sl_resize_edge(int net_wm_moveresize_size) {
+   switch (net_wm_moveresize_size) {
+     case NET_WM_MOVERESIZE_SIZE_TOPLEFT:
+-      return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_LEFT;
++      return XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT;
+     case NET_WM_MOVERESIZE_SIZE_TOP:
+-      return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP;
++      return XDG_TOPLEVEL_RESIZE_EDGE_TOP;
+     case NET_WM_MOVERESIZE_SIZE_TOPRIGHT:
+-      return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_TOP_RIGHT;
++      return XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT;
+     case NET_WM_MOVERESIZE_SIZE_RIGHT:
+-      return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_RIGHT;
++      return XDG_TOPLEVEL_RESIZE_EDGE_RIGHT;
+     case NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT:
+-      return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_RIGHT;
++      return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT;
+     case NET_WM_MOVERESIZE_SIZE_BOTTOM:
+-      return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM;
++      return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM;
+     case NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT:
+-      return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_BOTTOM_LEFT;
++      return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT;
+     case NET_WM_MOVERESIZE_SIZE_LEFT:
+-      return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_LEFT;
++      return XDG_TOPLEVEL_RESIZE_EDGE_LEFT;
+     default:
+-      return ZXDG_TOPLEVEL_V6_RESIZE_EDGE_NONE;
++      return XDG_TOPLEVEL_RESIZE_EDGE_NONE;
+   }
+ }
+ 
+@@ -2368,16 +2364,16 @@ static void sl_handle_client_message(struct sl_context* ctx,
+         return;
+ 
+       if (event->data.data32[2] == NET_WM_MOVERESIZE_MOVE) {
+-        zxdg_toplevel_v6_move(window->xdg_toplevel, seat->proxy,
+-                              seat->seat->last_serial);
++        xdg_toplevel_move(window->xdg_toplevel, seat->proxy,
++                          seat->seat->last_serial);
+       } else {
+         uint32_t edge = sl_resize_edge(event->data.data32[2]);
+ 
+-        if (edge == ZXDG_TOPLEVEL_V6_RESIZE_EDGE_NONE)
++        if (edge == XDG_TOPLEVEL_RESIZE_EDGE_NONE)
+           return;
+ 
+-        zxdg_toplevel_v6_resize(window->xdg_toplevel, seat->proxy,
+-                                seat->seat->last_serial, edge);
++        xdg_toplevel_resize(window->xdg_toplevel, seat->proxy,
++                            seat->seat->last_serial, edge);
+       }
+     }
+   } else if (event->type == ctx->atoms[ATOM_NET_WM_STATE].value) {
+@@ -2398,9 +2394,9 @@ static void sl_handle_client_message(struct sl_context* ctx,
+                     "action", net_wm_state_to_string(action), "window->name",
+                     window->name);
+         if (action == NET_WM_STATE_ADD)
+-          zxdg_toplevel_v6_set_fullscreen(window->xdg_toplevel, NULL);
++          xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL);
+         else if (action == NET_WM_STATE_REMOVE)
+-          zxdg_toplevel_v6_unset_fullscreen(window->xdg_toplevel);
++          xdg_toplevel_unset_fullscreen(window->xdg_toplevel);
+       }
+ 
+       if (changed[ATOM_NET_WM_STATE_MAXIMIZED_VERT] &&
+@@ -2411,9 +2407,9 @@ static void sl_handle_client_message(struct sl_context* ctx,
+             "action", net_wm_state_to_string(action), "window->name",
+             window->name);
+         if (action == NET_WM_STATE_ADD)
+-          zxdg_toplevel_v6_set_maximized(window->xdg_toplevel);
++          xdg_toplevel_set_maximized(window->xdg_toplevel);
+         else if (action == NET_WM_STATE_REMOVE)
+-          zxdg_toplevel_v6_unset_maximized(window->xdg_toplevel);
++          xdg_toplevel_unset_maximized(window->xdg_toplevel);
+       }
+     }
+   } else if (event->type == ctx->atoms[ATOM_WM_CHANGE_STATE].value &&
+@@ -2422,7 +2418,7 @@ static void sl_handle_client_message(struct sl_context* ctx,
+     TRACE_EVENT("x11wm", "XCB_CLIENT_MESSAGE: WM_STATE_ICONIC (minimize)",
+                 "window->name", window ? window->name : "<unknown>");
+     if (window && window->xdg_toplevel) {
+-      zxdg_toplevel_v6_set_minimized(window->xdg_toplevel);
++      xdg_toplevel_set_minimized(window->xdg_toplevel);
+     }
+   }
+ }
+@@ -2435,7 +2431,7 @@ static void sl_handle_focus_in(struct sl_context* ctx,
+     // window was realized.
+     struct sl_window* parent = sl_lookup_window(ctx, window->transient_for);
+     if (parent && parent->xdg_toplevel && window->xdg_toplevel)
+-      zxdg_toplevel_v6_set_parent(window->xdg_toplevel, parent->xdg_toplevel);
++      xdg_toplevel_set_parent(window->xdg_toplevel, parent->xdg_toplevel);
+   }
+ }
+ 
+@@ -2744,9 +2740,9 @@ static void sl_handle_property_notify(struct sl_context* ctx,
+       return;
+ 
+     if (window->name) {
+-      zxdg_toplevel_v6_set_title(window->xdg_toplevel, window->name);
++      xdg_toplevel_set_title(window->xdg_toplevel, window->name);
+     } else {
+-      zxdg_toplevel_v6_set_title(window->xdg_toplevel, "");
++      xdg_toplevel_set_title(window->xdg_toplevel, "");
+     }
+   } else if (event->atom == XCB_ATOM_WM_CLASS) {
+     struct sl_window* window = sl_lookup_window(ctx, event->window);
+@@ -2802,19 +2798,19 @@ static void sl_handle_property_notify(struct sl_context* ctx,
+       return;
+ 
+     if (window->size_flags & P_MIN_SIZE) {
+-      zxdg_toplevel_v6_set_min_size(window->xdg_toplevel,
+-                                    window->min_width / ctx->scale,
+-                                    window->min_height / ctx->scale);
++      xdg_toplevel_set_min_size(window->xdg_toplevel,
++                                window->min_width / ctx->scale,
++                                window->min_height / ctx->scale);
+     } else {
+-      zxdg_toplevel_v6_set_min_size(window->xdg_toplevel, 0, 0);
++      xdg_toplevel_set_min_size(window->xdg_toplevel, 0, 0);
+     }
+ 
+     if (window->size_flags & P_MAX_SIZE) {
+-      zxdg_toplevel_v6_set_max_size(window->xdg_toplevel,
+-                                    window->max_width / ctx->scale,
+-                                    window->max_height / ctx->scale);
++      xdg_toplevel_set_max_size(window->xdg_toplevel,
++                                window->max_width / ctx->scale,
++                                window->max_height / ctx->scale);
+     } else {
+-      zxdg_toplevel_v6_set_max_size(window->xdg_toplevel, 0, 0);
++      xdg_toplevel_set_max_size(window->xdg_toplevel, 0, 0);
+     }
+   } else if (event->atom == XCB_ATOM_WM_HINTS) {
+     struct sl_window* window = sl_lookup_window(ctx, event->window);
+diff --git a/vm_tools/sommelier/sommelier.h b/vm_tools/sommelier/sommelier.h
+index 79bcf6a3b3..d3157cd8a9 100644
+--- a/vm_tools/sommelier/sommelier.h
++++ b/vm_tools/sommelier/sommelier.h
+@@ -400,7 +400,7 @@ struct sl_xdg_shell {
+   struct sl_context* ctx;
+   uint32_t id;
+   struct sl_global* host_global;
+-  struct zxdg_shell_v6* internal;
++  struct xdg_wm_base* internal;
+ };
+ 
+ struct sl_aura_shell {
+@@ -500,9 +500,9 @@ struct sl_window {
+   int max_height;
+   struct sl_config next_config;
+   struct sl_config pending_config;
+-  struct zxdg_surface_v6* xdg_surface;
+-  struct zxdg_toplevel_v6* xdg_toplevel;
+-  struct zxdg_popup_v6* xdg_popup;
++  struct xdg_surface* xdg_surface;
++  struct xdg_toplevel* xdg_toplevel;
++  struct xdg_popup* xdg_popup;
+   struct zaura_surface* aura_surface;
+   struct wl_list link;
+ };
+-- 
+2.32.0
+
diff --git a/pkgs/os-specific/linux/chromium-os/sommelier/default.nix b/pkgs/os-specific/linux/chromium-os/sommelier/default.nix
new file mode 100644
index 00000000000..b45ab330c34
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/sommelier/default.nix
@@ -0,0 +1,37 @@
+{ common-mk, lib
+, abseil-cpp, mesa, grpc, openssl, libdrm, xlibs, protobuf, wayland
+, libxkbcommon, vm_protos, linuxHeaders, c-ares, zlib
+}:
+
+common-mk {
+  platformSubdir = "vm_tools/sommelier";
+
+  platform2Patches = [
+    ./0005-sommelier-don-t-leak-source-absolute-paths.patch
+    ./0006-Revert-Revert-vm_tools-sommelier-Switch-to-the-stabl.patch
+  ];
+
+  buildInputs = [
+    abseil-cpp mesa grpc openssl libdrm protobuf wayland libxkbcommon vm_protos
+    linuxHeaders c-ares zlib
+  ] ++ (with xlibs; [ pixman libxcb libX11 ]);
+
+  gnArgs.use_demos = false;
+
+  NIX_CFLAGS_COMPILE = [
+    "-Wno-error=sign-compare"
+    "-Wno-error=stringop-truncation"
+    "-Wno-error=class-memaccess"
+    "-Wno-error=maybe-uninitialized"
+  ];
+
+  installPhase = ''
+    mkdir -p $out/bin
+    install sommelier $out/bin
+  '';
+
+  meta = with lib; {
+    description = "Nested Wayland compositor with support for X11 forwarding";
+    maintainers = with maintainers; [ qyliss ];
+  };
+}
diff --git a/pkgs/applications/virtualization/crosvm/update.py b/pkgs/os-specific/linux/chromium-os/update.py
index 29e68b9f579..5852d4a3069 100755
--- a/pkgs/applications/virtualization/crosvm/update.py
+++ b/pkgs/os-specific/linux/chromium-os/update.py
@@ -1,19 +1,31 @@
 #! /usr/bin/env nix-shell
-#! nix-shell -p nix-prefetch-git "python3.withPackages (ps: with ps; [ lxml ])"
-#! nix-shell -i python
+#! nix-shell -i python -p "python3.withPackages (ps: with ps; [ lxml ])"
 
 import base64
 import json
-import re
 import subprocess
 from codecs import iterdecode
+from os import scandir
 from os.path import dirname, splitext
 from lxml import etree
 from lxml.etree import HTMLParser
+from re import MULTILINE, fullmatch, match, search
 from urllib.request import urlopen
 
-# ChromiumOS components required to build crosvm.
-components = ['chromiumos/platform/crosvm', 'chromiumos/third_party/adhd']
+# ChromiumOS components used in Nixpkgs
+component_paths = [
+    'src/aosp/external/minijail',
+    'src/platform/crosvm',
+    'src/platform/minigbm',
+    'src/platform2',
+    'src/third_party/adhd',
+    'src/third_party/chromiumos-overlay',
+    'src/third_party/kernel/v5.4',
+    'src/third_party/libqmi',
+    'src/third_party/modemmanager-next',
+    'src/third_party/modp_b64',
+    'src/third_party/rust-vmm/vhost',
+]
 
 git_root = 'https://chromium.googlesource.com/'
 manifest_versions = f'{git_root}chromiumos/manifest-versions'
@@ -29,6 +41,7 @@ buildspecs_url = f'{manifest_versions}/+/refs/heads/master/full/buildspecs/'
 # documented.
 with urlopen('https://cros-updates-serving.appspot.com/') as resp:
     document = etree.parse(resp, HTMLParser())
+
     # bgcolor="lightgreen" is set on the most up-to-date version for
     # each channel, so find a lightgreen cell in the "Stable" column.
     (platform_version, chrome_version) = document.xpath("""
@@ -38,8 +51,8 @@ with urlopen('https://cros-updates-serving.appspot.com/') as resp:
         ][@bgcolor="lightgreen"])[1]/text()
     """)
 
-chrome_major_version = re.match(r'\d+', chrome_version)[0]
-chromeos_tip_build = re.match(r'\d+', platform_version)[0]
+chrome_major_version = match(r'\d+', chrome_version)[0]
+chromeos_tip_build = match(r'\d+', platform_version)[0]
 
 # Find the most recent buildspec for the stable Chrome version and
 # Chromium OS build number.  Its branch build and branch branch build
@@ -54,38 +67,65 @@ with urlopen(f'{buildspecs_url}{chrome_major_version}/?format=TEXT') as resp:
     buildspecs.sort(reverse=True)
     buildspec = splitext(buildspecs[0])[0]
 
-revisions = {}
+components = {}
 
 # Read the buildspec, and extract the git revisions for each component.
 with urlopen(f'{buildspecs_url}{chrome_major_version}/{buildspec}.xml?format=TEXT') as resp:
     xml = base64.decodebytes(resp.read())
     root = etree.fromstring(xml)
+
+    default_remote = root.find('default').get('remote')
+    remotes = {}
+    for remote in root.findall('remote'):
+        remotes[remote.get('name')] = remote.get('fetch')
+
     for project in root.findall('project'):
-        revisions[project.get('name')] = project.get('revision')
+        name = project.get('name')
+        path = project.get('path')
+        remote_name = project.get('remote') or default_remote
+        remote = remotes[remote_name]
+
+        components[path] = {
+            'revision': project.get('revision'),
+            'url': f'{remote}/{name}',
+        }
 
 # Initialize the data that will be output from this script.  Leave the
 # rc number in buildspec so nobody else is subject to the same level
 # of confusion I have been.
 data = {'version': f'{chrome_major_version}.{buildspec}', 'components': {}}
 
+paths = {}
+
 # Fill in the 'components' dictionary with the output from
 # nix-prefetch-git, which can be passed straight to fetchGit when
 # imported by Nix.
-for component in components:
-    argv = ['nix-prefetch-git',
-            '--url', git_root + component,
-            '--rev', revisions[component]]
-
-    output = subprocess.check_output(argv)
-    data['components'][component] = json.loads(output.decode('utf-8'))
+for path in component_paths:
+    name = path.split('/')[-1]
+    url = components[path]['url']
+    rev = components[path]['revision']
+    tarball = f'{url}/+archive/{rev}.tar.gz'
+    output = subprocess.check_output(['nix-prefetch-url', '--print-path', '--unpack', '--name', name, tarball])
+    (sha256, store_path) = output.decode('utf-8').splitlines()
+    paths[path] = store_path
+    data['components'][path] = {
+        'name': name,
+        'url': url,
+        'rev': rev,
+        'sha256': sha256,
+    }
 
-# Find the path to crosvm's default.nix, so the srcs data can be
-# written into the same directory.
-argv = ['nix-instantiate', '--eval', '--json', '-A', 'crosvm.meta.position']
-position = json.loads(subprocess.check_output(argv).decode('utf-8'))
-filename = re.match(r'[^:]*', position)[0]
+# Get the version number of the kernel.
+kernel = paths['src/third_party/kernel/v5.4']
+makefile = open(f'{kernel}/Makefile').read()
+version = search(r'^VERSION = (.+)$', makefile, MULTILINE)[1]
+patchlevel = search(r'^PATCHLEVEL = (.*?)$', makefile, MULTILINE)[1]
+sublevel = search(r'^SUBLEVEL = (.*?)$', makefile, MULTILINE)[1]
+extra = search(r'^EXTRAVERSION =[ \t]*(.*?)$', makefile, MULTILINE)[1]
+full_ver = '.'.join(filter(None, [version, patchlevel, sublevel])) + extra
+data['components']['src/third_party/kernel/v5.4']['version'] = full_ver
 
 # Finally, write the output.
-with open(dirname(filename) + '/upstream-info.json', 'w') as out:
+with open(dirname(__file__) + '/upstream-info.json', 'w') as out:
     json.dump(data, out, indent=2)
     out.write('\n')
diff --git a/pkgs/os-specific/linux/chromium-os/upstream-info.json b/pkgs/os-specific/linux/chromium-os/upstream-info.json
new file mode 100644
index 00000000000..f23948d9cb4
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/upstream-info.json
@@ -0,0 +1,72 @@
+{
+  "version": "91.13904.0.0-rc2",
+  "components": {
+    "src/aosp/external/minijail": {
+      "name": "minijail",
+      "url": "https://android.googlesource.com/platform/external/minijail",
+      "rev": "4f830bddad22ccbb5862a866efd9931393a4b83b",
+      "sha256": "044ll9jmanjlv6ibb8n77nsqifsb1iqck3bgkyix43i3yqamlq5h"
+    },
+    "src/platform/crosvm": {
+      "name": "crosvm",
+      "url": "https://chromium.googlesource.com/chromiumos/platform/crosvm",
+      "rev": "401340ee7fa81db720a4f7a9476b6eea133e5b64",
+      "sha256": "0wbfprqqm0hqhypdb0x90bqn65kv0f3vpibr84p3paby6hmx14y2"
+    },
+    "src/platform/minigbm": {
+      "name": "minigbm",
+      "url": "https://chromium.googlesource.com/chromiumos/platform/minigbm",
+      "rev": "231913e75751498902132c825d5b8567684e3b8d",
+      "sha256": "03wcdwd4l0wg9k6z9nnqim2wqqddlmppa49v6bkry7mhmiar1jc9"
+    },
+    "src/platform2": {
+      "name": "platform2",
+      "url": "https://chromium.googlesource.com/chromiumos/platform2",
+      "rev": "1fcefaa166e868069ad1b81091333ff75e0657f6",
+      "sha256": "1p4rcq53qz35sfbqldmcnjz5ikjxppdw1xcb348zn8n3hnb6vmqf"
+    },
+    "src/third_party/adhd": {
+      "name": "adhd",
+      "url": "https://chromium.googlesource.com/chromiumos/third_party/adhd",
+      "rev": "3624a4296888926a21462bfd499d4ba91268046b",
+      "sha256": "0flph3fagyl21dbwpdvhhizwnhbrgll87yfpq1i00xzr0c9xc1cc"
+    },
+    "src/third_party/chromiumos-overlay": {
+      "name": "chromiumos-overlay",
+      "url": "https://chromium.googlesource.com/chromiumos/overlays/chromiumos-overlay",
+      "rev": "f5b0e4767c2e5f8caa899127c8b3060fda2a3694",
+      "sha256": "1vpfccb0ikqa1xh6m0wizbxcaick2arki7d76sacz8qgrxinq8mn"
+    },
+    "src/third_party/kernel/v5.4": {
+      "name": "v5.4",
+      "url": "https://chromium.googlesource.com/chromiumos/third_party/kernel",
+      "rev": "15178e41cf17cd7ce9f9a087bcb0677eaa57f82b",
+      "sha256": "0cwvxm5bx0xs80l2rq3dqj88445qdhiqxp1999ix1gcc8awvwzdb",
+      "version": "5.4.109"
+    },
+    "src/third_party/libqmi": {
+      "name": "libqmi",
+      "url": "https://chromium.googlesource.com/chromiumos/third_party/libqmi",
+      "rev": "d9def40a2e068fd7e961aa0a4c699cf62cb8672c",
+      "sha256": "0grf510l85p32hc8gn7ks12ma0c2s3gnk76dyq62kmrvfb43ijzm"
+    },
+    "src/third_party/modemmanager-next": {
+      "name": "modemmanager-next",
+      "url": "https://chromium.googlesource.com/chromiumos/third_party/modemmanager-next",
+      "rev": "2de7877b2662d64c162434a7d3fb89d43f24b913",
+      "sha256": "027pnj92kidfgrzfpgwz18pl7gjgxi128il6wx1s8jdxsk2m00jc"
+    },
+    "src/third_party/modp_b64": {
+      "name": "modp_b64",
+      "url": "https://chromium.googlesource.com/aosp/platform/external/modp_b64",
+      "rev": "269b6fb8401617b85e2dff7ae8a7b0f97613e2cd",
+      "sha256": "1mc5w70svgj7rq49r6lxkw9ix6q09dxrw0lamx3hn8anq5q8y532"
+    },
+    "src/third_party/rust-vmm/vhost": {
+      "name": "vhost",
+      "url": "https://chromium.googlesource.com/chromiumos/third_party/rust-vmm/vhost",
+      "rev": "98298cb96972c20e05bffed58861456b48328d43",
+      "sha256": "0ym5cmnaifn3ywcidvqmbswmsxqi0zw6gx88gfqfq0p5jis8vjhi"
+    }
+  }
+}
diff --git a/pkgs/os-specific/linux/chromium-os/vm_protos/0003-common-mk-add-goproto_library-source_relative-opt.patch b/pkgs/os-specific/linux/chromium-os/vm_protos/0003-common-mk-add-goproto_library-source_relative-opt.patch
new file mode 100644
index 00000000000..e921abd8032
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/vm_protos/0003-common-mk-add-goproto_library-source_relative-opt.patch
@@ -0,0 +1,48 @@
+From 211eea8e623c9e9beb61f38720c718f080bae883 Mon Sep 17 00:00:00 2001
+From: Alyssa Ross <hi@alyssa.is>
+Date: Mon, 28 Jun 2021 17:10:46 +0000
+Subject: [PATCH 3/6] common-mk: add goproto_library source_relative opt
+
+We need this for the go_package changes in protoc-gen-go 1.5.x.  If we
+didn't use source-relative paths, the full module path would be
+repeated in the output location, so we'd get paths like
+src/chromiumos/vm_tools/vm_crash/chromiumos/vm_tools/vm_crash/vm_crash.pb.go.
+
+To avoid the duplication, we either need to set source_relative, or
+set proto_out_dir to just go/src.  The latter isn't workable, because
+then everything two libraries that both use common.proto will both
+generate outputs called "go/src/common.pb.go", which will upset GN.
+
+Reviewed-by: Cole Helbling <cole.e.helbling@outlook.com>
+---
+ common-mk/proto_library.gni | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/common-mk/proto_library.gni b/common-mk/proto_library.gni
+index fb9fb4231d..23645a134f 100644
+--- a/common-mk/proto_library.gni
++++ b/common-mk/proto_library.gni
+@@ -225,6 +225,9 @@ template("proto_library") {
+ #   proto_lib_dirs (optional)
+ #       Directories to search for protos a proto file depends on.
+ #       proto_in_dir and "${sysroot}/usr/share/proto" are added by default.
++#   source_relative (optional)
++#       If true, the output file is placed in the same relative directory as the
++#       input file (but under proto_out_dir).
+ template("goproto_library") {
+   action(target_name) {
+     forward_variables_from(invoker,
+@@ -254,6 +257,10 @@ template("goproto_library") {
+ 
+     go_plugin_parameters = []
+ 
++    if (defined(invoker.source_relative) && invoker.source_relative) {
++      go_plugin_parameters += [ "paths=source_relative" ]
++    }
++
+     if (defined(invoker.gen_grpc) && invoker.gen_grpc) {
+       go_plugin_parameters += [ "plugins=grpc" ]
+     }
+-- 
+2.32.0
+
diff --git a/pkgs/os-specific/linux/chromium-os/vm_protos/0004-vm_tools-proto-set-go_package-correctly.patch b/pkgs/os-specific/linux/chromium-os/vm_protos/0004-vm_tools-proto-set-go_package-correctly.patch
new file mode 100644
index 00000000000..d77bcf2bdef
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/vm_protos/0004-vm_tools-proto-set-go_package-correctly.patch
@@ -0,0 +1,102 @@
+From fae12c5b06864c0a9687320735c9bed9219c30c8 Mon Sep 17 00:00:00 2001
+From: Alyssa Ross <hi@alyssa.is>
+Date: Wed, 16 Jun 2021 16:09:01 +0000
+Subject: [PATCH 4/6] vm_tools: proto: set go_package correctly
+
+protoc-gen-go 1.5.x has become a lot stricter about this.  We have to
+use import_mapping for common.proto because it ends up being included
+in multiple Go libraries.  I'm not sure why it needs to be built once
+per library, but that's the way it works.
+
+Reviewed-by: Cole Helbling <cole.e.helbling@outlook.com>
+---
+ vm_tools/proto/BUILD.gn       | 5 +++++
+ vm_tools/proto/tremplin.proto | 2 +-
+ vm_tools/proto/vm_crash.proto | 2 +-
+ vm_tools/proto/vm_guest.proto | 1 +
+ vm_tools/proto/vm_host.proto  | 1 +
+ 5 files changed, 9 insertions(+), 2 deletions(-)
+
+diff --git a/vm_tools/proto/BUILD.gn b/vm_tools/proto/BUILD.gn
+index 79c9b94c9f..aadc40165c 100644
+--- a/vm_tools/proto/BUILD.gn
++++ b/vm_tools/proto/BUILD.gn
+@@ -60,6 +60,8 @@ goproto_library("vm-crash-gorpcs") {
+   proto_in_dir = "./"
+   proto_out_dir = "go/src/chromiumos/vm_tools/vm_crash"
+   gen_grpc = true
++  source_relative = true
++  import_mapping = [ "common.proto=chromiumos/vm_tools/vm_crash" ]
+   sources = [
+     "${proto_in_dir}/common.proto",
+     "${proto_in_dir}/vm_crash.proto",
+@@ -97,6 +99,7 @@ goproto_library("tremplin-gorpcs") {
+   proto_in_dir = "./"
+   proto_out_dir = "go/src/chromiumos/vm_tools/tremplin_proto"
+   gen_grpc = true
++  source_relative = true
+   sources = [ "${proto_in_dir}/tremplin.proto" ]
+ }
+ 
+@@ -120,6 +123,8 @@ goproto_library("vm-gorpcs") {
+   proto_in_dir = "./"
+   proto_out_dir = "go/src/chromiumos/vm_tools/vm_rpc"
+   gen_grpc = true
++  source_relative = true
++  import_mapping = [ "common.proto=chromiumos/vm_tools/vm_rpc" ]
+   sources = [
+     "${proto_in_dir}/common.proto",
+     "${proto_in_dir}/vm_guest.proto",
+diff --git a/vm_tools/proto/tremplin.proto b/vm_tools/proto/tremplin.proto
+index aac76f7a9e..e6a7bbed0e 100644
+--- a/vm_tools/proto/tremplin.proto
++++ b/vm_tools/proto/tremplin.proto
+@@ -8,7 +8,7 @@ option cc_enable_arenas = true;
+ 
+ // This file defines services for tremplin, the container springboard service.
+ package vm_tools.tremplin;
+-option go_package = "tremplin_proto";
++option go_package = "chromiumos/vm_tools/tremplin_proto";
+ 
+ // This needs to be duplicated because the gyp rule for building
+ // go code makes it difficult to have imports.
+diff --git a/vm_tools/proto/vm_crash.proto b/vm_tools/proto/vm_crash.proto
+index 6e4f62fe13..3cd4279989 100644
+--- a/vm_tools/proto/vm_crash.proto
++++ b/vm_tools/proto/vm_crash.proto
+@@ -7,7 +7,7 @@ syntax = "proto3";
+ option cc_enable_arenas = true;
+ 
+ package vm_tools.cicerone;
+-option go_package = "vm_crash";
++option go_package = "chromiumos/vm_tools/vm_crash";
+ 
+ import "common.proto";
+ 
+diff --git a/vm_tools/proto/vm_guest.proto b/vm_tools/proto/vm_guest.proto
+index 86f11d0812..d0946078d5 100644
+--- a/vm_tools/proto/vm_guest.proto
++++ b/vm_tools/proto/vm_guest.proto
+@@ -8,6 +8,7 @@ option cc_enable_arenas = true;
+ 
+ // This file defines services that will be running in the guest VM.
+ package vm_tools;
++option go_package = "chromiumos/vm_tools/vm_rpc";
+ 
+ import "common.proto";
+ import "google/protobuf/timestamp.proto";
+diff --git a/vm_tools/proto/vm_host.proto b/vm_tools/proto/vm_host.proto
+index a8bd066f61..19759b0271 100644
+--- a/vm_tools/proto/vm_host.proto
++++ b/vm_tools/proto/vm_host.proto
+@@ -8,6 +8,7 @@ option cc_enable_arenas = true;
+ 
+ // This file defines services that will be running on the host for the VM.
+ package vm_tools;
++option go_package = "chromiumos/vm_tools/vm_rpc";
+ 
+ import "common.proto";
+ 
+-- 
+2.32.0
+
diff --git a/pkgs/os-specific/linux/chromium-os/vm_protos/default.nix b/pkgs/os-specific/linux/chromium-os/vm_protos/default.nix
new file mode 100644
index 00000000000..cded9c988b3
--- /dev/null
+++ b/pkgs/os-specific/linux/chromium-os/vm_protos/default.nix
@@ -0,0 +1,41 @@
+{ common-mk, lib, go-protobuf, grpc, openssl, protobuf }:
+
+common-mk {
+  pname = "vm_protos";
+  platformSubdir = "vm_tools/proto";
+
+  nativeBuildInputs = [ go-protobuf ];
+  buildInputs = [ grpc openssl protobuf ];
+
+  platform2Patches = [
+    ./0003-common-mk-add-goproto_library-source_relative-opt.patch
+    ./0004-vm_tools-proto-set-go_package-correctly.patch
+  ];
+
+  NIX_CFLAGS_COMPILE = [
+    "-Wno-error=array-bounds"
+    "-Wno-error=deprecated-declarations"
+  ];
+
+  postPatch = ''
+    substituteInPlace common-mk/proto_library.gni \
+        --replace /usr/bin/grpc_cpp_plugin ${grpc}/bin/grpc_cpp_plugin
+  '';
+
+  installPhase = ''
+    mkdir -p $out/lib/pkgconfig
+    install -m 644 ../../vm_tools/proto/vm_protos.pc $out/lib/pkgconfig
+
+    headerPath=include/vm_protos/proto_bindings
+    mkdir -p $out/$headerPath
+    install -m 644 gen/$headerPath/*.h $out/$headerPath
+
+    install -m 644 *.a $out/lib
+  '';
+
+  meta = with lib; {
+    description = "Protobuf definitions for Chromium OS system VMs";
+    maintainers = with maintainers; [ qyliss ];
+    platform = platforms.all;
+  };
+}
diff --git a/pkgs/os-specific/linux/kernel-headers/default.nix b/pkgs/os-specific/linux/kernel-headers/default.nix
index 9d727838b3f..8b7b5a4fa42 100644
--- a/pkgs/os-specific/linux/kernel-headers/default.nix
+++ b/pkgs/os-specific/linux/kernel-headers/default.nix
@@ -5,7 +5,7 @@
 assert stdenvNoCC.hostPlatform.isAndroid ->
   (flex != null && bison != null && python != null && rsync != null);
 
-let
+rec {
   makeLinuxHeaders = { src, version, patches ? [] }: stdenvNoCC.mkDerivation {
     inherit src;
 
@@ -78,8 +78,10 @@ let
       platforms = platforms.linux;
     };
   };
-in {
-  inherit makeLinuxHeaders;
+
+  linuxHeadersPatches = [
+    ./no-relocs.patch # for building x86 kernel headers on non-ELF platforms
+  ];
 
   linuxHeaders = let version = "5.12"; in
     makeLinuxHeaders {
@@ -88,8 +90,6 @@ in {
         url = "mirror://kernel/linux/kernel/v5.x/linux-${version}.tar.xz";
         sha256 = "sha256-fQ328r8jhNaNC9jh/j4HHWQ2Tc3GAC57XIfJLUj6w2Y=";
       };
-      patches = [
-         ./no-relocs.patch # for building x86 kernel headers on non-ELF platforms
-      ];
+      patches = linuxHeadersPatches;
     };
 }
diff --git a/pkgs/os-specific/linux/kernel/linux-cros.nix b/pkgs/os-specific/linux/kernel/linux-cros.nix
new file mode 100644
index 00000000000..edaa33ac0fb
--- /dev/null
+++ b/pkgs/os-specific/linux/kernel/linux-cros.nix
@@ -0,0 +1,38 @@
+{ stdenv, lib, buildPackages, fetchFromGitiles, upstreamInfo, perl, buildLinux
+, modDirVersionArg ? null
+, ... } @ args:
+
+let
+  versionData = upstreamInfo.components."src/third_party/kernel/v5.4";
+in
+
+with lib;
+with lib.kernel;
+
+buildLinux (args // rec {
+  inherit (versionData) version;
+
+  # modDirVersion needs to be x.y.z, will automatically add .0 if needed
+  modDirVersion =
+    if modDirVersionArg == null
+    then concatStringsSep "." (take 3 (splitVersion "${version}.0"))
+    else modDirVersionArg;
+
+  # branchVersion needs to be x.y
+  extraMeta.branch = versions.majorMinor version;
+
+  src = fetchFromGitiles { inherit (versionData) name url rev sha256; };
+
+  updateScript = ../chromium-os/update.py;
+
+  structuredExtraConfig = {
+    # Enabling this (the default) caused a build failure.  If you can
+    # archieve a successful build with this enabled, go ahead and
+    # enable it.
+    VIDEO_INTEL_IPU6 = no;
+
+    # RTW88_8822*E were being selected as Y when N/m/? are the only valid options
+    RTW88_8822BE = lib.mkForce module;
+    RTW88_8822CE = lib.mkForce module;
+  } // (args.structuredExtraConfig or {});
+} // (args.argsOverride or {}))
diff --git a/pkgs/os-specific/linux/spectrum/default.nix b/pkgs/os-specific/linux/spectrum/default.nix
new file mode 100644
index 00000000000..c4cccab3787
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/default.nix
@@ -0,0 +1,18 @@
+{ newScope, linux_cros }:
+
+let
+  self = with self; {
+    callPackage = newScope self;
+
+    sys-vms = callPackage ./vm { };
+
+    spectrum-vm = callPackage ./spectrum-vm { linux = linux_vm; };
+
+    spectrum-testhost = callPackage ./testhost { };
+
+    linux_vm = callPackage ./linux/vm.nix { linux = linux_cros; };
+
+    makeRootfs = callPackage ./rootfs { };
+  };
+in
+self
diff --git a/pkgs/os-specific/linux/spectrum/linux/vm.nix b/pkgs/os-specific/linux/spectrum/linux/vm.nix
new file mode 100644
index 00000000000..c657cb443e8
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/linux/vm.nix
@@ -0,0 +1,25 @@
+{ lib, linux, kernelPatches, structuredExtraConfig ? {} }:
+
+with lib.kernel;
+
+linux.override {
+  structuredExtraConfig = {
+    VIRTIO_PCI = yes;
+    VIRTIO_BLK = yes;
+    VIRTIO_WL = yes;
+    VIRTIO_NET = yes;
+    DEVTMPFS_MOUNT = yes;
+    SQUASHFS = yes;
+
+    # VOP is needed to work around a Kconfig bug:
+    # https://lore.kernel.org/lkml/87wob4tf9b.fsf@alyssa.is/
+    VOP = yes;
+    VOP_BUS = yes;
+    HW_RANDOM = yes;
+    HW_RANDOM_VIRTIO = yes;
+
+    NET_9P = yes;
+    NET_9P_VIRTIO = yes;
+    "9P_FS" = yes;
+  } // structuredExtraConfig;
+}
diff --git a/pkgs/os-specific/linux/spectrum/rootfs/default.nix b/pkgs/os-specific/linux/spectrum/rootfs/default.nix
new file mode 100644
index 00000000000..56f2d15b103
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/rootfs/default.nix
@@ -0,0 +1,58 @@
+{ runCommandNoCC, writeScript, writeReferencesToFile, makeFontsConf, lib
+, dash, execline, s6, s6-rc, s6-portable-utils, s6-linux-utils, s6-linux-init, busybox
+, mesa, squashfs-tools-ng, makeDBusConf, connman
+}:
+
+{ services, rcServices ? {}, fonts ? [], path ? [] }:
+
+let
+  stage1 = import ./stage1.nix {
+    inherit writeScript lib
+      execline s6 s6-rc s6-portable-utils s6-linux-utils s6-linux-init busybox mesa
+      path;
+  };
+
+  makeServicesDir = import ./services.nix {
+    inherit runCommandNoCC writeScript lib execline;
+  };
+
+  makeRcServicesDir = import ./rc-services.nix {
+    inherit runCommandNoCC lib s6-rc;
+  };
+
+  fontsConf = makeFontsConf { fontDirectories = fonts; };
+
+  squashfs = runCommandNoCC "root-squashfs" {} ''
+    cd ${rootfs}
+    (
+        grep -v ^${rootfs} ${writeReferencesToFile rootfs}
+        printf "%s\n" *
+    ) \
+        | xargs tar -cP --owner root:0 --group root:0 --hard-dereference \
+        | ${squashfs-tools-ng}/bin/tar2sqfs -c gzip -X level=1 $out
+  '';
+
+  rootfs = runCommandNoCC "rootfs" { passthru = { inherit squashfs; }; } ''
+    mkdir $out
+    cd $out
+
+    mkdir -p bin sbin dev proc run sys tmp var/lib
+    ln -s /run var/run
+    ln -s ${dash}/bin/dash bin/sh
+    ln -s ${stage1} sbin/init
+    cp -r ${./etc} etc
+    chmod u+w etc
+    ln -s ${makeDBusConf {
+      suidHelper = "/run/dbus-daemon-launch-helper";
+      serviceDirectories = [ connman ];
+    }} etc/dbus-1
+
+    mkdir etc/fonts
+    ln -s ${fontsConf} etc/fonts/fonts.conf
+
+    touch etc/login.defs
+    cp -r ${makeServicesDir { inherit services; }} etc/service
+    cp -r ${makeRcServicesDir { services = rcServices; }} etc/s6-rc
+  '';
+in
+rootfs
diff --git a/pkgs/os-specific/linux/spectrum/rootfs/etc/group b/pkgs/os-specific/linux/spectrum/rootfs/etc/group
new file mode 100644
index 00000000000..df4940a5516
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/rootfs/etc/group
@@ -0,0 +1,3 @@
+root:x:0:root
+messagebus:x:4:messagebus
+user:x:1000:user
diff --git a/pkgs/os-specific/linux/spectrum/rootfs/etc/passwd b/pkgs/os-specific/linux/spectrum/rootfs/etc/passwd
new file mode 100644
index 00000000000..ddb1f854a6e
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/rootfs/etc/passwd
@@ -0,0 +1,3 @@
+root:x:0:0:System administrator:/:/bin/sh
+messagebus:x:4:4:D-Bus system message bus daemon user:/run/dbus:/bin/sh
+user:x:1000:1000:User:/:/bin/sh
diff --git a/pkgs/os-specific/linux/spectrum/rootfs/etc/wayfire/wf-shell-defaults.ini b/pkgs/os-specific/linux/spectrum/rootfs/etc/wayfire/wf-shell-defaults.ini
new file mode 100644
index 00000000000..7ba621225aa
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/rootfs/etc/wayfire/wf-shell-defaults.ini
@@ -0,0 +1,2 @@
+[panel]
+widgets_right =
diff --git a/pkgs/os-specific/linux/spectrum/rootfs/rc-services.nix b/pkgs/os-specific/linux/spectrum/rootfs/rc-services.nix
new file mode 100644
index 00000000000..4c942189c5e
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/rootfs/rc-services.nix
@@ -0,0 +1,26 @@
+{ runCommandNoCC, lib, s6-rc }:
+
+{ services ? [] }:
+
+let
+  inherit (lib) concatStrings escapeShellArg mapAttrsToList optionalString;
+
+  source = runCommandNoCC "s6-services-source" {} ''
+    mkdir $out
+    ${concatStrings (mapAttrsToList (name: attrs: ''
+      mkdir $out/${name}
+      ${concatStrings (mapAttrsToList (key: value: ''
+        cp ${value} $out/${name}/${key}
+      '') attrs)}
+    '') services)}
+  '';
+
+  s6RcCompile = { fdhuser ? null }: source:
+    runCommandNoCC "s6-rc-compile" {} ''
+      ${s6-rc}/bin/s6-rc-compile \
+        ${optionalString (fdhuser != null) "-h ${escapeShellArg fdhuser}"} \
+        $out ${source}
+    '';
+in
+
+s6RcCompile {} source
diff --git a/pkgs/os-specific/linux/spectrum/rootfs/services.nix b/pkgs/os-specific/linux/spectrum/rootfs/services.nix
new file mode 100644
index 00000000000..b2b09faa06e
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/rootfs/services.nix
@@ -0,0 +1,28 @@
+{ runCommandNoCC, writeScript, lib, execline }:
+
+{ services ? [] }:
+
+let
+  services' = {
+    ".s6-svscan" = {
+      finish = writeScript "init-stage3" ''
+        #! ${execline}/bin/execlineb -P
+        foreground { s6-nuke -th }
+        s6-sleep -m -- 2000
+        foreground { s6-nuke -k }
+        wait { }
+        s6-linux-init-hpr -fr
+      '';
+    } // services.".s6-svscan" or {};
+  } // services;
+in
+
+runCommandNoCC "services" {} ''
+  mkdir $out
+  ${lib.concatStrings (lib.mapAttrsToList (name: attrs: ''
+    mkdir $out/${name}
+    ${lib.concatStrings (lib.mapAttrsToList (key: value: ''
+      cp ${value} $out/${name}/${key}
+    '') attrs)}
+  '') services')}
+''
diff --git a/pkgs/os-specific/linux/spectrum/rootfs/stage1.nix b/pkgs/os-specific/linux/spectrum/rootfs/stage1.nix
new file mode 100644
index 00000000000..de10d60ffb6
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/rootfs/stage1.nix
@@ -0,0 +1,44 @@
+{ writeScript, lib
+, execline, s6, s6-rc, s6-portable-utils, s6-linux-utils, s6-linux-init, busybox, mesa
+, path ? []
+}:
+
+let
+  path' = path ++ [
+    s6 s6-rc s6-portable-utils s6-linux-utils s6-linux-init busybox execline
+  ];
+in
+
+writeScript "init-stage1" ''
+  #! ${execline}/bin/execlineb -P
+  export PATH ${lib.makeBinPath path'}
+  ${s6}/bin/s6-setsid -qb --
+
+  umask 022
+  if { s6-mount -t tmpfs -o mode=0755 tmpfs /run }
+  if { s6-hiercopy /etc/service /run/service }
+  emptyenv -p
+
+  background {
+    s6-setsid --
+
+    if { s6-rc-init -c /etc/s6-rc /run/service }
+
+    if { s6-mkdir -p /run/user/0 /dev/pts /dev/shm }
+    if { install -o user -g user -d /run/user/1000 }
+
+    if { s6-mount -t devpts -o gid=4,mode=620 none /dev/pts }
+    if { s6-mount -t tmpfs none /dev/shm }
+    if { s6-mount -t tmpfs none /var/lib }
+    if { s6-mount -t proc none /proc }
+    if { s6-mount -t sysfs none /sys }
+
+    if { s6-ln -s ${mesa.drivers} /run/opengl-driver }
+
+    s6-rc change ok-all
+  }
+
+  unexport !
+  cd /run/service
+  s6-svscan
+''
diff --git a/pkgs/os-specific/linux/spectrum/spectrum-vm/default.nix b/pkgs/os-specific/linux/spectrum/spectrum-vm/default.nix
new file mode 100644
index 00000000000..c56d2537c63
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/spectrum-vm/default.nix
@@ -0,0 +1,35 @@
+{ stdenv, lib, makeWrapper, utillinux, crosvm, linux, sys-vms }:
+
+stdenv.mkDerivation {
+  name = "spectrum-vm";
+
+  src = ./spectrum-vm.in;
+
+  nativeBuildInputs = [ makeWrapper ];
+
+  unpackPhase = ''
+    cp $src spectrum-vm.in
+  '';
+
+  configurePhase = ''
+    substituteAll spectrum-vm.in spectrum-vm
+    chmod +x spectrum-vm
+  '';
+
+  getopt = "${lib.getBin utillinux}/bin/getopt";
+  crosvm = "${lib.getBin crosvm}/bin/crosvm";
+  kernel = "${sys-vms.comp.linux}/bzImage";
+  rootfs = sys-vms.comp.rootfs.squashfs;
+
+  installPhase = ''
+    mkdir -p $out/bin
+    cp spectrum-vm $out/bin
+  '';
+
+  meta = with lib; {
+    description = "Utility for testing Spectrum VM components";
+    maintainers = with maintainers; [ qyliss ];
+    license = licenses.gpl3Plus;
+    inherit (crosvm.meta) platforms;
+  };
+}
diff --git a/pkgs/os-specific/linux/spectrum/spectrum-vm/spectrum-vm.in b/pkgs/os-specific/linux/spectrum/spectrum-vm/spectrum-vm.in
new file mode 100755
index 00000000000..a72c3896141
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/spectrum-vm/spectrum-vm.in
@@ -0,0 +1,71 @@
+#!@shell@
+set -ue
+
+ex_usage() {
+    cat <<EOF
+Usage: $(basename "$0") [OPTION]... [-- CROSVM_OPTIONS]
+
+  -c COMMAND         shell command to run inside VM
+  -C, --crosvm PATH  path to custom crosvm executable
+  -k, --kernel PATH  path to custom kernel image
+  -f, --rootfs PATH  path to custom root file system image
+  -- CROSVM_OPTIONS  extra arguments to pass to crosvm
+EOF
+    exit "$1"
+}
+
+args="$(@getopt@ -s sh -l crosvm:,help,kernel:,rootfs: -o c:C:hk:f: -- "$@" || exit 1)"
+eval set -- "$args"
+
+command=
+crosvm=@crosvm@
+kernel=@kernel@
+rootfs=@rootfs@
+
+while :
+do
+    case "$1" in
+	-c)
+	    shift
+	    command="$1"
+	    shift
+	    ;;
+        -C|--crosvm)
+            shift
+            crosvm="$1"
+	    shift
+            ;;
+	-h|--help)
+	    ex_usage 0
+	    ;;
+        -k|--kernel)
+            shift
+            kernel="$1"
+	    shift
+            ;;
+        -f|--rootfs)
+            shift
+            rootfs="$1"
+	    shift
+            ;;
+        --)
+            shift
+            break
+            ;;
+    esac
+done
+
+if [ -n "${XDG_RUNTIME_DIR-}" ]
+then
+    set -- -s "$XDG_RUNTIME_DIR" "$@"
+    if [ -n "${WAYLAND_DISPLAY-}" ]
+    then set -- --wayland-sock "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" "$@"
+    fi
+fi
+
+exec "$crosvm" run \
+    -p init=/sbin/init \
+    -p "spectrumcmd=$(printf %s "$command" | base64 -w0)" \
+    --root "$rootfs" \
+    "$@" \
+    "$kernel"
diff --git a/pkgs/os-specific/linux/spectrum/testhost/default.nix b/pkgs/os-specific/linux/spectrum/testhost/default.nix
new file mode 100644
index 00000000000..de62f0add67
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/testhost/default.nix
@@ -0,0 +1,218 @@
+{ lib, runCommandNoCC, writeScript, writeScriptBin, writeShellScript, writeText
+, coreutils, cloud-hypervisor, crosvm, curl, execline, gnutar, gnused, iproute
+, iptables, jq, kmod, mktuntap, rsync, s6, s6-rc, sys-vms, utillinux
+}:
+
+let
+  inherit (lib) concatStrings escapeShellArg makeBinPath mapAttrsToList
+    optionalString;
+
+  compose2 = f: g: a: b: f (g a b);
+
+  concatMapAttrs = compose2 concatStrings mapAttrsToList;
+
+  makeServicesDir = { services }:
+    runCommandNoCC "services" {} ''
+      mkdir $out
+      ${concatMapAttrs (name: attrs: ''
+        mkdir $out/${name}
+        ${concatMapAttrs (key: value: ''
+          cp -r ${value} $out/${name}/${key}
+        '') attrs}
+      '') services}
+    '';
+
+  s6RcCompile = { fdhuser ? null }: source:
+    runCommandNoCC "s6-rc-compile" {} ''
+      ${s6-rc}/bin/s6-rc-compile \
+        ${optionalString (fdhuser != null) "-h ${escapeShellArg fdhuser}"} \
+        dest ${source}
+      tar -C dest -cf $out .
+    '';
+
+  compiledRcServicesDir = s6RcCompile {} (makeServicesDir {
+    services = {
+      vm-app = {
+        run = writeScript "app-run" ''
+          #! ${execline}/bin/execlineb -S0
+          # fdclose 0
+
+          # Checking the return value of the bridge creation is
+          # important, because if it fails due to the bridge already
+          # existing that means something else could already be using
+          # this bridge.
+          if { ip link add name br0 type bridge }
+          if { ip link set br0 up }
+
+          # Calculate the MACs for our TAP and the router's TAP.
+          # MAC address format, by octet:
+          #
+          #  0-3  Static OUI for Spectrum
+          #    4  Most significant bit is used to differentiate
+          #       routers from clients.  Other bits are reserved.
+          #  5-6  Last two octets of client's IP (in 100.64.0.0/16).
+          #
+          backtick -i router_mac {
+            pipeline { printf %.4x ${toString sys-vms.app.vmID} }
+            sed s/^\\(..\\)\\(..\\)$/0A:B3:EC:80:\\1:\\2/
+          }
+          backtick -i client_mac {
+            pipeline { printf %.4x ${toString sys-vms.app.vmID} }
+            sed s/^\\(..\\)\\(..\\)$/0A:B3:EC:00:\\1:\\2/
+          }
+          multisubstitute {
+            importas -iu router_mac router_mac
+            importas -iu client_mac client_mac
+          }
+
+          # Create the net VM end, and attach it to the net VM.
+          #
+          # Use a hardcoded name for now because if we use a dynamic
+          # one iproute2 has no way of telling us the name that was
+          # chosen:
+          # https://lore.kernel.org/netdev/20210406134240.wwumpnrzfjbttnmd@eve.qyliss.net/
+          define other_tap_name vmtapnet
+          # Try to delete the device in case the VM was powered off
+          # (as the finish script wouldn't have been run in that
+          # case.)  Since we check the return value of ip tuntap add,
+          # in the case of a race condition between deleting the
+          # device and creating it again, we'll just fail and try
+          # again.
+          foreground { ip link delete $other_tap_name }
+          if { ip tuntap add name $other_tap_name mode tap }
+          if { ip link set $other_tap_name master br0 }
+          if { ip link set $other_tap_name up }
+          if {
+            pipeline {
+              jq -n "$ARGS.named"
+                --arg tap $other_tap_name
+                --arg mac $router_mac
+            }
+            curl -iX PUT
+              -H "Accept: application/json"
+              -H "Content-Type: application/json"
+              --data-binary @-
+              --unix-socket ../vm-net/env/cloud-hypervisor.sock
+              http://localhost/api/v1/vm.add-net
+          }
+
+          mktuntap -pvBi vmtap%d 6
+          importas -iu tap_name TUNTAP_NAME
+          if { ip link set $tap_name master br0 }
+          if { ip link set $tap_name up }
+          if { iptables -t nat -A POSTROUTING -o $tap_name -j MASQUERADE }
+
+          ${crosvm}/bin/crosvm run -p init=/sbin/init -p notifyport=''${port}
+            # --serial type=file,path=/tmp/app.log
+            --cid 4
+            --tap-fd 6,mac=''${client_mac}
+            --root ${sys-vms.app.rootfs.squashfs} ${sys-vms.app.linux}/bzImage
+        '';
+        finish = writeScript "app-finish" ''
+          #! ${execline}/bin/execlineb -S0
+          # TODO: remove from vm-net
+          foreground { ip link delete vmtapnet }
+          ip link delete br0
+        '';
+        type = writeText "app-type" ''
+          longrun
+        '';
+        dependencies = writeText "app-dependencies" ''
+          vm-net
+        '';
+      };
+
+      vm-net = {
+        run = writeScript "net-run" ''
+          #! ${execline}/bin/execlineb -S0
+          # This is only necessary for when running s6 from a tty.
+          # (i.e. when debugging or running the demo).
+          redirfd -w 0 /dev/null
+
+          define PCI_LOCATION 0000:00:19.0
+          define PCI_PATH /sys/bus/pci/devices/''${PCI_LOCATION}
+
+          # Unbind the network device from the driver it's already
+          # attached to, if any.
+          foreground {
+            redirfd -w 1 ''${PCI_PATH}/driver/unbind
+            printf "%s" $PCI_LOCATION
+          }
+
+          # Tell the VFIO driver it should support our device.  This
+          # is allowed to fail because it might already know that, in
+          # which case it'll return EEXIST.
+          if { modprobe vfio-pci }
+          backtick -in device_id {
+            if { dd bs=2 skip=1 count=2 status=none if=''${PCI_PATH}/vendor }
+            if { printf " " }
+            dd bs=2 skip=1 count=2 status=none if=''${PCI_PATH}/device
+          }
+          importas -iu device_id device_id
+          foreground {
+            redirfd -w 1 /sys/bus/pci/drivers/vfio-pci/new_id
+            printf "%s" $device_id
+          }
+
+          # Bind the device to the VFIO driver.  This is allowed to
+          # fail because the new_id operation we just tried will have
+          # bound it automatically for us if it succeeded.  In such a
+          # case, the kernel will return ENODEV (conistency!).
+          foreground {
+            redirfd -w 1 /sys/bus/pci/drivers/vfio-pci/bind
+            printf "%s" $PCI_LOCATION
+          }
+
+          # Because we allow both new_id and bind to fail, we need to
+          # manually make sure now that at least one of them succeeded
+          # and the device is actually attached to the vfio-driver.
+          if { test -e /sys/bus/pci/drivers/vfio-pci/''${PCI_LOCATION} }
+
+          foreground { mkdir env }
+
+          ${cloud-hypervisor}/bin/cloud-hypervisor
+            --api-socket env/cloud-hypervisor.sock
+            --console off
+            # --serial tty
+            --cmdline "console=ttyS0 panic=30 root=/dev/vda"
+            --device path=''${PCI_PATH}
+            --disk path=${sys-vms.net.rootfs.squashfs},readonly=on
+            --kernel ${sys-vms.net.linux.dev}/vmlinux
+        '';
+        type = writeText "net-type" ''
+          longrun
+        '';
+      };
+    };
+  });
+
+  servicesDir = makeServicesDir {
+    services = {
+      ".s6-svscan" = {
+        finish = writeShellScript ".s6-svscan-finish" "";
+      };
+    };
+  };
+in
+
+writeScriptBin "spectrum-testhost" ''
+  #! ${execline}/bin/execlineb -S0
+  export PATH ${makeBinPath [
+    coreutils curl execline gnused gnutar iproute iptables jq kmod mktuntap rsync
+    s6 s6-rc
+  ]}
+
+  if { redirfd -w 1 /proc/sys/net/ipv4/ip_forward echo 1 }
+
+  importas -iu runtime_dir XDG_RUNTIME_DIR
+  backtick -in TOP { mktemp -dp $runtime_dir spectrum.XXXXXXXXXX }
+  importas -iu top TOP
+  if { echo $top }
+  if { rsync -r --chmod=Du+w ${servicesDir}/ ''${top}/service }
+  background {
+    if { mkdir -p ''${top}/s6-rc/compiled }
+    if { tar -C ''${top}/s6-rc/compiled -xf ${compiledRcServicesDir} }
+    s6-rc-init -c ''${top}/s6-rc/compiled -l ''${top}/s6-rc/live ''${top}/service
+  }
+  s6-svscan ''${top}/service
+''
diff --git a/pkgs/os-specific/linux/spectrum/vm/app/default.nix b/pkgs/os-specific/linux/spectrum/vm/app/default.nix
new file mode 100644
index 00000000000..f8ff480932c
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/vm/app/default.nix
@@ -0,0 +1,58 @@
+{ runCommand, writeScript, writeText, makeRootfs
+, busybox, execline, linux_vm, jq, iproute
+}:
+
+runCommand "vm-app" rec {
+  linux = linux_vm;
+
+  login = writeScript "login" ''
+    #! ${execline}/bin/execlineb -s0
+    unexport !
+    ${busybox}/bin/login -p -f root $@
+  '';
+
+  rootfs = makeRootfs {
+    rcServices.ok-all = {
+      type = writeText "ok-all-type" ''
+        bundle
+      '';
+      contents = writeText "ok-all-contents" ''
+        net
+      '';
+    };
+
+    rcServices.net = {
+      type = writeText "net-type" ''
+        oneshot
+      '';
+      up = writeText "net-up" ''
+        backtick -i LOCAL_IP {
+          pipeline { ip -j link show eth0 }
+          pipeline { jq -r ".[0].address | split(\":\") | .[4:6] | \"0x\" + .[]" }
+          xargs printf "100.64.%d.%d"
+        }
+        importas -iu LOCAL_IP LOCAL_IP
+
+        if { ip address add ''${LOCAL_IP}/32 dev eth0 }
+        if { ip link set eth0 up }
+        if { ip route add 169.254.0.1 dev eth0 }
+        ip route add default via 169.254.0.1 dev eth0
+      '';
+    };
+
+    services.getty.run = writeScript "getty-run" ''
+      #! ${execline}/bin/execlineb -P
+      ${busybox}/bin/getty -i -n -l ${login} 38400 ttyS0
+    '';
+
+    path = [ iproute jq ];
+  };
+
+  inherit (rootfs) squashfs;
+  vmID = 0;
+} ''
+  mkdir $out
+  echo "$vmID" > $out/vm-id
+  ln -s $linux/bzImage $out/kernel
+  ln -s $squashfs $out/squashfs
+''
diff --git a/pkgs/os-specific/linux/spectrum/vm/comp/default.nix b/pkgs/os-specific/linux/spectrum/vm/comp/default.nix
new file mode 100644
index 00000000000..eb6317ed6f2
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/vm/comp/default.nix
@@ -0,0 +1,73 @@
+{ lib, makeRootfs, runCommand, writeScript, writeText
+, busybox, emacs-nox, execline, gcc, linux_vm, s6, sommelier, source-code-pro
+, tinywl, westonLite, zsh
+}:
+
+runCommand "vm-comp" rec {
+  linux = linux_vm;
+
+  path = [
+    busybox emacs-nox execline gcc s6 sommelier tinywl westonLite zsh
+  ];
+
+  login = writeScript "login" ''
+    #! ${execline}/bin/execlineb -s0
+    unexport !
+    ${busybox}/bin/login -p -f root $@
+  '';
+
+  rootfs = makeRootfs {
+    services.getty.run = writeScript "getty-run" ''
+      #! ${execline}/bin/execlineb -P
+      ${busybox}/bin/getty -i -n -l ${login} 38400 ttyS0
+    '';
+
+    rcServices.ok-all = {
+      type = writeText "ok-all-type" ''
+        bundle
+      '';
+      contents = writeText "ok-all-contents" ''
+        compositor
+      '';
+    };
+
+    rcServices.compositor = {
+      type = writeText "compositor-type" ''
+        longrun
+      '';
+      run = writeScript "compositor-run" ''
+        #! ${execline}/bin/execlineb -S0
+
+        s6-applyuidgid -u 1000 -g 1000
+
+        export HOME /
+        export PATH ${lib.makeBinPath path}
+        export XDG_RUNTIME_DIR /run/user/1000
+        export XKB_DEFAULT_LAYOUT dvorak
+
+        ${sommelier}/bin/sommelier
+        ${tinywl}/bin/tinywl -s "weston-terminal --shell $(command -v zsh)"
+      '';
+      dependencies = writeText "compositor-dependencies" ''
+        wl0
+      '';
+    };
+
+    rcServices.wl0 = {
+      type = writeText "wl0-type" ''
+        oneshot
+      '';
+      up = writeText "wl0-run" ''
+        chown user /dev/wl0
+      '';
+    };
+
+    fonts = [ source-code-pro ];
+  };
+
+  inherit (rootfs) squashfs;
+} ''
+  mkdir $out
+  ln -s $linux/bzImage $out/kernel
+  ln -s $squashfs $out/squashfs
+''
diff --git a/pkgs/os-specific/linux/spectrum/vm/default.nix b/pkgs/os-specific/linux/spectrum/vm/default.nix
new file mode 100644
index 00000000000..f5d591a960a
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/vm/default.nix
@@ -0,0 +1,9 @@
+{ callPackage }:
+
+{
+  app = callPackage ./app { };
+
+  comp = callPackage ./comp { };
+
+  net = callPackage ./net { };
+}
diff --git a/pkgs/os-specific/linux/spectrum/vm/net/default.nix b/pkgs/os-specific/linux/spectrum/vm/net/default.nix
new file mode 100644
index 00000000000..5921b62fcf7
--- /dev/null
+++ b/pkgs/os-specific/linux/spectrum/vm/net/default.nix
@@ -0,0 +1,167 @@
+{ lib, makeRootfs, runCommand, writeScript, writeText
+, busybox, connman, dbus, execline, iptables, iproute, jq, linux_vm, mdevd
+}:
+
+runCommand "vm-net" rec {
+  linux = linux_vm.override {
+    structuredExtraConfig = with lib.kernel; {
+      E1000E = yes;
+      IGB = yes;
+      PACKET = yes;
+
+      IP_NF_NAT = yes;
+      IP_NF_IPTABLES = yes;
+      IP_NF_TARGET_MASQUERADE = yes;
+      NF_CONNTRACK = yes;
+    };
+  };
+
+  login = writeScript "login" ''
+    #! ${execline}/bin/execlineb -s0
+    unexport !
+    ${busybox}/bin/login -p -f root $@
+  '';
+
+  rootfs = makeRootfs {
+    rcServices.ok-all = {
+      type = writeText "ok-all-type" ''
+        bundle
+      '';
+      contents = writeText "ok-all-contents" ''
+        mdevd-coldplug
+      '';
+    };
+
+    rcServices.mdevd = {
+      type = writeText "mdevd-type" ''
+        longrun
+      '';
+      run = writeScript "mdevd-run" ''
+        #! ${execline}/bin/execlineb -P
+        ${mdevd}/bin/mdevd -D3 -f ${writeText "mdevd.conf" ''
+          $INTERFACE=.* 0:0 660 ! @${writeScript "interface" ''
+            #! ${execline}/bin/execlineb -S0
+
+            multisubstitute {
+              importas -i DEVPATH DEVPATH
+              importas -i INTERFACE INTERFACE
+            }
+
+            ifte
+
+            {
+              # This interface is connected to another VM.
+
+              # Our IP is encoded in the NIC-specific portion of the
+              # interface's MAC address.
+              backtick -i CLIENT_IP {
+                pipeline { ip -j link show $INTERFACE }
+                pipeline { jq -r ".[0].address | split(\":\") | .[4:6] | \"0x\" + .[]" }
+                xargs printf "100.64.%d.%d"
+              }
+              importas -iu CLIENT_IP CLIENT_IP
+
+              if { ip address add 169.254.0.1/32 dev $INTERFACE }
+              if { ip link set $INTERFACE up }
+              ip route add $CLIENT_IP dev $INTERFACE
+            }
+
+            {
+              if { test $INTERFACE != lo }
+              # This is a physical connection to a network device.
+              if { iptables -t nat -A POSTROUTING -o $INTERFACE -j MASQUERADE }
+              s6-rc -u change connman
+            }
+
+            grep -iq ^0A:B3:EC: /sys/class/net/''${INTERFACE}/address
+          ''}
+        ''}
+      '';
+      notification-fd = writeText "mdevd-notification-fd" ''
+        3
+      '';
+      dependencies = writeText "mdevd-dependencies" ''
+        sysctl
+      '';
+    };
+
+    rcServices.mdevd-coldplug = {
+      type = writeText "mdevd-coldplug-type" ''
+        oneshot
+      '';
+      up = writeText "mdevd-run" ''
+        ${mdevd}/bin/mdevd-coldplug
+      '';
+      dependencies = writeText "mdevd-coldplug-dependencies" ''
+        mdevd
+      '';
+    };
+
+    rcServices.dbus = {
+      type = writeText "dbus-daemon" ''
+        longrun
+      '';
+      run = writeScript "dbus-daemon-run" ''
+        #! ${execline}/bin/execlineb -S0
+        foreground { mkdir /run/dbus }
+        # Busybox cp doesn't have -n to avoid copying to paths that
+        # already exist, but we can abuse -u for the same effect,
+        # since every file in the store is from Jan 1 1970.
+        foreground { cp -u ${dbus}/libexec/dbus-daemon-launch-helper /run }
+        foreground { chgrp messagebus /run/dbus-daemon-launch-helper }
+        foreground { chmod 4550 /run/dbus-daemon-launch-helper }
+        ${dbus}/bin/dbus-daemon
+          --nofork --nosyslog --nopidfile --config-file=/etc/dbus-1/system.conf
+      '';
+    };
+
+    rcServices.connman = {
+      type = writeText "connman-type" ''
+        longrun
+      '';
+      run = writeScript "connman-run" ''
+        #! ${execline}/bin/execlineb -S0
+        backtick -in HARDWARE_INTERFACES {
+          pipeline {
+            find -L /sys/class/net -mindepth 2 -maxdepth 2 -name address -print0
+          }
+
+          # Filter out other VMs and the loopback device.
+          pipeline { xargs -0 grep -iL ^\\(0A:B3:EC:\\|00:00:00:00:00:00$\\) }
+
+          # Extract the interface names from the address file paths.
+          awk -F/ "{if (NR > 1) printf \",\"; printf \"%s\", $5}"
+        }
+        importas -iu HARDWARE_INTERFACES HARDWARE_INTERFACES
+
+        ${connman}/bin/connmand -ni $HARDWARE_INTERFACES
+      '';
+      dependencies = writeText "connman-dependencies" ''
+        dbus
+      '';
+    };
+
+    rcServices.sysctl = {
+      type = writeText "sysctl-type" ''
+        oneshot
+      '';
+      up = writeText "sysctl-up" ''
+        redirfd -w 1 /proc/sys/net/ipv4/ip_forward
+        echo 1
+      '';
+    };
+
+    services.getty.run = writeScript "getty-run" ''
+      #! ${execline}/bin/execlineb -P
+      ${busybox}/bin/getty -i -n -l ${login} 38400 ttyS0
+    '';
+
+    path = [ iproute iptables jq ];
+  };
+
+  inherit (rootfs) squashfs;
+} ''
+  mkdir $out
+  ln -s $linux/bzImage $out/kernel
+  ln -s $squashfs $out/squashfs
+''
diff --git a/pkgs/servers/rust-9p/default.nix b/pkgs/servers/rust-9p/default.nix
new file mode 100644
index 00000000000..115f1735d90
--- /dev/null
+++ b/pkgs/servers/rust-9p/default.nix
@@ -0,0 +1,34 @@
+{ stdenv, fetchFromGitHub, rustPlatform }:
+
+rustPlatform.buildRustPackage rec {
+  pname = "rust-9p";
+  version = "0.0.2019-05-17";
+
+  src = fetchFromGitHub {
+    owner = "pfpacket";
+    repo = pname;
+    rev = "01cf9c60bff0f35567d876db7be7fb86032b44eb";
+    sha256 = "0mhmr1912z5nyfpcvhnlgb3v67a5n7i2n9l5abi05sfqffqssi79";
+  };
+
+  sourceRoot = "source/example/unpfs";
+
+  cargoSha256 = "1d33nwj3i333a6ji3r3037mgg553lc3wsawm0pz13kbvhjf336i8";
+
+  RUSTC_BOOTSTRAP = 1;
+
+  postInstall = ''
+    install -D -m 0444 ../../README* -t "$out/share/doc/${pname}"
+    install -D -m 0444 ../../LICEN* -t "$out/share/doc/${pname}"
+  '';
+
+  meta = with stdenv.lib; {
+    description = "9P2000.L server implementation in Rust";
+    homepage = "https://github.com/pfpacket/rust-9p";
+    license = licenses.bsd3;
+    maintainers = with maintainers; [ raskin ];
+
+    # macOS build fails: https://github.com/pfpacket/rust-9p/issues/7
+    platforms = with platforms; linux;
+  };
+}
diff --git a/pkgs/tools/networking/mktuntap/default.nix b/pkgs/tools/networking/mktuntap/default.nix
new file mode 100644
index 00000000000..4f77c4ffc82
--- /dev/null
+++ b/pkgs/tools/networking/mktuntap/default.nix
@@ -0,0 +1,22 @@
+{ stdenv, lib, fetchgit }:
+
+stdenv.mkDerivation rec {
+  pname = "mktuntap";
+  version = "1.0";
+
+  src = fetchgit {
+    url = "https://spectrum-os.org/git/mktuntap";
+    rev = version;
+    sha256 = "136ichzd5811n289xqjyha81mln89yxq4a14w46ixnnx69905r47";
+  };
+
+  installFlags = [ "prefix=$(out)" ];
+
+  meta = with lib; {
+    description = "Utility program for creating TAP and TUN devices";
+    homepage = "https://spectrum-os.org/git/mktaptun";
+    maintainers = with maintainers; [ qyliss ];
+    license = licenses.gpl2;
+    platform = platforms.linux;
+  };
+}
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index c52ad74e2ee..bb79ace50fc 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -2409,8 +2409,6 @@ in
 
   crip = callPackage ../applications/audio/crip { };
 
-  crosvm = callPackage ../applications/virtualization/crosvm { };
-
   crunch = callPackage ../tools/security/crunch { };
 
   crudini = callPackage ../tools/misc/crudini { };
@@ -7154,6 +7152,8 @@ in
 
   mkrand = callPackage ../tools/security/mkrand { };
 
+  mktuntap = callPackage ../tools/networking/mktuntap { };
+
   mktemp = callPackage ../tools/security/mktemp { };
 
   mktorrent = callPackage ../tools/misc/mktorrent { };
@@ -14917,6 +14917,11 @@ in
 
   chromaprint = callPackage ../development/libraries/chromaprint { };
 
+  chromiumOSPackages = recurseIntoAttrs
+    (callPackage ../os-specific/linux/chromium-os { });
+
+  inherit (chromiumOSPackages) crosvm sommelier;
+
   cl = callPackage ../development/libraries/cl {
     erlang = erlangR23;
   };
@@ -18568,6 +18573,8 @@ in
 
   spdk = callPackage ../development/libraries/spdk { };
 
+  spectrumPackages = callPackage ../os-specific/linux/spectrum { };
+
   speechd = callPackage ../development/libraries/speechd { };
 
   speech-tools = callPackage ../development/libraries/speech-tools {};
@@ -20942,7 +20949,9 @@ in
   lkl = callPackage ../applications/virtualization/lkl { };
 
   inherit (callPackages ../os-specific/linux/kernel-headers { })
-    linuxHeaders makeLinuxHeaders;
+    linuxHeaders
+    linuxHeadersPatches
+    makeLinuxHeaders;
 
   kernelPatches = callPackage ../os-specific/linux/kernel/patches.nix { };
 
@@ -20950,6 +20959,8 @@ in
 
   klibcShrunk = lowPrio (callPackage ../os-specific/linux/klibc/shrunk.nix { });
 
+  linux_cros = chromiumOSPackages.linux;
+
   linux_mptcp = linux_mptcp_95;
 
   linux_mptcp_95 = callPackage ../os-specific/linux/kernel/linux-mptcp-95.nix {
@@ -21365,6 +21376,7 @@ in
   linux-rt = linuxPackages-rt.kernel;
   linux-rt_latest = linuxPackages-rt_latest.kernel;
 
+  linuxPackages_cros = linuxPackagesFor pkgs.linux_cros;
   linuxPackages_mptcp = linuxPackagesFor pkgs.linux_mptcp;
   linuxPackages_rpi1 = linuxPackagesFor pkgs.linux_rpi1;
   linuxPackages_rpi2 = linuxPackagesFor pkgs.linux_rpi2;
@@ -24925,6 +24937,7 @@ in
   super-productivity = callPackage ../applications/office/super-productivity { };
 
   wlroots = callPackage ../development/libraries/wlroots {
+    inherit (chromiumOSPackages) linuxHeaders;
     inherit (xorg) xcbutilrenderutil;
   };