diff --git a/meta/recipes-core/systemd/dlopen-deps.inc b/meta/recipes-core/systemd/dlopen-deps.inc
new file mode 100644
index 00000000000..eaf6ca1f79a
--- /dev/null
+++ b/meta/recipes-core/systemd/dlopen-deps.inc
@@ -0,0 +1,73 @@
+PACKAGEFUNCS =+ "package_generate_dlopen_deps"
+
+python package_generate_dlopen_deps() {
+    # https://systemd.io/ELF_DLOPEN_METADATA/
+
+    import struct, json
+
+    def extract_segment(filename, segment):
+        """
+        Return the named segment from the ELF.
+        """
+        import tempfile, subprocess
+
+        with tempfile.NamedTemporaryFile() as f:
+            cmd = [d.getVar("OBJCOPY"), "--dump-section", f"{segment}={f.name}", filename]
+            subprocess.run(cmd, check=True)
+            return f.read()
+
+    def parse(buffer, is_little):
+        deps = []
+        offset = 0
+        while offset < len(buffer):
+            format = f"{'<' if is_little else '>'}iii"
+            name_size, desc_size, note_type = struct.unpack_from(format, buffer, offset)
+            offset += struct.calcsize(format)
+
+            format = f"{name_size}s0i{desc_size}s0i"
+            if note_type == 0x407c0c0a:
+                name_b, desc_b = struct.unpack_from(format, buffer, offset)
+                name = name_b.strip(b"\x00").decode("ascii")
+                if name == "FDO":
+                    desc = desc_b.strip(b"\x00").decode("utf-8")
+                    deps.append(*json.loads(desc))
+            offset += struct.calcsize(format)
+        return deps
+
+    dep_map = {
+        "required": "RDEPENDS",
+        "recommended": "RRECOMMENDS",
+        "suggested": "RSUGGESTS"
+    }
+
+    shlibs = oe.package.read_shlib_providers(d)
+
+    for pkg, files in pkgfiles.items():
+        # Skip -dbg packages as we won't need to generate dependencies for those
+        # but scanning can take time
+        if pkg.endswith("-dbg"):
+            continue
+
+        for f in files:
+            # Skip symlinks, just look for real libraries
+            if cpath.islink(f):
+                continue
+
+            if ".so." in f or f.endswith(".so"):
+                try:
+                    elf = oe.qa.ELFFile(f)
+                    elf.open()
+                    for dep in parse(extract_segment(f, ".note.dlopen"), elf.isLittleEndian()):
+                        for soname in dep["soname"]:
+                            if soname in shlibs:
+                                # TODO assumes the first match is good
+                                package, version = list(shlibs[soname].values())[0]
+                                dependency = dep_map[dep["priority"]]
+                                bb.note(f"{pkg}: adding {dependency} on {package} via .note.dlopen")
+                                d.appendVar(f"{dependency}:{pkg}", f" {package} (>= {version})")
+                            else:
+                                bb.warn(f"cannot find {soname}")
+                except oe.qa.NotELFFileError as e:
+                    bb.note(f"Cannot extract ELF notes: {e}")
+                    pass
+}
diff --git a/meta/recipes-core/systemd/systemd_256.7.bb b/meta/recipes-core/systemd/systemd_256.7.bb
index f3af4ac44dc..cad74702818 100644
--- a/meta/recipes-core/systemd/systemd_256.7.bb
+++ b/meta/recipes-core/systemd/systemd_256.7.bb
@@ -742,8 +742,6 @@ INSANE_SKIP:libsystemd-shared += "libdir"
 FILES:libsystemd-shared = "${rootlibdir}/systemd/libsystemd-shared*.so"
 
 RPROVIDES:udev = "hotplug"
-# This can be removed when we parse .note.dlopen in the ELF
-RDEPENDS:udev += "libkmod"
 
 RDEPENDS:udev-bash-completion += "bash-completion"
 RDEPENDS:udev-hwdb += "udev"
@@ -909,3 +907,5 @@ pkg_postinst:udev-hwdb () {
 pkg_prerm:udev-hwdb () {
 	rm -f $D${sysconfdir}/udev/hwdb.bin
 }
+
+require dlopen-deps.inc
