diff mbox series

[bug#37868,v8] system: Add kernel-module-packages to operating-system.

Message ID 20200227155029.2542-1-dannym@scratchpost.org
State Accepted
Headers show
Series [bug#37868,v8] system: Add kernel-module-packages to operating-system. | expand

Checks

Context Check Description
cbaines/comparison success View comparision
cbaines/git branch success View Git branch
cbaines/applying patch success View Laminar job

Commit Message

Danny Milosavljevic Feb. 27, 2020, 3:50 p.m. UTC
* gnu/system.scm (<operating-system>): Add kernel-module-packages.
(operating-system-directory-base-entries): Use it.
* doc/guix.texi (operating-system Reference): Document KERNEL-LOADABLE-MODULES.
* gnu/build/linux-modules.scm (depmod!): New procedure.
(make-linux-module-directory): New procedure.  Export it.
* guix/profiles.scm (linux-module-database): New procedure.  Export it.
(input-files): New procedure.
* gnu/tests/linux-modules.scm: New file.
* gnu/local.mk (GNU_SYSTEM_MODULES): Add it.
* gnu/packages/linux.scm (make-linux-libre*)[arguments]<#:phases>[install]:
Disable depmod.  Remove "build" and "source" symlinks.
---
 doc/guix.texi               |   3 ++
 gnu/build/linux-modules.scm |  41 ++++++++++++++-
 gnu/local.mk                |   1 +
 gnu/packages/linux.scm      |  19 ++++++-
 gnu/system.scm              |  20 +++++--
 gnu/tests/linux-modules.scm | 102 ++++++++++++++++++++++++++++++++++++
 guix/profiles.scm           |  48 ++++++++++++++++-
 7 files changed, 226 insertions(+), 8 deletions(-)
 create mode 100644 gnu/tests/linux-modules.scm

Comments

Danny Milosavljevic March 14, 2020, 6:40 p.m. UTC | #1
Hello,

I'd like to push the patch to master on tuesday (with some minimal changes
to the commit message).

The only part I'm still unsure about is:

                          ;; TODO: system, target.
                          (profile-derivation
                           (packages->manifest
                            (cons kernel modules))
                           #:hooks (list linux-module-database)
                           #:locales? #f
                           #:allow-collisions? #f
                           #:relative-symlinks? #t))

Will Guix do the derivation (especially the invocation of depmod) for the
intended system and target?

Apparently, module-init-tools are supposed to be cross-platform anyway and work
when invoking depmod for files of an other architecture than the architecture
depmod is invoked on (and was compiled for).  So maybe we can also just ignore
the entire system/target propagation in this case.

To test that, I tried

  ./pre-inst-env guix build -s armhf-linux -m etc/system-tests.scm

and that seems to hang while compiling the kernel (?).

I'm confident that that has no connection to the patch because it hangs earlier
(at "AR      drivers/net/built-in.a").

Then I tried

  ./pre-inst-env guix build --target=xxx -m etc/system-tests.scm

and that seems to ignore target entirely.

[1] http://lists.busybox.net/pipermail/uclibc/2005-May/032671.html
Mathieu Othacehe March 15, 2020, 10:28 a.m. UTC | #2
Hello Danny,

> Will Guix do the derivation (especially the invocation of depmod) for the
> intended system and target?

Yes, "profile-derivation" should use the current system or target if the
#:system and #:target arguments are #f.

> Apparently, module-init-tools are supposed to be cross-platform anyway and work
> when invoking depmod for files of an other architecture than the architecture
> depmod is invoked on (and was compiled for).  So maybe we can also just ignore
> the entire system/target propagation in this case.

In that case, you should use #+kmod instead of #$kmod. This way, when
cross-compiling, the native kmod would be used.

> Then I tried
>
>   ./pre-inst-env guix build --target=xxx -m etc/system-tests.scm
>
> and that seems to ignore target entirely.

I'm not sure this has ever been tested. Support of cross-compilation for
Guix System is still wip, even if since a few days, core-updates is in a
good shape.

Anyway, if you're willing to wait a few days, I can test your patch does
not break system cross-compilation on core-updates.

Regarding --system, producing disk-images is currently broken on all
branches[1], so it will be harder to test it for now.

Also, here are a few remarks about your patch.

+(define (depmod! kmod version output)
+  "Given an (existing) OUTPUT directory, invoke KMOD's depmod on it for
+kernel version VERSION."

"OUTPUT" is maybe not the best naming as you read multiple "input" files
from it. Maybe just "DIRECTORY"?

+  (let ((destination-directory (string-append output "/lib/modules/" version))
+        ;; Note: "System.map" is an input file.
+        (maps-file (string-append output "/System.map"))
+        ;; Note: "Module.symvers" is an input file.
+        (symvers-file (string-append output "/Module.symvers")))
+    (for-each (lambda (basename)
+                (when (and (string-prefix? "modules." basename)
+                           ;; Note: "modules.builtin" is an input file.
+                           (not (string=? "modules.builtin" basename))
+                           ;; Note: "modules.order" is an input file.
+                           (not (string=? "modules.order" basename)))
+                  (delete-file (string-append destination-directory "/"
+                                              basename))))

You can maybe add a comment explaining what's the point of this
operation.

+              (scandir destination-directory))
+    (invoke (string-append kmod "/bin/depmod")
+            "-e" ; Report symbols that aren't supplied
+            ;"-w" ; Warn on duplicates
+            "-b" output
+            "-F" maps-file
+            "-E" symvers-file

The man page of depmod says that '-F' and '-E' options are mutually
exclusive.

+               (let* ((versions (filter (lambda (name)
+                                          (not (string-prefix? "." name)))
+                                        (scandir moddir)))
+                      (version (match versions
+                                ((x) x))))

If versions only contains one element, then you can use find instead of
filtering and matching.

+                          ;; TODO: system, target.
+                          (profile-derivation
+                           (packages->manifest
+                            (cons kernel modules))
+                           #:hooks (list linux-module-database)
+                           #:locales? #f
+                           #:allow-collisions? #f
+                           #:relative-symlinks? #t))
+                         (initrd -> (operating-system-initrd-file os))
+                         (params    (operating-system-boot-parameters-file os)))

As stated above, I think you are fine removing the TODO.

+(define (input-files inputs path)
+  "Given a list of directories INPUTS, return all entries with PATH in it."
+  ;; TODO: Use filter-map.
+  #~(begin
+      (use-modules (srfi srfi-1))
+      (filter file-exists?
+        (map (lambda (x)
+               (string-append x #$path))
+             '#$inputs))))
+

This TODO can be resolved I think :)

+(define (linux-module-database manifest)
+  "Return a derivation that unions all the kernel modules in the manifest
+and creates the dependency graph for all these kernel modules."
+  (mlet %store-monad ((kmod (manifest-lookup-package manifest "kmod")))
+    (define build
+      (with-imported-modules (source-module-closure '((guix build utils) (gnu build linux-modules)))
+        #~(begin
+            (use-modules (ice-9 ftw))
+            (use-modules (ice-9 match))
+            (use-modules (srfi srfi-1)) ; append-map
+            (use-modules (guix build utils)) ; mkdir-p
+            (use-modules (gnu build linux-modules)) ; make-linux-module-directory
+            (let* ((inputs '#$(manifest-inputs manifest))
+                   (module-directories #$(input-files (manifest-inputs manifest) "/lib/modules"))
+                   (directory-entries
+                    (lambda (directory-name)
+                      (scandir directory-name (lambda (basename)
+                                                (not (string-prefix? "." basename))))))
+                   ;; Note: Should usually result in one entry.
+                   (versions (delete-duplicates
+                              (append-map directory-entries
+                                          module-directories))))

This part is over the column limit.

+                (match versions
+                 ((version)
+                  (make-linux-module-directory #$kmod inputs version #$output)))

If depmod output is system agnostic, then we should use
#+kmod. If that's not the case, this will be an issue as running #$kmod
won't work when cross-compiling.

Thanks,

Mathieu
Mathieu Othacehe March 15, 2020, 10:33 a.m. UTC | #3
> Regarding --system, producing disk-images is currently broken on all
> branches[1], so it will be harder to test it for now.

And the forgotten link!

[1]: https://lists.gnu.org/archive/html/guix-devel/2019-12/msg00099.html

Mathieu
Danny Milosavljevic March 15, 2020, 6:17 p.m. UTC | #4
Hi Mathieu,

On Sun, 15 Mar 2020 11:28:37 +0100
Mathieu Othacehe <m.othacehe@gmail.com> wrote:

> Yes, "profile-derivation" should use the current system or target if the
> #:system and #:target arguments are #f.

OK!

> In that case, you should use #+kmod instead of #$kmod. This way, when
> cross-compiling, the native kmod would be used.

> Anyway, if you're willing to wait a few days, I can test your patch does
> not break system cross-compilation on core-updates.

Sure.

> The man page of depmod says that '-F' and '-E' options are mutually
> exclusive.

Linus Torvalds seems to be in favor of not supporting Module.symvers anymore,
so let's use "-F"...

> 
> +               (let* ((versions (filter (lambda (name)
> +                                          (not (string-prefix? "." name)))
> +                                        (scandir moddir)))
> +                      (version (match versions
> +                                ((x) x))))
> 
> If versions only contains one element, then you can use find instead of
> filtering and matching.

I don't really know that it only contains one element.  In normal supported
operation it should--but if the user does something stupid (put kernel
version A and module version B into the operating-system, where A != B),
I want it to fail and not depmod half the things (neither all the things, for
that matter).

> As stated above, I think you are fine removing the TODO.

Cool!
Ludovic Courtès March 15, 2020, 9:02 p.m. UTC | #5
Hi,

Danny Milosavljevic <dannym@scratchpost.org> skribis:

> The only part I'm still unsure about is:
>
>                           ;; TODO: system, target.
>                           (profile-derivation
>                            (packages->manifest
>                             (cons kernel modules))
>                            #:hooks (list linux-module-database)
>                            #:locales? #f
>                            #:allow-collisions? #f
>                            #:relative-symlinks? #t))
>
> Will Guix do the derivation (especially the invocation of depmod) for the
> intended system and target?

I would just write a test OS definition, and then run:

  ./pre-inst-env guix system build test.scm -nd -s armhf-linux

From there, you can inspect the ‘linux-module-database’ derivation,
check its system type, and check the kmod referred to in its “-builder”
file (is it the file name of the armhf-linux kmod?).

Likewise for cross-compilation.

HTH!

Ludo’.
Mathieu Othacehe March 16, 2020, 9:55 a.m. UTC | #6
Hello Danny,

I tested you patch by cross-compiling a simple system for
aarch64-gnu-linux with:

--8<---------------cut here---------------start------------->8---
(kernel-loadable-modules (list acpi-call-linux-module))
--8<---------------cut here---------------end--------------->8---

However, I have the following error:

--8<---------------cut here---------------start------------->8---
guix system: error: gnu/packages/linux.scm:886:2: acpi-call-linux-module@3.17: build system `linux-module' does not support cross builds
--8<---------------cut here---------------end--------------->8---

This is not caused by your patch, but it prevents me from testing :(

Thanks,

Mathieu
Danny Milosavljevic March 16, 2020, 8:10 p.m. UTC | #7
Hi Mathieu,

On Mon, 16 Mar 2020 10:55:57 +0100
Mathieu Othacehe <m.othacehe@gmail.com> wrote:

> --8<---------------cut here---------------start------------->8---
> guix system: error: gnu/packages/linux.scm:886:2: acpi-call-linux-module@3.17: build system `linux-module' does not support cross builds
> --8<---------------cut here---------------end--------------->8---
> 
> This is not caused by your patch, but it prevents me from testing :(

That's too bad.

I tried to preserve cross-compilation in guix/build-system/linux-module.scm
but apparently I missed something.  Sorry!

It could just be the

  (not target)                               ;XXX: no cross-compilation

in guix/build-system/linux-module.scm - because the other file should support
it just fine.
Ludovic Courtès March 17, 2020, 9:29 a.m. UTC | #8
Danny Milosavljevic <dannym@scratchpost.org> skribis:

> On Mon, 16 Mar 2020 10:55:57 +0100
> Mathieu Othacehe <m.othacehe@gmail.com> wrote:
>
>> --8<---------------cut here---------------start------------->8---
>> guix system: error: gnu/packages/linux.scm:886:2: acpi-call-linux-module@3.17: build system `linux-module' does not support cross builds
>> --8<---------------cut here---------------end--------------->8---
>> 
>> This is not caused by your patch, but it prevents me from testing :(
>
> That's too bad.
>
> I tried to preserve cross-compilation in guix/build-system/linux-module.scm
> but apparently I missed something.  Sorry!
>
> It could just be the
>
>   (not target)                               ;XXX: no cross-compilation

Yes, it means that ‘linux-module-build-system’ does not return a bag
when cross-compiling.

See ‘gnu-build-system’ for how to support cross-compilation.

In the meantime, Mathieu, perhaps you can test system cross-compilation
by using a ‘computed-file’ (instead of a package) as a fake package
providing modules?

Thanks,
Ludo’.
Mathieu Othacehe March 18, 2020, 2:50 p.m. UTC | #9
Hello Danny,

> It could just be the
>
>   (not target)                               ;XXX: no cross-compilation
>
> in guix/build-system/linux-module.scm - because the other file should support
> it just fine.

Removing the (not target) is enough to make it build. However, when
running:

--8<---------------cut here---------------start------------->8---
 guix build --target=aarch64-linux-gnu acpi-call-linux-module
--8<---------------cut here---------------end--------------->8---

the produced module does not seem to be cross-compiled:

--8<---------------cut here---------------start------------->8---
mathieu@meru:~/guix$ file /gnu/store/fkk5cd746xxh1nx4qvi7arzhznf37yxw-acpi-call-linux-module-3.17/lib/modules/5.4.25-gnu/extra/acpi_call.ko 
/gnu/store/fkk5cd746xxh1nx4qvi7arzhznf37yxw-acpi-call-linux-module-3.17/lib/modules/5.4.25-gnu/extra/acpi_call.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=a1d9e0ec7b8ef5096a4e1b5c2e7ca6a8bd524cf9, not stripped
--8<---------------cut here---------------end--------------->8---

Thanks,

Mathieu
Danny Milosavljevic March 18, 2020, 4:06 p.m. UTC | #10
Whoops.

weird.  guix/build/linux-module-build-system.scm sets CROSS_COMPILE up so it
should have worked.

Did it print the message

    (format #t "`CROSS_COMPILE' set to `~a'~%"
               (getenv "CROSS_COMPILE")))

?
Danny Milosavljevic March 18, 2020, 5 p.m. UTC | #11
Ohhhh, try adding (target target) to the bag in guix/build-system/linux-module.scm
Ludovic Courtès March 18, 2020, 5:35 p.m. UTC | #12
Hey!

Mathieu Othacehe <m.othacehe@gmail.com> skribis:

>> It could just be the
>>
>>   (not target)                               ;XXX: no cross-compilation
>>
>> in guix/build-system/linux-module.scm - because the other file should support
>> it just fine.
>
> Removing the (not target) is enough to make it build.

But then it’s a native build.  :-)

Ludo’.
Danny Milosavljevic March 20, 2020, 10:19 a.m. UTC | #13
Hi Mathieu,

what happen if there are no kernel-module-packages and one is cross-compiling?

Then (native) depmod should still be invoked on linux-libre's modules.

I think that that case is the most important to test in order to avoid
regressions.
Mathieu Othacehe March 20, 2020, 10:32 a.m. UTC | #14
Hello Danny,

> Then (native) depmod should still be invoked on linux-libre's modules.
>
> I think that that case is the most important to test in order to avoid
> regressions.

You are right and it that case, everything seems to work fine! It would
be nice to fix linux-module-build-system cross-compilation, but I think
that it can be done later.

Mathieu
diff mbox series

Patch

diff --git a/doc/guix.texi b/doc/guix.texi
index a66bb3d646..01e2d1ab57 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -11197,6 +11197,9 @@  The package object of the operating system kernel to use@footnote{Currently
 only the Linux-libre kernel is supported.  In the future, it will be
 possible to use the GNU@tie{}Hurd.}.
 
+@item @code{kernel-loadable-modules} (default: '())
+A list of objects (usually packages) to collect loadable kernel modules from.
+
 @item @code{kernel-arguments} (default: @code{'("quiet")})
 List of strings or gexps representing additional arguments to pass on
 the command-line of the kernel---e.g., @code{("console=ttyS0")}.
diff --git a/gnu/build/linux-modules.scm b/gnu/build/linux-modules.scm
index a149eff329..0b11c52103 100644
--- a/gnu/build/linux-modules.scm
+++ b/gnu/build/linux-modules.scm
@@ -22,12 +22,14 @@ 
   #:use-module (guix elf)
   #:use-module (guix glob)
   #:use-module (guix build syscalls)
-  #:use-module ((guix build utils) #:select (find-files))
+  #:use-module ((guix build utils) #:select (find-files invoke))
+  #:use-module (guix build union)
   #:use-module (rnrs io ports)
   #:use-module (rnrs bytevectors)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-11)
   #:use-module (srfi srfi-26)
+  #:use-module (ice-9 ftw)
   #:use-module (ice-9 vlist)
   #:use-module (ice-9 match)
   #:use-module (ice-9 rdelim)
@@ -56,7 +58,9 @@ 
 
             write-module-name-database
             write-module-alias-database
-            write-module-device-database))
+            write-module-device-database
+
+            make-linux-module-directory))
 
 ;;; Commentary:
 ;;;
@@ -631,4 +635,37 @@  be loaded on-demand, such as file system modules."
                            module devname type major minor)))
                 aliases))))
 
+(define (depmod! kmod version output)
+  "Given an (existing) OUTPUT directory, invoke KMOD's depmod on it for
+kernel version VERSION."
+  (let ((destination-directory (string-append output "/lib/modules/" version))
+        ;; Note: "System.map" is an input file.
+        (maps-file (string-append output "/System.map"))
+        ;; Note: "Module.symvers" is an input file.
+        (symvers-file (string-append output "/Module.symvers")))
+    (for-each (lambda (basename)
+                (when (and (string-prefix? "modules." basename)
+                           ;; Note: "modules.builtin" is an input file.
+                           (not (string=? "modules.builtin" basename))
+                           ;; Note: "modules.order" is an input file.
+                           (not (string=? "modules.order" basename)))
+                  (delete-file (string-append destination-directory "/"
+                                              basename))))
+              (scandir destination-directory))
+    (invoke (string-append kmod "/bin/depmod")
+            "-e" ; Report symbols that aren't supplied
+            ;"-w" ; Warn on duplicates
+            "-b" output
+            "-F" maps-file
+            "-E" symvers-file
+            version)))
+
+(define (make-linux-module-directory kmod inputs version output)
+  "Create a new directory OUTPUT and ensure that the directory
+OUTPUT/lib/modules/VERSION can be used as a source of Linux
+kernel modules for KMOD to eventually load.  Take modules to
+put into OUTPUT from INPUTS."
+  (union-build output inputs #:create-all-directories? #t)
+  (depmod! kmod version output))
+
 ;;; linux-modules.scm ends here
diff --git a/gnu/local.mk b/gnu/local.mk
index 857345cfad..b25c3ceea5 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -631,6 +631,7 @@  GNU_SYSTEM_MODULES =				\
   %D%/tests/nfs.scm				\
   %D%/tests/install.scm				\
   %D%/tests/ldap.scm				\
+  %D%/tests/linux-modules.scm			\
   %D%/tests/mail.scm				\
   %D%/tests/messaging.scm			\
   %D%/tests/networking.scm			\
diff --git a/gnu/packages/linux.scm b/gnu/packages/linux.scm
index 78182555c1..32b802bab4 100644
--- a/gnu/packages/linux.scm
+++ b/gnu/packages/linux.scm
@@ -675,6 +675,7 @@  for ARCH and optionally VARIANT, or #f if there is no such configuration."
                   (guix build utils)
                   (srfi srfi-1)
                   (srfi srfi-26)
+                  (ice-9 ftw)
                   (ice-9 match))
        #:phases
        (modify-phases %standard-phases
@@ -760,12 +761,26 @@  for ARCH and optionally VARIANT, or #f if there is no such configuration."
                ;; Install kernel modules
                (mkdir-p moddir)
                (invoke "make"
-                       (string-append "DEPMOD=" kmod "/bin/depmod")
+                       ;; Disable depmod because the Guix system's module directory
+                       ;; is an union of potentially multiple packages.  It is not
+                       ;; possible to use depmod to usefully calculate a dependency
+                       ;; graph while building only one of those packages.
+                       "DEPMOD=true"
                        (string-append "MODULE_DIR=" moddir)
                        (string-append "INSTALL_PATH=" out)
                        (string-append "INSTALL_MOD_PATH=" out)
                        "INSTALL_MOD_STRIP=1"
-                       "modules_install")))))
+                       "modules_install")
+               (let* ((versions (filter (lambda (name)
+                                          (not (string-prefix? "." name)))
+                                        (scandir moddir)))
+                      (version (match versions
+                                ((x) x))))
+                 (false-if-file-not-found
+                  (delete-file (string-append moddir "/" version "/build")))
+                 (false-if-file-not-found
+                  (delete-file (string-append moddir "/" version "/source"))))
+               #t))))
        #:tests? #f))
     (home-page "https://www.gnu.org/software/linux-libre/")
     (synopsis "100% free redistribution of a cleaned Linux kernel")
diff --git a/gnu/system.scm b/gnu/system.scm
index 01baa248a2..17b6e667d5 100644
--- a/gnu/system.scm
+++ b/gnu/system.scm
@@ -5,6 +5,7 @@ 
 ;;; Copyright © 2016 Chris Marusich <cmmarusich@gmail.com>
 ;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
 ;;; Copyright © 2019 Meiyo Peng <meiyo.peng@gmail.com>
+;;; Copyright © 2020 Danny Milosavljevic <dannym@scratchpost.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -164,6 +165,8 @@ 
 
   (kernel operating-system-kernel                 ; package
           (default linux-libre))
+  (kernel-loadable-modules operating-system-kernel-loadable-modules
+                    (default '()))                ; list of packages
   (kernel-arguments operating-system-user-kernel-arguments
                     (default '("quiet")))         ; list of gexps/strings
   (bootloader operating-system-bootloader)        ; <bootloader-configuration>
@@ -468,9 +471,20 @@  OS."
   "Return the basic entries of the 'system' directory of OS for use as the
 value of the SYSTEM-SERVICE-TYPE service."
   (let ((locale (operating-system-locale-directory os)))
-    (mlet %store-monad ((kernel -> (operating-system-kernel os))
-                        (initrd -> (operating-system-initrd-file os))
-                        (params    (operating-system-boot-parameters-file os)))
+    (mlet* %store-monad ((kernel -> (operating-system-kernel os))
+                         (modules ->
+                          (operating-system-kernel-loadable-modules os))
+                         (kernel
+                          ;; TODO: system, target.
+                          (profile-derivation
+                           (packages->manifest
+                            (cons kernel modules))
+                           #:hooks (list linux-module-database)
+                           #:locales? #f
+                           #:allow-collisions? #f
+                           #:relative-symlinks? #t))
+                         (initrd -> (operating-system-initrd-file os))
+                         (params    (operating-system-boot-parameters-file os)))
       (return `(("kernel" ,kernel)
                 ("parameters" ,params)
                 ("initrd" ,initrd)
diff --git a/gnu/tests/linux-modules.scm b/gnu/tests/linux-modules.scm
new file mode 100644
index 0000000000..82b9627639
--- /dev/null
+++ b/gnu/tests/linux-modules.scm
@@ -0,0 +1,102 @@ 
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2019 Jakob L. Kreuze <zerodaysfordays@sdf.org>
+;;; Copyright © 2020 Danny Milosavljevic <dannym@scratchpost.org>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (gnu tests linux-modules)
+  #:use-module (gnu packages linux)
+  #:use-module (gnu system)
+  #:use-module (gnu system vm)
+  #:use-module (gnu tests)
+  #:use-module (guix derivations)
+  #:use-module (guix gexp)
+  #:use-module (guix modules)
+  #:use-module (guix monads)
+  #:use-module (guix store)
+  #:export (%test-loadable-kernel-modules-0
+            %test-loadable-kernel-modules-1
+            %test-loadable-kernel-modules-2))
+
+;;; Commentary:
+;;;
+;;; Test <operating-system> kernel-loadable-modules.
+;;;
+;;; Code:
+
+(define* (module-loader-program os modules)
+  "Return an executable store item that, upon being evaluated, will dry-run
+load MODULES."
+  (program-file
+   "load-kernel-modules.scm"
+   (with-imported-modules (source-module-closure '((guix build utils)))
+     #~(begin
+         (use-modules (guix build utils))
+         (for-each (lambda (module)
+                     (invoke (string-append #$kmod "/bin/modprobe") "-n" "--" module))
+                   '#$modules)))))
+
+(define* (run-loadable-kernel-modules-test module-packages module-names)
+  "Run a test of an OS having MODULE-PACKAGES, and modprobe MODULE-NAMES."
+  (define os
+    (marionette-operating-system
+     (operating-system
+      (inherit (simple-operating-system))
+      (kernel-loadable-modules module-packages))
+     #:imported-modules '((guix combinators))))
+  (define vm (virtual-machine os))
+  (define (test script)
+    (with-imported-modules '((gnu build marionette))
+      #~(begin
+          (use-modules (gnu build marionette)
+                       (srfi srfi-64))
+          (define marionette
+            (make-marionette (list #$vm)))
+          (mkdir #$output)
+          (chdir #$output)
+          (test-begin "loadable-kernel-modules")
+          (test-assert "script successfully evaluated"
+            (marionette-eval
+             '(primitive-load #$script)
+             marionette))
+          (test-end)
+          (exit (= (test-runner-fail-count (test-runner-current)) 0)))))
+  (gexp->derivation "loadable-kernel-modules" (test (module-loader-program os module-names))))
+
+(define %test-loadable-kernel-modules-0
+  (system-test
+   (name "loadable-kernel-modules-0")
+   (description "Tests loadable kernel modules facility of <operating-system>
+with no extra modules.")
+   (value (run-loadable-kernel-modules-test '() '()))))
+
+(define %test-loadable-kernel-modules-1
+  (system-test
+   (name "loadable-kernel-modules-1")
+   (description "Tests loadable kernel modules facility of <operating-system>
+with one extra module.")
+   (value (run-loadable-kernel-modules-test
+           (list ddcci-driver-linux)
+           '("ddcci")))))
+
+(define %test-loadable-kernel-modules-2
+  (system-test
+   (name "loadable-kernel-modules-2")
+   (description "Tests loadable kernel modules facility of <operating-system>
+with two extra modules.")
+   (value (run-loadable-kernel-modules-test
+           (list acpi-call-linux-module ddcci-driver-linux)
+           '("acpi_call" "ddcci")))))
diff --git a/guix/profiles.scm b/guix/profiles.scm
index 0d38b2513f..e39067db04 100644
--- a/guix/profiles.scm
+++ b/guix/profiles.scm
@@ -10,6 +10,7 @@ 
 ;;; Copyright © 2017 Maxim Cournoyer <maxim.cournoyer@gmail.com>
 ;;; Copyright © 2019 Kyle Meyer <kyle@kyleam.com>
 ;;; Copyright © 2019 Mathieu Othacehe <m.othacehe@gmail.com>
+;;; Copyright © 2020 Danny Milosavljevic <dannym@scratchpost.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -139,7 +140,9 @@ 
             %current-profile
             ensure-profile-directory
             canonicalize-profile
-            user-friendly-profile))
+            user-friendly-profile
+
+            linux-module-database))
 
 ;;; Commentary:
 ;;;
@@ -1137,6 +1140,49 @@  for both major versions of GTK+."
                               (hook . gtk-im-modules)))
           (return #f)))))
 
+(define (input-files inputs path)
+  "Given a list of directories INPUTS, return all entries with PATH in it."
+  ;; TODO: Use filter-map.
+  #~(begin
+      (use-modules (srfi srfi-1))
+      (filter file-exists?
+        (map (lambda (x)
+               (string-append x #$path))
+             '#$inputs))))
+
+(define (linux-module-database manifest)
+  "Return a derivation that unions all the kernel modules in the manifest
+and creates the dependency graph for all these kernel modules."
+  (mlet %store-monad ((kmod (manifest-lookup-package manifest "kmod")))
+    (define build
+      (with-imported-modules (source-module-closure '((guix build utils) (gnu build linux-modules)))
+        #~(begin
+            (use-modules (ice-9 ftw))
+            (use-modules (ice-9 match))
+            (use-modules (srfi srfi-1)) ; append-map
+            (use-modules (guix build utils)) ; mkdir-p
+            (use-modules (gnu build linux-modules)) ; make-linux-module-directory
+            (let* ((inputs '#$(manifest-inputs manifest))
+                   (module-directories #$(input-files (manifest-inputs manifest) "/lib/modules"))
+                   (directory-entries
+                    (lambda (directory-name)
+                      (scandir directory-name (lambda (basename)
+                                                (not (string-prefix? "." basename))))))
+                   ;; Note: Should usually result in one entry.
+                   (versions (delete-duplicates
+                              (append-map directory-entries
+                                          module-directories))))
+                (match versions
+                 ((version)
+                  (make-linux-module-directory #$kmod inputs version #$output)))
+                (exit #t)))))
+    (gexp->derivation "linux-module-database" build
+                      #:local-build? #t
+                      #:substitutable? #f
+                      #:properties
+                      `((type . profile-hook)
+                        (hook . linux-module-database)))))
+
 (define (xdg-desktop-database manifest)
   "Return a derivation that builds the @file{mimeinfo.cache} database from
 desktop files.  It's used to query what applications can handle a given