#!/bin/bash ## Manage linux driver patches for netmap. ## usage (from the dir containing the Makefile): ## ## scripts/np [args...] ## ## where is any of the functions below. ## [ -f scripts/conf ] && source scripts/conf ## The following enviroment variables must be set: ## ## GITDIR: the absolute path of the netmap linux ## git repository, containing all the required netmap-* ## branches. [ -n "$GITDIR" -a -d "$GITDIR/.git" ] || { echo "GITDIR not set or not valid" >&2 exit 1 } NETMAP_BRANCH=${NETMAP_BRANCH:-master} function error { echo "$@" >&2 exit 1 } function get-params { local params=$1; shift err_msg="$PROGNAME $COMMAND $(echo $params| perl -pe 's/\S+/<$&>/g')" local param for param in $params; do [[ -z "$@" ]] && error "$err_msg" pname=$(echo -n $param | perl -pe 's/\W/_/g') eval $pname="$1" shift done [[ -n "$@" ]] && error "$err_msg" } ## ## LINUX_SOURCES: the absolute path of a ## directory used to store all required linux-* source trees ## (The script will extract linux-x.y.z from GITDIR if it needs ## it and $LINUX_SOURCES does not already contain it). ## ## LINUX_CONFIGS: the absolute path of a ## directory containing the configuration files for ## the linux kernel. The file for version x must be named ## config-x. config-all can be used as a default. ## ## The configuration variables can be put in scripts/conf. ## ## ## Available actions: ## ## ## driver-path ## retrieves the path of in the linux sources ## for version . The path is output to stdout. ## It uses a local cache to minimize the expensive ## file system search. function driver-path() { get-params "driver version" "$@" cat cache/$version/$driver/path 2>/dev/null && return local kern=$(get-kernel $version) mkdir -p cache/$version/$driver ( cd $kern find drivers/net -name $driver ) | tee cache/$version/$driver/path } ## ## get-patch [-c] ## extract the netmap patch for the given and the ## given kernel . The patch is stored in tmp-patches ## and the name of the patch is output to stdout. ## If a patch with the same name already exists in tmp-patches ## it is overwritten, unless the -c option is used, ## in which case the existing patch is kept (the patch name is still output). function get-patch() { local use_cache [ "$1" = -c ] && { use_cache=1; shift; } get-params "driver version" "$@" # convert kernel version to fixed notation local v1=$(scripts/vers $version -c) # compute next kernel version (in fixed notation) local v2=$(scripts/vers $version -i -c) local patchname=diff--$driver--$v1--$v2 local out=tmp-patches/$patchname [ -n "$use_cache" -a -s $out ] && { echo $out; return; } local drvpath=$(driver-path $driver $version) [ -n "$drvpath" ] || return local drvdir=$(dirname $drvpath) ( cd $GITDIR git diff --relative=$drvdir v$version..netmap-$version -- $drvpath ) > $out # an empty patch means no netmap support for this driver [ -s $out ] || { rm $out; return 1; } echo $out return 0; } ## ## get-range ## extracts the netmap patches for the given for ## all the kernel versions from (included) to ## (excluded). All patches are stored in tmp-patches ## and their names are output to stdout. function get-range() { get-params "driver version1 version2" "$@" local v=$version1 # while version is less than $version2 while scripts/vers -b $v $version2 -L; do get-patch $driver $v # compute next version v=$(scripts/vers $v -i) done } ## ## get-src ## copies the original sources of the given , ## from the given kernel to the given ## directory. ## It uses a local cache to minimize the expensive ## checkouts in GITDIR. function get-src() { get-params "driver version dest" "$@" local kern=$(get-kernel $version) local src=$(driver-path $driver $version) cp -r $kern/$src $dest } ## ## extend ## checks wether the range of applicability of the ## given can be extented to include . ## It returns 0 on success and 1 on failure. function extend() { get-params "patch version" "$@" local _patch=$(realpath $patch) # extract the driver name from the patch name local driver=$(scripts/vers $_patch -s -p -p) local tmpdir1=$(mktemp -d) local tmpdir2=$(mktemp -d) trap "rm -rf $tmpdir1 $tmpdir2" 0 # we get the driver sources for the given and # we apply two patches separately: # i) the given ; # ii) the proper patch from GITDIR. # We declare to be extendable if # - it is still applicable AND # - we obtain the same files from i) and ii) (ignoring whitespace) get-src $driver $version $tmpdir1 get-src $driver $version $tmpdir2 ( cd $tmpdir1 patch --no-backup-if-mismatch -p1 < $_patch >/dev/null 2>&1 ) || return 1 local patch2=$(get-patch -c $driver $version) patch2=$(realpath $patch2) ( cd $tmpdir2 patch -p1 < $patch2 >/dev/null 2>&1 ) # this will certainly apply diff -qbBr $tmpdir1 $tmpdir2 >/dev/null || return 1 return 0 } ## ## minimize ## tries to minimize the number of patch files for the given ## . It uses the patches currently found in tmp-patches ## and stores the resulting patches in final-patches. ## If final-patches already contained patches for , ## they are deleted first. function minimize() { get-params "driver" "$@" mkdir -p final-patches local drv=$(basename $driver) local patches=$(ls tmp-patches/diff--$drv--* 2>/dev/null) [ -n "$patches" ] || return 1 # put the patch names in $1, $2, ... set $patches rm -f final-patches/diff--$drv--* # the original patches (in tmp-patches) are ordered by version number. # We consider one patch in turn (the 'pivot') and try # to extend its range to cover the range of the next # patch. If this succedes, the merged patch is the new # pivot, otherwise the current pivot is output and the # next patch becomes the new pivot. The process # is repeated until there are no more patches to consider. local pivot=$1 [ -n "$pivot" -a -e "$pivot" ] || return 1 # extract the left end and right end of the pivot's range local ple=$(scripts/vers $pivot -s -p -C) local pre=$(scripts/vers $pivot -s -C) while [ -n "$pivot" ]; do shift if [ -n "$1" ]; then # extract the left end and right end of the next patch local nle=$(scripts/vers $1 -s -p -C) local nre=$(scripts/vers $1 -s -C) # we admit no gaps in the range if [ $pre = $nle ] && extend $pivot $nle; then pre=$nre continue fi fi # either out of patches or failed merge. # Compute the file name of the current pivot and store # the patch in its final location out=$(scripts/vers diff $drv $ple -c $pre -c -S4) cp $pivot final-patches/$out # the new pivot becames the next patch (if any) pivot=$1 pre=$nre ple=$nle done return 0 } ## ## infty ## if final-patches contains a patch for with a range ## ending in , extend it to infinity. ## Do nothing otherwise. function infty() { get-params "driver version" "$@" local drv=$(basename $driver) # convert kernel version to fixed notation local v=$(scripts/vers $version -c) local last=$(ls final-patches/diff--$drv--*--$v 2>/dev/null|tail -n1) [ -n "$last" ] || return 1 mv -n $last $(scripts/vers $last -s -p 99999 -S4) 2>/dev/null } function get-kernel() { get-params "version" "$@" local dst="$(realpath $LINUX_SOURCES)/linux-$version" [ -d $dst ] && { echo $dst; return; } mkdir -p $dst ( cd $GITDIR git archive v$v | tar xf - -C $dst ) echo $dst } ## ## build-prep ## prepare the linux tree for to be ready ## for external modules compilation. ## The tree is put in $LINUX_SOURCES/linux- and the ## configuration is obtained from $LINUX_CONFIGS/config- ## (or $LINUX_CONFIGS/config-all by default). ## Errors are logged to $LINUX_CONFIGS/linux-.log. ## If $LINUX_SOURCES/linux- already exists, ## nothing is done. ## In all cases, the absolute path of linux- is ## output. function build-prep() { get-params "version" "$@" local dst=$(get-kernel $version) exec 3>&1 4>&2 >$dst.log 2>&1 cp $LINUX_CONFIGS/config-$v $dst/.config 2>/dev/null || cp $LINUX_CONFIGS/config-all $dst/.config ( cd $dst yes '' | make oldconfig make modules_prepare ) exec 1>&3 2>&4 echo $dst } ## ## check-patch ## check that the given applies and compiles without ## error for all its declared range of applicability. ## Errors are logged to log/. function check-patch() { get-params "patch" "$@" # extract the left version local v1=$(scripts/vers $patch -s -p -C) # extract the right version local v2=$(scripts/vers $patch -s -C) # extract the driver name local driver=$(scripts/vers $patch -s -p -p) local p=$(realpath $patch) mkdir -p log local log="$(realpath log)/$(basename $patch)" local nmcommit=$(cd ..; git show-ref -s heads/$NETMAP_BRANCH) echo -n $patch... while scripts/vers -b $v1 $v2 -L; do # cache lookup local cache=cache/$v1/$driver local cpatch=$cache/patch local cnmcommit=$cache/nmcommit local cstatus=$cache/status local clog=$cache/log if [ -f $cpatch ] && cmp -s $cpatch $patch && [ "$nmcommit" = "$(cat $cnmcommit)" ]; then cp $clog $log ok=$(cat $cstatus) else # update cache cp $patch $cpatch echo $nmcommit > $cnmcommit local ksrc=$(build-prep $v1) local tmpdir=$(mktemp -d) trap "rm -rf $tmpdir" 0 (cd ..; git archive $NETMAP_BRANCH | tar xf - -C $tmpdir ) pushd $tmpdir/LINUX >/dev/null mkdir single-patch rm patches ln -s single-patch patches cp $p single-patch ok=false make KSRC=$ksrc >$log 2>&1 && ok=true popd >/dev/null cp $log $clog fi [ $ok = true ] || { echo FAILED; echo false > $cstatus; return 1; } echo true > $cstatus rm -rf $tmpdir # compute next version v1=$(scripts/vers $v1 -i) done echo OK } ## ## build-check ## do a check-patch for all the patches of that are ## currently in tmp-patches. Patches that fail the check ## are moved to failed-patches. function build-check() { get-params "driver" "$@" mkdir -p failed-patches local drv=$(basename $driver) local patches=$(ls tmp-patches/diff--$drv--* 2>/dev/null) local p for p in $patches; do check-patch $p || mv $p failed-patches done } ## ## forall [args...] ## exec [args...] for all known drivers. function forall() { local cmd=$1 shift # we obtain the value of DRIVER_SRC from the makefile # (the +% target is defined in our Makefile and prints # the contents of variable %) local driver_srcs=$(make +DRIVER_SRCS) local driver for driver in $driver_srcs; do $cmd $(basename $driver) "$@" done } mkdir -p tmp-patches PROGNAME=$0 [ -n "$1" ] || { scripts/help $PROGNAME; exit 1 } COMMAND=$1; shift case $COMMAND in *-all) forall ${COMMAND%-all} "$@" ;; -[hH]|--help|-help|help) scripts/help $PROGNAME ;; *) $COMMAND "$@" ;; esac