diff --git a/configure b/configure
index 11ae7fc2c33d18c05a5020160caf4bf013982c06..9c2bf96ec8262f355349d414e32aeef4aca2a7da 100755
--- a/configure
+++ b/configure
@@ -1577,7 +1577,8 @@ outfiles="$outfiles Makeconf GNUmakefile \
 	cdrom/groklilo/GNUmakefile \
 	dhcpd/dhcpd.conf.template dhcpd/GNUmakefile \
 	install/GNUmakefile install/ops-install install/boss-install \
-	install/newnode_sshkeys/GNUmakefile "
+	install/newnode_sshkeys/GNUmakefile \
+	mote/GNUmakefile mote/tbuisp "
 
 #
 # Do this for easy distclean.
diff --git a/configure.in b/configure.in
index aa7ff31a52de529df6c720342007584eb02584d5..83fc204ce64c07ee9ca6c0c95fc72d70800304d9 100755
--- a/configure.in
+++ b/configure.in
@@ -613,7 +613,8 @@ outfiles="$outfiles Makeconf GNUmakefile \
 	cdrom/groklilo/GNUmakefile \
 	dhcpd/dhcpd.conf.template dhcpd/GNUmakefile \
 	install/GNUmakefile install/ops-install install/boss-install \
-	install/newnode_sshkeys/GNUmakefile "
+	install/newnode_sshkeys/GNUmakefile \
+	mote/GNUmakefile mote/tbuisp "
 
 #
 # Do this for easy distclean.
diff --git a/mote/GNUmakefile.in b/mote/GNUmakefile.in
new file mode 100644
index 0000000000000000000000000000000000000000..e0cac2fba5222e5b2f631c52151f89568fd6347a
--- /dev/null
+++ b/mote/GNUmakefile.in
@@ -0,0 +1,29 @@
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2004 University of Utah and the Flux Group.
+# All rights reserved.
+#
+
+SRCDIR		= @srcdir@
+TESTBED_SRCDIR	= @top_srcdir@
+OBJDIR		= ..
+SUBDIR		= mote
+
+include $(OBJDIR)/Makeconf
+
+BIN_SCRIPTS	= tbuisp
+
+#
+# Force dependencies on the scripts so that they will be rerun through
+# configure if the .in file is changed.
+# 
+all:	$(BIN_SCRIPTS)
+
+include $(TESTBED_SRCDIR)/GNUmakerules
+
+install: \
+	$(addprefix $(INSTALL_BINDIR)/, $(BIN_SCRIPTS))
+
+post-install:
+
+clean:
diff --git a/mote/tbuisp.in b/mote/tbuisp.in
new file mode 100755
index 0000000000000000000000000000000000000000..c37313c0b75429b4cfc6ee1615932e9d114469a5
--- /dev/null
+++ b/mote/tbuisp.in
@@ -0,0 +1,168 @@
+#!/usr/bin/perl -w
+#
+# EMULAB-COPYRIGHT
+# Copyright (c) 2004 University of Utah and the Flux Group.
+# All rights reserved.
+#
+
+#
+# tbuisp - An emulab frontend to UISP, which uploads programs to Mica motes
+#
+
+use lib '@prefix@/lib';
+my $TB = '@prefix@';
+
+use libdb;
+use English;
+use Getopt::Std;
+
+use strict;
+
+#
+# Constants
+#
+my $UISP = "$TB/bin/uisp";
+my $DEBUG = 1;
+
+#
+# Handle command-line arguments
+# TODO: Allow a user to specify some of their own arguments to uisp
+# TODO: Allow a user to reprogram all motes in an experiment
+#
+sub usage() {
+    warn "Usage: $0 <operation> [filename] <motes...>\n";
+    warn "Supported operations: upload\n";
+    warn "[filename] is required with the 'upload' operation\n";
+    return 1;
+}
+
+my $operation = shift @ARGV;
+my $filename;
+if (!$operation) {
+    exit usage();
+}
+# Check the operation type
+# XXX - support the other operations uisp supports, like downloading code
+SWITCH: for ($operation) {
+    /^upload$/ && do {
+	$filename = shift @ARGV;
+	if (!$filename) {
+	    exit usage();
+	}
+	last SWITCH;
+    };
+    
+    # Default
+    warn "Uknown operation $operation\n";
+    exit usage();
+}
+
+# They have to give us at least one mote
+my @motes = @ARGV;
+if (!@motes) {
+    exit usage();
+}
+
+#
+# Permission check
+#
+if (!TBNodeAccessCheck($UID,TB_NODEACCESS_LOADIMAGE,@motes)) {
+    die "You do not have permission to modify one or more nodes\n";
+}
+
+#
+# Check the file to make sure it's readable
+#
+if ($filename) {
+    if (!-r $filename) {
+	die "$filename not readable\n";
+    }
+}
+
+#
+# Program each mote
+#
+my $errors = 0;
+MOTE: foreach my $mote (@motes) {
+    #
+    # Figure out the parameters we need to pass to uisp for this mote
+    #
+    my @uisp_args;
+
+    my ($type, $class) = TBNodeType($mote);
+    if ($class ne "mote") {
+	warn "$mote is not a mote - skipping\n";
+	$errors++;
+	next MOTE;
+    }
+
+    #
+    # Figure out how we talk to the programming board, and what chipset it has
+    #
+    TSWITCH: for ($type) {
+	/^emote$/ && do {
+	    # Crossbow MIB600CA
+
+	    # The name of the host to communicate with
+	    push @uisp_args, "-dhost=$mote";
+	    # The type of programming board on a emote
+	    push @uisp_args, "-dprog=stk500";
+	    last TSWITCH;
+	};
+	# Default
+	warn "Mote $mote has unsupported type $type - skipping";
+	$errors++;
+	next MOTE;
+    }
+
+    #
+    # Find the name of the microcontroller on the board
+    #
+    my ($proc, $speed) = TBNodeTypeProcInfo($type);
+    PSWITCH: for ($proc) {
+	/^ATmega128/i && do {
+	    push @uisp_args, "-dpart=ATmega128";
+	    last PSWITCH;
+	};
+	# Default
+	warn "Unsupported processor $proc for $mote - skipping\n";
+	$errors++;
+	next MOTE;
+    }
+
+    #
+    # The operation we want to perform
+    #
+    my $opstring;
+    OSWITCH: for ($operation) {
+	/^upload$/ && do {
+	    $opstring = "--wr_fuse_e=ff --erase --upload if=$filename";
+	    last OSWITCH;
+	};
+
+	# No default, we've checked for a valid operation above
+    }
+
+    #
+    # Actually run uisp
+    # TODO - Squelch output
+    # TODO - Allow for some parallelism
+    #
+    print "Uploading code to $mote\n";
+    my $commandstr = "$UISP " . join(" ",@uisp_args,$opstring);
+    dprint("$commandstr\n");
+    if (system($commandstr)) {
+    }
+}
+
+if ($errors) {
+    exit 1;
+} else {
+    exit 0;
+}
+
+sub dprint(@) {
+    if ($DEBUG) {
+	print @_;
+    }
+}