Index: uspace/app/hrctl/hrctl.c
===================================================================
--- uspace/app/hrctl/hrctl.c	(revision ee83e9cf5b7876922b8efc19ac46344d1f5194bf)
+++ uspace/app/hrctl/hrctl.c	(revision b0f13664307c5f49642065cfc42aec7d4a3040cb)
@@ -53,17 +53,18 @@
     "\n"
     "Options:\n"
-    "  -h, --help              display this help and exit\n"
-    "  -C, --config-file=path  create an array from file,\n"
-    "                          sample file at: " HRCTL_SAMPLE_CONFIG_PATH "\n"
-    "  -s, --status            display status of active arrays\n"
-    "  -a, --assemble=NAME     assemble an existing array\n"
-    "  -c, --create=NAME       create new array\n"
-    "  -n                      non-zero number of devices\n"
-    "  -l, --level=LEVEL       set the RAID level,\n"
-    "                          valid values: 0, 1, 5, linear\n"
-    "  -0                      striping\n"
-    "  -1                      mirroring\n"
-    "  -5                      distributed parity\n"
-    "  -L                      linear concatenation\n"
+    "  -h, --help                display this help and exit\n"
+    "  -C, --create-file=path    create an array from file,\n"
+    "                            sample file at: " HRCTL_SAMPLE_CONFIG_PATH "\n"
+    "  -A, --assemble-file=path  create an array from file\n"
+    "  -s, --status              display status of active arrays\n"
+    "  -c, --create=NAME         create new array\n"
+    "  -a, --assemble=NAME       assemble an existing array\n"
+    "  -n                        non-zero number of devices\n"
+    "  -l, --level=LEVEL         set the RAID level,\n"
+    "                            valid values: 0, 1, 5, linear\n"
+    "  -0                        striping\n"
+    "  -1                        mirroring\n"
+    "  -5                        distributed parity\n"
+    "  -L                        linear concatenation\n"
     "\n"
     "Example usage:\n"
@@ -83,5 +84,6 @@
 	{ "create", required_argument, 0, 'c' },
 	{ "level", required_argument, 0, 'l' },
-	{ "config-file", required_argument, 0, 'C' },
+	{ "create-file", required_argument, 0, 'C' },
+	{ "assemble-file", required_argument, 0, 'A' },
 	{ 0, 0, 0, 0 }
 };
@@ -154,9 +156,10 @@
 
 	level_str = sif_node_get_attr(narray, "level");
-	if (level_str == NULL) {
-		rc = EIO;
-		goto error;
-	}
-	cfg->level = strtol(level_str, NULL, 10);
+	if (level_str == NULL)
+		cfg->level = hr_l_empty;
+	else if (str_cmp(level_str, "linear") == 0)
+		cfg->level = hr_l_linear;
+	else
+		cfg->level = strtol(level_str, NULL, 10);
 
 	dev_no_str = sif_node_get_attr(narray, "n");
@@ -228,5 +231,5 @@
 
 	while (c != -1) {
-		c = getopt_long(argc, argv, "hsC:c:a:l:015Ln:",
+		c = getopt_long(argc, argv, "hsC:c:A:a:l:015Ln:",
 		    long_options, NULL);
 		switch (c) {
@@ -239,12 +242,4 @@
 				return 1;
 			return 0;
-		case 'a':
-			if (str_size(optarg) > sizeof(cfg->devname) - 1) {
-				printf("hrctl: device name too long\n");
-				return 1;
-			}
-			str_cpy(cfg->devname, sizeof(cfg->devname), optarg);
-			assemble = true;
-			break;
 		case 'C':
 			/* only support 1 array inside config for now XXX */
@@ -264,4 +259,20 @@
 			create = true;
 			break;
+		case 'A':
+			rc = load_config(optarg, cfg);
+			if (rc != EOK) {
+				printf("hrctl: failed to load config\n");
+				return 1;
+			}
+			assemble = true;
+			goto skip;
+		case 'a':
+			if (str_size(optarg) > sizeof(cfg->devname) - 1) {
+				printf("hrctl: device name too long\n");
+				return 1;
+			}
+			str_cpy(cfg->devname, sizeof(cfg->devname), optarg);
+			assemble = true;
+			break;
 		case 'l':
 			if (cfg->level != hr_l_empty)
@@ -296,8 +307,4 @@
 			if ((int) cfg->dev_no + optind != argc)
 				goto bad;
-			if (cfg->dev_no > HR_MAXDEVS) {
-				printf("hrctl: too many devices\n");
-				return 1;
-			}
 			rc = fill_config_devs(argc, argv, optind, cfg);
 			if (rc != EOK)
@@ -308,10 +315,20 @@
 
 skip:
-	if ((create && assemble) ||
-	    (!create && !assemble) ||
-	    (create && cfg->level == hr_l_empty) ||
-	    (assemble && cfg->level != hr_l_empty) ||
-	    (cfg->dev_no == 0)) {
+	if ((create && assemble) || (!create && !assemble))
 		goto bad;
+
+	if (create && cfg->level == hr_l_empty) {
+		printf("hrctl: invalid level, exiting\n");
+		return 1;
+	}
+
+	if (cfg->dev_no > HR_MAXDEVS) {
+		printf("hrctl: too many devices, exiting\n");
+		return 1;
+	}
+
+	if (cfg->dev_no == 0) {
+		printf("hrctl: invalid number of devices, exiting\n");
+		return 1;
 	}
 
@@ -327,5 +344,6 @@
 		printf("hrctl: hr_create() rc: %s\n", str_error(rc));
 	} else if (assemble) {
-		printf("hrctl: assemble not implemented yet\n");
+		rc = hr_assemble(hr, cfg);
+		printf("hrctl: hr_assemble() rc: %s\n", str_error(rc));
 	}
 
Index: uspace/lib/device/include/hr.h
===================================================================
--- uspace/lib/device/include/hr.h	(revision ee83e9cf5b7876922b8efc19ac46344d1f5194bf)
+++ uspace/lib/device/include/hr.h	(revision b0f13664307c5f49642065cfc42aec7d4a3040cb)
@@ -76,4 +76,5 @@
 
 extern errno_t hr_create(hr_t *, hr_config_t *);
+extern errno_t hr_assemble(hr_t *, hr_config_t *);
 extern errno_t hr_print_status(void);
 
Index: uspace/lib/device/include/ipc/hr.h
===================================================================
--- uspace/lib/device/include/ipc/hr.h	(revision ee83e9cf5b7876922b8efc19ac46344d1f5194bf)
+++ uspace/lib/device/include/ipc/hr.h	(revision b0f13664307c5f49642065cfc42aec7d4a3040cb)
@@ -40,4 +40,5 @@
 typedef enum {
 	HR_CREATE = IPC_FIRST_USER_METHOD,
+	HR_ASSEMBLE,
 	HR_STATUS
 } hr_request_t;
Index: uspace/lib/device/src/hr.c
===================================================================
--- uspace/lib/device/src/hr.c	(revision ee83e9cf5b7876922b8efc19ac46344d1f5194bf)
+++ uspace/lib/device/src/hr.c	(revision b0f13664307c5f49642065cfc42aec7d4a3040cb)
@@ -114,4 +114,31 @@
 }
 
+errno_t hr_assemble(hr_t *hr, hr_config_t *hr_config)
+{
+	errno_t rc, retval;
+	async_exch_t *exch;
+	aid_t req;
+
+	exch = async_exchange_begin(hr->sess);
+	if (exch == NULL)
+		return EINVAL;
+
+	req = async_send_0(exch, HR_ASSEMBLE, NULL);
+
+	rc = async_data_write_start(exch, hr_config, sizeof(hr_config_t));
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return rc;
+	}
+
+	async_exchange_end(exch);
+	async_wait_for(req, &retval);
+	if (retval != EOK)
+		return retval;
+
+	return EOK;
+}
+
 static errno_t print_vol_info(size_t index, hr_vol_info_t *vol_info)
 {
Index: uspace/srv/bd/hr/hr.c
===================================================================
--- uspace/srv/bd/hr/hr.c	(revision ee83e9cf5b7876922b8efc19ac46344d1f5194bf)
+++ uspace/srv/bd/hr/hr.c	(revision b0f13664307c5f49642065cfc42aec7d4a3040cb)
@@ -49,4 +49,6 @@
 #include <str_error.h>
 
+#include "superblock.h"
+#include "util.h"
 #include "var.h"
 
@@ -58,4 +60,5 @@
 
 static service_id_t ctl_sid;
+
 
 static void hr_create_srv(ipc_call_t *icall)
@@ -92,10 +95,12 @@
 		async_answer_0(&call, rc);
 		async_answer_0(icall, rc);
+		return;
 	}
 
 	new_volume = calloc(1, sizeof(hr_volume_t));
 	if (new_volume == NULL) {
-		rc = ENOMEM;
-		goto end;
+		free(cfg);
+		async_answer_0(icall, ENOMEM);
+		return;
 	}
 
@@ -104,4 +109,19 @@
 	new_volume->level = cfg->level;
 	new_volume->dev_no = cfg->dev_no;
+
+	rc = hr_init_devs(new_volume);
+	if (rc != EOK) {
+		free(cfg);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	rc = hr_check_devs(new_volume);
+	if (rc != EOK)
+		goto error;
+
+	rc = hr_write_meta_to_vol(new_volume);
+	if (rc != EOK)
+		goto error;
 
 	switch (new_volume->level) {
@@ -113,14 +133,13 @@
 		break;
 	default:
-		log_msg(LOG_DEFAULT, LVL_NOTE,
-		    "level %d not implemented yet\n", new_volume->level);
+		log_msg(LOG_DEFAULT, LVL_ERROR,
+		    "level %d not implemented yet", new_volume->level);
 		rc = EINVAL;
-		goto end;
+		goto error;
 	}
 
 	rc = new_volume->hr_ops.create(new_volume);
-	if (rc != EOK) {
-		goto end;
-	}
+	if (rc != EOK)
+		goto error;
 
 	fibril_mutex_lock(&hr_volumes_lock);
@@ -128,9 +147,115 @@
 	fibril_mutex_unlock(&hr_volumes_lock);
 
-	log_msg(LOG_DEFAULT, LVL_NOTE, "created volume \"%s\" (%" PRIun ")\n",
+	log_msg(LOG_DEFAULT, LVL_NOTE, "created volume \"%s\" (%" PRIun ")",
 	    new_volume->devname, new_volume->svc_id);
 
-end:
 	free(cfg);
+	async_answer_0(icall, rc);
+	return;
+error:
+	free(cfg);
+	hr_fini_devs(new_volume);
+	async_answer_0(icall, rc);
+}
+
+static void hr_assemble_srv(ipc_call_t *icall)
+{
+	log_msg(LOG_DEFAULT, LVL_NOTE, "hr_assemble_srv()");
+
+	errno_t rc;
+	size_t size;
+	hr_config_t *cfg;
+	hr_volume_t *new_volume;
+	ipc_call_t call;
+
+	if (!async_data_write_receive(&call, &size)) {
+		async_answer_0(&call, EREFUSED);
+		async_answer_0(icall, EREFUSED);
+		return;
+	}
+
+	if (size != sizeof(hr_config_t)) {
+		async_answer_0(&call, EINVAL);
+		async_answer_0(icall, EINVAL);
+		return;
+	}
+
+	cfg = calloc(1, sizeof(hr_config_t));
+	if (cfg == NULL) {
+		async_answer_0(&call, ENOMEM);
+		async_answer_0(icall, ENOMEM);
+		return;
+	}
+
+	rc = async_data_write_finalize(&call, cfg, size);
+	if (rc != EOK) {
+		async_answer_0(&call, rc);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	new_volume = calloc(1, sizeof(hr_volume_t));
+	if (new_volume == NULL) {
+		free(cfg);
+		async_answer_0(icall, ENOMEM);
+		return;
+	}
+
+	str_cpy(new_volume->devname, 32, cfg->devname);
+	memcpy(new_volume->devs, cfg->devs, sizeof(service_id_t) * HR_MAXDEVS);
+	new_volume->dev_no = cfg->dev_no;
+
+	if (cfg->level != hr_l_empty)
+		log_msg(LOG_DEFAULT, LVL_WARN,
+		    "level manually set when assembling, ingoring");
+
+	new_volume->level = hr_l_empty;
+
+	rc = hr_init_devs(new_volume);
+	if (rc != EOK) {
+		free(cfg);
+		async_answer_0(icall, rc);
+		return;
+	}
+
+	rc = hr_check_devs(new_volume);
+	if (rc != EOK)
+		goto error;
+
+	rc = hr_get_vol_from_meta(cfg, new_volume);
+	if (rc != EOK)
+		goto error;
+
+	switch (new_volume->level) {
+	case hr_l_1:
+		new_volume->hr_ops.create = hr_raid1_create;
+		break;
+	case hr_l_0:
+		new_volume->hr_ops.create = hr_raid0_create;
+		break;
+	default:
+		log_msg(LOG_DEFAULT, LVL_ERROR,
+		    "level %d not implemented yet", new_volume->level);
+		rc = EINVAL;
+		goto error;
+	}
+
+	rc = new_volume->hr_ops.create(new_volume);
+	if (rc != EOK)
+		goto error;
+
+	fibril_mutex_lock(&hr_volumes_lock);
+	list_append(&new_volume->lvolumes, &hr_volumes);
+	fibril_mutex_unlock(&hr_volumes_lock);
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "assembled volume \"%s\" (%" PRIun ")",
+	    new_volume->devname, new_volume->svc_id);
+
+	free(cfg);
+	async_answer_0(icall, rc);
+	return;
+error:
+	free(cfg);
+	hr_fini_devs(new_volume);
 	async_answer_0(icall, rc);
 }
@@ -170,5 +295,6 @@
 		info.extent_no = volume->dev_no;
 		info.level = volume->level;
-		info.nblocks = volume->nblocks;
+		/* print usable number of blocks */
+		info.nblocks = volume->data_blkno;
 		info.bsize = volume->bsize;
 
@@ -219,4 +345,7 @@
 		case HR_STATUS:
 			hr_print_status_srv(&call);
+			break;
+		case HR_ASSEMBLE:
+			hr_assemble_srv(&call);
 			break;
 		default:
Index: uspace/srv/bd/hr/meson.build
===================================================================
--- uspace/srv/bd/hr/meson.build	(revision ee83e9cf5b7876922b8efc19ac46344d1f5194bf)
+++ uspace/srv/bd/hr/meson.build	(revision b0f13664307c5f49642065cfc42aec7d4a3040cb)
@@ -28,3 +28,3 @@
 
 deps = [ 'block', 'device' ]
-src = files('hr.c', 'raid0.c', 'raid1.c', 'util.c')
+src = files('hr.c', 'raid0.c', 'raid1.c', 'superblock.c', 'util.c')
Index: uspace/srv/bd/hr/raid0.c
===================================================================
--- uspace/srv/bd/hr/raid0.c	(revision ee83e9cf5b7876922b8efc19ac46344d1f5194bf)
+++ uspace/srv/bd/hr/raid0.c	(revision b0f13664307c5f49642065cfc42aec7d4a3040cb)
@@ -48,6 +48,6 @@
 #include <str_error.h>
 
+#include "util.h"
 #include "var.h"
-#include "util.h"
 
 extern fibril_mutex_t big_lock;
@@ -64,6 +64,4 @@
 static errno_t hr_raid0_bd_get_num_blocks(bd_srv_t *, aoff64_t *);
 
-#define strip_size DATA_XFER_LIMIT
-
 static bd_ops_t hr_raid0_bd_ops = {
 	.open = hr_raid0_bd_open,
@@ -80,5 +78,5 @@
 {
 	uint64_t N = vol->dev_no; /* extents */
-	uint64_t L = strip_size / vol->bsize; /* size of strip in blocks */
+	uint64_t L = HR_STRIP_SIZE / vol->bsize; /* size of strip in blocks */
 
 	uint64_t i = (x / L) % N; /* extent */
@@ -114,4 +112,7 @@
 	while (left != 0) {
 		raid0_geometry(ba++, vol, &extent, &phys_block);
+		rc = hr_calc_ba(vol, cnt, &ba);
+		if (rc != EOK)
+			break;
 		rc = block_sync_cache(vol->devs[extent], phys_block, 1);
 		if (rc != EOK)
@@ -140,4 +141,7 @@
 	while (left != 0) {
 		raid0_geometry(ba++, vol, &extent, &phys_block);
+		rc = hr_calc_ba(vol, cnt, &ba);
+		if (rc != EOK)
+			break;
 		rc = block_read_direct(vol->devs[extent], phys_block, 1, buf);
 		buf = buf + vol->bsize;
@@ -167,4 +171,7 @@
 	while (left != 0) {
 		raid0_geometry(ba++, vol, &extent, &phys_block);
+		rc = hr_calc_ba(vol, cnt, &phys_block);
+		if (rc != EOK)
+			break;
 		rc = block_write_direct(vol->devs[extent], phys_block, 1, data);
 		data = data + vol->bsize;
@@ -190,5 +197,5 @@
 	hr_volume_t *vol = bd->srvs->sarg;
 
-	*rnb = vol->nblocks;
+	*rnb = vol->data_blkno;
 	return EOK;
 }
@@ -196,4 +203,6 @@
 errno_t hr_raid0_create(hr_volume_t *new_volume)
 {
+	errno_t rc;
+
 	assert(new_volume->level == hr_l_0);
 
@@ -204,53 +213,13 @@
 	}
 
-	errno_t rc;
-	size_t i, bsize, last_bsize;
-	uint64_t nblocks, last_nblocks;
-	uint64_t total_blocks = 0;
-
-	rc = hr_init_devs(new_volume);
-	if (rc != EOK)
-		return rc;
-
-	for (i = 0; i < new_volume->dev_no; i++) {
-		rc = block_get_nblocks(new_volume->devs[i], &nblocks);
-		if (rc != EOK)
-			goto error;
-		if (i != 0 && nblocks != last_nblocks) {
-			log_msg(LOG_DEFAULT, LVL_ERROR,
-			    "number of blocks differs");
-			rc = EINVAL;
-			goto error;
-		}
-		total_blocks += nblocks;
-		last_nblocks = nblocks;
-	}
-
-	for (i = 0; i < new_volume->dev_no; i++) {
-		rc = block_get_bsize(new_volume->devs[i], &bsize);
-		if (rc != EOK)
-			goto error;
-		if (i != 0 && bsize != last_bsize) {
-			log_msg(LOG_DEFAULT, LVL_ERROR, "block sizes differ");
-			rc = EINVAL;
-			goto error;
-		}
-		last_bsize = bsize;
-	}
-
 	bd_srvs_init(&new_volume->hr_bds);
 	new_volume->hr_bds.ops = &hr_raid0_bd_ops;
 	new_volume->hr_bds.sarg = new_volume;
-	new_volume->nblocks = total_blocks;
-	new_volume->bsize = bsize;
 
 	rc = hr_register_volume(new_volume);
 	if (rc != EOK)
-		goto error;
-
-	return EOK;
-error:
-	hr_fini_devs(new_volume);
-	return rc;
+		return rc;
+
+	return EOK;
 }
 
Index: uspace/srv/bd/hr/raid1.c
===================================================================
--- uspace/srv/bd/hr/raid1.c	(revision ee83e9cf5b7876922b8efc19ac46344d1f5194bf)
+++ uspace/srv/bd/hr/raid1.c	(revision b0f13664307c5f49642065cfc42aec7d4a3040cb)
@@ -47,6 +47,6 @@
 #include <str_error.h>
 
+#include "util.h"
 #include "var.h"
-#include "util.h"
 
 extern fibril_mutex_t big_lock;
@@ -85,5 +85,5 @@
 }
 
-static errno_t hr_raid1_bd_sync_cache(bd_srv_t *bd, aoff64_t ba, size_t size)
+static errno_t hr_raid1_bd_sync_cache(bd_srv_t *bd, aoff64_t ba, size_t cnt)
 {
 	fibril_mutex_lock(&big_lock);
@@ -93,6 +93,12 @@
 	size_t i;
 
+	rc = hr_calc_ba(vol, cnt, &ba);
+	if (rc != EOK) {
+		fibril_mutex_unlock(&big_lock);
+		return rc;
+	}
+
 	for (i = 0; i < vol->dev_no; i++) {
-		rc = block_sync_cache(vol->devs[i], ba, size);
+		rc = block_sync_cache(vol->devs[i], ba, cnt);
 		if (rc != EOK)
 			break;
@@ -111,4 +117,10 @@
 	errno_t rc;
 	size_t i;
+
+	rc = hr_calc_ba(vol, cnt, &ba);
+	if (rc != EOK) {
+		fibril_mutex_unlock(&big_lock);
+		return rc;
+	}
 
 	for (i = 0; i < vol->dev_no; i++) {
@@ -131,4 +143,10 @@
 	size_t i;
 
+	rc = hr_calc_ba(vol, cnt, &ba);
+	if (rc != EOK) {
+		fibril_mutex_unlock(&big_lock);
+		return rc;
+	}
+
 	for (i = 0; i < vol->dev_no; i++) {
 		rc = block_write_direct(vol->devs[i], ba, cnt, data);
@@ -153,5 +171,5 @@
 	hr_volume_t *vol = bd->srvs->sarg;
 
-	*rnb = vol->nblocks;
+	*rnb = vol->data_blkno;
 	return EOK;
 }
@@ -159,4 +177,6 @@
 errno_t hr_raid1_create(hr_volume_t *new_volume)
 {
+	errno_t rc;
+
 	assert(new_volume->level == hr_l_1);
 
@@ -167,53 +187,13 @@
 	}
 
-	errno_t rc;
-	size_t i, bsize, last_bsize;
-	uint64_t nblocks, last_nblocks;
-	uint64_t total_blocks = 0;
-
-	rc = hr_init_devs(new_volume);
-	if (rc != EOK)
-		return rc;
-
-	for (i = 0; i < new_volume->dev_no; i++) {
-		rc = block_get_nblocks(new_volume->devs[i], &nblocks);
-		if (rc != EOK)
-			goto error;
-		if (i != 0 && nblocks != last_nblocks) {
-			log_msg(LOG_DEFAULT, LVL_ERROR,
-			    "number of blocks differs");
-			rc = EINVAL;
-			goto error;
-		}
-		total_blocks += nblocks;
-		last_nblocks = nblocks;
-	}
-
-	for (i = 0; i < new_volume->dev_no; i++) {
-		rc = block_get_bsize(new_volume->devs[i], &bsize);
-		if (rc != EOK)
-			goto error;
-		if (i != 0 && bsize != last_bsize) {
-			log_msg(LOG_DEFAULT, LVL_ERROR, "block sizes differ");
-			rc = EINVAL;
-			goto error;
-		}
-		last_bsize = bsize;
-	}
-
 	bd_srvs_init(&new_volume->hr_bds);
 	new_volume->hr_bds.ops = &hr_raid1_bd_ops;
 	new_volume->hr_bds.sarg = new_volume;
-	new_volume->nblocks = total_blocks / new_volume->dev_no;
-	new_volume->bsize = bsize;
 
 	rc = hr_register_volume(new_volume);
 	if (rc != EOK)
-		goto error;
-
-	return EOK;
-error:
-	hr_fini_devs(new_volume);
-	return rc;
+		return rc;
+
+	return EOK;
 }
 
Index: uspace/srv/bd/hr/superblock.c
===================================================================
--- uspace/srv/bd/hr/superblock.c	(revision b0f13664307c5f49642065cfc42aec7d4a3040cb)
+++ uspace/srv/bd/hr/superblock.c	(revision b0f13664307c5f49642065cfc42aec7d4a3040cb)
@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2024 Miroslav Cimerman
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup hr
+ * @{
+ */
+/**
+ * @file
+ */
+
+#include <block.h>
+#include <byteorder.h>
+#include <errno.h>
+#include <io/log.h>
+#include <loc.h>
+#include <mem.h>
+#include <uuid.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <str.h>
+#include <types/uuid.h>
+
+#include "superblock.h"
+#include "util.h"
+#include "var.h"
+
+static errno_t read_metadata(service_id_t, hr_metadata_t *);
+
+errno_t hr_write_meta_to_vol(hr_volume_t *vol)
+{
+	log_msg(LOG_DEFAULT, LVL_NOTE, "hr_write_meta_to_vol()");
+
+	errno_t rc;
+	size_t i, data_offset;
+	uint64_t data_blkno;
+	hr_metadata_t *metadata;
+	uuid_t uuid;
+
+	metadata = calloc(1, HR_META_SIZE * vol->bsize);
+	if (metadata == NULL)
+		return ENOMEM;
+
+	if (vol->nblocks <= HR_META_OFF + HR_META_SIZE) {
+		log_msg(LOG_DEFAULT, LVL_ERROR,
+		    "not enough blocks");
+		rc = EINVAL;
+		goto error;
+	}
+
+	data_offset = HR_META_OFF + HR_META_SIZE;
+	if (vol->level == hr_l_1) {
+		data_blkno = vol->nblocks - data_offset;
+	} else if (vol->level == hr_l_0) {
+		data_blkno = vol->nblocks - (data_offset * vol->dev_no);
+	} else {
+		log_msg(LOG_DEFAULT, LVL_ERROR,
+		    "level %d not implemented yet", vol->level);
+		return EINVAL;
+	}
+
+	metadata->magic = host2uint64_t_le(HR_MAGIC);
+	metadata->extent_no = host2uint32_t_le(vol->dev_no);
+	metadata->level = host2uint32_t_le(vol->level);
+	metadata->nblocks = host2uint64_t_le(vol->nblocks);
+	metadata->data_blkno = host2uint64_t_le(data_blkno);
+	metadata->data_offset = host2uint32_t_le(data_offset);
+	for (i = 0; i < vol->dev_no; i++) {
+		metadata->index = host2uint32_t_le(i);
+
+		rc = uuid_generate(&uuid);
+		if (rc != EOK)
+			goto error;
+		uuid_encode(&uuid, metadata->uuid);
+
+		str_cpy(metadata->devname, 32, vol->devname);
+
+		rc = block_write_direct(vol->devs[i], HR_META_OFF, HR_META_SIZE,
+		    metadata);
+		if (rc != EOK)
+			goto error;
+	}
+
+	/* fill in new members */
+	vol->data_offset = data_offset;
+	vol->data_blkno = data_blkno;
+
+error:
+	free(metadata);
+	return rc;
+}
+
+errno_t hr_get_vol_from_meta(hr_config_t *cfg, hr_volume_t *new_volume)
+{
+	log_msg(LOG_DEFAULT, LVL_NOTE, "hr_get_vol_from_meta()");
+
+	errno_t rc;
+	hr_metadata_t *metadata;
+
+	metadata = calloc(1, HR_META_SIZE * new_volume->bsize);
+	if (metadata == NULL)
+		return ENOMEM;
+
+	/* for now assume metadata are in sync across extents */
+	rc = read_metadata(cfg->devs[0], metadata);
+	if (rc != EOK)
+		goto end;
+
+	/* TODO: sort new_volume->devs according to metadata extent index */
+
+	if (uint64_t_le2host(metadata->magic) != HR_MAGIC) {
+		printf("invalid magic\n");
+		rc = EINVAL;
+		goto end;
+	}
+
+	new_volume->level = uint32_t_le2host(metadata->level);
+	new_volume->dev_no = uint32_t_le2host(metadata->extent_no);
+	new_volume->nblocks = uint64_t_le2host(metadata->nblocks);
+	new_volume->data_blkno = uint64_t_le2host(metadata->data_blkno);
+	new_volume->data_offset = uint32_t_le2host(metadata->data_offset);
+
+	if (str_cmp(metadata->devname, new_volume->devname) != 0) {
+		log_msg(LOG_DEFAULT, LVL_NOTE,
+		    "devname on metadata (%s) and config (%s) differ, using config",
+		    metadata->devname, new_volume->devname);
+	}
+end:
+	free(metadata);
+	return EOK;
+}
+
+static errno_t read_metadata(service_id_t dev, hr_metadata_t *metadata)
+{
+	errno_t rc;
+	size_t bsize;
+	uint64_t nblocks;
+
+	rc = block_get_bsize(dev, &bsize);
+	if (rc != EOK)
+		return rc;
+
+	rc = block_get_nblocks(dev, &nblocks);
+	if (rc != EOK)
+		return rc;
+
+	if (nblocks < (HR_META_SIZE + HR_META_OFF) * bsize)
+		return EINVAL;
+
+	rc = block_read_direct(dev, HR_META_OFF, HR_META_SIZE, metadata);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/srv/bd/hr/superblock.h
===================================================================
--- uspace/srv/bd/hr/superblock.h	(revision b0f13664307c5f49642065cfc42aec7d4a3040cb)
+++ uspace/srv/bd/hr/superblock.h	(revision b0f13664307c5f49642065cfc42aec7d4a3040cb)
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2024 Miroslav Cimerman
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup hr
+ * @{
+ */
+/**
+ * @file
+ */
+
+#ifndef _HR_SUPERBLOCK_H
+#define _HR_SUPERBLOCK_H
+
+#include "var.h"
+
+#define HR_MAGIC 0x4420492041205248LLU
+
+typedef struct hr_metadata {
+	uint64_t magic;
+	uint32_t extent_no;
+	uint32_t level;
+	uint64_t nblocks;	/* all blocks */
+	uint64_t data_blkno;	/* usable blocks */
+	uint32_t data_offset;	/* block where data starts */
+	uint32_t index;		/* index of disk in array */
+	uint8_t uuid[16];
+	char devname[32];
+} hr_metadata_t;
+
+#define HR_META_SIZE 1	/* in blocks */
+#define HR_META_OFF 8	/* in blocks */
+
+extern errno_t hr_write_meta_to_vol(hr_volume_t *);
+extern errno_t hr_get_vol_from_meta(hr_config_t *, hr_volume_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/bd/hr/util.c
===================================================================
--- uspace/srv/bd/hr/util.c	(revision ee83e9cf5b7876922b8efc19ac46344d1f5194bf)
+++ uspace/srv/bd/hr/util.c	(revision b0f13664307c5f49642065cfc42aec7d4a3040cb)
@@ -43,6 +43,6 @@
 #include <str_error.h>
 
+#include "util.h"
 #include "var.h"
-#include "util.h"
 
 extern loc_srv_t *hr_srv;
@@ -82,4 +82,6 @@
 errno_t hr_register_volume(hr_volume_t *new_volume)
 {
+	log_msg(LOG_DEFAULT, LVL_NOTE, "hr_register_volume()");
+
 	errno_t rc;
 	service_id_t new_id;
@@ -120,4 +122,63 @@
 }
 
+errno_t hr_check_devs(hr_volume_t *vol)
+{
+	log_msg(LOG_DEFAULT, LVL_NOTE, "hr_check_devs()");
+
+	errno_t rc;
+	size_t i, bsize, last_bsize;
+	uint64_t nblocks, last_nblocks;
+	uint64_t total_blocks = 0;
+
+	for (i = 0; i < vol->dev_no; i++) {
+		rc = block_get_nblocks(vol->devs[i], &nblocks);
+		if (rc != EOK)
+			goto error;
+		if (i != 0 && nblocks != last_nblocks) {
+			log_msg(LOG_DEFAULT, LVL_ERROR,
+			    "number of blocks differs");
+			rc = EINVAL;
+			goto error;
+		}
+		total_blocks += nblocks;
+		last_nblocks = nblocks;
+	}
+
+	for (i = 0; i < vol->dev_no; i++) {
+		rc = block_get_bsize(vol->devs[i], &bsize);
+		if (rc != EOK)
+			goto error;
+		if (i != 0 && bsize != last_bsize) {
+			log_msg(LOG_DEFAULT, LVL_ERROR, "block sizes differ");
+			rc = EINVAL;
+			goto error;
+		}
+		last_bsize = bsize;
+	}
+
+	if (vol->level == hr_l_1) {
+		vol->nblocks = total_blocks / vol->dev_no;
+	} else if (vol->level == hr_l_0) {
+		vol->nblocks = total_blocks;
+	} else {
+		log_msg(LOG_DEFAULT, LVL_DEBUG, "unkown level, ok when assembling");
+		vol->nblocks = 0;
+	}
+
+	vol->bsize = bsize;
+
+error:
+	return rc;
+}
+
+errno_t hr_calc_ba(hr_volume_t *vol, size_t cnt, uint64_t *ba)
+{
+	if (*ba + cnt > vol->data_blkno)
+		return ERANGE;
+
+	*ba = *ba + vol->data_offset;
+	return EOK;
+}
+
 /** @}
  */
Index: uspace/srv/bd/hr/util.h
===================================================================
--- uspace/srv/bd/hr/util.h	(revision ee83e9cf5b7876922b8efc19ac46344d1f5194bf)
+++ uspace/srv/bd/hr/util.h	(revision b0f13664307c5f49642065cfc42aec7d4a3040cb)
@@ -44,4 +44,6 @@
 extern void hr_fini_devs(hr_volume_t *);
 extern errno_t hr_register_volume(hr_volume_t *);
+extern errno_t hr_check_devs(hr_volume_t *vol);
+errno_t hr_calc_ba(hr_volume_t *vol, size_t cnt, uint64_t *ba);
 
 #endif
Index: uspace/srv/bd/hr/var.h
===================================================================
--- uspace/srv/bd/hr/var.h	(revision ee83e9cf5b7876922b8efc19ac46344d1f5194bf)
+++ uspace/srv/bd/hr/var.h	(revision b0f13664307c5f49642065cfc42aec7d4a3040cb)
@@ -43,4 +43,6 @@
 #define NAME "hr"
 
+#define HR_STRIP_SIZE DATA_XFER_LIMIT
+
 typedef struct hr_volume hr_volume_t;
 
@@ -56,6 +58,8 @@
 	service_id_t devs[HR_MAXDEVS];
 	uint64_t nblocks;
+	uint64_t data_blkno;
+	uint32_t data_offset;
+	service_id_t svc_id;
 	size_t bsize;
-	service_id_t svc_id;
 	size_t dev_no;
 	hr_level_t level;
