Index: uspace/lib/c/include/ipc/services.h
===================================================================
--- uspace/lib/c/include/ipc/services.h	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
+++ uspace/lib/c/include/ipc/services.h	(revision d2da1be2df26d9abd2e8ef253d8ca5ce11175e9c)
@@ -58,4 +58,5 @@
 #define SERVICE_NAME_DISPLAY  "hid/display"
 #define SERVICE_NAME_WNDMGT   "hid/display"
+#define SERVICE_NAME_HR       "hr"
 #define SERVICE_NAME_DHCP     "net/dhcp"
 #define SERVICE_NAME_DNSR     "net/dnsr"
Index: uspace/lib/device/include/hr.h
===================================================================
--- uspace/lib/device/include/hr.h	(revision d2da1be2df26d9abd2e8ef253d8ca5ce11175e9c)
+++ uspace/lib/device/include/hr.h	(revision d2da1be2df26d9abd2e8ef253d8ca5ce11175e9c)
@@ -0,0 +1,131 @@
+/*
+ * 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 libdevice
+ * @{
+ */
+/**
+ * @file
+ */
+
+#ifndef LIBDEVICE_HR_H
+#define LIBDEVICE_HR_H
+
+#include <async.h>
+#include <errno.h>
+#include <loc.h>
+
+/* for now */
+#define HR_MAX_EXTENTS 4
+#define HR_MAX_HOTSPARES HR_MAX_EXTENTS
+
+#define HR_DEVNAME_LEN 32
+
+typedef enum hr_level {
+	HR_LVL_0	= 0x00, /* striping, no redundancy */
+	HR_LVL_1	= 0x01, /* n-way mirroring */
+	HR_LVL_4	= 0x04, /* dedicated parity */
+	HR_LVL_5	= 0x05, /* distributed parity */
+	HR_LVL_UNKNOWN	= 0xFF
+} hr_level_t;
+
+/*
+ * SNIA
+ * Common RAID Disk Data Format
+ * Specification
+ * Version 2.0 Revision 19
+ */
+#define HR_RLQ_RAID4_0	0x00 /* RAID-4 Non-Rotating Parity 0 */
+#define HR_RLQ_RAID4_N	0x01 /* RAID-4 Non-Rotating Parity N */
+#define HR_RLQ_RAID5_0R	0x00 /* RAID-5 Rotating Parity 0 with Data Restart */
+#define HR_RLQ_RAID5_NR	0x02 /* RAID-5 Rotating Parity N with Data Restart */
+#define HR_RLQ_RAID5_NC	0x03 /* RAID-5 Rotating Parity N with Data Continuation */
+
+typedef enum hr_vol_status {
+	HR_VOL_INVALID,
+	HR_VOL_ONLINE,	/* OPTIMAL */
+	HR_VOL_FAULTY,
+	HR_VOL_DEGRADED, /* also used for partial, but usable mirror */
+	HR_VOL_REBUILD
+} hr_vol_status_t;
+
+typedef enum hr_ext_status {
+	HR_EXT_INVALID,
+	HR_EXT_ONLINE,	/* OK */
+	HR_EXT_MISSING,
+	HR_EXT_FAILED,
+	HR_EXT_REBUILD,
+	HR_EXT_HOTSPARE
+} hr_ext_status_t;
+
+typedef struct hr {
+	async_sess_t *sess;
+} hr_t;
+
+typedef struct hr_config {
+	char devname[HR_DEVNAME_LEN];
+	service_id_t devs[HR_MAX_EXTENTS];
+	size_t dev_no;
+	hr_level_t level;
+} hr_config_t;
+
+typedef struct hr_extent {
+	service_id_t svc_id;
+	hr_ext_status_t status;
+} hr_extent_t;
+
+typedef struct hr_vol_info {
+	hr_extent_t extents[HR_MAX_EXTENTS];
+	hr_extent_t hotspares[HR_MAX_HOTSPARES];
+	size_t extent_no;
+	size_t hotspare_no;
+	service_id_t svc_id;
+	hr_level_t level;
+	uint64_t nblocks;
+	uint32_t strip_size;
+	size_t bsize;
+	hr_vol_status_t status;
+	uint8_t layout;
+} hr_vol_info_t;
+
+extern errno_t hr_sess_init(hr_t **);
+extern void hr_sess_destroy(hr_t *);
+
+extern errno_t hr_create(hr_t *, hr_config_t *, bool);
+extern errno_t hr_stop(const char *, long);
+extern errno_t hr_add_hotspare(service_id_t, service_id_t);
+extern errno_t hr_print_status(void);
+
+extern const char *hr_get_vol_status_msg(hr_vol_status_t);
+extern const char *hr_get_ext_status_msg(hr_ext_status_t);
+extern const char *hr_get_layout_str(hr_level_t, uint8_t);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/device/include/ipc/hr.h
===================================================================
--- uspace/lib/device/include/ipc/hr.h	(revision d2da1be2df26d9abd2e8ef253d8ca5ce11175e9c)
+++ uspace/lib/device/include/ipc/hr.h	(revision d2da1be2df26d9abd2e8ef253d8ca5ce11175e9c)
@@ -0,0 +1,51 @@
+/*
+ * 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 libdevice
+ * @{
+ */
+/** @file
+ */
+
+#ifndef LIBDEVICE_IPC_HR_H
+#define LIBDEVICE_IPC_HR_H
+
+#include <ipc/common.h>
+
+typedef enum {
+	HR_CREATE = IPC_FIRST_USER_METHOD,
+	HR_ASSEMBLE,
+	HR_STOP,
+	HR_ADD_HOTSPARE,
+	HR_STATUS
+} hr_request_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/device/meson.build
===================================================================
--- uspace/lib/device/meson.build	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
+++ uspace/lib/device/meson.build	(revision d2da1be2df26d9abd2e8ef253d8ca5ce11175e9c)
@@ -32,4 +32,5 @@
 	'src/devman.c',
 	'src/device/led_dev.c',
+	'src/hr.c',
 	'src/io/chardev.c',
 	'src/io/chardev_srv.c',
Index: uspace/lib/device/src/hr.c
===================================================================
--- uspace/lib/device/src/hr.c	(revision d2da1be2df26d9abd2e8ef253d8ca5ce11175e9c)
+++ uspace/lib/device/src/hr.c	(revision d2da1be2df26d9abd2e8ef253d8ca5ce11175e9c)
@@ -0,0 +1,387 @@
+/*
+ * 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 libdevice
+ * @{
+ */
+/**
+ * @file
+ */
+
+#include <abi/ipc/interfaces.h>
+#include <async.h>
+#include <hr.h>
+#include <ipc/hr.h>
+#include <ipc/services.h>
+#include <loc.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <str.h>
+
+errno_t hr_sess_init(hr_t **rhr)
+{
+	errno_t rc;
+	hr_t *hr = NULL;
+
+	if (rhr == NULL)
+		return EINVAL;
+
+	hr = calloc(1, sizeof(hr_t));
+	if (hr == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	service_id_t hr_svcid;
+
+	rc = loc_service_get_id(SERVICE_NAME_HR, &hr_svcid, 0);
+	if (rc != EOK)
+		goto error;
+
+	hr->sess = loc_service_connect(hr_svcid, INTERFACE_HR, 0);
+	if (hr->sess == NULL) {
+		rc = EIO;
+		goto error;
+	}
+
+	*rhr = hr;
+	return EOK;
+error:
+	if (hr != NULL)
+		free(hr);
+
+	return rc;
+}
+
+void hr_sess_destroy(hr_t *hr)
+{
+	if (hr == NULL)
+		return;
+
+	async_hangup(hr->sess);
+	free(hr);
+}
+
+errno_t hr_create(hr_t *hr, hr_config_t *hr_config, bool assemble)
+{
+	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, assemble ? HR_ASSEMBLE : HR_CREATE, 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)
+{
+	errno_t rc;
+	size_t i;
+	char *devname;
+	hr_extent_t *ext;
+
+	printf("--- vol %zu ---\n", index);
+
+	printf("svc_id: %lu\n", vol_info->svc_id);
+
+	rc = loc_service_get_name(vol_info->svc_id, &devname);
+	if (rc != EOK)
+		return rc;
+	printf("devname: %s\n", devname);
+
+	printf("status: %s\n", hr_get_vol_status_msg(vol_info->status));
+
+	printf("level: %d\n", vol_info->level);
+	if (vol_info->level == HR_LVL_4 || vol_info->level == HR_LVL_5) {
+		printf("layout: %s\n",
+		    hr_get_layout_str(vol_info->level, vol_info->layout));
+	}
+	if (vol_info->level == HR_LVL_0 || vol_info->level == HR_LVL_4) {
+		if (vol_info->strip_size / 1024 < 1)
+			printf("strip size in bytes: %u\n",
+			    vol_info->strip_size);
+		else
+			printf("strip size: %uK\n",
+			    vol_info->strip_size / 1024);
+	}
+	printf("size in bytes: %luMiB\n",
+	    vol_info->nblocks * vol_info->bsize / 1024 / 1024);
+	printf("size in blocks: %lu\n", vol_info->nblocks);
+	printf("block size: %zu\n", vol_info->bsize);
+
+	if (vol_info->level == HR_LVL_4)
+		printf("extents: [P] [status] [index] [devname]\n");
+	else
+		printf("extents: [status] [index] [devname]\n");
+	for (i = 0; i < vol_info->extent_no; i++) {
+		ext = &vol_info->extents[i];
+		if (ext->status == HR_EXT_MISSING) {
+			devname = (char *) "MISSING-devname";
+		} else {
+			rc = loc_service_get_name(ext->svc_id, &devname);
+			if (rc != EOK)
+				return rc;
+		}
+		if (vol_info->level == HR_LVL_4) {
+			if ((i == 0 && vol_info->layout == HR_RLQ_RAID4_0) ||
+			    (i == vol_info->extent_no - 1 &&
+			    vol_info->layout == HR_RLQ_RAID4_N))
+				printf("          P   %s    %zu       %s\n", hr_get_ext_status_msg(ext->status), i, devname);
+			else
+				printf("              %s    %zu       %s\n", hr_get_ext_status_msg(ext->status), i, devname);
+		} else {
+			printf("          %s    %zu       %s\n", hr_get_ext_status_msg(ext->status), i, devname);
+		}
+	}
+
+	if (vol_info->hotspare_no == 0)
+		return EOK;
+
+	printf("hotspares: [status] [index] [devname]\n");
+	for (i = 0; i < vol_info->hotspare_no; i++) {
+		ext = &vol_info->hotspares[i];
+		if (ext->status == HR_EXT_MISSING) {
+			devname = (char *) "MISSING-devname";
+		} else {
+			rc = loc_service_get_name(ext->svc_id, &devname);
+			if (rc != EOK)
+				return rc;
+		}
+		printf("            %s   %zu     %s\n",
+		    hr_get_ext_status_msg(ext->status), i, devname);
+	}
+
+	return EOK;
+}
+
+errno_t hr_stop(const char *devname, long extent)
+{
+	hr_t *hr;
+	errno_t rc;
+	async_exch_t *exch;
+	service_id_t svc_id;
+
+	rc = loc_service_get_id(devname, &svc_id, 0);
+	if (rc != EOK)
+		return rc;
+
+	rc = hr_sess_init(&hr);
+	if (rc != EOK)
+		return rc;
+
+	exch = async_exchange_begin(hr->sess);
+	if (exch == NULL) {
+		rc = EINVAL;
+		goto error;
+	}
+	rc = async_req_2_0(exch, HR_STOP, svc_id, extent);
+	async_exchange_end(exch);
+
+	if (rc != EOK)
+		goto error;
+error:
+	hr_sess_destroy(hr);
+	return rc;
+}
+
+errno_t hr_add_hotspare(service_id_t vol_svc_id, service_id_t hs_svc_id)
+{
+	hr_t *hr;
+	errno_t rc;
+	async_exch_t *exch;
+
+	rc = hr_sess_init(&hr);
+	if (rc != EOK)
+		return rc;
+
+	exch = async_exchange_begin(hr->sess);
+	if (exch == NULL) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	rc = async_req_2_0(exch, HR_ADD_HOTSPARE, vol_svc_id, hs_svc_id);
+	async_exchange_end(exch);
+error:
+	hr_sess_destroy(hr);
+	return rc;
+}
+
+errno_t hr_print_status(void)
+{
+	hr_t *hr;
+	errno_t rc, retval;
+	async_exch_t *exch;
+	aid_t req;
+	size_t size, i;
+	hr_vol_info_t *vols = NULL;
+
+	rc = hr_sess_init(&hr);
+	if (rc != EOK)
+		return rc;
+
+	exch = async_exchange_begin(hr->sess);
+	if (exch == NULL) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	req = async_send_0(exch, HR_STATUS, NULL);
+	rc = async_data_read_start(exch, &size, sizeof(size_t));
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return rc;
+	}
+
+	vols = calloc(size, sizeof(hr_vol_info_t));
+	if (vols == NULL) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return ENOMEM;
+	}
+
+	for (i = 0; i < size; i++) {
+		rc = async_data_read_start(exch, &vols[i],
+		    sizeof(hr_vol_info_t));
+		if (rc != EOK) {
+			async_exchange_end(exch);
+			async_forget(req);
+			goto error;
+		}
+	}
+
+	async_exchange_end(exch);
+	async_wait_for(req, &retval);
+	if (retval != EOK) {
+		rc = retval;
+		goto error;
+	}
+
+	if (size == 0) {
+		printf("no active arrays\n");
+		goto error;
+	}
+
+	for (i = 0; i < size; i++) {
+		rc = print_vol_info(i, &vols[i]);
+		if (rc != EOK)
+			goto error;
+	}
+
+error:
+	hr_sess_destroy(hr);
+	if (vols != NULL)
+		free(vols);
+	return rc;
+}
+
+const char *hr_get_vol_status_msg(hr_vol_status_t status)
+{
+	switch (status) {
+	case HR_VOL_INVALID:
+		return "INVALID";
+	case HR_VOL_ONLINE:
+		return "ONLINE";
+	case HR_VOL_FAULTY:
+		return "FAULTY";
+	case HR_VOL_DEGRADED:
+		return "DEGRADED";
+	case HR_VOL_REBUILD:
+		return "REBUILD";
+	default:
+		return "UNKNOWN";
+	}
+}
+
+const char *hr_get_ext_status_msg(hr_ext_status_t status)
+{
+	switch (status) {
+	case HR_EXT_INVALID:
+		return "INVALID";
+	case HR_EXT_ONLINE:
+		return "ONLINE";
+	case HR_EXT_MISSING:
+		return "MISSING";
+	case HR_EXT_FAILED:
+		return "FAILED";
+	case HR_EXT_REBUILD:
+		return "REBUILD";
+	case HR_EXT_HOTSPARE:
+		return "HOTSPARE";
+	default:
+		return "UNKNOWN";
+	}
+}
+
+const char *hr_get_layout_str(hr_level_t level, uint8_t layout)
+{
+	switch (level) {
+	case HR_LVL_4:
+		switch (layout) {
+		case HR_RLQ_RAID4_0:
+			return "RAID-4 Non-Rotating Parity 0";
+		case HR_RLQ_RAID4_N:
+			return "RAID-4 Non-Rotating Parity N";
+		default:
+			return "RAID-4 INVALID";
+		}
+	case HR_LVL_5:
+		switch (layout) {
+		case HR_RLQ_RAID5_0R:
+			return "RAID-5 Rotating Parity 0 with Data Restart";
+		case HR_RLQ_RAID5_NR:
+			return "RAID-5 Rotating Parity N with Data Restart";
+		case HR_RLQ_RAID5_NC:
+			return "RAID-5 Rotating Parity N with Data Continuation";
+		default:
+			return "RAID-5 INVALID";
+		}
+	default:
+		return "INVALID";
+	}
+}
+
+/** @}
+ */
