manager.py broadcast process states
diff --git a/cereal b/cereal
index ab79999e5dc2de..46f56a29771e9a 160000
--- a/cereal
+++ b/cereal
@@ -1 +1 @@
-Subproject commit ab79999e5dc2de25c3eb0c9acbed47c9e81f5d79
+Subproject commit 46f56a29771e9a1d47c417d722e6f0e2fa33a01c
diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py
index cc557644105365..03d77dc6c6c18d 100755
--- a/selfdrive/controls/controlsd.py
+++ b/selfdrive/controls/controlsd.py
@@ -29,6 +29,7 @@
 
 SIMULATION = "SIMULATION" in os.environ
 NOSENSOR = "NOSENSOR" in os.environ
+IGNORE_PROCESSES = set(["rtshield", "uploader", "deleter", "loggerd", "logmessaged", "tombstoned", "logcatd", "proclogd", "clocksd", "gpsd", "updated"])
 
 ThermalStatus = log.ThermalData.ThermalStatus
 State = log.ControlsState.OpenpilotState
@@ -52,10 +53,10 @@ def __init__(self, sm=None, pm=None, can_sock=None):
 
     self.sm = sm
     if self.sm is None:
-      ignore = ['ubloxRaw', 'frontFrame'] if SIMULATION else None
+      ignore = ['ubloxRaw', 'frontFrame', 'managerState'] if SIMULATION else None
       self.sm = messaging.SubMaster(['thermal', 'health', 'modelV2', 'liveCalibration', 'ubloxRaw',
                                      'dMonitoringState', 'plan', 'pathPlan', 'liveLocationKalman',
-                                     'frame', 'frontFrame'], ignore_alive=ignore)
+                                     'frame', 'frontFrame', 'managerState'], ignore_alive=ignore)
 
     self.can_sock = can_sock
     if can_sock is None:
@@ -166,6 +167,11 @@ def update_events(self, CS):
     if self.sm['thermal'].memUsedPercent > 90:
       self.events.add(EventName.lowMemory)
 
+    # Check if all manager processes are running
+    not_running = set(p.name for p in self.sm['managerState'].processes if not p.running)
+    if self.sm.rcv_frame['managerState'] and (not_running - IGNORE_PROCESSES):
+      self.events.add(EventName.processNotRunning)
+
     # Alert if fan isn't spinning for 5 seconds
     if self.sm['health'].hwType in [HwType.uno, HwType.dos]:
       if self.sm['health'].fanSpeedRpm == 0 and self.sm['thermal'].fanSpeed > 50:
diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py
index 2d6c0be68fdaf3..c16804aa822667 100644
--- a/selfdrive/controls/lib/events.py
+++ b/selfdrive/controls/lib/events.py
@@ -615,6 +615,11 @@ def wrong_car_mode_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: boo
                               audible_alert=AudibleAlert.chimeDisengage),
   },
 
+  EventName.processNotRunning: {
+    ET.NO_ENTRY: NoEntryAlert("System Malfunction: Reboot Your Device",
+                              audible_alert=AudibleAlert.chimeDisengage),
+  },
+
   EventName.radarCommIssue: {
     ET.SOFT_DISABLE: SoftDisableAlert("Radar Communication Issue"),
     ET.NO_ENTRY: NoEntryAlert("Radar Communication Issue",
diff --git a/selfdrive/manager.py b/selfdrive/manager.py
index ab8ed37287953c..59eb53ec18ad04 100755
--- a/selfdrive/manager.py
+++ b/selfdrive/manager.py
@@ -143,6 +143,7 @@ def build():
   build()
 
 import cereal.messaging as messaging
+from cereal import log
 
 from common.params import Params
 from selfdrive.registration import register
@@ -165,7 +166,6 @@ def build():
   "tombstoned": "selfdrive.tombstoned",
   "logcatd": ("selfdrive/logcatd", ["./logcatd"]),
   "proclogd": ("selfdrive/proclogd", ["./proclogd"]),
-  "boardd": ("selfdrive/boardd", ["./boardd"]),   # not used directly
   "pandad": "selfdrive.pandad",
   "ui": ("selfdrive/ui", ["./ui"]),
   "calibrationd": "selfdrive.locationd.calibrationd",
@@ -462,6 +462,7 @@ def manager_thread():
   logger_dead = False
   params = Params()
   thermal_sock = messaging.sub_sock('thermal')
+  pm = messaging.PubMaster(['managerState'])
 
   while 1:
     msg = messaging.recv_sock(thermal_sock, wait=True)
@@ -501,6 +502,20 @@ def manager_thread():
     running_list = ["%s%s\u001b[0m" % ("\u001b[32m" if running[p].is_alive() else "\u001b[31m", p) for p in running]
     cloudlog.debug(' '.join(running_list))
 
+    # send managerState
+    states = []
+    for p in managed_processes:
+      state = log.ManagerState.ProcessState.new_message()
+      state.name = p
+      if p in running:
+        state.running = running[p].is_alive()
+        state.pid = running[p].pid
+        state.exitCode = running[p].exitcode or 0
+      states.append(state)
+    msg = messaging.new_message('managerState')
+    msg.managerState.processes = states
+    pm.send('managerState', msg)
+
     # Exit main loop when uninstall is needed
     if params.get("DoUninstall", encoding='utf8') == "1":
       break
diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py
index 316467d247d834..b85fdc538c59a6 100755
--- a/selfdrive/test/process_replay/process_replay.py
+++ b/selfdrive/test/process_replay/process_replay.py
@@ -222,7 +222,7 @@ def ublox_rcv_callback(msg):
     pub_sub={
       "can": ["controlsState", "carState", "carControl", "sendcan", "carEvents", "carParams"],
       "thermal": [], "health": [], "liveCalibration": [], "dMonitoringState": [], "plan": [], "pathPlan": [], "gpsLocation": [], "liveLocationKalman": [],
-      "modelV2": [], "frontFrame": [], "frame": [], "ubloxRaw": [],
+      "modelV2": [], "frontFrame": [], "frame": [], "ubloxRaw": [], "managerState": [],
     },
     ignore=["logMonoTime", "valid", "controlsState.startMonoTime", "controlsState.cumLagMs"],
     init_callback=fingerprint,

<!-- Please copy and paste the relevant template -->

<!--- ***** Template: Car bug fix *****

**Description** [](A description of the bug and the fix. Also link any relevant issues.)

**Verification** [](Explain how you tested this bug fix.)

**Route**
Route: [a route with the bug fix]

-->

<!--- ***** Template: Bug fix *****

**Description** [](A description of the bug and the fix. Also link any relevant issues.)

**Verification** [](Explain how you tested this bug fix.)

-->

<!--- ***** Template: Car port *****

**Checklist**
- [ ] added to README
- [ ] test route added to [test_car_models](../../selfdrive/test/test_car_models.py)
- [ ] route with openpilot:
- [ ] route with stock system:

-->

<!--- ***** Template: Refactor *****

**Description** [](A description of the refactor, including the goals it accomplishes.)

**Verification** [](Explain how you tested the refactor for regressions.)

-->
