#!/bin/bash PROG_REQ=( kpartx mkfs.ext4 mkfs.vfat losetup readlink awk parted ) SLACK=128 function cleanup_disk_image() { kpartx -d /dev/mapper/diskimage dmsetup remove diskimage losetup -d $_loop_dev remove_tmp_files if [ "$DESTDIR_DEL" = true ] then rm -rf "$DESTDIR" fi } function cleanup_disk_image_error() { cleanup_disk_image rm $IMG } function check_depends() { for ((i=0; i<${#PROG_REQ[@]}; i++)) do if ! command -v ${PROG_REQ[i]} > /dev/null then echo "Please install the needed program: ${PROG_REQ[i]}." exit 1 fi done } function add_rootfs() { local dev=${LOAD_CMD%:*} dev=${dev##* } local par=${LOAD_CMD#*:} local j=$1 if [ -z "$dev" ] || [ -z "$par" ] then echo "Could not parse device and partition." exit 1 fi par=$(( $par + $j )) if [[ $LOAD_CMD =~ mmc ]] then retval="/dev/mmcblk${dev}p${par}" elif [[ $LOAD_CMD =~ scsi ]] then # converts number to a scsi device character dev=$((dev + 97)) dev=$(printf %x $dev) dev=$(printf "\x$dev") retval="/dev/sd${dev}${par}" else echo "Only mmc and scsi are supported for automatically setting the root partition." echo "Manually set DOM0_CMD with the root device in the config file to bypass this." exit 1 fi } function get_interrupts() { local xen_intr_txt="$1" local num_fields local intr_num local intr_str local val local i while IFS= read -r val do #Remove the prefix "interrupts = <" val=${val#*"interrupts = <"} #Remove the suffix ">;" from each line val=${val%>;*} num_fields=`echo $val | awk '{print NF}'` #We expect the following format #interrupts = <0x00 irq_no flags 0x00 irq_no flags 0x00 irq_no flags ..>; if test $((num_fields % 3)) -ne 0 then return 1 fi #Position of the first interrupt no i=2 while test $i -lt $num_fields do intr_num=`echo $val | awk -v i="$i" '{print $i}'` #The next irq_no will be after 3 places i=$((i+3)) intr_num=$((intr_num + 32)) if test "$intr_str" then intr_str="$intr_str," fi intr_str="$intr_str"$intr_num done done < "$xen_intr_txt" if test "$intr_str" then echo "$intr_str" fi } function contains() { local item="$1" local list="$2" local each for each in $list do if test "$each" = "$item" then echo "yes" fi done } function check_if_force_assign_iommu() { local xen_path="$1" local dtb="$2" local node="$3" local path local subnode local subnode_cnt local props local xen_path_present local assign_present subnode_cnt=$(fdtget -l "$dtb" "$node" | wc -l) props=$(fdtget -p "$dtb" "$node") xen_path_present=$(contains "xen,path" "$props") assign_present=$(contains "xen,force-assign-without-iommu" "$props") if test "$xen_path_present" = "yes" && test "$assign_present" = "yes" then path=$(fdtget "$dtb" "$node" "xen,path") if test "$path" = "$xen_path" then echo "yes" return 0 fi fi if test $subnode_cnt -gt 0 then for subnode in `fdtget -l "$dtb" "$node"` do subnode="$node/$subnode" check_if_force_assign_iommu "$xen_path" "$dtb" "$subnode" done fi } function get_iommu_prop() { local xen_path_txt="$1" local domU_dtb="$2" local dtdev_str local devpath local force_assign #Iterate through all the lines in the file while IFS= read -r devpath do #Extract the path of the device from each line # #For eg, the line may be as follows #xen,path = "/axi/serial@ff010000"; # #What we want is the following substring #"/axi/serial@ff010000" devpath=`echo $devpath | awk '{print $3}'` devpath=${devpath#*\"} devpath=${devpath%\";*} force_assign=$(check_if_force_assign_iommu "$devpath" "$domU_dtb" "/passthrough") if test -z "$force_assign" then if test "$dtdev_str" then dtdev_str="$dtdev_str, " fi dtdev_str=${dtdev_str}"\""${devpath}"\"" fi done < "$xen_path_txt" if test "$dtdev_str" then echo "$dtdev_str" fi } function get_iomem() { local xen_reg_txt="$1" local addr="$2" local size="$3" local val local i local num_fields local iomem_str local address_size_pair local mfn local gfn local size_ local PAGE_SHIFT=12 #Iterate through all the lines in the file #The file will look similar to the following:- # #xen,reg = <0x00 0xff010000 0x00 0x1000 0x00 0xff010000 0x00 0xff110000 0x00 0x1000 0x00 0xff110000 0x00 0xff120000 0x00 0x1000 0x00 0xff120000 0x00 0xff130000 0x00 0x1000 0x00 0xff130000 0x00 0xff140000 0x00 0x1000 0x00 0xff140000>; #xen,reg = <0x00 0xfd0c0000 0x00 0x2000 0x00 0xfd0c0000>; while IFS= read -r val do #Remove the prefix "xen,reg = <" from each line val=${val#*"xen,reg = <"} #Remove the suffix ">;" from each line val=${val%>;*} num_fields=`echo $val | awk '{print NF}'` i=0 while test $i -lt $num_fields do i=$((i + addr)) #Extract the iomem physical address mfn=`echo $val | awk -v i="$i" '{print $i}'` #Calculate the page address mfn=0x$(printf "%X" $(( $((mfn>>PAGE_SHIFT)) & 0xfffff)) ) i=$((i + size)) #Extract the size of the iomem address size_=`echo $val | awk -v i="$i" '{print $i}'` #Calculatde the number of pages size_=$(printf "%X" $(( $((size_>>PAGE_SHIFT)) & 0xfffff)) ) i=$((i + addr)) #Extract the iomem guest address gfn=`echo $val | awk -v i="$i" '{print $i}'` #Calculate the page address gfn=0x$(printf "%X" $(( $((gfn>>PAGE_SHIFT)) & 0xfffff)) ) if test "$gfn" != "$mfn" then address_size_pair="\"$mfn,${size_}@${gfn}\"" else address_size_pair="\"$mfn,$size_\"" fi if test "$iomem_str" then iomem_str="$iomem_str, $address_size_pair" else iomem_str=$address_size_pair fi done done < "$xen_reg_txt" if test "$iomem_str" then echo "$iomem_str" fi } function update_domU_cfg() { local domU_dtb="$1" local domU_cfg_file="$2" local addr=$(fdtget $domU_dtb /passthrough \#address-cells) local size=$(fdtget $domU_dtb /passthrough \#size-cells) local iomem_str local dtdev_str local intr_str local tmp_dts local tmp_search_results tmp_dts=`mktemp` tmp_files+=($tmp_dts) dtc -I dtb -O dts -o "$tmp_dts" "$domU_dtb" tmp_search_results=`mktemp` tmp_files+=($tmp_search_results) #Let's get all the lines containing xen,path in a file grep 'xen,reg' "$tmp_dts" > "$tmp_search_results" #From "xen,reg", one can determine the "iomem" attribute of the #cfg file. iomem_str="$(get_iomem $tmp_search_results $addr $size)" if test -z "$iomem_str" then echo "Error occurred while generating the iomem property" return 1 else iomem_str="iomem = [ "$iomem_str" ]" echo "${iomem_str}" >> $domU_cfg_file fi #Let's get all the lines containing xen,path in a file grep 'xen,path' $tmp_dts > $tmp_search_results #From "xen,path", one can determine the "dtdev" attribute of the #cfg file. This attribute is optional. dtdev_str=$(get_iommu_prop $tmp_search_results $domU_dtb) if test "$dtdev_str" then dtdev_str="dtdev = [ "$dtdev_str" ]" echo "${dtdev_str}" >> $domU_cfg_file fi #Let's get all the lines containing interrupts in a file grep 'interrupts' $tmp_dts > $tmp_search_results #From "interrupts", one can determine the "irqs" attribute of the #cfg file. intr_str=$(get_interrupts $tmp_search_results) if test "$intr_str" then intr_str="irqs = [ "$intr_str" ]" echo "${intr_str}" >> $domU_cfg_file fi return 0 } function generate_domU_configs() { local i=0 local j=$1 # $j + 1 - 1 as it is starting from 0 local n=$1 local dest local dtb_name mount /dev/mapper/diskimage$j $DESTDIR/part/disk$j mkdir -p $DESTDIR/part/disk$j/etc/xen add_rootfs 0 first_part=$retval while test $i -lt $NUM_DOMUS do if test "${DOMU_NOBOOT[$i]}" then if test -z "${DOMU_PASSTHROUGH_DTB[$i]}" && test "${DOMU_PASSTHROUGH_PATHS[$i]}" then output_dir=`mktemp -d` tmp_dirs+=($output_dir) # Check if the below function returns error # and unmount the partition compile_merge_partial_dts $output_dir "$PASSTHROUGH_DTS_REPO" if test $? -ne 0 then umount $DESTDIR/part/disk$j cleanup_disk_image_error exit 1 fi fi dest="$DESTDIR/part/disk$j/etc/xen/domU$i.cfg" echo "name=\"domU$i\"" >> $dest echo "memory=${DOMU_MEM[$i]}" >> $dest echo "vcpus=${DOMU_VCPUS[$i]}" >> $dest echo "# mount $first_part /boot" >> $dest echo "kernel=\"/boot/${DOMU_KERNEL[$i]}\"" >> $dest if test "${DOMU_RAMDISK[$i]}" then echo "ramdisk=\"/boot/${DOMU_RAMDISK[$i]}\"" >> $dest fi if [ -z "${DOMU_CMD[$i]}" ] then DOMU_CMD[$i]="console=hvc0" fi if [[ ! ${DOMU_CMD[$i]} =~ root= ]] then if test -z "${DOMU_ROOTFS[$i]}" then DOMU_CMD[$i]="${DOMU_CMD[$i]} root=/dev/ram0" >> $dest else DOMU_CMD[$i]="${DOMU_CMD[$i]} root=/dev/xvda" >> $dest fi fi echo "extra=\"${DOMU_CMD[$i]}\"" >> $dest if test "${DOMU_ROOTFS[$i]}" then add_rootfs $n echo "disk=[\"$retval,,xvda\"]" >> $dest fi if test "${DOMU_PASSTHROUGH_DTB[$i]}" then cp ${DOMU_PASSTHROUGH_DTB[$i]} $DESTDIR/part/disk$j/etc/xen/ dtb_name="$(basename ${DOMU_PASSTHROUGH_DTB[$i]})" echo "device_tree=\"/etc/xen/$dtb_name\"" >> $dest update_domU_cfg ${DOMU_PASSTHROUGH_DTB[$i]} $dest if test $? -ne 0 then umount $DESTDIR/part/disk$j cleanup_disk_image_error exit 1 fi fi fi n=$(( $n + 1 )) i=$(( $i + 1 )) done umount $DESTDIR/part/disk$j } function add_partition() { local rootfs="$1" local aux_dir=$(mktemp -d) cd "$aux_dir" if [[ $rootfs = *.cpio ]] then cat "${UBOOT_OUT_ABS}/$rootfs" | cpio -id elif [[ $rootfs = *.cpio.gz ]] then cat "${UBOOT_OUT_ABS}/$rootfs" | gunzip | cpio -id elif [[ $rootfs = *.tar.gz ]] then tar -xf "${UBOOT_OUT_ABS}/$rootfs" else echo "Ignoring $rootfs: unsupported file format. Use cpio or cpio.gz or tar.gz." exit 1 fi _part_size=$(du -sb| awk '{print $1}') cd - rm -rf "$aux_dir" _part_size=$(( $_part_size + $offset - 1)) _part_size=$(( $_part_size & ~($offset - 1) )) # account for gzip compression _part_size=$(( $_part_size * 2 )) # add some slack _part_size=$(( $SLACK*1024*1024 + $_part_size )) _part_size=$(( $_part_size / 1024 / 1024 )) echo PART size: "$_part_size"MB prev=$(( $_npart - 1 )) _sector_start[$_npart]=$(( ${_sector_end[$prev]} + 1 )) _sector_end[$_npart]=$(( $_part_size * 1024 * 1024 / $_sector_size + ${_sector_start[$_npart]} - 1)) _tot_size=$(( $_tot_size + $_part_size )) _npart=$(( $_npart + 1 )) } function align_size_power_of_two() { local _cnt_bits=0 local num=$1 local _num=$num while [ $_num -ne 0 ] do _num=$((_num >> 1)) _cnt_bits=$((_cnt_bits + 1)) done _num=$((1<<(_cnt_bits - 1))) if [ $num -gt $_num ] then _num=$((1<<_cnt_bits)) fi echo $_num } function write_rootfs() { local j=$1 local rootfs=$2 # create mount point and mount diskn mkdir -p ${DESTDIR}/part/disk$j mount /dev/mapper/diskimage$j $DESTDIR/part/disk$j # Extract rootfs cpio archive into `.../part/vos_$j` cd ${DESTDIR}/part/disk$j if [[ $rootfs = *.cpio ]] then cat "${UBOOT_OUT_ABS}/$rootfs" | cpio -id elif [[ $rootfs = *.cpio.gz ]] then cat "${UBOOT_OUT_ABS}/$rootfs" | gunzip | cpio -id elif [[ $rootfs = *.tar.gz ]] then tar -xf "${UBOOT_OUT_ABS}/$rootfs" else echo "Ignoring $rootfs: unsupported file format. Use cpio or cpio.gz or tar.gz." fi cd - # umount sync umount $DESTDIR/part/disk$j } function print_help { echo "usage:" echo " $0 -c CONFIG_FILE -d UBOOT_DIRECTORY -t UBOOT_TYPE <-w WORK_DIRECTORY> <-s SLACK> <-a> -o IMG_FILE" echo " $0 -h" echo "where:" echo " -c CONFIG_FILE - configuration file" echo " -d UBOOT_DIRECTORY - root directory for the paths specified in CONFIG_FILE" echo " -t UBOOT_TYPE can be:" echo " sd - alias for \"mmc load 0:1\" for uboot load commands" echo " scsi - alias for \"scsi load 0:1\" for uboot load commands" echo " tftp - alias for \"tftpb\" for uboot load cammnds" echo " < > - used for uboot load commands" echo " -w WORK_DIRECTORY - work directory used when building the image" echo " -s SLACK - free MB to add to each partition, default 128" echo " -a specifies that the size of IMG_FILE has to be aligned to the nearest power of two" echo " -o IMG_FILE - the output img file " echo "Example:" echo " $0 -c ../config -d ./build42 -w tmp -o disk.img" } # before anything else, check if we have root privilege if ! [ $(id -u) = 0 ] then echo "This script needs root privilege to run, exiting." exit 1 fi while getopts ":w:d:c:t:s:o:ah" opt do case ${opt} in t ) case $OPTARG in scsi ) LOAD_CMD="load scsi 0:1" ;; sd ) LOAD_CMD="load mmc 0:1" ;; tftp ) LOAD_CMD="tftpb" ;; * ) LOAD_CMD="$OPTARG" ;; esac ;; s ) SLACK=$OPTARG if ! test $SLACK -gt 0 then echo "Invalid SLACK parameter." exit 1 fi ;; w ) DESTDIR=$OPTARG ;; d ) UBOOT_OUT=$OPTARG ;; c ) CFG_FILE=$OPTARG ;; o ) IMG=$OPTARG ;; a ) ALIGN=1 ;; h ) print_help exit 0 ;; * ) echo "Unknown option, see \"$0 -h\"" exit 1 ;; esac done shift $((OPTIND -1)) if [ -z "$UBOOT_OUT" ] || [ -z "$CFG_FILE" ] || [ -z "$IMG" ] then echo "Undefined arguments, see \"$0 -h\"" exit 1 fi # if the user hasn't specified a working directing, create it if [ -z "$DESTDIR" ] then DESTDIR="$(mktemp -d /tmp/imagebuilder.XXXXXX)" DESTDIR_DEL=true else DESTDIR_DEL=false fi UBOOT_OUT_ABS="$(readlink -f $UBOOT_OUT)" DESTDIR_ABS="$(readlink -f $DESTDIR)" check_depends source "$CFG_FILE" SCRIPT_PATH=$(dirname "$0") source "$SCRIPT_PATH/common" i=0 while test $i -lt $NUM_DOMUS do if test -z "${DOMU_MEM[$i]}" then DOMU_MEM[$i]=512 fi if test -z "${DOMU_VCPUS[$i]}" then DOMU_VCPUS[$i]=1 fi i=$(( $i + 1 )) done offset=$((2*1024*1024)) _part1_size=`stat -L --printf="%s" $UBOOT_OUT/$DOM0_KERNEL` _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/$DEVICE_TREE` )) _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/$UBOOT_SCRIPT` )) if test "${DOM0_RAMDISK}" then _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/${DOM0_RAMDISK}` )) fi if test "$NUM_DT_OVERLAY" && test "$NUM_DT_OVERLAY" -gt 0 then i=0 while test $i -lt "$NUM_DT_OVERLAY" do _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/${DT_OVERLAY[$i]}` )) i=$(( $i + 1 )) done fi if test "${UBOOT_SOURCE}" then _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/$UBOOT_SOURCE` )) fi if test "${XEN}" then _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/$XEN` )) fi if test "$NUM_BOOT_AUX_FILE" && test "$NUM_BOOT_AUX_FILE" -gt 0 then i=0 while test $i -lt "$NUM_BOOT_AUX_FILE" do if [ ! -f "$UBOOT_OUT/${BOOT_AUX_FILE[$i]}" ] then echo "Can not find ${BOOT_AUX_FILE[$i]}, exiting" fi _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/${BOOT_AUX_FILE[$i]}` )) i=$(( $i + 1 )) done fi if test "${BITSTREAM}" then _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/$BITSTREAM` )) fi i=0 while test $i -lt $NUM_DOMUS do _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/${DOMU_KERNEL[$i]}` )) if test "${DOMU_RAMDISK[$i]}" then _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/${DOMU_RAMDISK[$i]}` )) fi if test "${DOMU_PASSTHROUGH_DTB[$i]}" then _part1_size=$(( $_part1_size + `stat -L --printf="%s" $UBOOT_OUT/${DOMU_PASSTHROUGH_DTB[$i]}` )) fi i=$(( $i + 1 )) done # add 16 MB slack _part1_size=$(( $_part1_size + 16777216 )) _part1_size=$(( $_part1_size + $offset - 1)) _part1_size=$(( $_part1_size & ~($offset - 1) )) _part1_size=$(( $_part1_size / 1024 / 1024 )) echo PART1 size: "$_part1_size"MB _sector_size=512 # _sector_start[0] needs to be aligned to 2048 _sector_start[0]=2048 _sector_end[0]=$(( $_part1_size * 1024 * 1024 / $_sector_size + ${_sector_start[0]} - 1)) _tot_size=$(( $_part1_size )) _npart=1 if test "$DOM0_ROOTFS" then add_partition "$DOM0_ROOTFS" fi i=0 while test $i -lt $NUM_DOMUS do if test "${DOMU_ROOTFS[$i]}" then add_partition "${DOMU_ROOTFS[$i]}" fi i=$(( $i + 1 )) done _tot_size=$(( $_tot_size + 16 )) # NOTE: Increase the size of the image to the nearest power of two if test "$ALIGN" then _tot_size=$(align_size_power_of_two $_tot_size) fi # NOTE: Increase vos_a to 256 to accomodate rootfs # 528 MiB (256 + 256 + 16) truncate $IMG -s "$_tot_size"M if test "$GPT" then # create GPT partition table sgdisk -og $IMG i=0 j=1 while test $i -lt $_npart do sgdisk -n $j:${_sector_start[$i]}:${_sector_end[$i]} -c $j:"Linux""$j" -t $j:8300 $IMG i=$(( $i + 1 )) j=$(( $j + 1 )) done else tmp_dos=`mktemp` tmp_files+=($tmp_dos) i=0 parted -s $IMG mklabel "msdos" parted $IMG mkpart primary fat32 "${_sector_start[$i]}"s "${_sector_end[$i]}"s parted $IMG set 1 boot on i=1 j=2 while test $i -lt $_npart do parted $IMG mkpart logical ext4 "${_sector_start[$i]}"s "${_sector_end[$i]}"s i=$(( $i + 1 )) j=$(( $j + 1 )) done fi # find the first available loop device _loop_dev=$(losetup -f) # attach loopback device to $IMG losetup $_loop_dev $IMG _disksize=$(blockdev --getsize $_loop_dev) dmsetup create diskimage --table "0 $_disksize linear $_loop_dev 0" # ensure that /dev/mapper/diskimage exists while [ ! -b /dev/mapper/diskimage ] do sleep 2 done kpartx -a /dev/mapper/diskimage # ensure that /dev/mapperdiskimage1 exists while [ ! -b /dev/mapper/diskimage1 ] do sleep 2 done mkfs.vfat -F 32 -n boot /dev/mapper/diskimage1 i=1 j=2 while test $i -lt $_npart do mkfs.ext4 -L vos_$j -F /dev/mapper/diskimage$j i=$(( $i + 1 )) j=$(( $j + 1 )) done # create mount point and mount disk1 mkdir -p ${DESTDIR}/part/disk1 mount /dev/mapper/diskimage1 $DESTDIR/part/disk1 # only copy over files that were counted for the partition size cd "$UBOOT_OUT" cp --parents "$DOM0_KERNEL" "${DESTDIR_ABS}/part/disk1/" cp --parents "$DEVICE_TREE" "${DESTDIR_ABS}/part/disk1/" cp --parents "$UBOOT_SCRIPT" "${DESTDIR_ABS}/part/disk1/" if test "${DOM0_RAMDISK}" then cp --parents "$DOM0_RAMDISK" "${DESTDIR_ABS}/part/disk1/" fi if test "$NUM_DT_OVERLAY" && test "$NUM_DT_OVERLAY" -gt 0 then i=0 while test $i -lt "$NUM_DT_OVERLAY" do cp --parents "${DT_OVERLAY[$i]}" "${DESTDIR_ABS}/part/disk1/" i=$(( $i + 1 )) done fi if test "${UBOOT_SOURCE}" then cp --parents "$UBOOT_SOURCE" "${DESTDIR_ABS}/part/disk1/" fi if test "${XEN}" then cp --parents "$XEN" "${DESTDIR_ABS}/part/disk1/" fi if test "$NUM_BOOT_AUX_FILE" && test "$NUM_BOOT_AUX_FILE" -gt 0 then i=0 while test $i -lt "$NUM_BOOT_AUX_FILE" do cp --parents "${BOOT_AUX_FILE[$i]}" "${DESTDIR_ABS}/part/disk1/" i=$(( $i + 1 )) done fi if test "${BITSTREAM}" then cp --parents "$BITSTREAM" "${DESTDIR_ABS}/part/disk1/" fi i=0 while test $i -lt $NUM_DOMUS do cp --parents "${DOMU_KERNEL[$i]}" "${DESTDIR_ABS}/part/disk1/" if test "${DOMU_RAMDISK[$i]}" then cp --parents "${DOMU_RAMDISK[$i]}" "${DESTDIR_ABS}/part/disk1/" fi if test "${DOMU_PASSTHROUGH_DTB[$i]}" then cp --parents "${DOMU_PASSTHROUGH_DTB[$i]}" "${DESTDIR_ABS}/part/disk1/" fi i=$(( $i + 1 )) done cd - # unmount sync # This fails for some reason. It could work now because we are not using qemu-user # fstrim $DESTDIR/part/disk1 umount $DESTDIR/part/disk1 j=2 if test "$DOM0_ROOTFS" then write_rootfs 2 "$DOM0_ROOTFS" generate_domU_configs 2 j=$(( $j + 1 )) fi i=0 while test $i -lt $NUM_DOMUS do if test "${DOMU_ROOTFS[$i]}" then write_rootfs $j "${DOMU_ROOTFS[$i]}" j=$(( $j + 1 )) fi i=$(( $i + 1 )) done cleanup_disk_image