Skip to content

Commit

Permalink
Initial AudioVM implementation with pipewire and pulseaudio
Browse files Browse the repository at this point in the history
Initial version of AudioVM with Pipewire backend and pulseaudio TCP
remote communication layer for the guest VMs. Note that this is not
really secure design (yet) basically all VMs can access the pulseaudio
TCP service.

Signed-off-by: Jon Sahlberg <jon.sahlberg@unikie.com>
  • Loading branch information
josa41 committed May 24, 2024
1 parent 1815f06 commit 6ac140a
Show file tree
Hide file tree
Showing 12 changed files with 351 additions and 33 deletions.
35 changes: 35 additions & 0 deletions modules/hardware/definition.nix
Original file line number Diff line number Diff line change
Expand Up @@ -139,5 +139,40 @@
SUBSYSTEM=="input",ATTRS{name}=="AT Translated Set 2 keyboard",GROUP="kvm"
'';
};

audio = {
# With the current implementation, the whole PCI IOMMU group 14:
# 00:1f.x in the example from Lenovo X1 Carbon
# must be defined for passthrough to AudioVM
pciDevices = mkOption {
description = "PCI Devices to passthrough to AudioVM";
type = types.listOf pciDevSubmodule;
default = [];
example = literalExpression ''
[
{
path = "0000:00:1f.0";
vendorId = "8086";
productId = "519d";
}
{
path = "0000:00:1f.3";
vendorId = "8086";
productId = "51ca";
}
{
path = "0000:00:1f.4";
vendorId = "8086";
productId = "51a3";
}
{
path = "0000:00:1f.5";
vendorId = "8086";
productId = "51a4";
}
]
'';
};
};
};
}
1 change: 1 addition & 0 deletions modules/hardware/lenovo-x1/definitions/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ in {
inherit (hwDefinition) disks;
inherit (hwDefinition) network;
inherit (hwDefinition) gpu;
inherit (hwDefinition) audio;

virtioInputHostEvdevs = [
# Lenovo X1 touchpad and keyboard
Expand Down
2 changes: 2 additions & 0 deletions modules/hardware/lenovo-x1/definitions/x1-gen10.nix
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@
productId = "46a6";
}
];

audio.pciDevices = [];
}
30 changes: 30 additions & 0 deletions modules/hardware/lenovo-x1/definitions/x1-gen11.nix
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,34 @@
productId = "a7a1";
}
];

# With the current implementation, the whole PCI IOMMU group 14:
# 00:1f.x in the example from Lenovo X1 Carbon
# must be defined for passthrough to AudioVM
audio.pciDevices = [
{
# ISA bridge: Intel Corporation Raptor Lake LPC/eSPI Controller (rev 01)
path = "0000:00:1f.0";
vendorId = "8086";
productId = "519d";
}
{
# Audio device: Intel Corporation Raptor Lake-P/U/H cAVS (rev 01)
path = "0000:00:1f.3";
vendorId = "8086";
productId = "51ca";
}
{
# SMBus: Intel Corporation Alder Lake PCH-P SMBus Host Controller (rev 01)
path = "0000:00:1f.4";
vendorId = "8086";
productId = "51a3";
}
{
# Serial bus controller: Intel Corporation Alder Lake-P PCH SPI Controller (rev 01)
path = "0000:00:1f.5";
vendorId = "8086";
productId = "51a4";
}
];
}
1 change: 1 addition & 0 deletions modules/microvm/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
./virtualization/microvm/idsvm/mitmproxy
./virtualization/microvm/appvm.nix
./virtualization/microvm/guivm.nix
./virtualization/microvm/audiovm.nix
./networking.nix
./power-control.nix
];
Expand Down
114 changes: 114 additions & 0 deletions modules/microvm/virtualization/microvm/audiovm.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors
# SPDX-License-Identifier: Apache-2.0
{
config,
lib,
...
}: let
configHost = config;
vmName = "audio-vm";
macAddress = "02:00:00:03:03:03";
audiovmBaseConfiguration = {
imports = [
(import ./common/vm-networking.nix {inherit vmName macAddress;})
({
lib,
pkgs,
...
}: {
ghaf = {
users.accounts.enable = lib.mkDefault configHost.ghaf.users.accounts.enable;
profiles.debug.enable = lib.mkDefault configHost.ghaf.profiles.debug.enable;

development = {
ssh.daemon.enable = lib.mkDefault configHost.ghaf.development.ssh.daemon.enable;
debug.tools.enable = lib.mkDefault configHost.ghaf.development.debug.tools.enable;
nix-setup.enable = lib.mkDefault configHost.ghaf.development.nix-setup.enable;
};
systemd = {
enable = true;
withName = "audiovm-systemd";
withNss = true;
withResolved = true;
withTimesyncd = true;
withDebug = configHost.ghaf.profiles.debug.enable;
};
};

environment = {
systemPackages = [
pkgs.pulseaudio
pkgs.pamixer
pkgs.pipewire
];
};

system.stateVersion = lib.trivial.release;

nixpkgs.buildPlatform.system = configHost.nixpkgs.buildPlatform.system;
nixpkgs.hostPlatform.system = configHost.nixpkgs.hostPlatform.system;

microvm = {
optimize.enable = true;
vcpu = 1;
mem = 256;
hypervisor = "qemu";
shares = [
{
tag = "ro-store";
source = "/nix/store";
mountPoint = "/nix/.ro-store";
}
];
writableStoreOverlay = lib.mkIf config.ghaf.development.debug.tools.enable "/nix/.rw-store";
qemu = {
machine =
{
# Use the same machine type as the host
x86_64-linux = "q35";
aarch64-linux = "virt";
}
.${configHost.nixpkgs.hostPlatform.system};
};
};

imports = [
../../../common
];

# Fixed IP-address for debugging subnet
systemd.network.networks."10-ethint0".addresses = [
{
addressConfig.Address = "192.168.101.4/24";
}
];
})
];
};
cfg = config.ghaf.virtualization.microvm.audiovm;
in {
options.ghaf.virtualization.microvm.audiovm = {
enable = lib.mkEnableOption "AudioVM";

extraModules = lib.mkOption {
description = ''
List of additional modules to be imported and evaluated as part of
AudioVM's NixOS configuration.
'';
default = [];
};
};

config = lib.mkIf cfg.enable {
microvm.vms."${vmName}" = {
autostart = true;
config =
audiovmBaseConfiguration
// {
imports =
audiovmBaseConfiguration.imports
++ cfg.extraModules;
};
};
};
}
1 change: 1 addition & 0 deletions modules/microvm/virtualization/microvm/guivm.nix
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
pkgs.waypipe
pkgs.networkmanagerapplet
pkgs.nm-launcher
pkgs.pamixer
]
++ (lib.optional (configHost.ghaf.profiles.debug.enable && configHost.ghaf.virtualization.microvm.idsvm.mitmproxy.enable) pkgs.mitmweb-ui);
};
Expand Down
24 changes: 13 additions & 11 deletions targets/lenovo-x1/appvms/chromium.nix
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ in {
'';
in [
pkgs.chromium
pkgs.pamixer
pkgs.pulseaudio
pkgs.xdg-utils
xdgPdfItem
xdgOpenPdf
Expand All @@ -32,22 +32,24 @@ in {
cores = 4;
extraModules = [
{
# Enable pulseaudio for user ghaf
# Enable pulseaudio for Chromium VM
security.rtkit.enable = true;
sound.enable = true;
hardware.pulseaudio.enable = true;
users.extraUsers.ghaf.extraGroups = ["audio"];
users.extraUsers.ghaf.extraGroups = ["audio" "video"];

hardware.pulseaudio.extraConfig = ''
load-module module-tunnel-sink sink_name=chromium-speaker server=audio-vm.ghaf:4713 format=s16le channels=2 rate=48000
load-module module-tunnel-source source_name=chromium-mic server=audio-vm.ghaf:4713 format=s16le channels=1 rate=48000
# Set sink and source default max volume to about 90% (0-65536)
set-sink-volume chromium-speaker 60000
set-source-volume chromium-mic 60000
'';

time.timeZone = "Asia/Dubai";

microvm.qemu.extraArgs = [
# Connect sound device to hosts pulseaudio socket
"-audiodev"
"pa,id=pa1,server=unix:/run/pulse/native"
# Add HDA sound device to guest
"-device"
"intel-hda"
"-device"
"hda-duplex,audiodev=pa1"
# Lenovo X1 integrated usb webcam
"-device"
"qemu-xhci"
Expand Down
18 changes: 10 additions & 8 deletions targets/lenovo-x1/appvms/element.nix
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,20 @@ in {
extraModules = [
{
# Enable pulseaudio for user ghaf to access mic
security.rtkit.enable = true;
sound.enable = true;
hardware.pulseaudio.enable = true;
users.extraUsers.ghaf.extraGroups = ["audio" "video"];

hardware.pulseaudio.extraConfig = ''
load-module module-tunnel-sink sink_name=element-speaker server=audio-vm.ghaf:4713 format=s16le channels=2 rate=48000
load-module module-tunnel-source source_name=element-mic server=audio-vm.ghaf:4713 format=s16le channels=1 rate=48000
# Set sink and source default max volume to about 90% (0-65536)
set-sink-volume element-speaker 60000
set-source-volume element-mic 60000
'';

systemd.network = {
enable = true;
networks."10-ethint0" = {
Expand Down Expand Up @@ -94,14 +104,6 @@ in {
"qemu-xhci"
"-device"
"usb-host,hostbus=3,hostport=8"
# Connect sound device to hosts pulseaudio socket
"-audiodev"
"pa,id=pa1,server=unix:/run/pulse/native"
# Add HDA sound device to guest
"-device"
"intel-hda"
"-device"
"hda-duplex,audiodev=pa1"
# External USB GPS receiver
"-device"
"usb-host,vendorid=0x067b,productid=0x23a3"
Expand Down
94 changes: 94 additions & 0 deletions targets/lenovo-x1/audiovmExtraModules.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Copyright 2022-2024 TII (SSRC) and the Ghaf contributors
# SPDX-License-Identifier: Apache-2.0
#
{
lib,
pkgs,
microvm,
configH,
...
}: let
# TCP port used by Pipewire-pulseaudio service
pulseaudioTcpPort = 4713;

audiovmPCIPassthroughModule = {
microvm.devices = lib.mkForce (
builtins.map (d: {
bus = "pci";
inherit (d) path;
})
configH.ghaf.hardware.definition.audio.pciDevices
);
};

audiovmExtraConfigurations = {
time.timeZone = "Asia/Dubai";

# Enable pipewire service for audioVM with pulseaudio support
security.rtkit.enable = true;
sound.enable = true;
services.pipewire = {
enable = true;
# alsa.enable = true;
# alsa.support32Bit = true;
pulse.enable = true;
systemWide = true;
};

services.pipewire.configPackages = [
(pkgs.writeTextDir "share/pipewire/pipewire.conf.d/10-remote-simple.conf" ''
context.modules = [
{ name = libpipewire-module-protocol-pulse
args = {
server.address = [
"tcp:4713" # IPv4 and IPv6 on all addresses
];
pulse.min.req = 128/48000; # 2.7ms
pulse.default.req = 960/48000; # 20 milliseconds
pulse.min.frag = 128/48000; # 2.7ms
pulse.default.frag = 512/48000; # ~10 ms
pulse.default.tlength = 512/48000; # ~10 ms
pulse.min.quantum = 128/48000; # 2.7ms
}
}
];
'')
];

hardware.pulseaudio.extraConfig = ''
# Set sink and source default max volume to about 75% (0-65536)
set-sink-volume @DEFAULT_SINK@ 48000
set-source-volume @DEFAULT_SOURCE@ 48000
'';

# Allow ghaf user to access pulseaudio and pipewire
users.extraUsers.ghaf.extraGroups = ["audio" "video" "pulse-access" "pipewire"];

# Dummy service to get pipewire and pulseaudio services started at boot
# Normally Pipewire and pulseaudio are started when they are needed by user,
# We don't have users in audiovm so we need to give PW/PA a slight kick..
# This calls pulseaudios pa-info binary to get information about pulseaudio current
# state which starts pipewire-pulseaudio service in the process.
systemd.services.pulseaudio-starter = {
after = ["pipewire.service" "network-online.target"];
requires = ["pipewire.service" "network-online.target"];
wantedBy = ["default.target"];
path = [pkgs.coreutils];
enable = true;
serviceConfig = {
User = "ghaf";
Group = "ghaf";
};
script = ''${pkgs.pulseaudio}/bin/pa-info > /dev/null 2>&1'';
};

# Open TCP port for the PDF XDG socket
networking.firewall.allowedTCPPorts = [pulseaudioTcpPort];

microvm.qemu.extraArgs = [
];
};
in [
audiovmPCIPassthroughModule
audiovmExtraConfigurations
]

0 comments on commit 6ac140a

Please sign in to comment.