#!/bin/sh

set -e
. /usr/share/debconf/confmodule
#set -x

if [ -z "$1" ]; then
	PROGRESSBAR=hw-detect/detect_progress_step
else
	PROGRESSBAR=$1
fi

NEWLINE="
"
MISSING_MODULES_LIST=""
SUBARCH="$(archdetect)"

finish_install=/usr/lib/finish-install.d/30hw-detect

LOAD_IDE=""
if db_get hw-detect/load-ide && [ "$RET" = true ]; then
	LOAD_IDE=1
fi

# Check for virtio devices
if [ -d /sys/bus/pci/devices ] && \
	grep -q 0x1af4 /sys/bus/pci/devices/*/vendor 2>/dev/null && \
	! grep -q ^virtio_ /proc/modules; then
	anna-install virtio-modules || true
fi

if [ -x /sbin/depmod ]; then
	depmod -a > /dev/null 2>&1 || true
fi

log () {
	logger -t hw-detect "$@"
}

is_not_loaded() {
	! ((cut -d" " -f1 /proc/modules | grep -q "^$1\$") || \
	   (cut -d" " -f1 /proc/modules | sed -e 's/_/-/g' | grep -q "^$1\$"))
}

is_available () {
	[ "$(modprobe -l $1)" ] || return 1
}

# Module as first parameter, description of device the second.
missing_module () {
	if ! in_list "$1" "$MISSING_MODULES_LIST"; then
		if [ -n "$MISSING_MODULES_LIST" ]; then
			MISSING_MODULES_LIST="$MISSING_MODULES_LIST, "
		fi
		MISSING_MODULES_LIST="$MISSING_MODULES_LIST$1 ($2)"
	fi
}

# The list can be delimited with spaces or spaces and commas.
in_list() {
	echo "$2" | grep -q "\(^\| \)$1\(,\| \|$\)"
}

snapshot_devs() {
	echo -n `grep : /proc/net/dev | cut -d':' -f1`
}

compare_devs() {
	local olddevs="$1"
	local devs="$2"
	local dev newdevs

	newdevs=
	for dev in $devs; do
		if ! echo " $olddevs " | grep -q " $dev "; then
			newdevs="${newdevs:+$newdevs }$dev"
		fi
	done
	echo "$newdevs"
}

load_module() {
	local module="$1"
	local cardname="$2"
	local devs=""
	local olddevs=""
	local newdev=""

	old=`cat /proc/sys/kernel/printk`
	echo 0 > /proc/sys/kernel/printk

	devs="$(snapshot_devs)"
	if log-output -t hw-detect modprobe -v "$module"; then
		olddevs="$devs"
		devs="$(snapshot_devs)"
		newdevs="$(compare_devs "$olddevs" "$devs")"

		# Make sure space is used as a delimiter.
		IFS_SAVE="$IFS"
		IFS=" "
		if [ -n "$newdevs" -a -n "$cardname" ]; then
			mkdir -p /etc/network
			for dev in $newdevs; do
				echo "${dev}:${cardname}" >> /etc/network/devnames
			done
		fi
		IFS="$IFS_SAVE"
	else   
		log "Error loading '$module'"
		if [ "$module" != floppy ] && [ "$module" != ide-floppy ] && \
		   [ "$module" != ide-cd ] && [ "$module" != ide-generic ]; then
			db_subst hw-detect/modprobe_error CMD_LINE_PARAM "modprobe -v $module"
			db_input medium hw-detect/modprobe_error || [ $? -eq 30 ]
			db_go
		fi
	fi

	echo $old > /proc/sys/kernel/printk
}

# Some pci chipsets are needed or there can be DMA or other problems.
get_ide_chipset_info() {
	for ide_module in $(find /lib/modules/*/kernel/drivers/ide/pci/ -type f 2>/dev/null); do
		if [ -e $ide_module ]; then
			baseidemod=$(echo $ide_module | sed 's/\.ko$//; s/.*\///')
			echo "$baseidemod:IDE chipset support"
		fi
	done
}

# Return list of lines formatted "module:Description"
get_detected_hw_info() {
	if [ "${SUBARCH%%/*}" = powerpc ]; then
		discover-mac-io
		if [ "$SUBARCH" = powerpc/chrp_rs6k ] || \
		   [ "$SUBARCH" = powerpc/chrp_ibm ]; then
			discover-ibm
		fi
	fi
	if [ "${SUBARCH%%/*}" = sparc ]; then
		discover-sbus
	fi
	if [ -d /sys/bus/usb ]; then
		echo "usb-storage:USB storage"
	fi
}

# NewWorld PowerMacs don't want floppy or ide-floppy, and on some models
# (e.g. G5s) the kernel hangs when loading the module.
get_floppy_info() {
	case $SUBARCH in
		powerpc/powermac_newworld) ;;
		*) echo "floppy:Linux Floppy" ;;
	esac
}

get_ide_floppy_info() {
	case $SUBARCH in
		powerpc/powermac_newworld) ;;
		*) echo "ide-floppy:Linux IDE floppy" ;;
	esac
}

# Manually load modules to enable things we can't detect.
# XXX: This isn't the best way to do this; we should autodetect.
# The order of these modules are important.
get_manual_hw_info() {
	if [ "$LOAD_IDE" ]; then
		get_floppy_info
		get_ide_chipset_info
		echo "ide-generic:Linux IDE support"
		get_ide_floppy_info
		echo "ide-disk:Linux ATA DISK"
		echo "ide-cd:Linux ATAPI CD-ROM"
	fi
}

# Should be greater than the number of kernel modules we can reasonably
# expect it will ever need to load.
MAX_STEPS=1000
OTHER_STEPS=4
# Use 1/10th of the progress bar for the non-module-load steps.
OTHER_STEPSIZE=$(expr $MAX_STEPS / 10 / $OTHER_STEPS)
db_progress START 0 $MAX_STEPS $PROGRESSBAR

db_progress INFO hw-detect/detect_progress_step

# TODO: Can possibly be removed if udev will load yenta_socket automatically
# Load yenta_socket, if hardware is available, for Cardbus cards.
if [ -d /sys/bus/pci/devices ] && \
	grep -q 0x060700 /sys/bus/pci/devices/*/class 2>/dev/null && \
	! grep -q ^yenta_socket /proc/modules; then
	db_subst hw-detect/load_progress_step CARDNAME "Cardbus bridge"
	db_subst hw-detect/load_progress_step MODULE "yenta_socket"
	db_progress INFO hw-detect/load_progress_step
	
	log "Detected Cardbus bridge, loading yenta_socket"
	load_module yenta_socket
	# Ugly hack, but what's the alternative?
	sleep 3 || true
fi

# Load the ethernet gadget network driver (g_ether) on S3C2410/S3C2440 (Openmoko GTA01/02)
if [ -d /sys/bus/platform/devices/s3c2440-usbgadget -o \
	-d /sys/bus/platform/devices/s3c2410-usbgadget ] ; then
	db_subst hw-detect/load_progress_step CARDNAME "S3C2410/S3C2440 SoC"
	db_subst hw-detect/load_progress_step MODULE "g_ether"
	db_progress INFO hw-detect/load_progress_step
	
	log "Detected S3C2410/S3C2440 SoC, loading g_ether"
	load_module g_ether
	register-module g_ether
fi

# Load xenbus_probe_frontend if we're running under the Xen hypervisor, so
# that it can deal with autoloading such things as xen-blkfront and
# xen-netfront.
if [ "$(cat /sys/hypervisor/type 2>/dev/null || true)" = xen ] && \
   [ ! -d /sys/bus/xen ]; then
	db_subst hw-detect/load_progress_step CARDNAME "Xen frontend"
	db_subst hw-detect/load_progress_step MODULE "xenbus_probe_frontend"
	db_progress INFO hw-detect/load_progress_step

	log "Detected Xen hypervisor, loading xenbus_probe_frontend"
	load_module xenbus_probe_frontend
	register-module -i xenbus_probe_frontend
fi

# If using real hotplug, re-run the rc scripts to pick up new modules.
# TODO: this just loads modules itself, rather than handing back a list
# Since we've just run depmod, new modules might be available, so we
# must trigger as well as settle.
update-dev >/dev/null

ALL_HW_INFO=$(get_detected_hw_info; get_manual_hw_info)
db_progress STEP $OTHER_STEPSIZE

# Remove modules that are already loaded or not available, and construct
# the list for the question.
LIST=""
PROCESSED=""
AVAIL_MODULES="$(find /lib/modules/$(uname -r)/ | sed 's!.*/!!' | cut -d . -f 1)"
LOADED_MODULES="$(cut -d " " -f 1 /proc/modules) $(cut -d " " -f 1 /proc/modules | sed -e 's/_/-/g')"
IFS_SAVE="$IFS"
IFS="$NEWLINE"
for device in $ALL_HW_INFO; do
	module="${device%%:*}"
	cardname="${device##*:}"
	if [ "$module" != "ignore" -a "$module" != "" ] &&
	   ! in_list "$module" "$LOADED_MODULES" &&
	   ! in_list "$module" "$PROCESSED"
	then
		if [ -z "$cardname" ]; then
			cardname="[Unknown]"
		fi
		
		if in_list "$module" "$AVAIL_MODULES"; then
			LIST="${LIST:+$LIST, }$module ($(echo "$cardname" | sed 's/,/ /g'))"
			PROCESSED="$PROCESSED $module"
		else
			missing_module "$module" "$cardname"
		fi
	fi
done
IFS="$IFS_SAVE"
db_progress STEP $OTHER_STEPSIZE

if [ "$LIST" ]; then
	# Ask which modules to install.
	db_subst hw-detect/select_modules list "$LIST"
	db_set hw-detect/select_modules "$LIST"
	db_input medium hw-detect/select_modules || true
	db_go || exit 10 # back up
	db_get hw-detect/select_modules
	LIST="$RET"
fi

list_to_lines() {
	echo "$LIST" | sed 's/, /\n/g'
}

# Work out amount to step per module load. expr rounds down, so 
# it may not get quite to 100%, but will at least never exceed it.
MODULE_STEPS=$(expr \( $MAX_STEPS - \( $OTHER_STEPS \* $OTHER_STEPSIZE \) \))
if [ "$LIST" ]; then
	MODULE_STEPSIZE=$(expr $MODULE_STEPS / $(list_to_lines | wc -l))
fi

IFS="$NEWLINE"

for device in $(list_to_lines); do
	module="${device%% *}"
	cardname="`echo $device | cut -d'(' -f2 | sed 's/)$//'`"
	# Restore IFS after extracting the fields.
	IFS="$IFS_SAVE"

	if [ -z "$module" ] ; then module="[Unknown]" ; fi
	if [ -z "$cardname" ] ; then cardname="[Unknown]" ; fi

	log "Detected module '$module' for '$cardname'"

	if is_not_loaded "$module"; then
		db_subst hw-detect/load_progress_step CARDNAME "$cardname"
		db_subst hw-detect/load_progress_step MODULE "$module"
		db_progress INFO hw-detect/load_progress_step
		if [ "$cardname" = "[Unknown]" ]; then
			load_module "$module"
		else
			load_module "$module" "$cardname"
		fi
	fi

	db_progress STEP $MODULE_STEPSIZE
	IFS="$NEWLINE"
done
IFS="$IFS_SAVE"

if [ -z "$LIST" ]; then
	db_progress STEP $MODULE_STEPS
fi

# Load ide-generic and check if that results in new block devices.
# If so, make sure it is added to the initrd for the installed system.
# Note: this may need to be done for more systems than just systems
# that have an ISA bus, but that seems like a good start; it could also
# be done unconditionally.
if [ -z "$LOAD_IDE" ] && is_not_loaded ide-generic && \
   [ -e /sys/bus/isa ] && is_available ide-generic; then
	update-dev --settle >/dev/null
	blockdev_count=$(ls /sys/block | wc -w)

	log "ISA bus detected; loading module 'ide-generic'"
	load_module ide-generic
	update-dev --settle >/dev/null
	if [ $(ls /sys/block | wc -w) -gt $blockdev_count ]; then
		log "New devices detected after loading ide-generic"

		# This will tell initramfs-tools to load ide-generic
		kopts=
		if db_get debian-installer/add-kernel-opts && [ "$RET" ]; then
			kopts="$RET"
		fi
		if ! echo "$kopt" | grep -Eq "(^| )all_generic_ide(=1|)( |$)"; then
			db_set debian-installer/add-kernel-opts \
				"${kopts:+$kopts }all_generic_ide=1"
		fi
	fi
fi

if ! is_not_loaded ohci1394 || ! is_not_loaded firewire-ohci; then
	# if firewire was found, try to enable firewire cd support
	if is_not_loaded sbp2 && is_not_loaded firewire-sbp2 && \
	    is_available scsi_mod; then
	    	sbp2module=
		if is_available firewire-sbp2; then
			sbp2module=firewire-sbp2
		elif is_available sbp2; then
			sbp2module=sbp2
		fi
		if [ -n "$sbp2module" ]; then
			db_subst hw-detect/load_progress_step CARDNAME "FireWire CDROM support"
			db_subst hw-detect/load_progress_step MODULE "$sbp2module"
			db_progress INFO hw-detect/load_progress_step
			load_module "$sbp2module"
			register-module "$sbp2module"
		else
			missing_module firewire-sbp2 "FireWire CDROM"
		fi
	fi
	db_progress STEP $OTHER_STEPSIZE

	# also try to enable firewire ethernet (The right way to do this is
	# really to catch the hotplug events from the kernel.)
	if is_not_loaded eth1394; then
		if is_available eth1394; then
			db_subst hw-detect/load_progress_step CARDNAME "FireWire ethernet support"
			db_subst hw-detect/load_progress_step MODULE "eth1394"
			db_progress INFO hw-detect/load_progress_step
			load_module eth1394 "FireWire ethernet"
			# do DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo DULEdo 