* [PATCH 1/7] fuse: Export fuse_simple_request
2020-11-26 23:32 [PATCH 0/7] [RFC] MUSE: Userspace backed MTD v2 Richard Weinberger
@ 2020-11-26 23:32 ` Richard Weinberger
2020-11-26 23:32 ` [PATCH 2/7] fuse: Export IO helpers Richard Weinberger
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Richard Weinberger @ 2020-11-26 23:32 UTC (permalink / raw)
To: miklos
Cc: miquel.raynal, vigneshr, linux-fsdevel, linux-kernel, linux-mtd,
Richard Weinberger
MUSE will use this function to issue requests,
so export it.
Signed-off-by: Richard Weinberger <richard@nod.at>
---
fs/fuse/dev.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 588f8d1240aa..8b7209537683 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -522,6 +522,7 @@ ssize_t fuse_simple_request(struct fuse_mount *fm, struct fuse_args *args)
return ret;
}
+EXPORT_SYMBOL_GPL(fuse_simple_request);
static bool fuse_request_queue_background(struct fuse_req *req)
{
--
2.26.2
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH 2/7] fuse: Export IO helpers
2020-11-26 23:32 [PATCH 0/7] [RFC] MUSE: Userspace backed MTD v2 Richard Weinberger
2020-11-26 23:32 ` [PATCH 1/7] fuse: Export fuse_simple_request Richard Weinberger
@ 2020-11-26 23:32 ` Richard Weinberger
2020-11-26 23:32 ` [PATCH 3/7] fuse: Make cuse_parse_one a common helper Richard Weinberger
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Richard Weinberger @ 2020-11-26 23:32 UTC (permalink / raw)
To: miklos
Cc: miquel.raynal, vigneshr, linux-fsdevel, linux-kernel, linux-mtd,
Richard Weinberger
MUSE will use this functions in its IO path,
so export them.
Signed-off-by: Richard Weinberger <richard@nod.at>
---
fs/fuse/file.c | 16 +++-------------
fs/fuse/fuse_i.h | 16 ++++++++++++++++
2 files changed, 19 insertions(+), 13 deletions(-)
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index c03034e8c152..ed91ca8b1203 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -20,8 +20,8 @@
#include <linux/uio.h>
#include <linux/fs.h>
-static struct page **fuse_pages_alloc(unsigned int npages, gfp_t flags,
- struct fuse_page_desc **desc)
+struct page **fuse_pages_alloc(unsigned int npages, gfp_t flags,
+ struct fuse_page_desc **desc)
{
struct page **pages;
@@ -31,6 +31,7 @@ static struct page **fuse_pages_alloc(unsigned int npages, gfp_t flags,
return pages;
}
+EXPORT_SYMBOL_GPL(fuse_pages_alloc);
static int fuse_send_open(struct fuse_mount *fm, u64 nodeid, struct file *file,
int opcode, struct fuse_open_out *outargp)
@@ -1338,17 +1339,6 @@ static inline void fuse_page_descs_length_init(struct fuse_page_desc *descs,
descs[i].length = PAGE_SIZE - descs[i].offset;
}
-static inline unsigned long fuse_get_user_addr(const struct iov_iter *ii)
-{
- return (unsigned long)ii->iov->iov_base + ii->iov_offset;
-}
-
-static inline size_t fuse_get_frag_size(const struct iov_iter *ii,
- size_t max_size)
-{
- return min(iov_iter_single_seg_count(ii), max_size);
-}
-
static int fuse_get_user_pages(struct fuse_args_pages *ap, struct iov_iter *ii,
size_t *nbytesp, int write,
unsigned int max_pages)
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index d51598017d13..d23954908610 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -31,6 +31,7 @@
#include <linux/pid_namespace.h>
#include <linux/refcount.h>
#include <linux/user_namespace.h>
+#include <linux/uio.h>
/** Default max number of pages that can be used in a single read request */
#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
@@ -858,6 +859,17 @@ static inline u64 fuse_get_attr_version(struct fuse_conn *fc)
return atomic64_read(&fc->attr_version);
}
+static inline unsigned long fuse_get_user_addr(const struct iov_iter *ii)
+{
+ return (unsigned long)ii->iov->iov_base + ii->iov_offset;
+}
+
+static inline size_t fuse_get_frag_size(const struct iov_iter *ii,
+ size_t max_size)
+{
+ return min(iov_iter_single_seg_count(ii), max_size);
+}
+
/** Device operations */
extern const struct file_operations fuse_dev_operations;
@@ -1210,4 +1222,8 @@ void fuse_dax_inode_cleanup(struct inode *inode);
bool fuse_dax_check_alignment(struct fuse_conn *fc, unsigned int map_alignment);
void fuse_dax_cancel_work(struct fuse_conn *fc);
+/* file.c */
+struct page **fuse_pages_alloc(unsigned int npages, gfp_t flags,
+ struct fuse_page_desc **desc);
+
#endif /* _FS_FUSE_I_H */
--
2.26.2
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH 3/7] fuse: Make cuse_parse_one a common helper
2020-11-26 23:32 [PATCH 0/7] [RFC] MUSE: Userspace backed MTD v2 Richard Weinberger
2020-11-26 23:32 ` [PATCH 1/7] fuse: Export fuse_simple_request Richard Weinberger
2020-11-26 23:32 ` [PATCH 2/7] fuse: Export IO helpers Richard Weinberger
@ 2020-11-26 23:32 ` Richard Weinberger
2020-11-26 23:32 ` [PATCH 4/7] mtd: Add MTD_MUSE flag Richard Weinberger
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Richard Weinberger @ 2020-11-26 23:32 UTC (permalink / raw)
To: miklos
Cc: miquel.raynal, vigneshr, linux-fsdevel, linux-kernel, linux-mtd,
Richard Weinberger
This function will be used by MUSE too, let's share it.
Signed-off-by: Richard Weinberger <richard@nod.at>
---
fs/fuse/Kconfig | 4 +++
fs/fuse/Makefile | 1 +
fs/fuse/cuse.c | 58 +--------------------------------------
fs/fuse/fuse_i.h | 2 ++
fs/fuse/helper.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 78 insertions(+), 57 deletions(-)
create mode 100644 fs/fuse/helper.c
diff --git a/fs/fuse/Kconfig b/fs/fuse/Kconfig
index 40ce9a1c12e5..9c8cc1e7b3a5 100644
--- a/fs/fuse/Kconfig
+++ b/fs/fuse/Kconfig
@@ -18,9 +18,13 @@ config FUSE_FS
If you want to develop a userspace FS, or if you want to use
a filesystem based on FUSE, answer Y or M.
+config FUSE_HELPER
+ def_bool n
+
config CUSE
tristate "Character device in Userspace support"
depends on FUSE_FS
+ select FUSE_HELPER
help
This FUSE extension allows character devices to be
implemented in userspace.
diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
index 8c7021fb2cd4..7a5768cce6be 100644
--- a/fs/fuse/Makefile
+++ b/fs/fuse/Makefile
@@ -9,5 +9,6 @@ obj-$(CONFIG_VIRTIO_FS) += virtiofs.o
fuse-y := dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o
fuse-$(CONFIG_FUSE_DAX) += dax.o
+fuse-$(CONFIG_FUSE_HELPER) += helper.o
virtiofs-y := virtio_fs.o
diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c
index 45082269e698..fe8515844064 100644
--- a/fs/fuse/cuse.c
+++ b/fs/fuse/cuse.c
@@ -199,62 +199,6 @@ struct cuse_devinfo {
const char *name;
};
-/**
- * cuse_parse_one - parse one key=value pair
- * @pp: i/o parameter for the current position
- * @end: points to one past the end of the packed string
- * @keyp: out parameter for key
- * @valp: out parameter for value
- *
- * *@pp points to packed strings - "key0=val0\0key1=val1\0" which ends
- * at @end - 1. This function parses one pair and set *@keyp to the
- * start of the key and *@valp to the start of the value. Note that
- * the original string is modified such that the key string is
- * terminated with '\0'. *@pp is updated to point to the next string.
- *
- * RETURNS:
- * 1 on successful parse, 0 on EOF, -errno on failure.
- */
-static int cuse_parse_one(char **pp, char *end, char **keyp, char **valp)
-{
- char *p = *pp;
- char *key, *val;
-
- while (p < end && *p == '\0')
- p++;
- if (p == end)
- return 0;
-
- if (end[-1] != '\0') {
- pr_err("info not properly terminated\n");
- return -EINVAL;
- }
-
- key = val = p;
- p += strlen(p);
-
- if (valp) {
- strsep(&val, "=");
- if (!val)
- val = key + strlen(key);
- key = strstrip(key);
- val = strstrip(val);
- } else
- key = strstrip(key);
-
- if (!strlen(key)) {
- pr_err("zero length info key specified\n");
- return -EINVAL;
- }
-
- *pp = p;
- *keyp = key;
- if (valp)
- *valp = val;
-
- return 1;
-}
-
/**
* cuse_parse_dev_info - parse device info
* @p: device info string
@@ -275,7 +219,7 @@ static int cuse_parse_devinfo(char *p, size_t len, struct cuse_devinfo *devinfo)
int rc;
while (true) {
- rc = cuse_parse_one(&p, end, &key, &val);
+ rc = fuse_kv_parse_one(&p, end, &key, &val);
if (rc < 0)
return rc;
if (!rc)
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index d23954908610..8eba93cd8fb8 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -1225,5 +1225,7 @@ void fuse_dax_cancel_work(struct fuse_conn *fc);
/* file.c */
struct page **fuse_pages_alloc(unsigned int npages, gfp_t flags,
struct fuse_page_desc **desc);
+/* helper.c */
+int fuse_kv_parse_one(char **pp, char *end, char **keyp, char **valp);
#endif /* _FS_FUSE_I_H */
diff --git a/fs/fuse/helper.c b/fs/fuse/helper.c
new file mode 100644
index 000000000000..0c828daf8e8a
--- /dev/null
+++ b/fs/fuse/helper.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Helper functions used by CUSE and MUSE
+ *
+ * Copyright (C) 2008-2009 SUSE Linux Products GmbH
+ * Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+ *
+ */
+
+#include <linux/string.h>
+#include <linux/module.h>
+
+#include "fuse_i.h"
+
+/**
+ * fuse_kv_parse_one - parse one key=value pair
+ * @pp: i/o parameter for the current position
+ * @end: points to one past the end of the packed string
+ * @keyp: out parameter for key
+ * @valp: out parameter for value
+ *
+ * *@pp points to packed strings - "key0=val0\0key1=val1\0" which ends
+ * at @end - 1. This function parses one pair and set *@keyp to the
+ * start of the key and *@valp to the start of the value. Note that
+ * the original string is modified such that the key string is
+ * terminated with '\0'. *@pp is updated to point to the next string.
+ *
+ * RETURNS:
+ * 1 on successful parse, 0 on EOF, -errno on failure.
+ */
+int fuse_kv_parse_one(char **pp, char *end, char **keyp, char **valp)
+{
+ char *p = *pp;
+ char *key, *val;
+
+ while (p < end && *p == '\0')
+ p++;
+ if (p == end)
+ return 0;
+
+ if (end[-1] != '\0') {
+ pr_err("info not properly terminated\n");
+ return -EINVAL;
+ }
+
+ key = val = p;
+ p += strlen(p);
+
+ if (valp) {
+ strsep(&val, "=");
+ if (!val)
+ val = key + strlen(key);
+ key = strstrip(key);
+ val = strstrip(val);
+ } else
+ key = strstrip(key);
+
+ if (!strlen(key)) {
+ pr_err("zero length info key specified\n");
+ return -EINVAL;
+ }
+
+ *pp = p;
+ *keyp = key;
+ if (valp)
+ *valp = val;
+
+ return 1;
+}
+EXPORT_SYMBOL_GPL(fuse_kv_parse_one);
--
2.26.2
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH 4/7] mtd: Add MTD_MUSE flag
2020-11-26 23:32 [PATCH 0/7] [RFC] MUSE: Userspace backed MTD v2 Richard Weinberger
` (2 preceding siblings ...)
2020-11-26 23:32 ` [PATCH 3/7] fuse: Make cuse_parse_one a common helper Richard Weinberger
@ 2020-11-26 23:32 ` Richard Weinberger
2020-11-26 23:32 ` [PATCH 5/7] fuse: Add MUSE specific defines FUSE interface Richard Weinberger
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Richard Weinberger @ 2020-11-26 23:32 UTC (permalink / raw)
To: miklos
Cc: miquel.raynal, vigneshr, linux-fsdevel, linux-kernel, linux-mtd,
Richard Weinberger
This flag will get set if an MTD is implemeted in userspace
using MUSE.
Signed-off-by: Richard Weinberger <richard@nod.at>
---
include/uapi/mtd/mtd-abi.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/include/uapi/mtd/mtd-abi.h b/include/uapi/mtd/mtd-abi.h
index 65b9db936557..2ad2217e3a96 100644
--- a/include/uapi/mtd/mtd-abi.h
+++ b/include/uapi/mtd/mtd-abi.h
@@ -105,6 +105,7 @@ struct mtd_write_req {
#define MTD_NO_ERASE 0x1000 /* No erase necessary */
#define MTD_POWERUP_LOCK 0x2000 /* Always locked after reset */
#define MTD_SLC_ON_MLC_EMULATION 0x4000 /* Emulate SLC behavior on MLC NANDs */
+#define MTD_MUSE 0x8000 /* This MTD is implemented in userspace */
/* Some common devices / combinations of capabilities */
#define MTD_CAP_ROM 0
--
2.26.2
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH 5/7] fuse: Add MUSE specific defines FUSE interface
2020-11-26 23:32 [PATCH 0/7] [RFC] MUSE: Userspace backed MTD v2 Richard Weinberger
` (3 preceding siblings ...)
2020-11-26 23:32 ` [PATCH 4/7] mtd: Add MTD_MUSE flag Richard Weinberger
@ 2020-11-26 23:32 ` Richard Weinberger
2020-11-26 23:32 ` [PATCH 6/7] fuse: Implement MUSE: MTD in userspace Richard Weinberger
2020-11-26 23:33 ` [PATCH 7/7] MAINTAINERS: Add entry for MUSE Richard Weinberger
6 siblings, 0 replies; 8+ messages in thread
From: Richard Weinberger @ 2020-11-26 23:32 UTC (permalink / raw)
To: miklos
Cc: miquel.raynal, vigneshr, linux-fsdevel, linux-kernel, linux-mtd,
Richard Weinberger
Raise the FUSE API minor version to 33 and add all
MUSE specific operations and data structures.
MUSE_INIT: Initialize a new connection and install the MTD
MUSE_ERASE: Erase a block
MUSE_READ: Read a page
MUSE_WRITE: Write a page
MUSE_MARKBAD: Mark a block as bad
MUSE_ISBAD: Check whether a block is bad
MUSE_SYNC: Flush all cached data
Signed-off-by: Richard Weinberger <richard@nod.at>
---
include/uapi/linux/fuse.h | 73 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 72 insertions(+), 1 deletion(-)
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index 7233502ea991..2f7cbe5ce434 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -175,6 +175,10 @@
*
* 7.32
* - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS
+ *
+ * 7.33
+ * - add support for MUSE: MUSE_INIT, MUSE_ERASE, MUSE_READ, MUSE_WRITE,
+ * MUSE_MARKBAD, MUSE_ISBAD and MUSE_SYNC
*/
#ifndef _LINUX_FUSE_H
@@ -210,7 +214,7 @@
#define FUSE_KERNEL_VERSION 7
/** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 32
+#define FUSE_KERNEL_MINOR_VERSION 33
/** The node ID of the root inode */
#define FUSE_ROOT_ID 1
@@ -483,6 +487,15 @@ enum fuse_opcode {
/* CUSE specific operations */
CUSE_INIT = 4096,
+ /* MUSE specific operations */
+ MUSE_INIT = 8192,
+ MUSE_ERASE = 8193,
+ MUSE_READ = 8194,
+ MUSE_WRITE = 8195,
+ MUSE_MARKBAD = 8196,
+ MUSE_ISBAD = 8197,
+ MUSE_SYNC = 8198,
+
/* Reserved opcodes: helpful to detect structure endian-ness */
CUSE_INIT_BSWAP_RESERVED = 1048576, /* CUSE_INIT << 8 */
FUSE_INIT_BSWAP_RESERVED = 436207616, /* FUSE_INIT << 24 */
@@ -936,4 +949,62 @@ struct fuse_removemapping_one {
#define FUSE_REMOVEMAPPING_MAX_ENTRY \
(PAGE_SIZE / sizeof(struct fuse_removemapping_one))
+#define MUSE_INIT_INFO_MAX 4096
+
+struct muse_init_in {
+ uint32_t fuse_major;
+ uint32_t fuse_minor;
+};
+
+struct muse_init_out {
+ uint32_t fuse_major;
+ uint32_t fuse_minor;
+ uint32_t max_read;
+ uint32_t max_write;
+};
+
+struct muse_erase_in {
+ uint64_t addr;
+ uint64_t len;
+};
+
+struct muse_read_in {
+ uint64_t dataaddr;
+ uint64_t datalen;
+ uint32_t flags;
+ uint32_t padding;
+};
+
+struct muse_read_out {
+ uint64_t datalen;
+ uint32_t soft_error;
+ uint32_t padding;
+};
+
+struct muse_write_in {
+ uint64_t dataaddr;
+ uint64_t datalen;
+ uint32_t flags;
+ uint32_t padding;
+};
+
+struct muse_write_out {
+ uint64_t datalen;
+ uint32_t soft_error;
+ uint32_t padding;
+};
+
+struct muse_markbad_in {
+ uint64_t addr;
+};
+
+struct muse_isbad_in {
+ uint64_t addr;
+};
+
+struct muse_isbad_out {
+ uint32_t result;
+ uint32_t padding;
+};
+
#endif /* _LINUX_FUSE_H */
--
2.26.2
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH 6/7] fuse: Implement MUSE: MTD in userspace
2020-11-26 23:32 [PATCH 0/7] [RFC] MUSE: Userspace backed MTD v2 Richard Weinberger
` (4 preceding siblings ...)
2020-11-26 23:32 ` [PATCH 5/7] fuse: Add MUSE specific defines FUSE interface Richard Weinberger
@ 2020-11-26 23:32 ` Richard Weinberger
2020-11-26 23:33 ` [PATCH 7/7] MAINTAINERS: Add entry for MUSE Richard Weinberger
6 siblings, 0 replies; 8+ messages in thread
From: Richard Weinberger @ 2020-11-26 23:32 UTC (permalink / raw)
To: miklos
Cc: miquel.raynal, vigneshr, linux-fsdevel, linux-kernel, linux-mtd,
Richard Weinberger
MUSE allows implementing a MTD in userspace.
So far userspace has control over mtd_read, mtd_write, mtd_erase,
mtd_block_isbad, mtd_block_markbad, and mtd_sync.
It can also set the following MTD parameters:
name, flags, site, writesize and erasesize.
That way advanced simulators for many type of flashes
can be implemented in userspace.
Signed-off-by: Richard Weinberger <richard@nod.at>
---
fs/fuse/Kconfig | 11 +
fs/fuse/Makefile | 1 +
fs/fuse/muse.c | 730 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 742 insertions(+)
create mode 100644 fs/fuse/muse.c
diff --git a/fs/fuse/Kconfig b/fs/fuse/Kconfig
index 9c8cc1e7b3a5..2fc63dc18a53 100644
--- a/fs/fuse/Kconfig
+++ b/fs/fuse/Kconfig
@@ -56,3 +56,14 @@ config FUSE_DAX
If you want to allow mounting a Virtio Filesystem with the "dax"
option, answer Y.
+
+config MUSE
+ tristate "Memory Technology Device (MTD) in Userspace support"
+ depends on FUSE_FS
+ select FUSE_HELPER
+ select MTD
+ help
+ This FUSE extension allows an MTD to be implemented in userspace.
+
+ If you want to develop or use a userspace MTD based on MUSE,
+ answer Y or M.
diff --git a/fs/fuse/Makefile b/fs/fuse/Makefile
index 7a5768cce6be..67a7af3fb047 100644
--- a/fs/fuse/Makefile
+++ b/fs/fuse/Makefile
@@ -6,6 +6,7 @@
obj-$(CONFIG_FUSE_FS) += fuse.o
obj-$(CONFIG_CUSE) += cuse.o
obj-$(CONFIG_VIRTIO_FS) += virtiofs.o
+obj-$(CONFIG_MUSE) += muse.o
fuse-y := dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o
fuse-$(CONFIG_FUSE_DAX) += dax.o
diff --git a/fs/fuse/muse.c b/fs/fuse/muse.c
new file mode 100644
index 000000000000..b947f5aa2e1c
--- /dev/null
+++ b/fs/fuse/muse.c
@@ -0,0 +1,730 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * MUSE: MTD in userspace
+ * Copyright (C) 2020 sigma star gmbh
+ * Author: Richard Weinberger <richard@nod.at>
+ */
+
+#define pr_fmt(fmt) "MUSE: " fmt
+
+#include <linux/fuse.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/slab.h>
+
+#include "fuse_i.h"
+
+static struct file_operations muse_ctrl_fops;
+
+/*
+ * struct muse_conn - MUSE connection object.
+ *
+ * @fm: FUSE mount object.
+ * @fc: FUSE connection object.
+ * @mtd: MTD object.
+ * @init_done: true when the MTD was registered.
+ *
+ * Describes a connection to a userspace server.
+ * Each connection implements a single MTD.
+ */
+struct muse_conn {
+ struct fuse_mount fm;
+ struct fuse_conn fc;
+ struct mtd_info mtd;
+ bool init_done;
+};
+
+struct muse_init_args {
+ struct fuse_args_pages ap;
+ struct muse_init_in in;
+ struct muse_init_out out;
+ struct page *page;
+ struct fuse_page_desc desc;
+};
+
+static void muse_fc_release(struct fuse_conn *fc)
+{
+ struct muse_conn *mc = container_of(fc, struct muse_conn, fc);
+
+ kfree_rcu(mc, fc.rcu);
+}
+
+static int muse_mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
+{
+ struct muse_conn *mc = mtd->priv;
+ struct fuse_mount *fm = &mc->fm;
+ struct muse_erase_in inarg;
+ FUSE_ARGS(args);
+ ssize_t ret;
+
+ inarg.addr = instr->addr;
+ inarg.len = instr->len;
+
+ args.opcode = MUSE_ERASE;
+ args.nodeid = FUSE_ROOT_ID;
+ args.in_numargs = 1;
+ args.in_args[0].size = sizeof(inarg);
+ args.in_args[0].value = &inarg;
+
+ ret = fuse_simple_request(fm, &args);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int muse_mtd_markbad(struct mtd_info *mtd, loff_t addr)
+{
+ struct muse_conn *mc = mtd->priv;
+ struct fuse_mount *fm = &mc->fm;
+ struct muse_markbad_in inarg;
+ FUSE_ARGS(args);
+ ssize_t ret;
+
+ inarg.addr = addr;
+
+ args.opcode = MUSE_MARKBAD;
+ args.nodeid = FUSE_ROOT_ID;
+ args.in_numargs = 1;
+ args.in_args[0].size = sizeof(inarg);
+ args.in_args[0].value = &inarg;
+
+ ret = fuse_simple_request(fm, &args);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int muse_mtd_isbad(struct mtd_info *mtd, loff_t addr)
+{
+ struct muse_conn *mc = mtd->priv;
+ struct fuse_mount *fm = &mc->fm;
+ struct muse_isbad_in inarg;
+ struct muse_isbad_out outarg;
+ FUSE_ARGS(args);
+ ssize_t ret;
+
+ inarg.addr = addr;
+
+ args.opcode = MUSE_ISBAD;
+ args.nodeid = FUSE_ROOT_ID;
+ args.in_numargs = 1;
+ args.in_args[0].size = sizeof(inarg);
+ args.in_args[0].value = &inarg;
+ args.out_numargs = 1;
+ args.out_args[0].size = sizeof(outarg);
+ args.out_args[0].value = &outarg;
+
+ ret = fuse_simple_request(fm, &args);
+ if (ret < 0)
+ return ret;
+
+ return outarg.result;
+}
+
+static void muse_mtd_sync(struct mtd_info *mtd)
+{
+ struct muse_conn *mc = mtd->priv;
+ struct fuse_mount *fm = &mc->fm;
+ FUSE_ARGS(args);
+
+ args.opcode = MUSE_SYNC;
+ args.nodeid = FUSE_ROOT_ID;
+ args.in_numargs = 0;
+
+ fuse_simple_request(fm, &args);
+}
+
+static ssize_t muse_send_write(struct fuse_args_pages *ap, struct fuse_mount *fm,
+ loff_t from, size_t count, int *soft_error)
+{
+ struct fuse_args *args = &ap->args;
+ ssize_t ret;
+
+ struct muse_write_in in;
+ struct muse_write_out out;
+
+ in.dataaddr = from;
+ in.datalen = count;
+ in.flags = 0;
+ args->opcode = MUSE_WRITE;
+ args->nodeid = FUSE_ROOT_ID;
+ args->in_numargs = 2;
+ args->in_args[0].size = sizeof(in);
+ args->in_args[0].value = ∈
+ /*
+ * args->in_args[1].value was set in set_ap_inout_bufs()
+ */
+ args->in_args[1].size = count;
+ args->out_numargs = 1;
+ args->out_args[0].size = sizeof(out);
+ args->out_args[0].value = &out;
+
+ ret = fuse_simple_request(fm, &ap->args);
+ if (ret < 0)
+ goto out;
+
+ ret = out.datalen;
+ *soft_error = out.soft_error;
+
+out:
+ return ret;
+}
+
+static ssize_t muse_send_read(struct fuse_args_pages *ap, struct fuse_mount *fm,
+ loff_t from, size_t count, int *soft_error)
+{
+ struct fuse_args *args = &ap->args;
+ ssize_t ret;
+
+ struct muse_read_in in;
+ struct muse_read_out out;
+
+ in.dataaddr = from;
+ in.datalen = count;
+ in.flags = 0;
+ args->opcode = MUSE_READ;
+ args->nodeid = FUSE_ROOT_ID;
+ args->in_numargs = 1;
+ args->in_args[0].size = sizeof(in);
+ args->in_args[0].value = ∈
+ args->out_argvar = true;
+ args->out_numargs = 2;
+ args->out_args[0].size = sizeof(out);
+ args->out_args[0].value = &out;
+ /*
+ * args->out_args[1].value was set in set_ap_inout_bufs()
+ */
+ args->out_args[1].size = count;
+
+ ret = fuse_simple_request(fm, &ap->args);
+ if (ret < 0)
+ goto out;
+
+ ret = out.datalen;
+ *soft_error = out.soft_error;
+
+out:
+ return ret;
+}
+
+/*
+ * set_ap_inout_bufs - Set in/out buffers for fuse args
+ *
+ * @ap: FUSE args pages object
+ * @iter: IOV iter which describes source/destination of the IO operation
+ * @count: Inputs the max amount of data we can process,
+ * outputs the amount of data @iter has left.
+ * @write: If non-zero, this is a write operation, read otherwise.
+ *
+ * This function takes a IOV iter object and sets up FUSE args pointer.
+ * Since in MTD all buffers are kernel memory we can directly use
+ * fuse_get_user_addr().
+ */
+static void set_ap_inout_bufs(struct fuse_args_pages *ap, struct iov_iter *iter,
+ size_t *count, int write)
+{
+ unsigned long addr;
+ size_t frag_size;
+
+ addr = fuse_get_user_addr(iter);
+ frag_size = fuse_get_frag_size(iter, *count);
+
+ if (write)
+ ap->args.in_args[1].value = (void *)addr;
+ else
+ ap->args.out_args[1].value = (void *)addr;
+
+ iov_iter_advance(iter, frag_size);
+ *count = frag_size;
+}
+
+/*
+ * muse_do_io - MUSE main IO processing function.
+ *
+ * @mc: MUSE connection object.
+ * @ops: MTD read/write operation object.
+ * @pos: Where to start reading/writing on the MTD.
+ * @retcode: Outputs the return code for the MTD subsystem.
+ * @write: If non-zero, this is a write operation, read otherwise.
+ *
+ * This function is responsible for processing reads and writes to the MTD.
+ * It directly takes @pos and @ops from the MTD subsystem.
+ * All IO is synchronous and buffers provided by @ops have to be kernel memory.
+ * Each MUSE_READ/MUSE_WRITE operation is at most mtd->writebuffer long,
+ * such that the userspace server can assume that each operaion affects at most
+ * one page.
+ * The userspace server can inject also custom errors into the IO path,
+ * mostly -EUCLEAN to signal fixed bit-flips or -EBADMSG for uncorrectable
+ * bit-flips.
+ *
+ * It returns the amount of processed bytes and via @retcode the return code
+ * for the MTD subsystem.
+ */
+static ssize_t muse_do_io(struct muse_conn *mc, struct mtd_oob_ops *ops,
+ loff_t pos, int *retcode, int write)
+{
+ struct kvec iov = { .iov_base = ops->datbuf, .iov_len = ops->len };
+ struct fuse_mount *fm = &mc->fm;
+ struct fuse_conn *fc = fm->fc;
+ size_t fc_max_io = write ? fc->max_write : fc->max_read;
+ size_t count;
+ size_t retlen = 0;
+ struct fuse_args_pages ap;
+ unsigned int max_pages;
+ int bitflips = 0;
+ int eccerrors = 0;
+ ssize_t ret = 0;
+ struct iov_iter iter;
+
+ /*
+ * TODO: Implement OOB support
+ */
+ if (ops->mode != MTD_OPS_PLACE_OOB || ops->ooblen) {
+ ret = -ENOTSUPP;
+ goto out;
+ }
+
+ iov_iter_kvec(&iter, write ? WRITE : READ, &iov, 1, ops->len);
+
+ /*
+ * A full page needs to fit into a single FUSE request.
+ */
+ if (fc_max_io < mc->mtd.writebufsize) {
+ ret = -ENOBUFS;
+ goto out;
+ }
+
+ count = iov_iter_count(&iter);
+
+ max_pages = iov_iter_npages(&iter, fc->max_pages);
+ memset(&ap, 0, sizeof(ap));
+
+ ap.pages = fuse_pages_alloc(max_pages, GFP_KERNEL, &ap.descs);
+ if (!ap.pages) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ *retcode = 0;
+
+ while (count) {
+ size_t nbytes = min_t(size_t, count, mc->mtd.writebufsize);
+ int soft_error;
+
+ set_ap_inout_bufs(&ap, &iter, &nbytes, write);
+
+ if (write)
+ ret = muse_send_write(&ap, fm, pos, nbytes, &soft_error);
+ else
+ ret = muse_send_read(&ap, fm, pos, nbytes, &soft_error);
+
+ kfree(ap.pages);
+ ap.pages = NULL;
+
+ if (ret < 0) {
+ iov_iter_revert(&iter, nbytes);
+ break;
+ }
+
+ if (soft_error) {
+ /*
+ * Userspace wants to inject an error code.
+ */
+
+ if (write) {
+ /*
+ * For writes, take it as-is.
+ */
+ ret = soft_error;
+ break;
+ }
+
+ /*
+ * -EUCLEAN and -EBADMSG are special for reads
+ * in MTD, it expects from a device to return all
+ * requsted data even if there are (un)correctable errors.
+ * The upper layer, such as UBI, has to deal with them.
+ */
+ if (soft_error == -EUCLEAN) {
+ bitflips++;
+ } else if (soft_error == -EBADMSG) {
+ eccerrors++;
+ } else {
+ ret = soft_error;
+ break;
+ }
+ }
+
+ /*
+ * No short reads are allowed in MTD.
+ */
+ if (ret != nbytes) {
+ iov_iter_revert(&iter, nbytes - ret);
+ ret = -EIO;
+ break;
+ }
+
+ count -= ret;
+ retlen += ret;
+ pos += ret;
+
+ if (count) {
+ max_pages = iov_iter_npages(&iter, fc->max_pages);
+ memset(&ap, 0, sizeof(ap));
+ ap.pages = fuse_pages_alloc(max_pages, GFP_KERNEL, &ap.descs);
+ if (!ap.pages)
+ break;
+ }
+ }
+
+ kfree(ap.pages);
+
+ if (bitflips)
+ *retcode = -EUCLEAN;
+ if (eccerrors)
+ *retcode = -EBADMSG;
+
+out:
+ /*
+ * If ret is set, it must be a fatal error which overrides
+ * -EUCLEAN and -EBADMSG.
+ */
+ if (ret < 0)
+ *retcode = ret;
+
+ return retlen;
+}
+
+static int muse_mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
+{
+ struct muse_conn *mc = mtd->priv;
+ int retcode;
+
+ ops->retlen = muse_do_io(mc, ops, from, &retcode, 0);
+
+ return retcode;
+}
+
+static int muse_mtd_write_oob(struct mtd_info *mtd, loff_t to, struct mtd_oob_ops *ops)
+{
+ struct muse_conn *mc = mtd->priv;
+ int retcode;
+
+ ops->retlen = muse_do_io(mc, ops, to, &retcode, 1);
+
+ return retcode;
+}
+
+static int muse_mtd_get_device(struct mtd_info *mtd)
+{
+ struct muse_conn *mc = mtd->priv;
+
+ fuse_conn_get(&mc->fc);
+
+ return 0;
+}
+
+static void muse_mtd_put_device(struct mtd_info *mtd)
+{
+ struct muse_conn *mc = mtd->priv;
+
+ fuse_conn_put(&mc->fc);
+}
+
+struct mtdreq {
+ const char *name;
+ struct mtd_info_user mi;
+};
+
+static int muse_parse_mtdreq(char *p, size_t len, struct mtd_info *mtd)
+{
+ struct mtdreq req = {};
+ char *end = p + len;
+ char *key, *val;
+ int ret;
+
+ for (;;) {
+ ret = fuse_kv_parse_one(&p, end, &key, &val);
+ if (ret < 0)
+ goto out;
+ if (!ret)
+ break;
+
+ if (strcmp(key, "NAME") == 0) {
+ req.name = val;
+ } else if (strcmp(key, "TYPE") == 0) {
+ ret = kstrtoul(val, 10, &req.mi.type);
+ if (ret)
+ goto out;
+ } else if (strcmp(key, "FLAGS") == 0) {
+ ret = kstrtoul(val, 10, &req.mi.flags);
+ if (ret)
+ goto out;
+ } else if (strcmp(key, "SIZE") == 0) {
+ ret = kstrtoul(val, 10, &req.mi.size);
+ if (ret)
+ goto out;
+ } else if (strcmp(key, "WRITESIZE") == 0) {
+ ret = kstrtoul(val, 10, &req.mi.writesize);
+ if (ret)
+ goto out;
+ } else if (strcmp(key, "ERASESIZE") == 0) {
+ ret = kstrtoul(val, 10, &req.mi.erasesize);
+ if (ret)
+ goto out;
+ } else {
+ pr_warn("Ignoring unknown MTD param \"%s\"\n", key);
+ }
+ }
+
+ ret = -EINVAL;
+
+ if (!req.name)
+ goto out;
+
+ if (!req.mi.size || !req.mi.writesize || !req.mi.erasesize)
+ goto out;
+
+ if (req.mi.size % req.mi.writesize)
+ goto out;
+
+ if (req.mi.size % req.mi.erasesize)
+ goto out;
+
+ if (req.mi.flags & ~(MTD_WRITEABLE | MTD_BIT_WRITEABLE | MTD_NO_ERASE))
+ goto out;
+
+ /*
+ * MTD_ABSENT and MTD_UBIVOLUME and special, and can only be used by
+ * internal MTD drivers. Allowing userspace to emulate them asks for
+ * trouble.
+ */
+ if (req.mi.type == MTD_ABSENT || req.mi.type == MTD_UBIVOLUME)
+ goto out;
+
+ mtd->name = kstrdup(req.name, GFP_KERNEL);
+ if (!mtd->name) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ mtd->size = req.mi.size;
+ mtd->erasesize = req.mi.erasesize;
+ mtd->writesize = req.mi.writesize;
+ mtd->writebufsize = mtd->writesize;
+ mtd->type = req.mi.type;
+ mtd->flags = MTD_MUSE | req.mi.flags;
+
+ ret = 0;
+out:
+ return ret;
+}
+
+static void muse_process_init_reply(struct fuse_mount *fm,
+ struct fuse_args *args, int error)
+{
+ struct fuse_conn *fc = fm->fc;
+ struct muse_init_args *mia = container_of(args, struct muse_init_args, ap.args);
+ struct muse_conn *mc = container_of(fc, struct muse_conn, fc);
+ struct fuse_args_pages *ap = &mia->ap;
+ struct muse_init_out *arg = &mia->out;
+ struct page *page = ap->pages[0];
+ struct mtd_info *mtd = &mc->mtd;
+ int ret;
+
+ if (error || arg->fuse_major != FUSE_KERNEL_VERSION || arg->fuse_minor < 33)
+ goto abort;
+
+ fc->minor = arg->fuse_minor;
+ fc->max_read = max_t(unsigned int, arg->max_read, 4096);
+ fc->max_write = max_t(unsigned int, arg->max_write, 4096);
+
+ ret = muse_parse_mtdreq(page_address(page), ap->args.out_args[1].size, mtd);
+ if (ret)
+ goto abort;
+
+ mtd->_erase = muse_mtd_erase;
+ mtd->_sync = muse_mtd_sync;
+ mtd->_read_oob = muse_mtd_read_oob;
+ mtd->_write_oob = muse_mtd_write_oob;
+ mtd->_get_device = muse_mtd_get_device;
+ mtd->_put_device = muse_mtd_put_device;
+
+ /*
+ * Bad blocks make only sense on NAND devices.
+ * As soon _block_isbad is set, upper layer such as
+ * UBI expects a working _block_isbad, so userspace
+ * has to implement MUSE_ISBAD.
+ */
+ if (mtd_type_is_nand(mtd)) {
+ mtd->_block_isbad = muse_mtd_isbad;
+ mtd->_block_markbad = muse_mtd_markbad;
+ }
+
+ mtd->priv = mc;
+ mtd->owner = THIS_MODULE;
+
+ /*
+ * We want one READ/WRITE op per MTD io. So the MTD pagesize needs
+ * to fit into max_write/max_read
+ */
+ if (fc->max_write < mtd->writebufsize || fc->max_read < mtd->writebufsize)
+ goto abort;
+
+ if (mtd_device_register(mtd, NULL, 0) != 0)
+ goto abort;
+
+ mc->init_done = true;
+
+ kfree(mia);
+ __free_page(page);
+ return;
+
+abort:
+ fuse_abort_conn(fc);
+}
+
+static int muse_send_init(struct muse_conn *mc)
+{
+ struct fuse_mount *fm = &mc->fm;
+ struct fuse_args_pages *ap;
+ struct muse_init_args *mia;
+ struct page *page;
+ int ret = -ENOMEM;
+
+ BUILD_BUG_ON(MUSE_INIT_INFO_MAX > PAGE_SIZE);
+
+ page = alloc_page(GFP_KERNEL | __GFP_ZERO);
+ if (!page)
+ goto err;
+
+ mia = kzalloc(sizeof(*mia), GFP_KERNEL);
+ if (!mia)
+ goto err_page;
+
+ ap = &mia->ap;
+ mia->in.fuse_major = FUSE_KERNEL_VERSION;
+ mia->in.fuse_minor = FUSE_KERNEL_MINOR_VERSION;
+ ap->args.opcode = MUSE_INIT;
+ ap->args.in_numargs = 1;
+ ap->args.in_args[0].size = sizeof(mia->in);
+ ap->args.in_args[0].value = &mia->in;
+ ap->args.out_numargs = 2;
+ ap->args.out_args[0].size = sizeof(mia->out);
+ ap->args.out_args[0].value = &mia->out;
+ ap->args.out_args[1].size = MUSE_INIT_INFO_MAX;
+ ap->args.out_argvar = true;
+ ap->args.out_pages = true;
+ ap->num_pages = 1;
+ ap->pages = &mia->page;
+ ap->descs = &mia->desc;
+ mia->page = page;
+ mia->desc.length = ap->args.out_args[1].size;
+ ap->args.end = muse_process_init_reply;
+
+ ret = fuse_simple_background(fm, &ap->args, GFP_KERNEL);
+ if (ret)
+ goto err_ia;
+
+ return 0;
+
+err_ia:
+ kfree(mia);
+err_page:
+ __free_page(page);
+err:
+ return ret;
+}
+
+static int muse_ctrl_open(struct inode *inode, struct file *file)
+{
+ struct muse_conn *mc;
+ struct fuse_dev *fud;
+ int ret;
+
+ /*
+ * Paranoia check.
+ */
+ if (!capable(CAP_SYS_ADMIN)) {
+ ret = -EPERM;
+ goto err;
+ }
+
+ mc = kzalloc(sizeof(*mc), GFP_KERNEL);
+ if (!mc) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ fuse_conn_init(&mc->fc, &mc->fm, get_user_ns(&init_user_ns),
+ &fuse_dev_fiq_ops, NULL);
+
+ fud = fuse_dev_alloc_install(&mc->fc);
+ if (!fud) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ mc->fc.release = muse_fc_release;
+ mc->fc.initialized = 1;
+
+ ret = muse_send_init(mc);
+ if (ret)
+ goto err_dev;
+
+ file->private_data = fud;
+
+ return 0;
+
+err_dev:
+ fuse_dev_free(fud);
+ fuse_conn_put(&mc->fc);
+err_free:
+ kfree(mc);
+err:
+ return ret;
+}
+
+static int muse_ctrl_release(struct inode *inode, struct file *file)
+{
+ struct fuse_dev *fud = file->private_data;
+ struct muse_conn *mc = container_of(fud->fc, struct muse_conn, fc);
+
+ if (mc->init_done)
+ mtd_device_unregister(&mc->mtd);
+
+ fuse_conn_put(&mc->fc);
+
+ return fuse_dev_release(inode, file);
+}
+
+static struct miscdevice muse_ctrl_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "muse",
+ .fops = &muse_ctrl_fops,
+};
+
+static int __init muse_init(void)
+{
+ muse_ctrl_fops = fuse_dev_operations;
+ muse_ctrl_fops.owner = THIS_MODULE;
+ muse_ctrl_fops.open = muse_ctrl_open;
+ muse_ctrl_fops.release = muse_ctrl_release;
+
+ return misc_register(&muse_ctrl_dev);
+}
+
+static void __exit muse_exit(void)
+{
+ misc_deregister(&muse_ctrl_dev);
+}
+
+module_init(muse_init);
+module_exit(muse_exit);
+
+MODULE_AUTHOR("Richard Weinberger <richard@nod.at>");
+MODULE_DESCRIPTION("MTD in userspace");
+MODULE_LICENSE("GPL");
--
2.26.2
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH 7/7] MAINTAINERS: Add entry for MUSE
2020-11-26 23:32 [PATCH 0/7] [RFC] MUSE: Userspace backed MTD v2 Richard Weinberger
` (5 preceding siblings ...)
2020-11-26 23:32 ` [PATCH 6/7] fuse: Implement MUSE: MTD in userspace Richard Weinberger
@ 2020-11-26 23:33 ` Richard Weinberger
6 siblings, 0 replies; 8+ messages in thread
From: Richard Weinberger @ 2020-11-26 23:33 UTC (permalink / raw)
To: miklos
Cc: miquel.raynal, vigneshr, linux-fsdevel, linux-kernel, linux-mtd,
Richard Weinberger
Since MUSE lifes in fs/fuse/, make sure that linux-mtd@ is CC'ed
on patches such that MTD related aspects of changes can be reviewed.
Signed-off-by: Richard Weinberger <richard@nod.at>
---
MAINTAINERS | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 94ac10a153c7..92359bb8d133 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11959,6 +11959,13 @@ L: linux-usb@vger.kernel.org
S: Maintained
F: drivers/usb/musb/
+MUSE: MTD IN USERSPACE DRIVER
+M: Richard Weinberger <richard@nod.at>
+L: linux-mtd@lists.infradead.org
+L: linux-fsdevel@vger.kernel.org
+S: Maintained
+F: fs/fuse/muse.c
+
MXL301RF MEDIA DRIVER
M: Akihiro Tsukada <tskd08@gmail.com>
L: linux-media@vger.kernel.org
--
2.26.2
^ permalink raw reply related [flat|nested] 8+ messages in thread