chromebook-linux-audio/setup-audio
2025-02-26 17:47:12 -05:00

258 lines
12 KiB
Python
Executable file

#!/usr/bin/env python3
import argparse
import json
import os
import sys
import re
import subprocess as sp
from urllib.request import urlopen, urlretrieve
from functions import *
# parse arguments from the cli. Only for testing/advanced use.
def process_args():
parser = argparse.ArgumentParser()
parser.add_argument("-b", dest="board_name", type=str, nargs=1, default=[""],
help="Override board name.")
parser.add_argument("--enable-debug", action='store_const', const="Enabling", dest="debug",
help="Enable audio debugging.")
parser.add_argument("--disable-debug", action='store_const', const="Disabling", dest="debug",
help="Disable audio debugging.")
parser.add_argument("--force-avs-install", action="store_true", dest="force_avs_install", default=False,
help="DANGEROUS: Force enable AVS install. MIGHT CAUSE PERMANENT DAMAGE TO SPEAKERS!")
parser.add_argument("--branch", dest="branch_name", type=str, nargs=1, default=["standalone"],
help="Use a different branch when cloning ucm. FOR DEVS AND TESTERS ONLY!")
return parser.parse_args()
def install_ucm():
print_status("Installing UCM configuration")
try:
bash("rm -rf /tmp/alsa-ucm-conf-cros")
bash(f"git clone https://github.com/WeirdTreeThing/alsa-ucm-conf-cros -b {args.branch_name[0]} /tmp/alsa-ucm-conf-cros")
except:
print_error("Error: Failed to clone UCM repo")
exit(1)
cpdir("/tmp/alsa-ucm-conf-cros/ucm2", "/usr/share/alsa/ucm2/")
cpdir("/tmp/alsa-ucm-conf-cros/overrides", "/usr/share/alsa/ucm2/conf.d")
def get_board():
if not args.board_name[0]:
with open("/sys/devices/virtual/dmi/id/product_name", "r") as dmi:
device_board = dmi.read()
else: # use the board name from the args, for testing only
device_board = str(args.board_name[0])
print_warning(f"Board name override: {device_board}")
return device_board.lower().strip()
def check_arch():
# dmi doesnt exist on arm chromebooks
if not path_exists("/sys/devices/virtual/dmi/id/"):
print_error("ARM Chromebooks are not supported by this script. See your distro's documentation for audio support status.")
exit(1)
def match_platform(device_board):
with open("conf/boards.json", "r") as file:
boards = json.load(file)
try:
match boards[device_board]:
case "bdw" | "byt" | "bsw":
hifi2_audio()
case "skl" | "kbl":
avs_audio()
case "apl":
apl_audio()
case "glk" | "cml" | "jsl" | "tgl" | "adl":
sof_audio(boards[device_board])
case "stoney" | "picasso" | "cezanne" | "mendocino":
amd_audio(boards[device_board])
case _:
print_error(f"Unknown/Unsupported chromebook model: {device_board}")
exit(1)
except KeyError:
print_error(f"Unknown/Unsupported chromebook model: {device_board}")
exit(1)
def avs_audio():
print_status("Installing AVS")
# Only show the warning to devices with max98357a
override_avs = False
if path_exists("/sys/bus/acpi/devices/MX98357A:00"):
if args.force_avs_install:
print_error(
"WARNING: Your device has max98357a and can cause permanent damage to your speakers if you set the volume too loud!")
while input('Type "I understand the risk of permanently damaging my speakers" in all caps to continue: ')\
!= "I UNDERSTAND THE RISK OF PERMANENTLY DAMAGING MY SPEAKERS":
print_error("Try again")
override_avs = True
else:
print_error(
"WARNING: Your device has max98357a and can cause permanent damage to your speakers if you "
"set the volume too loud! As a safety precaution devices with max98357a have speakers "
"disabled until a fix is in place. Headphones and HDMI audio are safe from this.")
print_question("If you want to disable this check, restart the script with --force-avs-install")
while input('Type "I Understand my speakers will not work since my device has max98357a!" in all caps to continue: ')\
!= "I UNDERSTAND MY SPEAKERS WILL NOT WORK SINCE MY DEVICE HAS MAX98357A!":
print_error("Try again")
override_avs = False
# avs tplg is from https://github.com/thesofproject/avs-topology-xml, but isn't packaged in distros yet
print_status("Installing topology")
mkdir("/tmp/avs_tplg")
avs_tplg_ver = "2024.02"
bash(f"tar xf ./blobs/avs-topology_{avs_tplg_ver}.tar.gz -C /tmp/avs_tplg")
mkdir("/lib/firmware/intel/avs", create_parents=True)
cpdir("/tmp/avs_tplg/avs-topology/lib/firmware/intel/avs", "/lib/firmware/intel/avs")
# Force AVS driver since the kernel will use the SKL driver by default
print_status("Installing modprobe config")
cpfile("conf/avs/snd-avs.conf", "/etc/modprobe.d/snd-avs.conf")
# updated avs dsp firmware recently got merged upstream but is not packaged in any distro yet
print_status("Installing AVS firmware")
mkdir("/lib/firmware/intel/avs/skl")
mkdir("/lib/firmware/intel/avs/apl")
try:
urlretrieve("https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/plain/intel/avs/apl/"
"dsp_basefw.bin", filename="/lib/firmware/intel/avs/apl/dsp_basefw.bin")
urlretrieve("https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/plain/intel/avs/skl/"
"dsp_basefw.bin", filename="/lib/firmware/intel/avs/skl/dsp_basefw.bin")
except:
print_error("Error: Failed to download AVS firmware")
# Delete topology for max98357a to prevent it from working until there is a volume limiter.
if not override_avs:
rmfile("/lib/firmware/intel/avs/max98357a-tplg.bin")
# Check for kernels with avs module enabled
kernels_installed = sp.check_output("ls /lib/modules/", shell=True, text=True).strip().split('\n')
has_avs = False
for kernel_version in kernels_installed:
if path_exists(f"/lib/modules/{kernel_version}/kernel/sound/soc/intel/avs"):\
has_avs = True
if not has_avs:
print_error("Looks like your kernel doesn't have the avs modules. Make sure you are on at least 6.4 with avs enabled")
exit(0)
def apl_audio():
print_status("Apollolake has two audio drivers:")
print_status("SOF: Stable but doesn't work with headphones.")
print_status("AVS: Unstable and can cause damage to speakers but supports all audio hardware.")
print_error("NOTE: Speakers are disabled on AVS as a safety precaution. (use --force-avs-install to override)"
"Your speakers will still work on SOF though.")
while True:
user_input = input("Which driver would you like to use? [sof/avs]: ")
if user_input.lower() == "sof":
print_status("Using sof")
# Remove avs modprobe config if it exists
rmfile("/etc/modprobe.d/snd-avs.conf")
sof_audio("apl")
# Install apl specific modprobe config
cpfile("conf/sof/apl-sof.conf", "/etc/modprobe.d/apl-sof.conf")
break
elif user_input.lower() == "avs":
print_status("Using avs")
# Remove sof modprobe config if it exists
rmfile("/etc/modprobe.d/snd-sof.conf")
rmfile("/etc/modprobe.d/apl-sof.conf")
avs_audio()
break
else:
print_error(f"Invalid option: {user_input}")
continue
def sof_audio(platform):
print_status("Installing SOF")
# Install sof firmware
if not path_exists("/lib/firmware/intel/sof"):
print_status("Installing SOF firmware")
install_package("sof-firmware", "firmware-sof-signed", "alsa-sof-firmware", "sof-firmware", "sof-firmware", "sof-firmware")
# Special tplg cases
# RPL devices load tplg with a different file name than ADL, despite being the exact same file as their ADL counterparts
# sof-bin currently doesn't include these symlinks, so we create them ourselves
if platform == "adl":
tplgs = ["cs35l41", "max98357a-rt5682-4ch", "max98357a-rt5682", "max98360a-cs42l42", "max98360a-nau8825", "max98360a-rt5682-2way", "max98360a-rt5682-4ch", "max98360a-rt5682", "max98373-nau8825", "max98390-rt5682", "max98390-ssp2-rt5682-ssp0", "nau8825", "rt1019-nau8825", "rt1019-rt5682", "rt5682", "rt711", "sdw-max98373-rt5682"]
for tplg in tplgs:
tplg_path="/lib/firmware/intel/sof-tplg"
if path_exists(f"{tplg_path}/sof-adl-{tplg}.tplg"):
bash(f"ln -sf {tplg_path}/sof-adl-{tplg}.tplg {tplg_path}/sof-rpl-{tplg}.tplg")
if path_exists(f"{tplg_path}/sof-adl-{tplg}.tplg.xz"):
bash(f"ln -sf {tplg_path}/sof-adl-{tplg}.tplg.xz {tplg_path}/sof-rpl-{tplg}.tplg.xz")
# sof-adl-max98360a-cs42l42.tplg is symlinked to sof-adl-max98360a-rt5682.tplg in ChromeOS
tplg_file1="/lib/firmware/intel/sof-tplg/sof-adl-max98360a-rt5682.tplg"
tplg_file2="/lib/firmware/intel/sof-tplg/sof-adl-max98360a-cs42l42.tplg"
if path_exists(f"{tplg_file1}"):
bash(f"ln -sf {tplg_file1} {tplg_file2}")
if path_exists(f"{tplg_file1}.xz"):
bash(f"ln -sf {tplg_file1}.xz {tplg_file2}.xz")
def hifi2_audio():
print_status("Forcing SOF driver in debug mode")
if not path_exists("/lib/firmware/intel/sof"):
install_package("sof-firmware", "firmware-sof-signed", "alsa-sof-firmware", "sof-firmware", "sof-firmware", "sof-firmware")
cpfile("conf/sof/hifi2-sof.conf", "/etc/modprobe.d/hifi2-sof.conf")
def amd_audio(platform):
# Install sof firmware and modprobe config on mendocino
if platform == "mendocino":
print_status("Installing SOF firmware")
mkdir("/lib/firmware/amd/sof/community", create_parents=True)
mkdir("/lib/firmware/amd/sof-tplg", create_parents=True)
cpdir("blobs/mdn/fw", "/lib/firmware/amd/sof/community")
cpdir("blobs/mdn/tplg", "/lib/firmware/amd/sof-tplg")
elif platform == "stoney":
print_warning("WARNING: Your audio will not work unless you install a custom kernel")
print_warning("You can get a prebuilt kernel from https://nightly.link/chrultrabook/stoney-kernel/workflows/build/main/stoney-kernel.zip")
if __name__ == "__main__":
check_arch()
args = process_args()
# Restart script as root
if os.geteuid() != 0:
# make the two people that use doas happy
if path_exists("/usr/bin/doas"):
doas_args = ['doas', sys.executable] + sys.argv + [os.environ]
os.execlpe('doas', *doas_args)
# other 99 percent of linux users
sudo_args = ['sudo', sys.executable] + sys.argv + [os.environ]
os.execlpe('sudo', *sudo_args)
# Important message
print_warning("WARNING: You may run into audio issues, even after running this script. Please report any issues on github.")
# Some distros (Solus) don't have /etc/modprobe.d/ for some reason
mkdir("/etc/modprobe.d", create_parents=True)
# platform specific configuration
board = get_board()
match_platform(board)
# UCM
install_ucm()
# Install wireplumber config to increase headroom
# fixes instability and crashes on various devices
if path_exists("/usr/bin/wireplumber"):
print_status("Increasing alsa headroom (fixes instability)")
mkdir("/etc/wireplumber/wireplumber.conf.d/", create_parents=True)
cpfile("conf/common/51-increase-headroom.conf", "/etc/wireplumber/wireplumber.conf.d/51-increase-headroom.conf")
print_header("Audio installed successfully! Reboot to finish setup.")
print_status("If this script has been helpful for you and you would like to support the work I do, consider donating to https://paypal.me/weirdtreething")