diff mbox series

[RFC,1/2] docs: Add documentation generation for Kconfig symbols

Message ID 20250404-kconfig-docs-v1-1-4c3155d4ba44@collabora.com (mailing list archive)
State New
Headers show
Series Add Kconfig pages and cross-references to Documentation | expand

Commit Message

Nícolas F. R. A. Prado April 4, 2025, 2:02 p.m. UTC
Add the contents of all Kconfig files to the Documentation to both
increase their visibility and allow for cross-referencing throughout the
documentation. In order to achieve this:
* Add a new script 'kconfig2rst' that converts a Kconfig file into a
  reStructuredText document.
* Add an extra step to the documentation building that runs the script
  for every Kconfig in the source tree, generating a documentation page
  for each one.
* Add a new "Kconfig symbols" page in the documentation, that is listed
  on the "Kernel Build System" page, which contains an index of all
  Kconfig files and their Kconfig symbols, linking to the corresponding
  pages.

The generated documentation pages have the config symbols as sections
with labels that can be referenced from anywhere in the documentation.
The exceptions are configs that appear multiple times. Those don't get
labels, as that would generate 'duplicate label' warnings from sphinx.
To allow this, a list of configs that appear more than once is embedded
in the kconfig2rst script. When a config appears more than once in the
same Kconfig file, a count is appended in the section to prevent
sphinx's auto-labeling to cause the same warning.

The paths in 'source' directives in the Kconfig files are turned into
links to the generated documentation page to allow for navigation to
included Kconfig files.

Config symbols on 'depends'/'select'/etc lines are prepended by
'CONFIG_' to allow them to be cross-referenced by automarkup, though no
cross-references are created in this commit.

Signed-off-by: Nícolas F. R. A. Prado <nfraprado@collabora.com>
---
 Documentation/.gitignore       |   2 +
 Documentation/Config/index.rst |  17 +++
 Documentation/Makefile         |  12 +-
 Documentation/kbuild/index.rst |   2 +
 scripts/kconfig2rst.py         | 336 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 368 insertions(+), 1 deletion(-)

Comments

Mauro Carvalho Chehab April 7, 2025, 2:47 a.m. UTC | #1
Em Fri, 04 Apr 2025 10:02:52 -0400
Nícolas F. R. A. Prado <nfraprado@collabora.com> escreveu:

> Add the contents of all Kconfig files to the Documentation to both
> increase their visibility and allow for cross-referencing throughout the
> documentation. In order to achieve this:
> * Add a new script 'kconfig2rst' that converts a Kconfig file into a
>   reStructuredText document.
> * Add an extra step to the documentation building that runs the script
>   for every Kconfig in the source tree, generating a documentation page
>   for each one.
> * Add a new "Kconfig symbols" page in the documentation, that is listed
>   on the "Kernel Build System" page, which contains an index of all
>   Kconfig files and their Kconfig symbols, linking to the corresponding
>   pages.
> 
> The generated documentation pages have the config symbols as sections
> with labels that can be referenced from anywhere in the documentation.
> The exceptions are configs that appear multiple times. Those don't get
> labels, as that would generate 'duplicate label' warnings from sphinx.
> To allow this, a list of configs that appear more than once is embedded
> in the kconfig2rst script. When a config appears more than once in the
> same Kconfig file, a count is appended in the section to prevent
> sphinx's auto-labeling to cause the same warning.
> 
> The paths in 'source' directives in the Kconfig files are turned into
> links to the generated documentation page to allow for navigation to
> included Kconfig files.
> 
> Config symbols on 'depends'/'select'/etc lines are prepended by
> 'CONFIG_' to allow them to be cross-referenced by automarkup, though no
> cross-references are created in this commit.

Despite the huge increase on the time to produce documentation, I'm not
sure how worth is to have it, as there are already cross-reference
services doing something somewhat similar, like:

	https://elixir.bootlin.com

Yet, I didn't test this series yet. So, not sure yet about its
value.

Anyway, it follows some comments about the current implementation.

After addressed on a v2, I intend to test and see how it behaves.

> 
> Signed-off-by: Nícolas F. R. A. Prado <nfraprado@collabora.com>
> ---
>  Documentation/.gitignore       |   2 +
>  Documentation/Config/index.rst |  17 +++
>  Documentation/Makefile         |  12 +-
>  Documentation/kbuild/index.rst |   2 +
>  scripts/kconfig2rst.py         | 336 +++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 368 insertions(+), 1 deletion(-)
> 
> diff --git a/Documentation/.gitignore b/Documentation/.gitignore
> index d6dc7c9b8e25020f1f3b28811df2291c38695d5f..2fc70a398dc874fcb83834cb6337f602c64a070a 100644
> --- a/Documentation/.gitignore
> +++ b/Documentation/.gitignore
> @@ -1,3 +1,5 @@
>  # SPDX-License-Identifier: GPL-2.0-only
>  output
>  *.pyc
> +Config/
> +!Config/index.rst
> diff --git a/Documentation/Config/index.rst b/Documentation/Config/index.rst
> new file mode 100644
> index 0000000000000000000000000000000000000000..2abaa9844dd2a9f57bed0a8d050da3538865b1a5
> --- /dev/null
> +++ b/Documentation/Config/index.rst
> @@ -0,0 +1,17 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +===============
> +Kconfig symbols
> +===============
> +
> +.. toctree::
> +   :glob:
> +   :maxdepth: 2
> +
> +   *
> +   */*
> +   */*/*
> +   */*/*/*
> +   */*/*/*/*
> +   */*/*/*/*/*
> +   */*/*/*/*/*/*

That sounds weird, hard to maintain and probably slow.

Better to have a Sphinx extension instead, with a decent implementation
of glob. The Python's one is slow, on my tests with the Kernel tree.
I worked on something that worked fine for kernel-doc.py:

	https://lore.kernel.org/linux-doc/12a54f1b8f4afd2e70a87195a2aa34f96d736b77.1740387599.git.mchehab+huawei@kernel.org/

Perhaps this script could import the class from it, once such
series gets merged. It could make sense to split it on a separate file
if we're going to re-use its code.

> diff --git a/Documentation/Makefile b/Documentation/Makefile
> index 63094646df2890a788542a273e4a828a844b2932..74ebc5303b47f0837a9ab31d39b5464af5f17995 100644
> --- a/Documentation/Makefile
> +++ b/Documentation/Makefile
> @@ -115,7 +115,7 @@ $(YNL_INDEX): $(YNL_RST_FILES)
>  $(YNL_RST_DIR)/%.rst: $(YNL_YAML_DIR)/%.yaml $(YNL_TOOL)
>  	$(Q)$(YNL_TOOL) -i $< -o $@
>  
> -htmldocs texinfodocs latexdocs epubdocs xmldocs: $(YNL_INDEX)
> +htmldocs texinfodocs latexdocs epubdocs xmldocs: $(YNL_INDEX) kconfigdocs
>  
>  htmldocs:
>  	@$(srctree)/scripts/sphinx-pre-install --version-check
> @@ -182,9 +182,19 @@ endif # HAVE_SPHINX
>  refcheckdocs:
>  	$(Q)cd $(srctree);scripts/documentation-file-ref-check
>  
> +KCONFIG_DOC_DIR=$(srctree)/Documentation/Config
> +KCONFIGS := $(shell find $(srctree) -name Kconfig -type f)
> +KCONFIGS_RST := $(patsubst %, $(KCONFIG_DOC_DIR)/%.rst, $(KCONFIGS))
> +
> +$(KCONFIGS_RST): $(KCONFIGS)
> +	$(Q)cd $(srctree); $(foreach var,$^,$(shell mkdir -p $(KCONFIG_DOC_DIR)/$(shell dirname $(var)); scripts/kconfig2rst.py $(var) >$(KCONFIG_DOC_DIR)/$(var).rst))
> +
> +kconfigdocs: $(KCONFIGS_RST)
> +
>  cleandocs:
>  	$(Q)rm -f $(YNL_INDEX) $(YNL_RST_FILES)
>  	$(Q)rm -rf $(BUILDDIR)
> +	$(Q)rm -rf $(filter-out %index.rst,$(wildcard $(KCONFIG_DOC_DIR)/*))
>  	$(Q)$(MAKE) BUILDDIR=$(abspath $(BUILDDIR)) $(build)=Documentation/userspace-api/media clean
>  
>  dochelp:
> diff --git a/Documentation/kbuild/index.rst b/Documentation/kbuild/index.rst
> index 3731ab22bfe745c5c51963cffe58fb652dadf88c..47a1d9753a9fb7b55b8a7141da8123ca97b15cfb 100644
> --- a/Documentation/kbuild/index.rst
> +++ b/Documentation/kbuild/index.rst
> @@ -15,6 +15,8 @@ Kernel Build System
>      makefiles
>      modules
>  
> +    /Config/index
> +
>      headers_install
>  
>      issues
> diff --git a/scripts/kconfig2rst.py b/scripts/kconfig2rst.py
> new file mode 100755
> index 0000000000000000000000000000000000000000..5af073a1c669ac43c95bb7af00099dcd9473a6ae
> --- /dev/null
> +++ b/scripts/kconfig2rst.py
> @@ -0,0 +1,336 @@
> +#!/usr/bin/env python3
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright 2025 Collabora Ltd
> +
> +import sys
> +import re
> +import os
> +
> +import argparse
> +
> +BASE_PATH_DEFAULT = "Documentation/Config/"
> +CFG_LEN = 60

> +RE_indentation = r"^[ \t]*"
> +in_help_txt = False
> +help_txt = ""

Better to follow Python's standards and keep all constants on uppercase.

I would place the main code inside a class, with the non-const data
inside the class, as it makes the code cleaner and helps using it as
both a Sphinx extension and as a standalone command (which is useful
for testing it).

> +
> +# These configs appear more than once, thus we don't generate labels or xrefs to
> +# them to avoid duplicate label warnings from Sphinx
> +REPEATED_CONFIGS = [
> +    "32BIT",
> +    "4KSTACKS",
> +    "64BIT",
> +    "A",
> +    "ADVANCED_OPTIONS",
> +    "ALPHA_LEGACY_START_ADDRESS",
> +    "ARCH_AIROHA",
> +    "ARCH_ALPINE",
> +    "ARCH_BCM2835",
> +    "ARCH_BCM_IPROC",
> +    "ARCH_BRCMSTB",
> +    "ARCH_DEFAULT_CRASH_DUMP",
> +    "ARCH_FLATMEM_ENABLE",
> +    "ARCH_FORCE_MAX_ORDER",
> +    "ARCH_HAS_ADD_PAGES",
> +    "ARCH_HAS_CACHE_LINE_SIZE",
> +    "ARCH_HAS_GENERIC_CRASHKERNEL_RESERVATION",
> +    "ARCH_HAS_ILOG2_U32",
> +    "ARCH_HAS_ILOG2_U64",
> +    "ARCH_HIBERNATION_HEADER",
> +    "ARCH_HIBERNATION_POSSIBLE",
> +    "ARCH_HISI",
> +    "ARCH_MAY_HAVE_PC_FDC",
> +    "ARCH_MEMORY_PROBE",
> +    "ARCH_MMAP_RND_BITS_MAX",
> +    "ARCH_MMAP_RND_BITS_MIN",
> +    "ARCH_MMAP_RND_COMPAT_BITS_MAX",
> +    "ARCH_MMAP_RND_COMPAT_BITS_MIN",
> +    "ARCH_MTD_XIP",
> +    "ARCH_OMAP",
> +    "ARCH_PKEY_BITS",
> +    "ARCH_PROC_KCORE_TEXT",
> +    "ARCH_R9A07G043",
> +    "ARCH_RENESAS",
> +    "ARCH_ROCKCHIP",
> +    "ARCH_SELECT_MEMORY_MODEL",
> +    "ARCH_SELECTS_CRASH_DUMP",
> +    "ARCH_SELECTS_KEXEC_FILE",
> +    "ARCH_SPARSEMEM_DEFAULT",
> +    "ARCH_SPARSEMEM_ENABLE",
> +    "ARCH_SUNXI",
> +    "ARCH_SUPPORTS_CRASH_DUMP",
> +    "ARCH_SUPPORTS_CRASH_HOTPLUG",
> +    "ARCH_SUPPORTS_KEXEC",
> +    "ARCH_SUPPORTS_KEXEC_FILE",
> +    "ARCH_SUPPORTS_KEXEC_JUMP",
> +    "ARCH_SUPPORTS_KEXEC_PURGATORY",
> +    "ARCH_SUPPORTS_KEXEC_SIG",
> +    "ARCH_SUPPORTS_UPROBES",
> +    "ARCH_SUSPEND_POSSIBLE",
> +    "ARCH_UNIPHIER",
> +    "ARCH_VIRT",
> +    "AUDIT_ARCH",
> +    "B",
> +    "BCH_CONST_M",
> +    "BCH_CONST_T",
> +    "BUILTIN_DTB",
> +    "BUILTIN_DTB_NAME",
> +    "C",
> +    "CC_HAVE_STACKPROTECTOR_TLS",
> +    "CHOICE_B",
> +    "CHOICE_C",
> +    "CMDLINE",
> +    "CMDLINE_BOOL",
> +    "CMDLINE_EXTEND",
> +    "CMDLINE_FORCE",
> +    "CMDLINE_FROM_BOOTLOADER",
> +    "CMDLINE_OVERRIDE",
> +    "CMM",
> +    "COMPAT",
> +    "COMPAT_VDSO",
> +    "CORE",
> +    "CORE_BELL_A",
> +    "CORE_BELL_A_ADVANCED",
> +    "CPU_BIG_ENDIAN",
> +    "CPU_HAS_FPU",
> +    "CPU_HAS_PREFETCH",
> +    "CPU_LITTLE_ENDIAN",
> +    "CRYPTO_CHACHA20_NEON",
> +    "CRYPTO_JITTERENTROPY_MEMORY_BLOCKS",
> +    "CRYPTO_JITTERENTROPY_MEMORY_BLOCKSIZE",
> +    "CRYPTO_JITTERENTROPY_OSR",
> +    "CRYPTO_JITTERENTROPY_TESTINTERFACE",
> +    "CRYPTO_NHPOLY1305_NEON",
> +    "DEBUG_ENTRY",
> +    "DMA_NONCOHERENT",
> +    "DMI",
> +    "DRAM_BASE",
> +    "DUMMY",
> +    "DUMMY_CONSOLE",
> +    "EARLY_PRINTK",
> +    "EFI",
> +    "EFI_STUB",
> +    "FIT_IMAGE_FDT_EPM5",
> +    "FIX_EARLYCON_MEM",
> +    "FPU",
> +    "GENERIC_BUG",
> +    "GENERIC_BUG_RELATIVE_POINTERS",
> +    "GENERIC_CALIBRATE_DELAY",
> +    "GENERIC_CSUM",
> +    "GENERIC_HWEIGHT",
> +    "GENERIC_ISA_DMA",
> +    "GENERIC_LOCKBREAK",
> +    "HAS_IOMEM",
> +    "HAVE_SMP",
> +    "HAVE_TCM",
> +    "HEARTBEAT",
> +    "HIGHMEM",
> +    "HOTPLUG_CPU",
> +    "HW_PERF_EVENTS",
> +    "HZ",
> +    "HZ_100",
> +    "HZ_1000",
> +    "HZ_1024",
> +    "HZ_128",
> +    "HZ_250",
> +    "HZ_256",
> +    "ILLEGAL_POINTER_VALUE",
> +    "IRQSTACKS",
> +    "ISA",
> +    "ISA_DMA_API",
> +    "KASAN_SHADOW_OFFSET",
> +    "KERNEL_MODE_NEON",
> +    "KERNEL_START",
> +    "KERNEL_START_BOOL",
> +    "KUSER_HELPERS",
> +    "KVM",
> +    "KVM_GUEST",
> +    "L1_CACHE_SHIFT",
> +    "LEDS_EXPRESSWIRE",
> +    "LOCKDEP_SUPPORT",
> +    "LOWMEM_SIZE",
> +    "LOWMEM_SIZE_BOOL",
> +    "MACH_LOONGSON32",
> +    "MACH_LOONGSON64",
> +    "MACH_TX49XX",
> +    "MAGIC_SYSRQ",
> +    "MATH_EMULATION",
> +    "MCOUNT",
> +    "MMU",
> +    "NODES_SHIFT",
> +    "NO_IOPORT_MAP",
> +    "NR_CPUS",
> +    "NR_CPUS_DEFAULT",
> +    "NR_CPUS_RANGE_END",
> +    "NUMA",
> +    "PAGE_OFFSET",
> +    "PANIC_TIMEOUT",
> +    "PARAVIRT",
> +    "PARAVIRT_SPINLOCKS",
> +    "PARAVIRT_TIME_ACCOUNTING",
> +    "PFAULT",
> +    "PGTABLE_LEVELS",
> +    "PHYSICAL_ALIGN",
> +    "PHYSICAL_START",
> +    "PID_IN_CONTEXTIDR",
> +    "PM",
> +    "POWERPC64_CPU",
> +    "PRINT_STACK_DEPTH",
> +    "RANDOMIZE_BASE",
> +    "RANDOMIZE_BASE_MAX_OFFSET",
> +    "RELOCATABLE",
> +    "SBUS",
> +    "SCHED_CLUSTER",
> +    "SCHED_HRTICK",
> +    "SCHED_MC",
> +    "SCHED_OMIT_FRAME_POINTER",
> +    "SCHED_SMT",
> +    "SERIAL_CONSOLE",
> +    "SMP",
> +    "STACKPROTECTOR_PER_TASK",
> +    "STACKTRACE_SUPPORT",
> +    "SWAP_IO_SPACE",
> +    "SYS_SUPPORTS_APM_EMULATION",
> +    "SYS_SUPPORTS_NUMA",
> +    "SYS_SUPPORTS_SMP",
> +    "TASK_SIZE",
> +    "TASK_SIZE_BOOL",
> +    "TCP_CONG_CUBIC",
> +    "TIME_LOW_RES",
> +    "UNWINDER_FRAME_POINTER",
> +    "UNWINDER_GUESS",
> +    "UNWINDER_ORC",
> +    "USE_OF",
> +    "VMSPLIT_1G",
> +    "VMSPLIT_2G",
> +    "VMSPLIT_3G",
> +    "VMSPLIT_3G_OPT",
> +    "X",
> +    "X86_32",
> +    "X86_64",
> +    "XEN",
> +    "XEN_DOM0",
> +    "XIP_KERNEL",
> +    "XIP_PHYS_ADDR",
> +    "ARCH_BCM",
> +    "VIRTUALIZATION",
> +]

Maintaining this sounds a nightmare, as new (eventually duplicated)
symbols may happen anytime.

The best here sounds to do something similar to what I did with
get_abi.py: parse them all altogether, dynamically detecting
duplication. IMO, it also makes sense to have dereference pages
for such duplicated symbols pointing to all occurrences of them.


> +
> +
> +def print_title(title):
> +    heading = "=" * len(title)
> +    print(heading)
> +    print(title)
> +    print(heading)
> +    print()
> +
> +
> +parser = argparse.ArgumentParser(
> +    prog="kconfig2rst", description="Convert a Kconfig file into ReStructuredText"
> +)
> +
> +parser.add_argument("kconfig", help="Path to input Kconfig file")
> +parser.add_argument(
> +    "--base-doc-path",
> +    default=BASE_PATH_DEFAULT,
> +    help="Base path of generated rST files for usage in 'source' links",
> +)
> +args = parser.parse_args()
> +
> +print_title(args.kconfig)
> +
> +line_accum = ""
> +continued_line = False
> +
> +repeated_config_count = {}
> +
> +with open(args.kconfig) as f:
> +    for il in f:

This won't handle directories. Better to use my glob function.

Also, calling lines as as "il" sounds weird for me. I would just
call it "line".

> +        # If line ends with \, accumulate it and handle full line
> +        if re.search(r"\\\n$", il):

Better to use endswith("\\\n"),, as it is faster. We can also use
removesuffix(), as I guess the minimal Python version is now 3.9.

> +            continued_line = True
> +            line_accum += il[:-2]  # accumulate without backslash and newline
> +            continue
> +
> +        if continued_line:
> +            continued_line = False
> +            l = line_accum + il
> +            line_accum = ""
> +        else:
> +            l = il
> +
> +        if in_help_txt:
> +            if l == "\n":
> +                help_txt += l
> +                continue
> +            if first_line_help_txt:
> +                help_txt_indentation = re.match(RE_indentation, l).group(0).expandtabs()

Please compile all regular expressions, to make it faster.

> +                first_line_help_txt = False
> +            # Consider any line with same or more indentation as part of help text
> +            if (
> +                help_txt_indentation
> +                in re.match(RE_indentation, l).group(0).expandtabs()
> +            ):
> +                help_txt += l
> +                continue
> +            else:
> +                in_help_txt = False
> +                print(help_txt)
> +                help_txt = ""
> +        else:
> +            l = re.sub(r"[*]", r"\*", l)  # Escape asterisks
> +
> +        if re.match(r"^[ \t]*#.*", l):
> +            # Skip comments
> +            continue

I would strip comments first, as I guess Kconfig syntaxe allow to use
comments after any texts, like:

	config SYMBOL # some comment

> +
> +        if re.match(r"^[ \t]*help", l):
> +            in_help_txt = True
> +            first_line_help_txt = True
> +            print("* help::\n")
> +            continue
> +
> +        m = re.match("^[ \t]*(menu)?config (?P<cfgname>[A-Za-z0-9_]+)", l)

Better to accept multiple spaces after config, as it would be valid to
have:

	config    FOO

> +        if m:
> +            section_name = f"\nCONFIG_{m.group('cfgname')}"
> +            underline = f"\n{'='*CFG_LEN}\n"
> +            if m.group("cfgname") in REPEATED_CONFIGS:
> +                repeated_config_count[m.group("cfgname")] = (
> +                    repeated_config_count.get(m.group("cfgname"), 0) + 1
> +                )
> +                if repeated_config_count[m.group("cfgname")] > 1:
> +                    section_name += f"({repeated_config_count[m.group('cfgname')]})"
> +                print(section_name + underline)
> +            else:
> +                print(f"\n.. _CONFIG_{m.group('cfgname')}:\n\n" + section_name + underline)
> +            continue
> +
> +        m = re.match(
> +            r"^[ \t]*(def_bool|def_tristate|depends on|select|range|visible if|imply|default|prompt|bool|tristate|string|hex|int|modules)( \"(.*)\")?(?P<expr> [^\"]*)?",
> +            l,
> +        )

I would place the valid matches on an array and do a join to create the
compiled regex to match them. This would make easier to maintain as 
Kconfig syntax add more notations.

> +        if m:
> +            expr = m.group('expr') if m.group('expr') else ''
> +            not_expr = l
> +            if expr:
> +                expr = re.sub(r'[A-Z0-9_]{2,}', rf" CONFIG_\g<0> ", expr)
> +                not_expr = l[:m.start('expr')]
> +            print("* " + not_expr.lstrip() + expr.rstrip())
> +            continue
> +
> +        m = re.match(r'^[ \t]*source "(.*)"', l)
> +        if m:
> +            # Format Kconfig file paths as Documentation/... so they can be turned
> +            # into links by the automarkup plugin
> +            print(f"\nsource {args.base_doc_path + m.group(1)}.rst\n")
> +            continue
> +
> +        m = re.match(r"[^ \t]*choice|endchoice|comment|menu|endmenu|if|endif", l)

Same here.

> +        if m:
> +            print("\n" + l.strip() + "\n")
> +            continue
> +
> +        print(l.strip())
> +
> +if help_txt:
> +    print(help_txt)  # Flush any pending help text
>
diff mbox series

Patch

diff --git a/Documentation/.gitignore b/Documentation/.gitignore
index d6dc7c9b8e25020f1f3b28811df2291c38695d5f..2fc70a398dc874fcb83834cb6337f602c64a070a 100644
--- a/Documentation/.gitignore
+++ b/Documentation/.gitignore
@@ -1,3 +1,5 @@ 
 # SPDX-License-Identifier: GPL-2.0-only
 output
 *.pyc
+Config/
+!Config/index.rst
diff --git a/Documentation/Config/index.rst b/Documentation/Config/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..2abaa9844dd2a9f57bed0a8d050da3538865b1a5
--- /dev/null
+++ b/Documentation/Config/index.rst
@@ -0,0 +1,17 @@ 
+.. SPDX-License-Identifier: GPL-2.0
+
+===============
+Kconfig symbols
+===============
+
+.. toctree::
+   :glob:
+   :maxdepth: 2
+
+   *
+   */*
+   */*/*
+   */*/*/*
+   */*/*/*/*
+   */*/*/*/*/*
+   */*/*/*/*/*/*
diff --git a/Documentation/Makefile b/Documentation/Makefile
index 63094646df2890a788542a273e4a828a844b2932..74ebc5303b47f0837a9ab31d39b5464af5f17995 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -115,7 +115,7 @@  $(YNL_INDEX): $(YNL_RST_FILES)
 $(YNL_RST_DIR)/%.rst: $(YNL_YAML_DIR)/%.yaml $(YNL_TOOL)
 	$(Q)$(YNL_TOOL) -i $< -o $@
 
-htmldocs texinfodocs latexdocs epubdocs xmldocs: $(YNL_INDEX)
+htmldocs texinfodocs latexdocs epubdocs xmldocs: $(YNL_INDEX) kconfigdocs
 
 htmldocs:
 	@$(srctree)/scripts/sphinx-pre-install --version-check
@@ -182,9 +182,19 @@  endif # HAVE_SPHINX
 refcheckdocs:
 	$(Q)cd $(srctree);scripts/documentation-file-ref-check
 
+KCONFIG_DOC_DIR=$(srctree)/Documentation/Config
+KCONFIGS := $(shell find $(srctree) -name Kconfig -type f)
+KCONFIGS_RST := $(patsubst %, $(KCONFIG_DOC_DIR)/%.rst, $(KCONFIGS))
+
+$(KCONFIGS_RST): $(KCONFIGS)
+	$(Q)cd $(srctree); $(foreach var,$^,$(shell mkdir -p $(KCONFIG_DOC_DIR)/$(shell dirname $(var)); scripts/kconfig2rst.py $(var) >$(KCONFIG_DOC_DIR)/$(var).rst))
+
+kconfigdocs: $(KCONFIGS_RST)
+
 cleandocs:
 	$(Q)rm -f $(YNL_INDEX) $(YNL_RST_FILES)
 	$(Q)rm -rf $(BUILDDIR)
+	$(Q)rm -rf $(filter-out %index.rst,$(wildcard $(KCONFIG_DOC_DIR)/*))
 	$(Q)$(MAKE) BUILDDIR=$(abspath $(BUILDDIR)) $(build)=Documentation/userspace-api/media clean
 
 dochelp:
diff --git a/Documentation/kbuild/index.rst b/Documentation/kbuild/index.rst
index 3731ab22bfe745c5c51963cffe58fb652dadf88c..47a1d9753a9fb7b55b8a7141da8123ca97b15cfb 100644
--- a/Documentation/kbuild/index.rst
+++ b/Documentation/kbuild/index.rst
@@ -15,6 +15,8 @@  Kernel Build System
     makefiles
     modules
 
+    /Config/index
+
     headers_install
 
     issues
diff --git a/scripts/kconfig2rst.py b/scripts/kconfig2rst.py
new file mode 100755
index 0000000000000000000000000000000000000000..5af073a1c669ac43c95bb7af00099dcd9473a6ae
--- /dev/null
+++ b/scripts/kconfig2rst.py
@@ -0,0 +1,336 @@ 
+#!/usr/bin/env python3
+# SPDX-License-Identifier: GPL-2.0
+# Copyright 2025 Collabora Ltd
+
+import sys
+import re
+import os
+
+import argparse
+
+BASE_PATH_DEFAULT = "Documentation/Config/"
+CFG_LEN = 60
+RE_indentation = r"^[ \t]*"
+in_help_txt = False
+help_txt = ""
+
+# These configs appear more than once, thus we don't generate labels or xrefs to
+# them to avoid duplicate label warnings from Sphinx
+REPEATED_CONFIGS = [
+    "32BIT",
+    "4KSTACKS",
+    "64BIT",
+    "A",
+    "ADVANCED_OPTIONS",
+    "ALPHA_LEGACY_START_ADDRESS",
+    "ARCH_AIROHA",
+    "ARCH_ALPINE",
+    "ARCH_BCM2835",
+    "ARCH_BCM_IPROC",
+    "ARCH_BRCMSTB",
+    "ARCH_DEFAULT_CRASH_DUMP",
+    "ARCH_FLATMEM_ENABLE",
+    "ARCH_FORCE_MAX_ORDER",
+    "ARCH_HAS_ADD_PAGES",
+    "ARCH_HAS_CACHE_LINE_SIZE",
+    "ARCH_HAS_GENERIC_CRASHKERNEL_RESERVATION",
+    "ARCH_HAS_ILOG2_U32",
+    "ARCH_HAS_ILOG2_U64",
+    "ARCH_HIBERNATION_HEADER",
+    "ARCH_HIBERNATION_POSSIBLE",
+    "ARCH_HISI",
+    "ARCH_MAY_HAVE_PC_FDC",
+    "ARCH_MEMORY_PROBE",
+    "ARCH_MMAP_RND_BITS_MAX",
+    "ARCH_MMAP_RND_BITS_MIN",
+    "ARCH_MMAP_RND_COMPAT_BITS_MAX",
+    "ARCH_MMAP_RND_COMPAT_BITS_MIN",
+    "ARCH_MTD_XIP",
+    "ARCH_OMAP",
+    "ARCH_PKEY_BITS",
+    "ARCH_PROC_KCORE_TEXT",
+    "ARCH_R9A07G043",
+    "ARCH_RENESAS",
+    "ARCH_ROCKCHIP",
+    "ARCH_SELECT_MEMORY_MODEL",
+    "ARCH_SELECTS_CRASH_DUMP",
+    "ARCH_SELECTS_KEXEC_FILE",
+    "ARCH_SPARSEMEM_DEFAULT",
+    "ARCH_SPARSEMEM_ENABLE",
+    "ARCH_SUNXI",
+    "ARCH_SUPPORTS_CRASH_DUMP",
+    "ARCH_SUPPORTS_CRASH_HOTPLUG",
+    "ARCH_SUPPORTS_KEXEC",
+    "ARCH_SUPPORTS_KEXEC_FILE",
+    "ARCH_SUPPORTS_KEXEC_JUMP",
+    "ARCH_SUPPORTS_KEXEC_PURGATORY",
+    "ARCH_SUPPORTS_KEXEC_SIG",
+    "ARCH_SUPPORTS_UPROBES",
+    "ARCH_SUSPEND_POSSIBLE",
+    "ARCH_UNIPHIER",
+    "ARCH_VIRT",
+    "AUDIT_ARCH",
+    "B",
+    "BCH_CONST_M",
+    "BCH_CONST_T",
+    "BUILTIN_DTB",
+    "BUILTIN_DTB_NAME",
+    "C",
+    "CC_HAVE_STACKPROTECTOR_TLS",
+    "CHOICE_B",
+    "CHOICE_C",
+    "CMDLINE",
+    "CMDLINE_BOOL",
+    "CMDLINE_EXTEND",
+    "CMDLINE_FORCE",
+    "CMDLINE_FROM_BOOTLOADER",
+    "CMDLINE_OVERRIDE",
+    "CMM",
+    "COMPAT",
+    "COMPAT_VDSO",
+    "CORE",
+    "CORE_BELL_A",
+    "CORE_BELL_A_ADVANCED",
+    "CPU_BIG_ENDIAN",
+    "CPU_HAS_FPU",
+    "CPU_HAS_PREFETCH",
+    "CPU_LITTLE_ENDIAN",
+    "CRYPTO_CHACHA20_NEON",
+    "CRYPTO_JITTERENTROPY_MEMORY_BLOCKS",
+    "CRYPTO_JITTERENTROPY_MEMORY_BLOCKSIZE",
+    "CRYPTO_JITTERENTROPY_OSR",
+    "CRYPTO_JITTERENTROPY_TESTINTERFACE",
+    "CRYPTO_NHPOLY1305_NEON",
+    "DEBUG_ENTRY",
+    "DMA_NONCOHERENT",
+    "DMI",
+    "DRAM_BASE",
+    "DUMMY",
+    "DUMMY_CONSOLE",
+    "EARLY_PRINTK",
+    "EFI",
+    "EFI_STUB",
+    "FIT_IMAGE_FDT_EPM5",
+    "FIX_EARLYCON_MEM",
+    "FPU",
+    "GENERIC_BUG",
+    "GENERIC_BUG_RELATIVE_POINTERS",
+    "GENERIC_CALIBRATE_DELAY",
+    "GENERIC_CSUM",
+    "GENERIC_HWEIGHT",
+    "GENERIC_ISA_DMA",
+    "GENERIC_LOCKBREAK",
+    "HAS_IOMEM",
+    "HAVE_SMP",
+    "HAVE_TCM",
+    "HEARTBEAT",
+    "HIGHMEM",
+    "HOTPLUG_CPU",
+    "HW_PERF_EVENTS",
+    "HZ",
+    "HZ_100",
+    "HZ_1000",
+    "HZ_1024",
+    "HZ_128",
+    "HZ_250",
+    "HZ_256",
+    "ILLEGAL_POINTER_VALUE",
+    "IRQSTACKS",
+    "ISA",
+    "ISA_DMA_API",
+    "KASAN_SHADOW_OFFSET",
+    "KERNEL_MODE_NEON",
+    "KERNEL_START",
+    "KERNEL_START_BOOL",
+    "KUSER_HELPERS",
+    "KVM",
+    "KVM_GUEST",
+    "L1_CACHE_SHIFT",
+    "LEDS_EXPRESSWIRE",
+    "LOCKDEP_SUPPORT",
+    "LOWMEM_SIZE",
+    "LOWMEM_SIZE_BOOL",
+    "MACH_LOONGSON32",
+    "MACH_LOONGSON64",
+    "MACH_TX49XX",
+    "MAGIC_SYSRQ",
+    "MATH_EMULATION",
+    "MCOUNT",
+    "MMU",
+    "NODES_SHIFT",
+    "NO_IOPORT_MAP",
+    "NR_CPUS",
+    "NR_CPUS_DEFAULT",
+    "NR_CPUS_RANGE_END",
+    "NUMA",
+    "PAGE_OFFSET",
+    "PANIC_TIMEOUT",
+    "PARAVIRT",
+    "PARAVIRT_SPINLOCKS",
+    "PARAVIRT_TIME_ACCOUNTING",
+    "PFAULT",
+    "PGTABLE_LEVELS",
+    "PHYSICAL_ALIGN",
+    "PHYSICAL_START",
+    "PID_IN_CONTEXTIDR",
+    "PM",
+    "POWERPC64_CPU",
+    "PRINT_STACK_DEPTH",
+    "RANDOMIZE_BASE",
+    "RANDOMIZE_BASE_MAX_OFFSET",
+    "RELOCATABLE",
+    "SBUS",
+    "SCHED_CLUSTER",
+    "SCHED_HRTICK",
+    "SCHED_MC",
+    "SCHED_OMIT_FRAME_POINTER",
+    "SCHED_SMT",
+    "SERIAL_CONSOLE",
+    "SMP",
+    "STACKPROTECTOR_PER_TASK",
+    "STACKTRACE_SUPPORT",
+    "SWAP_IO_SPACE",
+    "SYS_SUPPORTS_APM_EMULATION",
+    "SYS_SUPPORTS_NUMA",
+    "SYS_SUPPORTS_SMP",
+    "TASK_SIZE",
+    "TASK_SIZE_BOOL",
+    "TCP_CONG_CUBIC",
+    "TIME_LOW_RES",
+    "UNWINDER_FRAME_POINTER",
+    "UNWINDER_GUESS",
+    "UNWINDER_ORC",
+    "USE_OF",
+    "VMSPLIT_1G",
+    "VMSPLIT_2G",
+    "VMSPLIT_3G",
+    "VMSPLIT_3G_OPT",
+    "X",
+    "X86_32",
+    "X86_64",
+    "XEN",
+    "XEN_DOM0",
+    "XIP_KERNEL",
+    "XIP_PHYS_ADDR",
+    "ARCH_BCM",
+    "VIRTUALIZATION",
+]
+
+
+def print_title(title):
+    heading = "=" * len(title)
+    print(heading)
+    print(title)
+    print(heading)
+    print()
+
+
+parser = argparse.ArgumentParser(
+    prog="kconfig2rst", description="Convert a Kconfig file into ReStructuredText"
+)
+
+parser.add_argument("kconfig", help="Path to input Kconfig file")
+parser.add_argument(
+    "--base-doc-path",
+    default=BASE_PATH_DEFAULT,
+    help="Base path of generated rST files for usage in 'source' links",
+)
+args = parser.parse_args()
+
+print_title(args.kconfig)
+
+line_accum = ""
+continued_line = False
+
+repeated_config_count = {}
+
+with open(args.kconfig) as f:
+    for il in f:
+        # If line ends with \, accumulate it and handle full line
+        if re.search(r"\\\n$", il):
+            continued_line = True
+            line_accum += il[:-2]  # accumulate without backslash and newline
+            continue
+
+        if continued_line:
+            continued_line = False
+            l = line_accum + il
+            line_accum = ""
+        else:
+            l = il
+
+        if in_help_txt:
+            if l == "\n":
+                help_txt += l
+                continue
+            if first_line_help_txt:
+                help_txt_indentation = re.match(RE_indentation, l).group(0).expandtabs()
+                first_line_help_txt = False
+            # Consider any line with same or more indentation as part of help text
+            if (
+                help_txt_indentation
+                in re.match(RE_indentation, l).group(0).expandtabs()
+            ):
+                help_txt += l
+                continue
+            else:
+                in_help_txt = False
+                print(help_txt)
+                help_txt = ""
+        else:
+            l = re.sub(r"[*]", r"\*", l)  # Escape asterisks
+
+        if re.match(r"^[ \t]*#.*", l):
+            # Skip comments
+            continue
+
+        if re.match(r"^[ \t]*help", l):
+            in_help_txt = True
+            first_line_help_txt = True
+            print("* help::\n")
+            continue
+
+        m = re.match("^[ \t]*(menu)?config (?P<cfgname>[A-Za-z0-9_]+)", l)
+        if m:
+            section_name = f"\nCONFIG_{m.group('cfgname')}"
+            underline = f"\n{'='*CFG_LEN}\n"
+            if m.group("cfgname") in REPEATED_CONFIGS:
+                repeated_config_count[m.group("cfgname")] = (
+                    repeated_config_count.get(m.group("cfgname"), 0) + 1
+                )
+                if repeated_config_count[m.group("cfgname")] > 1:
+                    section_name += f"({repeated_config_count[m.group('cfgname')]})"
+                print(section_name + underline)
+            else:
+                print(f"\n.. _CONFIG_{m.group('cfgname')}:\n\n" + section_name + underline)
+            continue
+
+        m = re.match(
+            r"^[ \t]*(def_bool|def_tristate|depends on|select|range|visible if|imply|default|prompt|bool|tristate|string|hex|int|modules)( \"(.*)\")?(?P<expr> [^\"]*)?",
+            l,
+        )
+        if m:
+            expr = m.group('expr') if m.group('expr') else ''
+            not_expr = l
+            if expr:
+                expr = re.sub(r'[A-Z0-9_]{2,}', rf" CONFIG_\g<0> ", expr)
+                not_expr = l[:m.start('expr')]
+            print("* " + not_expr.lstrip() + expr.rstrip())
+            continue
+
+        m = re.match(r'^[ \t]*source "(.*)"', l)
+        if m:
+            # Format Kconfig file paths as Documentation/... so they can be turned
+            # into links by the automarkup plugin
+            print(f"\nsource {args.base_doc_path + m.group(1)}.rst\n")
+            continue
+
+        m = re.match(r"[^ \t]*choice|endchoice|comment|menu|endmenu|if|endif", l)
+        if m:
+            print("\n" + l.strip() + "\n")
+            continue
+
+        print(l.strip())
+
+if help_txt:
+    print(help_txt)  # Flush any pending help text