git: 477c594d9063 - main - kobj: allow multiple inheritance with per-class softc

From: Michal Meloun <mmel_at_FreeBSD.org>
Date: Fri, 11 Sep 2026 14:09:45 UTC
The branch main has been updated by mmel:

URL: https://cgit.FreeBSD.org/src/commit/?id=477c594d906328e210c30cf2c21d983152f21de3

commit 477c594d906328e210c30cf2c21d983152f21de3
Author:     Michal Meloun <mmel@FreeBSD.org>
AuthorDate: 2026-09-11 11:47:30 +0000
Commit:     Michal Meloun <mmel@FreeBSD.org>
CommitDate: 2026-09-11 14:09:36 +0000

    kobj: allow multiple inheritance with per-class softc
    
    Add support for hierarchical softc layout so that a leaf class and each
    of its base classes owns a private softc region inside a single
    allocation.
    
    device_get_softc_class(dev, cls) returns a pointer to the softc that
    belongs to the requested class.  The classic device_get_softc() still
    returns the leaf softc and remains fully compatible with existing
    drivers.
    
    Existing drivers are unaffected; they simply obtain a slightly larger
    softc block when they inherit from base classes.
    
    MFC after:      2 months
    Reviewed by:    kib
    Differential Revision:  https://reviews.freebsd.org/D59115
---
 sys/kern/subr_bus.c  |  19 ++++++++--
 sys/kern/subr_kobj.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++
 sys/sys/bus.h        |   1 +
 sys/sys/kobj.h       |  30 +++++++++++++---
 4 files changed, 142 insertions(+), 8 deletions(-)

diff --git a/sys/kern/subr_bus.c b/sys/kern/subr_bus.c
index b5c1c5b61f60..4398b3fa9e13 100644
--- a/sys/kern/subr_bus.c
+++ b/sys/kern/subr_bus.c
@@ -2159,6 +2159,17 @@ device_get_softc(device_t dev)
 	return (dev->softc);
 }
 
+void *
+device_get_softc_class(device_t dev, kobj_class_t cls)
+{
+	char *ptr;
+
+	ptr = dev->softc;
+	ptr += kobj_instance_offset(device_get_driver(dev), cls);
+
+	return (ptr);
+}
+
 /**
  * @brief Set the device's softc field
  *
@@ -2472,6 +2483,7 @@ device_set_driver(device_t dev, driver_t *driver)
 {
 	int domain;
 	struct domainset *policy;
+	size_t size;
 
 	if (dev->state >= DS_ATTACHED)
 		return (EBUSY);
@@ -2488,13 +2500,14 @@ device_set_driver(device_t dev, driver_t *driver)
 	dev->driver = driver;
 	if (driver) {
 		kobj_init((kobj_t) dev, (kobj_class_t) driver);
-		if (!(dev->flags & DF_EXTERNALSOFTC) && driver->size > 0) {
+		size = kobj_total_data_size(driver);
+		if (!(dev->flags & DF_EXTERNALSOFTC) && size > 0) {
 			if (bus_get_domain(dev, &domain) == 0)
 				policy = DOMAINSET_PREF(domain);
 			else
 				policy = DOMAINSET_RR();
-			dev->softc = malloc_domainset(driver->size, M_BUS_SC,
-			    policy, M_WAITOK | M_ZERO);
+			dev->softc = malloc_domainset(size, M_BUS_SC, policy,
+			    M_WAITOK | M_ZERO);
 		}
 	} else {
 		kobj_init((kobj_t) dev, &null_class);
diff --git a/sys/kern/subr_kobj.c b/sys/kern/subr_kobj.c
index a88639289954..0f9f7a79aaf0 100644
--- a/sys/kern/subr_kobj.c
+++ b/sys/kern/subr_kobj.c
@@ -94,6 +94,35 @@ kobj_error_method(void)
 	return ENXIO;
 }
 
+static inline size_t
+kobj_data_size_roundup(size_t size)
+{
+
+	return (roundup2((size), _Alignof(__max_align_t)));
+}
+
+static void
+kobj_class_init_total_size(kobj_class_t cls)
+{
+	kobj_class_t *bases;
+	size_t size;
+	int i;
+
+	MPASS(!cls->total_size_inited);
+	bases = cls->baseclasses;
+
+	size = kobj_data_size_roundup(cls->size);
+	if (bases != NULL) {
+		for (i = 0; bases[i] != NULL; i++) {
+			if (!bases[i]->total_size_inited)
+				kobj_class_init_total_size(bases[i]);
+			size += bases[i]->total_size;
+		}
+	}
+	cls->total_size_inited = true;
+	cls->total_size = size;
+}
+
 static void
 kobj_class_compile_common(kobj_class_t cls, kobj_ops_t ops)
 {
@@ -121,6 +150,12 @@ kobj_class_compile_common(kobj_class_t cls, kobj_ops_t ops)
 		ops->cache[i] = &null_method;
 	ops->cls = cls;
 	cls->ops = ops;
+
+	/*
+	 * Then compute total object data size.
+	 */
+	if (!cls->total_size_inited)
+		kobj_class_init_total_size(cls);
 }
 
 static int
@@ -349,3 +384,68 @@ kobj_delete(kobj_t obj, struct malloc_type *mtype)
 	if (mtype)
 		free(obj, mtype);
 }
+
+static inline bool
+kobj_get_instance_offset(kobj_class_t cls, kobj_class_t subclass, size_t *offs)
+{
+	kobj_class_t *bases;
+	size_t tmp;
+	int i;
+
+	if (cls == subclass)
+		return (true);
+
+	bases = cls->baseclasses;
+	*offs += kobj_data_size_roundup(cls->size);
+
+	if (bases == NULL || bases[0] == NULL)
+		return (false);
+
+	/* Try most common cases (offset of direct subclass) first. */
+	tmp = *offs;
+	for (i = 0; bases[i] != NULL; i++) {
+		if (bases[i] == subclass) {
+			*offs = tmp;
+			return (true);
+		}
+		tmp += bases[i]->total_size;
+	}
+
+	/* Then do recursion for nested classes. */
+	for (i = 0; bases[i] != NULL; i++) {
+		if (kobj_get_instance_offset(bases[i], subclass, offs))
+			return (true);
+	}
+	return (false);
+}
+
+/*
+ * Get the data offset for the given class.
+ * layout is:  [main | base0 | base1 | ... ]
+ */
+size_t
+kobj_instance_offset(kobj_class_t cls, kobj_class_t subclass)
+{
+	size_t offs;
+
+	if (cls == subclass)
+		return (0);
+
+	offs = 0;
+	if (!kobj_get_instance_offset(cls, subclass, &offs))
+		panic("%s: class %s not found in hierarchy of %s", __func__,
+		    subclass->name, cls->name);
+	return (offs);
+}
+
+/*
+ * Get the total data size of this class and all its subclasses.
+ */
+size_t
+kobj_total_data_size(kobj_class_t cls)
+{
+
+	MPASS(cls->total_size_inited);
+
+	return (cls->total_size);
+}
diff --git a/sys/sys/bus.h b/sys/sys/bus.h
index a2e315bb9dee..4ea411a4dd80 100644
--- a/sys/sys/bus.h
+++ b/sys/sys/bus.h
@@ -724,6 +724,7 @@ void	device_set_ivars(device_t dev, void *ivars);
 const	char *device_get_name(device_t dev);
 const	char *device_get_nameunit(device_t dev);
 void	*device_get_softc(device_t dev);
+void	*device_get_softc_class(device_t dev, kobj_class_t cls);
 device_state_t	device_get_state(device_t dev);
 int	device_get_unit(device_t dev);
 struct sysctl_ctx_list *device_get_sysctl_ctx(device_t dev);
diff --git a/sys/sys/kobj.h b/sys/sys/kobj.h
index 7705f609bea9..e248f9eafaf8 100644
--- a/sys/sys/kobj.h
+++ b/sys/sys/kobj.h
@@ -31,6 +31,11 @@
 
 #include <sys/types.h>
 
+/* XXX workaround for bool type for tools/build/test-includes */
+#if !defined(_KERNEL) && !defined(_STANDALONE)
+#include <stdbool.h> 
+#endif
+
 /*
  * Forward declarations
  */
@@ -59,7 +64,9 @@ struct kobj_method {
 	size_t		size;		/* object size */		\
 	kobj_class_t	*baseclasses;	/* base classes */		\
 	u_int		refs;		/* reference count */		\
-	kobj_ops_t	ops		/* compiled method table */
+	kobj_ops_t	ops;		/* compiled method table */	\
+	size_t		total_size;	/* total object size */		\
+	bool		total_size_inited
 
 struct kobj_class {
 	KOBJ_CLASS_FIELDS;
@@ -124,7 +131,7 @@ DEFINE_CLASS_0(name, name ## _class, methods, size)
 #define DEFINE_CLASS_0(name, classvar, methods, size)	\
 							\
 struct kobj_class classvar = {				\
-	#name, methods, size, NULL			\
+	#name, methods, size, NULL, 0, false		\
 }
 
 /*
@@ -139,7 +146,8 @@ struct kobj_class classvar = {				\
 static kobj_class_t name ## _baseclasses[] =		\
 	{ &base1, NULL };				\
 struct kobj_class classvar = {				\
-	#name, methods, size, name ## _baseclasses	\
+	#name, methods, size, name ## _baseclasses, 0,	\
+	false						\
 }
 
 /*
@@ -155,7 +163,8 @@ static kobj_class_t name ## _baseclasses[] =		\
 	{ &base1,					\
 	  &base2, NULL };				\
 struct kobj_class classvar = {				\
-	#name, methods, size, name ## _baseclasses	\
+	#name, methods, size, name ## _baseclasses, 0,	\
+	false						\
 }
 
 /*
@@ -172,7 +181,8 @@ static kobj_class_t name ## _baseclasses[] =		\
 	  &base2,					\
 	  &base3, NULL };				\
 struct kobj_class classvar = {				\
-	#name, methods, size, name ## _baseclasses	\
+	#name, methods, size, name ## _baseclasses, 0,	\
+	false						\
 }
 
 /*
@@ -209,6 +219,16 @@ void		kobj_init_static(kobj_t obj, kobj_class_t cls);
  */
 void		kobj_delete(kobj_t obj, struct malloc_type *mtype);
 
+/*
+ * Get the data offset for the given class.
+ */
+size_t		kobj_instance_offset(kobj_class_t cls, kobj_class_t subclass);
+
+/*
+ * Get the total data size of this class and all its subclasses.
+ */
+size_t		kobj_total_data_size(kobj_class_t cls);
+
 /*
  * Maintain stats on hits/misses in lookup caches.
  */