diff --git a/spec_files/jupiter-hw-support/jupiter-hw-support-bazzite.spec b/spec_files/jupiter-hw-support/jupiter-hw-support-bazzite.spec new file mode 100644 index 00000000..00374814 --- /dev/null +++ b/spec_files/jupiter-hw-support/jupiter-hw-support-bazzite.spec @@ -0,0 +1,108 @@ +%define packagename jupiter-hw-support +Name: %{packagename}-bazzite +Version: {{{ git_dir_version }}} +Release: 1%{?dist} +Summary: Steam Deck Hardware Support Package +License: GPLv3 +URL: https://github.com/ublue-os/bazzite + +Source: https://gitlab.com/evlaV/%{packagename}/-/archive/master/%{packagename}-master.tar.gz +Patch0: fedora.patch + +Requires: python3 +Requires: python3-libevdev +Requires: python3-crcmod +Requires: python3-click +Requires: python3-progressbar2 +Requires: python3-hid +Requires: hidapi +Requires: dmidecode +Requires: jq +Requires: alsa-utils +Requires: parted +Requires: e2fsprogs + +BuildRequires: systemd-rpm-macros + +%description +SteamOS 3.0 Steam Deck Hardware Support Package + +# Disable debug packages +%define debug_package %{nil} + +%prep +%setup -n %{packagename}-master +%patch 0 -p1 + +%build + +%install +export QA_RPATHS=0x0003 +mkdir -p %{buildroot}%{_datadir}/ +mkdir -p %{buildroot}%{_unitdir}/ +mkdir -p %{buildroot}%{_bindir}/ +mkdir -p %{buildroot}%{_sysconfdir}/ +cp -rv usr/share/* %{buildroot}%{_datadir} +cp -rv usr/lib/systemd/system/* %{buildroot}%{_unitdir}/ +cp -rv usr/lib/hwsupport %{buildroot}%{_prefix}/lib/hwsupport +cp -rv usr/lib/udev %{buildroot}%{_prefix}/lib/udev +cp -rv usr/bin/* %{buildroot}%{_bindir} +cp -rv usr/lib/systemd/system/* %{buildroot}%{_unitdir} +cp -rv etc/* %{buildroot}%{_sysconfdir} +# Remove unneeded files +rm %{buildroot}%{_sysconfdir}/default/grub-steamos +rm %{buildroot}%{_datadir}/jupiter_bios_updater/h2offt-g +rm %{buildroot}%{_datadir}/jupiter_bios_updater/H2OFFTx64-G.sh +rm -rf %{buildroot}%{_datadir}/jupiter_bios_updater/driver +rm -rf %{buildroot}%{_unitdir}/multi-user.target.wants +rm %{buildroot}%{_prefix}/lib/udev/rules.d/99-steamos-automount.rules +rm %{buildroot}%{_prefix}/lib/hwsupport/format-device.sh +rm %{buildroot}%{_prefix}/lib/hwsupport/steamos-automount.sh + +# Do post-installation +%post +%systemd_post jupiter-biosupdate.service +%systemd_post jupiter-controller-update.service + +# Do before uninstallation +%preun +%systemd_preun jupiter-biosupdate.service +%systemd_preun jupiter-controller-update.service + +# Do after uninstallation +%postun +%systemd_postun_with_restart jupiter-biosupdate.service +%systemd_postun_with_restart jupiter-controller-update.service + +# This lists all the files that are included in the rpm package and that +# are going to be installed into target system where the rpm is installed. +%files +%{_sysconfdir}/systemd/system/* +%{_bindir}/amd_system_info +%{_bindir}/foxnet-biosupdate +%{_bindir}/jupiter-biosupdate +%{_bindir}/jupiter-check-support +%{_bindir}/jupiter-controller-update +%{_bindir}/steamos-polkit-helpers/* +%{_bindir}/thumbstick_cal +%{_bindir}/thumbstick_fine_cal +%{_bindir}/trigger_cal +%{_prefix}/lib/hwsupport/* +%{_prefix}/lib/systemd/system/* +%{_prefix}/lib/udev/rules.d/* +%{_datadir}/alsa/ucm2/conf.d/acp5x/* +%{_datadir}/icons/steam/* +%{_datadir}/steamos/steamos.png +%{_datadir}/jupiter_bios/* +%{_datadir}/jupiter_bios_updater/* +%{_datadir}/jupiter_controller_fw_updater/* +%{_datadir}/plymouth/themes/steamos/* +%{_datadir}/polkit-1/actions/org.valve.steamos.policy +%{_datadir}/polkit-1/rules.d/org.valve.steamos.rules +%{_datadir}/steamos/steamos-cursor-config +%{_datadir}/steamos/steamos-cursor.png + +# Finally, changes from the latest release of your application are generated from +# your project's Git history. It will be empty until you make first annotated Git tag. +%changelog +{{{ git_dir_changelog }}} diff --git a/system_files/deck/usr/lib/hwsupport/format-device.sh b/system_files/deck/usr/lib/hwsupport/format-device.sh new file mode 100644 index 00000000..f2115dd3 --- /dev/null +++ b/system_files/deck/usr/lib/hwsupport/format-device.sh @@ -0,0 +1,148 @@ +#!/bin/bash + +set -e +exec &> >(tee | logger -t steamos-format-device) + +RUN_VALIDATION=1 +EXTENDED_OPTIONS="nodiscard" +# default owner for the new filesystem +OWNER="1000:1000" + +OPTS=$(getopt -l force,skip-validation,full,quick,owner:,device: -n format-device.sh -- "" "$@") + +eval set -- "$OPTS" + +while true; do + case "$1" in + --force) RUN_VALIDATION=0; shift ;; + --skip-validation) RUN_VALIDATION=0; shift ;; + --full) EXTENDED_OPTIONS="discard"; shift ;; + --quick) EXTENDED_OPTIONS="nodiscard"; shift ;; + --owner) OWNER="$2"; shift 2;; + --device) STORAGE_DEVICE="$2"; shift 2 ;; + --) shift; break ;; + esac +done + +if [[ "$#" -gt 0 ]]; then + echo "Unknown option $1"; exit 22 +fi + +EXTENDED_OPTIONS="$EXTENDED_OPTIONS,root_owner=$OWNER" + +# We only support SD/MMC and USB mass-storage devices +case "$STORAGE_DEVICE" in + "") + echo "Usage: $(basename $0) [--force] [--skip-validation] [--full] [--quick] [--owner :] --device " + exit 19 #ENODEV + ;; + /dev/mmcblk?) + STORAGE_PARTITION="${STORAGE_DEVICE}p1" + ;; + /dev/sd?) + STORAGE_PARTITION="${STORAGE_DEVICE}1" + ;; + *) + echo "Unknown or unsupported device: $STORAGE_DEVICE" + exit 19 #ENODEV +esac + +if [[ ! -e "$STORAGE_DEVICE" ]]; then + exit 19 #ENODEV +fi + +# Prevent accidental formatting of system drives +if [[ $(lsblk -d -n -r -o hotplug "$STORAGE_DEVICE") != "1" ]]; then + echo "$STORAGE_DEVICE is not a hotplug device" + exit 19 #ENODEV +fi + +STORAGE_PARTBASE="${STORAGE_PARTITION#/dev/}" + +systemctl stop steamos-automount@"$STORAGE_PARTBASE".service + +# lock file prevents the mount service from re-mounting as it gets triggered by udev rules. +# +# NOTE: Uses a shared lock filename between this and the auto-mount script to ensure we're not double-triggering nor +# automounting while formatting or vice-versa. +MOUNT_LOCK="/var/run/jupiter-automount-${STORAGE_PARTBASE//\/_}.lock" +MOUNT_LOCK_FD=9 +exec 9<>"$MOUNT_LOCK" + +if ! flock -n "$MOUNT_LOCK_FD"; then + echo "Failed to obtain lock $MOUNT_LOCK, failing" + exit 5 +fi + +# If any partitions on the device are mounted, unmount them before continuing +# to prevent problems later +for m in $(lsblk -n "$STORAGE_DEVICE" -o MOUNTPOINTS| awk NF | sort -u); do + if ! umount "$m"; then + echo "Failed to unmount filesystem: $m" + exit 32 # EPIPE + fi +done + +# Test the sdcard +# Some fake cards advertise a larger size than their actual capacity, +# which can result in data loss or other unexpected behaviour. It is +# best to try to detect these issues as early as possible. +if [[ "$RUN_VALIDATION" != "0" ]]; then + echo "stage=testing" + if ! f3probe --destructive "$STORAGE_DEVICE"; then + # Fake sdcards tend to only behave correctly when formatted as exfat + # The tricks they try to pull fall apart with any other filesystem and + # it renders the card unusuable. + # + # Here we restore the card to exfat so that it can be used with other devices. + # It won't be usable with the deck, and usage of the card will most likely + # result in data loss. We return a special error code so we can surface + # a specific error to the user. + echo "stage=rescuing" + echo "Bad sdcard - rescuing" + for i in {1..3}; do # Give this a couple of tries since it fails sometimes + echo "Create partition table: $i" + dd if=/dev/zero of="$STORAGE_DEVICE" bs=512 count=1024 # see comment in similar statement below + if ! parted --script "$STORAGE_DEVICE" mklabel msdos mkpart primary 0% 100% ; then + echo "Failed to create partition table: $i" + continue # try again + fi + + echo "Create exfat filesystem: $i" + sync + if ! mkfs.exfat "$STORAGE_PARTITION"; then + echo "Failed to exfat filesystem: $i" + continue # try again + fi + + echo "Successfully restored device" + break + done + + # Return a specific error code so the UI can warn the user about this bad device + exit 14 # EFAULT + fi +fi + +# Clear out the garbage bits generated by f3probe from the partition table sectors +# Otherwise parted may think we have existing partitions in a bogus state +dd if=/dev/zero of="$STORAGE_DEVICE" bs=512 count=1024 + +# Format as EXT4 with casefolding for proton compatibility +echo "stage=formatting" +sync +parted --script "$STORAGE_DEVICE" mklabel gpt mkpart primary 0% 100% +sync +mkfs.ext4 -m 0 -O casefold -E "$EXTENDED_OPTIONS" -F "$STORAGE_PARTITION" +sync +udevadm settle + +# trigger the mount service +flock -u "$MOUNT_LOCK_FD" +if ! systemctl start steamos-automount@"$STORAGE_PARTBASE".service; then + echo "Failed to start mount service" + journalctl --no-pager --boot=0 -u steamos-automount@"$STORAGE_PARTBASE".service + exit 5 +fi + +exit 0 diff --git a/system_files/deck/usr/lib/hwsupport/steamos-automount.sh b/system_files/deck/usr/lib/hwsupport/steamos-automount.sh new file mode 100644 index 00000000..bdac3c4a --- /dev/null +++ b/system_files/deck/usr/lib/hwsupport/steamos-automount.sh @@ -0,0 +1,185 @@ +#!/bin/bash + +set -euo pipefail + +# Originally from https://serverfault.com/a/767079 + +# This script is called from our systemd unit file to mount or unmount +# a USB drive. + +usage() +{ + echo "Usage: $0 {add|remove} device_name (e.g. sdb1)" + exit 1 +} + +if [[ $# -ne 2 ]]; then + usage +fi + +ACTION=$1 +DEVBASE=$2 +DEVICE="/dev/${DEVBASE}" + +# Shared between this and the auto-mount script to ensure we're not double-triggering nor automounting while formatting +# or vice-versa. +MOUNT_LOCK="/var/run/jupiter-automount-${DEVBASE//\/_}.lock" + +# Obtain lock +exec 9<>"$MOUNT_LOCK" +if ! flock -n 9; then + echo "$MOUNT_LOCK is active: ignoring action $ACTION" + # Do not return a success exit code: it could end up putting the service in 'started' state without doing the mount + # work (further start commands will be ignored after that) + exit 1 +fi + +# Wait N seconds for steam +wait_steam() +{ + local i=0 + local wait=$1 + echo "Waiting up to $wait seconds for steam to load" + while ! pgrep -x steamwebhelper &>/dev/null && (( i++ < wait )); do + sleep 1 + done +} + +send_steam_url() +{ + local command="$1" + local arg="$2" + local encoded=$(urlencode "$arg") + if pgrep -x "steam" > /dev/null; then + # TODO use -ifrunning and check return value - if there was a steam process and it returns -1, the message wasn't sent + # need to retry until either steam process is gone or -ifrunning returns 0, or timeout i guess + systemd-run -M 1000@ --user --collect --wait sh -c "./.steam/root/ubuntu12_32/steam steam://${command}/${encoded@Q}" + echo "Sent URL to steam: steam://${command}/${arg} (steam://${command}/${encoded})" + else + echo "Could not send steam URL steam://${command}/${arg} (steam://${command}/${encoded}) -- steam not running" + fi +} + +# From https://gist.github.com/HazCod/da9ec610c3d50ebff7dd5e7cac76de05 +urlencode() +{ + [ -z "$1" ] || echo -n "$@" | hexdump -v -e '/1 "%02x"' | sed 's/\(..\)/%\1/g' +} + +do_mount() +{ + # Get info for this drive: $ID_FS_LABEL, and $ID_FS_TYPE + dev_json=$(lsblk -o PATH,LABEL,FSTYPE --json -- "$DEVICE" | jq '.blockdevices[0]') + ID_FS_LABEL=$(jq -r '.label | select(type == "string")' <<< "$dev_json") + ID_FS_TYPE=$(jq -r '.fstype | select(type == "string")' <<< "$dev_json") + + # Global mount options + OPTS="rw,noatime" + + # File system type specific mount options + #if [[ ${ID_FS_TYPE} == "vfat" ]]; then + # OPTS+=",users,gid=100,umask=000,shortname=mixed,utf8=1,flush" + #fi + + # We need symlinks for Steam for now, so only automount ext4 as that'll Steam will format right now + if [[ ${ID_FS_TYPE} != "ext4" ]]; then + echo "Error mounting ${DEVICE}: wrong fstype: ${ID_FS_TYPE} - ${dev_json}" + exit 2 + fi + + # Prior to talking to udisks, we need all udev hooks (we were started by one) to finish, so we know it has knowledge + # of the drive. Our own rule starts us as a service with --no-block, so we can wait for rules to settle here + # safely. + if ! udevadm settle; then + echo "Failed to wait for \`udevadm settle\`" + exit 1 + fi + + # Ask udisks to auto-mount. This needs a version of udisks that supports the 'as-user' option. + ret=0 + reply=$(busctl call --allow-interactive-authorization=false --expect-reply=true --json=short \ + org.freedesktop.UDisks2 \ + /org/freedesktop/UDisks2/block_devices/"${DEVBASE}" \ + org.freedesktop.UDisks2.Filesystem \ + Mount 'a{sv}' 3 \ + as-user s deck \ + auth.no_user_interaction b true \ + options s "$OPTS") || ret=$? + + if [[ $ret -ne 0 ]]; then + echo "Error mounting ${DEVICE} (status = $ret)" + exit 1 + fi + + # Expected reply is of the format + # {"type":"s","data":["/run/media/deck/home"]} + mount_point=$(jq -r '.data[0] | select(type == "string")' <<< "$reply" || true) + if [[ -z $mount_point ]]; then + echo "Error when mounting ${DEVICE}: udisks returned success but could not parse reply:" + echo "---"$'\n'"$reply"$'\n'"---" + exit 1 + fi + + # Create a symlink from /run/media to keep compatibility with apps + # that use the older mount point (for SD cards only). + case "${DEVBASE}" in + mmcblk0p*) + if [[ -z "${ID_FS_LABEL}" ]]; then + old_mount_point="/run/media/${DEVBASE}" + else + old_mount_point="/run/media/${mount_point##*/}" + fi + if [[ ! -d "${old_mount_point}" ]]; then + rm -f -- "${old_mount_point}" + ln -s -- "${mount_point}" "${old_mount_point}" + fi + ;; + esac + + echo "**** Mounted ${DEVICE} at ${mount_point} ****" + + # If Steam is running, notify it + send_steam_url "addlibraryfolder" "${mount_point}" +} + +do_unmount() +{ + # If Steam is running, notify it + local mount_point=$(findmnt -fno TARGET "${DEVICE}" || true) + if [[ -n $mount_point ]]; then + send_steam_url "removelibraryfolder" "${mount_point}" + # Remove symlink to the mount point that we're unmounting + find /run/media -maxdepth 1 -xdev -type l -lname "${mount_point}" -exec rm -- {} \; + else + # If we don't know the mount point then remove all broken symlinks + find /run/media -maxdepth 1 -xdev -xtype l -exec rm -- {} \; + fi +} + +do_retrigger() +{ + local mount_point=$(findmnt -fno TARGET "${DEVICE}" || true) + [[ -n $mount_point ]] || return 0 + + # In retrigger mode, we want to wait a bit for steam as the common pattern is starting in parallel with a retrigger + wait_steam 10 + # This is a truly gnarly way to ensure steam is ready for commands. + # TODO literally anything else + sleep 6 + send_steam_url "addlibraryfolder" "${mount_point}" +} + +case "${ACTION}" in + add) + do_mount + ;; + remove) + do_unmount + ;; + retrigger) + do_retrigger + ;; + *) + usage + ;; +esac