Merge changes I476d73b1,I439bd21c,Ibefb3e1b

* changes:
  Add irq_affinity process for managing kthreads and irqs
  Expose thread list to users and track kthreads
  Add code to parse /proc/interrupts (and tests)
diff --git a/.bazelrc b/.bazelrc
index 9c94391..ce727ec 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -79,7 +79,7 @@
 build --show_result 5
 # Dump the output of the failing test to stdout.
 # Keep the default test timeouts except make 'eternal'=4500 secs
-test --test_output=errors --test_timeout=-1,-1,-1,4500
+test --test_output=errors --test_timeout=-1,-1,-1,5500
 
 build --sandbox_base=/dev/shm/
 build --experimental_multi_threaded_digest
diff --git a/WORKSPACE b/WORKSPACE
index 3158eb8..e14280e 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -988,39 +988,28 @@
     url = "https://www.frc971.org/Build-Dependencies/opencv_amd64_v3.tar.gz",
 )
 
-# Downloaded from:
-# https://github.com/halide/Halide/releases/download/release_2019_08_27/halide-linux-64-gcc53-800-65c26cba6a3eca2d08a0bccf113ca28746012cc3.tgz
-# which is "Halide 2019/08/27" at https://github.com/halide/Halide/releases.
 http_archive(
     name = "halide_k8",
     build_file = "@//debian:halide.BUILD",
-    sha256 = "c67185d50a99adba86f6b2cc43c7e2cf11bcdfba9052d05e764a89b456a50446",
-    strip_prefix = "halide/",
-    url = "https://www.frc971.org/Build-Dependencies/halide-linux-64-gcc53-800-65c26cba6a3eca2d08a0bccf113ca28746012cc3.tgz",
+    sha256 = "be3bdd067acb9ee0d37d0830821113cd69174bee46da466a836d8829fef7cf91",
+    strip_prefix = "Halide-14.0.0-x86-64-linux/",
+    url = "https://github.com/halide/Halide/releases/download/v14.0.0/Halide-14.0.0-x86-64-linux-6b9ed2afd1d6d0badf04986602c943e287d44e46.tar.gz",
 )
 
-# Downloaded from:
-# https://github.com/halide/Halide/releases/download/v8.0.0/halide-arm64-linux-64-trunk-65c26cba6a3eca2d08a0bccf113ca28746012cc3.tgz
-# which is "Halide 8.0.0" at https://github.com/halide/Halide/releases.
-# The "2019/08/27" release was renamed as per the release notes:
-# https://github.com/halide/Halide/releases/tag/v8.0.0
 http_archive(
     name = "halide_arm64",
     build_file = "@//debian:halide.BUILD",
-    sha256 = "97b3e54565cd9df52abdd6452f3720ffd38861524154d74ae3d20dc949ed2a63",
-    strip_prefix = "halide/",
-    url = "https://www.frc971.org/Build-Dependencies/halide-arm64-linux-64-trunk-65c26cba6a3eca2d08a0bccf113ca28746012cc3.tgz",
+    sha256 = "cdd42411bcbba682f73d7db0af69837c4857ee90f1727c6feb37fc9a98132385",
+    strip_prefix = "Halide-14.0.0-arm-64-linux/",
+    url = "https://github.com/halide/Halide/releases/download/v14.0.0/Halide-14.0.0-arm-64-linux-6b9ed2afd1d6d0badf04986602c943e287d44e46.tar.gz",
 )
 
-# Downloaded from:
-# https://github.com/halide/Halide/releases/download/release_2019_08_27/halide-arm32-linux-32-trunk-65c26cba6a3eca2d08a0bccf113ca28746012cc3.tgz
-# which is "Halide 2019/08/27" at https://github.com/halide/Halide/releases.
 http_archive(
     name = "halide_armhf",
     build_file = "@//debian:halide.BUILD",
-    sha256 = "10564c559c9e04a173823413916d05fadd6e697d91bab21ddc5041190fa8f0f0",
-    strip_prefix = "halide/",
-    url = "https://www.frc971.org/Build-Dependencies/halide-arm32-linux-32-trunk-65c26cba6a3eca2d08a0bccf113ca28746012cc3.tgz",
+    sha256 = "6b3fe3396391b57990a2c41d8dcea74b0734d1b2a0fd42fe0858d954aa45df2b",
+    strip_prefix = "Halide-14.0.0-arm-32-linux/",
+    url = "https://github.com/halide/Halide/releases/download/v14.0.0/Halide-14.0.0-arm-32-linux-6b9ed2afd1d6d0badf04986602c943e287d44e46.tar.gz",
 )
 
 # Downloaded from:
diff --git a/aos/BUILD b/aos/BUILD
index 164f40b..9768753 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -625,6 +625,7 @@
     visibility = ["//visibility:public"],
     deps = [
         "@com_github_google_glog//:glog",
+        "@com_google_absl//absl/strings",
     ],
 )
 
diff --git a/aos/ftrace.cc b/aos/ftrace.cc
index 9806661..37dc147 100644
--- a/aos/ftrace.cc
+++ b/aos/ftrace.cc
@@ -3,23 +3,35 @@
 #include <cstdarg>
 #include <cstdio>
 
-DEFINE_bool(
-    enable_ftrace, false,
-    "If false, disable logging to /sys/kernel/debug/tracing/trace_marker");
+#include "absl/strings/str_cat.h"
+
+DEFINE_bool(enable_ftrace, false,
+            "If false, disable logging to /sys/kernel/tracing/trace_marker");
 
 namespace aos {
 
+namespace {
 int MaybeCheckOpen(const char *file) {
   if (!FLAGS_enable_ftrace) return -1;
-  int result = open(file, O_WRONLY);
-  PCHECK(result >= 0) << ": Failed to open " << file;
+  int result =
+      open(absl::StrCat("/sys/kernel/tracing/", file).c_str(), O_WRONLY);
+  if (result == -1) {
+    result = open(absl::StrCat("/sys/kernel/debug/tracing/", file).c_str(),
+                  O_WRONLY);
+  }
+
+  // New kernels prefer /sys/kernel/tracing, and old kernels prefer
+  // /sys/kernel/debug/tracing...  When Ubuntu 18.04 and the 4.9 kernel
+  // disappear finally, we can switch fully to /sys/kernel/tracing.
+  PCHECK(result >= 0) << ": Failed to open /sys/kernel/tracing/" << file
+                      << " or legacy /sys/kernel/debug/tracing/" << file;
   return result;
 }
+}  // namespace
 
 Ftrace::Ftrace()
-    : message_fd_(MaybeCheckOpen("/sys/kernel/debug/tracing/trace_marker")),
-      on_fd_(MaybeCheckOpen("/sys/kernel/debug/tracing/tracing_on")) {
-}
+    : message_fd_(MaybeCheckOpen("trace_marker")),
+      on_fd_(MaybeCheckOpen("tracing_on")) {}
 
 Ftrace::~Ftrace() {
   if (message_fd_ != -1) {
diff --git a/aos/realtime.cc b/aos/realtime.cc
index ea54d9e..2f299e6 100644
--- a/aos/realtime.cc
+++ b/aos/realtime.cc
@@ -293,10 +293,11 @@
 }
 
 void FatalUnsetRealtimePriority() {
+  int saved_errno = errno;
   // Drop our priority first.  We are about to do lots of work to undo
   // everything, don't get overly clever.
   struct sched_param param;
-  param.sched_priority = 20;
+  param.sched_priority = 0;
   sched_setscheduler(0, SCHED_OTHER, &param);
 
   is_realtime = false;
@@ -311,12 +312,13 @@
       // ignore . and .. which are zeroes for some reason
       if (thread_id != 0) {
         struct sched_param param;
-        param.sched_priority = 20;
+        param.sched_priority = 0;
         sched_setscheduler(thread_id, SCHED_OTHER, &param);
       }
     }
     closedir(dirp);
   }
+  errno = saved_errno;
 }
 
 void RegisterMallocHook() {
diff --git a/debian/halide.BUILD b/debian/halide.BUILD
index 972df97..a38d910 100644
--- a/debian/halide.BUILD
+++ b/debian/halide.BUILD
@@ -8,7 +8,7 @@
 
 cc_library(
     name = "gengen",
-    srcs = ["tools/GenGen.cpp"],
+    srcs = ["share/Halide/tools/GenGen.cpp"],
     visibility = ["//visibility:public"],
     deps = [
         ":halide",
@@ -29,7 +29,7 @@
     name = "build_files",
     srcs = [
         "lib/libHalide.a",
-        "tools/GenGen.cpp",
+        "share/Halide/tools/GenGen.cpp",
     ] + glob(["include/*.h"]),
     visibility = ["//visibility:public"],
 )
diff --git a/frc971/control_loops/python/BUILD b/frc971/control_loops/python/BUILD
index f3dcb48..ef7b31b 100644
--- a/frc971/control_loops/python/BUILD
+++ b/frc971/control_loops/python/BUILD
@@ -1,5 +1,23 @@
 package(default_visibility = ["//visibility:public"])
 
+py_library(
+    name = "constants",
+    srcs = ["constants.py"],
+    deps = [
+        "@pip//pygobject",
+    ],
+)
+
+py_library(
+    name = "drawing_constants",
+    srcs = ["drawing_constants.py"],
+    deps = [
+        ":color",
+        "@pip//numpy",
+        "@pip//pygobject",
+    ],
+)
+
 py_binary(
     name = "haptic_wheel",
     srcs = [
@@ -154,7 +172,6 @@
 py_binary(
     name = "spline_graph",
     srcs = [
-        "color.py",
         "graph.py",
         "multispline.py",
         "path_edit.py",
@@ -171,6 +188,9 @@
     visibility = ["//visibility:public"],
     deps = [
         ":basic_window",
+        ":color",
+        ":constants",
+        ":drawing_constants",
         ":libspline",
         ":python_init",
         "@matplotlib_repo//:matplotlib3",
@@ -182,11 +202,11 @@
     name = "basic_window",
     srcs = [
         "basic_window.py",
-        "color.py",
     ],
     target_compatible_with = ["@platforms//cpu:x86_64"],
     visibility = ["//visibility:public"],
     deps = [
+        ":constants",
         ":python_init",
         "@python_gtk",
     ],
diff --git a/frc971/rockpi/.config b/frc971/rockpi/.config
index 18a33b7..5fe3348 100644
--- a/frc971/rockpi/.config
+++ b/frc971/rockpi/.config
@@ -115,8 +115,7 @@
 #
 CONFIG_TICK_CPU_ACCOUNTING=y
 # CONFIG_VIRT_CPU_ACCOUNTING_GEN is not set
-CONFIG_IRQ_TIME_ACCOUNTING=y
-CONFIG_HAVE_SCHED_AVG_IRQ=y
+# CONFIG_IRQ_TIME_ACCOUNTING is not set
 CONFIG_SCHED_THERMAL_PRESSURE=y
 CONFIG_BSD_PROCESS_ACCT=y
 CONFIG_BSD_PROCESS_ACCT_V3=y
@@ -134,21 +133,30 @@
 #
 CONFIG_TREE_RCU=y
 CONFIG_PREEMPT_RCU=y
-# CONFIG_RCU_EXPERT is not set
+CONFIG_RCU_EXPERT=y
 CONFIG_SRCU=y
 CONFIG_TREE_SRCU=y
 CONFIG_TASKS_RCU_GENERIC=y
+# CONFIG_FORCE_TASKS_RCU is not set
 CONFIG_TASKS_RCU=y
+# CONFIG_FORCE_TASKS_RUDE_RCU is not set
+CONFIG_TASKS_RUDE_RCU=y
+# CONFIG_FORCE_TASKS_TRACE_RCU is not set
 CONFIG_TASKS_TRACE_RCU=y
 CONFIG_RCU_STALL_COMMON=y
 CONFIG_RCU_NEED_SEGCBLIST=y
+CONFIG_RCU_FANOUT=64
+CONFIG_RCU_FANOUT_LEAF=16
 CONFIG_RCU_BOOST=y
 CONFIG_RCU_BOOST_DELAY=500
+# CONFIG_RCU_EXP_KTHREAD is not set
+# CONFIG_RCU_NOCB_CPU is not set
+CONFIG_TASKS_TRACE_RCU_READ_MB=y
 # end of RCU Subsystem
 
 CONFIG_IKCONFIG=y
 CONFIG_IKCONFIG_PROC=y
-# CONFIG_IKHEADERS is not set
+CONFIG_IKHEADERS=m
 CONFIG_LOG_BUF_SHIFT=17
 CONFIG_LOG_CPU_MAX_BUF_SHIFT=12
 CONFIG_PRINTK_SAFE_LOG_BUF_SHIFT=13
@@ -414,12 +422,9 @@
 CONFIG_HW_PERF_EVENTS=y
 CONFIG_PARAVIRT=y
 # CONFIG_PARAVIRT_TIME_ACCOUNTING is not set
-CONFIG_KEXEC=y
 # CONFIG_KEXEC_FILE is not set
 CONFIG_CRASH_DUMP=y
-CONFIG_TRANS_TABLE=y
-CONFIG_XEN_DOM0=y
-CONFIG_XEN=y
+# CONFIG_XEN is not set
 CONFIG_FORCE_MAX_ZONEORDER=11
 CONFIG_UNMAP_KERNEL_AT_EL0=y
 CONFIG_MITIGATE_SPECTRE_BRANCH_HISTORY=y
@@ -511,29 +516,17 @@
 #
 # Power management options
 #
-CONFIG_SUSPEND=y
-CONFIG_SUSPEND_FREEZER=y
-# CONFIG_SUSPEND_SKIP_SYNC is not set
-CONFIG_HIBERNATE_CALLBACKS=y
-CONFIG_HIBERNATION=y
-CONFIG_HIBERNATION_SNAPSHOT_DEV=y
-CONFIG_PM_STD_PARTITION=""
-CONFIG_PM_SLEEP=y
-CONFIG_PM_SLEEP_SMP=y
-# CONFIG_PM_AUTOSLEEP is not set
-# CONFIG_PM_USERSPACE_AUTOSLEEP is not set
-# CONFIG_PM_WAKELOCKS is not set
+# CONFIG_SUSPEND is not set
+# CONFIG_HIBERNATION is not set
 CONFIG_PM=y
 # CONFIG_PM_DEBUG is not set
 CONFIG_PM_CLK=y
 CONFIG_PM_GENERIC_DOMAINS=y
-CONFIG_WQ_POWER_EFFICIENT_DEFAULT=y
-CONFIG_PM_GENERIC_DOMAINS_SLEEP=y
+# CONFIG_WQ_POWER_EFFICIENT_DEFAULT is not set
 CONFIG_PM_GENERIC_DOMAINS_OF=y
 CONFIG_CPU_PM=y
 # CONFIG_ENERGY_MODEL is not set
 CONFIG_ARCH_HIBERNATION_POSSIBLE=y
-CONFIG_ARCH_HIBERNATION_HEADER=y
 CONFIG_ARCH_SUSPEND_POSSIBLE=y
 # end of Power management options
 
@@ -545,18 +538,14 @@
 # CPU Idle
 #
 CONFIG_CPU_IDLE=y
-CONFIG_CPU_IDLE_MULTIPLE_DRIVERS=y
 CONFIG_CPU_IDLE_GOV_LADDER=y
 # CONFIG_CPU_IDLE_GOV_MENU is not set
 # CONFIG_CPU_IDLE_GOV_TEO is not set
-CONFIG_DT_IDLE_STATES=y
-CONFIG_DT_IDLE_GENPD=y
 
 #
 # ARM CPU Idle Drivers
 #
-CONFIG_ARM_PSCI_CPUIDLE=y
-CONFIG_ARM_PSCI_CPUIDLE_DOMAIN=y
+# CONFIG_ARM_PSCI_CPUIDLE is not set
 # end of ARM CPU Idle Drivers
 # end of CPU Idle
 
@@ -620,14 +609,13 @@
 #
 # General architecture-dependent options
 #
-CONFIG_CRASH_CORE=y
-CONFIG_KEXEC_CORE=y
 CONFIG_ARCH_HAS_SUBPAGE_FAULTS=y
-# CONFIG_KPROBES is not set
+CONFIG_KPROBES=y
 CONFIG_JUMP_LABEL=y
 # CONFIG_STATIC_KEYS_SELFTEST is not set
 CONFIG_UPROBES=y
 CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y
+CONFIG_KRETPROBES=y
 CONFIG_HAVE_IOREMAP_PROT=y
 CONFIG_HAVE_KPROBES=y
 CONFIG_HAVE_KRETPROBES=y
@@ -794,7 +782,6 @@
 CONFIG_ARCH_USE_QUEUED_RWLOCKS=y
 CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE=y
 CONFIG_ARCH_HAS_SYSCALL_WRAPPER=y
-CONFIG_FREEZER=y
 
 #
 # Executable file formats
@@ -851,7 +838,6 @@
 CONFIG_ARCH_ENABLE_HUGEPAGE_MIGRATION=y
 CONFIG_CONTIG_ALLOC=y
 CONFIG_PHYS_ADDR_T_64BIT=y
-CONFIG_MMU_NOTIFIER=y
 CONFIG_KSM=y
 CONFIG_DEFAULT_MMAP_MIN_ADDR=4096
 CONFIG_ARCH_SUPPORTS_MEMORY_FAILURE=y
@@ -1378,7 +1364,6 @@
 CONFIG_FW_LOADER_USER_HELPER=y
 CONFIG_FW_LOADER_USER_HELPER_FALLBACK=y
 # CONFIG_FW_LOADER_COMPRESS is not set
-CONFIG_FW_CACHE=y
 # CONFIG_FW_UPLOAD is not set
 # end of Firmware loader
 
@@ -1389,7 +1374,6 @@
 # CONFIG_DEBUG_DEVRES is not set
 # CONFIG_DEBUG_TEST_DRIVER_REMOVE is not set
 # CONFIG_TEST_ASYNC_DRIVER_PROBE is not set
-CONFIG_SYS_HYPERVISOR=y
 CONFIG_GENERIC_CPU_AUTOPROBE=y
 CONFIG_GENERIC_CPU_VULNERABILITIES=y
 CONFIG_SOC_BUS=y
@@ -1621,8 +1605,6 @@
 # CONFIG_BLK_DEV_RAM is not set
 # CONFIG_CDROM_PKTCDVD is not set
 # CONFIG_ATA_OVER_ETH is not set
-# CONFIG_XEN_BLKDEV_FRONTEND is not set
-# CONFIG_XEN_BLKDEV_BACKEND is not set
 # CONFIG_VIRTIO_BLK is not set
 # CONFIG_BLK_DEV_RBD is not set
 # CONFIG_BLK_DEV_UBLK is not set
@@ -1719,7 +1701,7 @@
 #
 # SCSI support type (disk, tape, CD-ROM)
 #
-# CONFIG_BLK_DEV_SD is not set
+CONFIG_BLK_DEV_SD=m
 # CONFIG_CHR_DEV_ST is not set
 # CONFIG_BLK_DEV_SR is not set
 # CONFIG_CHR_DEV_SG is not set
@@ -2104,6 +2086,10 @@
 
 # CONFIG_PPP is not set
 # CONFIG_SLIP is not set
+
+#
+# Host-side USB support is needed for USB Network Adapter support
+#
 # CONFIG_USB_NET_DRIVERS is not set
 # CONFIG_WLAN is not set
 # CONFIG_WAN is not set
@@ -2114,8 +2100,6 @@
 # CONFIG_WWAN is not set
 # end of Wireless WAN
 
-# CONFIG_XEN_NETDEV_FRONTEND is not set
-# CONFIG_XEN_NETDEV_BACKEND is not set
 # CONFIG_VMXNET3 is not set
 # CONFIG_NETDEVSIM is not set
 # CONFIG_NET_FAILOVER is not set
@@ -2175,7 +2159,6 @@
 CONFIG_VT=y
 CONFIG_CONSOLE_TRANSLATIONS=y
 CONFIG_VT_CONSOLE=y
-CONFIG_VT_CONSOLE_SLEEP=y
 CONFIG_HW_CONSOLE=y
 CONFIG_VT_HW_CONSOLE_BINDING=y
 CONFIG_UNIX98_PTYS=y
@@ -2241,7 +2224,6 @@
 # CONFIG_N_GSM is not set
 # CONFIG_NOZOMI is not set
 # CONFIG_NULL_TTY is not set
-# CONFIG_HVC_XEN is not set
 # CONFIG_HVC_DCC is not set
 # CONFIG_RPMSG_TTY is not set
 CONFIG_SERIAL_DEV_BUS=y
@@ -2839,7 +2821,6 @@
 # CONFIG_ALIM7101_WDT is not set
 # CONFIG_I6300ESB_WDT is not set
 # CONFIG_MEN_A21_WDT is not set
-# CONFIG_XEN_WDT is not set
 
 #
 # PCI-based Watchdog Cards
@@ -3792,7 +3773,6 @@
 # CONFIG_DRM_DP_AUX_CHARDEV is not set
 # CONFIG_DRM_DP_CEC is not set
 CONFIG_DRM_GEM_CMA_HELPER=m
-CONFIG_DRM_GEM_SHMEM_HELPER=m
 CONFIG_DRM_SCHED=m
 
 #
@@ -3849,7 +3829,7 @@
 # CONFIG_DRM_PANEL_BOE_TV101WUM_NL6 is not set
 # CONFIG_DRM_PANEL_DSI_CM is not set
 # CONFIG_DRM_PANEL_LVDS is not set
-CONFIG_DRM_PANEL_SIMPLE=m
+# CONFIG_DRM_PANEL_SIMPLE is not set
 # CONFIG_DRM_PANEL_EDP is not set
 # CONFIG_DRM_PANEL_EBBG_FT8719 is not set
 # CONFIG_DRM_PANEL_ELIDA_KD35T133 is not set
@@ -3987,9 +3967,8 @@
 # CONFIG_TINYDRM_ST7586 is not set
 # CONFIG_TINYDRM_ST7735R is not set
 # CONFIG_DRM_PL111 is not set
-# CONFIG_DRM_XEN_FRONTEND is not set
-CONFIG_DRM_LIMA=m
-CONFIG_DRM_PANFROST=m
+# CONFIG_DRM_LIMA is not set
+# CONFIG_DRM_PANFROST is not set
 # CONFIG_DRM_TIDSS is not set
 # CONFIG_DRM_GUD is not set
 # CONFIG_DRM_SSD130X is not set
@@ -4061,7 +4040,7 @@
 # CONFIG_HID_BETOP_FF is not set
 # CONFIG_HID_BIGBEN_FF is not set
 CONFIG_HID_CHERRY=y
-CONFIG_HID_CHICONY=y
+CONFIG_HID_CHICONY=m
 # CONFIG_HID_CORSAIR is not set
 # CONFIG_HID_COUGAR is not set
 # CONFIG_HID_MACALLY is not set
@@ -4096,7 +4075,7 @@
 # CONFIG_HID_LED is not set
 # CONFIG_HID_LENOVO is not set
 # CONFIG_HID_LETSKETCH is not set
-CONFIG_HID_LOGITECH=y
+CONFIG_HID_LOGITECH=m
 # CONFIG_HID_LOGITECH_HIDPP is not set
 # CONFIG_LOGITECH_FF is not set
 # CONFIG_LOGIRUMBLEPAD2_FF is not set
@@ -4155,9 +4134,16 @@
 #
 # USB HID support
 #
-CONFIG_USB_HID=y
+CONFIG_USB_HID=m
 # CONFIG_HID_PID is not set
 # CONFIG_USB_HIDDEV is not set
+
+#
+# USB HID Boot Protocol drivers
+#
+# CONFIG_USB_KBD is not set
+# CONFIG_USB_MOUSE is not set
+# end of USB HID Boot Protocol drivers
 # end of USB HID support
 
 #
@@ -4173,10 +4159,10 @@
 CONFIG_USB_SUPPORT=y
 CONFIG_USB_COMMON=y
 # CONFIG_USB_LED_TRIG is not set
-CONFIG_USB_ULPI_BUS=y
+CONFIG_USB_ULPI_BUS=m
 # CONFIG_USB_CONN_GPIO is not set
 CONFIG_USB_ARCH_HAS_HCD=y
-CONFIG_USB=y
+CONFIG_USB=m
 CONFIG_USB_PCI=y
 # CONFIG_USB_ANNOUNCE_NEW_DEVICES is not set
 
@@ -4186,10 +4172,9 @@
 CONFIG_USB_DEFAULT_PERSIST=y
 # CONFIG_USB_FEW_INIT_RETRIES is not set
 # CONFIG_USB_DYNAMIC_MINORS is not set
-CONFIG_USB_OTG=y
+# CONFIG_USB_OTG is not set
 # CONFIG_USB_OTG_PRODUCTLIST is not set
 # CONFIG_USB_OTG_DISABLE_EXTERNAL_HUB is not set
-# CONFIG_USB_OTG_FSM is not set
 # CONFIG_USB_LEDS_TRIGGER_USBPORT is not set
 CONFIG_USB_AUTOSUSPEND_DELAY=2
 # CONFIG_USB_MON is not set
@@ -4197,30 +4182,30 @@
 #
 # USB Host Controller Drivers
 #
-# CONFIG_USB_C67X00_HCD is not set
-CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_C67X00_HCD=m
+CONFIG_USB_XHCI_HCD=m
 # CONFIG_USB_XHCI_DBGCAP is not set
-CONFIG_USB_XHCI_PCI=y
+CONFIG_USB_XHCI_PCI=m
 # CONFIG_USB_XHCI_PCI_RENESAS is not set
-CONFIG_USB_XHCI_PLATFORM=y
-CONFIG_USB_EHCI_HCD=y
+CONFIG_USB_XHCI_PLATFORM=m
+CONFIG_USB_EHCI_HCD=m
 CONFIG_USB_EHCI_ROOT_HUB_TT=y
 CONFIG_USB_EHCI_TT_NEWSCHED=y
-CONFIG_USB_EHCI_PCI=y
+CONFIG_USB_EHCI_PCI=m
 # CONFIG_USB_EHCI_FSL is not set
-CONFIG_USB_EHCI_HCD_PLATFORM=y
-# CONFIG_USB_OXU210HP_HCD is not set
-# CONFIG_USB_ISP116X_HCD is not set
-# CONFIG_USB_FOTG210_HCD is not set
-# CONFIG_USB_MAX3421_HCD is not set
-CONFIG_USB_OHCI_HCD=y
-CONFIG_USB_OHCI_HCD_PCI=y
-CONFIG_USB_OHCI_HCD_PLATFORM=y
-# CONFIG_USB_UHCI_HCD is not set
-# CONFIG_USB_SL811_HCD is not set
-# CONFIG_USB_R8A66597_HCD is not set
+CONFIG_USB_EHCI_HCD_PLATFORM=m
+CONFIG_USB_OXU210HP_HCD=m
+CONFIG_USB_ISP116X_HCD=m
+CONFIG_USB_FOTG210_HCD=m
+CONFIG_USB_MAX3421_HCD=m
+CONFIG_USB_OHCI_HCD=m
+CONFIG_USB_OHCI_HCD_PCI=m
+CONFIG_USB_OHCI_HCD_PLATFORM=m
+CONFIG_USB_UHCI_HCD=m
+CONFIG_USB_SL811_HCD=m
+# CONFIG_USB_SL811_HCD_ISO is not set
+CONFIG_USB_R8A66597_HCD=m
 # CONFIG_USB_HCD_TEST_MODE is not set
-# CONFIG_USB_XEN_HCD is not set
 
 #
 # USB Device Class drivers
@@ -4237,34 +4222,37 @@
 #
 # also be needed; see USB_STORAGE Help for more info
 #
-CONFIG_USB_STORAGE=y
+CONFIG_USB_STORAGE=m
 # CONFIG_USB_STORAGE_DEBUG is not set
-# CONFIG_USB_STORAGE_REALTEK is not set
-# CONFIG_USB_STORAGE_DATAFAB is not set
-# CONFIG_USB_STORAGE_FREECOM is not set
-# CONFIG_USB_STORAGE_ISD200 is not set
-# CONFIG_USB_STORAGE_USBAT is not set
-# CONFIG_USB_STORAGE_SDDR09 is not set
-# CONFIG_USB_STORAGE_SDDR55 is not set
-# CONFIG_USB_STORAGE_JUMPSHOT is not set
-# CONFIG_USB_STORAGE_ALAUDA is not set
-# CONFIG_USB_STORAGE_ONETOUCH is not set
-# CONFIG_USB_STORAGE_KARMA is not set
-# CONFIG_USB_STORAGE_CYPRESS_ATACB is not set
-# CONFIG_USB_STORAGE_ENE_UB6250 is not set
-# CONFIG_USB_UAS is not set
+CONFIG_USB_STORAGE_REALTEK=m
+CONFIG_REALTEK_AUTOPM=y
+CONFIG_USB_STORAGE_DATAFAB=m
+CONFIG_USB_STORAGE_FREECOM=m
+CONFIG_USB_STORAGE_ISD200=m
+CONFIG_USB_STORAGE_USBAT=m
+CONFIG_USB_STORAGE_SDDR09=m
+CONFIG_USB_STORAGE_SDDR55=m
+CONFIG_USB_STORAGE_JUMPSHOT=m
+CONFIG_USB_STORAGE_ALAUDA=m
+CONFIG_USB_STORAGE_ONETOUCH=m
+CONFIG_USB_STORAGE_KARMA=m
+CONFIG_USB_STORAGE_CYPRESS_ATACB=m
+CONFIG_USB_STORAGE_ENE_UB6250=m
+CONFIG_USB_UAS=m
 
 #
 # USB Imaging devices
 #
 # CONFIG_USB_MDC800 is not set
 # CONFIG_USB_MICROTEK is not set
-# CONFIG_USBIP_CORE is not set
+CONFIG_USBIP_CORE=m
+# CONFIG_USBIP_VHCI_HCD is not set
+# CONFIG_USBIP_HOST is not set
+# CONFIG_USBIP_VUDC is not set
+# CONFIG_USBIP_DEBUG is not set
 # CONFIG_USB_CDNS_SUPPORT is not set
 CONFIG_USB_MUSB_HDRC=y
-# CONFIG_USB_MUSB_HOST is not set
-# CONFIG_USB_MUSB_GADGET is not set
-CONFIG_USB_MUSB_DUAL_ROLE=y
+CONFIG_USB_MUSB_GADGET=y
 
 #
 # Platform Glue Layer
@@ -4274,15 +4262,92 @@
 # MUSB DMA mode
 #
 # CONFIG_MUSB_PIO_ONLY is not set
-# CONFIG_USB_DWC3 is not set
-# CONFIG_USB_DWC2 is not set
+CONFIG_USB_DWC3=m
+CONFIG_USB_DWC3_ULPI=y
+CONFIG_USB_DWC3_HOST=y
+# CONFIG_USB_DWC3_GADGET is not set
+# CONFIG_USB_DWC3_DUAL_ROLE is not set
+
+#
+# Platform Glue Driver Support
+#
+CONFIG_USB_DWC3_HAPS=m
+CONFIG_USB_DWC3_OF_SIMPLE=m
+CONFIG_USB_DWC2=m
+CONFIG_USB_DWC2_HOST=y
+
+#
+# Gadget/Dual-role mode requires USB Gadget support to be enabled
+#
+# CONFIG_USB_DWC2_PERIPHERAL is not set
+# CONFIG_USB_DWC2_DUAL_ROLE is not set
+# CONFIG_USB_DWC2_PCI is not set
+# CONFIG_USB_DWC2_DEBUG is not set
+# CONFIG_USB_DWC2_TRACK_MISSED_SOFS is not set
 # CONFIG_USB_CHIPIDEA is not set
-# CONFIG_USB_ISP1760 is not set
+CONFIG_USB_ISP1760=m
+CONFIG_USB_ISP1760_HCD=y
+CONFIG_USB_ISP1761_UDC=y
+# CONFIG_USB_ISP1760_HOST_ROLE is not set
+# CONFIG_USB_ISP1760_GADGET_ROLE is not set
+CONFIG_USB_ISP1760_DUAL_ROLE=y
 
 #
 # USB port drivers
 #
-# CONFIG_USB_SERIAL is not set
+CONFIG_USB_SERIAL=m
+# CONFIG_USB_SERIAL_GENERIC is not set
+# CONFIG_USB_SERIAL_SIMPLE is not set
+# CONFIG_USB_SERIAL_AIRCABLE is not set
+# CONFIG_USB_SERIAL_ARK3116 is not set
+# CONFIG_USB_SERIAL_BELKIN is not set
+# CONFIG_USB_SERIAL_CH341 is not set
+# CONFIG_USB_SERIAL_WHITEHEAT is not set
+# CONFIG_USB_SERIAL_DIGI_ACCELEPORT is not set
+# CONFIG_USB_SERIAL_CP210X is not set
+# CONFIG_USB_SERIAL_CYPRESS_M8 is not set
+# CONFIG_USB_SERIAL_EMPEG is not set
+# CONFIG_USB_SERIAL_FTDI_SIO is not set
+# CONFIG_USB_SERIAL_VISOR is not set
+# CONFIG_USB_SERIAL_IPAQ is not set
+# CONFIG_USB_SERIAL_IR is not set
+# CONFIG_USB_SERIAL_EDGEPORT is not set
+# CONFIG_USB_SERIAL_EDGEPORT_TI is not set
+# CONFIG_USB_SERIAL_F81232 is not set
+# CONFIG_USB_SERIAL_F8153X is not set
+# CONFIG_USB_SERIAL_GARMIN is not set
+# CONFIG_USB_SERIAL_IPW is not set
+# CONFIG_USB_SERIAL_IUU is not set
+# CONFIG_USB_SERIAL_KEYSPAN_PDA is not set
+# CONFIG_USB_SERIAL_KEYSPAN is not set
+# CONFIG_USB_SERIAL_KLSI is not set
+# CONFIG_USB_SERIAL_KOBIL_SCT is not set
+# CONFIG_USB_SERIAL_MCT_U232 is not set
+# CONFIG_USB_SERIAL_METRO is not set
+# CONFIG_USB_SERIAL_MOS7720 is not set
+# CONFIG_USB_SERIAL_MOS7840 is not set
+# CONFIG_USB_SERIAL_MXUPORT is not set
+# CONFIG_USB_SERIAL_NAVMAN is not set
+# CONFIG_USB_SERIAL_PL2303 is not set
+# CONFIG_USB_SERIAL_OTI6858 is not set
+# CONFIG_USB_SERIAL_QCAUX is not set
+# CONFIG_USB_SERIAL_QUALCOMM is not set
+# CONFIG_USB_SERIAL_SPCP8X5 is not set
+# CONFIG_USB_SERIAL_SAFE is not set
+# CONFIG_USB_SERIAL_SIERRAWIRELESS is not set
+# CONFIG_USB_SERIAL_SYMBOL is not set
+# CONFIG_USB_SERIAL_TI is not set
+# CONFIG_USB_SERIAL_CYBERJACK is not set
+# CONFIG_USB_SERIAL_OPTION is not set
+# CONFIG_USB_SERIAL_OMNINET is not set
+# CONFIG_USB_SERIAL_OPTICON is not set
+# CONFIG_USB_SERIAL_XSENS_MT is not set
+# CONFIG_USB_SERIAL_WISHBONE is not set
+# CONFIG_USB_SERIAL_SSU100 is not set
+# CONFIG_USB_SERIAL_QT2 is not set
+# CONFIG_USB_SERIAL_UPD78F0730 is not set
+# CONFIG_USB_SERIAL_XR is not set
+# CONFIG_USB_SERIAL_DEBUG is not set
 
 #
 # USB Miscellaneous drivers
@@ -4313,7 +4378,7 @@
 # CONFIG_USB_HSIC_USB4604 is not set
 # CONFIG_USB_LINK_LAYER_TEST is not set
 # CONFIG_USB_CHAOSKEY is not set
-# CONFIG_USB_ONBOARD_HUB is not set
+CONFIG_USB_ONBOARD_HUB=m
 
 #
 # USB Physical Layer drivers
@@ -4353,7 +4418,6 @@
 # CONFIG_USB_EG20T is not set
 # CONFIG_USB_GADGET_XILINX is not set
 # CONFIG_USB_MAX3420_UDC is not set
-# CONFIG_USB_DUMMY_HCD is not set
 # end of USB Peripheral Controller
 
 # CONFIG_USB_CONFIGFS is not set
@@ -4735,30 +4799,6 @@
 #
 # end of Microsoft Hyper-V guest support
 
-#
-# Xen driver support
-#
-CONFIG_XEN_BALLOON=y
-CONFIG_XEN_SCRUB_PAGES_DEFAULT=y
-CONFIG_XEN_DEV_EVTCHN=y
-CONFIG_XEN_BACKEND=y
-CONFIG_XENFS=y
-CONFIG_XEN_COMPAT_XENFS=y
-CONFIG_XEN_SYS_HYPERVISOR=y
-CONFIG_XEN_GNTDEV=y
-CONFIG_XEN_GRANT_DEV_ALLOC=y
-# CONFIG_XEN_GRANT_DMA_ALLOC is not set
-CONFIG_SWIOTLB_XEN=y
-CONFIG_XEN_PCI_STUB=y
-CONFIG_XEN_PCIDEV_STUB=m
-# CONFIG_XEN_PVCALLS_FRONTEND is not set
-# CONFIG_XEN_PVCALLS_BACKEND is not set
-CONFIG_XEN_PRIVCMD=y
-CONFIG_XEN_EFI=y
-CONFIG_XEN_AUTO_XLATE=y
-# CONFIG_XEN_VIRTIO is not set
-# end of Xen driver support
-
 # CONFIG_GREYBUS is not set
 # CONFIG_COMEDI is not set
 CONFIG_STAGING=y
@@ -4974,7 +5014,6 @@
 # NXP/Freescale QorIQ SoC drivers
 #
 # CONFIG_QUICC_ENGINE is not set
-# CONFIG_FSL_RCPM is not set
 # end of NXP/Freescale QorIQ SoC drivers
 
 #
@@ -5610,7 +5649,7 @@
 # CONFIG_PHY_CPCAP_USB is not set
 # CONFIG_PHY_MAPPHONE_MDM6600 is not set
 # CONFIG_PHY_OCELOT_SERDES is not set
-CONFIG_PHY_QCOM_USB_HS=y
+CONFIG_PHY_QCOM_USB_HS=m
 # CONFIG_PHY_QCOM_USB_HSIC is not set
 CONFIG_PHY_ROCKCHIP_DP=m
 CONFIG_PHY_ROCKCHIP_DPHY_RX0=m
@@ -5623,6 +5662,7 @@
 CONFIG_PHY_ROCKCHIP_PCIE=m
 CONFIG_PHY_ROCKCHIP_TYPEC=y
 CONFIG_PHY_ROCKCHIP_USB=m
+# CONFIG_PHY_SAMSUNG_USB2 is not set
 # CONFIG_PHY_TUSB1210 is not set
 # end of PHY Subsystem
 
@@ -5832,6 +5872,7 @@
 CONFIG_PSTORE_COMPRESS_DEFAULT="deflate"
 # CONFIG_PSTORE_CONSOLE is not set
 # CONFIG_PSTORE_PMSG is not set
+# CONFIG_PSTORE_FTRACE is not set
 # CONFIG_PSTORE_RAM is not set
 # CONFIG_PSTORE_BLK is not set
 # CONFIG_SYSV_FS is not set
@@ -6192,7 +6233,6 @@
 # CONFIG_RANDOM32_SELFTEST is not set
 CONFIG_ZLIB_INFLATE=y
 CONFIG_ZLIB_DEFLATE=y
-CONFIG_LZO_COMPRESS=y
 CONFIG_LZO_DECOMPRESS=y
 CONFIG_LZ4_DECOMPRESS=y
 CONFIG_ZSTD_DECOMPRESS=y
@@ -6214,7 +6254,6 @@
 CONFIG_DECOMPRESS_LZ4=y
 CONFIG_DECOMPRESS_ZSTD=y
 CONFIG_GENERIC_ALLOCATOR=y
-CONFIG_INTERVAL_TREE=y
 CONFIG_ASSOCIATIVE_ARRAY=y
 CONFIG_HAS_IOMEM=y
 CONFIG_HAS_IOPORT_MAP=y
@@ -6487,7 +6526,11 @@
 CONFIG_TRACING_SUPPORT=y
 CONFIG_FTRACE=y
 CONFIG_BOOTTIME_TRACING=y
-# CONFIG_FUNCTION_TRACER is not set
+CONFIG_FUNCTION_TRACER=y
+CONFIG_FUNCTION_GRAPH_TRACER=y
+CONFIG_DYNAMIC_FTRACE=y
+CONFIG_DYNAMIC_FTRACE_WITH_REGS=y
+CONFIG_FUNCTION_PROFILER=y
 # CONFIG_STACK_TRACER is not set
 CONFIG_TRACE_PREEMPT_TOGGLE=y
 CONFIG_IRQSOFF_TRACER=y
@@ -6503,21 +6546,30 @@
 # CONFIG_PROFILE_ANNOTATED_BRANCHES is not set
 # CONFIG_PROFILE_ALL_BRANCHES is not set
 # CONFIG_BLK_DEV_IO_TRACE is not set
+CONFIG_KPROBE_EVENTS=y
+CONFIG_KPROBE_EVENTS_ON_NOTRACE=y
 CONFIG_UPROBE_EVENTS=y
 CONFIG_BPF_EVENTS=y
 CONFIG_DYNAMIC_EVENTS=y
 CONFIG_PROBE_EVENTS=y
+# CONFIG_BPF_KPROBE_OVERRIDE is not set
+CONFIG_FTRACE_MCOUNT_RECORD=y
+CONFIG_FTRACE_MCOUNT_USE_PATCHABLE_FUNCTION_ENTRY=y
 CONFIG_SYNTH_EVENTS=y
 # CONFIG_HIST_TRIGGERS is not set
 CONFIG_TRACE_EVENT_INJECT=y
 # CONFIG_TRACEPOINT_BENCHMARK is not set
 # CONFIG_RING_BUFFER_BENCHMARK is not set
 # CONFIG_TRACE_EVAL_MAP_FILE is not set
+CONFIG_FTRACE_RECORD_RECURSION=y
+CONFIG_FTRACE_RECORD_RECURSION_SIZE=128
+CONFIG_RING_BUFFER_RECORD_RECURSION=y
 # CONFIG_FTRACE_STARTUP_TEST is not set
 # CONFIG_RING_BUFFER_STARTUP_TEST is not set
 # CONFIG_RING_BUFFER_VALIDATE_TIME_DELTAS is not set
 # CONFIG_PREEMPTIRQ_DELAY_TEST is not set
 # CONFIG_SYNTH_EVENT_GEN_TEST is not set
+# CONFIG_KPROBE_EVENT_GEN_TEST is not set
 # CONFIG_RV is not set
 # CONFIG_SAMPLES is not set
 CONFIG_STRICT_DEVMEM=y
@@ -6536,6 +6588,7 @@
 #
 # CONFIG_KUNIT is not set
 # CONFIG_NOTIFIER_ERROR_INJECTION is not set
+CONFIG_FUNCTION_ERROR_INJECTION=y
 # CONFIG_FAULT_INJECTION is not set
 CONFIG_ARCH_HAS_KCOV=y
 CONFIG_CC_HAS_SANCOV_TRACE_PC=y
diff --git a/frc971/rockpi/build_kernel.sh b/frc971/rockpi/build_kernel.sh
index 73c0a51..f3cadd4 100755
--- a/frc971/rockpi/build_kernel.sh
+++ b/frc971/rockpi/build_kernel.sh
@@ -39,4 +39,13 @@
 
 VERSION="$(cat linux/include/config/kernel.release)"
 
+(
+  cd ../../y2022/localizer/kernel/
+  make rockpi
+)
+
+cp ../../y2022/localizer/kernel/adis16505.ko "kernel-install/lib/modules/${VERSION}/kernel/"
+
+/sbin/depmod -b ./kernel-install ${VERSION}
+
 tar -cvf "linux-kernel-${VERSION}.tar.xz" -C kernel-install .
diff --git a/frc971/rockpi/build_rootfs.sh b/frc971/rockpi/build_rootfs.sh
index a1d0a0a..e5706fc 100755
--- a/frc971/rockpi/build_rootfs.sh
+++ b/frc971/rockpi/build_rootfs.sh
@@ -154,8 +154,9 @@
 
 target "apt-get update"
 target "apt-get -y install -t bullseye-backports systemd"
+target "apt-get -y install -t bullseye-backports bpfcc-tools"
 
-target "apt-get install -y sudo openssh-server python3 bash-completion git v4l-utils cpufrequtils pmount rsync vim-nox chrony libopencv-calib3d4.5 libopencv-contrib4.5 libopencv-core4.5 libopencv-features2d4.5 libopencv-flann4.5 libopencv-highgui4.5 libopencv-imgcodecs4.5 libopencv-imgproc4.5 libopencv-ml4.5 libopencv-objdetect4.5 libopencv-photo4.5 libopencv-shape4.5 libopencv-stitching4.5 libopencv-superres4.5 libopencv-video4.5 libopencv-videoio4.5 libopencv-videostab4.5 libopencv-viz4.5 libnice10 pmount libnice-dev feh libgstreamer1.0-0 libgstreamer-plugins-base1.0-0 libgstreamer-plugins-bad1.0-0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-nice usbutils locales"
+target "apt-get install -y sudo openssh-server python3 bash-completion git v4l-utils cpufrequtils pmount rsync vim-nox chrony libopencv-calib3d4.5 libopencv-contrib4.5 libopencv-core4.5 libopencv-features2d4.5 libopencv-flann4.5 libopencv-highgui4.5 libopencv-imgcodecs4.5 libopencv-imgproc4.5 libopencv-ml4.5 libopencv-objdetect4.5 libopencv-photo4.5 libopencv-shape4.5 libopencv-stitching4.5 libopencv-superres4.5 libopencv-video4.5 libopencv-videoio4.5 libopencv-videostab4.5 libopencv-viz4.5 libnice10 pmount libnice-dev feh libgstreamer1.0-0 libgstreamer-plugins-base1.0-0 libgstreamer-plugins-bad1.0-0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-nice usbutils locales trace-cmd"
 
 target "usermod -a -G sudo pi"
 target "usermod -a -G video pi"
@@ -175,11 +176,14 @@
 copyfile root.root 644 etc/systemd/system/grow-rootfs.service
 copyfile root.root 644 etc/sysctl.d/sctp.conf
 copyfile root.root 644 etc/systemd/logind.conf
+copyfile root.root 555 etc/bash_completion.d/aos_dump_autocomplete
+copyfile root.root 444 etc/modules-load.d/adis16505.conf
 copyfile root.root 644 etc/security/limits.d/rt.conf
 copyfile root.root 644 etc/systemd/system/frc971.service
 copyfile root.root 644 etc/systemd/system/frc971chrt.service
 copyfile root.root 644 etc/systemd/system/usb-mount@.service
 copyfile root.root 644 etc/udev/rules.d/99-usb-mount.rules
+copyfile root.root 644 etc/udev/rules.d/99-adis16505.rules
 
 target "apt-get update"
 target "apt-get -y install -t bullseye-backports systemd"
diff --git a/frc971/rockpi/contents/etc/bash_completion.d/aos_dump_autocomplete b/frc971/rockpi/contents/etc/bash_completion.d/aos_dump_autocomplete
new file mode 100644
index 0000000..1b82c46
--- /dev/null
+++ b/frc971/rockpi/contents/etc/bash_completion.d/aos_dump_autocomplete
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+if [[ -e "/home/pi/bin/aos_dump_autocomplete.sh" ]]; then
+  . /home/pi/bin/aos_dump_autocomplete.sh
+fi
diff --git a/frc971/rockpi/contents/etc/modules-load.d/adis16505.conf b/frc971/rockpi/contents/etc/modules-load.d/adis16505.conf
new file mode 100644
index 0000000..0e7b16b
--- /dev/null
+++ b/frc971/rockpi/contents/etc/modules-load.d/adis16505.conf
@@ -0,0 +1 @@
+adis16505
diff --git a/frc971/rockpi/contents/etc/systemd/network/eth0.network b/frc971/rockpi/contents/etc/systemd/network/eth0.network
index f9e7c2e..8bfaf61 100644
--- a/frc971/rockpi/contents/etc/systemd/network/eth0.network
+++ b/frc971/rockpi/contents/etc/systemd/network/eth0.network
@@ -5,3 +5,7 @@
 Address=10.9.71.20/24
 Gateway=10.9.71.13
 DNS=8.8.8.8
+
+# ipv6 adds an extra 10 seconds to boot, and we don't use it...
+# Disable all the route discovery and stuff
+LinkLocalAddressing=no
diff --git a/frc971/rockpi/contents/etc/systemd/system/frc971chrt.service b/frc971/rockpi/contents/etc/systemd/system/frc971chrt.service
index 6086fa0..1fc304e 100644
--- a/frc971/rockpi/contents/etc/systemd/system/frc971chrt.service
+++ b/frc971/rockpi/contents/etc/systemd/system/frc971chrt.service
@@ -1,9 +1,10 @@
 [Unit]
-Description=Configure IRQs
+Description=Configure tracing to be available by everyone
+After=sys-kernel-tracing.mount
 
 [Service]
 Type=oneshot
-ExecStart=/home/pi/bin/chrt.sh
+ExecStart=/root/bin/chrt.sh
 
 [Install]
 WantedBy=multi-user.target
diff --git a/frc971/rockpi/contents/etc/udev/rules.d/99-adis16505.rules b/frc971/rockpi/contents/etc/udev/rules.d/99-adis16505.rules
new file mode 100644
index 0000000..25145d2
--- /dev/null
+++ b/frc971/rockpi/contents/etc/udev/rules.d/99-adis16505.rules
@@ -0,0 +1 @@
+SUBSYSTEM=="adis16505_class", MODE="0666", GROUP="dialout"
diff --git a/frc971/rockpi/contents/root/bin/chrt.sh b/frc971/rockpi/contents/root/bin/chrt.sh
new file mode 100644
index 0000000..4a6b917
--- /dev/null
+++ b/frc971/rockpi/contents/root/bin/chrt.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+set -ex
+
+chmod a+rwx /sys/kernel/debug
+
+# Make sure /sys/kernel/tracing is reasonably accessible so --enable_ftrace
+# works.
+chmod a+w /sys/kernel/tracing/trace_marker
+echo 10000 > /sys/kernel/tracing/buffer_size_kb
+chmod a+rw /sys/kernel/tracing/tracing_on
+chmod a+rwx /sys/kernel/tracing
+
+# Make sure we never scale the CPUs down.
+# This takes time to do which we don't have...
+cat /sys/devices/system/cpu/cpufreq/policy0/scaling_max_freq > \
+  /sys/devices/system/cpu/cpufreq/policy0/scaling_min_freq
+cat /sys/devices/system/cpu/cpufreq/policy4/scaling_max_freq > \
+  /sys/devices/system/cpu/cpufreq/policy4/scaling_min_freq
diff --git a/frc971/rockpi/contents/root/bin/deploy_kernel.sh b/frc971/rockpi/contents/root/bin/deploy_kernel.sh
index 69a6297..5de42a0 100644
--- a/frc971/rockpi/contents/root/bin/deploy_kernel.sh
+++ b/frc971/rockpi/contents/root/bin/deploy_kernel.sh
@@ -3,8 +3,11 @@
 set -ex
 
 KERNEL_VERSION=6.0.8-rt14-rockpi4b
+TAR=${1-./linux-kernel-${KERNEL_VERSION}.tar.xz}
+
+KERNEL_VERSION=$(echo "${TAR}" | sed 's/.*linux-kernel-\(.*\).tar.xz/\1/')
 
 rm -rf /boot/dtbs/${KERNEL_VERSION}/
 rm -rf /lib/modules/${KERNEL_VERSION}/
-tar --owner=0 --group=0 --no-same-owner --no-same-permissions -xvf ./linux-kernel-${KERNEL_VERSION}.tar.xz -C /boot/ --strip-components=2 ./boot/
-tar --strip-components=3 -xvf linux-kernel-${KERNEL_VERSION}.tar.xz -C /lib/modules/ ./lib/modules
+tar --owner=0 --group=0 --no-same-owner --no-same-permissions -xvf ${TAR} -C /boot/ --strip-components=2 ./boot/
+tar --strip-components=3 -xvf ${TAR} -C /lib/modules/ ./lib/modules
diff --git a/frc971/vision/BUILD b/frc971/vision/BUILD
index 159c779..4a8d50b 100644
--- a/frc971/vision/BUILD
+++ b/frc971/vision/BUILD
@@ -28,6 +28,7 @@
     visibility = ["//visibility:public"],
     deps = [
         ":vision_fbs",
+        "//aos/events:epoll",
         "//aos/events:event_loop",
         "//aos/scoped:scoped_fd",
         "@com_github_google_glog//:glog",
diff --git a/frc971/vision/v4l2_reader.cc b/frc971/vision/v4l2_reader.cc
index d46e7b5..e549ae5 100644
--- a/frc971/vision/v4l2_reader.cc
+++ b/frc971/vision/v4l2_reader.cc
@@ -99,23 +99,28 @@
     EnqueueBuffer(saved_buffer_.index);
     saved_buffer_.Clear();
   }
+  ftrace_.FormatMessage("Enqueued previous buffer %d", saved_buffer_.index);
   while (true) {
     const BufferInfo previous_buffer = saved_buffer_;
     saved_buffer_ = DequeueBuffer();
+    ftrace_.FormatMessage("Dequeued %d", saved_buffer_.index);
     if (saved_buffer_) {
       // We got a new buffer. Return the previous one (if relevant) and keep
       // going.
       if (previous_buffer) {
+        ftrace_.FormatMessage("Previous %d", previous_buffer.index);
         EnqueueBuffer(previous_buffer.index);
       }
       continue;
     }
     if (!previous_buffer) {
       // There were no images to read. Return an indication of that.
+      ftrace_.FormatMessage("No images to read");
       return false;
     }
     // We didn't get a new one, but we already got one in a previous
     // iteration, which means we found an image so return it.
+    ftrace_.FormatMessage("Got saved buffer %d", saved_buffer_.index);
     saved_buffer_ = previous_buffer;
     buffers_[saved_buffer_.index].PrepareMessage(rows_, cols_, ImageSize(),
                                                  saved_buffer_.monotonic_eof);
@@ -297,9 +302,19 @@
 }
 
 RockchipV4L2Reader::RockchipV4L2Reader(aos::EventLoop *event_loop,
+                                       aos::internal::EPoll *epoll,
                                        const std::string &device_name)
-    : V4L2ReaderBase(event_loop, device_name) {
+    : V4L2ReaderBase(event_loop, device_name), epoll_(epoll) {
   StreamOn();
+  epoll_->OnReadable(fd().get(), [this]() { OnImageReady(); });
+}
+
+void RockchipV4L2Reader::OnImageReady() {
+  if (!ReadLatestImage()) {
+    return;
+  }
+
+  SendLatestImage();
 }
 
 }  // namespace vision
diff --git a/frc971/vision/v4l2_reader.h b/frc971/vision/v4l2_reader.h
index e813a00..334ad81 100644
--- a/frc971/vision/v4l2_reader.h
+++ b/frc971/vision/v4l2_reader.h
@@ -5,7 +5,9 @@
 #include <string>
 
 #include "absl/types/span.h"
+#include "aos/events/epoll.h"
 #include "aos/events/event_loop.h"
+#include "aos/ftrace.h"
 #include "aos/scoped/scoped_fd.h"
 #include "frc971/vision/vision_generated.h"
 #include "glog/logging.h"
@@ -61,6 +63,8 @@
   // H.264 frames.
   size_t ImageSize() const { return rows_ * cols_ * 2 /* bytes per pixel */; }
 
+  const aos::ScopedFD &fd() { return fd_; };
+
  private:
   static constexpr int kNumberBuffers = 4;
 
@@ -138,7 +142,13 @@
 // properly configured before this class is constructed.
 class RockchipV4L2Reader : public V4L2ReaderBase {
  public:
-  RockchipV4L2Reader(aos::EventLoop *event_loop, const std::string &device_name);
+  RockchipV4L2Reader(aos::EventLoop *event_loop, aos::internal::EPoll *epoll,
+                     const std::string &device_name);
+
+ private:
+  void OnImageReady();
+
+  aos::internal::EPoll *epoll_;
 };
 
 }  // namespace vision
diff --git a/tools/lint/run-ci.sh b/tools/lint/run-ci.sh
index 6e026ee..a05aa0f 100755
--- a/tools/lint/run-ci.sh
+++ b/tools/lint/run-ci.sh
@@ -111,11 +111,12 @@
 # All the linters that we are going to run.
 readonly -a LINTERS=(
     gofmt
-    gomod
-    update_go_repos
-    gazelle
-    tweak_gazelle_go_deps
-    clean_up_go_mirrors
+    # TODO(phil): Re-enable these. No idea what's going on.
+    #gomod
+    #update_go_repos
+    #gazelle
+    #tweak_gazelle_go_deps
+    #clean_up_go_mirrors
     rustfmt
     cargo_lockfile
     cargo_raze
diff --git a/tools/python/generate_pip_packages_in_docker.sh b/tools/python/generate_pip_packages_in_docker.sh
index 13d51b7..f651918 100755
--- a/tools/python/generate_pip_packages_in_docker.sh
+++ b/tools/python/generate_pip_packages_in_docker.sh
@@ -115,6 +115,7 @@
   echo "Repairing wheel ${wheel}"
   if ! auditwheel show "${wheel_path}"; then
     echo "Assuming ${wheel} is a non-platform wheel. Skipping."
+    cp "${wheel_path}" "${SCRIPT_DIR}"/wheelhouse/
     continue
   fi
   auditwheel repair \
diff --git a/tools/python/package_annotations.bzl b/tools/python/package_annotations.bzl
index b359d72..cff14e7 100644
--- a/tools/python/package_annotations.bzl
+++ b/tools/python/package_annotations.bzl
@@ -9,4 +9,7 @@
         data = ["@gtk_runtime//:gtk_runtime"],
         deps = ["@bazel_tools//tools/python/runfiles"],
     ),
+    "python-gflags": package_annotation(
+        deps = ["@pip_deps_six//:pkg"],
+    ),
 }
diff --git a/tools/python/pip_configure.py b/tools/python/pip_configure.py
index 4f9dff1..132cc24 100644
--- a/tools/python/pip_configure.py
+++ b/tools/python/pip_configure.py
@@ -13,10 +13,21 @@
 in BUILD files.
 """
 
+import re
 import sys
 import textwrap
 from pathlib import Path
 
+# Regex to parse the lines in a requirements file.
+# - Ignore line comments.
+# - Remove any inline comments that may or may not exist.
+# - Also remove any version specifiers. We don't use it.
+#
+# E.g:
+#  numpy==1.2.3  # needed because we like it.
+# turns into "numpy".
+REQUIREMENT_MATCH = re.compile(r"[-_.a-zA-Z0-9]+")
+
 
 def parse_requirements(requirements_path: Path) -> list[str]:
     """Parses tools/python/requirements.txt.
@@ -25,18 +36,9 @@
     depend on explicitly requested pip packages. We don't want users to depend
     on transitive dependencies of our requested pip packages.
     """
-    result = []
-    for line in requirements_path.read_text().splitlines():
-        # Ignore line comments.
-        if not line or line.startswith("#"):
-            continue
-
-        # Remove any inline comments that may or may not exist.
-        # E.g:
-        # numpy==1.2.3  # needed because we like it.
-        result.append(line.split()[0])
-
-    return result
+    lines = requirements_path.read_text().splitlines()
+    matches = map(REQUIREMENT_MATCH.match, lines)
+    return [match.group(0) for match in matches if match]
 
 
 def generate_build_files(requirements: list[str]) -> None:
diff --git a/tools/python/requirements.lock.txt b/tools/python/requirements.lock.txt
index 93ac065..7470cae 100644
--- a/tools/python/requirements.lock.txt
+++ b/tools/python/requirements.lock.txt
@@ -99,6 +99,10 @@
     --hash=sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 \
     --hash=sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343
     # via mkdocs
+glog==0.3.1 \
+    --hash=sha256:88cee83dea8bddf73db7edbf5bd697237628389ef476c0a0ecad639c606189e5 \
+    --hash=sha256:b721edef6009eabc0b4d9f2619e153d2627a7b71a3657c8ed69f02ef7c78be97
+    # via -r tools/python/requirements.txt
 idna==3.4 \
     --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
     --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
@@ -319,6 +323,7 @@
     #   osqp
     #   qdldl
     #   scipy
+    #   shapely
 opencv-python==4.6.0.66 \
     --hash=sha256:0dc82a3d8630c099d2f3ac1b1aabee164e8188db54a786abb7a4e27eba309440 \
     --hash=sha256:5af8ba35a4fcb8913ffb86e92403e9a656a4bff4a645d196987468f0f8947875 \
@@ -456,6 +461,11 @@
     # via
     #   ghp-import
     #   matplotlib
+python-gflags==3.1.2 \
+    --hash=sha256:40ae131e899ef68e9e14aa53ca063839c34f6a168afe622217b5b875492a1ee2
+    # via
+    #   -r tools/python/requirements.txt
+    #   glog
 pyyaml==6.0 \
     --hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \
     --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
@@ -552,10 +562,52 @@
     #   -r tools/python/requirements.txt
     #   osqp
     #   qdldl
+shapely==2.0.0 \
+    --hash=sha256:11f1b1231a6c04213fb1226c6968d1b1b3b369ec42d1e9655066af87631860ea \
+    --hash=sha256:13a9f978cd287e0fa95f39904a2bb36deddab490e4fab8bf43eba01b7d9eb58f \
+    --hash=sha256:17d0f89581aa15f7887052a6adf2753f9fe1c3fdbb6116653972e0d43e720e65 \
+    --hash=sha256:21ba32a6c45b7f8ab7d2d8d5cf339704e2d1dfdf3e2fb465b950a0c9bc894a4f \
+    --hash=sha256:2287d0cb592c1814e9f48065888af7ee3f13e090e6f7fa3e208b06a83fb2f6af \
+    --hash=sha256:292c22ff7806e3a25bc4324295e9204169c61a09165d4c9ee0a9784c1709c85e \
+    --hash=sha256:40c397d67ba609a163d38b649eee2b06c5f9bdc86d244a8e4cd09c6e2791cf3c \
+    --hash=sha256:44198fc188fe4b7dd39ef0fd325395d1d6ab0c29a7bbaa15663a16c362bf6f62 \
+    --hash=sha256:5477be8c11bf3109f7b804bb2d57536538b8d0a6118207f1020d71338f1a827c \
+    --hash=sha256:550f110940d79931b6a12a17de07f6b158c9586c4b121f885af11458ae5626d7 \
+    --hash=sha256:56c0e70749f8c2956493e9333375d2e2264ce25c838fc49c3a2ececbf2d3ba92 \
+    --hash=sha256:5fe8649aafe6adcb4d90f7f735f06ca8ca02a16da273d901f1dd02afc0d3618e \
+    --hash=sha256:6c71738702cf5c3fc60b3bbe869c321b053ea754f57addded540a71c78c2612e \
+    --hash=sha256:7266080d39946395ba4b31fa35b9b7695e0a4e38ccabf0c67e2936caf9f9b054 \
+    --hash=sha256:73771b3f65c2949cce0b310b9b62b8ce069407ceb497a9dd4436f9a4d059f12c \
+    --hash=sha256:73d605fcefd06ee997ba307ef363448d355f3c3e81b3f56ed332eaf6d506e1b5 \
+    --hash=sha256:7b2c41514ba985ea3772eee9b386d620784cccb7a459a270a072f3ef01fdd807 \
+    --hash=sha256:820bee508e4a0e564db22f8b55bb5e6e7f326d8d7c103639c42f5d3f378f4067 \
+    --hash=sha256:8a7ba97c97d85c1f07c57f9524c45128ef2bf8279061945d78052c78862b357f \
+    --hash=sha256:8b9f780c3b79b4a6501e0e8833b1877841b7b0e0a243e77b529fda8f1030afc2 \
+    --hash=sha256:91bbca0378eb82f0808f0e59150ac0952086f4caaab87ad8515a5e55e896c21e \
+    --hash=sha256:99420c89af78f371b96f0e2bad9afdebc6d0707d4275d157101483e4c4049fd6 \
+    --hash=sha256:a391cae931976fb6d8d15a4f4a92006358e93486454a812dde1d64184041a476 \
+    --hash=sha256:a9b6651812f2caa23e4d06bc06a2ed34450f82cb1c110c170a25b01bbb090895 \
+    --hash=sha256:b1def13ec2a74ebda2210d2fc1c53cecce5a079ec90f341101399427874507f1 \
+    --hash=sha256:b3d97f3ce6df47ca68c2d64b8c3cfa5c8ccc0fbc81ef8e15ff6004a6426e71b1 \
+    --hash=sha256:c47a61b1cd0c5b064c6d912bce7dba78c01f319f65ecccd6e61eecd21861a37a \
+    --hash=sha256:c4b99a3456e06dc55482569669ece969cdab311f2ad2a1d5622fc770f68cf3cd \
+    --hash=sha256:d28e19791c9be2ba1cb2fddefa86f73364bdf8334e88dbcd78a8e4494c0af66b \
+    --hash=sha256:d486cab823f0a978964ae97ca10564ea2b2ced93e84a2ef0b7b62cbacec9d3d2 \
+    --hash=sha256:de3722c68e49fbde8cb6859695bbb8fb9a4d48bbdf34fcf38b7994d2bd9772e2 \
+    --hash=sha256:e4ed31658fd0799eaa3569982aab1a5bc8fcf25ec196606bf137ee4fa984be88 \
+    --hash=sha256:e991ad155783cd0830b895ec8f310fde9e79a7b283776b889a751fb1e7c819fc \
+    --hash=sha256:eab24b60ae96b7375adceb1f120be818c59bd69db0f3540dc89527d8a371d253 \
+    --hash=sha256:eaea9ddee706654026a84aceb9a3156105917bab3de58fcf150343f847478202 \
+    --hash=sha256:ef98fec4a3aca6d33e3b9fdd680fe513cc7d1c6aedc65ada8a3965601d9d4bcf \
+    --hash=sha256:f69c418f2040c8593e33b1aba8f2acf890804b073b817535b5d291139d152af5 \
+    --hash=sha256:f96b24da0242791cd6042f6caf074e7a4537a66ca2d1b57d423feb98ba901295
+    # via -r tools/python/requirements.txt
 six==1.16.0 \
     --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
     --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
-    # via python-dateutil
+    # via
+    #   glog
+    #   python-dateutil
 urllib3==1.26.13 \
     --hash=sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc \
     --hash=sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8
diff --git a/tools/python/requirements.txt b/tools/python/requirements.txt
index a4cbd6a..3b3ab3a 100644
--- a/tools/python/requirements.txt
+++ b/tools/python/requirements.txt
@@ -11,4 +11,9 @@
 pygobject
 requests
 scipy
+shapely
 yapf
+
+# TODO(phil): Migrate to absl-py. These are abandoned as far as I can tell.
+python-gflags
+glog
diff --git a/tools/python/whl_overrides.json b/tools/python/whl_overrides.json
index 3d724b9..108a544 100644
--- a/tools/python/whl_overrides.json
+++ b/tools/python/whl_overrides.json
@@ -27,6 +27,10 @@
         "sha256": "8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619",
         "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/ghp_import-2.1.0-py3-none-any.whl"
     },
+    "glog==0.3.1": {
+        "sha256": "88cee83dea8bddf73db7edbf5bd697237628389ef476c0a0ecad639c606189e5",
+        "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/glog-0.3.1-py2.py3-none-any.whl"
+    },
     "idna==3.4": {
         "sha256": "90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2",
         "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/idna-3.4-py3-none-any.whl"
@@ -103,6 +107,10 @@
         "sha256": "961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9",
         "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/python_dateutil-2.8.2-py2.py3-none-any.whl"
     },
+    "python_gflags==3.1.2": {
+        "sha256": "e2bd55abd9bb6e3b32026fd6c26a81c3f49979f24162fe73dc48da4fc306e74b",
+        "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/python_gflags-3.1.2-py3-none-any.whl"
+    },
     "pyyaml==6.0": {
         "sha256": "40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0",
         "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl"
@@ -123,6 +131,10 @@
         "sha256": "c68db6b290cbd4049012990d7fe71a2abd9ffbe82c0056ebe0f01df8be5436b0",
         "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/scipy-1.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
     },
+    "shapely==2.0.0": {
+        "sha256": "91bbca0378eb82f0808f0e59150ac0952086f4caaab87ad8515a5e55e896c21e",
+        "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/shapely-2.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
+    },
     "six==1.16.0": {
         "sha256": "8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254",
         "url": "https://software.frc971.org/Build-Dependencies/wheelhouse/six-1.16.0-py2.py3-none-any.whl"
diff --git a/y2020/vision/sift/fast_gaussian.bzl b/y2020/vision/sift/fast_gaussian.bzl
index 1560f6a..af87245 100644
--- a/y2020/vision/sift/fast_gaussian.bzl
+++ b/y2020/vision/sift/fast_gaussian.bzl
@@ -23,7 +23,7 @@
     objects = [f + ".o" for f in files] + [
         "fast_gaussian_runtime.o",
     ]
-    htmls = [f + ".html" for f in files]
+    htmls = [f + ".stmt.html" for f in files]
 
     native.genrule(
         name = "generate_fast_gaussian",
diff --git a/y2020/vision/sift/fast_gaussian_halide_generator.sh b/y2020/vision/sift/fast_gaussian_halide_generator.sh
index cec9995..e305f5b 100755
--- a/y2020/vision/sift/fast_gaussian_halide_generator.sh
+++ b/y2020/vision/sift/fast_gaussian_halide_generator.sh
@@ -58,6 +58,7 @@
   "${HALIDE}/lib/libHalide.a" \
   -lstdc++ -lpthread -ldl -lm -lz \
   "${SOURCE}" \
-  "${HALIDE}/tools/GenGen.cpp" \
+  "${HALIDE}/share/Halide/tools/GenGen.cpp" \
+  --std=gnu++17 \
   -ggdb3 \
   -o "${BINARY}"
diff --git a/y2022/localizer/kernel/Makefile b/y2022/localizer/kernel/Makefile
index ad4b9b0..184f8a3 100644
--- a/y2022/localizer/kernel/Makefile
+++ b/y2022/localizer/kernel/Makefile
@@ -2,6 +2,9 @@
  
 PWD := $(CURDIR) 
  
+rockpi:
+	ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make -C ../../../frc971/rockpi/linux M=$(PWD) modules
+
 all: 
 	ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make -C ../../../../linux M=$(PWD) modules 
 
diff --git a/y2022/localizer/kernel/adis16505.c b/y2022/localizer/kernel/adis16505.c
index a91006e..963ca4a 100644
--- a/y2022/localizer/kernel/adis16505.c
+++ b/y2022/localizer/kernel/adis16505.c
@@ -49,7 +49,7 @@
   char tx_buff[128];
   char rx_buff[128];
 
-  int count;
+  atomic_t count;
 
   spinlock_t lock;
 
@@ -62,21 +62,18 @@
 static int adis16505_dev_open(struct inode *in, struct file *f) {
   struct adis16505_state *ts =
       container_of(in->i_cdev, struct adis16505_state, handle_cdev);
-  int count;
+  int count = 0;
 
   f->private_data = ts;
+  count = atomic_cmpxchg(&ts->count, 0, 1);
 
-  spin_lock(&ts->lock);
-  count = ts->count;
-  if (count == 0) {
-    ++(ts->count);
-  }
-  spin_unlock(&ts->lock);
-
-  printk("open %p, count %d\n", ts, count);
   if (count > 0) {
     return -EBUSY;
   }
+
+  // Enable the IRQ after we've declared the device open.
+  enable_irq(ts->spi->irq);
+
   return 0;
 }
 
@@ -84,10 +81,13 @@
   struct adis16505_state *ts;
   ts = container_of(in->i_cdev, struct adis16505_state, handle_cdev);
 
-  printk("release %p\n", ts);
-  spin_lock(&ts->lock);
-  --(ts->count);
-  spin_unlock(&ts->lock);
+  // Disable before declaring ourselves closed so we don't fire the IRQ when we
+  // are disabled, or let someone else open it up again before we disable it.
+  disable_irq(ts->spi->irq);
+
+  if (atomic_cmpxchg(&ts->count, 1, 0) != 1) {
+    BUG_ON(true);
+  }
 
   return 0;
 }
@@ -230,7 +230,7 @@
 
   spin_lock_init(&ts->lock);
   spin_lock_init(&ts->fifo_read_lock);
-  ts->count = 0;
+  atomic_set(&ts->count, 0);
   INIT_KFIFO(ts->fifo);
   init_waitqueue_head(&ts->wq);
 
@@ -241,11 +241,20 @@
   err = request_threaded_irq(spi->irq, NULL, adis16505_irq, IRQF_ONESHOT,
                              spi->dev.driver->name, ts);
 
-  if (!ts) {
+  if (err < 0) {
     dev_dbg(&spi->dev, "irq %d busy?\n", spi->irq);
     goto err_free_mem;
   }
 
+  // Immediately disable the IRQ.  Opening the device will enable it, so this
+  // lets us leave the driver probed all the time and only use it when userspace
+  // asks.
+  //
+  // Note: the IRQ will fire probably once before we get it disabled...  It
+  // might initiate a transfer from a device which isn't connected, which should
+  // just return 0 for everything.  This isn't actually a huge concern.
+  disable_irq(spi->irq);
+
   err = alloc_chrdev_region(&ts->character_device, 0, 1, "adis16505");
   if (err < 0) {
     dev_dbg(&spi->dev, "alloc_chrdev_region error %i", err);
@@ -295,7 +304,7 @@
   return err;
 }
 
-static int adis16505_remove(struct spi_device *spi) {
+static void adis16505_remove(struct spi_device *spi) {
   struct adis16505_state *ts = spi_get_drvdata(spi);
 
   device_destroy(ts->device_class, ts->character_device);
@@ -309,7 +318,6 @@
   kfree(ts);
 
   dev_dbg(&spi->dev, "unregistered adis16505\n");
-  return 0;
 }
 
 static struct spi_driver adis16505_driver = {
diff --git a/y2023/vision/BUILD b/y2023/vision/BUILD
new file mode 100644
index 0000000..2f93b16
--- /dev/null
+++ b/y2023/vision/BUILD
@@ -0,0 +1,29 @@
+cc_binary(
+    name = "camera_reader",
+    srcs = [
+        "camera_reader.cc",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//y2023:__subpackages__"],
+    deps = [
+        "//aos:init",
+        "//aos/events:shm_event_loop",
+        "//frc971/vision:media_device",
+        "//frc971/vision:v4l2_reader",
+    ],
+)
+
+cc_binary(
+    name = "viewer",
+    srcs = [
+        "viewer.cc",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//y2023:__subpackages__"],
+    deps = [
+        "//aos:init",
+        "//aos/events:shm_event_loop",
+        "//frc971/vision:vision_fbs",
+        "//third_party:opencv",
+    ],
+)
diff --git a/y2023/vision/camera_reader.cc b/y2023/vision/camera_reader.cc
new file mode 100644
index 0000000..0e86c32
--- /dev/null
+++ b/y2023/vision/camera_reader.cc
@@ -0,0 +1,108 @@
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_split.h"
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "frc971/vision/media_device.h"
+#include "frc971/vision/v4l2_reader.h"
+
+DEFINE_string(config, "aos_config.json", "Path to the config file to use.");
+
+namespace y2022 {
+namespace vision {
+namespace {
+
+using namespace frc971::vision;
+
+void CameraReaderMain() {
+  std::optional<MediaDevice> media_device = FindMediaDevice("platform:rkisp1");
+
+  if (VLOG_IS_ON(1)) {
+    media_device->Log();
+  }
+
+  media_device->Reset();
+
+  media_device->Enable(
+      media_device->FindLink("ov5647 4-0036", 0, "rkisp1_csi", 0));
+  media_device->Enable(
+      media_device->FindLink("rkisp1_csi", 1, "rkisp1_isp", 0));
+  media_device->Enable(
+      media_device->FindLink("rkisp1_isp", 2, "rkisp1_resizer_selfpath", 0));
+  media_device->Enable(
+      media_device->FindLink("rkisp1_isp", 2, "rkisp1_resizer_mainpath", 0));
+
+  media_device->FindEntity("ov5647 4-0036")
+      ->pads()[0]
+      ->SetSubdevFormat(1296, 972, MEDIA_BUS_FMT_SBGGR10_1X10);
+
+  Entity *rkisp1_csi = media_device->FindEntity("rkisp1_csi");
+  rkisp1_csi->pads()[0]->SetSubdevFormat(1296, 972, MEDIA_BUS_FMT_SBGGR10_1X10);
+  rkisp1_csi->pads()[1]->SetSubdevFormat(1296, 972, MEDIA_BUS_FMT_SBGGR10_1X10);
+
+  // TODO(austin): Should we set this on the link?
+  // TODO(austin): Need to update crop too.
+  Entity *rkisp1_isp = media_device->FindEntity("rkisp1_isp");
+  rkisp1_isp->pads(0)->SetSubdevCrop(1296, 972);
+  rkisp1_isp->pads(0)->SetSubdevFormat(1296, 972, MEDIA_BUS_FMT_SBGGR10_1X10);
+
+  rkisp1_isp->pads(2)->SetSubdevCrop(1296, 972);
+  rkisp1_isp->pads(2)->SetSubdevFormat(1296, 972, MEDIA_BUS_FMT_YUYV8_2X8);
+
+  Entity *rkisp1_resizer_selfpath =
+      media_device->FindEntity("rkisp1_resizer_selfpath");
+  rkisp1_resizer_selfpath->pads(0)->SetSubdevFormat(1296, 972,
+                                                    MEDIA_BUS_FMT_YUYV8_2X8);
+  rkisp1_resizer_selfpath->pads(1)->SetSubdevFormat(1296, 972,
+                                                    MEDIA_BUS_FMT_YUYV8_2X8);
+  rkisp1_resizer_selfpath->pads(0)->SetSubdevCrop(1296, 972);
+
+  Entity *rkisp1_resizer_mainpath =
+      media_device->FindEntity("rkisp1_resizer_mainpath");
+  rkisp1_resizer_mainpath->pads(0)->SetSubdevFormat(1296, 972,
+                                                    MEDIA_BUS_FMT_YUYV8_2X8);
+  rkisp1_resizer_mainpath->pads(1)->SetSubdevFormat(1296 / 2, 972 / 2,
+                                                    MEDIA_BUS_FMT_YUYV8_2X8);
+  rkisp1_resizer_mainpath->pads(0)->SetSubdevCrop(1296 / 2, 972 / 2);
+
+  Entity *rkisp1_mainpath = media_device->FindEntity("rkisp1_mainpath");
+  rkisp1_mainpath->SetFormat(1296 / 2, 972 / 2, V4L2_PIX_FMT_YUV422P);
+
+  Entity *rkisp1_selfpath = media_device->FindEntity("rkisp1_selfpath");
+  rkisp1_selfpath->SetFormat(1296, 972, V4L2_PIX_FMT_YUYV);
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(FLAGS_config);
+
+  aos::ShmEventLoop event_loop(&config.message());
+
+  event_loop.SetRuntimeRealtimePriority(55);
+
+  RockchipV4L2Reader v4l2_reader(&event_loop, event_loop.epoll(),
+                                 rkisp1_selfpath->device());
+
+  // TODO(austin): Figure out exposure and stuff.
+  /*
+  const uint32_t exposure =
+      (FLAGS_use_outdoors ? FLAGS_outdoors_exposure : FLAGS_exposure);
+  if (exposure > 0) {
+    LOG(INFO) << "Setting camera to Manual Exposure mode with exposure = "
+              << exposure << " or " << static_cast<double>(exposure) / 10.0
+              << " ms";
+    v4l2_reader.SetExposure(exposure);
+  } else {
+    LOG(INFO) << "Setting camera to use Auto Exposure";
+    v4l2_reader.UseAutoExposure();
+  }
+  */
+
+  event_loop.Run();
+}
+
+}  // namespace
+}  // namespace vision
+}  // namespace y2022
+
+int main(int argc, char **argv) {
+  aos::InitGoogle(&argc, &argv);
+  y2022::vision::CameraReaderMain();
+}
diff --git a/y2023/vision/viewer.cc b/y2023/vision/viewer.cc
new file mode 100644
index 0000000..990ad7a
--- /dev/null
+++ b/y2023/vision/viewer.cc
@@ -0,0 +1,88 @@
+#include <opencv2/highgui/highgui.hpp>
+#include <opencv2/imgproc.hpp>
+
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "aos/time/time.h"
+#include "frc971/vision/vision_generated.h"
+
+DEFINE_string(config, "aos_config.json", "Path to the config file to use.");
+DEFINE_string(channel, "/camera", "Channel name for the image.");
+
+DEFINE_string(capture, "",
+              "If set, capture a single image and save it to this filename.");
+
+namespace frc971 {
+namespace vision {
+namespace {
+
+aos::Fetcher<CameraImage> image_fetcher;
+bool DisplayLoop() {
+  const CameraImage *image;
+
+  // Read next image
+  if (!image_fetcher.Fetch()) {
+    VLOG(2) << "Couldn't fetch next image";
+    return true;
+  }
+
+  image = image_fetcher.get();
+  CHECK(image != nullptr) << "Couldn't read image";
+
+  // Create color image:
+  cv::Mat image_color_mat(cv::Size(image->cols(), image->rows()), CV_8UC2,
+                          (void *)image->data()->data());
+  cv::Mat bgr_image(cv::Size(image->cols(), image->rows()), CV_8UC3);
+  cv::cvtColor(image_color_mat, bgr_image, cv::COLOR_YUV2BGR_YUYV);
+
+  if (!FLAGS_capture.empty()) {
+    cv::imwrite(FLAGS_capture, bgr_image);
+    return false;
+  }
+
+  cv::imshow("Display", bgr_image);
+  int keystroke = cv::waitKey(1);
+  if ((keystroke & 0xFF) == static_cast<int>('c')) {
+    // Convert again, to get clean image
+    cv::cvtColor(image_color_mat, bgr_image, cv::COLOR_YUV2BGR_YUYV);
+    std::stringstream name;
+    name << "capture-" << aos::realtime_clock::now() << ".png";
+    cv::imwrite(name.str(), bgr_image);
+    LOG(INFO) << "Saved image file: " << name.str();
+  } else if ((keystroke & 0xFF) == static_cast<int>('q')) {
+    return false;
+  }
+  return true;
+}
+
+void ViewerMain() {
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(FLAGS_config);
+
+  aos::ShmEventLoop event_loop(&config.message());
+
+  image_fetcher = event_loop.MakeFetcher<CameraImage>(FLAGS_channel);
+
+  // Run the display loop
+  event_loop.AddPhasedLoop(
+      [&event_loop](int) {
+        if (!DisplayLoop()) {
+          LOG(INFO) << "Calling event_loop Exit";
+          event_loop.Exit();
+        };
+      },
+      ::std::chrono::milliseconds(100));
+
+  event_loop.Run();
+
+  image_fetcher = aos::Fetcher<CameraImage>();
+}
+
+}  // namespace
+}  // namespace vision
+}  // namespace frc971
+
+int main(int argc, char **argv) {
+  aos::InitGoogle(&argc, &argv);
+  frc971::vision::ViewerMain();
+}