diff mbox series

[16/63] dyndbg-API: replace DECLARE_DYNDBG_CLASSMAP

Message ID 20250125064619.8305-17-jim.cromie@gmail.com (mailing list archive)
State New, archived
Headers show
Series Fix CONFIG_DRM_USE_DYNAMIC_DEBUG=y | expand

Commit Message

Jim Cromie Jan. 25, 2025, 6:45 a.m. UTC
DECLARE_DYNDBG_CLASSMAP() has a design error; its usage fails a basic
K&R rule: "define once, refer many times".

It is used across DRM core & drivers, each use re-defines the classmap
understood by that module; and all must match for the modules to
respond together when DRM.debug categories are enabled.  This is
brittle; a maintenance foot-gun.

Further, its culpable in the CONFIG_DRM_USE_DYNAMIC_DEBUG=Y
regression; its use in both core & drivers obfuscates the 2 roles.
So 1st drm.ko loads, and dyndbg initializes its DRM.debug callsites,
then a drm-driver loads, but too late - it missed the DRM.debug
enablement.

So replace it with 2 macros:
  DYNDBG_CLASSMAP_DEFINE - invoked once from core - drm.ko
  DYNDBG_CLASSMAP_USE    - from all drm drivers and helpers.

DYNDBG_CLASSMAP_DEFINE: it just modifies DECLARE_DYNDBG_CLASSMAP,
dropping drop the static qualifier, and exports it instead.

DYNDBG_CLASSMAP_USE: then refers to the exported var by name:
  used from drivers, helper-mods
  lets us drop the repetitive "classname" declarations
  fixes 2nd-defn problem
  creates a ddebug_class_user record in new __dyndbg_class_users section
  this allows ddebug_add_module(etal) to handle users differently.

DECLARE_DYNDBG_CLASSMAP is preserved temporarily, to decouple DRM
adaptation work and avoid compile-errs before its done.  IOW, DRM gets
fixed when they commit the adopt-new-api patches, and remove the
BROKEN marking.

The DEFINE,USE distinction, and the separate classmap-use record,
allows dyndbg to initialize the driver's & helper's DRM.debug
callsites separately after each is modprobed.

Basically, the classmap init-scan is repeated for classmap-users.

To review, dyndbg's existing __dyndbg_classes[] section does:

. catalogs the classmaps defined by a module (or builtin modules)
. tells dyndbg the module has class'd prdbgs, allowing >control
. DYNDBG_CLASSMAP_DEFINE creates classmaps in this section.

This patch adds __dyndbg_class_users[] section:

. catalogs uses/references to the classmap definitions.
. authorizes dyndbg to >control those class'd prdbgs in ref'g module.
. DYNDBG_CLASSMAP_USE() creates classmap-user records in this new section.

Now ddebug_add_module(etal) can handle classmap-uses similar to (and
after) classmaps; when a dependent module is loaded, if it has
classmap-uses (to a classmap-def in another module), that module's
kernel params are scanned to find if it has a kparam that is wired to
dyndbg's param-ops, and whose classmap is the one being ref'd.

To support this, theres a few data/header changes:

new struct ddebug_class_user
  contains: user-module-name, &classmap-defn
  it records drm-driver's use of a classmap in the section, allowing lookup

struct ddebug_info gets 2 new fields for the new sections:
  class_users, num_class_users.
  set by dynamic_debug_init() for builtins.
  or by kernel/module/main:load_info() for loadable modules.

vmlinux.lds.h: new BOUNDED_SECTION for __dyndbg_class_users

dynamic_debug.c has 2 changes in ddebug_add_module(), ddebug_change():

ddebug_add_module() already calls ddebug_attach_module_classes()
to handle classmaps DEFINEd by a module, now it also calls
ddebug_attach_user_module_classes() to handle USEd classmaps.  To
avoid this work when possible, 1st scan the module's descriptors and
count the number of class'd pr_debugs.

ddebug_attach_user_module_classes() scans the module's class_users
section, follows the refs to the parent's classmap, and calls
ddebug_apply_params() on each.  It also avoids work by checking the
module's class-ct.

ddebug_apply_params(new fn):

It scans module's/builtin kernel-params, calls ddebug_match_apply_kparam
for each to find any params/sysfs-nodes which may be wired to a classmap.

ddebug_match_apply_kparam(new fn):

1st, it tests the kernel-param.ops is dyndbg's; this guarantees that
the attached arg is a struct ddebug_class_param, which has a ref to
the param's state, and to the classmap defining the param's handling.

2nd, it requires that the classmap ref'd by the kparam is the one
we're called for; modules can use many separate classmaps (as
test_dynamic_debug does).

Then apply the "parent" kparam's setting to the dependent module,
using ddebug_apply_class_bitmap().

ddebug_change(and callees) also gets adjustments:

ddebug_find_valid_class(): This does a search over the module's
classmaps, looking for the class FOO echo'd to >control.  So now it
searches over __dyndbg_class_users[] after __dyndbg_classes[].

ddebug_class_name(): return class-names for defined AND used classes.

test_dynamic_debug.c, test_dynamic_debug_submod.c:

This demonstrates the 2 types of classmaps & sysfs-params, following
the 4-part recipe:

1. define an enum for the classmap: DRM.debug has DRM_UT_{CORE,KMS,...}
   multiple classes must share 0-62 classid space.
2. DYNDBG_CLASSMAP_DEFINE(.. DRM_UT_{CORE,KMS,...})
3. DYNDBG_CLASSMAP_PARAM* (classmap)
4. DYNDBG_CLASSMAP_USE()
   by _submod only, skipping 2,3

Move all the enum declarations together, to better explain how they
share the 0..62 class-id space available to a module (non-overlapping
subranges).

reorg macros 2,3 by name.  This gives a tabular format, making it easy
to see the pattern of repetition, and the points of change.

And extend the test to replicate the 2-module (parent & dependent)
scenario which caused the CONFIG_DRM_USE_DYNAMIC_DEBUG=y regression
seen in drm & drivers.

The _submod.c is a 2-line file: #define _SUBMOD, #include parent.

This gives identical complements of prdbgs in parent & _submod, and
thus identical print behavior when all of: >control, >params, and
parent->_submod propagation are working correctly.

It also puts all the parent/_submod declarations together in the same
source, with the new ifdef _SUBMOD block invoking DYNDBG_CLASSMAP_USE
for the 2 test-interfaces.  I think this is clearer.

These 2 modules are both tristate, allowing 3 super/sub combos: Y/Y,
Y/M, M/M (not N/N).  Y/Y testing exposed a missing __align(8) in the
_METADATA macro, which M/M didn't see because the module-loader memory
placement constrains it instead.

NB: this patch ignores a checkpatch do-while warning which is fixed by
a preceding commit, which would be wrong for declarative macros like
module_param_named() etc.

Fixes: aad0214f3026 ("dyndbg: add DECLARE_DYNDBG_CLASSMAP macro")
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
v9 - commit-msg tweaks
     DRM:CHECK warnings on macros: add parens
     extern DEFINEd _var, static classnames
     change ddebug_class_user.user_mod_name to .mod_name
     simplify ddebug_find_valid_class
     improve vpr_cm_info msg format
     wrap (base) in macro body
     move __DYNDBG_CLASSMAP_CHECK above kdoc for DYNDBG_CLASSMAP_DEFINE

v8 - split drm parts to separate commits.
     preserve DECLARE_DYNDBG_CLASSMAP to decouple DRM, no flag day.
     fixup block comment

v7 - previous submission-blocking bug:

missing __align(8) in DYNAMIC_DEBUG_DECLARE_METADATA on
ddebug_class_user caused corrupt records, but only for builtin
modules; module loader code probably pinned allocations to the right
alignment naturally, hiding the bug for typical builds.

v6- get rid of WARN_ON_ONCE
v?- fix _var expanded 2x in macro

dyndbg:

This fn formerly returned the map which contained the class (thus
validating it), and as a side-effect set the class-id in an outvar.

But the caller didn't use the map (after checking its not null), only
the valid class-id.  So simplify the fn to return the class-id of the
validated classname, or -ENOENT when the queried classname is not
found.

Convey more useful info in the debug-msg: print class-names[0,last],
and [base,+len] instead of the class-type printout, which is currently
always "type:DISJOINT_BITS".  And drop ddebug_classmap_typenames,
which is now unused.

[root@v6 b0-dd]# modprobe test_dynamic_debug_submod
[   18.864962] dyndbg: loaded classmap: test_dynamic_debug [16..24] V0..V7
[   18.865046] dyndbg:  found kp:p_level_num =0x0
[   18.865048] dyndbg:   mapped to: test_dynamic_debug [16..24] V0..V7
[   18.865164] dyndbg:   p_level_num: lvl:0 bits:0x0
[   18.865217] dyndbg: loaded classmap: test_dynamic_debug [0..10] D2_CORE..D2_DRMRES
[   18.865297] dyndbg:  found kp:p_disjoint_bits =0x0
[   18.865298] dyndbg:   mapped to: test_dynamic_debug [0..10] D2_CORE..D2_DRMRES
[   18.865424] dyndbg:   p_disjoint_bits: classbits: 0x0
[   18.865472] dyndbg: module:test_dynamic_debug attached 2 classmaps
[   18.865533] dyndbg:  23 debug prints in module test_dynamic_debug
[   18.866558] dyndbg: loaded classmap: test_dynamic_debug_submod [16..24] V0..V7
[   18.866698] dyndbg:  found kp:p_level_num =0x0
[   18.866699] dyndbg:   mapped to: test_dynamic_debug_submod [16..24] V0..V7
[   18.866865] dyndbg:   p_level_num: lvl:0 bits:0x0
[   18.866926] dyndbg: loaded classmap: test_dynamic_debug_submod [0..10] D2_CORE..D2_DRMRES
[   18.867026] dyndbg:  found kp:p_disjoint_bits =0x0
[   18.867027] dyndbg:   mapped to: test_dynamic_debug_submod [0..10] D2_CORE..D2_DRMRES
[   18.867193] dyndbg:   p_disjoint_bits: classbits: 0x0
[   18.867255] dyndbg: module:test_dynamic_debug_submod attached 2 classmap uses
[   18.867351] dyndbg:  23 debug prints in module test_dynamic_debug_submod
---
 MAINTAINERS                       |   2 +-
 include/asm-generic/vmlinux.lds.h |   1 +
 include/linux/dynamic_debug.h     |  83 ++++++++++--
 kernel/module/main.c              |   3 +
 lib/Kconfig.debug                 |  24 +++-
 lib/Makefile                      |   3 +
 lib/dynamic_debug.c               | 203 ++++++++++++++++++++++++------
 lib/test_dynamic_debug.c          | 111 +++++++++++-----
 lib/test_dynamic_debug_submod.c   |  10 ++
 9 files changed, 355 insertions(+), 85 deletions(-)
 create mode 100644 lib/test_dynamic_debug_submod.c

Comments

Louis Chauvet Feb. 25, 2025, 2:16 p.m. UTC | #1
Le 25/01/2025 à 07:45, Jim Cromie a écrit :
> DECLARE_DYNDBG_CLASSMAP() has a design error; its usage fails a basic
> K&R rule: "define once, refer many times".
> 
> It is used across DRM core & drivers, each use re-defines the classmap
> understood by that module; and all must match for the modules to
> respond together when DRM.debug categories are enabled.  This is
> brittle; a maintenance foot-gun.
> 
> Further, its culpable in the CONFIG_DRM_USE_DYNAMIC_DEBUG=Y
> regression; its use in both core & drivers obfuscates the 2 roles.
> So 1st drm.ko loads, and dyndbg initializes its DRM.debug callsites,
> then a drm-driver loads, but too late - it missed the DRM.debug
> enablement.
> 
> So replace it with 2 macros:
>    DYNDBG_CLASSMAP_DEFINE - invoked once from core - drm.ko
>    DYNDBG_CLASSMAP_USE    - from all drm drivers and helpers.
> 
> DYNDBG_CLASSMAP_DEFINE: it just modifies DECLARE_DYNDBG_CLASSMAP,
> dropping drop the static qualifier, and exports it instead.
> 
> DYNDBG_CLASSMAP_USE: then refers to the exported var by name:
>    used from drivers, helper-mods
>    lets us drop the repetitive "classname" declarations
>    fixes 2nd-defn problem
>    creates a ddebug_class_user record in new __dyndbg_class_users section
>    this allows ddebug_add_module(etal) to handle users differently.
> 
> DECLARE_DYNDBG_CLASSMAP is preserved temporarily, to decouple DRM
> adaptation work and avoid compile-errs before its done.  IOW, DRM gets
> fixed when they commit the adopt-new-api patches, and remove the
> BROKEN marking.
> 
> The DEFINE,USE distinction, and the separate classmap-use record,
> allows dyndbg to initialize the driver's & helper's DRM.debug
> callsites separately after each is modprobed.
> 
> Basically, the classmap init-scan is repeated for classmap-users.
> 
> To review, dyndbg's existing __dyndbg_classes[] section does:
> 
> . catalogs the classmaps defined by a module (or builtin modules)
> . tells dyndbg the module has class'd prdbgs, allowing >control
> . DYNDBG_CLASSMAP_DEFINE creates classmaps in this section.
> 
> This patch adds __dyndbg_class_users[] section:
> 
> . catalogs uses/references to the classmap definitions.
> . authorizes dyndbg to >control those class'd prdbgs in ref'g module.
> . DYNDBG_CLASSMAP_USE() creates classmap-user records in this new section.
> 
> Now ddebug_add_module(etal) can handle classmap-uses similar to (and
> after) classmaps; when a dependent module is loaded, if it has
> classmap-uses (to a classmap-def in another module), that module's
> kernel params are scanned to find if it has a kparam that is wired to
> dyndbg's param-ops, and whose classmap is the one being ref'd.
> 
> To support this, theres a few data/header changes:
> 
> new struct ddebug_class_user
>    contains: user-module-name, &classmap-defn
>    it records drm-driver's use of a classmap in the section, allowing lookup
> 
> struct ddebug_info gets 2 new fields for the new sections:
>    class_users, num_class_users.
>    set by dynamic_debug_init() for builtins.
>    or by kernel/module/main:load_info() for loadable modules.
> 
> vmlinux.lds.h: new BOUNDED_SECTION for __dyndbg_class_users
> 
> dynamic_debug.c has 2 changes in ddebug_add_module(), ddebug_change():
> 
> ddebug_add_module() already calls ddebug_attach_module_classes()
> to handle classmaps DEFINEd by a module, now it also calls
> ddebug_attach_user_module_classes() to handle USEd classmaps.  To
> avoid this work when possible, 1st scan the module's descriptors and
> count the number of class'd pr_debugs.
> 
> ddebug_attach_user_module_classes() scans the module's class_users
> section, follows the refs to the parent's classmap, and calls
> ddebug_apply_params() on each.  It also avoids work by checking the
> module's class-ct.
> 
> ddebug_apply_params(new fn):
> 
> It scans module's/builtin kernel-params, calls ddebug_match_apply_kparam
> for each to find any params/sysfs-nodes which may be wired to a classmap.
> 
> ddebug_match_apply_kparam(new fn):
> 
> 1st, it tests the kernel-param.ops is dyndbg's; this guarantees that
> the attached arg is a struct ddebug_class_param, which has a ref to
> the param's state, and to the classmap defining the param's handling.
> 
> 2nd, it requires that the classmap ref'd by the kparam is the one
> we're called for; modules can use many separate classmaps (as
> test_dynamic_debug does).
> 
> Then apply the "parent" kparam's setting to the dependent module,
> using ddebug_apply_class_bitmap().
> 
> ddebug_change(and callees) also gets adjustments:
> 
> ddebug_find_valid_class(): This does a search over the module's
> classmaps, looking for the class FOO echo'd to >control.  So now it
> searches over __dyndbg_class_users[] after __dyndbg_classes[].
> 
> ddebug_class_name(): return class-names for defined AND used classes.
> 
> test_dynamic_debug.c, test_dynamic_debug_submod.c:
> 
> This demonstrates the 2 types of classmaps & sysfs-params, following
> the 4-part recipe:
> 
> 1. define an enum for the classmap: DRM.debug has DRM_UT_{CORE,KMS,...}
>     multiple classes must share 0-62 classid space.
> 2. DYNDBG_CLASSMAP_DEFINE(.. DRM_UT_{CORE,KMS,...})
> 3. DYNDBG_CLASSMAP_PARAM* (classmap)
> 4. DYNDBG_CLASSMAP_USE()
>     by _submod only, skipping 2,3
> 
> Move all the enum declarations together, to better explain how they
> share the 0..62 class-id space available to a module (non-overlapping
> subranges).
> 
> reorg macros 2,3 by name.  This gives a tabular format, making it easy
> to see the pattern of repetition, and the points of change.
> 
> And extend the test to replicate the 2-module (parent & dependent)
> scenario which caused the CONFIG_DRM_USE_DYNAMIC_DEBUG=y regression
> seen in drm & drivers.
> 
> The _submod.c is a 2-line file: #define _SUBMOD, #include parent.
> 
> This gives identical complements of prdbgs in parent & _submod, and
> thus identical print behavior when all of: >control, >params, and
> parent->_submod propagation are working correctly.
> 
> It also puts all the parent/_submod declarations together in the same
> source, with the new ifdef _SUBMOD block invoking DYNDBG_CLASSMAP_USE
> for the 2 test-interfaces.  I think this is clearer.
> 
> These 2 modules are both tristate, allowing 3 super/sub combos: Y/Y,
> Y/M, M/M (not N/N).  Y/Y testing exposed a missing __align(8) in the
> _METADATA macro, which M/M didn't see because the module-loader memory
> placement constrains it instead.
> 
> NB: this patch ignores a checkpatch do-while warning which is fixed by
> a preceding commit, which would be wrong for declarative macros like
> module_param_named() etc.
> 
> Fixes: aad0214f3026 ("dyndbg: add DECLARE_DYNDBG_CLASSMAP macro")
> Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
> ---
> v9 - commit-msg tweaks
>       DRM:CHECK warnings on macros: add parens
>       extern DEFINEd _var, static classnames
>       change ddebug_class_user.user_mod_name to .mod_name
>       simplify ddebug_find_valid_class
>       improve vpr_cm_info msg format
>       wrap (base) in macro body
>       move __DYNDBG_CLASSMAP_CHECK above kdoc for DYNDBG_CLASSMAP_DEFINE
> 
> v8 - split drm parts to separate commits.
>       preserve DECLARE_DYNDBG_CLASSMAP to decouple DRM, no flag day.
>       fixup block comment
> 
> v7 - previous submission-blocking bug:
> 
> missing __align(8) in DYNAMIC_DEBUG_DECLARE_METADATA on
> ddebug_class_user caused corrupt records, but only for builtin
> modules; module loader code probably pinned allocations to the right
> alignment naturally, hiding the bug for typical builds.
> 
> v6- get rid of WARN_ON_ONCE
> v?- fix _var expanded 2x in macro
> 
> dyndbg:
> 
> This fn formerly returned the map which contained the class (thus
> validating it), and as a side-effect set the class-id in an outvar.
> 
> But the caller didn't use the map (after checking its not null), only
> the valid class-id.  So simplify the fn to return the class-id of the
> validated classname, or -ENOENT when the queried classname is not
> found.
> 
> Convey more useful info in the debug-msg: print class-names[0,last],
> and [base,+len] instead of the class-type printout, which is currently
> always "type:DISJOINT_BITS".  And drop ddebug_classmap_typenames,
> which is now unused.
> 
> [root@v6 b0-dd]# modprobe test_dynamic_debug_submod
> [   18.864962] dyndbg: loaded classmap: test_dynamic_debug [16..24] V0..V7
> [   18.865046] dyndbg:  found kp:p_level_num =0x0
> [   18.865048] dyndbg:   mapped to: test_dynamic_debug [16..24] V0..V7
> [   18.865164] dyndbg:   p_level_num: lvl:0 bits:0x0
> [   18.865217] dyndbg: loaded classmap: test_dynamic_debug [0..10] D2_CORE..D2_DRMRES
> [   18.865297] dyndbg:  found kp:p_disjoint_bits =0x0
> [   18.865298] dyndbg:   mapped to: test_dynamic_debug [0..10] D2_CORE..D2_DRMRES
> [   18.865424] dyndbg:   p_disjoint_bits: classbits: 0x0
> [   18.865472] dyndbg: module:test_dynamic_debug attached 2 classmaps
> [   18.865533] dyndbg:  23 debug prints in module test_dynamic_debug
> [   18.866558] dyndbg: loaded classmap: test_dynamic_debug_submod [16..24] V0..V7
> [   18.866698] dyndbg:  found kp:p_level_num =0x0
> [   18.866699] dyndbg:   mapped to: test_dynamic_debug_submod [16..24] V0..V7
> [   18.866865] dyndbg:   p_level_num: lvl:0 bits:0x0
> [   18.866926] dyndbg: loaded classmap: test_dynamic_debug_submod [0..10] D2_CORE..D2_DRMRES
> [   18.867026] dyndbg:  found kp:p_disjoint_bits =0x0
> [   18.867027] dyndbg:   mapped to: test_dynamic_debug_submod [0..10] D2_CORE..D2_DRMRES
> [   18.867193] dyndbg:   p_disjoint_bits: classbits: 0x0
> [   18.867255] dyndbg: module:test_dynamic_debug_submod attached 2 classmap uses
> [   18.867351] dyndbg:  23 debug prints in module test_dynamic_debug_submod
> ---
>   MAINTAINERS                       |   2 +-
>   include/asm-generic/vmlinux.lds.h |   1 +
>   include/linux/dynamic_debug.h     |  83 ++++++++++--
>   kernel/module/main.c              |   3 +
>   lib/Kconfig.debug                 |  24 +++-
>   lib/Makefile                      |   3 +
>   lib/dynamic_debug.c               | 203 ++++++++++++++++++++++++------
>   lib/test_dynamic_debug.c          | 111 +++++++++++-----
>   lib/test_dynamic_debug_submod.c   |  10 ++

Hi Jim,

While reading the files test_dynamic_debug*, I was wondering, they are 
more samples than tests. Does it make sense to move them in samples/? 
There is no need to do it in this series.

>   9 files changed, 355 insertions(+), 85 deletions(-)
>   create mode 100644 lib/test_dynamic_debug_submod.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 0fa7c5728f1e..38afccb3b71e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -8041,7 +8041,7 @@ M:	Jim Cromie <jim.cromie@gmail.com>
>   S:	Maintained
>   F:	include/linux/dynamic_debug.h
>   F:	lib/dynamic_debug.c
> -F:	lib/test_dynamic_debug.c
> +F:	lib/test_dynamic_debug*.c
>   
>   DYNAMIC INTERRUPT MODERATION
>   M:	Tal Gilboa <talgi@nvidia.com>
> diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
> index 54504013c749..eb93fd09832b 100644
> --- a/include/asm-generic/vmlinux.lds.h
> +++ b/include/asm-generic/vmlinux.lds.h
> @@ -366,6 +366,7 @@ defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPELLER_CLANG)
>   	/* implement dynamic printk debug */				\
>   	. = ALIGN(8);							\
>   	BOUNDED_SECTION_BY(__dyndbg_classes, ___dyndbg_classes)		\
> +	BOUNDED_SECTION_BY(__dyndbg_class_users, ___dyndbg_class_users)	\
>   	BOUNDED_SECTION_BY(__dyndbg, ___dyndbg)				\
>   	CODETAG_SECTIONS()						\
>   	LIKELY_PROFILE()		       				\
> diff --git a/include/linux/dynamic_debug.h b/include/linux/dynamic_debug.h
> index c8102e89beb2..dc610a12b91c 100644
> --- a/include/linux/dynamic_debug.h
> +++ b/include/linux/dynamic_debug.h
> @@ -71,9 +71,28 @@ enum ddebug_class_map_type {
>   	 */
>   };
>   
> +/*
> + * dyndbg-classmaps are devised to support DRM.debug directly:
> + *    10 enum-vals: DRM_UT_* define the categories
> + *   ~23 categorized *_dbg() macros, each passing a DRM_UT_* val as 1st arg
> + *     2 macros below them: drm_dev_dbg, __drm_dbg
> + * ~5000 calls to the categorized macros, across all of drivers/gpu/drm/
> + *
> + * When CONFIG_DRM_USE_DYNAMIC_DEBUG=y, the 2 low macros are redefined
> + * to invoke _dynamic_func_call_cls().  This compiles the category
> + * into each callsite's class_id field, where dyndbg can select on it
> + * and alter a callsite's patch-state, avoiding repeated __drm_debug
> + * checks.
> + *
> + * To make the callsites manageable from the >control file, authors
> + * provide a "classmap" of names to class_ids in use by the module(s),
> + * usually by stringifying the enum-vals.  Modules with multiple
> + * classmaps must arrange to share the 0..62 class_id space.
> + */
> +
>   struct ddebug_class_map {
> -	struct module *mod;
> -	const char *mod_name;	/* needed for builtins */
> +	const struct module *mod;		/* NULL for builtins */
> +	const char *mod_name;

Is this change belongs to this patch? If not can you move this in a 
"cleanup" patch?

>   	const char **class_names;
>   	const int length;
>   	const int base;		/* index of 1st .class_id, allows split/shared space */
> @@ -81,11 +100,34 @@ struct ddebug_class_map {
>   };
>   
>   /**
> - * DECLARE_DYNDBG_CLASSMAP - declare classnames known by a module
> - * @_var:   a struct ddebug_class_map, passed to module_param_cb
> - * @_type:  enum class_map_type, chooses bits/verbose, numeric/symbolic
> - * @_base:  offset of 1st class-name. splits .class_id space
> - * @classes: class-names used to control class'd prdbgs
> + * DYNDBG_CLASSMAP_DEFINE - define debug classes used by a module.
> + * @_var:   name of the classmap, exported for other modules coordinated use.
> + * @_mapty: enum ddebug_class_map_type: 0:DISJOINT - independent, 1:LEVEL - v2>v1> + * @_base:  reserve N classids starting at _base, to split 0..62 
classid space
> + * @classes: names of the N classes.
> + *
> + * This tells dyndbg what class_ids the module is using: _base..+N, by
> + * mapping names onto them.  This qualifies "class NAME" >controls on
> + * the defining module, ignoring unknown names.
> + */
> +#define DYNDBG_CLASSMAP_DEFINE(_var, _mapty, _base, ...)		\
> +	static const char *_var##_classnames[] = { __VA_ARGS__ };	\
> +	extern struct ddebug_class_map _var;				\
> +	struct ddebug_class_map __aligned(8) __used			\
> +		__section("__dyndbg_classes") _var = {			\
> +		.mod = THIS_MODULE,					\
> +		.mod_name = KBUILD_MODNAME,				\
> +		.base = (_base),					\
> +		.map_type = (_mapty),					\
> +		.length = ARRAY_SIZE(_var##_classnames),		\
> +		.class_names = _var##_classnames,			\
> +	};								\
> +	EXPORT_SYMBOL(_var)
> +
> +/*
> + * XXX: keep this until DRM adapts to use the DEFINE/USE api, it
> + * differs from DYNDBG_CLASSMAP_DEFINE by the static replacing the
> + * extern/EXPORT on the struct init.
>    */
>   #define DECLARE_DYNDBG_CLASSMAP(_var, _maptype, _base, ...)		\
>   	static const char *_var##_classnames[] = { __VA_ARGS__ };	\
> @@ -99,12 +141,37 @@ struct ddebug_class_map {
>   		.class_names = _var##_classnames,			\
>   	}
>   
> +struct ddebug_class_user {
> +	char *mod_name;
> +	struct ddebug_class_map *map;
> +};
> +
> +/**
> + * DYNDBG_CLASSMAP_USE - refer to a classmap, DEFINEd elsewhere.
> + * @_var: name of the exported classmap var
> + *
> + * This tells dyndbg that the module has prdbgs with classids defined
> + * in the named classmap.  This qualifies "class NAME" >controls on
> + * the user module, ignoring unknown names.
> + */
> +#define DYNDBG_CLASSMAP_USE(_var)					\
> +	DYNDBG_CLASSMAP_USE_(_var, __UNIQUE_ID(ddebug_class_user))
> +#define DYNDBG_CLASSMAP_USE_(_var, _uname)				\
> +	extern struct ddebug_class_map _var;				\
> +	static struct ddebug_class_user __aligned(8) __used		\
> +	__section("__dyndbg_class_users") _uname = {			\
> +		.mod_name = KBUILD_MODNAME,				\
> +		.map = &(_var),						\
> +	}
> +

I'm not sure I understand properly how __section works, but can we use 
multiple DYNDBG_CLASSMAP_USE in one module? Can we also use 
DYNDBG_CLASSMAP_DEFINE while also importing an other classmap 
DYNDBG_CLASSMAP_USE?

If not, does it make sense to allow it (for example MFD devices can 
touch multiple subsystems)?

>   /* encapsulate linker provided built-in (or module) dyndbg data */
>   struct _ddebug_info {
>   	struct _ddebug *descs;
>   	struct ddebug_class_map *classes;
> +	struct ddebug_class_user *class_users;
>   	unsigned int num_descs;
>   	unsigned int num_classes;
> +	unsigned int num_class_users;
>   };
>   
>   struct ddebug_class_param {
> @@ -205,7 +272,7 @@ void __dynamic_ibdev_dbg(struct _ddebug *descriptor,
>    * (|_no_desc):	former gets callsite descriptor as 1st arg (for prdbgs)
>    */
>   #define __dynamic_func_call_cls(id, cls, fmt, func, ...) do {	\
> -	DEFINE_DYNAMIC_DEBUG_METADATA_CLS(id, cls, fmt);	\
> +	DEFINE_DYNAMIC_DEBUG_METADATA_CLS((id), cls, fmt);	\

Can you move this in a "cleanup" patch?

>   	if (DYNAMIC_DEBUG_BRANCH(id))				\
>   		func(&id, ##__VA_ARGS__);			\
>   } while (0)
> diff --git a/kernel/module/main.c b/kernel/module/main.c
> index 5399c182b3cb..c394a0c6e8c6 100644
> --- a/kernel/module/main.c
> +++ b/kernel/module/main.c
> @@ -2546,6 +2546,9 @@ static int find_module_sections(struct module *mod, struct load_info *info)
>   	mod->dyndbg_info.classes = section_objs(info, "__dyndbg_classes",
>   						sizeof(*mod->dyndbg_info.classes),
>   						&mod->dyndbg_info.num_classes);
> +	mod->dyndbg_info.class_users = section_objs(info, "__dyndbg_class_users",
> +						    sizeof(*mod->dyndbg_info.class_users),
> +						   &mod->dyndbg_info.num_class_users);
>   #endif
>   
>   	return 0;
> diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> index f3d723705879..933f69ef87f8 100644
> --- a/lib/Kconfig.debug
> +++ b/lib/Kconfig.debug
> @@ -2884,12 +2884,26 @@ config TEST_STATIC_KEYS
>   	  If unsure, say N.
>   
>   config TEST_DYNAMIC_DEBUG
> -	tristate "Test DYNAMIC_DEBUG"
> -	depends on DYNAMIC_DEBUG
> +	tristate "Build test-dynamic-debug module"
> +	depends on DYNAMIC_DEBUG || DYNAMIC_DEBUG_CORE
>   	help
> -	  This module registers a tracer callback to count enabled
> -	  pr_debugs in a 'do_debugging' function, then alters their
> -	  enablements, calls the function, and compares counts.
> +	  This module exercises/demonstrates dyndbg's classmap API, by
> +	  creating 2 classes: a DISJOINT classmap (supporting DRM.debug)
> +	  and a LEVELS/VERBOSE classmap (like verbose2 > verbose1).
> +
> +	  If unsure, say N.
> +
> +config TEST_DYNAMIC_DEBUG_SUBMOD
> +	tristate "Build test-dynamic-debug submodule"
> +	default m
> +	depends on DYNAMIC_DEBUG || DYNAMIC_DEBUG_CORE
> +	depends on TEST_DYNAMIC_DEBUG
> +	help
> +	  This sub-module uses a classmap defined and exported by the
> +	  parent module, recapitulating drm & driver's shared use of
> +	  drm.debug to control enabled debug-categories.
> +	  It is tristate, independent of parent, to allow testing all
> +	  proper combinations of parent=y/m submod=y/m.
>   
>   	  If unsure, say N.
>   
> diff --git a/lib/Makefile b/lib/Makefile
> index a8155c972f02..7f66fc1c163d 100644
> --- a/lib/Makefile
> +++ b/lib/Makefile
> @@ -84,6 +84,7 @@ obj-$(CONFIG_TEST_SORT) += test_sort.o
>   obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_keys.o
>   obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_key_base.o
>   obj-$(CONFIG_TEST_DYNAMIC_DEBUG) += test_dynamic_debug.o
> +obj-$(CONFIG_TEST_DYNAMIC_DEBUG_SUBMOD) += test_dynamic_debug_submod.o
>   obj-$(CONFIG_TEST_PRINTF) += test_printf.o
>   obj-$(CONFIG_TEST_SCANF) += test_scanf.o
>   
> @@ -228,6 +229,8 @@ obj-$(CONFIG_ARCH_NEED_CMPXCHG_1_EMU) += cmpxchg-emu.o
>   obj-$(CONFIG_DYNAMIC_DEBUG_CORE) += dynamic_debug.o
>   #ensure exported functions have prototypes
>   CFLAGS_dynamic_debug.o := -DDYNAMIC_DEBUG_MODULE
> +CFLAGS_test_dynamic_debug.o := -DDYNAMIC_DEBUG_MODULE
> +CFLAGS_test_dynamic_debug_submod.o := -DDYNAMIC_DEBUG_MODULE
>   
>   obj-$(CONFIG_SYMBOLIC_ERRNAME) += errname.o
>   
> diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c
> index 094d6e62a9d1..6bca0c6727d4 100644
> --- a/lib/dynamic_debug.c
> +++ b/lib/dynamic_debug.c
> @@ -43,13 +43,16 @@ extern struct _ddebug __start___dyndbg[];
>   extern struct _ddebug __stop___dyndbg[];
>   extern struct ddebug_class_map __start___dyndbg_classes[];
>   extern struct ddebug_class_map __stop___dyndbg_classes[];
> +extern struct ddebug_class_user __start___dyndbg_class_users[];
> +extern struct ddebug_class_user __stop___dyndbg_class_users[];
>   
>   struct ddebug_table {
>   	struct list_head link;
>   	const char *mod_name;
>   	struct _ddebug *ddebugs;
>   	struct ddebug_class_map *classes;
> -	unsigned int num_ddebugs, num_classes;
> +	struct ddebug_class_user *class_users;
> +	unsigned int num_ddebugs, num_classes, num_class_users;
>   };
>   
>   struct ddebug_query {
> @@ -148,23 +151,35 @@ static void vpr_info_dq(const struct ddebug_query *query, const char *msg)
>   		  query->first_lineno, query->last_lineno, query->class_string);
>   }
>   
> -#define __outvar /* filled by callee */
> -static struct ddebug_class_map *ddebug_find_valid_class(struct ddebug_table const *dt,
> -							const char *class_string,
> -							__outvar int *class_id)
> +#define vpr_dt_info(dt_p, msg_p, ...) ({				\
> +	struct ddebug_table const *_dt = dt_p;				\
> +	v2pr_info(msg_p " module:%s nd:%d nc:%d nu:%d\n", ##__VA_ARGS__, \
> +		  _dt->mod_name, _dt->num_ddebugs, _dt->num_classes,	\
> +		  _dt->num_class_users);				\
> +	})
> +
> +static int ddebug_find_valid_class(struct ddebug_table const *dt, const char *class_string)
>   {
>   	struct ddebug_class_map *map;
> +	struct ddebug_class_user *cli;
>   	int i, idx;
>   
> -	for (map = dt->classes, i = 0; i < dt->num_classes; i++, map++) {
> +	for (i = 0, map = dt->classes; i < dt->num_classes; i++, map++) {
>   		idx = match_string(map->class_names, map->length, class_string);
>   		if (idx >= 0) {
> -			*class_id = idx + map->base;
> -			return map;
> +			vpr_dt_info(dt, "good-class: %s.%s ", map->mod_name, class_string);
> +			return idx + map->base;
>   		}
>   	}
> -	*class_id = -ENOENT;
> -	return NULL;
> +	for (i = 0, cli = dt->class_users; i < dt->num_class_users; i++, cli++) {
> +		idx = match_string(cli->map->class_names, cli->map->length, class_string);
> +		if (idx >= 0) {
> +			vpr_dt_info(dt, "class-ref: %s -> %s.%s ",
> +				    cli->mod_name, cli->map->mod_name, class_string);
> +			return idx + cli->map->base;
> +		}
> +	}
> +	return -ENOENT;
>   }
>   
>   /*
> @@ -173,16 +188,14 @@ static struct ddebug_class_map *ddebug_find_valid_class(struct ddebug_table cons
>    * callsites, normally the same as number of changes.  If verbose,
>    * logs the changes.  Takes ddebug_lock.
>    */
> -static int ddebug_change(const struct ddebug_query *query,
> -			 struct flag_settings *modifiers)
> +static int ddebug_change(const struct ddebug_query *query, struct flag_settings *modifiers)
>   {
>   	int i;
>   	struct ddebug_table *dt;
>   	unsigned int newflags;
>   	unsigned int nfound = 0;
>   	struct flagsbuf fbuf, nbuf;
> -	struct ddebug_class_map *map = NULL;
> -	int __outvar valid_class;
> +	int valid_class;
>   
>   	/* search for matching ddebugs */
>   	mutex_lock(&ddebug_lock);
> @@ -194,8 +207,8 @@ static int ddebug_change(const struct ddebug_query *query,
>   			continue;
>   
>   		if (query->class_string) {
> -			map = ddebug_find_valid_class(dt, query->class_string, &valid_class);
> -			if (!map)
> +			valid_class = ddebug_find_valid_class(dt, query->class_string);
> +			if (valid_class < 0)
>   				continue;
>   		} else {
>   			/* constrain query, do not touch class'd callsites */
> @@ -559,7 +572,7 @@ static int ddebug_exec_query(char *query_string, const char *modname)
>   
>   /* handle multiple queries in query string, continue on error, return
>      last error or number of matching callsites.  Module name is either
> -   in param (for boot arg) or perhaps in query string.
> +   in the modname arg (for boot args) or perhaps in query string.
>   */
>   static int ddebug_exec_queries(char *query, const char *modname)
>   {
> @@ -688,12 +701,12 @@ static int param_set_dyndbg_module_classes(const char *instr,
>   }
>   
>   /**
> - * param_set_dyndbg_classes - class FOO >control
> + * param_set_dyndbg_classes - set all classes in a classmap
>    * @instr: string echo>d to sysfs, input depends on map_type
> - * @kp:    kp->arg has state: bits/lvl, map, map_type
> + * @kp:    kp->arg has state: bits/lvl, classmap, map_type
>    *
> - * Enable/disable prdbgs by their class, as given in the arguments to
> - * DECLARE_DYNDBG_CLASSMAP.  For LEVEL map-types, enforce relative
> + * For all classes in the classmap, enable/disable them per the input
> + * (depending on map_type).  For LEVEL map-types, enforce relative
>    * levels by bitpos.
>    *
>    * Returns: 0 or <0 if error.
> @@ -1038,12 +1051,17 @@ static void *ddebug_proc_next(struct seq_file *m, void *p, loff_t *pos)
>   static const char *ddebug_class_name(struct ddebug_table *dt, struct _ddebug *dp)
>   {
>   	struct ddebug_class_map *map = dt->classes;
> +	struct ddebug_class_user *cli = dt->class_users;
>   	int i;
>   
>   	for (i = 0; i < dt->num_classes; i++, map++)
>   		if (class_in_range(dp->class_id, map))
>   			return map->class_names[dp->class_id - map->base];
>   
> +	for (i = 0; i < dt->num_class_users; i++, cli++)
> +		if (class_in_range(dp->class_id, cli->map))
> +			return cli->map->class_names[dp->class_id - cli->map->base];
> +
>   	return NULL;
>   }
>   
> @@ -1124,31 +1142,129 @@ static const struct proc_ops proc_fops = {
>   	.proc_write = ddebug_proc_write
>   };
>   
> -static void ddebug_attach_module_classes(struct ddebug_table *dt, struct _ddebug_info *di)
> +#define vpr_cm_info(cm_p, msg_fmt, ...) ({				\
> +	struct ddebug_class_map const *_cm = cm_p;			\
> +	v2pr_info(msg_fmt " %s [%d..%d] %s..%s\n", ##__VA_ARGS__,	\
> +		  _cm->mod_name, _cm->base, _cm->base + _cm->length,	\
> +		  _cm->class_names[0], _cm->class_names[_cm->length - 1]); \
> +	})
> +
> +static void ddebug_sync_classbits(const struct kernel_param *kp, const char *modname)
> +{
> +	const struct ddebug_class_param *dcp = kp->arg;
> +
> +	/* clamp initial bitvec, mask off hi-bits */
> +	if (*dcp->bits & ~CLASSMAP_BITMASK(dcp->map->length)) {
> +		*dcp->bits &= CLASSMAP_BITMASK(dcp->map->length);
> +		v2pr_info("preset classbits: %lx\n", *dcp->bits);
> +	}
> +	/* force class'd prdbgs (in USEr module) to match (DEFINEr module) class-param */
> +	ddebug_apply_class_bitmap(dcp, dcp->bits, ~0, modname);
> +	ddebug_apply_class_bitmap(dcp, dcp->bits, 0, modname);
> +}
> +
> +static void ddebug_match_apply_kparam(const struct kernel_param *kp,
> +				      const struct ddebug_class_map *map,
> +				      const char *modnm)
> +{
> +	struct ddebug_class_param *dcp;
> +
> +	if (kp->ops != &param_ops_dyndbg_classes)
> +		return;
> +
> +	dcp = (struct ddebug_class_param *)kp->arg;
> +
> +	if (map == dcp->map) {
> +		v2pr_info(" kp:%s.%s =0x%lx", modnm, kp->name, *dcp->bits);
> +		vpr_cm_info(map, " %s mapped to: ", modnm);
> +		ddebug_sync_classbits(kp, modnm);
> +	}
> +}
> +
> +static void ddebug_apply_params(const struct ddebug_class_map *cm, const char *modnm)
> +{
> +	const struct kernel_param *kp;
> +#if IS_ENABLED(CONFIG_MODULES)
> +	int i;
> +
> +	if (cm->mod) {
> +		vpr_cm_info(cm, "loaded classmap: %s", modnm);
> +		/* ifdef protects the cm->mod->kp deref */
> +		for (i = 0, kp = cm->mod->kp; i < cm->mod->num_kp; i++, kp++)
> +			ddebug_match_apply_kparam(kp, cm, modnm);
> +	}
> +#endif
> +	if (!cm->mod) {
> +		vpr_cm_info(cm, "builtin classmap: %s", modnm);
> +		for (kp = __start___param; kp < __stop___param; kp++)
> +			ddebug_match_apply_kparam(kp, cm, modnm);
> +	}
> +}
> +
> +/*
> + * Find this module's classmaps in a sub/whole-range of the builtin/
> + * modular classmap vector/section.  Save the start and length of the
> + * subrange at its edges.
> + */
> +static void ddebug_attach_module_classes(struct ddebug_table *dt,
> +					 const struct _ddebug_info *di)
>   {
>   	struct ddebug_class_map *cm;
>   	int i, nc = 0;
>   
> -	/*
> -	 * Find this module's classmaps in a subrange/wholerange of
> -	 * the builtin/modular classmap vector/section.  Save the start
> -	 * and length of the subrange at its edges.
> -	 */
> -	for (cm = di->classes, i = 0; i < di->num_classes; i++, cm++) {
> -
> +	for (i = 0, cm = di->classes; i < di->num_classes; i++, cm++) {
>   		if (!strcmp(cm->mod_name, dt->mod_name)) {
> -			if (!nc) {
> -				v2pr_info("start subrange, class[%d]: module:%s base:%d len:%d ty:%d\n",
> -					  i, cm->mod_name, cm->base, cm->length, cm->map_type);
> +			vpr_cm_info(cm, "classes[%d]:", i);
> +			if (!nc++)
>   				dt->classes = cm;
> -			}
> -			nc++;
>   		}
>   	}
> -	if (nc) {
> -		dt->num_classes = nc;
> -		vpr_info("module:%s attached %d classes\n", dt->mod_name, nc);
> +	if (!nc)
> +		return;
> +
> +	vpr_info("module:%s attached %d classes\n", dt->mod_name, nc);
> +	dt->num_classes = nc;
> +
> +	for (i = 0, cm = dt->classes; i < dt->num_classes; i++, cm++)
> +		ddebug_apply_params(cm, cm->mod_name);
> +}
> +
> +/*
> + * propagates class-params thru their classmaps to class-users.  this
> + * means a query against the dt/module, which means it must be on the
> + * list to be seen by ddebug_change.
> + */
> +static void ddebug_attach_user_module_classes(struct ddebug_table *dt,
> +					      const struct _ddebug_info *di)
> +{
> +	struct ddebug_class_user *cli;
> +	int i, nc = 0;
> +
> +	/*
> +	 * For builtins: scan the array, find start/length of this
> +	 * module's refs, save to dt.  For loadables, this is the
> +	 * whole array.
> +	 */
> +	for (i = 0, cli = di->class_users; i < di->num_class_users; i++, cli++) {
> +		if (WARN_ON_ONCE(!cli || !cli->map || !cli->mod_name))
> +			continue;
> +		if (!strcmp(cli->mod_name, dt->mod_name)) {
> +			vpr_cm_info(cli->map, "class_ref[%d] %s -> %s", i,
> +				    cli->mod_name, cli->map->mod_name);
> +			if (!nc++)
> +				dt->class_users = cli;
> +		}
>   	}
> +	if (!nc)
> +		return;
> +
> +	dt->num_class_users = nc;
> +
> +	/* now iterate dt */
> +	for (i = 0, cli = dt->class_users; i < dt->num_class_users; i++, cli++)
> +		ddebug_apply_params(cli->map, cli->mod_name);
> +
> +	vpr_dt_info(dt, "attach-client-module: ");
>   }
>   
>   /*
> @@ -1158,6 +1274,8 @@ static void ddebug_attach_module_classes(struct ddebug_table *dt, struct _ddebug
>   static int ddebug_add_module(struct _ddebug_info *di, const char *modname)
>   {
>   	struct ddebug_table *dt;
> +	struct _ddebug *iter;
> +	int i, class_ct = 0;
>   
>   	if (!di->num_descs)
>   		return 0;
> @@ -1181,13 +1299,20 @@ static int ddebug_add_module(struct _ddebug_info *di, const char *modname)
>   
>   	INIT_LIST_HEAD(&dt->link);
>   
> -	if (di->classes && di->num_classes)
> +	for (i = 0, iter = di->descs; i < di->num_descs; i++, iter++)
> +		if (iter->class_id != _DPRINTK_CLASS_DFLT)
> +			class_ct++;
> +
> +	if (class_ct && di->num_classes)
>   		ddebug_attach_module_classes(dt, di);
>   
>   	mutex_lock(&ddebug_lock);
>   	list_add_tail(&dt->link, &ddebug_tables);
>   	mutex_unlock(&ddebug_lock);
>   
> +	if (class_ct && di->num_class_users)
> +		ddebug_attach_user_module_classes(dt, di);
> +
>   	vpr_info("%3u debug prints in module %s\n", di->num_descs, modname);
>   	return 0;
>   }
> @@ -1337,8 +1462,10 @@ static int __init dynamic_debug_init(void)
>   	struct _ddebug_info di = {
>   		.descs = __start___dyndbg,
>   		.classes = __start___dyndbg_classes,
> +		.class_users = __start___dyndbg_class_users,
>   		.num_descs = __stop___dyndbg - __start___dyndbg,
>   		.num_classes = __stop___dyndbg_classes - __start___dyndbg_classes,
> +		.num_class_users = __stop___dyndbg_class_users - __start___dyndbg_class_users,
>   	};
>   
>   #ifdef CONFIG_MODULES
> diff --git a/lib/test_dynamic_debug.c b/lib/test_dynamic_debug.c
> index 74d183ebf3e0..1838f62738c4 100644
> --- a/lib/test_dynamic_debug.c
> +++ b/lib/test_dynamic_debug.c
> @@ -6,11 +6,15 @@
>    *      Jim Cromie	<jim.cromie@gmail.com>
>    */
>   
> -#define pr_fmt(fmt) "test_dd: " fmt
> +#if defined(TEST_DYNAMIC_DEBUG_SUBMOD)
> +  #define pr_fmt(fmt) "test_dd_submod: " fmt
> +#else
> +  #define pr_fmt(fmt) "test_dd: " fmt
> +#endif
>   
>   #include <linux/module.h>
>   
> -/* run tests by reading or writing sysfs node: do_prints */
> +/* re-gen output by reading or writing sysfs node: do_prints */
>   
>   static void do_prints(void); /* device under test */
>   static int param_set_do_prints(const char *instr, const struct kernel_param *kp)
> @@ -29,24 +33,39 @@ static const struct kernel_param_ops param_ops_do_prints = {
>   };
>   module_param_cb(do_prints, &param_ops_do_prints, NULL, 0600);
>   
> -/*
> - * Using the CLASSMAP api:
> - * - classmaps must have corresponding enum
> - * - enum symbols must match/correlate with class-name strings in the map.
> - * - base must equal enum's 1st value
> - * - multiple maps must set their base to share the 0-30 class_id space !!
> - *   (build-bug-on tips welcome)
> - * Additionally, here:
> - * - tie together sysname, mapname, bitsname, flagsname
> - */
> -#define DD_SYS_WRAP(_model, _flags)					\
> -	static unsigned long bits_##_model;				\
> -	static struct ddebug_class_param _flags##_model = {		\
> +#define CLASSMAP_BITMASK(width, base) (((1UL << (width)) - 1) << (base))
> +
> +/* sysfs param wrapper, proto-API */
> +#define DYNDBG_CLASSMAP_PARAM_(_model, _flags, _init)			\
> +	static unsigned long bits_##_model = _init;			\
> +	static struct ddebug_class_param _flags##_##_model = {		\
>   		.bits = &bits_##_model,					\
>   		.flags = #_flags,					\
>   		.map = &map_##_model,					\
>   	};								\
> -	module_param_cb(_flags##_##_model, &param_ops_dyndbg_classes, &_flags##_model, 0600)
> +	module_param_cb(_flags##_##_model, &param_ops_dyndbg_classes,	\
> +			&_flags##_##_model, 0600)
> +#ifdef DEBUG
> +#define DYNDBG_CLASSMAP_PARAM(_model, _flags)  DYNDBG_CLASSMAP_PARAM_(_model, _flags, ~0)
> +#else
> +#define DYNDBG_CLASSMAP_PARAM(_model, _flags)  DYNDBG_CLASSMAP_PARAM_(_model, _flags, 0)
> +#endif
> +
> +/*
> + * Demonstrate/test all 4 class-typed classmaps with a sys-param.
> + *
> + * Each is 3 part: client-enum decl, _DEFINE, _PARAM.
> + * Declare them in blocks to show patterns of use (repetitions and
> + * changes) within each.
> + *
> + * 1st, dyndbg expects a client-provided enum-type as source of
> + * category/classid truth.  DRM has DRM_UT_<CORE,DRIVER,KMS,etc>.
> + *
> + * Modules with multiple CLASSMAPS must have enums with distinct
> + * value-ranges, arranged below with explicit enum_sym = X inits.
> + *
> + * Declare all 4 enums now, for different types
> + */
>   
>   /* numeric input, independent bits */
>   enum cat_disjoint_bits {
> @@ -60,26 +79,51 @@ enum cat_disjoint_bits {
>   	D2_LEASE,
>   	D2_DP,
>   	D2_DRMRES };
> -DECLARE_DYNDBG_CLASSMAP(map_disjoint_bits, DD_CLASS_TYPE_DISJOINT_BITS, 0,
> -			"D2_CORE",
> -			"D2_DRIVER",
> -			"D2_KMS",
> -			"D2_PRIME",
> -			"D2_ATOMIC",
> -			"D2_VBL",
> -			"D2_STATE",
> -			"D2_LEASE",
> -			"D2_DP",
> -			"D2_DRMRES");
> -DD_SYS_WRAP(disjoint_bits, p);
> -DD_SYS_WRAP(disjoint_bits, T);
>   
>   /* numeric verbosity, V2 > V1 related */
>   enum cat_level_num { V0 = 14, V1, V2, V3, V4, V5, V6, V7 };
> -DECLARE_DYNDBG_CLASSMAP(map_level_num, DD_CLASS_TYPE_LEVEL_NUM, 14,
> -		       "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7");
> -DD_SYS_WRAP(level_num, p);
> -DD_SYS_WRAP(level_num, T);
> +
> +/* recapitulate DRM's parent(drm.ko) <-- _submod(drivers,helpers) */
> +#if !defined(TEST_DYNAMIC_DEBUG_SUBMOD)

It was not clear at first read that the test_dynamic_debug.c code is 
used twice.

What do you think about creating a third file "common" with 
do_prints/do_levels/... implementations and avoid this #if !defined?

> +/*
> + * In single user, or parent / coordinator (drm.ko) modules, define
> + * classmaps on the client enums above, and then declares the PARAMS
> + * ref'g the classmaps.  Each is exported.
> + */
> +DYNDBG_CLASSMAP_DEFINE(map_disjoint_bits, DD_CLASS_TYPE_DISJOINT_BITS,

map_disjoint_bits is defined in both test_dynamic_debug.c and 
test_dynamic_debug_submodule.c, is it normal/required?

> +		       D2_CORE,
> +		       "D2_CORE",
> +		       "D2_DRIVER",
> +		       "D2_KMS",
> +		       "D2_PRIME",
> +		       "D2_ATOMIC",
> +		       "D2_VBL",
> +		       "D2_STATE",
> +		       "D2_LEASE",
> +		       "D2_DP",
> +		       "D2_DRMRES");
> +
> +DYNDBG_CLASSMAP_DEFINE(map_level_num, DD_CLASS_TYPE_LEVEL_NUM,
> +		       V0, "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7");
> +
> +/*
> + * now add the sysfs-params
> + */
> +
> +DYNDBG_CLASSMAP_PARAM(disjoint_bits, p);
> +DYNDBG_CLASSMAP_PARAM(level_num, p);
> +
> +#else /* TEST_DYNAMIC_DEBUG_SUBMOD */
> +
> +/*
> + * in submod/drm-drivers, use the classmaps defined in top/parent
> + * module above.
> + */
> +
> +DYNDBG_CLASSMAP_USE(map_disjoint_bits);
> +DYNDBG_CLASSMAP_USE(map_level_num);
> +
> +#endif
>   
>   /* stand-in for all pr_debug etc */
>   #define prdbg(SYM) __pr_debug_cls(SYM, #SYM " msg\n")
> @@ -115,6 +159,7 @@ static void do_levels(void)
>   
>   static void do_prints(void)
>   {
> +	pr_debug("do_prints:\n");
>   	do_cats();
>   	do_levels();

I just observed it, is the ordering of logs garanteed in 
/proc/dynamic_debug/control?

When I run this test, I have this order:

do_cats =_ "doing categories\n"
[...]
do_levels =_ "doing levels\n"
[...]
do_prints =_ "do_prints:\n"
test_dynamic_debug_init =_ "init start\n"
test_dynamic_debug_init =_ "init done\n"
test_dynamic_debug_exit =_ "exited\n"

Which is clearly not the code execution order.

>   }
> diff --git a/lib/test_dynamic_debug_submod.c b/lib/test_dynamic_debug_submod.c
> new file mode 100644
> index 000000000000..9a893402ce1a
> --- /dev/null
> +++ b/lib/test_dynamic_debug_submod.c
> @@ -0,0 +1,10 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Kernel module for testing dynamic_debug
> + *
> + * Authors:
> + *      Jim Cromie	<jim.cromie@gmail.com>
> + */
> +
> +#define TEST_DYNAMIC_DEBUG_SUBMOD
> +#include "test_dynamic_debug.c"
Jim Cromie March 16, 2025, 7:46 p.m. UTC | #2
hi Louis,

On Tue, Feb 25, 2025 at 7:16 AM Louis Chauvet <louis.chauvet@bootlin.com> wrote:
>
>
>
> Le 25/01/2025 à 07:45, Jim Cromie a écrit :
> > DECLARE_DYNDBG_CLASSMAP() has a design error; its usage fails a basic
> > K&R rule: "define once, refer many times".
> >
> > It is used across DRM core & drivers, each use re-defines the classmap
> > understood by that module; and all must match for the modules to
> > respond together when DRM.debug categories are enabled.  This is
> > brittle; a maintenance foot-gun.
> >
> > Further, its culpable in the CONFIG_DRM_USE_DYNAMIC_DEBUG=Y
> > regression; its use in both core & drivers obfuscates the 2 roles.
> > So 1st drm.ko loads, and dyndbg initializes its DRM.debug callsites,
> > then a drm-driver loads, but too late - it missed the DRM.debug
> > enablement.
> >
> > So replace it with 2 macros:
> >    DYNDBG_CLASSMAP_DEFINE - invoked once from core - drm.ko
> >    DYNDBG_CLASSMAP_USE    - from all drm drivers and helpers.
> >
> > DYNDBG_CLASSMAP_DEFINE: it just modifies DECLARE_DYNDBG_CLASSMAP,
> > dropping drop the static qualifier, and exports it instead.
> >
> > DYNDBG_CLASSMAP_USE: then refers to the exported var by name:
> >    used from drivers, helper-mods
> >    lets us drop the repetitive "classname" declarations
> >    fixes 2nd-defn problem
> >    creates a ddebug_class_user record in new __dyndbg_class_users section
> >    this allows ddebug_add_module(etal) to handle users differently.
> >
> > DECLARE_DYNDBG_CLASSMAP is preserved temporarily, to decouple DRM
> > adaptation work and avoid compile-errs before its done.  IOW, DRM gets
> > fixed when they commit the adopt-new-api patches, and remove the
> > BROKEN marking.
> >
> > The DEFINE,USE distinction, and the separate classmap-use record,
> > allows dyndbg to initialize the driver's & helper's DRM.debug
> > callsites separately after each is modprobed.
> >
> > Basically, the classmap init-scan is repeated for classmap-users.
> >
> > To review, dyndbg's existing __dyndbg_classes[] section does:
> >
> > . catalogs the classmaps defined by a module (or builtin modules)
> > . tells dyndbg the module has class'd prdbgs, allowing >control
> > . DYNDBG_CLASSMAP_DEFINE creates classmaps in this section.
> >
> > This patch adds __dyndbg_class_users[] section:
> >
> > . catalogs uses/references to the classmap definitions.
> > . authorizes dyndbg to >control those class'd prdbgs in ref'g module.
> > . DYNDBG_CLASSMAP_USE() creates classmap-user records in this new section.
> >
> > Now ddebug_add_module(etal) can handle classmap-uses similar to (and
> > after) classmaps; when a dependent module is loaded, if it has
> > classmap-uses (to a classmap-def in another module), that module's
> > kernel params are scanned to find if it has a kparam that is wired to
> > dyndbg's param-ops, and whose classmap is the one being ref'd.
> >
> > To support this, theres a few data/header changes:
> >
> > new struct ddebug_class_user
> >    contains: user-module-name, &classmap-defn
> >    it records drm-driver's use of a classmap in the section, allowing lookup
> >
> > struct ddebug_info gets 2 new fields for the new sections:
> >    class_users, num_class_users.
> >    set by dynamic_debug_init() for builtins.
> >    or by kernel/module/main:load_info() for loadable modules.
> >
> > vmlinux.lds.h: new BOUNDED_SECTION for __dyndbg_class_users
> >
> > dynamic_debug.c has 2 changes in ddebug_add_module(), ddebug_change():
> >
> > ddebug_add_module() already calls ddebug_attach_module_classes()
> > to handle classmaps DEFINEd by a module, now it also calls
> > ddebug_attach_user_module_classes() to handle USEd classmaps.  To
> > avoid this work when possible, 1st scan the module's descriptors and
> > count the number of class'd pr_debugs.
> >
> > ddebug_attach_user_module_classes() scans the module's class_users
> > section, follows the refs to the parent's classmap, and calls
> > ddebug_apply_params() on each.  It also avoids work by checking the
> > module's class-ct.
> >
> > ddebug_apply_params(new fn):
> >
> > It scans module's/builtin kernel-params, calls ddebug_match_apply_kparam
> > for each to find any params/sysfs-nodes which may be wired to a classmap.
> >
> > ddebug_match_apply_kparam(new fn):
> >
> > 1st, it tests the kernel-param.ops is dyndbg's; this guarantees that
> > the attached arg is a struct ddebug_class_param, which has a ref to
> > the param's state, and to the classmap defining the param's handling.
> >
> > 2nd, it requires that the classmap ref'd by the kparam is the one
> > we're called for; modules can use many separate classmaps (as
> > test_dynamic_debug does).
> >
> > Then apply the "parent" kparam's setting to the dependent module,
> > using ddebug_apply_class_bitmap().
> >
> > ddebug_change(and callees) also gets adjustments:
> >
> > ddebug_find_valid_class(): This does a search over the module's
> > classmaps, looking for the class FOO echo'd to >control.  So now it
> > searches over __dyndbg_class_users[] after __dyndbg_classes[].
> >
> > ddebug_class_name(): return class-names for defined AND used classes.
> >
> > test_dynamic_debug.c, test_dynamic_debug_submod.c:
> >
> > This demonstrates the 2 types of classmaps & sysfs-params, following
> > the 4-part recipe:
> >
> > 1. define an enum for the classmap: DRM.debug has DRM_UT_{CORE,KMS,...}
> >     multiple classes must share 0-62 classid space.
> > 2. DYNDBG_CLASSMAP_DEFINE(.. DRM_UT_{CORE,KMS,...})
> > 3. DYNDBG_CLASSMAP_PARAM* (classmap)
> > 4. DYNDBG_CLASSMAP_USE()
> >     by _submod only, skipping 2,3
> >
> > Move all the enum declarations together, to better explain how they
> > share the 0..62 class-id space available to a module (non-overlapping
> > subranges).
> >
> > reorg macros 2,3 by name.  This gives a tabular format, making it easy
> > to see the pattern of repetition, and the points of change.
> >
> > And extend the test to replicate the 2-module (parent & dependent)
> > scenario which caused the CONFIG_DRM_USE_DYNAMIC_DEBUG=y regression
> > seen in drm & drivers.
> >
> > The _submod.c is a 2-line file: #define _SUBMOD, #include parent.
> >
> > This gives identical complements of prdbgs in parent & _submod, and
> > thus identical print behavior when all of: >control, >params, and
> > parent->_submod propagation are working correctly.
> >
> > It also puts all the parent/_submod declarations together in the same
> > source, with the new ifdef _SUBMOD block invoking DYNDBG_CLASSMAP_USE
> > for the 2 test-interfaces.  I think this is clearer.
> >
> > These 2 modules are both tristate, allowing 3 super/sub combos: Y/Y,
> > Y/M, M/M (not N/N).  Y/Y testing exposed a missing __align(8) in the
> > _METADATA macro, which M/M didn't see because the module-loader memory
> > placement constrains it instead.
> >
> > NB: this patch ignores a checkpatch do-while warning which is fixed by
> > a preceding commit, which would be wrong for declarative macros like
> > module_param_named() etc.
> >
> > Fixes: aad0214f3026 ("dyndbg: add DECLARE_DYNDBG_CLASSMAP macro")
> > Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
> > ---
> > v9 - commit-msg tweaks
> >       DRM:CHECK warnings on macros: add parens
> >       extern DEFINEd _var, static classnames
> >       change ddebug_class_user.user_mod_name to .mod_name
> >       simplify ddebug_find_valid_class
> >       improve vpr_cm_info msg format
> >       wrap (base) in macro body
> >       move __DYNDBG_CLASSMAP_CHECK above kdoc for DYNDBG_CLASSMAP_DEFINE
> >
> > v8 - split drm parts to separate commits.
> >       preserve DECLARE_DYNDBG_CLASSMAP to decouple DRM, no flag day.
> >       fixup block comment
> >
> > v7 - previous submission-blocking bug:
> >
> > missing __align(8) in DYNAMIC_DEBUG_DECLARE_METADATA on
> > ddebug_class_user caused corrupt records, but only for builtin
> > modules; module loader code probably pinned allocations to the right
> > alignment naturally, hiding the bug for typical builds.
> >
> > v6- get rid of WARN_ON_ONCE
> > v?- fix _var expanded 2x in macro
> >
> > dyndbg:
> >
> > This fn formerly returned the map which contained the class (thus
> > validating it), and as a side-effect set the class-id in an outvar.
> >
> > But the caller didn't use the map (after checking its not null), only
> > the valid class-id.  So simplify the fn to return the class-id of the
> > validated classname, or -ENOENT when the queried classname is not
> > found.
> >
> > Convey more useful info in the debug-msg: print class-names[0,last],
> > and [base,+len] instead of the class-type printout, which is currently
> > always "type:DISJOINT_BITS".  And drop ddebug_classmap_typenames,
> > which is now unused.
> >
> > [root@v6 b0-dd]# modprobe test_dynamic_debug_submod
> > [   18.864962] dyndbg: loaded classmap: test_dynamic_debug [16..24] V0..V7
> > [   18.865046] dyndbg:  found kp:p_level_num =0x0
> > [   18.865048] dyndbg:   mapped to: test_dynamic_debug [16..24] V0..V7
> > [   18.865164] dyndbg:   p_level_num: lvl:0 bits:0x0
> > [   18.865217] dyndbg: loaded classmap: test_dynamic_debug [0..10] D2_CORE..D2_DRMRES
> > [   18.865297] dyndbg:  found kp:p_disjoint_bits =0x0
> > [   18.865298] dyndbg:   mapped to: test_dynamic_debug [0..10] D2_CORE..D2_DRMRES
> > [   18.865424] dyndbg:   p_disjoint_bits: classbits: 0x0
> > [   18.865472] dyndbg: module:test_dynamic_debug attached 2 classmaps
> > [   18.865533] dyndbg:  23 debug prints in module test_dynamic_debug
> > [   18.866558] dyndbg: loaded classmap: test_dynamic_debug_submod [16..24] V0..V7
> > [   18.866698] dyndbg:  found kp:p_level_num =0x0
> > [   18.866699] dyndbg:   mapped to: test_dynamic_debug_submod [16..24] V0..V7
> > [   18.866865] dyndbg:   p_level_num: lvl:0 bits:0x0
> > [   18.866926] dyndbg: loaded classmap: test_dynamic_debug_submod [0..10] D2_CORE..D2_DRMRES
> > [   18.867026] dyndbg:  found kp:p_disjoint_bits =0x0
> > [   18.867027] dyndbg:   mapped to: test_dynamic_debug_submod [0..10] D2_CORE..D2_DRMRES
> > [   18.867193] dyndbg:   p_disjoint_bits: classbits: 0x0
> > [   18.867255] dyndbg: module:test_dynamic_debug_submod attached 2 classmap uses
> > [   18.867351] dyndbg:  23 debug prints in module test_dynamic_debug_submod
> > ---
> >   MAINTAINERS                       |   2 +-
> >   include/asm-generic/vmlinux.lds.h |   1 +
> >   include/linux/dynamic_debug.h     |  83 ++++++++++--
> >   kernel/module/main.c              |   3 +
> >   lib/Kconfig.debug                 |  24 +++-
> >   lib/Makefile                      |   3 +
> >   lib/dynamic_debug.c               | 203 ++++++++++++++++++++++++------
> >   lib/test_dynamic_debug.c          | 111 +++++++++++-----
> >   lib/test_dynamic_debug_submod.c   |  10 ++
>
> Hi Jim,
>
> While reading the files test_dynamic_debug*, I was wondering, they are
> more samples than tests. Does it make sense to move them in samples/?
> There is no need to do it in this series.
>
> >   9 files changed, 355 insertions(+), 85 deletions(-)
> >   create mode 100644 lib/test_dynamic_debug_submod.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 0fa7c5728f1e..38afccb3b71e 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -8041,7 +8041,7 @@ M:      Jim Cromie <jim.cromie@gmail.com>
> >   S:  Maintained
> >   F:  include/linux/dynamic_debug.h
> >   F:  lib/dynamic_debug.c
> > -F:   lib/test_dynamic_debug.c
> > +F:   lib/test_dynamic_debug*.c
> >
> >   DYNAMIC INTERRUPT MODERATION
> >   M:  Tal Gilboa <talgi@nvidia.com>
> > diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
> > index 54504013c749..eb93fd09832b 100644
> > --- a/include/asm-generic/vmlinux.lds.h
> > +++ b/include/asm-generic/vmlinux.lds.h
> > @@ -366,6 +366,7 @@ defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPELLER_CLANG)
> >       /* implement dynamic printk debug */                            \
> >       . = ALIGN(8);                                                   \
> >       BOUNDED_SECTION_BY(__dyndbg_classes, ___dyndbg_classes)         \
> > +     BOUNDED_SECTION_BY(__dyndbg_class_users, ___dyndbg_class_users) \
> >       BOUNDED_SECTION_BY(__dyndbg, ___dyndbg)                         \
> >       CODETAG_SECTIONS()                                              \
> >       LIKELY_PROFILE()                                                \
> > diff --git a/include/linux/dynamic_debug.h b/include/linux/dynamic_debug.h
> > index c8102e89beb2..dc610a12b91c 100644
> > --- a/include/linux/dynamic_debug.h
> > +++ b/include/linux/dynamic_debug.h
> > @@ -71,9 +71,28 @@ enum ddebug_class_map_type {
> >        */
> >   };
> >
> > +/*

BTW, Im gonna refine this ...

> > + * dyndbg-classmaps are devised to support DRM.debug directly:
> > + *    10 enum-vals: DRM_UT_* define the categories
> > + *   ~23 categorized *_dbg() macros, each passing a DRM_UT_* val as 1st arg
> > + *     2 macros below them: drm_dev_dbg, __drm_dbg
> > + * ~5000 calls to the categorized macros, across all of drivers/gpu/drm/
> > + *
> > + * When CONFIG_DRM_USE_DYNAMIC_DEBUG=y, the 2 low macros are redefined
> > + * to invoke _dynamic_func_call_cls().  This compiles the category
> > + * into each callsite's class_id field, where dyndbg can select on it
> > + * and alter a callsite's patch-state, avoiding repeated __drm_debug
> > + * checks.
> > + *
> > + * To make the callsites manageable from the >control file, authors
> > + * provide a "classmap" of names to class_ids in use by the module(s),
> > + * usually by stringifying the enum-vals.  Modules with multiple
> > + * classmaps must arrange to share the 0..62 class_id space.
> > + */
> > +
> >   struct ddebug_class_map {
> > -     struct module *mod;
> > -     const char *mod_name;   /* needed for builtins */
> > +     const struct module *mod;               /* NULL for builtins */
> > +     const char *mod_name;
>
> Is this change belongs to this patch? If not can you move this in a
> "cleanup" patch?

It belongs, insofar as the mod field is actually NULL checked to determine
builtin vs modular, mod_name is backup.
Also its more specific than "needed for builtins"

But, grumble, ya, the above was an existing test, not a new one.
OTOH - I guess Ive just written the cleanup commit msg :-)

>
> >       const char **class_names;
> >       const int length;
> >       const int base;         /* index of 1st .class_id, allows split/shared space */
> > @@ -81,11 +100,34 @@ struct ddebug_class_map {
> >   };
> >
> >   /**
> > - * DECLARE_DYNDBG_CLASSMAP - declare classnames known by a module
> > - * @_var:   a struct ddebug_class_map, passed to module_param_cb
> > - * @_type:  enum class_map_type, chooses bits/verbose, numeric/symbolic
> > - * @_base:  offset of 1st class-name. splits .class_id space
> > - * @classes: class-names used to control class'd prdbgs
> > + * DYNDBG_CLASSMAP_DEFINE - define debug classes used by a module.
> > + * @_var:   name of the classmap, exported for other modules coordinated use.
> > + * @_mapty: enum ddebug_class_map_type: 0:DISJOINT - independent, 1:LEVEL - v2>v1> + * @_base:  reserve N classids starting at _base, to split 0..62
> classid space
> > + * @classes: names of the N classes.
> > + *
> > + * This tells dyndbg what class_ids the module is using: _base..+N, by
> > + * mapping names onto them.  This qualifies "class NAME" >controls on
> > + * the defining module, ignoring unknown names.
> > + */
> > +#define DYNDBG_CLASSMAP_DEFINE(_var, _mapty, _base, ...)             \
> > +     static const char *_var##_classnames[] = { __VA_ARGS__ };       \
> > +     extern struct ddebug_class_map _var;                            \
> > +     struct ddebug_class_map __aligned(8) __used                     \
> > +             __section("__dyndbg_classes") _var = {                  \
> > +             .mod = THIS_MODULE,                                     \
> > +             .mod_name = KBUILD_MODNAME,                             \
> > +             .base = (_base),                                        \
> > +             .map_type = (_mapty),                                   \
> > +             .length = ARRAY_SIZE(_var##_classnames),                \
> > +             .class_names = _var##_classnames,                       \
> > +     };                                                              \
> > +     EXPORT_SYMBOL(_var)
> > +
> > +/*
> > + * XXX: keep this until DRM adapts to use the DEFINE/USE api, it
> > + * differs from DYNDBG_CLASSMAP_DEFINE by the static replacing the
> > + * extern/EXPORT on the struct init.
> >    */
> >   #define DECLARE_DYNDBG_CLASSMAP(_var, _maptype, _base, ...)         \
> >       static const char *_var##_classnames[] = { __VA_ARGS__ };       \
> > @@ -99,12 +141,37 @@ struct ddebug_class_map {
> >               .class_names = _var##_classnames,                       \
> >       }
> >
> > +struct ddebug_class_user {
> > +     char *mod_name;
> > +     struct ddebug_class_map *map;
> > +};
> > +
> > +/**
> > + * DYNDBG_CLASSMAP_USE - refer to a classmap, DEFINEd elsewhere.
> > + * @_var: name of the exported classmap var
> > + *
> > + * This tells dyndbg that the module has prdbgs with classids defined
> > + * in the named classmap.  This qualifies "class NAME" >controls on
> > + * the user module, ignoring unknown names.
> > + */
> > +#define DYNDBG_CLASSMAP_USE(_var)                                    \
> > +     DYNDBG_CLASSMAP_USE_(_var, __UNIQUE_ID(ddebug_class_user))
> > +#define DYNDBG_CLASSMAP_USE_(_var, _uname)                           \
> > +     extern struct ddebug_class_map _var;                            \
> > +     static struct ddebug_class_user __aligned(8) __used             \
> > +     __section("__dyndbg_class_users") _uname = {                    \
> > +             .mod_name = KBUILD_MODNAME,                             \
> > +             .map = &(_var),                                         \
> > +     }
> > +
>
> I'm not sure I understand properly how __section works, but can we use
> multiple DYNDBG_CLASSMAP_USE in one module? Can we also use
> DYNDBG_CLASSMAP_DEFINE while also importing an other classmap
> DYNDBG_CLASSMAP_USE?
>

Yes, its supposed to work that way.

I havent tested that specific scenario (yet), but
a _USEr module, like test-dynamic-debug-submod,
could also _DEFINE its own, as long as it honors
the class-id mapping it is using and therefore sharing.
The on-modprobe conflict check should catch this condition.

And __section (ISTM) accumulates entries, typically static struct var
initializations.
AFAICT, scanning the sections is how these { scoped statics } are
often reachable.

For example, dd's _METADATA_ builds a { static _ddebug } for every pr_debug.
They all go into the __dyndbg section (renamed with _descriptors suffix soon),
in the order their respective definer objects are linked.

include/asm-generic/vmlinux.lds.h  then places the __dyndbg_* sections
into DATA, along with lots of other freight, for the various
mechanisms they serve.




> If not, does it make sense to allow it (for example MFD devices can
> touch multiple subsystems)?

We have another use case !
Do you know your way around that case ?

Note that DEFINEr  & USEr calls set up linkage dependencies,
As long as these are consistent with other module deps,
it should work.


>
> >   /* encapsulate linker provided built-in (or module) dyndbg data */
> >   struct _ddebug_info {
> >       struct _ddebug *descs;
> >       struct ddebug_class_map *classes;
> > +     struct ddebug_class_user *class_users;
> >       unsigned int num_descs;
> >       unsigned int num_classes;
> > +     unsigned int num_class_users;
> >   };
> >
> >   struct ddebug_class_param {
> > @@ -205,7 +272,7 @@ void __dynamic_ibdev_dbg(struct _ddebug *descriptor,
> >    * (|_no_desc):     former gets callsite descriptor as 1st arg (for prdbgs)
> >    */
> >   #define __dynamic_func_call_cls(id, cls, fmt, func, ...) do {       \
> > -     DEFINE_DYNAMIC_DEBUG_METADATA_CLS(id, cls, fmt);        \
> > +     DEFINE_DYNAMIC_DEBUG_METADATA_CLS((id), cls, fmt);      \
>
> Can you move this in a "cleanup" patch?
>
> >       if (DYNAMIC_DEBUG_BRANCH(id))                           \
> >               func(&id, ##__VA_ARGS__);                       \
> >   } while (0)
> > diff --git a/kernel/module/main.c b/kernel/module/main.c
> > index 5399c182b3cb..c394a0c6e8c6 100644
> > --- a/kernel/module/main.c
> > +++ b/kernel/module/main.c
> > @@ -2546,6 +2546,9 @@ static int find_module_sections(struct module *mod, struct load_info *info)
> >       mod->dyndbg_info.classes = section_objs(info, "__dyndbg_classes",
> >                                               sizeof(*mod->dyndbg_info.classes),
> >                                               &mod->dyndbg_info.num_classes);
> > +     mod->dyndbg_info.class_users = section_objs(info, "__dyndbg_class_users",
> > +                                                 sizeof(*mod->dyndbg_info.class_users),
> > +                                                &mod->dyndbg_info.num_class_users);
> >   #endif
> >
> >       return 0;
> > diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> > index f3d723705879..933f69ef87f8 100644
> > --- a/lib/Kconfig.debug
> > +++ b/lib/Kconfig.debug
> > @@ -2884,12 +2884,26 @@ config TEST_STATIC_KEYS
> >         If unsure, say N.
> >
> >   config TEST_DYNAMIC_DEBUG
> > -     tristate "Test DYNAMIC_DEBUG"
> > -     depends on DYNAMIC_DEBUG
> > +     tristate "Build test-dynamic-debug module"
> > +     depends on DYNAMIC_DEBUG || DYNAMIC_DEBUG_CORE
> >       help
> > -       This module registers a tracer callback to count enabled
> > -       pr_debugs in a 'do_debugging' function, then alters their
> > -       enablements, calls the function, and compares counts.
> > +       This module exercises/demonstrates dyndbg's classmap API, by
> > +       creating 2 classes: a DISJOINT classmap (supporting DRM.debug)
> > +       and a LEVELS/VERBOSE classmap (like verbose2 > verbose1).
> > +
> > +       If unsure, say N.
> > +
> > +config TEST_DYNAMIC_DEBUG_SUBMOD
> > +     tristate "Build test-dynamic-debug submodule"
> > +     default m
> > +     depends on DYNAMIC_DEBUG || DYNAMIC_DEBUG_CORE
> > +     depends on TEST_DYNAMIC_DEBUG
> > +     help
> > +       This sub-module uses a classmap defined and exported by the
> > +       parent module, recapitulating drm & driver's shared use of
> > +       drm.debug to control enabled debug-categories.
> > +       It is tristate, independent of parent, to allow testing all
> > +       proper combinations of parent=y/m submod=y/m.
> >
> >         If unsure, say N.
> >
> > diff --git a/lib/Makefile b/lib/Makefile
> > index a8155c972f02..7f66fc1c163d 100644
> > --- a/lib/Makefile
> > +++ b/lib/Makefile
> > @@ -84,6 +84,7 @@ obj-$(CONFIG_TEST_SORT) += test_sort.o
> >   obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_keys.o
> >   obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_key_base.o
> >   obj-$(CONFIG_TEST_DYNAMIC_DEBUG) += test_dynamic_debug.o
> > +obj-$(CONFIG_TEST_DYNAMIC_DEBUG_SUBMOD) += test_dynamic_debug_submod.o
> >   obj-$(CONFIG_TEST_PRINTF) += test_printf.o
> >   obj-$(CONFIG_TEST_SCANF) += test_scanf.o
> >
> > @@ -228,6 +229,8 @@ obj-$(CONFIG_ARCH_NEED_CMPXCHG_1_EMU) += cmpxchg-emu.o
> >   obj-$(CONFIG_DYNAMIC_DEBUG_CORE) += dynamic_debug.o
> >   #ensure exported functions have prototypes
> >   CFLAGS_dynamic_debug.o := -DDYNAMIC_DEBUG_MODULE
> > +CFLAGS_test_dynamic_debug.o := -DDYNAMIC_DEBUG_MODULE
> > +CFLAGS_test_dynamic_debug_submod.o := -DDYNAMIC_DEBUG_MODULE
> >
> >   obj-$(CONFIG_SYMBOLIC_ERRNAME) += errname.o
> >
> > diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c
> > index 094d6e62a9d1..6bca0c6727d4 100644
> > --- a/lib/dynamic_debug.c
> > +++ b/lib/dynamic_debug.c
> > @@ -43,13 +43,16 @@ extern struct _ddebug __start___dyndbg[];
> >   extern struct _ddebug __stop___dyndbg[];
> >   extern struct ddebug_class_map __start___dyndbg_classes[];
> >   extern struct ddebug_class_map __stop___dyndbg_classes[];
> > +extern struct ddebug_class_user __start___dyndbg_class_users[];
> > +extern struct ddebug_class_user __stop___dyndbg_class_users[];
> >
> >   struct ddebug_table {
> >       struct list_head link;
> >       const char *mod_name;
> >       struct _ddebug *ddebugs;
> >       struct ddebug_class_map *classes;
> > -     unsigned int num_ddebugs, num_classes;
> > +     struct ddebug_class_user *class_users;
> > +     unsigned int num_ddebugs, num_classes, num_class_users;
> >   };
> >
> >   struct ddebug_query {
> > @@ -148,23 +151,35 @@ static void vpr_info_dq(const struct ddebug_query *query, const char *msg)
> >                 query->first_lineno, query->last_lineno, query->class_string);
> >   }
> >
> > -#define __outvar /* filled by callee */
> > -static struct ddebug_class_map *ddebug_find_valid_class(struct ddebug_table const *dt,
> > -                                                     const char *class_string,
> > -                                                     __outvar int *class_id)
> > +#define vpr_dt_info(dt_p, msg_p, ...) ({                             \
> > +     struct ddebug_table const *_dt = dt_p;                          \
> > +     v2pr_info(msg_p " module:%s nd:%d nc:%d nu:%d\n", ##__VA_ARGS__, \
> > +               _dt->mod_name, _dt->num_ddebugs, _dt->num_classes,    \
> > +               _dt->num_class_users);                                \
> > +     })
> > +
> > +static int ddebug_find_valid_class(struct ddebug_table const *dt, const char *class_string)
> >   {
> >       struct ddebug_class_map *map;
> > +     struct ddebug_class_user *cli;
> >       int i, idx;
> >
> > -     for (map = dt->classes, i = 0; i < dt->num_classes; i++, map++) {
> > +     for (i = 0, map = dt->classes; i < dt->num_classes; i++, map++) {
> >               idx = match_string(map->class_names, map->length, class_string);
> >               if (idx >= 0) {
> > -                     *class_id = idx + map->base;
> > -                     return map;
> > +                     vpr_dt_info(dt, "good-class: %s.%s ", map->mod_name, class_string);
> > +                     return idx + map->base;
> >               }
> >       }
> > -     *class_id = -ENOENT;
> > -     return NULL;
> > +     for (i = 0, cli = dt->class_users; i < dt->num_class_users; i++, cli++) {
> > +             idx = match_string(cli->map->class_names, cli->map->length, class_string);
> > +             if (idx >= 0) {
> > +                     vpr_dt_info(dt, "class-ref: %s -> %s.%s ",
> > +                                 cli->mod_name, cli->map->mod_name, class_string);
> > +                     return idx + cli->map->base;
> > +             }
> > +     }
> > +     return -ENOENT;
> >   }
> >
> >   /*
> > @@ -173,16 +188,14 @@ static struct ddebug_class_map *ddebug_find_valid_class(struct ddebug_table cons
> >    * callsites, normally the same as number of changes.  If verbose,
> >    * logs the changes.  Takes ddebug_lock.
> >    */
> > -static int ddebug_change(const struct ddebug_query *query,
> > -                      struct flag_settings *modifiers)
> > +static int ddebug_change(const struct ddebug_query *query, struct flag_settings *modifiers)
> >   {
> >       int i;
> >       struct ddebug_table *dt;
> >       unsigned int newflags;
> >       unsigned int nfound = 0;
> >       struct flagsbuf fbuf, nbuf;
> > -     struct ddebug_class_map *map = NULL;
> > -     int __outvar valid_class;
> > +     int valid_class;
> >
> >       /* search for matching ddebugs */
> >       mutex_lock(&ddebug_lock);
> > @@ -194,8 +207,8 @@ static int ddebug_change(const struct ddebug_query *query,
> >                       continue;
> >
> >               if (query->class_string) {
> > -                     map = ddebug_find_valid_class(dt, query->class_string, &valid_class);
> > -                     if (!map)
> > +                     valid_class = ddebug_find_valid_class(dt, query->class_string);
> > +                     if (valid_class < 0)
> >                               continue;
> >               } else {
> >                       /* constrain query, do not touch class'd callsites */
> > @@ -559,7 +572,7 @@ static int ddebug_exec_query(char *query_string, const char *modname)
> >
> >   /* handle multiple queries in query string, continue on error, return
> >      last error or number of matching callsites.  Module name is either
> > -   in param (for boot arg) or perhaps in query string.
> > +   in the modname arg (for boot args) or perhaps in query string.
> >   */
> >   static int ddebug_exec_queries(char *query, const char *modname)
> >   {
> > @@ -688,12 +701,12 @@ static int param_set_dyndbg_module_classes(const char *instr,
> >   }
> >
> >   /**
> > - * param_set_dyndbg_classes - class FOO >control
> > + * param_set_dyndbg_classes - set all classes in a classmap
> >    * @instr: string echo>d to sysfs, input depends on map_type
> > - * @kp:    kp->arg has state: bits/lvl, map, map_type
> > + * @kp:    kp->arg has state: bits/lvl, classmap, map_type
> >    *
> > - * Enable/disable prdbgs by their class, as given in the arguments to
> > - * DECLARE_DYNDBG_CLASSMAP.  For LEVEL map-types, enforce relative
> > + * For all classes in the classmap, enable/disable them per the input
> > + * (depending on map_type).  For LEVEL map-types, enforce relative
> >    * levels by bitpos.
> >    *
> >    * Returns: 0 or <0 if error.
> > @@ -1038,12 +1051,17 @@ static void *ddebug_proc_next(struct seq_file *m, void *p, loff_t *pos)
> >   static const char *ddebug_class_name(struct ddebug_table *dt, struct _ddebug *dp)
> >   {
> >       struct ddebug_class_map *map = dt->classes;
> > +     struct ddebug_class_user *cli = dt->class_users;
> >       int i;
> >
> >       for (i = 0; i < dt->num_classes; i++, map++)
> >               if (class_in_range(dp->class_id, map))
> >                       return map->class_names[dp->class_id - map->base];
> >
> > +     for (i = 0; i < dt->num_class_users; i++, cli++)
> > +             if (class_in_range(dp->class_id, cli->map))
> > +                     return cli->map->class_names[dp->class_id - cli->map->base];
> > +
> >       return NULL;
> >   }
> >
> > @@ -1124,31 +1142,129 @@ static const struct proc_ops proc_fops = {
> >       .proc_write = ddebug_proc_write
> >   };
> >
> > -static void ddebug_attach_module_classes(struct ddebug_table *dt, struct _ddebug_info *di)
> > +#define vpr_cm_info(cm_p, msg_fmt, ...) ({                           \
> > +     struct ddebug_class_map const *_cm = cm_p;                      \
> > +     v2pr_info(msg_fmt " %s [%d..%d] %s..%s\n", ##__VA_ARGS__,       \
> > +               _cm->mod_name, _cm->base, _cm->base + _cm->length,    \
> > +               _cm->class_names[0], _cm->class_names[_cm->length - 1]); \
> > +     })
> > +
> > +static void ddebug_sync_classbits(const struct kernel_param *kp, const char *modname)
> > +{
> > +     const struct ddebug_class_param *dcp = kp->arg;
> > +
> > +     /* clamp initial bitvec, mask off hi-bits */
> > +     if (*dcp->bits & ~CLASSMAP_BITMASK(dcp->map->length)) {
> > +             *dcp->bits &= CLASSMAP_BITMASK(dcp->map->length);
> > +             v2pr_info("preset classbits: %lx\n", *dcp->bits);
> > +     }
> > +     /* force class'd prdbgs (in USEr module) to match (DEFINEr module) class-param */
> > +     ddebug_apply_class_bitmap(dcp, dcp->bits, ~0, modname);
> > +     ddebug_apply_class_bitmap(dcp, dcp->bits, 0, modname);
> > +}
> > +
> > +static void ddebug_match_apply_kparam(const struct kernel_param *kp,
> > +                                   const struct ddebug_class_map *map,
> > +                                   const char *modnm)
> > +{
> > +     struct ddebug_class_param *dcp;
> > +
> > +     if (kp->ops != &param_ops_dyndbg_classes)
> > +             return;
> > +
> > +     dcp = (struct ddebug_class_param *)kp->arg;
> > +
> > +     if (map == dcp->map) {
> > +             v2pr_info(" kp:%s.%s =0x%lx", modnm, kp->name, *dcp->bits);
> > +             vpr_cm_info(map, " %s mapped to: ", modnm);
> > +             ddebug_sync_classbits(kp, modnm);
> > +     }
> > +}
> > +
> > +static void ddebug_apply_params(const struct ddebug_class_map *cm, const char *modnm)
> > +{
> > +     const struct kernel_param *kp;
> > +#if IS_ENABLED(CONFIG_MODULES)
> > +     int i;
> > +
> > +     if (cm->mod) {
> > +             vpr_cm_info(cm, "loaded classmap: %s", modnm);
> > +             /* ifdef protects the cm->mod->kp deref */
> > +             for (i = 0, kp = cm->mod->kp; i < cm->mod->num_kp; i++, kp++)
> > +                     ddebug_match_apply_kparam(kp, cm, modnm);
> > +     }
> > +#endif
> > +     if (!cm->mod) {
> > +             vpr_cm_info(cm, "builtin classmap: %s", modnm);
> > +             for (kp = __start___param; kp < __stop___param; kp++)
> > +                     ddebug_match_apply_kparam(kp, cm, modnm);
> > +     }
> > +}
> > +
> > +/*
> > + * Find this module's classmaps in a sub/whole-range of the builtin/
> > + * modular classmap vector/section.  Save the start and length of the
> > + * subrange at its edges.
> > + */
> > +static void ddebug_attach_module_classes(struct ddebug_table *dt,
> > +                                      const struct _ddebug_info *di)
> >   {
> >       struct ddebug_class_map *cm;
> >       int i, nc = 0;
> >
> > -     /*
> > -      * Find this module's classmaps in a subrange/wholerange of
> > -      * the builtin/modular classmap vector/section.  Save the start
> > -      * and length of the subrange at its edges.
> > -      */
> > -     for (cm = di->classes, i = 0; i < di->num_classes; i++, cm++) {
> > -
> > +     for (i = 0, cm = di->classes; i < di->num_classes; i++, cm++) {
> >               if (!strcmp(cm->mod_name, dt->mod_name)) {
> > -                     if (!nc) {
> > -                             v2pr_info("start subrange, class[%d]: module:%s base:%d len:%d ty:%d\n",
> > -                                       i, cm->mod_name, cm->base, cm->length, cm->map_type);
> > +                     vpr_cm_info(cm, "classes[%d]:", i);
> > +                     if (!nc++)
> >                               dt->classes = cm;
> > -                     }
> > -                     nc++;
> >               }
> >       }
> > -     if (nc) {
> > -             dt->num_classes = nc;
> > -             vpr_info("module:%s attached %d classes\n", dt->mod_name, nc);
> > +     if (!nc)
> > +             return;
> > +
> > +     vpr_info("module:%s attached %d classes\n", dt->mod_name, nc);
> > +     dt->num_classes = nc;
> > +
> > +     for (i = 0, cm = dt->classes; i < dt->num_classes; i++, cm++)
> > +             ddebug_apply_params(cm, cm->mod_name);
> > +}
> > +
> > +/*
> > + * propagates class-params thru their classmaps to class-users.  this
> > + * means a query against the dt/module, which means it must be on the
> > + * list to be seen by ddebug_change.
> > + */
> > +static void ddebug_attach_user_module_classes(struct ddebug_table *dt,
> > +                                           const struct _ddebug_info *di)
> > +{
> > +     struct ddebug_class_user *cli;
> > +     int i, nc = 0;
> > +
> > +     /*
> > +      * For builtins: scan the array, find start/length of this
> > +      * module's refs, save to dt.  For loadables, this is the
> > +      * whole array.
> > +      */
> > +     for (i = 0, cli = di->class_users; i < di->num_class_users; i++, cli++) {
> > +             if (WARN_ON_ONCE(!cli || !cli->map || !cli->mod_name))
> > +                     continue;
> > +             if (!strcmp(cli->mod_name, dt->mod_name)) {
> > +                     vpr_cm_info(cli->map, "class_ref[%d] %s -> %s", i,
> > +                                 cli->mod_name, cli->map->mod_name);
> > +                     if (!nc++)
> > +                             dt->class_users = cli;
> > +             }
> >       }
> > +     if (!nc)
> > +             return;
> > +
> > +     dt->num_class_users = nc;
> > +
> > +     /* now iterate dt */
> > +     for (i = 0, cli = dt->class_users; i < dt->num_class_users; i++, cli++)
> > +             ddebug_apply_params(cli->map, cli->mod_name);
> > +
> > +     vpr_dt_info(dt, "attach-client-module: ");
> >   }
> >
> >   /*
> > @@ -1158,6 +1274,8 @@ static void ddebug_attach_module_classes(struct ddebug_table *dt, struct _ddebug
> >   static int ddebug_add_module(struct _ddebug_info *di, const char *modname)
> >   {
> >       struct ddebug_table *dt;
> > +     struct _ddebug *iter;
> > +     int i, class_ct = 0;
> >
> >       if (!di->num_descs)
> >               return 0;
> > @@ -1181,13 +1299,20 @@ static int ddebug_add_module(struct _ddebug_info *di, const char *modname)
> >
> >       INIT_LIST_HEAD(&dt->link);
> >
> > -     if (di->classes && di->num_classes)
> > +     for (i = 0, iter = di->descs; i < di->num_descs; i++, iter++)
> > +             if (iter->class_id != _DPRINTK_CLASS_DFLT)
> > +                     class_ct++;
> > +
> > +     if (class_ct && di->num_classes)
> >               ddebug_attach_module_classes(dt, di);
> >
> >       mutex_lock(&ddebug_lock);
> >       list_add_tail(&dt->link, &ddebug_tables);
> >       mutex_unlock(&ddebug_lock);
> >
> > +     if (class_ct && di->num_class_users)
> > +             ddebug_attach_user_module_classes(dt, di);
> > +
> >       vpr_info("%3u debug prints in module %s\n", di->num_descs, modname);
> >       return 0;
> >   }
> > @@ -1337,8 +1462,10 @@ static int __init dynamic_debug_init(void)
> >       struct _ddebug_info di = {
> >               .descs = __start___dyndbg,
> >               .classes = __start___dyndbg_classes,
> > +             .class_users = __start___dyndbg_class_users,
> >               .num_descs = __stop___dyndbg - __start___dyndbg,
> >               .num_classes = __stop___dyndbg_classes - __start___dyndbg_classes,
> > +             .num_class_users = __stop___dyndbg_class_users - __start___dyndbg_class_users,
> >       };
> >
> >   #ifdef CONFIG_MODULES
> > diff --git a/lib/test_dynamic_debug.c b/lib/test_dynamic_debug.c
> > index 74d183ebf3e0..1838f62738c4 100644
> > --- a/lib/test_dynamic_debug.c
> > +++ b/lib/test_dynamic_debug.c
> > @@ -6,11 +6,15 @@
> >    *      Jim Cromie  <jim.cromie@gmail.com>
> >    */
> >
> > -#define pr_fmt(fmt) "test_dd: " fmt
> > +#if defined(TEST_DYNAMIC_DEBUG_SUBMOD)
> > +  #define pr_fmt(fmt) "test_dd_submod: " fmt
> > +#else
> > +  #define pr_fmt(fmt) "test_dd: " fmt
> > +#endif
> >
> >   #include <linux/module.h>
> >
> > -/* run tests by reading or writing sysfs node: do_prints */
> > +/* re-gen output by reading or writing sysfs node: do_prints */
> >
> >   static void do_prints(void); /* device under test */
> >   static int param_set_do_prints(const char *instr, const struct kernel_param *kp)
> > @@ -29,24 +33,39 @@ static const struct kernel_param_ops param_ops_do_prints = {
> >   };
> >   module_param_cb(do_prints, &param_ops_do_prints, NULL, 0600);
> >
> > -/*
> > - * Using the CLASSMAP api:
> > - * - classmaps must have corresponding enum
> > - * - enum symbols must match/correlate with class-name strings in the map.
> > - * - base must equal enum's 1st value
> > - * - multiple maps must set their base to share the 0-30 class_id space !!
> > - *   (build-bug-on tips welcome)
> > - * Additionally, here:
> > - * - tie together sysname, mapname, bitsname, flagsname
> > - */
> > -#define DD_SYS_WRAP(_model, _flags)                                  \
> > -     static unsigned long bits_##_model;                             \
> > -     static struct ddebug_class_param _flags##_model = {             \
> > +#define CLASSMAP_BITMASK(width, base) (((1UL << (width)) - 1) << (base))
> > +
> > +/* sysfs param wrapper, proto-API */
> > +#define DYNDBG_CLASSMAP_PARAM_(_model, _flags, _init)                        \
> > +     static unsigned long bits_##_model = _init;                     \
> > +     static struct ddebug_class_param _flags##_##_model = {          \
> >               .bits = &bits_##_model,                                 \
> >               .flags = #_flags,                                       \
> >               .map = &map_##_model,                                   \
> >       };                                                              \
> > -     module_param_cb(_flags##_##_model, &param_ops_dyndbg_classes, &_flags##_model, 0600)
> > +     module_param_cb(_flags##_##_model, &param_ops_dyndbg_classes,   \
> > +                     &_flags##_##_model, 0600)
> > +#ifdef DEBUG
> > +#define DYNDBG_CLASSMAP_PARAM(_model, _flags)  DYNDBG_CLASSMAP_PARAM_(_model, _flags, ~0)
> > +#else
> > +#define DYNDBG_CLASSMAP_PARAM(_model, _flags)  DYNDBG_CLASSMAP_PARAM_(_model, _flags, 0)
> > +#endif
> > +
> > +/*
> > + * Demonstrate/test all 4 class-typed classmaps with a sys-param.
> > + *
> > + * Each is 3 part: client-enum decl, _DEFINE, _PARAM.
> > + * Declare them in blocks to show patterns of use (repetitions and
> > + * changes) within each.
> > + *
> > + * 1st, dyndbg expects a client-provided enum-type as source of
> > + * category/classid truth.  DRM has DRM_UT_<CORE,DRIVER,KMS,etc>.
> > + *
> > + * Modules with multiple CLASSMAPS must have enums with distinct
> > + * value-ranges, arranged below with explicit enum_sym = X inits.
> > + *
> > + * Declare all 4 enums now, for different types
> > + */
> >
> >   /* numeric input, independent bits */
> >   enum cat_disjoint_bits {
> > @@ -60,26 +79,51 @@ enum cat_disjoint_bits {
> >       D2_LEASE,
> >       D2_DP,
> >       D2_DRMRES };
> > -DECLARE_DYNDBG_CLASSMAP(map_disjoint_bits, DD_CLASS_TYPE_DISJOINT_BITS, 0,
> > -                     "D2_CORE",
> > -                     "D2_DRIVER",
> > -                     "D2_KMS",
> > -                     "D2_PRIME",
> > -                     "D2_ATOMIC",
> > -                     "D2_VBL",
> > -                     "D2_STATE",
> > -                     "D2_LEASE",
> > -                     "D2_DP",
> > -                     "D2_DRMRES");
> > -DD_SYS_WRAP(disjoint_bits, p);
> > -DD_SYS_WRAP(disjoint_bits, T);
> >
> >   /* numeric verbosity, V2 > V1 related */
> >   enum cat_level_num { V0 = 14, V1, V2, V3, V4, V5, V6, V7 };
> > -DECLARE_DYNDBG_CLASSMAP(map_level_num, DD_CLASS_TYPE_LEVEL_NUM, 14,
> > -                    "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7");
> > -DD_SYS_WRAP(level_num, p);
> > -DD_SYS_WRAP(level_num, T);
> > +
> > +/* recapitulate DRM's parent(drm.ko) <-- _submod(drivers,helpers) */
> > +#if !defined(TEST_DYNAMIC_DEBUG_SUBMOD)
>
> It was not clear at first read that the test_dynamic_debug.c code is
> used twice.
>

Yes, that was a miss.  Now in big comment block at the top of both,
right before the pr-fmt  mod-name shortening for the logs.


> What do you think about creating a third file "common" with
> do_prints/do_levels/... implementations and avoid this #if !defined?
>

I'd rather not.  I think the multi-module usage is sufficiently interconnected
(common enum, parent side, clone side) that all-in-one presentation
helps to communicate this "linkage".

I hope the new comments clearly elucidate this.  PLMK if not.

> > +/*
> > + * In single user, or parent / coordinator (drm.ko) modules, define
> > + * classmaps on the client enums above, and then declares the PARAMS
> > + * ref'g the classmaps.  Each is exported.
> > + */
> > +DYNDBG_CLASSMAP_DEFINE(map_disjoint_bits, DD_CLASS_TYPE_DISJOINT_BITS,
>
> map_disjoint_bits is defined in both test_dynamic_debug.c and
> test_dynamic_debug_submodule.c, is it normal/required?

Its the enum : cat_disjoint_bits, that is defined for both.

the _DEFINE itself is right after the ifdef TEST_MOD_SUBMOD.

do you think it needs a better MOD_SUBMOD name ?
or an ifndef ?

>
> > +                    D2_CORE,
> > +                    "D2_CORE",
> > +                    "D2_DRIVER",
> > +                    "D2_KMS",
> > +                    "D2_PRIME",
> > +                    "D2_ATOMIC",
> > +                    "D2_VBL",
> > +                    "D2_STATE",
> > +                    "D2_LEASE",
> > +                    "D2_DP",
> > +                    "D2_DRMRES");
> > +
> > +DYNDBG_CLASSMAP_DEFINE(map_level_num, DD_CLASS_TYPE_LEVEL_NUM,
> > +                    V0, "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7");
> > +
> > +/*
> > + * now add the sysfs-params
> > + */
> > +
> > +DYNDBG_CLASSMAP_PARAM(disjoint_bits, p);
> > +DYNDBG_CLASSMAP_PARAM(level_num, p);
> > +
> > +#else /* TEST_DYNAMIC_DEBUG_SUBMOD */
> > +
> > +/*
> > + * in submod/drm-drivers, use the classmaps defined in top/parent
> > + * module above.
> > + */
> > +
> > +DYNDBG_CLASSMAP_USE(map_disjoint_bits);
> > +DYNDBG_CLASSMAP_USE(map_level_num);
> > +
> > +#endif
> >
> >   /* stand-in for all pr_debug etc */
> >   #define prdbg(SYM) __pr_debug_cls(SYM, #SYM " msg\n")
> > @@ -115,6 +159,7 @@ static void do_levels(void)
> >
> >   static void do_prints(void)
> >   {
> > +     pr_debug("do_prints:\n");
> >       do_cats();
> >       do_levels();
>
> I just observed it, is the ordering of logs garanteed in
> /proc/dynamic_debug/control?
>

Yes. the fully ordered table/__section is pretty important.
If its not true for descriptors, its probably not true for class-maps
or users either
and the list-iter --> vector-block conversion a few commits ago isnt
solid either.

its also an explicit assumption under these commits

773beabbb8e8 dyndbg: reverse module.callsite walk in cat control
2ad556f70043 dyndbg: reverse module walk in cat control

All in all, I think something would have broken by now.


> When I run this test, I have this order:
>
> do_cats =_ "doing categories\n"
> [...]
> do_levels =_ "doing levels\n"
> [...]
> do_prints =_ "do_prints:\n"
> test_dynamic_debug_init =_ "init start\n"
> test_dynamic_debug_init =_ "init done\n"
> test_dynamic_debug_exit =_ "exited\n"
>
> Which is clearly not the code execution order.

Youre correct.
the control file reflects compile link order.

Im pretty sure the appearance of init 1st in control derives from init/builtin.a
being built 1st and/or linked 1st.

>
> >   }
> > diff --git a/lib/test_dynamic_debug_submod.c b/lib/test_dynamic_debug_submod.c
> > new file mode 100644
> > index 000000000000..9a893402ce1a
> > --- /dev/null
> > +++ b/lib/test_dynamic_debug_submod.c
> > @@ -0,0 +1,10 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Kernel module for testing dynamic_debug
> > + *
> > + * Authors:
> > + *      Jim Cromie   <jim.cromie@gmail.com>
> > + */
> > +
> > +#define TEST_DYNAMIC_DEBUG_SUBMOD
> > +#include "test_dynamic_debug.c"
>
> --
> Louis Chauvet, Bootlin
> Embedded Linux and Kernel engineering
> https://bootlin.com
>

thanks again Lous,
Jim
Louis Chauvet March 24, 2025, 3:07 p.m. UTC | #3
Le 16/03/2025 à 20:46, jim.cromie@gmail.com a écrit :
> hi Louis,
> 
> On Tue, Feb 25, 2025 at 7:16 AM Louis Chauvet <louis.chauvet@bootlin.com> wrote:
>>
>>
>>
>> Le 25/01/2025 à 07:45, Jim Cromie a écrit :
>>> DECLARE_DYNDBG_CLASSMAP() has a design error; its usage fails a basic
>>> K&R rule: "define once, refer many times".
>>>
>>> It is used across DRM core & drivers, each use re-defines the classmap
>>> understood by that module; and all must match for the modules to
>>> respond together when DRM.debug categories are enabled.  This is
>>> brittle; a maintenance foot-gun.
>>>
>>> Further, its culpable in the CONFIG_DRM_USE_DYNAMIC_DEBUG=Y
>>> regression; its use in both core & drivers obfuscates the 2 roles.
>>> So 1st drm.ko loads, and dyndbg initializes its DRM.debug callsites,
>>> then a drm-driver loads, but too late - it missed the DRM.debug
>>> enablement.
>>>
>>> So replace it with 2 macros:
>>>     DYNDBG_CLASSMAP_DEFINE - invoked once from core - drm.ko
>>>     DYNDBG_CLASSMAP_USE    - from all drm drivers and helpers.
>>>
>>> DYNDBG_CLASSMAP_DEFINE: it just modifies DECLARE_DYNDBG_CLASSMAP,
>>> dropping drop the static qualifier, and exports it instead.
>>>
>>> DYNDBG_CLASSMAP_USE: then refers to the exported var by name:
>>>     used from drivers, helper-mods
>>>     lets us drop the repetitive "classname" declarations
>>>     fixes 2nd-defn problem
>>>     creates a ddebug_class_user record in new __dyndbg_class_users section
>>>     this allows ddebug_add_module(etal) to handle users differently.
>>>
>>> DECLARE_DYNDBG_CLASSMAP is preserved temporarily, to decouple DRM
>>> adaptation work and avoid compile-errs before its done.  IOW, DRM gets
>>> fixed when they commit the adopt-new-api patches, and remove the
>>> BROKEN marking.
>>>
>>> The DEFINE,USE distinction, and the separate classmap-use record,
>>> allows dyndbg to initialize the driver's & helper's DRM.debug
>>> callsites separately after each is modprobed.
>>>
>>> Basically, the classmap init-scan is repeated for classmap-users.
>>>
>>> To review, dyndbg's existing __dyndbg_classes[] section does:
>>>
>>> . catalogs the classmaps defined by a module (or builtin modules)
>>> . tells dyndbg the module has class'd prdbgs, allowing >control
>>> . DYNDBG_CLASSMAP_DEFINE creates classmaps in this section.
>>>
>>> This patch adds __dyndbg_class_users[] section:
>>>
>>> . catalogs uses/references to the classmap definitions.
>>> . authorizes dyndbg to >control those class'd prdbgs in ref'g module.
>>> . DYNDBG_CLASSMAP_USE() creates classmap-user records in this new section.
>>>
>>> Now ddebug_add_module(etal) can handle classmap-uses similar to (and
>>> after) classmaps; when a dependent module is loaded, if it has
>>> classmap-uses (to a classmap-def in another module), that module's
>>> kernel params are scanned to find if it has a kparam that is wired to
>>> dyndbg's param-ops, and whose classmap is the one being ref'd.
>>>
>>> To support this, theres a few data/header changes:
>>>
>>> new struct ddebug_class_user
>>>     contains: user-module-name, &classmap-defn
>>>     it records drm-driver's use of a classmap in the section, allowing lookup
>>>
>>> struct ddebug_info gets 2 new fields for the new sections:
>>>     class_users, num_class_users.
>>>     set by dynamic_debug_init() for builtins.
>>>     or by kernel/module/main:load_info() for loadable modules.
>>>
>>> vmlinux.lds.h: new BOUNDED_SECTION for __dyndbg_class_users
>>>
>>> dynamic_debug.c has 2 changes in ddebug_add_module(), ddebug_change():
>>>
>>> ddebug_add_module() already calls ddebug_attach_module_classes()
>>> to handle classmaps DEFINEd by a module, now it also calls
>>> ddebug_attach_user_module_classes() to handle USEd classmaps.  To
>>> avoid this work when possible, 1st scan the module's descriptors and
>>> count the number of class'd pr_debugs.
>>>
>>> ddebug_attach_user_module_classes() scans the module's class_users
>>> section, follows the refs to the parent's classmap, and calls
>>> ddebug_apply_params() on each.  It also avoids work by checking the
>>> module's class-ct.
>>>
>>> ddebug_apply_params(new fn):
>>>
>>> It scans module's/builtin kernel-params, calls ddebug_match_apply_kparam
>>> for each to find any params/sysfs-nodes which may be wired to a classmap.
>>>
>>> ddebug_match_apply_kparam(new fn):
>>>
>>> 1st, it tests the kernel-param.ops is dyndbg's; this guarantees that
>>> the attached arg is a struct ddebug_class_param, which has a ref to
>>> the param's state, and to the classmap defining the param's handling.
>>>
>>> 2nd, it requires that the classmap ref'd by the kparam is the one
>>> we're called for; modules can use many separate classmaps (as
>>> test_dynamic_debug does).
>>>
>>> Then apply the "parent" kparam's setting to the dependent module,
>>> using ddebug_apply_class_bitmap().
>>>
>>> ddebug_change(and callees) also gets adjustments:
>>>
>>> ddebug_find_valid_class(): This does a search over the module's
>>> classmaps, looking for the class FOO echo'd to >control.  So now it
>>> searches over __dyndbg_class_users[] after __dyndbg_classes[].
>>>
>>> ddebug_class_name(): return class-names for defined AND used classes.
>>>
>>> test_dynamic_debug.c, test_dynamic_debug_submod.c:
>>>
>>> This demonstrates the 2 types of classmaps & sysfs-params, following
>>> the 4-part recipe:
>>>
>>> 1. define an enum for the classmap: DRM.debug has DRM_UT_{CORE,KMS,...}
>>>      multiple classes must share 0-62 classid space.
>>> 2. DYNDBG_CLASSMAP_DEFINE(.. DRM_UT_{CORE,KMS,...})
>>> 3. DYNDBG_CLASSMAP_PARAM* (classmap)
>>> 4. DYNDBG_CLASSMAP_USE()
>>>      by _submod only, skipping 2,3
>>>
>>> Move all the enum declarations together, to better explain how they
>>> share the 0..62 class-id space available to a module (non-overlapping
>>> subranges).
>>>
>>> reorg macros 2,3 by name.  This gives a tabular format, making it easy
>>> to see the pattern of repetition, and the points of change.
>>>
>>> And extend the test to replicate the 2-module (parent & dependent)
>>> scenario which caused the CONFIG_DRM_USE_DYNAMIC_DEBUG=y regression
>>> seen in drm & drivers.
>>>
>>> The _submod.c is a 2-line file: #define _SUBMOD, #include parent.
>>>
>>> This gives identical complements of prdbgs in parent & _submod, and
>>> thus identical print behavior when all of: >control, >params, and
>>> parent->_submod propagation are working correctly.
>>>
>>> It also puts all the parent/_submod declarations together in the same
>>> source, with the new ifdef _SUBMOD block invoking DYNDBG_CLASSMAP_USE
>>> for the 2 test-interfaces.  I think this is clearer.
>>>
>>> These 2 modules are both tristate, allowing 3 super/sub combos: Y/Y,
>>> Y/M, M/M (not N/N).  Y/Y testing exposed a missing __align(8) in the
>>> _METADATA macro, which M/M didn't see because the module-loader memory
>>> placement constrains it instead.
>>>
>>> NB: this patch ignores a checkpatch do-while warning which is fixed by
>>> a preceding commit, which would be wrong for declarative macros like
>>> module_param_named() etc.
>>>
>>> Fixes: aad0214f3026 ("dyndbg: add DECLARE_DYNDBG_CLASSMAP macro")
>>> Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
>>> ---
>>> v9 - commit-msg tweaks
>>>        DRM:CHECK warnings on macros: add parens
>>>        extern DEFINEd _var, static classnames
>>>        change ddebug_class_user.user_mod_name to .mod_name
>>>        simplify ddebug_find_valid_class
>>>        improve vpr_cm_info msg format
>>>        wrap (base) in macro body
>>>        move __DYNDBG_CLASSMAP_CHECK above kdoc for DYNDBG_CLASSMAP_DEFINE
>>>
>>> v8 - split drm parts to separate commits.
>>>        preserve DECLARE_DYNDBG_CLASSMAP to decouple DRM, no flag day.
>>>        fixup block comment
>>>
>>> v7 - previous submission-blocking bug:
>>>
>>> missing __align(8) in DYNAMIC_DEBUG_DECLARE_METADATA on
>>> ddebug_class_user caused corrupt records, but only for builtin
>>> modules; module loader code probably pinned allocations to the right
>>> alignment naturally, hiding the bug for typical builds.
>>>
>>> v6- get rid of WARN_ON_ONCE
>>> v?- fix _var expanded 2x in macro
>>>
>>> dyndbg:
>>>
>>> This fn formerly returned the map which contained the class (thus
>>> validating it), and as a side-effect set the class-id in an outvar.
>>>
>>> But the caller didn't use the map (after checking its not null), only
>>> the valid class-id.  So simplify the fn to return the class-id of the
>>> validated classname, or -ENOENT when the queried classname is not
>>> found.
>>>
>>> Convey more useful info in the debug-msg: print class-names[0,last],
>>> and [base,+len] instead of the class-type printout, which is currently
>>> always "type:DISJOINT_BITS".  And drop ddebug_classmap_typenames,
>>> which is now unused.
>>>
>>> [root@v6 b0-dd]# modprobe test_dynamic_debug_submod
>>> [   18.864962] dyndbg: loaded classmap: test_dynamic_debug [16..24] V0..V7
>>> [   18.865046] dyndbg:  found kp:p_level_num =0x0
>>> [   18.865048] dyndbg:   mapped to: test_dynamic_debug [16..24] V0..V7
>>> [   18.865164] dyndbg:   p_level_num: lvl:0 bits:0x0
>>> [   18.865217] dyndbg: loaded classmap: test_dynamic_debug [0..10] D2_CORE..D2_DRMRES
>>> [   18.865297] dyndbg:  found kp:p_disjoint_bits =0x0
>>> [   18.865298] dyndbg:   mapped to: test_dynamic_debug [0..10] D2_CORE..D2_DRMRES
>>> [   18.865424] dyndbg:   p_disjoint_bits: classbits: 0x0
>>> [   18.865472] dyndbg: module:test_dynamic_debug attached 2 classmaps
>>> [   18.865533] dyndbg:  23 debug prints in module test_dynamic_debug
>>> [   18.866558] dyndbg: loaded classmap: test_dynamic_debug_submod [16..24] V0..V7
>>> [   18.866698] dyndbg:  found kp:p_level_num =0x0
>>> [   18.866699] dyndbg:   mapped to: test_dynamic_debug_submod [16..24] V0..V7
>>> [   18.866865] dyndbg:   p_level_num: lvl:0 bits:0x0
>>> [   18.866926] dyndbg: loaded classmap: test_dynamic_debug_submod [0..10] D2_CORE..D2_DRMRES
>>> [   18.867026] dyndbg:  found kp:p_disjoint_bits =0x0
>>> [   18.867027] dyndbg:   mapped to: test_dynamic_debug_submod [0..10] D2_CORE..D2_DRMRES
>>> [   18.867193] dyndbg:   p_disjoint_bits: classbits: 0x0
>>> [   18.867255] dyndbg: module:test_dynamic_debug_submod attached 2 classmap uses
>>> [   18.867351] dyndbg:  23 debug prints in module test_dynamic_debug_submod
>>> ---
>>>    MAINTAINERS                       |   2 +-
>>>    include/asm-generic/vmlinux.lds.h |   1 +
>>>    include/linux/dynamic_debug.h     |  83 ++++++++++--
>>>    kernel/module/main.c              |   3 +
>>>    lib/Kconfig.debug                 |  24 +++-
>>>    lib/Makefile                      |   3 +
>>>    lib/dynamic_debug.c               | 203 ++++++++++++++++++++++++------
>>>    lib/test_dynamic_debug.c          | 111 +++++++++++-----
>>>    lib/test_dynamic_debug_submod.c   |  10 ++
>>
>> Hi Jim,
>>
>> While reading the files test_dynamic_debug*, I was wondering, they are
>> more samples than tests. Does it make sense to move them in samples/?
>> There is no need to do it in this series.
>>
>>>    9 files changed, 355 insertions(+), 85 deletions(-)
>>>    create mode 100644 lib/test_dynamic_debug_submod.c
>>>
>>> diff --git a/MAINTAINERS b/MAINTAINERS
>>> index 0fa7c5728f1e..38afccb3b71e 100644
>>> --- a/MAINTAINERS
>>> +++ b/MAINTAINERS
>>> @@ -8041,7 +8041,7 @@ M:      Jim Cromie <jim.cromie@gmail.com>
>>>    S:  Maintained
>>>    F:  include/linux/dynamic_debug.h
>>>    F:  lib/dynamic_debug.c
>>> -F:   lib/test_dynamic_debug.c
>>> +F:   lib/test_dynamic_debug*.c
>>>
>>>    DYNAMIC INTERRUPT MODERATION
>>>    M:  Tal Gilboa <talgi@nvidia.com>
>>> diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
>>> index 54504013c749..eb93fd09832b 100644
>>> --- a/include/asm-generic/vmlinux.lds.h
>>> +++ b/include/asm-generic/vmlinux.lds.h
>>> @@ -366,6 +366,7 @@ defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPELLER_CLANG)
>>>        /* implement dynamic printk debug */                            \
>>>        . = ALIGN(8);                                                   \
>>>        BOUNDED_SECTION_BY(__dyndbg_classes, ___dyndbg_classes)         \
>>> +     BOUNDED_SECTION_BY(__dyndbg_class_users, ___dyndbg_class_users) \
>>>        BOUNDED_SECTION_BY(__dyndbg, ___dyndbg)                         \
>>>        CODETAG_SECTIONS()                                              \
>>>        LIKELY_PROFILE()                                                \
>>> diff --git a/include/linux/dynamic_debug.h b/include/linux/dynamic_debug.h
>>> index c8102e89beb2..dc610a12b91c 100644
>>> --- a/include/linux/dynamic_debug.h
>>> +++ b/include/linux/dynamic_debug.h
>>> @@ -71,9 +71,28 @@ enum ddebug_class_map_type {
>>>         */
>>>    };
>>>
>>> +/*
> 
> BTW, Im gonna refine this ...
> 
>>> + * dyndbg-classmaps are devised to support DRM.debug directly:
>>> + *    10 enum-vals: DRM_UT_* define the categories
>>> + *   ~23 categorized *_dbg() macros, each passing a DRM_UT_* val as 1st arg
>>> + *     2 macros below them: drm_dev_dbg, __drm_dbg
>>> + * ~5000 calls to the categorized macros, across all of drivers/gpu/drm/
>>> + *
>>> + * When CONFIG_DRM_USE_DYNAMIC_DEBUG=y, the 2 low macros are redefined
>>> + * to invoke _dynamic_func_call_cls().  This compiles the category
>>> + * into each callsite's class_id field, where dyndbg can select on it
>>> + * and alter a callsite's patch-state, avoiding repeated __drm_debug
>>> + * checks.
>>> + *
>>> + * To make the callsites manageable from the >control file, authors
>>> + * provide a "classmap" of names to class_ids in use by the module(s),
>>> + * usually by stringifying the enum-vals.  Modules with multiple
>>> + * classmaps must arrange to share the 0..62 class_id space.
>>> + */
>>> +
>>>    struct ddebug_class_map {
>>> -     struct module *mod;
>>> -     const char *mod_name;   /* needed for builtins */
>>> +     const struct module *mod;               /* NULL for builtins */
>>> +     const char *mod_name;
>>
>> Is this change belongs to this patch? If not can you move this in a
>> "cleanup" patch?
> 
> It belongs, insofar as the mod field is actually NULL checked to determine
> builtin vs modular, mod_name is backup.
> Also its more specific than "needed for builtins"
> 
> But, grumble, ya, the above was an existing test, not a new one.
> OTOH - I guess Ive just written the cleanup commit msg :-)
> 
>>
>>>        const char **class_names;
>>>        const int length;
>>>        const int base;         /* index of 1st .class_id, allows split/shared space */
>>> @@ -81,11 +100,34 @@ struct ddebug_class_map {
>>>    };
>>>
>>>    /**
>>> - * DECLARE_DYNDBG_CLASSMAP - declare classnames known by a module
>>> - * @_var:   a struct ddebug_class_map, passed to module_param_cb
>>> - * @_type:  enum class_map_type, chooses bits/verbose, numeric/symbolic
>>> - * @_base:  offset of 1st class-name. splits .class_id space
>>> - * @classes: class-names used to control class'd prdbgs
>>> + * DYNDBG_CLASSMAP_DEFINE - define debug classes used by a module.
>>> + * @_var:   name of the classmap, exported for other modules coordinated use.
>>> + * @_mapty: enum ddebug_class_map_type: 0:DISJOINT - independent, 1:LEVEL - v2>v1> + * @_base:  reserve N classids starting at _base, to split 0..62
>> classid space
>>> + * @classes: names of the N classes.
>>> + *
>>> + * This tells dyndbg what class_ids the module is using: _base..+N, by
>>> + * mapping names onto them.  This qualifies "class NAME" >controls on
>>> + * the defining module, ignoring unknown names.
>>> + */
>>> +#define DYNDBG_CLASSMAP_DEFINE(_var, _mapty, _base, ...)             \
>>> +     static const char *_var##_classnames[] = { __VA_ARGS__ };       \
>>> +     extern struct ddebug_class_map _var;                            \
>>> +     struct ddebug_class_map __aligned(8) __used                     \
>>> +             __section("__dyndbg_classes") _var = {                  \
>>> +             .mod = THIS_MODULE,                                     \
>>> +             .mod_name = KBUILD_MODNAME,                             \
>>> +             .base = (_base),                                        \
>>> +             .map_type = (_mapty),                                   \
>>> +             .length = ARRAY_SIZE(_var##_classnames),                \
>>> +             .class_names = _var##_classnames,                       \
>>> +     };                                                              \
>>> +     EXPORT_SYMBOL(_var)
>>> +
>>> +/*
>>> + * XXX: keep this until DRM adapts to use the DEFINE/USE api, it
>>> + * differs from DYNDBG_CLASSMAP_DEFINE by the static replacing the
>>> + * extern/EXPORT on the struct init.
>>>     */
>>>    #define DECLARE_DYNDBG_CLASSMAP(_var, _maptype, _base, ...)         \
>>>        static const char *_var##_classnames[] = { __VA_ARGS__ };       \
>>> @@ -99,12 +141,37 @@ struct ddebug_class_map {
>>>                .class_names = _var##_classnames,                       \
>>>        }
>>>
>>> +struct ddebug_class_user {
>>> +     char *mod_name;
>>> +     struct ddebug_class_map *map;
>>> +};
>>> +
>>> +/**
>>> + * DYNDBG_CLASSMAP_USE - refer to a classmap, DEFINEd elsewhere.
>>> + * @_var: name of the exported classmap var
>>> + *
>>> + * This tells dyndbg that the module has prdbgs with classids defined
>>> + * in the named classmap.  This qualifies "class NAME" >controls on
>>> + * the user module, ignoring unknown names.
>>> + */
>>> +#define DYNDBG_CLASSMAP_USE(_var)                                    \
>>> +     DYNDBG_CLASSMAP_USE_(_var, __UNIQUE_ID(ddebug_class_user))
>>> +#define DYNDBG_CLASSMAP_USE_(_var, _uname)                           \
>>> +     extern struct ddebug_class_map _var;                            \
>>> +     static struct ddebug_class_user __aligned(8) __used             \
>>> +     __section("__dyndbg_class_users") _uname = {                    \
>>> +             .mod_name = KBUILD_MODNAME,                             \
>>> +             .map = &(_var),                                         \
>>> +     }
>>> +
>>
>> I'm not sure I understand properly how __section works, but can we use
>> multiple DYNDBG_CLASSMAP_USE in one module? Can we also use
>> DYNDBG_CLASSMAP_DEFINE while also importing an other classmap
>> DYNDBG_CLASSMAP_USE?
>>
> 
> Yes, its supposed to work that way.
> 
> I havent tested that specific scenario (yet), but
> a _USEr module, like test-dynamic-debug-submod,
> could also _DEFINE its own, as long as it honors
> the class-id mapping it is using and therefore sharing.
> The on-modprobe conflict check should catch this condition.
> 
> And __section (ISTM) accumulates entries, typically static struct var
> initializations.
> AFAICT, scanning the sections is how these { scoped statics } are
> often reachable.
> 
> For example, dd's _METADATA_ builds a { static _ddebug } for every pr_debug.
> They all go into the __dyndbg section (renamed with _descriptors suffix soon),
> in the order their respective definer objects are linked.
> 
> include/asm-generic/vmlinux.lds.h  then places the __dyndbg_* sections
> into DATA, along with lots of other freight, for the various
> mechanisms they serve.
> 
> 
> 
> 
>> If not, does it make sense to allow it (for example MFD devices can
>> touch multiple subsystems)?
> 
> We have another use case !
> Do you know your way around that case ?
>

No, I don't have other use cases, I was just thinking about possible 
scenarios of the "include multiple classmap".

So, happy to konw it is not an issue with the section, but do I 
understand properly the code (completly hypotetical example): if drm.ko 
defines classes 0..10 and spi.ko defines classes 0..4, it means 
driver.ko can't use both classmap? (I don't have such use-case, and 
maybe this use-case does not exists!)

The only solution I see is to add more stuff in the _ddebug structure 
(for example a "classmap identifier"). But for now, the current user 
will be DRM, so we don't really need to fix this issue right now.

I just found a possible user for dyndbg classes [1], it seems to 
implement something similar.

[1]:https://elixir.bootlin.com/linux/v6.13.7/source/drivers/block/drbd/drbd_polymorph_printk.h

> Note that DEFINEr  & USEr calls set up linkage dependencies,
> As long as these are consistent with other module deps,
> it should work.
> 
> 
>>
>>>    /* encapsulate linker provided built-in (or module) dyndbg data */
>>>    struct _ddebug_info {
>>>        struct _ddebug *descs;
>>>        struct ddebug_class_map *classes;
>>> +     struct ddebug_class_user *class_users;
>>>        unsigned int num_descs;
>>>        unsigned int num_classes;
>>> +     unsigned int num_class_users;
>>>    };
>>>
>>>    struct ddebug_class_param {
>>> @@ -205,7 +272,7 @@ void __dynamic_ibdev_dbg(struct _ddebug *descriptor,
>>>     * (|_no_desc):     former gets callsite descriptor as 1st arg (for prdbgs)
>>>     */
>>>    #define __dynamic_func_call_cls(id, cls, fmt, func, ...) do {       \
>>> -     DEFINE_DYNAMIC_DEBUG_METADATA_CLS(id, cls, fmt);        \
>>> +     DEFINE_DYNAMIC_DEBUG_METADATA_CLS((id), cls, fmt);      \
>>
>> Can you move this in a "cleanup" patch?
>>
>>>        if (DYNAMIC_DEBUG_BRANCH(id))                           \
>>>                func(&id, ##__VA_ARGS__);                       \
>>>    } while (0)
>>> diff --git a/kernel/module/main.c b/kernel/module/main.c
>>> index 5399c182b3cb..c394a0c6e8c6 100644
>>> --- a/kernel/module/main.c
>>> +++ b/kernel/module/main.c
>>> @@ -2546,6 +2546,9 @@ static int find_module_sections(struct module *mod, struct load_info *info)
>>>        mod->dyndbg_info.classes = section_objs(info, "__dyndbg_classes",
>>>                                                sizeof(*mod->dyndbg_info.classes),
>>>                                                &mod->dyndbg_info.num_classes);
>>> +     mod->dyndbg_info.class_users = section_objs(info, "__dyndbg_class_users",
>>> +                                                 sizeof(*mod->dyndbg_info.class_users),
>>> +                                                &mod->dyndbg_info.num_class_users);
>>>    #endif
>>>
>>>        return 0;
>>> diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
>>> index f3d723705879..933f69ef87f8 100644
>>> --- a/lib/Kconfig.debug
>>> +++ b/lib/Kconfig.debug
>>> @@ -2884,12 +2884,26 @@ config TEST_STATIC_KEYS
>>>          If unsure, say N.
>>>
>>>    config TEST_DYNAMIC_DEBUG
>>> -     tristate "Test DYNAMIC_DEBUG"
>>> -     depends on DYNAMIC_DEBUG
>>> +     tristate "Build test-dynamic-debug module"
>>> +     depends on DYNAMIC_DEBUG || DYNAMIC_DEBUG_CORE
>>>        help
>>> -       This module registers a tracer callback to count enabled
>>> -       pr_debugs in a 'do_debugging' function, then alters their
>>> -       enablements, calls the function, and compares counts.
>>> +       This module exercises/demonstrates dyndbg's classmap API, by
>>> +       creating 2 classes: a DISJOINT classmap (supporting DRM.debug)
>>> +       and a LEVELS/VERBOSE classmap (like verbose2 > verbose1).
>>> +
>>> +       If unsure, say N.
>>> +
>>> +config TEST_DYNAMIC_DEBUG_SUBMOD
>>> +     tristate "Build test-dynamic-debug submodule"
>>> +     default m
>>> +     depends on DYNAMIC_DEBUG || DYNAMIC_DEBUG_CORE
>>> +     depends on TEST_DYNAMIC_DEBUG
>>> +     help
>>> +       This sub-module uses a classmap defined and exported by the
>>> +       parent module, recapitulating drm & driver's shared use of
>>> +       drm.debug to control enabled debug-categories.
>>> +       It is tristate, independent of parent, to allow testing all
>>> +       proper combinations of parent=y/m submod=y/m.
>>>
>>>          If unsure, say N.
>>>
>>> diff --git a/lib/Makefile b/lib/Makefile
>>> index a8155c972f02..7f66fc1c163d 100644
>>> --- a/lib/Makefile
>>> +++ b/lib/Makefile
>>> @@ -84,6 +84,7 @@ obj-$(CONFIG_TEST_SORT) += test_sort.o
>>>    obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_keys.o
>>>    obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_key_base.o
>>>    obj-$(CONFIG_TEST_DYNAMIC_DEBUG) += test_dynamic_debug.o
>>> +obj-$(CONFIG_TEST_DYNAMIC_DEBUG_SUBMOD) += test_dynamic_debug_submod.o
>>>    obj-$(CONFIG_TEST_PRINTF) += test_printf.o
>>>    obj-$(CONFIG_TEST_SCANF) += test_scanf.o
>>>
>>> @@ -228,6 +229,8 @@ obj-$(CONFIG_ARCH_NEED_CMPXCHG_1_EMU) += cmpxchg-emu.o
>>>    obj-$(CONFIG_DYNAMIC_DEBUG_CORE) += dynamic_debug.o
>>>    #ensure exported functions have prototypes
>>>    CFLAGS_dynamic_debug.o := -DDYNAMIC_DEBUG_MODULE
>>> +CFLAGS_test_dynamic_debug.o := -DDYNAMIC_DEBUG_MODULE
>>> +CFLAGS_test_dynamic_debug_submod.o := -DDYNAMIC_DEBUG_MODULE
>>>
>>>    obj-$(CONFIG_SYMBOLIC_ERRNAME) += errname.o
>>>
>>> diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c
>>> index 094d6e62a9d1..6bca0c6727d4 100644
>>> --- a/lib/dynamic_debug.c
>>> +++ b/lib/dynamic_debug.c
>>> @@ -43,13 +43,16 @@ extern struct _ddebug __start___dyndbg[];
>>>    extern struct _ddebug __stop___dyndbg[];
>>>    extern struct ddebug_class_map __start___dyndbg_classes[];
>>>    extern struct ddebug_class_map __stop___dyndbg_classes[];
>>> +extern struct ddebug_class_user __start___dyndbg_class_users[];
>>> +extern struct ddebug_class_user __stop___dyndbg_class_users[];
>>>
>>>    struct ddebug_table {
>>>        struct list_head link;
>>>        const char *mod_name;
>>>        struct _ddebug *ddebugs;
>>>        struct ddebug_class_map *classes;
>>> -     unsigned int num_ddebugs, num_classes;
>>> +     struct ddebug_class_user *class_users;
>>> +     unsigned int num_ddebugs, num_classes, num_class_users;
>>>    };
>>>
>>>    struct ddebug_query {
>>> @@ -148,23 +151,35 @@ static void vpr_info_dq(const struct ddebug_query *query, const char *msg)
>>>                  query->first_lineno, query->last_lineno, query->class_string);
>>>    }
>>>
>>> -#define __outvar /* filled by callee */
>>> -static struct ddebug_class_map *ddebug_find_valid_class(struct ddebug_table const *dt,
>>> -                                                     const char *class_string,
>>> -                                                     __outvar int *class_id)
>>> +#define vpr_dt_info(dt_p, msg_p, ...) ({                             \
>>> +     struct ddebug_table const *_dt = dt_p;                          \
>>> +     v2pr_info(msg_p " module:%s nd:%d nc:%d nu:%d\n", ##__VA_ARGS__, \
>>> +               _dt->mod_name, _dt->num_ddebugs, _dt->num_classes,    \
>>> +               _dt->num_class_users);                                \
>>> +     })
>>> +
>>> +static int ddebug_find_valid_class(struct ddebug_table const *dt, const char *class_string)
>>>    {
>>>        struct ddebug_class_map *map;
>>> +     struct ddebug_class_user *cli;
>>>        int i, idx;
>>>
>>> -     for (map = dt->classes, i = 0; i < dt->num_classes; i++, map++) {
>>> +     for (i = 0, map = dt->classes; i < dt->num_classes; i++, map++) {
>>>                idx = match_string(map->class_names, map->length, class_string);
>>>                if (idx >= 0) {
>>> -                     *class_id = idx + map->base;
>>> -                     return map;
>>> +                     vpr_dt_info(dt, "good-class: %s.%s ", map->mod_name, class_string);
>>> +                     return idx + map->base;
>>>                }
>>>        }
>>> -     *class_id = -ENOENT;
>>> -     return NULL;
>>> +     for (i = 0, cli = dt->class_users; i < dt->num_class_users; i++, cli++) {
>>> +             idx = match_string(cli->map->class_names, cli->map->length, class_string);
>>> +             if (idx >= 0) {
>>> +                     vpr_dt_info(dt, "class-ref: %s -> %s.%s ",
>>> +                                 cli->mod_name, cli->map->mod_name, class_string);
>>> +                     return idx + cli->map->base;
>>> +             }
>>> +     }
>>> +     return -ENOENT;
>>>    }
>>>
>>>    /*
>>> @@ -173,16 +188,14 @@ static struct ddebug_class_map *ddebug_find_valid_class(struct ddebug_table cons
>>>     * callsites, normally the same as number of changes.  If verbose,
>>>     * logs the changes.  Takes ddebug_lock.
>>>     */
>>> -static int ddebug_change(const struct ddebug_query *query,
>>> -                      struct flag_settings *modifiers)
>>> +static int ddebug_change(const struct ddebug_query *query, struct flag_settings *modifiers)
>>>    {
>>>        int i;
>>>        struct ddebug_table *dt;
>>>        unsigned int newflags;
>>>        unsigned int nfound = 0;
>>>        struct flagsbuf fbuf, nbuf;
>>> -     struct ddebug_class_map *map = NULL;
>>> -     int __outvar valid_class;
>>> +     int valid_class;
>>>
>>>        /* search for matching ddebugs */
>>>        mutex_lock(&ddebug_lock);
>>> @@ -194,8 +207,8 @@ static int ddebug_change(const struct ddebug_query *query,
>>>                        continue;
>>>
>>>                if (query->class_string) {
>>> -                     map = ddebug_find_valid_class(dt, query->class_string, &valid_class);
>>> -                     if (!map)
>>> +                     valid_class = ddebug_find_valid_class(dt, query->class_string);
>>> +                     if (valid_class < 0)
>>>                                continue;
>>>                } else {
>>>                        /* constrain query, do not touch class'd callsites */
>>> @@ -559,7 +572,7 @@ static int ddebug_exec_query(char *query_string, const char *modname)
>>>
>>>    /* handle multiple queries in query string, continue on error, return
>>>       last error or number of matching callsites.  Module name is either
>>> -   in param (for boot arg) or perhaps in query string.
>>> +   in the modname arg (for boot args) or perhaps in query string.
>>>    */
>>>    static int ddebug_exec_queries(char *query, const char *modname)
>>>    {
>>> @@ -688,12 +701,12 @@ static int param_set_dyndbg_module_classes(const char *instr,
>>>    }
>>>
>>>    /**
>>> - * param_set_dyndbg_classes - class FOO >control
>>> + * param_set_dyndbg_classes - set all classes in a classmap
>>>     * @instr: string echo>d to sysfs, input depends on map_type
>>> - * @kp:    kp->arg has state: bits/lvl, map, map_type
>>> + * @kp:    kp->arg has state: bits/lvl, classmap, map_type
>>>     *
>>> - * Enable/disable prdbgs by their class, as given in the arguments to
>>> - * DECLARE_DYNDBG_CLASSMAP.  For LEVEL map-types, enforce relative
>>> + * For all classes in the classmap, enable/disable them per the input
>>> + * (depending on map_type).  For LEVEL map-types, enforce relative
>>>     * levels by bitpos.
>>>     *
>>>     * Returns: 0 or <0 if error.
>>> @@ -1038,12 +1051,17 @@ static void *ddebug_proc_next(struct seq_file *m, void *p, loff_t *pos)
>>>    static const char *ddebug_class_name(struct ddebug_table *dt, struct _ddebug *dp)
>>>    {
>>>        struct ddebug_class_map *map = dt->classes;
>>> +     struct ddebug_class_user *cli = dt->class_users;
>>>        int i;
>>>
>>>        for (i = 0; i < dt->num_classes; i++, map++)
>>>                if (class_in_range(dp->class_id, map))
>>>                        return map->class_names[dp->class_id - map->base];
>>>
>>> +     for (i = 0; i < dt->num_class_users; i++, cli++)
>>> +             if (class_in_range(dp->class_id, cli->map))
>>> +                     return cli->map->class_names[dp->class_id - cli->map->base];
>>> +
>>>        return NULL;
>>>    }
>>>
>>> @@ -1124,31 +1142,129 @@ static const struct proc_ops proc_fops = {
>>>        .proc_write = ddebug_proc_write
>>>    };
>>>
>>> -static void ddebug_attach_module_classes(struct ddebug_table *dt, struct _ddebug_info *di)
>>> +#define vpr_cm_info(cm_p, msg_fmt, ...) ({                           \
>>> +     struct ddebug_class_map const *_cm = cm_p;                      \
>>> +     v2pr_info(msg_fmt " %s [%d..%d] %s..%s\n", ##__VA_ARGS__,       \
>>> +               _cm->mod_name, _cm->base, _cm->base + _cm->length,    \
>>> +               _cm->class_names[0], _cm->class_names[_cm->length - 1]); \
>>> +     })
>>> +
>>> +static void ddebug_sync_classbits(const struct kernel_param *kp, const char *modname)
>>> +{
>>> +     const struct ddebug_class_param *dcp = kp->arg;
>>> +
>>> +     /* clamp initial bitvec, mask off hi-bits */
>>> +     if (*dcp->bits & ~CLASSMAP_BITMASK(dcp->map->length)) {
>>> +             *dcp->bits &= CLASSMAP_BITMASK(dcp->map->length);
>>> +             v2pr_info("preset classbits: %lx\n", *dcp->bits);
>>> +     }
>>> +     /* force class'd prdbgs (in USEr module) to match (DEFINEr module) class-param */
>>> +     ddebug_apply_class_bitmap(dcp, dcp->bits, ~0, modname);
>>> +     ddebug_apply_class_bitmap(dcp, dcp->bits, 0, modname);
>>> +}
>>> +
>>> +static void ddebug_match_apply_kparam(const struct kernel_param *kp,
>>> +                                   const struct ddebug_class_map *map,
>>> +                                   const char *modnm)
>>> +{
>>> +     struct ddebug_class_param *dcp;
>>> +
>>> +     if (kp->ops != &param_ops_dyndbg_classes)
>>> +             return;
>>> +
>>> +     dcp = (struct ddebug_class_param *)kp->arg;
>>> +
>>> +     if (map == dcp->map) {
>>> +             v2pr_info(" kp:%s.%s =0x%lx", modnm, kp->name, *dcp->bits);
>>> +             vpr_cm_info(map, " %s mapped to: ", modnm);
>>> +             ddebug_sync_classbits(kp, modnm);
>>> +     }
>>> +}
>>> +
>>> +static void ddebug_apply_params(const struct ddebug_class_map *cm, const char *modnm)
>>> +{
>>> +     const struct kernel_param *kp;
>>> +#if IS_ENABLED(CONFIG_MODULES)
>>> +     int i;
>>> +
>>> +     if (cm->mod) {
>>> +             vpr_cm_info(cm, "loaded classmap: %s", modnm);
>>> +             /* ifdef protects the cm->mod->kp deref */
>>> +             for (i = 0, kp = cm->mod->kp; i < cm->mod->num_kp; i++, kp++)
>>> +                     ddebug_match_apply_kparam(kp, cm, modnm);
>>> +     }
>>> +#endif
>>> +     if (!cm->mod) {
>>> +             vpr_cm_info(cm, "builtin classmap: %s", modnm);
>>> +             for (kp = __start___param; kp < __stop___param; kp++)
>>> +                     ddebug_match_apply_kparam(kp, cm, modnm);
>>> +     }
>>> +}
>>> +
>>> +/*
>>> + * Find this module's classmaps in a sub/whole-range of the builtin/
>>> + * modular classmap vector/section.  Save the start and length of the
>>> + * subrange at its edges.
>>> + */
>>> +static void ddebug_attach_module_classes(struct ddebug_table *dt,
>>> +                                      const struct _ddebug_info *di)
>>>    {
>>>        struct ddebug_class_map *cm;
>>>        int i, nc = 0;
>>>
>>> -     /*
>>> -      * Find this module's classmaps in a subrange/wholerange of
>>> -      * the builtin/modular classmap vector/section.  Save the start
>>> -      * and length of the subrange at its edges.
>>> -      */
>>> -     for (cm = di->classes, i = 0; i < di->num_classes; i++, cm++) {
>>> -
>>> +     for (i = 0, cm = di->classes; i < di->num_classes; i++, cm++) {
>>>                if (!strcmp(cm->mod_name, dt->mod_name)) {
>>> -                     if (!nc) {
>>> -                             v2pr_info("start subrange, class[%d]: module:%s base:%d len:%d ty:%d\n",
>>> -                                       i, cm->mod_name, cm->base, cm->length, cm->map_type);
>>> +                     vpr_cm_info(cm, "classes[%d]:", i);
>>> +                     if (!nc++)
>>>                                dt->classes = cm;
>>> -                     }
>>> -                     nc++;
>>>                }
>>>        }
>>> -     if (nc) {
>>> -             dt->num_classes = nc;
>>> -             vpr_info("module:%s attached %d classes\n", dt->mod_name, nc);
>>> +     if (!nc)
>>> +             return;
>>> +
>>> +     vpr_info("module:%s attached %d classes\n", dt->mod_name, nc);
>>> +     dt->num_classes = nc;
>>> +
>>> +     for (i = 0, cm = dt->classes; i < dt->num_classes; i++, cm++)
>>> +             ddebug_apply_params(cm, cm->mod_name);
>>> +}
>>> +
>>> +/*
>>> + * propagates class-params thru their classmaps to class-users.  this
>>> + * means a query against the dt/module, which means it must be on the
>>> + * list to be seen by ddebug_change.
>>> + */
>>> +static void ddebug_attach_user_module_classes(struct ddebug_table *dt,
>>> +                                           const struct _ddebug_info *di)
>>> +{
>>> +     struct ddebug_class_user *cli;
>>> +     int i, nc = 0;
>>> +
>>> +     /*
>>> +      * For builtins: scan the array, find start/length of this
>>> +      * module's refs, save to dt.  For loadables, this is the
>>> +      * whole array.
>>> +      */
>>> +     for (i = 0, cli = di->class_users; i < di->num_class_users; i++, cli++) {
>>> +             if (WARN_ON_ONCE(!cli || !cli->map || !cli->mod_name))
>>> +                     continue;
>>> +             if (!strcmp(cli->mod_name, dt->mod_name)) {
>>> +                     vpr_cm_info(cli->map, "class_ref[%d] %s -> %s", i,
>>> +                                 cli->mod_name, cli->map->mod_name);
>>> +                     if (!nc++)
>>> +                             dt->class_users = cli;
>>> +             }
>>>        }
>>> +     if (!nc)
>>> +             return;
>>> +
>>> +     dt->num_class_users = nc;
>>> +
>>> +     /* now iterate dt */
>>> +     for (i = 0, cli = dt->class_users; i < dt->num_class_users; i++, cli++)
>>> +             ddebug_apply_params(cli->map, cli->mod_name);
>>> +
>>> +     vpr_dt_info(dt, "attach-client-module: ");
>>>    }
>>>
>>>    /*
>>> @@ -1158,6 +1274,8 @@ static void ddebug_attach_module_classes(struct ddebug_table *dt, struct _ddebug
>>>    static int ddebug_add_module(struct _ddebug_info *di, const char *modname)
>>>    {
>>>        struct ddebug_table *dt;
>>> +     struct _ddebug *iter;
>>> +     int i, class_ct = 0;
>>>
>>>        if (!di->num_descs)
>>>                return 0;
>>> @@ -1181,13 +1299,20 @@ static int ddebug_add_module(struct _ddebug_info *di, const char *modname)
>>>
>>>        INIT_LIST_HEAD(&dt->link);
>>>
>>> -     if (di->classes && di->num_classes)
>>> +     for (i = 0, iter = di->descs; i < di->num_descs; i++, iter++)
>>> +             if (iter->class_id != _DPRINTK_CLASS_DFLT)
>>> +                     class_ct++;
>>> +
>>> +     if (class_ct && di->num_classes)
>>>                ddebug_attach_module_classes(dt, di);
>>>
>>>        mutex_lock(&ddebug_lock);
>>>        list_add_tail(&dt->link, &ddebug_tables);
>>>        mutex_unlock(&ddebug_lock);
>>>
>>> +     if (class_ct && di->num_class_users)
>>> +             ddebug_attach_user_module_classes(dt, di);
>>> +
>>>        vpr_info("%3u debug prints in module %s\n", di->num_descs, modname);
>>>        return 0;
>>>    }
>>> @@ -1337,8 +1462,10 @@ static int __init dynamic_debug_init(void)
>>>        struct _ddebug_info di = {
>>>                .descs = __start___dyndbg,
>>>                .classes = __start___dyndbg_classes,
>>> +             .class_users = __start___dyndbg_class_users,
>>>                .num_descs = __stop___dyndbg - __start___dyndbg,
>>>                .num_classes = __stop___dyndbg_classes - __start___dyndbg_classes,
>>> +             .num_class_users = __stop___dyndbg_class_users - __start___dyndbg_class_users,
>>>        };
>>>
>>>    #ifdef CONFIG_MODULES
>>> diff --git a/lib/test_dynamic_debug.c b/lib/test_dynamic_debug.c
>>> index 74d183ebf3e0..1838f62738c4 100644
>>> --- a/lib/test_dynamic_debug.c
>>> +++ b/lib/test_dynamic_debug.c
>>> @@ -6,11 +6,15 @@
>>>     *      Jim Cromie  <jim.cromie@gmail.com>
>>>     */
>>>
>>> -#define pr_fmt(fmt) "test_dd: " fmt
>>> +#if defined(TEST_DYNAMIC_DEBUG_SUBMOD)
>>> +  #define pr_fmt(fmt) "test_dd_submod: " fmt
>>> +#else
>>> +  #define pr_fmt(fmt) "test_dd: " fmt
>>> +#endif
>>>
>>>    #include <linux/module.h>
>>>
>>> -/* run tests by reading or writing sysfs node: do_prints */
>>> +/* re-gen output by reading or writing sysfs node: do_prints */
>>>
>>>    static void do_prints(void); /* device under test */
>>>    static int param_set_do_prints(const char *instr, const struct kernel_param *kp)
>>> @@ -29,24 +33,39 @@ static const struct kernel_param_ops param_ops_do_prints = {
>>>    };
>>>    module_param_cb(do_prints, &param_ops_do_prints, NULL, 0600);
>>>
>>> -/*
>>> - * Using the CLASSMAP api:
>>> - * - classmaps must have corresponding enum
>>> - * - enum symbols must match/correlate with class-name strings in the map.
>>> - * - base must equal enum's 1st value
>>> - * - multiple maps must set their base to share the 0-30 class_id space !!
>>> - *   (build-bug-on tips welcome)
>>> - * Additionally, here:
>>> - * - tie together sysname, mapname, bitsname, flagsname
>>> - */
>>> -#define DD_SYS_WRAP(_model, _flags)                                  \
>>> -     static unsigned long bits_##_model;                             \
>>> -     static struct ddebug_class_param _flags##_model = {             \
>>> +#define CLASSMAP_BITMASK(width, base) (((1UL << (width)) - 1) << (base))
>>> +
>>> +/* sysfs param wrapper, proto-API */
>>> +#define DYNDBG_CLASSMAP_PARAM_(_model, _flags, _init)                        \
>>> +     static unsigned long bits_##_model = _init;                     \
>>> +     static struct ddebug_class_param _flags##_##_model = {          \
>>>                .bits = &bits_##_model,                                 \
>>>                .flags = #_flags,                                       \
>>>                .map = &map_##_model,                                   \
>>>        };                                                              \
>>> -     module_param_cb(_flags##_##_model, &param_ops_dyndbg_classes, &_flags##_model, 0600)
>>> +     module_param_cb(_flags##_##_model, &param_ops_dyndbg_classes,   \
>>> +                     &_flags##_##_model, 0600)
>>> +#ifdef DEBUG
>>> +#define DYNDBG_CLASSMAP_PARAM(_model, _flags)  DYNDBG_CLASSMAP_PARAM_(_model, _flags, ~0)
>>> +#else
>>> +#define DYNDBG_CLASSMAP_PARAM(_model, _flags)  DYNDBG_CLASSMAP_PARAM_(_model, _flags, 0)
>>> +#endif
>>> +
>>> +/*
>>> + * Demonstrate/test all 4 class-typed classmaps with a sys-param.
>>> + *
>>> + * Each is 3 part: client-enum decl, _DEFINE, _PARAM.
>>> + * Declare them in blocks to show patterns of use (repetitions and
>>> + * changes) within each.
>>> + *
>>> + * 1st, dyndbg expects a client-provided enum-type as source of
>>> + * category/classid truth.  DRM has DRM_UT_<CORE,DRIVER,KMS,etc>.
>>> + *
>>> + * Modules with multiple CLASSMAPS must have enums with distinct
>>> + * value-ranges, arranged below with explicit enum_sym = X inits.
>>> + *
>>> + * Declare all 4 enums now, for different types
>>> + */
>>>
>>>    /* numeric input, independent bits */
>>>    enum cat_disjoint_bits {
>>> @@ -60,26 +79,51 @@ enum cat_disjoint_bits {
>>>        D2_LEASE,
>>>        D2_DP,
>>>        D2_DRMRES };
>>> -DECLARE_DYNDBG_CLASSMAP(map_disjoint_bits, DD_CLASS_TYPE_DISJOINT_BITS, 0,
>>> -                     "D2_CORE",
>>> -                     "D2_DRIVER",
>>> -                     "D2_KMS",
>>> -                     "D2_PRIME",
>>> -                     "D2_ATOMIC",
>>> -                     "D2_VBL",
>>> -                     "D2_STATE",
>>> -                     "D2_LEASE",
>>> -                     "D2_DP",
>>> -                     "D2_DRMRES");
>>> -DD_SYS_WRAP(disjoint_bits, p);
>>> -DD_SYS_WRAP(disjoint_bits, T);
>>>
>>>    /* numeric verbosity, V2 > V1 related */
>>>    enum cat_level_num { V0 = 14, V1, V2, V3, V4, V5, V6, V7 };
>>> -DECLARE_DYNDBG_CLASSMAP(map_level_num, DD_CLASS_TYPE_LEVEL_NUM, 14,
>>> -                    "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7");
>>> -DD_SYS_WRAP(level_num, p);
>>> -DD_SYS_WRAP(level_num, T);
>>> +
>>> +/* recapitulate DRM's parent(drm.ko) <-- _submod(drivers,helpers) */
>>> +#if !defined(TEST_DYNAMIC_DEBUG_SUBMOD)
>>
>> It was not clear at first read that the test_dynamic_debug.c code is
>> used twice.
>>
> 
> Yes, that was a miss.  Now in big comment block at the top of both,
> right before the pr-fmt  mod-name shortening for the logs.
> 
> 
>> What do you think about creating a third file "common" with
>> do_prints/do_levels/... implementations and avoid this #if !defined?
>>
> 
> I'd rather not.  I think the multi-module usage is sufficiently interconnected
> (common enum, parent side, clone side) that all-in-one presentation
> helps to communicate this "linkage".
> 
> I hope the new comments clearly elucidate this.  PLMK if not.
> 
>>> +/*
>>> + * In single user, or parent / coordinator (drm.ko) modules, define
>>> + * classmaps on the client enums above, and then declares the PARAMS
>>> + * ref'g the classmaps.  Each is exported.
>>> + */
>>> +DYNDBG_CLASSMAP_DEFINE(map_disjoint_bits, DD_CLASS_TYPE_DISJOINT_BITS,
>>
>> map_disjoint_bits is defined in both test_dynamic_debug.c and
>> test_dynamic_debug_submodule.c, is it normal/required?
> 
> Its the enum : cat_disjoint_bits, that is defined for both.
> 
> the _DEFINE itself is right after the ifdef TEST_MOD_SUBMOD.
> 
> do you think it needs a better MOD_SUBMOD name ?
> or an ifndef ?
> 
>>
>>> +                    D2_CORE,
>>> +                    "D2_CORE",
>>> +                    "D2_DRIVER",
>>> +                    "D2_KMS",
>>> +                    "D2_PRIME",
>>> +                    "D2_ATOMIC",
>>> +                    "D2_VBL",
>>> +                    "D2_STATE",
>>> +                    "D2_LEASE",
>>> +                    "D2_DP",
>>> +                    "D2_DRMRES");
>>> +
>>> +DYNDBG_CLASSMAP_DEFINE(map_level_num, DD_CLASS_TYPE_LEVEL_NUM,
>>> +                    V0, "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7");
>>> +
>>> +/*
>>> + * now add the sysfs-params
>>> + */
>>> +
>>> +DYNDBG_CLASSMAP_PARAM(disjoint_bits, p);
>>> +DYNDBG_CLASSMAP_PARAM(level_num, p);
>>> +
>>> +#else /* TEST_DYNAMIC_DEBUG_SUBMOD */
>>> +
>>> +/*
>>> + * in submod/drm-drivers, use the classmaps defined in top/parent
>>> + * module above.
>>> + */
>>> +
>>> +DYNDBG_CLASSMAP_USE(map_disjoint_bits);
>>> +DYNDBG_CLASSMAP_USE(map_level_num);
>>> +
>>> +#endif
>>>
>>>    /* stand-in for all pr_debug etc */
>>>    #define prdbg(SYM) __pr_debug_cls(SYM, #SYM " msg\n")
>>> @@ -115,6 +159,7 @@ static void do_levels(void)
>>>
>>>    static void do_prints(void)
>>>    {
>>> +     pr_debug("do_prints:\n");
>>>        do_cats();
>>>        do_levels();
>>
>> I just observed it, is the ordering of logs garanteed in
>> /proc/dynamic_debug/control?
>>
> 
> Yes. the fully ordered table/__section is pretty important.
> If its not true for descriptors, its probably not true for class-maps
> or users either
> and the list-iter --> vector-block conversion a few commits ago isnt
> solid either.
> 
> its also an explicit assumption under these commits
> 
> 773beabbb8e8 dyndbg: reverse module.callsite walk in cat control
> 2ad556f70043 dyndbg: reverse module walk in cat control
> 
> All in all, I think something would have broken by now.
> 
> 
>> When I run this test, I have this order:
>>
>> do_cats =_ "doing categories\n"
>> [...]
>> do_levels =_ "doing levels\n"
>> [...]
>> do_prints =_ "do_prints:\n"
>> test_dynamic_debug_init =_ "init start\n"
>> test_dynamic_debug_init =_ "init done\n"
>> test_dynamic_debug_exit =_ "exited\n"
>>
>> Which is clearly not the code execution order.
> 
> Youre correct.
> the control file reflects compile link order.
> 
> Im pretty sure the appearance of init 1st in control derives from init/builtin.a
> being built 1st and/or linked 1st.
> 
>>
>>>    }
>>> diff --git a/lib/test_dynamic_debug_submod.c b/lib/test_dynamic_debug_submod.c
>>> new file mode 100644
>>> index 000000000000..9a893402ce1a
>>> --- /dev/null
>>> +++ b/lib/test_dynamic_debug_submod.c
>>> @@ -0,0 +1,10 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + * Kernel module for testing dynamic_debug
>>> + *
>>> + * Authors:
>>> + *      Jim Cromie   <jim.cromie@gmail.com>
>>> + */
>>> +
>>> +#define TEST_DYNAMIC_DEBUG_SUBMOD
>>> +#include "test_dynamic_debug.c"
>>
>> --
>> Louis Chauvet, Bootlin
>> Embedded Linux and Kernel engineering
>> https://bootlin.com
>>
> 
> thanks again Lous,
> Jim
Jim Cromie March 25, 2025, 4:03 p.m. UTC | #4
On Mon, Mar 24, 2025 at 9:07 AM Louis Chauvet <louis.chauvet@bootlin.com> wrote:
>
trimming

> >>> +     __section("__dyndbg_class_users") _uname = {                    \
> >>> +             .mod_name = KBUILD_MODNAME,                             \
> >>> +             .map = &(_var),                                         \
> >>> +     }
> >>> +
> >>
> >> I'm not sure I understand properly how __section works, but can we use
> >> multiple DYNDBG_CLASSMAP_USE in one module? Can we also use
> >> DYNDBG_CLASSMAP_DEFINE while also importing an other classmap
> >> DYNDBG_CLASSMAP_USE?
> >>
> >
> > Yes, its supposed to work that way.
> >
> > I havent tested that specific scenario (yet), but
> > a _USEr module, like test-dynamic-debug-submod,
> > could also _DEFINE its own, as long as it honors
> > the class-id mapping it is using and therefore sharing.
> > The on-modprobe conflict check should catch this condition.
> >
> > And __section (ISTM) accumulates entries, typically static struct var
> > initializations.
> > AFAICT, scanning the sections is how these { scoped statics } are
> > often reachable.
> >
> > For example, dd's _METADATA_ builds a { static _ddebug } for every pr_debug.
> > They all go into the __dyndbg section (renamed with _descriptors suffix soon),
> > in the order their respective definer objects are linked.
> >
> > include/asm-generic/vmlinux.lds.h  then places the __dyndbg_* sections
> > into DATA, along with lots of other freight, for the various
> > mechanisms they serve.
> >
> >
> >
> >
> >> If not, does it make sense to allow it (for example MFD devices can
> >> touch multiple subsystems)?
> >
> > We have another use case !
> > Do you know your way around that case ?
> >
>
> No, I don't have other use cases, I was just thinking about possible
> scenarios of the "include multiple classmap".
>
> So, happy to konw it is not an issue with the section, but do I
> understand properly the code (completly hypotetical example): if drm.ko
> defines classes 0..10 and spi.ko defines classes 0..4, it means
> driver.ko can't use both classmap? (I don't have such use-case, and
> maybe this use-case does not exists!)
>

It sounds realistic on the face of it, so lets break it down:

1st off, drm drivers/helpers are full dependents on (core) drm.ko.
they are the "subsystem" users I considered.

This dependence is "+1" by _USE ref'g the exported DEFINE product.
If that dependence doesn't suit a situation, it doesn't quite fit.
The dependence dictates module-load order, amongst other things.

So it follows that spi.ko would never be a dependent module on drm.ko,
if there is a relationship, DRM would use spi, and whatever classes it defines.

Suppose spi.ko DEFINEd a classmap:  with ENDPOINT, TRANSPORT, BULK
categories of pr_debugs,  those classes would need to map to different class-ids
than DRM_UT_<*>, cuz the callsites only have the classids, not the
name-->id mapping.

if both DRM_UT_CORE and ENPOINT had class-id = 0,
then both these commands would alter the same set of pr-debugs

  echo class DRM_UT_CORE +p > /proc/dynamic-debug/control
  echo class SPI_ENDPOINT -p > /proc/dynamic-debug/control

Thats not as troublesome as it might seem:

DRM's DRM_UT_<*> vals are only exposed to userspace
by the existence of : /sys/module/drm/parameter/debug,
cuz it exposes the bit values in >debug input.

and this already changed DRM_UT_<*> vals wo anybody caring.
0406faf25fb1 drm_print: condense enum drm_debug_category

DYNAMIC-DEBUG-CLASSMAP-DEFINE() has _base arg,
which offsets the natural/obvious 0..N range to allow sharing of 0..62 range.

The outer edge of inconvenience in coordinating class-id reservations
would be N providers and M consumers. ATM, N=M=1.

Say DRM  used 2 modules defining classmaps:  spi (as discussed),
and (pure wag) gpu_engine.
Those 2 modules dont really have any other connection, but would have
to coordinate
(but maybe gpu_engine needs spi to control its cooling, and would want
to anyway)

DRM (or any classmap-definer/user) is also free to define a 2nd "category"
enum to separate the user facing aspects of DRM_UT_*
from its name->ID mapping (very internal)

Also:  "classnames" are a public-namespace; theres nothing
to stop a module from defining their own version of "DRM_UT_CORE",
and getting their pr_debugs enabled/disabled along with all of DRMs callsites.

Such a use-case would be obvious in review, and would want some justification.

WAG:  a _base arg to the _USE() macro could specify a local user offset.

Theres a saying:  if youre explaining, youre losing.

How'd I do ?

> The only solution I see is to add more stuff in the _ddebug structure
> (for example a "classmap identifier"). But for now, the current user
> will be DRM, so we don't really need to fix this issue right now.
>
> I just found a possible user for dyndbg classes [1], it seems to
> implement something similar.
>
> [1]:https://elixir.bootlin.com/linux/v6.13.7/source/drivers/block/drbd/drbd_polymorph_printk.h
>

thats a pretty impressive stack of macros.
I like the componentry and assembly

One thing that puts it out of scope is its use of KERN_EMERG, CRIT etc.
dyndbg is just KERN_DEBUG

Also those #undef *DYNAMIC_DEBUG*
explicitly unwire  dyndbg apparatus, and plug into __dynamic_pr_debug directly.
Making it an interesting case.


> > Note that DEFINEr  & USEr calls set up linkage dependencies,
> > As long as these are consistent with other module deps,
> > it should work.
> >
> >
> >>
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 0fa7c5728f1e..38afccb3b71e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8041,7 +8041,7 @@  M:	Jim Cromie <jim.cromie@gmail.com>
 S:	Maintained
 F:	include/linux/dynamic_debug.h
 F:	lib/dynamic_debug.c
-F:	lib/test_dynamic_debug.c
+F:	lib/test_dynamic_debug*.c
 
 DYNAMIC INTERRUPT MODERATION
 M:	Tal Gilboa <talgi@nvidia.com>
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 54504013c749..eb93fd09832b 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -366,6 +366,7 @@  defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPELLER_CLANG)
 	/* implement dynamic printk debug */				\
 	. = ALIGN(8);							\
 	BOUNDED_SECTION_BY(__dyndbg_classes, ___dyndbg_classes)		\
+	BOUNDED_SECTION_BY(__dyndbg_class_users, ___dyndbg_class_users)	\
 	BOUNDED_SECTION_BY(__dyndbg, ___dyndbg)				\
 	CODETAG_SECTIONS()						\
 	LIKELY_PROFILE()		       				\
diff --git a/include/linux/dynamic_debug.h b/include/linux/dynamic_debug.h
index c8102e89beb2..dc610a12b91c 100644
--- a/include/linux/dynamic_debug.h
+++ b/include/linux/dynamic_debug.h
@@ -71,9 +71,28 @@  enum ddebug_class_map_type {
 	 */
 };
 
+/*
+ * dyndbg-classmaps are devised to support DRM.debug directly:
+ *    10 enum-vals: DRM_UT_* define the categories
+ *   ~23 categorized *_dbg() macros, each passing a DRM_UT_* val as 1st arg
+ *     2 macros below them: drm_dev_dbg, __drm_dbg
+ * ~5000 calls to the categorized macros, across all of drivers/gpu/drm/
+ *
+ * When CONFIG_DRM_USE_DYNAMIC_DEBUG=y, the 2 low macros are redefined
+ * to invoke _dynamic_func_call_cls().  This compiles the category
+ * into each callsite's class_id field, where dyndbg can select on it
+ * and alter a callsite's patch-state, avoiding repeated __drm_debug
+ * checks.
+ *
+ * To make the callsites manageable from the >control file, authors
+ * provide a "classmap" of names to class_ids in use by the module(s),
+ * usually by stringifying the enum-vals.  Modules with multiple
+ * classmaps must arrange to share the 0..62 class_id space.
+ */
+
 struct ddebug_class_map {
-	struct module *mod;
-	const char *mod_name;	/* needed for builtins */
+	const struct module *mod;		/* NULL for builtins */
+	const char *mod_name;
 	const char **class_names;
 	const int length;
 	const int base;		/* index of 1st .class_id, allows split/shared space */
@@ -81,11 +100,34 @@  struct ddebug_class_map {
 };
 
 /**
- * DECLARE_DYNDBG_CLASSMAP - declare classnames known by a module
- * @_var:   a struct ddebug_class_map, passed to module_param_cb
- * @_type:  enum class_map_type, chooses bits/verbose, numeric/symbolic
- * @_base:  offset of 1st class-name. splits .class_id space
- * @classes: class-names used to control class'd prdbgs
+ * DYNDBG_CLASSMAP_DEFINE - define debug classes used by a module.
+ * @_var:   name of the classmap, exported for other modules coordinated use.
+ * @_mapty: enum ddebug_class_map_type: 0:DISJOINT - independent, 1:LEVEL - v2>v1
+ * @_base:  reserve N classids starting at _base, to split 0..62 classid space
+ * @classes: names of the N classes.
+ *
+ * This tells dyndbg what class_ids the module is using: _base..+N, by
+ * mapping names onto them.  This qualifies "class NAME" >controls on
+ * the defining module, ignoring unknown names.
+ */
+#define DYNDBG_CLASSMAP_DEFINE(_var, _mapty, _base, ...)		\
+	static const char *_var##_classnames[] = { __VA_ARGS__ };	\
+	extern struct ddebug_class_map _var;				\
+	struct ddebug_class_map __aligned(8) __used			\
+		__section("__dyndbg_classes") _var = {			\
+		.mod = THIS_MODULE,					\
+		.mod_name = KBUILD_MODNAME,				\
+		.base = (_base),					\
+		.map_type = (_mapty),					\
+		.length = ARRAY_SIZE(_var##_classnames),		\
+		.class_names = _var##_classnames,			\
+	};								\
+	EXPORT_SYMBOL(_var)
+
+/*
+ * XXX: keep this until DRM adapts to use the DEFINE/USE api, it
+ * differs from DYNDBG_CLASSMAP_DEFINE by the static replacing the
+ * extern/EXPORT on the struct init.
  */
 #define DECLARE_DYNDBG_CLASSMAP(_var, _maptype, _base, ...)		\
 	static const char *_var##_classnames[] = { __VA_ARGS__ };	\
@@ -99,12 +141,37 @@  struct ddebug_class_map {
 		.class_names = _var##_classnames,			\
 	}
 
+struct ddebug_class_user {
+	char *mod_name;
+	struct ddebug_class_map *map;
+};
+
+/**
+ * DYNDBG_CLASSMAP_USE - refer to a classmap, DEFINEd elsewhere.
+ * @_var: name of the exported classmap var
+ *
+ * This tells dyndbg that the module has prdbgs with classids defined
+ * in the named classmap.  This qualifies "class NAME" >controls on
+ * the user module, ignoring unknown names.
+ */
+#define DYNDBG_CLASSMAP_USE(_var)					\
+	DYNDBG_CLASSMAP_USE_(_var, __UNIQUE_ID(ddebug_class_user))
+#define DYNDBG_CLASSMAP_USE_(_var, _uname)				\
+	extern struct ddebug_class_map _var;				\
+	static struct ddebug_class_user __aligned(8) __used		\
+	__section("__dyndbg_class_users") _uname = {			\
+		.mod_name = KBUILD_MODNAME,				\
+		.map = &(_var),						\
+	}
+
 /* encapsulate linker provided built-in (or module) dyndbg data */
 struct _ddebug_info {
 	struct _ddebug *descs;
 	struct ddebug_class_map *classes;
+	struct ddebug_class_user *class_users;
 	unsigned int num_descs;
 	unsigned int num_classes;
+	unsigned int num_class_users;
 };
 
 struct ddebug_class_param {
@@ -205,7 +272,7 @@  void __dynamic_ibdev_dbg(struct _ddebug *descriptor,
  * (|_no_desc):	former gets callsite descriptor as 1st arg (for prdbgs)
  */
 #define __dynamic_func_call_cls(id, cls, fmt, func, ...) do {	\
-	DEFINE_DYNAMIC_DEBUG_METADATA_CLS(id, cls, fmt);	\
+	DEFINE_DYNAMIC_DEBUG_METADATA_CLS((id), cls, fmt);	\
 	if (DYNAMIC_DEBUG_BRANCH(id))				\
 		func(&id, ##__VA_ARGS__);			\
 } while (0)
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 5399c182b3cb..c394a0c6e8c6 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -2546,6 +2546,9 @@  static int find_module_sections(struct module *mod, struct load_info *info)
 	mod->dyndbg_info.classes = section_objs(info, "__dyndbg_classes",
 						sizeof(*mod->dyndbg_info.classes),
 						&mod->dyndbg_info.num_classes);
+	mod->dyndbg_info.class_users = section_objs(info, "__dyndbg_class_users",
+						    sizeof(*mod->dyndbg_info.class_users),
+						   &mod->dyndbg_info.num_class_users);
 #endif
 
 	return 0;
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index f3d723705879..933f69ef87f8 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2884,12 +2884,26 @@  config TEST_STATIC_KEYS
 	  If unsure, say N.
 
 config TEST_DYNAMIC_DEBUG
-	tristate "Test DYNAMIC_DEBUG"
-	depends on DYNAMIC_DEBUG
+	tristate "Build test-dynamic-debug module"
+	depends on DYNAMIC_DEBUG || DYNAMIC_DEBUG_CORE
 	help
-	  This module registers a tracer callback to count enabled
-	  pr_debugs in a 'do_debugging' function, then alters their
-	  enablements, calls the function, and compares counts.
+	  This module exercises/demonstrates dyndbg's classmap API, by
+	  creating 2 classes: a DISJOINT classmap (supporting DRM.debug)
+	  and a LEVELS/VERBOSE classmap (like verbose2 > verbose1).
+
+	  If unsure, say N.
+
+config TEST_DYNAMIC_DEBUG_SUBMOD
+	tristate "Build test-dynamic-debug submodule"
+	default m
+	depends on DYNAMIC_DEBUG || DYNAMIC_DEBUG_CORE
+	depends on TEST_DYNAMIC_DEBUG
+	help
+	  This sub-module uses a classmap defined and exported by the
+	  parent module, recapitulating drm & driver's shared use of
+	  drm.debug to control enabled debug-categories.
+	  It is tristate, independent of parent, to allow testing all
+	  proper combinations of parent=y/m submod=y/m.
 
 	  If unsure, say N.
 
diff --git a/lib/Makefile b/lib/Makefile
index a8155c972f02..7f66fc1c163d 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -84,6 +84,7 @@  obj-$(CONFIG_TEST_SORT) += test_sort.o
 obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_keys.o
 obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_key_base.o
 obj-$(CONFIG_TEST_DYNAMIC_DEBUG) += test_dynamic_debug.o
+obj-$(CONFIG_TEST_DYNAMIC_DEBUG_SUBMOD) += test_dynamic_debug_submod.o
 obj-$(CONFIG_TEST_PRINTF) += test_printf.o
 obj-$(CONFIG_TEST_SCANF) += test_scanf.o
 
@@ -228,6 +229,8 @@  obj-$(CONFIG_ARCH_NEED_CMPXCHG_1_EMU) += cmpxchg-emu.o
 obj-$(CONFIG_DYNAMIC_DEBUG_CORE) += dynamic_debug.o
 #ensure exported functions have prototypes
 CFLAGS_dynamic_debug.o := -DDYNAMIC_DEBUG_MODULE
+CFLAGS_test_dynamic_debug.o := -DDYNAMIC_DEBUG_MODULE
+CFLAGS_test_dynamic_debug_submod.o := -DDYNAMIC_DEBUG_MODULE
 
 obj-$(CONFIG_SYMBOLIC_ERRNAME) += errname.o
 
diff --git a/lib/dynamic_debug.c b/lib/dynamic_debug.c
index 094d6e62a9d1..6bca0c6727d4 100644
--- a/lib/dynamic_debug.c
+++ b/lib/dynamic_debug.c
@@ -43,13 +43,16 @@  extern struct _ddebug __start___dyndbg[];
 extern struct _ddebug __stop___dyndbg[];
 extern struct ddebug_class_map __start___dyndbg_classes[];
 extern struct ddebug_class_map __stop___dyndbg_classes[];
+extern struct ddebug_class_user __start___dyndbg_class_users[];
+extern struct ddebug_class_user __stop___dyndbg_class_users[];
 
 struct ddebug_table {
 	struct list_head link;
 	const char *mod_name;
 	struct _ddebug *ddebugs;
 	struct ddebug_class_map *classes;
-	unsigned int num_ddebugs, num_classes;
+	struct ddebug_class_user *class_users;
+	unsigned int num_ddebugs, num_classes, num_class_users;
 };
 
 struct ddebug_query {
@@ -148,23 +151,35 @@  static void vpr_info_dq(const struct ddebug_query *query, const char *msg)
 		  query->first_lineno, query->last_lineno, query->class_string);
 }
 
-#define __outvar /* filled by callee */
-static struct ddebug_class_map *ddebug_find_valid_class(struct ddebug_table const *dt,
-							const char *class_string,
-							__outvar int *class_id)
+#define vpr_dt_info(dt_p, msg_p, ...) ({				\
+	struct ddebug_table const *_dt = dt_p;				\
+	v2pr_info(msg_p " module:%s nd:%d nc:%d nu:%d\n", ##__VA_ARGS__, \
+		  _dt->mod_name, _dt->num_ddebugs, _dt->num_classes,	\
+		  _dt->num_class_users);				\
+	})
+
+static int ddebug_find_valid_class(struct ddebug_table const *dt, const char *class_string)
 {
 	struct ddebug_class_map *map;
+	struct ddebug_class_user *cli;
 	int i, idx;
 
-	for (map = dt->classes, i = 0; i < dt->num_classes; i++, map++) {
+	for (i = 0, map = dt->classes; i < dt->num_classes; i++, map++) {
 		idx = match_string(map->class_names, map->length, class_string);
 		if (idx >= 0) {
-			*class_id = idx + map->base;
-			return map;
+			vpr_dt_info(dt, "good-class: %s.%s ", map->mod_name, class_string);
+			return idx + map->base;
 		}
 	}
-	*class_id = -ENOENT;
-	return NULL;
+	for (i = 0, cli = dt->class_users; i < dt->num_class_users; i++, cli++) {
+		idx = match_string(cli->map->class_names, cli->map->length, class_string);
+		if (idx >= 0) {
+			vpr_dt_info(dt, "class-ref: %s -> %s.%s ",
+				    cli->mod_name, cli->map->mod_name, class_string);
+			return idx + cli->map->base;
+		}
+	}
+	return -ENOENT;
 }
 
 /*
@@ -173,16 +188,14 @@  static struct ddebug_class_map *ddebug_find_valid_class(struct ddebug_table cons
  * callsites, normally the same as number of changes.  If verbose,
  * logs the changes.  Takes ddebug_lock.
  */
-static int ddebug_change(const struct ddebug_query *query,
-			 struct flag_settings *modifiers)
+static int ddebug_change(const struct ddebug_query *query, struct flag_settings *modifiers)
 {
 	int i;
 	struct ddebug_table *dt;
 	unsigned int newflags;
 	unsigned int nfound = 0;
 	struct flagsbuf fbuf, nbuf;
-	struct ddebug_class_map *map = NULL;
-	int __outvar valid_class;
+	int valid_class;
 
 	/* search for matching ddebugs */
 	mutex_lock(&ddebug_lock);
@@ -194,8 +207,8 @@  static int ddebug_change(const struct ddebug_query *query,
 			continue;
 
 		if (query->class_string) {
-			map = ddebug_find_valid_class(dt, query->class_string, &valid_class);
-			if (!map)
+			valid_class = ddebug_find_valid_class(dt, query->class_string);
+			if (valid_class < 0)
 				continue;
 		} else {
 			/* constrain query, do not touch class'd callsites */
@@ -559,7 +572,7 @@  static int ddebug_exec_query(char *query_string, const char *modname)
 
 /* handle multiple queries in query string, continue on error, return
    last error or number of matching callsites.  Module name is either
-   in param (for boot arg) or perhaps in query string.
+   in the modname arg (for boot args) or perhaps in query string.
 */
 static int ddebug_exec_queries(char *query, const char *modname)
 {
@@ -688,12 +701,12 @@  static int param_set_dyndbg_module_classes(const char *instr,
 }
 
 /**
- * param_set_dyndbg_classes - class FOO >control
+ * param_set_dyndbg_classes - set all classes in a classmap
  * @instr: string echo>d to sysfs, input depends on map_type
- * @kp:    kp->arg has state: bits/lvl, map, map_type
+ * @kp:    kp->arg has state: bits/lvl, classmap, map_type
  *
- * Enable/disable prdbgs by their class, as given in the arguments to
- * DECLARE_DYNDBG_CLASSMAP.  For LEVEL map-types, enforce relative
+ * For all classes in the classmap, enable/disable them per the input
+ * (depending on map_type).  For LEVEL map-types, enforce relative
  * levels by bitpos.
  *
  * Returns: 0 or <0 if error.
@@ -1038,12 +1051,17 @@  static void *ddebug_proc_next(struct seq_file *m, void *p, loff_t *pos)
 static const char *ddebug_class_name(struct ddebug_table *dt, struct _ddebug *dp)
 {
 	struct ddebug_class_map *map = dt->classes;
+	struct ddebug_class_user *cli = dt->class_users;
 	int i;
 
 	for (i = 0; i < dt->num_classes; i++, map++)
 		if (class_in_range(dp->class_id, map))
 			return map->class_names[dp->class_id - map->base];
 
+	for (i = 0; i < dt->num_class_users; i++, cli++)
+		if (class_in_range(dp->class_id, cli->map))
+			return cli->map->class_names[dp->class_id - cli->map->base];
+
 	return NULL;
 }
 
@@ -1124,31 +1142,129 @@  static const struct proc_ops proc_fops = {
 	.proc_write = ddebug_proc_write
 };
 
-static void ddebug_attach_module_classes(struct ddebug_table *dt, struct _ddebug_info *di)
+#define vpr_cm_info(cm_p, msg_fmt, ...) ({				\
+	struct ddebug_class_map const *_cm = cm_p;			\
+	v2pr_info(msg_fmt " %s [%d..%d] %s..%s\n", ##__VA_ARGS__,	\
+		  _cm->mod_name, _cm->base, _cm->base + _cm->length,	\
+		  _cm->class_names[0], _cm->class_names[_cm->length - 1]); \
+	})
+
+static void ddebug_sync_classbits(const struct kernel_param *kp, const char *modname)
+{
+	const struct ddebug_class_param *dcp = kp->arg;
+
+	/* clamp initial bitvec, mask off hi-bits */
+	if (*dcp->bits & ~CLASSMAP_BITMASK(dcp->map->length)) {
+		*dcp->bits &= CLASSMAP_BITMASK(dcp->map->length);
+		v2pr_info("preset classbits: %lx\n", *dcp->bits);
+	}
+	/* force class'd prdbgs (in USEr module) to match (DEFINEr module) class-param */
+	ddebug_apply_class_bitmap(dcp, dcp->bits, ~0, modname);
+	ddebug_apply_class_bitmap(dcp, dcp->bits, 0, modname);
+}
+
+static void ddebug_match_apply_kparam(const struct kernel_param *kp,
+				      const struct ddebug_class_map *map,
+				      const char *modnm)
+{
+	struct ddebug_class_param *dcp;
+
+	if (kp->ops != &param_ops_dyndbg_classes)
+		return;
+
+	dcp = (struct ddebug_class_param *)kp->arg;
+
+	if (map == dcp->map) {
+		v2pr_info(" kp:%s.%s =0x%lx", modnm, kp->name, *dcp->bits);
+		vpr_cm_info(map, " %s mapped to: ", modnm);
+		ddebug_sync_classbits(kp, modnm);
+	}
+}
+
+static void ddebug_apply_params(const struct ddebug_class_map *cm, const char *modnm)
+{
+	const struct kernel_param *kp;
+#if IS_ENABLED(CONFIG_MODULES)
+	int i;
+
+	if (cm->mod) {
+		vpr_cm_info(cm, "loaded classmap: %s", modnm);
+		/* ifdef protects the cm->mod->kp deref */
+		for (i = 0, kp = cm->mod->kp; i < cm->mod->num_kp; i++, kp++)
+			ddebug_match_apply_kparam(kp, cm, modnm);
+	}
+#endif
+	if (!cm->mod) {
+		vpr_cm_info(cm, "builtin classmap: %s", modnm);
+		for (kp = __start___param; kp < __stop___param; kp++)
+			ddebug_match_apply_kparam(kp, cm, modnm);
+	}
+}
+
+/*
+ * Find this module's classmaps in a sub/whole-range of the builtin/
+ * modular classmap vector/section.  Save the start and length of the
+ * subrange at its edges.
+ */
+static void ddebug_attach_module_classes(struct ddebug_table *dt,
+					 const struct _ddebug_info *di)
 {
 	struct ddebug_class_map *cm;
 	int i, nc = 0;
 
-	/*
-	 * Find this module's classmaps in a subrange/wholerange of
-	 * the builtin/modular classmap vector/section.  Save the start
-	 * and length of the subrange at its edges.
-	 */
-	for (cm = di->classes, i = 0; i < di->num_classes; i++, cm++) {
-
+	for (i = 0, cm = di->classes; i < di->num_classes; i++, cm++) {
 		if (!strcmp(cm->mod_name, dt->mod_name)) {
-			if (!nc) {
-				v2pr_info("start subrange, class[%d]: module:%s base:%d len:%d ty:%d\n",
-					  i, cm->mod_name, cm->base, cm->length, cm->map_type);
+			vpr_cm_info(cm, "classes[%d]:", i);
+			if (!nc++)
 				dt->classes = cm;
-			}
-			nc++;
 		}
 	}
-	if (nc) {
-		dt->num_classes = nc;
-		vpr_info("module:%s attached %d classes\n", dt->mod_name, nc);
+	if (!nc)
+		return;
+
+	vpr_info("module:%s attached %d classes\n", dt->mod_name, nc);
+	dt->num_classes = nc;
+
+	for (i = 0, cm = dt->classes; i < dt->num_classes; i++, cm++)
+		ddebug_apply_params(cm, cm->mod_name);
+}
+
+/*
+ * propagates class-params thru their classmaps to class-users.  this
+ * means a query against the dt/module, which means it must be on the
+ * list to be seen by ddebug_change.
+ */
+static void ddebug_attach_user_module_classes(struct ddebug_table *dt,
+					      const struct _ddebug_info *di)
+{
+	struct ddebug_class_user *cli;
+	int i, nc = 0;
+
+	/*
+	 * For builtins: scan the array, find start/length of this
+	 * module's refs, save to dt.  For loadables, this is the
+	 * whole array.
+	 */
+	for (i = 0, cli = di->class_users; i < di->num_class_users; i++, cli++) {
+		if (WARN_ON_ONCE(!cli || !cli->map || !cli->mod_name))
+			continue;
+		if (!strcmp(cli->mod_name, dt->mod_name)) {
+			vpr_cm_info(cli->map, "class_ref[%d] %s -> %s", i,
+				    cli->mod_name, cli->map->mod_name);
+			if (!nc++)
+				dt->class_users = cli;
+		}
 	}
+	if (!nc)
+		return;
+
+	dt->num_class_users = nc;
+
+	/* now iterate dt */
+	for (i = 0, cli = dt->class_users; i < dt->num_class_users; i++, cli++)
+		ddebug_apply_params(cli->map, cli->mod_name);
+
+	vpr_dt_info(dt, "attach-client-module: ");
 }
 
 /*
@@ -1158,6 +1274,8 @@  static void ddebug_attach_module_classes(struct ddebug_table *dt, struct _ddebug
 static int ddebug_add_module(struct _ddebug_info *di, const char *modname)
 {
 	struct ddebug_table *dt;
+	struct _ddebug *iter;
+	int i, class_ct = 0;
 
 	if (!di->num_descs)
 		return 0;
@@ -1181,13 +1299,20 @@  static int ddebug_add_module(struct _ddebug_info *di, const char *modname)
 
 	INIT_LIST_HEAD(&dt->link);
 
-	if (di->classes && di->num_classes)
+	for (i = 0, iter = di->descs; i < di->num_descs; i++, iter++)
+		if (iter->class_id != _DPRINTK_CLASS_DFLT)
+			class_ct++;
+
+	if (class_ct && di->num_classes)
 		ddebug_attach_module_classes(dt, di);
 
 	mutex_lock(&ddebug_lock);
 	list_add_tail(&dt->link, &ddebug_tables);
 	mutex_unlock(&ddebug_lock);
 
+	if (class_ct && di->num_class_users)
+		ddebug_attach_user_module_classes(dt, di);
+
 	vpr_info("%3u debug prints in module %s\n", di->num_descs, modname);
 	return 0;
 }
@@ -1337,8 +1462,10 @@  static int __init dynamic_debug_init(void)
 	struct _ddebug_info di = {
 		.descs = __start___dyndbg,
 		.classes = __start___dyndbg_classes,
+		.class_users = __start___dyndbg_class_users,
 		.num_descs = __stop___dyndbg - __start___dyndbg,
 		.num_classes = __stop___dyndbg_classes - __start___dyndbg_classes,
+		.num_class_users = __stop___dyndbg_class_users - __start___dyndbg_class_users,
 	};
 
 #ifdef CONFIG_MODULES
diff --git a/lib/test_dynamic_debug.c b/lib/test_dynamic_debug.c
index 74d183ebf3e0..1838f62738c4 100644
--- a/lib/test_dynamic_debug.c
+++ b/lib/test_dynamic_debug.c
@@ -6,11 +6,15 @@ 
  *      Jim Cromie	<jim.cromie@gmail.com>
  */
 
-#define pr_fmt(fmt) "test_dd: " fmt
+#if defined(TEST_DYNAMIC_DEBUG_SUBMOD)
+  #define pr_fmt(fmt) "test_dd_submod: " fmt
+#else
+  #define pr_fmt(fmt) "test_dd: " fmt
+#endif
 
 #include <linux/module.h>
 
-/* run tests by reading or writing sysfs node: do_prints */
+/* re-gen output by reading or writing sysfs node: do_prints */
 
 static void do_prints(void); /* device under test */
 static int param_set_do_prints(const char *instr, const struct kernel_param *kp)
@@ -29,24 +33,39 @@  static const struct kernel_param_ops param_ops_do_prints = {
 };
 module_param_cb(do_prints, &param_ops_do_prints, NULL, 0600);
 
-/*
- * Using the CLASSMAP api:
- * - classmaps must have corresponding enum
- * - enum symbols must match/correlate with class-name strings in the map.
- * - base must equal enum's 1st value
- * - multiple maps must set their base to share the 0-30 class_id space !!
- *   (build-bug-on tips welcome)
- * Additionally, here:
- * - tie together sysname, mapname, bitsname, flagsname
- */
-#define DD_SYS_WRAP(_model, _flags)					\
-	static unsigned long bits_##_model;				\
-	static struct ddebug_class_param _flags##_model = {		\
+#define CLASSMAP_BITMASK(width, base) (((1UL << (width)) - 1) << (base))
+
+/* sysfs param wrapper, proto-API */
+#define DYNDBG_CLASSMAP_PARAM_(_model, _flags, _init)			\
+	static unsigned long bits_##_model = _init;			\
+	static struct ddebug_class_param _flags##_##_model = {		\
 		.bits = &bits_##_model,					\
 		.flags = #_flags,					\
 		.map = &map_##_model,					\
 	};								\
-	module_param_cb(_flags##_##_model, &param_ops_dyndbg_classes, &_flags##_model, 0600)
+	module_param_cb(_flags##_##_model, &param_ops_dyndbg_classes,	\
+			&_flags##_##_model, 0600)
+#ifdef DEBUG
+#define DYNDBG_CLASSMAP_PARAM(_model, _flags)  DYNDBG_CLASSMAP_PARAM_(_model, _flags, ~0)
+#else
+#define DYNDBG_CLASSMAP_PARAM(_model, _flags)  DYNDBG_CLASSMAP_PARAM_(_model, _flags, 0)
+#endif
+
+/*
+ * Demonstrate/test all 4 class-typed classmaps with a sys-param.
+ *
+ * Each is 3 part: client-enum decl, _DEFINE, _PARAM.
+ * Declare them in blocks to show patterns of use (repetitions and
+ * changes) within each.
+ *
+ * 1st, dyndbg expects a client-provided enum-type as source of
+ * category/classid truth.  DRM has DRM_UT_<CORE,DRIVER,KMS,etc>.
+ *
+ * Modules with multiple CLASSMAPS must have enums with distinct
+ * value-ranges, arranged below with explicit enum_sym = X inits.
+ *
+ * Declare all 4 enums now, for different types
+ */
 
 /* numeric input, independent bits */
 enum cat_disjoint_bits {
@@ -60,26 +79,51 @@  enum cat_disjoint_bits {
 	D2_LEASE,
 	D2_DP,
 	D2_DRMRES };
-DECLARE_DYNDBG_CLASSMAP(map_disjoint_bits, DD_CLASS_TYPE_DISJOINT_BITS, 0,
-			"D2_CORE",
-			"D2_DRIVER",
-			"D2_KMS",
-			"D2_PRIME",
-			"D2_ATOMIC",
-			"D2_VBL",
-			"D2_STATE",
-			"D2_LEASE",
-			"D2_DP",
-			"D2_DRMRES");
-DD_SYS_WRAP(disjoint_bits, p);
-DD_SYS_WRAP(disjoint_bits, T);
 
 /* numeric verbosity, V2 > V1 related */
 enum cat_level_num { V0 = 14, V1, V2, V3, V4, V5, V6, V7 };
-DECLARE_DYNDBG_CLASSMAP(map_level_num, DD_CLASS_TYPE_LEVEL_NUM, 14,
-		       "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7");
-DD_SYS_WRAP(level_num, p);
-DD_SYS_WRAP(level_num, T);
+
+/* recapitulate DRM's parent(drm.ko) <-- _submod(drivers,helpers) */
+#if !defined(TEST_DYNAMIC_DEBUG_SUBMOD)
+/*
+ * In single user, or parent / coordinator (drm.ko) modules, define
+ * classmaps on the client enums above, and then declares the PARAMS
+ * ref'g the classmaps.  Each is exported.
+ */
+DYNDBG_CLASSMAP_DEFINE(map_disjoint_bits, DD_CLASS_TYPE_DISJOINT_BITS,
+		       D2_CORE,
+		       "D2_CORE",
+		       "D2_DRIVER",
+		       "D2_KMS",
+		       "D2_PRIME",
+		       "D2_ATOMIC",
+		       "D2_VBL",
+		       "D2_STATE",
+		       "D2_LEASE",
+		       "D2_DP",
+		       "D2_DRMRES");
+
+DYNDBG_CLASSMAP_DEFINE(map_level_num, DD_CLASS_TYPE_LEVEL_NUM,
+		       V0, "V0", "V1", "V2", "V3", "V4", "V5", "V6", "V7");
+
+/*
+ * now add the sysfs-params
+ */
+
+DYNDBG_CLASSMAP_PARAM(disjoint_bits, p);
+DYNDBG_CLASSMAP_PARAM(level_num, p);
+
+#else /* TEST_DYNAMIC_DEBUG_SUBMOD */
+
+/*
+ * in submod/drm-drivers, use the classmaps defined in top/parent
+ * module above.
+ */
+
+DYNDBG_CLASSMAP_USE(map_disjoint_bits);
+DYNDBG_CLASSMAP_USE(map_level_num);
+
+#endif
 
 /* stand-in for all pr_debug etc */
 #define prdbg(SYM) __pr_debug_cls(SYM, #SYM " msg\n")
@@ -115,6 +159,7 @@  static void do_levels(void)
 
 static void do_prints(void)
 {
+	pr_debug("do_prints:\n");
 	do_cats();
 	do_levels();
 }
diff --git a/lib/test_dynamic_debug_submod.c b/lib/test_dynamic_debug_submod.c
new file mode 100644
index 000000000000..9a893402ce1a
--- /dev/null
+++ b/lib/test_dynamic_debug_submod.c
@@ -0,0 +1,10 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Kernel module for testing dynamic_debug
+ *
+ * Authors:
+ *      Jim Cromie	<jim.cromie@gmail.com>
+ */
+
+#define TEST_DYNAMIC_DEBUG_SUBMOD
+#include "test_dynamic_debug.c"