Linux RDMA and InfiniBand development
 help / color / mirror / Atom feed
From: Md Haris Iqbal <haris.iqbal@ionos.com>
To: linux-block@vger.kernel.org, linux-rdma@vger.kernel.org
Cc: linux-kernel@vger.kernel.org, axboe@kernel.dk,
	bvanassche@acm.org, hch@lst.de, jgg@ziepe.ca, leon@kernel.org,
	jinpu.wang@ionos.com, Md Haris Iqbal <haris.iqbal@ionos.com>,
	Jia Li <jia.li@ionos.com>
Subject: [PATCH 10/13] block/brmr: client: sysfs interface functions
Date: Tue,  5 May 2026 09:46:22 +0200	[thread overview]
Message-ID: <20260505074644.195453-11-haris.iqbal@ionos.com> (raw)
In-Reply-To: <20260505074644.195453-1-haris.iqbal@ionos.com>

Add the BRMR client sysfs interface used to map and unmap remote
devices.  Writes to /sys/devices/virtual/brmr-client/ctl/map_device
trigger creation of a /dev/brmrN gendisk backed by the named RMR
pool; per-device attribute groups expose the device state and
statistics, and accept unmap requests.

This file is not compiled until the modules are wired into the
build in a later patch in this series.

Signed-off-by: Md Haris Iqbal <haris.iqbal@ionos.com>
Signed-off-by: Jia Li <jia.li@ionos.com>
---
 drivers/block/brmr/brmr-clt-sysfs.c | 463 ++++++++++++++++++++++++++++
 1 file changed, 463 insertions(+)
 create mode 100644 drivers/block/brmr/brmr-clt-sysfs.c

diff --git a/drivers/block/brmr/brmr-clt-sysfs.c b/drivers/block/brmr/brmr-clt-sysfs.c
new file mode 100644
index 000000000000..7d2435acac6a
--- /dev/null
+++ b/drivers/block/brmr/brmr-clt-sysfs.c
@@ -0,0 +1,463 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Block device over RMR (BRMR)
+ *
+ * Copyright (c) 2026 IONOS SE
+ */
+
+#undef pr_fmt
+#define pr_fmt(fmt) KBUILD_MODNAME " L" __stringify(__LINE__) ": " fmt
+
+#include <linux/types.h>
+#include <linux/ctype.h>
+#include <linux/parser.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/device.h>
+
+#include "brmr-clt.h"
+
+static struct device *brmr_dev;
+static struct class *brmr_dev_class;
+static struct kobject *brmr_devs_kobj;
+
+enum {
+	BRMR_OPT_ERR		= 0,
+	BRMR_OPT_POOL		= 1 << 1,
+	BRMR_OPT_SIZE		= 1 << 2,
+};
+
+static int brmr_clt_create_dev_sysfs_files(struct brmr_clt_dev *dev);
+static int brmr_add_dev_symlink(struct brmr_clt_dev *dev);
+
+static unsigned int brmr_opt_mandatory[] = {
+	BRMR_OPT_POOL,
+};
+
+static const match_table_t brmr_opt_tokens = {
+	{	BRMR_OPT_POOL,		"pool=%s"	},
+	{	BRMR_OPT_SIZE,		"size=%s"	},
+	{	BRMR_OPT_ERR,		NULL		},
+};
+
+/* remove new line from string */
+static void strip(char *s)
+{
+	char *p = s;
+
+	while (*s != '\0') {
+		if (*s != '\n')
+			*p++ = *s++;
+		else
+			++s;
+	}
+	*p = '\0';
+}
+
+static int brmr_clt_parse_options(const char *buf,
+			      char *pool,
+			      unsigned long *size)
+{
+	char *options, *sep_opt;
+	char *p;
+	substring_t args[MAX_OPT_ARGS];
+	int opt_mask = 0;
+	int token;
+	int ret = -EINVAL;
+	int i;
+
+	options = kstrdup(buf, GFP_KERNEL);
+	if (!options)
+		return -ENOMEM;
+
+	sep_opt = strstrip(options);
+	strip(sep_opt);
+	while ((p = strsep(&sep_opt, " ")) != NULL) {
+		if (!*p)
+			continue;
+
+		token = match_token(p, brmr_opt_tokens, args);
+		opt_mask |= token;
+
+		switch (token) {
+		case BRMR_OPT_POOL:
+			p = match_strdup(args);
+			if (!p) {
+				ret = -ENOMEM;
+				goto out;
+			}
+			if (strlen(p) > NAME_MAX) {
+				pr_err("poolname too long\n");
+				ret = -EINVAL;
+				kfree(p);
+				goto out;
+			}
+			strscpy(pool, p, NAME_MAX);
+			kfree(p);
+			break;
+
+		case BRMR_OPT_SIZE:
+			p = match_strdup(args);
+			if (!p) {
+				ret = -ENOMEM;
+				goto out;
+			}
+
+			/*
+			 * The conventional semantics are that if the number begins with 0x, it will
+			 * be parsed as hexadecimal; if it begins with 0, it will be parsed as
+			 * octal; otherwise, it will be parsed as decimal.
+			 */
+			ret = kstrtoul(p, 0, size);
+			if (ret) {
+				pr_err("size '%s' isn't an integer: %d\n", p, ret);
+				kfree(p);
+				goto out;
+			}
+			kfree(p);
+			break;
+
+
+		default:
+			pr_err("unknown parameter or missing value"
+			       " '%s'\n", p);
+			ret = -EINVAL;
+			goto out;
+		}
+	}
+
+	for (i = 0; i < ARRAY_SIZE(brmr_opt_mandatory); i++) {
+		if ((opt_mask & brmr_opt_mandatory[i])) {
+			ret = 0;
+		} else {
+			pr_err("parameters missing\n");
+			ret = -EINVAL;
+			break;
+		}
+	}
+
+out:
+	kfree(options);
+	return ret;
+}
+
+static ssize_t brmr_map_device_show(struct kobject *kobj,
+				    struct kobj_attribute *attr,
+				    char *page)
+{
+	return scnprintf(page, PAGE_SIZE, "Usage: echo \""
+			 "pool=<name of the RMR pool> "
+			 "size=<size of the volume in sectors>\" > %s\n",
+			 attr->attr.name);
+}
+
+static ssize_t brmr_map_device_store(struct kobject *kobj,
+				     struct kobj_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct brmr_clt_dev *dev;
+	char pool[NAME_MAX];
+	unsigned long size = 0;
+	int ret;
+
+	ret = brmr_clt_parse_options(buf, pool, &size);
+	if (ret)
+		goto err;
+
+	dev = find_and_get_device(pool);
+	if (dev) {
+		pr_err("Device exists and opened as %s\n",
+		       dev->gd->disk_name);
+		brmr_clt_put_dev(dev);
+		ret = -EEXIST;
+		goto err;
+	}
+
+	dev = brmr_clt_map_device(pool, size);
+	if (IS_ERR(dev)) {
+		pr_err("Error mapping device to pool %s\n", pool);
+		ret = PTR_ERR(dev);
+		goto err;
+	}
+	ret = brmr_clt_create_dev_sysfs_files(dev);
+	if (ret)
+		goto close_device;
+
+	ret = brmr_add_dev_symlink(dev);
+	if (ret)
+		goto destroy_sysfs;
+
+	return count;
+
+destroy_sysfs:
+	sysfs_remove_link(&dev->kobj, BRMR_LINK_NAME);
+	brmr_clt_destroy_dev_sysfs_files(dev, NULL);
+close_device:
+	brmr_clt_close_device(dev, NULL);
+err:
+	return ret;
+}
+
+static struct kobj_attribute brmr_map_device_attr =
+	__ATTR(map_device, 0644,
+	       brmr_map_device_show, brmr_map_device_store);
+
+static struct attribute *default_attrs[] = {
+	&brmr_map_device_attr.attr,
+	NULL,
+};
+
+static struct attribute_group default_attr_group = {
+	.attrs = default_attrs,
+};
+
+static ssize_t brmr_unmap_device_show(struct kobject *kobj,
+				      struct kobj_attribute *attr, char *page)
+{
+	return scnprintf(page, PAGE_SIZE, "Usage: echo <normal|force> > %s\n",
+			 attr->attr.name);
+}
+
+static ssize_t brmr_unmap_device_store(struct kobject *kobj,
+				       struct kobj_attribute *attr,
+				       const char *buf, size_t count)
+{
+	struct brmr_clt_dev *dev;
+	int err;
+
+	dev = container_of(kobj, struct brmr_clt_dev, kobj);
+
+	if (!sysfs_streq(buf, "1")) {
+		pr_err("%s: unknown value: '%s'\n", attr->attr.name, buf);
+		return -EINVAL;
+	}
+
+	pr_info("Closing device %s.\n", dev->gd->disk_name);
+
+	/*
+	 * We take explicit module reference only for one reason: do not
+	 * race with lockless ibnbd_destroy_sessions().
+	 */
+	if (!try_module_get(THIS_MODULE)) {
+		return -ENODEV;
+	}
+	err = brmr_clt_close_device(dev, &attr->attr);
+	if (unlikely(err)) {
+		if (unlikely(err != -EALREADY))
+			pr_err("unmap_device %s: %d\n",
+			       dev->gd->disk_name, err);
+		goto module_put;
+	}
+
+	/*
+	 * Here device can be vanished!
+	 */
+	err = count;
+
+module_put:
+	module_put(THIS_MODULE);
+
+	return err;
+}
+
+static struct kobj_attribute brmr_unmap_device_attr =
+	__ATTR(unmap_device, 0644,
+	       brmr_unmap_device_show, brmr_unmap_device_store);
+
+static ssize_t brmr_clt_device_state_show(struct kobject *kobj,
+				   struct kobj_attribute *attr,
+				   char *page)
+{
+	struct brmr_clt_dev *dev;
+	int cnt;
+
+	dev = container_of(kobj, struct brmr_clt_dev, kobj);
+
+	switch (dev->dev_state) {
+	case DEV_STATE_INIT:
+		cnt = sysfs_emit(page, "init\n");
+		break;
+	case DEV_STATE_READY:
+		cnt = sysfs_emit(page, "ready\n");
+		break;
+	case DEV_STATE_DISCONNECTED:
+		cnt = sysfs_emit(page, "disconnected\n");
+		break;
+	case DEV_STATE_CLOSING:
+		cnt = sysfs_emit(page, "closing\n");
+		break;
+	default:
+		cnt = sysfs_emit(page, "unknown\n");
+		break;
+	}
+
+	if (dev->map_incomplete)
+		cnt += sysfs_emit_at(page, cnt, "degraded\n");
+
+	return cnt;
+}
+
+static struct kobj_attribute brmr_clt_device_state =
+	__ATTR(state, 0444, brmr_clt_device_state_show, NULL);
+
+static struct attribute *brmr_clt_dev_attrs[] = {
+	&brmr_unmap_device_attr.attr,
+	&brmr_clt_device_state.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(brmr_clt_dev);
+
+static struct kobj_type brmr_clt_device_ktype = {
+	.sysfs_ops      = &kobj_sysfs_ops,
+	.default_groups  = brmr_clt_dev_groups,
+};
+
+static struct kobj_type brmr_clt_stats_ktype = {
+	.sysfs_ops      = &kobj_sysfs_ops,
+};
+
+static int brmr_clt_create_stats_files(struct kobject *kobj,
+				   struct kobject *kobj_stats);
+
+static int brmr_clt_create_dev_sysfs_files(struct brmr_clt_dev *dev)
+{
+	int ret;
+
+	ret = kobject_init_and_add(&dev->kobj, &brmr_clt_device_ktype,
+				   brmr_devs_kobj,
+				   "%s", dev->gd->disk_name);
+	if (ret)
+		pr_err("Failed to create sysfs dir for device '%s': %d\n",
+		       dev->gd->disk_name, ret);
+
+	ret = brmr_clt_create_stats_files(&dev->kobj, &dev->kobj_stats);
+	if (unlikely(ret)) {
+		pr_err("Failed to create sysfs stats files "
+		       "for device '%s': %d\n", dev->gd->disk_name, ret);
+		kobject_del(&dev->kobj);
+		kobject_put(&dev->kobj);
+	}
+	return ret;
+}
+
+static int brmr_add_dev_symlink(struct brmr_clt_dev *dev)
+{
+	struct kobject *gd_kobj = &disk_to_dev(dev->gd)->kobj;
+	int ret;
+
+	ret = sysfs_create_link(&dev->kobj, gd_kobj, BRMR_LINK_NAME);
+	if (ret) {
+		pr_err("Creating symlink for %s failed, err: %d\n",
+		       dev->gd->disk_name, ret);
+	}
+
+	return ret;
+}
+
+void brmr_clt_destroy_dev_sysfs_files(struct brmr_clt_dev *dev,
+				     const struct attribute *sysfs_self)
+{
+	if (dev->kobj.state_in_sysfs) {
+
+		kobject_del(&dev->kobj_stats);
+		kobject_put(&dev->kobj_stats);
+		if (sysfs_self)
+			sysfs_remove_file_self(&dev->kobj, sysfs_self);
+		kobject_del(&dev->kobj);
+		kobject_put(&dev->kobj);
+	}
+}
+
+int brmr_clt_create_sysfs_files(void)
+{
+	int err;
+
+	brmr_dev_class = class_create("brmr-client");
+	if (IS_ERR(brmr_dev_class))
+		return PTR_ERR(brmr_dev_class);
+
+	brmr_dev = device_create(brmr_dev_class, NULL,
+				 MKDEV(0, 0), NULL, "ctl");
+	if (IS_ERR(brmr_dev)) {
+		err = PTR_ERR(brmr_dev);
+		goto cls_destroy;
+	}
+	brmr_devs_kobj = kobject_create_and_add("devices", &brmr_dev->kobj);
+	if (unlikely(!brmr_devs_kobj)) {
+		err = -ENOMEM;
+		goto dev_destroy;
+	}
+	err = sysfs_create_group(&brmr_dev->kobj, &default_attr_group);
+	if (unlikely(err))
+		goto put_devs_kobj;
+
+	return 0;
+
+put_devs_kobj:
+	kobject_del(brmr_devs_kobj);
+	kobject_put(brmr_devs_kobj);
+dev_destroy:
+	device_unregister(brmr_dev);
+cls_destroy:
+	class_destroy(brmr_dev_class);
+
+	return err;
+}
+
+void brmr_clt_destroy_sysfs_files(void)
+{
+	sysfs_remove_group(&brmr_dev->kobj, &default_attr_group);
+	kobject_del(brmr_devs_kobj);
+	kobject_put(brmr_devs_kobj);
+	device_unregister(brmr_dev);
+	class_destroy(brmr_dev_class);
+}
+
+STAT_ATTR(struct brmr_clt_dev, requests,
+	  brmr_clt_stats_rq_to_str, brmr_clt_reset_submitted_req);
+STAT_ATTR(struct brmr_clt_dev, request_sizes,
+	  brmr_clt_stats_sizes_to_str, brmr_clt_reset_req_sizes);
+STAT_ATTR(struct brmr_clt_dev, sts_resource,
+	  brmr_stats_sts_resource_to_str, brmr_clt_reset_sts_resource);
+STAT_ATTR(struct brmr_clt_dev, sts_resource_per_cpu,
+	  brmr_stats_sts_resource_per_cpu_to_str, brmr_clt_reset_sts_resource);
+
+static struct attribute *brmr_stats_attrs[] = {
+	&requests_attr.attr,
+	&request_sizes_attr.attr,
+	&sts_resource_attr.attr,
+	&sts_resource_per_cpu_attr.attr,
+	NULL,
+};
+
+static struct attribute_group brmr_stats_attr_group = {
+	.attrs = brmr_stats_attrs,
+};
+
+static int brmr_clt_create_stats_files(struct kobject *kobj,
+				   struct kobject *kobj_stats)
+{
+	int ret;
+
+	ret = kobject_init_and_add(kobj_stats, &brmr_clt_stats_ktype, kobj, "stats");
+	if (ret) {
+		pr_err("Failed to init and add stats kobject, err: %d\n",
+		       ret);
+		return ret;
+	}
+
+	ret = sysfs_create_group(kobj_stats, &brmr_stats_attr_group);
+	if (ret) {
+		pr_err("failed to create stats sysfs group, err: %d\n",
+		       ret);
+		goto put_stats_obj;
+	}
+
+	return 0;
+
+put_stats_obj:
+	kobject_del(kobj_stats);
+	kobject_put(kobj_stats);
+
+	return ret;
+}
-- 
2.43.0


  parent reply	other threads:[~2026-05-05  7:47 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-05  7:46 [LSF/MM/BPF RFC PATCH 00/13] Md Haris Iqbal
2026-05-05  7:46 ` [PATCH 01/13] RDMA/rmr: add public and private headers Md Haris Iqbal
2026-05-05  7:46 ` [PATCH 02/13] RDMA/rmr: add shared library code (pool, map, request) Md Haris Iqbal
2026-05-05  7:46 ` [PATCH 03/13] RDMA/rmr: client: main functionality Md Haris Iqbal
2026-05-05  7:46 ` [PATCH 04/13] RDMA/rmr: client: sysfs interface functions Md Haris Iqbal
2026-05-05  7:46 ` [PATCH 05/13] RDMA/rmr: server: main functionality Md Haris Iqbal
2026-05-05  7:46 ` [PATCH 06/13] RDMA/rmr: server: sysfs interface functions Md Haris Iqbal
2026-05-05  7:46 ` [PATCH 07/13] RDMA/rmr: include client and server modules into kernel compilation Md Haris Iqbal
2026-05-05  7:46 ` [PATCH 08/13] block/brmr: add private headers with brmr protocol structs and helpers Md Haris Iqbal
2026-05-05  7:46 ` [PATCH 09/13] block/brmr: client: main functionality Md Haris Iqbal
2026-05-05  7:46 ` Md Haris Iqbal [this message]
2026-05-05  7:46 ` [PATCH 11/13] block/brmr: server: " Md Haris Iqbal
2026-05-05  7:46 ` [PATCH 12/13] block/brmr: server: sysfs interface functions Md Haris Iqbal
2026-05-05  7:46 ` [PATCH 13/13] block/brmr: include client and server modules into kernel compilation Md Haris Iqbal
2026-05-12 10:34 ` [LSF/MM/BPF RFC PATCH 00/13] Leon Romanovsky

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260505074644.195453-11-haris.iqbal@ionos.com \
    --to=haris.iqbal@ionos.com \
    --cc=axboe@kernel.dk \
    --cc=bvanassche@acm.org \
    --cc=hch@lst.de \
    --cc=jgg@ziepe.ca \
    --cc=jia.li@ionos.com \
    --cc=jinpu.wang@ionos.com \
    --cc=leon@kernel.org \
    --cc=linux-block@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-rdma@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox