diff --git a/MAINTAINERS b/MAINTAINERS
index 3a003f310574..5bb467c0b36f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12876,6 +12876,14 @@ L:	platform-driver-x86@vger.kernel.org
 S:	Maintained
 F:	drivers/platform/x86/panasonic-laptop.c
 
+PANIC EVENTS
+M:	Luis Chamberlain <mcgrof@kernel.org>
+L:	linux-kernel@vger.kernel.org
+S:	Maintained
+F:	include/linux/panic_events.h
+F:	include/uapi/linux/panic_events.h
+F:	kernel/panic_events.c
+
 PARALLAX PING IIO SENSOR DRIVER
 M:	Andreas Klinger <ak@it-klinger.de>
 L:	linux-iio@vger.kernel.org
diff --git a/include/linux/panic_events.h b/include/linux/panic_events.h
new file mode 100644
index 000000000000..8e7e331ecaaf
--- /dev/null
+++ b/include/linux/panic_events.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+#ifndef _LINUX_PANIC_EVENTS_H
+
+#include <linux/module.h>
+#include <uapi/linux/panic_events.h>
+
+#ifdef CONFIG_PANIC_EVENTS
+
+void panic_uevent(enum panic_uevent event);
+void panic_uevent_taint(unsigned int flag, struct module *mod);
+
+#else
+static inline void panic_events_init(void)
+{
+}
+
+static inline panic_uevent(enum panic_uevent event)
+{
+}
+
+static inline void panic_uevent_taint(unsigned int flag, struct module *mod)
+{
+}
+#endif
+
+#endif /* _LINUX_PANIC_EVENTS_H */
diff --git a/include/uapi/linux/panic_events.h b/include/uapi/linux/panic_events.h
new file mode 100644
index 000000000000..4f409597be52
--- /dev/null
+++ b/include/uapi/linux/panic_events.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+#ifndef _UAPI_PANIC_EVENTS_H
+#define _UAPI_PANIC_EVENTS_H
+
+/**
+ * enum panic_uevent - panic uevents
+ *
+ * @PANIC_LOCKDEP_DISABLED: lockdep has been disabled
+ * @PANIC_TAINT: lockdep has been disabled
+ */
+enum panic_uevent {
+	PANIC_LOCKDEP_DISABLED,
+	PANIC_TAINT,
+	__PANIC_MAX, /* non-ABI */
+};
+
+#endif /* _UAPI_PANIC_EVENTS_H */
diff --git a/init/main.c b/init/main.c
index 0ead83e86b5a..2bf33b5ec7b4 100644
--- a/init/main.c
+++ b/init/main.c
@@ -96,6 +96,7 @@
 #include <linux/jump_label.h>
 #include <linux/mem_encrypt.h>
 #include <linux/kcsan.h>
+#include <linux/panic_events.h>
 
 #include <asm/io.h>
 #include <asm/bugs.h>
diff --git a/kernel/Makefile b/kernel/Makefile
index 0bd4ed7ca157..0c1794818579 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -12,6 +12,7 @@ obj-y     = fork.o exec_domain.o panic.o \
 	    notifier.o ksysfs.o cred.o reboot.o \
 	    async.o range.o smpboot.o ucount.o
 
+obj-$(CONFIG_PANIC_EVENTS) += panic_events.o
 obj-$(CONFIG_MODULES) += kmod.o
 obj-$(CONFIG_MULTIUSER) += groups.o
 
diff --git a/kernel/module.c b/kernel/module.c
index 128bfc3e7ada..9b85d58441a2 100644
--- a/kernel/module.c
+++ b/kernel/module.c
@@ -56,6 +56,7 @@
 #include <linux/bsearch.h>
 #include <linux/dynamic_debug.h>
 #include <linux/audit.h>
+#include <linux/panic_events.h>
 #include <uapi/linux/module.h>
 #include "module-internal.h"
 
@@ -330,6 +331,7 @@ static inline void add_taint_module(struct module *mod, unsigned flag,
 {
 	add_taint(flag, lockdep_ok);
 	set_bit(flag, &mod->taints);
+	panic_uevent_taint(flag, mod);
 }
 
 /*
diff --git a/kernel/panic.c b/kernel/panic.c
index e2157ca387c8..48e9e2efa5bb 100644
--- a/kernel/panic.c
+++ b/kernel/panic.c
@@ -30,6 +30,7 @@
 #include <linux/console.h>
 #include <linux/bug.h>
 #include <linux/ratelimit.h>
+#include <linux/panic_events.h>
 #include <linux/debugfs.h>
 #include <asm/sections.h>
 
@@ -440,8 +441,10 @@ unsigned long get_taint(void)
  */
 void add_taint(unsigned flag, enum lockdep_ok lockdep_ok)
 {
-	if (lockdep_ok == LOCKDEP_NOW_UNRELIABLE && __debug_locks_off())
+	if (lockdep_ok == LOCKDEP_NOW_UNRELIABLE && __debug_locks_off()) {
 		pr_warn("Disabling lock debugging due to kernel taint\n");
+		panic_uevent(PANIC_LOCKDEP_DISABLED);
+	}
 
 	set_bit(flag, &tainted_mask);
 
@@ -449,6 +452,8 @@ void add_taint(unsigned flag, enum lockdep_ok lockdep_ok)
 		panic_on_taint = 0;
 		panic("panic_on_taint set ...");
 	}
+
+	panic_uevent_taint(flag, NULL);
 }
 EXPORT_SYMBOL(add_taint);
 
diff --git a/kernel/panic_events.c b/kernel/panic_events.c
new file mode 100644
index 000000000000..5a53a1b1fd9a
--- /dev/null
+++ b/kernel/panic_events.c
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/panic_events.h>
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+/**
+ *  DOC: Panic events
+ *
+ *  Panic events are sent to userspace to inform userspace that something
+ *  critical has happened to the kernel. These events can happen in any
+ *  context, and so to send these events to userspace we preallocate memory
+ *  needed during initialization as needed for operation. The events are
+ *  queued and later dispatched. The uevents sent are best effort, if we are
+ *  short of memory kobject_uevent_env() can fail.
+ */
+
+/* The max amount of lines on a uevent we support */
+#define PANIC_UEVENT_MAX_LINES	8
+
+/* We assume each possible CPU can trigger these events */
+#define PANIC_MAX_EVENTS_PER_CPU 4
+
+/* The max number of concurrent uvents we support, otherwise we drop events */
+#define PANIC_NUM_CACHE_EVENTS (num_possible_cpus() * PANIC_MAX_EVENTS_PER_CPU)
+
+static LIST_HEAD(panic_free_list);
+static spinlock_t free_lock;
+
+static LIST_HEAD(panic_pend_list);
+static spinlock_t pend_lock;
+
+struct panic_event {
+	enum panic_uevent uevent;
+	char module_name[MODULE_NAME_LEN];
+	enum system_states sys_state;
+	unsigned int flag;
+	struct list_head list;
+};
+
+static struct panic_event *panic_events;
+
+static struct kset *panic_kset;
+static struct kobj_type empty_ktype;
+static struct kobject *panic_events_kobj;
+static struct kobject _panic_events_kobj;
+
+static void panic_process_events(struct work_struct *work);
+static DECLARE_WORK(panic_events_work, panic_process_events);
+
+static char (*panic_envp)[PATH_MAX];
+/* Protects panic_envp */
+static DEFINE_MUTEX(panic_mutex);
+
+struct panic_event *get_free_panic_event(void)
+{
+	struct panic_event *event = NULL;
+
+	spin_lock(&free_lock);
+	if (list_empty(&panic_free_list)) {
+		pr_warn_once("Not enough free panic pool events, we need to bump PANIC_NUM_CACHE_EVENTS, please report this\n");
+		goto out;
+	}
+	event = list_first_entry(&panic_free_list, struct panic_event, list);
+	list_del_init(&event->list);
+
+out:
+	spin_unlock(&free_lock);
+
+	return event;
+}
+
+static void queue_panic_event(struct panic_event *event)
+{
+	spin_lock(&pend_lock);
+	list_add_tail(&event->list, &panic_pend_list);
+	spin_unlock(&pend_lock);
+}
+
+struct panic_event *get_pend_panic_event(void)
+{
+	struct panic_event *event = NULL;
+
+	spin_lock(&pend_lock);
+	if (list_empty(&panic_pend_list))
+		goto out;
+
+	event = list_first_entry(&panic_pend_list, struct panic_event, list);
+	list_del_init(&event->list);
+
+out:
+	spin_unlock(&pend_lock);
+
+	return event;
+}
+
+static void panic_send_event(struct panic_event *event)
+{
+	unsigned int idx = 0, i;
+	bool pending = false;
+	char *envp[PANIC_UEVENT_MAX_LINES];
+	int r;
+
+	mutex_lock(&panic_mutex);
+	memset(panic_envp, 0, PATH_MAX * PANIC_UEVENT_MAX_LINES);
+	snprintf(panic_envp[idx++], PATH_MAX, "SYSTEM_STATE=%d",
+		 event->sys_state);
+	snprintf(panic_envp[idx++], PATH_MAX, "EVENT=%d", event->uevent);
+
+	if (event->uevent == PANIC_LOCKDEP_DISABLED)
+		goto out_send;
+
+	/*
+	 * add_taint_module() will trigger two uevents, one for the kernel,
+	 * and another for the module, if the module was not built-in.
+	 */
+	if (event->uevent == PANIC_TAINT) {
+		snprintf(panic_envp[idx++], PATH_MAX, "TAINT=%d", event->flag);
+		if (strcmp(event->module_name, "") != 0)
+			snprintf(panic_envp[idx++], PATH_MAX, "MODULE_NAME=%s",
+				 event->module_name);
+	}
+
+out_send:
+	for (i = 0; i < idx; i++)
+		envp[i] = panic_envp[i];
+	envp[idx] = NULL;
+
+	r = kobject_uevent_env(panic_events_kobj, KOBJ_CHANGE, envp);
+	if (!r)
+		pr_debug("failed to sent uevent: %d\n", event->uevent);
+	mutex_unlock(&panic_mutex);
+
+	memset(event, 0, sizeof(struct panic_event));
+
+	spin_lock(&free_lock);
+	list_add_tail(&event->list, &panic_free_list);
+	spin_unlock(&free_lock);
+
+	spin_lock(&pend_lock);
+	if (!list_empty(&panic_pend_list))
+		pending = true;
+	spin_unlock(&pend_lock);
+
+	if (pending)
+		schedule_work(&panic_events_work);
+}
+
+static void panic_process_events(struct work_struct *work)
+{
+	struct panic_event *event;
+	bool pending = false;
+
+	event = get_pend_panic_event();
+	if (!event)
+		goto out;
+
+	panic_send_event(event);
+
+out:
+	spin_lock(&pend_lock);
+	if (!list_empty(&panic_pend_list))
+		pending = true;
+	spin_unlock(&pend_lock);
+
+	if (pending)
+		schedule_work(&panic_events_work);
+}
+
+/* For simple panic uvents which only need an event type specified */
+void panic_uevent(enum panic_uevent uevent)
+{
+	struct panic_event *event;
+
+	if (!panic_events_kobj)
+		return;
+
+	event = get_free_panic_event();
+	if (!event)
+		return;
+
+	event->uevent = uevent;
+	event->sys_state = system_state;
+
+	queue_panic_event(event);
+	schedule_work(&panic_events_work);
+}
+
+void panic_uevent_taint(unsigned int flag, struct module *mod)
+{
+	struct panic_event *event;
+
+	if (!panic_events_kobj)
+		return;
+
+	event = get_free_panic_event();
+	if (!event)
+		return;
+
+	event->uevent = PANIC_TAINT;
+	event->sys_state = system_state;
+	event->flag = flag;
+
+	if (mod)
+		strncpy(event->module_name, module_name(mod), MODULE_NAME_LEN);
+
+	queue_panic_event(event);
+	schedule_work(&panic_events_work);
+}
+
+static __init void panic_events_init(void)
+{
+	struct panic_event *event;
+	char *envp[2];
+	unsigned int i;
+	size_t used;
+	int r;
+
+	spin_lock_init(&free_lock);
+	spin_lock_init(&pend_lock);
+
+	mutex_lock(&panic_mutex);
+
+	panic_envp = kzalloc(PATH_MAX * PANIC_UEVENT_MAX_LINES, GFP_KERNEL);
+	if (!panic_envp)
+		goto out_unlock;
+
+	panic_events = kzalloc(sizeof(struct panic_event) *
+			       PANIC_NUM_CACHE_EVENTS, GFP_KERNEL);
+	if (!panic_events)
+		goto out_env;
+
+	for (i = 0; i < PANIC_NUM_CACHE_EVENTS; i++) {
+		event = &panic_events[i];
+		list_add_tail(&event->list, &panic_free_list);
+	}
+
+	snprintf(panic_envp[0], PATH_MAX, "MAX_EVENT_SUPPORTED=%d",
+		 __PANIC_MAX-1);
+
+	envp[0] = panic_envp[0];
+	envp[1] = NULL;
+
+	panic_kset = kset_create_and_add("panic", NULL, kernel_kobj);
+	if (!panic_kset)
+		goto out_events;
+
+	_panic_events_kobj.kset = panic_kset;
+
+	r = kobject_init_and_add(&_panic_events_kobj, &empty_ktype,
+				 NULL, "events");
+	if (r) {
+		pr_warn("Could not add panic events kobject\n");
+		goto out_kset;
+	}
+
+	/* Without this set this infrastructure is ignored */
+	panic_events_kobj = &_panic_events_kobj;
+
+	/* Inform userspace which events we'll send uevents for */
+	r = kobject_uevent_env(panic_events_kobj, KOBJ_ADD, envp);
+	if (r != 0)
+		pr_debug("failed to send first event\n");
+
+	mutex_unlock(&panic_mutex);
+
+	used = (PATH_MAX * PANIC_UEVENT_MAX_LINES) +
+	       (sizeof(struct panic_event) * PANIC_NUM_CACHE_EVENTS);
+
+	pr_info("initialized using %zu preallocated bytes\n", used);
+
+	return;
+
+out_kset:
+	kset_unregister(panic_kset);
+out_events:
+	kfree(panic_events);
+out_env:
+	kfree(panic_envp);
+out_unlock:
+	mutex_unlock(&panic_mutex);
+}
+
+core_initcall(panic_events_init);
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index cf77b3881a21..966e8eaad09e 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -835,6 +835,19 @@ config DEBUG_SHIRQ
 
 menu "Debug Oops, Lockups and Hangs"
 
+config PANIC_EVENTS
+	bool "Enable uevents relating to panics or taints"
+	help
+	  Say Y here to enable the kernel to send uevents relating to panics or
+	  when taint events happen. This may be useful if you want to craft some
+	  userspace component to analyze a taint, or inform the user of this
+	  event. These events are useful later in boot, prior to device driver
+	  initialization, so it won't capture events early in boot. The events
+	  are also best effort, if the system is low on memory some uevents
+	  not reach userspace.
+
+	  Say N if unsure.
+
 config PANIC_ON_OOPS
 	bool "Panic on Oops"
 	help
