librem14-coreboot-grub-qubes/build.sh
2022-01-07 15:34:22 +01:00

668 lines
22 KiB
Bash

#!/bin/bash
# use TERM to exit on error
trap "exit 1" TERM
export TOP_PID=$$
# coreboot repo
COREBOOT_REPO_PURISM="https://source.puri.sm/coreboot/coreboot.git"
COREBOOT_REPO_TAG="4.14-Purism-1"
# dependencies
DEPS_BASE=(dmidecode wget sha256sum gzip)
DEPS_FLASH=(git build-essential libpci-dev libudev-dev zlib1g-dev)
DEPS_FLASH_FEDORA=(git gcc g++ make xz bzip2 pciutils-devel)
DEPS_COREBOOT=(gnat m4 bison flex libncurses5-dev python python2)
DEPS_COREBOOT_FEDORA=(gcc-gnat bison flex ncurses-devel zlib-devel python python2 patch)
DEPS_GRUB2=(autoconf automake autopoint libfreetype-dev fonts-unifont)
DEPS_GRUB2_FEDORA=(autoconf automake gettext-devel freetype-devel unifont-fonts)
#local dirs
LOCAL_TOOLS_PATH="./tools"
LOCAL_FIRMWARE_PATH="./firmware"
LOCAL_TEMP_DIR="$(mktemp -d)"
# locally compiled coreboot utils
CBFSTOOL_CB="./coreboot/util/cbfstool/cbfstool"
IFDTOOL_CB="./coreboot/util/ifdtool/ifdtool"
# functions, functions, functions
check_root() {
if [[ "$EUID" != 0 ]]; then
die "This script must be run as root; re-run prefixed with sudo"
fi
}
print_info () {
clear
echo "################################################"
echo "## Purism Librem coreboot Utility "
echo "################################################"
if [[ "${LIBREM_MODEL^^}" != "UNKNOWN" ]]; then
model=${LIBREM_MODEL^}
echo "# Device: Librem ${model//_/ }"
echo "# Serial: ${DMIDECODE_SERIAL_NUMBER}"
echo "# Firmware: ${FW_TYPE_STRING}"
echo "# Version: ${CURRENT_FW_VER} ${CURRENT_FW_DATE}"
echo "################################################"
fi
}
initial_setup () {
# get model, firmware version, serial # via dmidecode
vendor=$(dmidecode -s bios-vendor)
model=$(dmidecode -s system-product-name | grep -i Librem)
CURRENT_FW_VER=$(dmidecode -s bios-version)
CURRENT_FW_DATE=$(dmidecode -s bios-release-date)
DMIDECODE_SERIAL_NUMBER=$(dmidecode -t 1 | grep "Serial Number" | cut -d' ' -f 3-)
# set CURRENT_FW_TYPE type
if [ "$vendor" = "coreboot" ]; then
case ${CURRENT_FW_VER^^} in
*"PUREBOOT"* )
CURRENT_FW_TYPE="HEADS";;
*"HEADS"* )
CURRENT_FW_TYPE="HEADS";;
*"GRUB2"* )
CURRENT_FW_TYPE="GRUB2";;
* )
CURRENT_FW_TYPE="STANDARD";;
esac
else
CURRENT_FW_TYPE="UNKNOWN"
fi
FW_UPDATE=""
case $CURRENT_FW_TYPE in
"HEADS" )
FW_TYPE_STRING="PureBoot (coreboot+Heads)"
CURR_FW_NUM=`echo ${CURRENT_FW_VER} | sed -e "s/^PureBoot-beta-//" -e "s/^PureBoot-Release-//"`
UPD_FW_NUM=`echo ${COREBOOT_HEADS_VERSION} | sed -e "s/^PureBoot-beta-//" -e "s/^PureBoot-Release-//"`
[[ "$UPD_FW_NUM" > "$CURR_FW_NUM" ]] \
&& FW_UPDATE=`echo ${COREBOOT_HEADS_VERSION} | tr '-' ' '`
CURRENT_FW_VER=`echo ${CURRENT_FW_VER#PureBoot-} | tr '-' ' '`
CURRENT_FW_DATE=""
;;
"STANDARD" )
FW_TYPE_STRING="Standard (coreboot+SeaBIOS)"
[[ "$COREBOOT_SEABIOS_VERSION" > "$CURRENT_FW_VER" ]] \
&& FW_UPDATE="$COREBOOT_SEABIOS_VERSION";
CURRENT_FW_DATE="(${CURRENT_FW_DATE})"
;;
"GRUB2" )
FW_TYPE_STRING="Grub2 (coreboot+Grub2)"
[[ "$COREBOOT_GRUB2_VERSION" > "$CURRENT_FW_VER" ]] \
&& FW_UPDATE="$COREBOOT_GRUB2_VERSION";
CURRENT_FW_DATE="(${CURRENT_FW_DATE})"
;;
* )
FW_TYPE_STRING="Unknown" ;;
esac
# set LIBREM_MODEL, MODEL_INDEX
# strip leading 'librem' + space/underscore
model=`echo ${model,,} | sed -e "s/^librem//" -e "s/^ //" -e "s/^_//"`
case ${model} in
"14")
LIBREM_MODEL="14";
PLATFORM="CML";
MODEL_INDEX="9";;
*)
LIBREM_MODEL="unknown";
PLATFORM="UNK";
MODEL_INDEX="0";;
esac
}
cleanup() {
# ensure all files owned by calling/non-root user
chown -R -f $(logname). .
}
die () {
local msg=$1
if [ ! -z "$msg" ]; then
echo ""
echo -e "$msg"
echo ""
fi
kill -s TERM $TOP_PID
cleanup
exit 1
}
get_flashrom () {
# check if flashrom exists on system
echo ""
echo "Checking for usable version of flashrom"
FLASHROM_CMD=$(which flashrom)
if [[ ! -z "$FLASHROM_CMD" && "$PLATFORM" != "CML" ]]; then
#check version
version=$($FLASHROM_CMD -R | awk -F" " '{print $2;exit}' | grep "1.")
if [[ "$version" > "v1.1" ]]; then
# verify build supports logging (some PureOS/Debian versions broken)
if $FLASHROM_CMD -L -o /tmp/flashrom.log >/dev/null 2>&1 ; then
#use system flashrom
echo "Found built-in flashrom version $version"
return
fi
fi
fi
# no system flashrom, not version 1.2+, or PLATFORM == CML:
# build from upstream flashrom source
# check deps first
check_dependencies "flashrom"
if [ ! -d ${LOCAL_TOOLS_PATH}/flashrom ]; then
(
mkdir -p ${LOCAL_TOOLS_PATH}
cd ${LOCAL_TOOLS_PATH}
echo -n "Cloning flashrom git repo..."
git clone --single-branch --branch master https://review.coreboot.org/flashrom.git >/dev/null
[ $? -ne 0 ] && die "Error cloning flashrom repo from git"
echo "done"
)
fi
(
echo -n "Building flashrom from source..."
cd ${LOCAL_TOOLS_PATH}/flashrom
git fetch origin >/dev/null 2>&1
if ! git show c64486b >/dev/null 2>&1; then
cd ..
rm -rf ./flashrom
git clone --single-branch --branch master https://review.coreboot.org/flashrom.git >/dev/null
[ $? -ne 0 ] && die "Error cloning flashrom repo from git"
cd flashrom
fi
git reset --hard c64486b >/dev/null 2>&1
[ $? -ne 0 ] && die "Error checking out flashrom via git"
make CONFIG_NOTHING=yes CONFIG_DUMMY=yes CONFIG_INTERNAL=yes WARNERROR=no
[ $? -ne 0 ] && die "Error building flashrom (missing build dependency libpci-dev?)"
echo "done"
)
FLASHROM_CMD="${LOCAL_TOOLS_PATH}/flashrom/flashrom"
}
get_serial () {
if [ "$PLATFORM" = "BDW" ]; then
#no serial to set, clear file if exists
rm -f ${LOCAL_TEMP_DIR}/serial_number.txt >/dev/null
return;
fi
#prompt user for serial # selection/entry
echo ""
echo "Set the device serial number:"
echo ""
if [ "$DMIDECODE_SERIAL_NUMBER" != "" ]; then
DMIDECODE_SERIAL_OPT=1
MANUAL_SERIAL_OPT=2
NO_SERIAL_OPT=3
echo "${DMIDECODE_SERIAL_OPT} - Extracted from your local system (${DMIDECODE_SERIAL_NUMBER})"
else
DMIDECODE_SERIAL_OPT=0
MANUAL_SERIAL_OPT=1
NO_SERIAL_OPT=2
fi
echo "${MANUAL_SERIAL_OPT} - Enter serial number manually"
echo "${NO_SERIAL_OPT} - Do not set a serial number"
echo ""
serial=0
[ ${MODEL_INDEX} = 0 ] && default=2 || default=1
while [ "$serial" == "0" ]; do
read -r -p "Enter your choice (default: $default): " serial
[ "$serial" = "" ] && serial=$default
case $serial in
${DMIDECODE_SERIAL_OPT} )
if [ "$DMIDECODE_SERIAL_OPT" = 1 ]; then
echo -n "$DMIDECODE_SERIAL_NUMBER" > ${LOCAL_TEMP_DIR}/serial_number.txt
else
echo "Invalid option"
serial=0
fi
;;
${MANUAL_SERIAL_OPT} )
read -r -p "Enter the machine's serial number : " serial_number
echo -n "$serial_number" > ${LOCAL_TEMP_DIR}/serial_number.txt
;;
${NO_SERIAL_OPT} )
echo -n "" > ${LOCAL_TEMP_DIR}/serial_number.txt
;;
* )
echo "Invalid option"
serial=0
;;
esac
done
echo ""
}
flashrom_progress() {
local current=0
local total_bytes=0
local percent=0
local IN=''
local spin='-\|/'
local spin_idx=0
local progressbar=''
local progressbar2=''
local status='init'
local prev_word=''
local prev_prev_word=''
progressbar2=$(for ((i=0; i < 49; i++)) do echo -ne ' ' ; done)
echo "Initializing internal Flash Programmer"
while true ; do
prev_prev_word=$prev_word
prev_word=$IN
read -r -d' ' IN || break
if [ "$total_bytes" != "0" ]; then
current=$(echo "$IN" | grep -E -o '0x[0-9a-f]+-0x[0-9a-f]+:.*' | grep -E -o "0x[0-9a-f]+" | tail -n 1)
if [ "${current}" != "" ]; then
percent=$((100 * (current + 1) / total_bytes))
progressbar=$(for ((i=0; i < $((percent / 2)); i++)) do echo -ne '#' ; done)
progressbar2=$(for ((i=0; i < $((49 - percent / 2)); i++)) do echo -ne ' ' ; done)
fi
else
if [ "$prev_prev_word" == "Reading" ] && [ "$IN" == "bytes" ]; then
total_bytes=$prev_word
echo "Total flash size : $total_bytes bytes"
fi
fi
if [ "$percent" -eq 100 ]; then
spin_idx=4
else
spin_idx=$(( (spin_idx+1) %4 ))
fi
if [ "$status" == "init" ]; then
if [ "$IN" == "contents..." ]; then
status="reading"
echo "Reading old flash contents. Please wait..."
fi
fi
if [ "$status" == "reading" ]; then
if echo "${IN}" | grep "done." > /dev/null ; then
status="writing"
fi
fi
if [ "$status" == "writing" ]; then
echo -ne "Flashing: [${progressbar}${spin:$spin_idx:1}${progressbar2}] (${percent}%)\\r"
if echo "$IN" | grep "Verifying" > /dev/null ; then
status="verifying"
echo ""
echo "Verifying flash contents. Please wait..."
fi
if echo "$IN" | grep "identical" > /dev/null ; then
status="done"
echo ""
echo "The flash contents are identical to the image being flashed."
fi
fi
if [ "$status" == "verifying" ]; then
if echo "${IN}" | grep "VERIFIED." > /dev/null ; then
status="done"
echo "The flash contents were verified and the image was flashed correctly."
fi
fi
done
echo ""
if [ "$status" == "done" ]; then
return 0
else
echo 'Error flashing coreboot -- see timestampped flashrom log in current directory for more info'
echo ""
return 1
fi
}
set_serial_number () {
# pass in full path of file in which to inject serial
[[ -z "$1" || ! -f "$1" ]] && die "Error: a valid firmware filename is required"
# check if we have a serial # to add to cbfs
if [ -f ${LOCAL_TEMP_DIR}/serial_number.txt ]; then
# get cbfstool if needed
get_cbfstool
# inject serial into coreboot image
echo "Injecting serial number into firmware image"
$CBFSTOOL_BIN "$1" remove -n serial_number -r COREBOOT >/dev/null 2>&1
$CBFSTOOL_BIN "$1" add -n serial_number -t raw -f ${LOCAL_TEMP_DIR}/serial_number.txt -r COREBOOT >/dev/null 2>&1
[ $? -ne 0 ] && die "Error adding serial number to file ${1}"
fi
}
backup_firmware () {
# assume FLASHROM_CMD exists/is set already, LOCAL_TEMP_DIR set up
mkdir -p ${BACKUP_DIR}
DATE_FIX=$(date '+%Y%m%d-%H%M%S')
$FLASHROM_CMD -p internal:ich_spi_mode=hwseq -r ${BACKUP_DIR}/backup-${DATE_FIX}.bin -o ${BACKUP_DIR}/backup-$(date '+%Y%m%d-%H%M%S').log >/dev/null 2>&1
if [ $? -ne 0 ]; then
die "Error reading current firmware; see flashrom log for more info."
fi
}
flash_firmware_internal () {
# assume FLASHROM_CMD exists/is set already
# pass in full path of file to be flashed
[[ -z "$1" || ! -f "$1" ]] && die "Error: a valid filename to be flashed is required"
$FLASHROM_CMD -p internal:ich_spi_mode=hwseq -w "$1" -V -o "./flashrom-$(date '+%Y%m%d-%H%M%S').log" 2>&1 | flashrom_progress
return $?
}
update_serial_number () {
# ensure using SeaBIOS
if [[ "${CURRENT_FW_TYPE}" != "STANDARD" ]]; then
echo "This feature is only valid for use with the standard/SeaBIOS firmware"
echo ""
return
fi
# show serial menu / create serial.txt
get_serial
# check for / get flashrom
get_flashrom
# read current firmware
echo ""
if [ ! -f ${LOCAL_TEMP_DIR}/${CURRENT_FW_BIN} ]; then
echo "Reading current firmware..."
read_current_firmware
else
echo "Current firmware already read from flash"
fi
# inject into current firmware
set_serial_number "${LOCAL_TEMP_DIR}/${CURRENT_FW_BIN}"
# prompt to update
echo ""
flash=0
while [ "$flash" != "y" ] && [ "$flash" != "n" ]; do
read -r -p "Do you want to update the serial number now (Y/n) ? " flash
[[ "$flash" == "N" ]] && flash="n"
[[ "$flash" = "" || "$flash" == "Y" ]] && flash="y"
done
if [ "$flash" == "y" ]; then
echo ""
echo "coreboot flashing in progress. Do NOT interrupt this process."
echo ""
flash_firmware_internal "${LOCAL_TEMP_DIR}/${CURRENT_FW_BIN}"
if [ $? -eq 0 ]; then
echo "Serial number successfully updated; change will take effect on next boot"
echo ""
fi
fi
}
update_crossgcc_toolchain() {
# assume called from coreboot root dir
local CURRENT_TOOLCHAIN_VERSION=0
local GCC_FILE='util/crossgcc/xgcc/bin/i386-elf-gcc'
local TARGET_TOOLCHAIN_VERSION="$(git log -n 1 --pretty=%h util/crossgcc)"
if [ -f "${GCC_FILE}" ]; then
CURRENT_TOOLCHAIN_VERSION=$(${GCC_FILE} --version | grep -m 1 'coreboot toolchain' \
| cut -f2 -d'v' | cut -f2 -d'_' | cut -f1 -d')')
fi
if [ "${CURRENT_TOOLCHAIN_VERSION}" != "${TARGET_TOOLCHAIN_VERSION}" ]; then
echo "coreboot toolchain version changed from ${CURRENT_TOOLCHAIN_VERSION} to ${TARGET_TOOLCHAIN_VERSION}"
echo "Cleaning crossgcc compiler before rebuilding it"
make crossgcc-clean
retries=0
until [ "$retries" -ge 5 ]
do
make crossgcc-i386 CPUS=$(nproc) && break
retries=$((retries+1))
done
[ $? -ne 0 ] && die "Error building coreboot toolchain" || true
fi
}
build_coreboot() {
# extract / set device serial # to be injected later
get_serial
echo ""
echo "Checking out/updating coreboot repo..."
echo ""
# check dir, clone if needed
if [ ! -d coreboot ]; then
echo ""
git clone --branch ${COREBOOT_REPO_TAG} ${COREBOOT_REPO_PURISM} coreboot
[ $? -ne 0 ] && die "Error cloning coreboot git repo"
(
cd coreboot
# It can fail if you don't have a git global user.name/user.email setup
make gitconfig 2>/dev/null || true
# init/update submodules
git submodule update --init --force --checkout >/dev/null 2>&1
[ $? -ne 0 ] && die "Error checking out/updating coreboot submodules"
)
fi
# check out correct branch
(
cd coreboot
git fetch 2>/dev/null
[ $? -ne 0 ] && die "Error fetching coreboot git repo"
git fetch --tags 2>/dev/null
[ $? -ne 0 ] && die "Error fetching coreboot git repo"
git checkout --detach ${COREBOOT_REPO_TAG} 2>/dev/null
[ $? -ne 0 ] && die "Error checking out coreboot git repo"
# ensure submodules sane
if [[ "`git diff 3rdparty`" != "" ]]; then
git submodule update --force --checkout >/dev/null 2>&1
[[ "`git diff 3rdparty`" != "" ]] && \
die "submodules have been modified; build would not be reproducible"
fi
#build cbfstool and ifdtool
(
cd util/cbfstool
make
[ $? -ne 0 ] && die "Error building cbfstool"
)
(
cd util/ifdtool
make
[ $? -ne 0 ] && die "Error building ifdtool"
)
)
# build coreboot
(
# set pwd
cd coreboot
# let user know this will take a few
echo -e "\n\n"
echo "Ready to build coreboot - this will take some time depending on your connection"
echo "speed and CPU/RAM, esp if the toolchain needs to be built."
echo ""
# check/build toolchain
update_crossgcc_toolchain || die
# get git version
GIT_VERSION=$(git describe --tags --dirty)
# do a clean build
make distclean
# copy config
cp ../resources/config .config
# copt grub config
cp ../resources/grub.cfg grub.cfg
echo "CONFIG_LOCALVERSION=\"${GIT_VERSION}\"" >> .config
make olddefconfig >/dev/null
# build coreboot and payload(s)
make
[ $? -ne 0 ] && die "Error building coreboot"
# copy to root dir
#cp build/coreboot.rom ../${COREBOOT_IMAGE}
)
# calculate hash of BIOS region before injecting bootorder/serial
${IFDTOOL_CB} -x ${COREBOOT_IMAGE}
bios_sha=$(sha256sum flashregion_1_bios.bin | awk '{print $1}')
rm -f flashregion*.bin
# set serial
set_serial_number ${COREBOOT_IMAGE}
#set boot delay
# add an 8s boot delay for the Librem Mini so splash screen
# actually shown on displays with sluggish init
if [[ ${LIBREM_MODEL^^} == "MINI"* ]]; then
${CBFSTOOL_CB} ${COREBOOT_IMAGE} add-int -i 8000 -n etc/boot-menu-wait >/dev/null
fi
# print CBFS contents
${CBFSTOOL_CB} ${COREBOOT_IMAGE} print
echo ""
echo ""
echo "Finished building coreboot for Librem ${LIBREM_MODEL^}"
echo ""
COREBOOT_BIOS_SHA="COREBOOT_BIOS_SHA_${LIBREM_MODEL^^}"
COREBOOT_BIOS_SHA=${!COREBOOT_BIOS_SHA}
if [ "${bios_sha}" != "${COREBOOT_BIOS_SHA}" ]; then
echo "WARNING: Built coreboot image hash does not match expected reproducible build hash"
echo "Built: ${bios_sha}"
echo "Expected: ${COREBOOT_BIOS_SHA}"
else
echo "Built coreboot image hash matches expected reproducible build hash"
fi
# prompt to flash
echo ""
if [ ${CURRENT_FW_TYPE} != "HEADS" ]; then
flash=0
while [ "$flash" != "y" ] && [ "$flash" != "n" ]; do
read -r -p "Do you want to flash the coreboot update now (y/N) ? " flash
if [ "$flash" = "" ] || [ "$flash" == "N" ]; then
flash="n"
fi
if [ "$flash" == "Y" ]; then
flash="y"
fi
done
if [ "$flash" == "y" ]; then
# check for / get flashrom
get_flashrom
echo ""
echo "coreboot flashing in progress. Do NOT interrupt this process."
echo ""
flash_firmware_internal ${COREBOOT_IMAGE}
if [ $? -eq 0 ]; then
echo ""
echo "You must reboot for the coreboot update to take effect."
echo ""
read -r -p "Reboot now? (y/N) ? " rb
if [ "$rb" = "Y" ] || [ "$rb" == "y" ]; then
cleanup
reboot
fi
fi
else
echo ""
fi
else
echo "You may now copy the coreboot update file ($COREBOOT_IMAGE) to USB for updating via Heads."
echo ""
read -ep "Press [Enter] to exit."
cleanup
exit 0
fi
}
check_dependencies() {
local dep_type=$1
local DEPS
local use_dnf=false
[[ `which dnf 2>/dev/null` ]] && use_dnf=true
case $dep_type in
"base")
DEPS=${DEPS_BASE[@]} ;;
"flashrom")
if [ $use_dnf = true ]; then
DEPS=(${DEPS_BASE[@]} ${DEPS_FLASH_FEDORA[@]}) ;
else
DEPS=(${DEPS_BASE[@]} ${DEPS_FLASH[@]}) ;
fi
;;
"coreboot")
if [ $use_dnf = true ]; then
DEPS=(${DEPS_BASE[@]} ${DEPS_FLASH_FEDORA[@]} ${DEPS_GRUB2_FEDORA[@]}) ;
else
DEPS=(${DEPS_BASE[@]} ${DEPS_FLASH[@]} ${DEPS_COREBOOT[@]} ${DEPS_GRUB2[@]}) ;
fi
;;
*)
;;
esac
local pkg needed=()
for pkg in "${DEPS[@]}"; do
if [[ ! `which ${pkg} 2>/dev/null` \
&& ! `dnf list installed 2>/dev/null | grep "^${pkg}"` \
&& ! `apt list --installed 2>/dev/null | grep "^${pkg}"` \
&& ! -f "/usr/share/doc/${pkg}/copyright" ]]; then
needed+=("${pkg}")
fi
done
if [[ "${#needed[@]}" -gt 0 ]]; then
echo ""
echo "One or more required dependencies are missing: ${needed[@]}"
echo "The script will now attempt to install them for you"
echo ""
read -rp "Press [Enter] to continue " discard
echo ""
if [ $use_dnf = true ]; then
if ! sudo dnf -y install "${needed[@]}" ; then
die "Some required dependencies could not be installed automatically"
fi
elif which apt >/dev/null ; then
sudo apt-get update >/dev/null 2>&1
if ! sudo apt-get -y install "${needed[@]}" ; then
die "Some required dependencies could not be installed automatically"
fi
else
echo "The script was unable to install the required dependencies automatically"
echo ""
die "Please ensure all required dependencies are installed and re-run this script"
fi
fi
}
# Start of main script
# let's do stuff
check_root
check_dependencies "base"
check_dependencies "flashrom"
check_dependencies "coreboot"
initial_setup
print_info
get_flashrom
build_coreboot