Merge changes Ie6b43814,I6bbcafa9

* changes:
  Redo timer math for spurrious events
  Add scripts to modify a root filesystem for the vision pi
diff --git a/aos/events/event_loop.h b/aos/events/event_loop.h
index 6047cbc..d0ca196 100644
--- a/aos/events/event_loop.h
+++ b/aos/events/event_loop.h
@@ -344,8 +344,9 @@
  protected:
   TimerHandler(EventLoop *event_loop, std::function<void()> fn);
 
-  void Call(std::function<monotonic_clock::time_point()> get_time,
-            monotonic_clock::time_point event_time);
+  monotonic_clock::time_point Call(
+      std::function<monotonic_clock::time_point()> get_time,
+      monotonic_clock::time_point event_time);
 
  private:
   friend class EventLoop;
diff --git a/aos/events/event_loop_tmpl.h b/aos/events/event_loop_tmpl.h
index dfb5a6d..1daf88c 100644
--- a/aos/events/event_loop_tmpl.h
+++ b/aos/events/event_loop_tmpl.h
@@ -100,7 +100,7 @@
   return false;
 }
 
-inline void TimerHandler::Call(
+inline monotonic_clock::time_point TimerHandler::Call(
     std::function<monotonic_clock::time_point()> get_time,
     monotonic_clock::time_point event_time) {
   CHECK_NOTNULL(timing_.timer);
@@ -131,6 +131,7 @@
           monotonic_end_time - monotonic_start_time)
           .count();
   timing_.handler_time.Add(handler_latency);
+  return monotonic_start_time;
 }
 
 inline void PhasedLoopHandler::Call(
diff --git a/aos/events/shm_event_loop.cc b/aos/events/shm_event_loop.cc
index b3026fa..dad62cd 100644
--- a/aos/events/shm_event_loop.cc
+++ b/aos/events/shm_event_loop.cc
@@ -470,8 +470,14 @@
       : TimerHandler(shm_event_loop, std::move(fn)),
         shm_event_loop_(shm_event_loop),
         event_(this) {
-    shm_event_loop_->epoll_.OnReadable(
-        timerfd_.fd(), [this]() { shm_event_loop_->HandleEvent(); });
+    shm_event_loop_->epoll_.OnReadable(timerfd_.fd(), [this]() {
+      // The timer may fire spurriously.  HandleEvent on the event loop will
+      // call the callback if it is needed.  It may also have called it when
+      // processing some other event, and the kernel decided to deliver this
+      // wakeup anyways.
+      timerfd_.Read();
+      shm_event_loop_->HandleEvent();
+    });
   }
 
   ~TimerHandlerState() {
@@ -480,22 +486,29 @@
   }
 
   void HandleEvent() {
-    uint64_t elapsed_cycles = timerfd_.Read();
-    if (elapsed_cycles == 0u) {
-      // We got called before the timer interrupt could happen, but because we
-      // are checking the time, we got called on time.  Push the timer out by 1
-      // cycle.
-      elapsed_cycles = 1u;
-      timerfd_.SetTime(base_ + repeat_offset_, repeat_offset_);
+    CHECK(!event_.valid());
+    const auto monotonic_now = Call(monotonic_clock::now, base_);
+    if (event_.valid()) {
+      // If someone called Setup inside Call, rescheduling is already taken care
+      // of.  Bail.
+      return;
     }
 
-    Call(monotonic_clock::now, base_);
+    if (repeat_offset_ == chrono::seconds(0)) {
+      timerfd_.Disable();
+    } else {
+      // Compute how many cycles have elapsed and schedule the next iteration
+      // for the next iteration in the future.
+      const int elapsed_cycles =
+          std::max<int>(0, (monotonic_now - base_ + repeat_offset_ -
+                            std::chrono::nanoseconds(1)) /
+                               repeat_offset_);
+      base_ += repeat_offset_ * elapsed_cycles;
 
-    base_ += repeat_offset_ * elapsed_cycles;
-
-    if (repeat_offset_ != chrono::seconds(0)) {
+      // Update the heap and schedule the timerfd wakeup.
       event_.set_event_time(base_);
       shm_event_loop_->AddEvent(&event_);
+      timerfd_.SetTime(base_, chrono::seconds(0));
     }
   }
 
diff --git a/aos/starter/starter.sh b/aos/starter/starter.sh
index c5af65a..f5b14a5 100755
--- a/aos/starter/starter.sh
+++ b/aos/starter/starter.sh
@@ -1,10 +1,15 @@
 #!/bin/bash
 
-# NI already has a core pattern, so we probably shouldn't change it.
-#echo '/home/driver/tmp/robot_logs/%e-%s-%p-%t.coredump' > /proc/sys/kernel/core_pattern
 
-ln -s /var/local/natinst/log/FRC_UserProgram.log /tmp/FRC_UserProgram.log
-ROBOT_CODE="/home/admin/robot_code"
+if [[ "$(hostname)" == "roboRIO"* ]]; then
+  ROBOT_CODE="/home/admin/robot_code"
+
+  ln -s /var/local/natinst/log/FRC_UserProgram.log /tmp/FRC_UserProgram.log
+  ln -s /var/local/natinst/log/FRC_UserProgram.log "${ROBOT_CODE}/FRC_UserProgram.log"
+else
+  ROBOT_CODE="${HOME}/robot_code"
+fi
+
 cd "${ROBOT_CODE}"
 export PATH="${PATH}:${ROBOT_CODE}"
 while true; do
diff --git a/y2020/vision/rootfs/README b/y2020/vision/rootfs/README
new file mode 100644
index 0000000..2320774
--- /dev/null
+++ b/y2020/vision/rootfs/README
@@ -0,0 +1,16 @@
+This modifies a stock debian root filesystem to be able to operate as a vision
+pi.  It is not trying to be reproducible, but should be good enough for FRC
+purposes.
+
+The default hostname and IP is pi-8971-1, 10.89.71.101.
+  Username pi, password raspberry.
+
+Download 2020-02-13-raspbian-buster-lite.img (or equivalent) and edit
+modify_rootfs.sh to point to it.  Run modify_rootfs.sh to build the filesystem
+(you might need to hit return in a spot or two).
+
+After confirming the target device, deploy with
+  dd if=2020-02-13-raspbian-buster-lite.img of=/dev/sdX
+
+From there, log in, sudo to root, and run /root/bin/change_hostname.sh to
+change the hostname to the actual target.
diff --git a/y2020/vision/rootfs/change_hostname.sh b/y2020/vision/rootfs/change_hostname.sh
new file mode 100755
index 0000000..1842504
--- /dev/null
+++ b/y2020/vision/rootfs/change_hostname.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+set -euo pipefail
+
+HOSTNAME="$1"
+
+if [[ ! "${HOSTNAME}" =~ ^pi-[0-9]*-[0-9]$ ]]; then
+  echo "Invalid hostname ${HOSTNAME}, needs to be pi-[team#]-[pi#]"
+  exit 1
+fi
+
+TEAM_NUMBER="$(echo ${HOSTNAME} | sed 's/pi-\(.*\)-.*/\1/')"
+PI_NUMBER="$(echo ${HOSTNAME} | sed 's/pi-.*-\(.*\)/\1/')"
+IP_BASE="$(echo ${TEAM_NUMBER} | sed 's/\(.*\)\(..\)/10.\1.\2/')"
+IP="${IP_BASE}.$(( 100 + ${PI_NUMBER}))"
+
+echo "Changing to team number ${TEAM_NUMBER}, IP ${IP}"
+
+sed -i "s/^static ip_address=.*$/static ip_address=${IP}\/24/" /etc/dhcpcd.conf
+
+sed -i "s/\(127\.0\.1\.1\t\).*$/\1${HOSTNAME}/" /etc/hosts
+
+echo "${HOSTNAME}" > /etc/hostname
+
+if grep /etc/hosts '^10\.[0-9]*\.[0-9]*\.[0-9]*\s*pi-[0-9]*-[0-9] pi[0-9]$' /etc/hosts >/dev/null ;
+then
+  sed -i "s/^10\.[0-9]*\.[0-9]*\(\.[0-9]*\s*pi-\)[0-9]*\(-[0-9] pi[0-9]\)$/${IP_BASE}\1${TEAM_NUMBER}\2/" /etc/hosts
+else
+  for i in {1..6}; do
+    echo -e "${IP_BASE}.$(( i + 100 ))\tpi-${TEAM_NUMBER}-${i} pi${i}" >> /etc/hosts
+  done
+fi
+
+if grep '^10\.[0-9]*\.[0-9]*\.2\s*roborio$' /etc/hosts >/dev/null;
+then
+  sed -i "s/^10\.[0-9]*\.[0-9]*\(\.2\s*roborio\)$/${IP_BASE}\1/" /etc/hosts
+else
+  echo -e "${IP_BASE}.2\troborio" >> /etc/hosts
+fi
+
diff --git a/y2020/vision/rootfs/dhcpcd.conf b/y2020/vision/rootfs/dhcpcd.conf
new file mode 100644
index 0000000..43a54b7
--- /dev/null
+++ b/y2020/vision/rootfs/dhcpcd.conf
@@ -0,0 +1,59 @@
+# A sample configuration for dhcpcd.
+# See dhcpcd.conf(5) for details.
+
+# Allow users of this group to interact with dhcpcd via the control socket.
+#controlgroup wheel
+
+# Inform the DHCP server of our hostname for DDNS.
+hostname
+
+# Use the hardware address of the interface for the Client ID.
+clientid
+# or
+# Use the same DUID + IAID as set in DHCPv6 for DHCPv4 ClientID as per RFC4361.
+# Some non-RFC compliant DHCP servers do not reply with this set.
+# In this case, comment out duid and enable clientid above.
+#duid
+
+# Persist interface configuration when dhcpcd exits.
+persistent
+
+# Rapid commit support.
+# Safe to enable by default because it requires the equivalent option set
+# on the server to actually work.
+option rapid_commit
+
+# A list of options to request from the DHCP server.
+option domain_name_servers, domain_name, domain_search, host_name
+option classless_static_routes
+# Respect the network MTU. This is applied to DHCP routes.
+option interface_mtu
+
+# Most distributions have NTP support.
+#option ntp_servers
+
+# A ServerID is required by RFC2131.
+require dhcp_server_identifier
+
+# Generate SLAAC address using the Hardware Address of the interface
+#slaac hwaddr
+# OR generate Stable Private IPv6 Addresses based from the DUID
+slaac private
+
+# Example static IP configuration:
+interface eth0
+static ip_address=10.89.71.102/24
+#static ip6_address=fd51:42f8:caae:d92e::ff/64
+#static routers=192.168.0.1
+#static domain_name_servers=192.168.0.1 8.8.8.8 fd51:42f8:caae:d92e::1
+
+# It is possible to fall back to a static IP if DHCP fails:
+# define static profile
+#profile static_eth0
+#static ip_address=192.168.1.23/24
+#static routers=192.168.1.1
+#static domain_name_servers=192.168.1.1
+
+# fallback to static profile on eth0
+#interface eth0
+#fallback static_eth0
diff --git a/y2020/vision/rootfs/modify_rootfs.sh b/y2020/vision/rootfs/modify_rootfs.sh
new file mode 100755
index 0000000..c472caa
--- /dev/null
+++ b/y2020/vision/rootfs/modify_rootfs.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+set -xe
+
+IMAGE="2020-02-13-raspbian-buster-lite.img"
+PARTITION="2020-02-13-raspbian-buster-lite.img.partition"
+HOSTNAME="pi-8971-1"
+
+function target() {
+  HOME=/root/ USER=root sudo proot -0 -q qemu-arm-static -w / -r "${PARTITION}" "$@"
+}
+
+function user_pi_target() {
+  USER=root sudo proot -0 -q qemu-arm-static -w / -r "${PARTITION}" sudo -h 127.0.0.1 -u pi "$@"
+}
+
+OFFSET="$(fdisk -lu "${IMAGE}" | grep "${IMAGE}2" | awk '{print 512*$2}')"
+mkdir -p "${PARTITION}"
+
+if mount | grep "${PARTITION}" >/dev/null ;
+then
+  echo "Already mounted"
+else
+  echo "Mounting"
+  sudo mount -o loop,offset=${OFFSET} "${IMAGE}" "${PARTITION}"
+fi
+
+sudo cp target_configure.sh "${PARTITION}/tmp/"
+sudo cp dhcpcd.conf "${PARTITION}/tmp/dhcpcd.conf"
+sudo cp change_hostname.sh "${PARTITION}/tmp/change_hostname.sh"
+
+target /bin/mkdir -p /home/pi/.ssh/
+cat ~/.ssh/id_rsa.pub | target tee /home/pi/.ssh/authorized_keys
+
+target /bin/bash /tmp/target_configure.sh
+
+# Run a prompt as root inside the target to poke around and check things.
+target /bin/bash --rcfile /root/.bashrc
+
+sudo umount "${PARTITION}"
+rmdir "${PARTITION}"
diff --git a/y2020/vision/rootfs/target_configure.sh b/y2020/vision/rootfs/target_configure.sh
new file mode 100755
index 0000000..2a072e5
--- /dev/null
+++ b/y2020/vision/rootfs/target_configure.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+
+set -ex
+
+mkdir -p /root/bin
+
+# Give it a static IP
+cp /tmp/dhcpcd.conf /etc/
+
+# And provide a script to change it.
+cp /tmp/change_hostname.sh /root/bin/
+chmod a+x /root/bin/change_hostname.sh
+
+chown -R pi.pi /home/pi/.ssh
+
+apt-get install -y vim-nox \
+  git \
+  cpufrequtils
+
+echo 'GOVERNOR="performance"' > /etc/default/cpufrequtils
+
+# Add a .bashrc and friends for root.
+if [[ ! -e "/root/.dotfiles" ]]; then
+  cd /root/
+  git clone --separate-git-dir=/root/.dotfiles https://github.com/AustinSchuh/.dotfiles.git tmpdotfiles
+  rsync --recursive --verbose --exclude '.git' tmpdotfiles/ /root/
+  rm -r tmpdotfiles
+  git --git-dir=/root/.dotfiles/ --work-tree=/root/ config --local status.showUntrackedFiles no
+  # Run the vundle installer which installs plugins on the first run of vim.
+  vim -c ":qa!"
+fi
+
+# Add a .bashrc and friends for pi.
+if [[ ! -e "/home/pi/.dotfiles" ]]; then
+  cd /home/pi/
+  su -c "git clone --separate-git-dir=/home/pi/.dotfiles https://github.com/AustinSchuh/.dotfiles.git tmpdotfiles" pi
+  su -c "rsync --recursive --verbose --exclude '.git' tmpdotfiles/ /home/pi/" pi
+  su -c "rm -r tmpdotfiles" pi
+  su -c "git --git-dir=/home/pi/.dotfiles/ --work-tree=/home/pi/ config --local status.showUntrackedFiles no" pi
+  # Run the vundle installer which installs plugins on the first run of vim.
+  su -c "vim -c ':qa!'" pi
+fi
+
+# Make SSH work and not complain about pi being the username and pw still.
+rm -f /etc/profile.d/sshpwd.sh
+rm -f /etc/profile.d/wifi-check.sh
+
+systemctl enable ssh.service
+
+# Default us to pi-8971-1
+/root/bin/change_hostname.sh pi-8971-1