aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format3
-rw-r--r--.gitignore9
-rw-r--r--kernel/dtm/Makefile15
-rw-r--r--kernel/dtm/README.md29
-rw-r--r--kernel/dtm/dtm.c114
5 files changed, 170 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
index d21d53a..da4056d 100644
--- a/.clang-format
+++ b/.clang-format
@@ -55,6 +55,9 @@ DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
+ForEachMacros:
+ - 'for_each_possible_cpu'
+
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '.*'
diff --git a/.gitignore b/.gitignore
index 9c1804a..f7c3406 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,9 @@
+# Generic stuff.
*.o
+*.ko
+*.mod
+*.mod.c
+*.cmd
# arch/riscv
arch/riscv/usr/basics
@@ -6,3 +11,7 @@ arch/riscv/machine/machine
arch/riscv/kernel/hello
arch/riscv/init/init
arch/riscv/init/little_shell
+
+# kernel/dtm
+kernel/dtm/modules.order
+kernel/dtm/Module.symvers
diff --git a/kernel/dtm/Makefile b/kernel/dtm/Makefile
new file mode 100644
index 0000000..5233661
--- /dev/null
+++ b/kernel/dtm/Makefile
@@ -0,0 +1,15 @@
+obj-m += dtm.o
+
+# Build either from a local kernel or from the running system.
+KERNEL ?=
+ifeq ($(strip $(KERNEL)),)
+ DIR=/lib/modules/$$(uname -r)/build
+else
+ DIR="$(KERNEL)"
+endif
+
+all:
+ make -C $(DIR) M=$(PWD) modules
+
+clean:
+ make -C $(DIR) M=$(PWD) clean
diff --git a/kernel/dtm/README.md b/kernel/dtm/README.md
new file mode 100644
index 0000000..593c1d2
--- /dev/null
+++ b/kernel/dtm/README.md
@@ -0,0 +1,29 @@
+# Device Tree Module
+
+This is a simple kernel module which show cases some of the kernel API functions
+for manipulating device tree nodes.
+
+In order to build this module you need either a local build of the Linux kernel,
+or the the source that you can get from your Linux distribution (e.g.
+`kernel-source` on openSUSE). For the former you will need to specify the path
+to your local development tree with the `KERNEL` variable. So:
+
+```
+$ make KERNEL=<path/to/development/tree>
+```
+
+This will produce a `dtm.ko` file, which you will need to `insmod` on your
+target machine. Then you will get this output from `dmesg`:
+
+```
+[ 571.298548] [ T813] dtm: loading out-of-tree module taints kernel.
+[ 571.305842] [ T813] Inside of VisionFive2
+[ 571.309945] [ T813] RISC-V ISA for 'cpu@1': rv64imafdc_zba_zbb
+[ 571.315826] [ T813] parent: cpus
+[ 571.319091] [ T813] RISC-V ISA for 'cpu@2': rv64imafdc_zba_zbb
+[ 571.324962] [ T813] parent: cpus
+[ 571.328224] [ T813] RISC-V ISA for 'cpu@3': rv64imafdc_zba_zbb
+[ 571.334092] [ T813] parent: cpus
+[ 571.337354] [ T813] RISC-V ISA for 'cpu@4': rv64imafdc_zba_zbb
+[ 571.343228] [ T813] parent: cpus
+```
diff --git a/kernel/dtm/dtm.c b/kernel/dtm/dtm.c
new file mode 100644
index 0000000..adda89a
--- /dev/null
+++ b/kernel/dtm/dtm.c
@@ -0,0 +1,114 @@
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+MODULE_DESCRIPTION("Kernel module to show off the DeviceTree Kernel API");
+MODULE_AUTHOR("Miquel Sabaté Solà");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0.0.1");
+
+// `compatible` value for QEMU/KVM.
+static const char *const qemu[] = {
+ "riscv-virtio",
+ NULL,
+};
+
+// `compatible` values that will match on Starfive VisionFive2 v1.3b (plus a
+// boogie value).
+static const char *const vf2[] = {
+ "starfive,visionfive-2-v1.3b",
+ "starfive,jh7110",
+ "this is bullshit but it won't interfere",
+ NULL,
+};
+
+static int __init dtm_init(void)
+{
+ unsigned int cpu;
+ struct device_node *node, *parent;
+ const char *isa;
+ int rc;
+
+ /*
+ * `of_compatible_match` returns true if there is _one_ element of the given
+ * array which matches a string on the `compatible` file. That is, not all
+ * of them have to match.
+ */
+ if (of_machine_compatible_match(qemu)) {
+ pr_info("Inside of QEMU\n");
+ } else if (of_machine_compatible_match(vf2)) {
+ pr_info("Inside of VisionFive2\n");
+ } else {
+ pr_info("Somewhere over the rainbow\n");
+ }
+
+ /*
+ * The `for_each_possible_cpu` is a function-like macro provided by the
+ * Kernel which allows `cpu` to be an iterator over CPU device nodes.
+ */
+ for_each_possible_cpu(cpu) {
+ /* isa = NULL; */
+
+ /*
+ * Given a CPU identifier, `of_cpu_device_node_get` returns a `struct
+ * device_node` related to the specified CPU (or NULL on error).
+ */
+ node = of_cpu_device_node_get(cpu);
+ if (!node) {
+ pr_warn("Unable to find cpu node\n");
+ continue;
+ }
+
+ /*
+ * `of_property_present` returns a boolean on whether the given node
+ * property is present for the given device node.
+ */
+ if (!of_property_present(node, "riscv,isa")) {
+ pr_warn("Unable to find 'riscv,isa' for this node\n");
+ continue;
+ }
+
+ /*
+ * `of_property_read_string` initializes the given `isa` string with the
+ * contents of the property we are trying to fetch. Returns 0 on
+ * success, and a kernel error otherwise. Note that the previous
+ * `of_property_present` call was actually not needed, we would've get
+ * here an `-EINVAL` error eitherway.
+ */
+ rc = of_property_read_string(node, "riscv,isa", &isa);
+ if (rc) {
+ pr_warn("Unable to find \"riscv,isa\" devicetree entry\n");
+ continue;
+ }
+ pr_info("RISC-V ISA for '%s': %s\n", node->full_name, isa);
+
+ /*
+ * `of_get_parent` returns the parent device node for the one provided
+ * or NULL if there's none.
+ */
+ parent = of_get_parent(node);
+ if (!node) {
+ pr_warn("No parent for the given node!\n");
+ continue;
+ }
+ pr_info("parent: %s\n", node->full_name);
+
+ /*
+ * `of_node_put` is used to decrement the reference counter on device
+ * nodes. We are done using these instances, so we should decrement
+ * their life times.
+ */
+ of_node_put(node);
+ of_node_put(parent);
+ }
+ return 0;
+}
+
+static void __exit dtm_exit(void)
+{
+ pr_info("Exiting from DTM module\n");
+}
+
+module_init(dtm_init);
+module_exit(dtm_exit);