@@ -3,4 +3,4 @@
# Makefile for persistent kernel filesystem
#
-obj-$(CONFIG_PKERNFS_FS) += pkernfs.o inode.o allocator.o dir.o file.o
+obj-$(CONFIG_PKERNFS_FS) += pkernfs.o inode.o allocator.o dir.o file.o iommu.o
@@ -25,11 +25,18 @@ struct inode *pkernfs_inode_get(struct super_block *sb, unsigned long ino)
inode->i_sb = sb;
if (pkernfs_inode->flags & PKERNFS_INODE_FLAG_DIR) {
inode->i_op = &pkernfs_dir_inode_operations;
+ inode->i_fop = &pkernfs_dir_fops;
inode->i_mode = S_IFDIR;
- } else {
+ } else if (pkernfs_inode->flags & PKERNFS_INODE_FLAG_FILE) {
inode->i_op = &pkernfs_file_inode_operations;
- inode->i_mode = S_IFREG;
inode->i_fop = &pkernfs_file_fops;
+ inode->i_mode = S_IFREG;
+ } else if (pkernfs_inode->flags & PKERNFS_INODE_FLAG_IOMMU_DIR) {
+ inode->i_op = &pkernfs_iommu_dir_inode_operations;
+ inode->i_fop = &pkernfs_dir_fops;
+ inode->i_mode = S_IFDIR;
+ } else if (pkernfs_inode->flags | PKERNFS_INODE_FLAG_IOMMU_ROOT_PGTABLES) {
+ inode->i_mode = S_IFREG;
}
inode->i_atime = inode->i_mtime = current_time(inode);
@@ -41,7 +48,7 @@ struct inode *pkernfs_inode_get(struct super_block *sb, unsigned long ino)
return inode;
}
-static unsigned long pkernfs_allocate_inode(struct super_block *sb)
+unsigned long pkernfs_allocate_inode(struct super_block *sb)
{
unsigned long next_free_ino;
@@ -167,3 +174,7 @@ const struct inode_operations pkernfs_dir_inode_operations = {
.unlink = pkernfs_unlink,
};
+const struct inode_operations pkernfs_iommu_dir_inode_operations = {
+ .lookup = pkernfs_lookup,
+};
+
new file mode 100644
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "pkernfs.h"
+#include <linux/io.h>
+
+
+void pkernfs_alloc_iommu_root_pgtables(struct pkernfs_region *pkernfs_region)
+{
+ unsigned long *mappings_block_vaddr;
+ unsigned long inode_idx;
+ struct pkernfs_inode *iommu_pgtables, *iommu_dir = NULL;
+ int rc;
+
+ pkernfs_init();
+
+ /* Try find a 'iommu' directory */
+ inode_idx = pkernfs_get_persisted_inode(NULL, 1)->child_ino;
+ while (inode_idx) {
+ if (!strncmp(pkernfs_get_persisted_inode(NULL, inode_idx)->filename,
+ "iommu", PKERNFS_FILENAME_LEN)) {
+ iommu_dir = pkernfs_get_persisted_inode(NULL, inode_idx);
+ break;
+ }
+ inode_idx = pkernfs_get_persisted_inode(NULL, inode_idx)->sibling_ino;
+ }
+
+ if (!iommu_dir) {
+ unsigned long root_pgtables_ino = 0;
+ unsigned long iommu_dir_ino = pkernfs_allocate_inode(NULL);
+
+ iommu_dir = pkernfs_get_persisted_inode(NULL, iommu_dir_ino);
+ strscpy(iommu_dir->filename, "iommu", PKERNFS_FILENAME_LEN);
+ iommu_dir->flags = PKERNFS_INODE_FLAG_IOMMU_DIR;
+
+ /* Make this the head of the list. */
+ iommu_dir->sibling_ino = pkernfs_get_persisted_inode(NULL, 1)->child_ino;
+ pkernfs_get_persisted_inode(NULL, 1)->child_ino = iommu_dir_ino;
+
+ /* Add a child file for pgtables. */
+ root_pgtables_ino = pkernfs_allocate_inode(NULL);
+ iommu_pgtables = pkernfs_get_persisted_inode(NULL, root_pgtables_ino);
+ strscpy(iommu_pgtables->filename, "root-pgtables", PKERNFS_FILENAME_LEN);
+ iommu_pgtables->sibling_ino = iommu_dir->child_ino;
+ iommu_dir->child_ino = root_pgtables_ino;
+ iommu_pgtables->flags = PKERNFS_INODE_FLAG_IOMMU_ROOT_PGTABLES;
+ iommu_pgtables->mappings_block = pkernfs_alloc_block(NULL);
+ /* TODO: make alloc zero. */
+ memset(pkernfs_addr_for_block(NULL, iommu_pgtables->mappings_block), 0, (2 << 20));
+ } else {
+ inode_idx = iommu_dir->child_ino;
+ while (inode_idx) {
+ if (!strncmp(pkernfs_get_persisted_inode(NULL, inode_idx)->filename,
+ "root-pgtables", PKERNFS_FILENAME_LEN)) {
+ iommu_pgtables = pkernfs_get_persisted_inode(NULL, inode_idx);
+ break;
+ }
+ inode_idx = pkernfs_get_persisted_inode(NULL, inode_idx)->sibling_ino;
+ }
+ }
+
+ /*
+ * For a pkernfs region block, the "mappings_block" field is still
+ * just a block index, but that block doesn't actually contain mappings
+ * it contains the pkernfs_region data
+ */
+
+ mappings_block_vaddr = (unsigned long *)pkernfs_addr_for_block(NULL,
+ iommu_pgtables->mappings_block);
+ set_bit(0, mappings_block_vaddr);
+ pkernfs_region->vaddr = mappings_block_vaddr;
+ pkernfs_region->paddr = pkernfs_base + (iommu_pgtables->mappings_block * PMD_SIZE);
+ pkernfs_region->bytes = PMD_SIZE;
+
+ dev_set_name(&pkernfs_region->dev, "iommu_root_pgtables");
+ rc = device_register(&pkernfs_region->dev);
+ if (rc)
+ pr_err("device_register failed: %i\n", rc);
+
+ pkernfs_region->pgmap.range.start = pkernfs_base +
+ (iommu_pgtables->mappings_block * PMD_SIZE);
+ pkernfs_region->pgmap.range.end =
+ pkernfs_region->pgmap.range.start + PMD_SIZE - 1;
+ pkernfs_region->pgmap.nr_range = 1;
+ pkernfs_region->pgmap.type = MEMORY_DEVICE_GENERIC;
+ pkernfs_region->vaddr =
+ devm_memremap_pages(&pkernfs_region->dev, &pkernfs_region->pgmap);
+ pkernfs_region->paddr = pkernfs_base +
+ (iommu_pgtables->mappings_block * PMD_SIZE);
+}
+
+void *pkernfs_region_paddr_to_vaddr(struct pkernfs_region *region, unsigned long paddr)
+{
+ if (WARN_ON(paddr >= region->paddr + region->bytes))
+ return NULL;
+ if (WARN_ON(paddr < region->paddr))
+ return NULL;
+ return region->vaddr + (paddr - region->paddr);
+}
@@ -11,12 +11,14 @@ phys_addr_t pkernfs_base, pkernfs_size;
void *pkernfs_mem;
static const struct super_operations pkernfs_super_ops = { };
-static int pkernfs_fill_super(struct super_block *sb, struct fs_context *fc)
+void pkernfs_init(void)
{
- struct inode *inode;
- struct dentry *dentry;
+ static int inited;
struct pkernfs_sb *psb;
+ if (inited++)
+ return;
+
pkernfs_mem = memremap(pkernfs_base, pkernfs_size, MEMREMAP_WB);
psb = (struct pkernfs_sb *) pkernfs_mem;
@@ -24,13 +26,21 @@ static int pkernfs_fill_super(struct super_block *sb, struct fs_context *fc)
pr_info("pkernfs: Restoring from super block\n");
} else {
pr_info("pkernfs: Clean super block; initialising\n");
- pkernfs_initialise_inode_store(sb);
- pkernfs_zero_allocations(sb);
+ pkernfs_initialise_inode_store(NULL);
+ pkernfs_zero_allocations(NULL);
psb->magic_number = PKERNFS_MAGIC_NUMBER;
- pkernfs_get_persisted_inode(sb, 1)->flags = PKERNFS_INODE_FLAG_DIR;
- strscpy(pkernfs_get_persisted_inode(sb, 1)->filename, ".", PKERNFS_FILENAME_LEN);
+ pkernfs_get_persisted_inode(NULL, 1)->flags = PKERNFS_INODE_FLAG_DIR;
+ strscpy(pkernfs_get_persisted_inode(NULL, 1)->filename, ".", PKERNFS_FILENAME_LEN);
psb->next_free_ino = 2;
}
+}
+
+static int pkernfs_fill_super(struct super_block *sb, struct fs_context *fc)
+{
+ struct inode *inode;
+ struct dentry *dentry;
+
+ pkernfs_init();
sb->s_op = &pkernfs_super_ops;
@@ -77,12 +87,9 @@ static struct file_system_type pkernfs_fs_type = {
.fs_flags = FS_USERNS_MOUNT,
};
-static int __init pkernfs_init(void)
+static int __init pkernfs_fs_init(void)
{
- int ret;
-
- ret = register_filesystem(&pkernfs_fs_type);
- return ret;
+ return register_filesystem(&pkernfs_fs_type);
}
/**
@@ -97,7 +104,12 @@ static int __init parse_pkernfs_extents(char *p)
return 0;
}
+bool pkernfs_enabled(void)
+{
+ return !!pkernfs_base;
+}
+
early_param("pkernfs", parse_pkernfs_extents);
MODULE_ALIAS_FS("pkernfs");
-module_init(pkernfs_init);
+module_init(pkernfs_fs_init);
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <linux/fs.h>
+#include <linux/pkernfs.h>
#define PKERNFS_MAGIC_NUMBER 0x706b65726e6673
#define PKERNFS_FILENAME_LEN 255
@@ -18,6 +19,8 @@ struct pkernfs_sb {
// If neither of these are set the inode is not in use.
#define PKERNFS_INODE_FLAG_FILE (1 << 0)
#define PKERNFS_INODE_FLAG_DIR (1 << 1)
+#define PKERNFS_INODE_FLAG_IOMMU_DIR (1 << 2)
+#define PKERNFS_INODE_FLAG_IOMMU_ROOT_PGTABLES (1 << 3)
struct pkernfs_inode {
int flags;
/*
@@ -31,20 +34,24 @@ struct pkernfs_inode {
*/
unsigned long child_ino;
char filename[PKERNFS_FILENAME_LEN];
+ /* Block index for where the mappings live. */
int mappings_block;
int num_mappings;
};
void pkernfs_initialise_inode_store(struct super_block *sb);
+void pkernfs_init(void);
void pkernfs_zero_allocations(struct super_block *sb);
unsigned long pkernfs_alloc_block(struct super_block *sb);
struct inode *pkernfs_inode_get(struct super_block *sb, unsigned long ino);
void *pkernfs_addr_for_block(struct super_block *sb, int block_idx);
+unsigned long pkernfs_allocate_inode(struct super_block *sb);
struct pkernfs_inode *pkernfs_get_persisted_inode(struct super_block *sb, int ino);
extern const struct file_operations pkernfs_dir_fops;
extern const struct file_operations pkernfs_file_fops;
extern const struct inode_operations pkernfs_file_inode_operations;
+extern const struct inode_operations pkernfs_iommu_dir_inode_operations;
new file mode 100644
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: MIT */
+
+#ifndef _LINUX_PKERNFS_H
+#define _LINUX_PKERNFS_H
+
+#include <linux/memremap.h>
+#include <linux/device.h>
+
+#ifdef CONFIG_PKERNFS_FS
+extern bool pkernfs_enabled(void);
+#else
+static inline bool pkernfs_enabled(void)
+{
+ return false;
+}
+#endif
+
+/*
+ * This is a light wrapper around the data behind a pkernfs
+ * file. Really it should be a file but the filesystem comes
+ * up too late: IOMMU needs root pgtables before fs is up.
+ */
+struct pkernfs_region {
+ void *vaddr;
+ unsigned long paddr;
+ unsigned long bytes;
+ struct dev_pagemap pgmap;
+ struct device dev;
+};
+
+void pkernfs_alloc_iommu_root_pgtables(struct pkernfs_region *pkernfs_region);
+void pkernfs_alloc_page_from_region(struct pkernfs_region *pkernfs_region,
+ void **vaddr, unsigned long *paddr);
+void *pkernfs_region_paddr_to_vaddr(struct pkernfs_region *region, unsigned long paddr);
+
+#endif /* _LINUX_PKERNFS_H */