m/n/c/devmgr: init
Add a minimal device manager based on kobject/uevents. Currently this
only loads kernel modules. Further functionality will be added in
subsequent CLs.
Change-Id: I444ecdaff3f8ddb9ec169b094ba03e169dd70c4e
Reviewed-on: https://review.monogon.dev/c/monogon/+/1790
Reviewed-by: Serge Bazanski <serge@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/node/core/devmgr/BUILD.bazel b/metropolis/node/core/devmgr/BUILD.bazel
new file mode 100644
index 0000000..15b8065
--- /dev/null
+++ b/metropolis/node/core/devmgr/BUILD.bazel
@@ -0,0 +1,13 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+ name = "devmgr",
+ srcs = ["devmgr.go"],
+ importpath = "source.monogon.dev/metropolis/node/core/devmgr",
+ visibility = ["//visibility:public"],
+ deps = [
+ "//metropolis/pkg/kmod",
+ "//metropolis/pkg/supervisor",
+ "@com_github_mdlayher_kobject//:kobject",
+ ],
+)
diff --git a/metropolis/node/core/devmgr/devmgr.go b/metropolis/node/core/devmgr/devmgr.go
new file mode 100644
index 0000000..fc626b0
--- /dev/null
+++ b/metropolis/node/core/devmgr/devmgr.go
@@ -0,0 +1,78 @@
+// Package devmgr is the userspace pendant to the kernel device management
+// system. It talks to the kernel and the performs any further userspace actions
+// related to device events. It corresponds very roughly to systemd-udevd on
+// more conventional Linux distros. It currently only handles dynamic module
+// loading, but will be extended to provide better device handling in other
+// parts of the system.
+package devmgr
+
+import (
+ "context"
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+
+ "github.com/mdlayher/kobject"
+
+ "source.monogon.dev/metropolis/pkg/kmod"
+ "source.monogon.dev/metropolis/pkg/supervisor"
+)
+
+type Service struct{}
+
+func New() *Service {
+ return &Service{}
+}
+
+func (s *Service) Run(ctx context.Context) error {
+ c, err := kobject.New()
+ if err != nil {
+ return fmt.Errorf("unable to create kobject uevent socket: %w", err)
+ }
+ defer c.Close()
+
+ l := supervisor.Logger(ctx)
+
+ modMgr, err := kmod.NewManagerFromPath("/lib/modules")
+ if err != nil {
+ return fmt.Errorf("error creating module manager: %w", err)
+ }
+
+ // Start goroutine which instructs the kernel to generate "synthetic"
+ // uevents for all preexisting devices. This allows the kobject netlink
+ // listener below to "catch up" on devices added before it was created.
+ // This functionality is minimally documented in the Linux kernel, the
+ // closest we have is
+ // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-uevent which
+ // contains documentation on how to trigger synthetic events.
+ go func() {
+ err = filepath.WalkDir("/sys/devices", func(path string, d fs.DirEntry, err error) error {
+ if !d.IsDir() && d.Name() == "uevent" {
+ if err := os.WriteFile(path, []byte("add"), 0); err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ l.Errorf("failed to load initial device database: %v", err)
+ } else {
+ l.Info("Initial device loading done")
+ }
+ }()
+
+ for {
+ e, err := c.Receive()
+ if err != nil {
+ return fmt.Errorf("error receiving kobject uevent: %w", err)
+ }
+ if e.Action == kobject.Add {
+ if e.Values["MODALIAS"] != "" {
+ if err := modMgr.LoadModulesForDevice(e.Values["MODALIAS"]); err != nil {
+ l.Errorf("Error loading kernel modules: %w", err)
+ }
+ }
+ }
+ }
+}