Index: uspace/app/sysinst/rdimg.c
===================================================================
--- uspace/app/sysinst/rdimg.c	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/app/sysinst/rdimg.c	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -191,5 +191,5 @@
 
 	printf("rd_img_close: eject RAM disk volume\n");
-	rc = vol_part_eject(vol, rd_svcid);
+	rc = vol_part_eject(vol, rd_svcid, vef_none);
 	if (rc != EOK) {
 		printf("Error ejecting RAM disk volume.\n");
Index: uspace/app/sysinst/sysinst.c
===================================================================
--- uspace/app/sysinst/sysinst.c	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/app/sysinst/sysinst.c	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2024 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -475,5 +475,5 @@
 	}
 
-	rc = vol_part_eject(vol, part_id);
+	rc = vol_part_eject(vol, part_id, vef_physical);
 	if (rc != EOK) {
 		printf("Error ejecting volume.\n");
Index: uspace/app/vol/vol.c
===================================================================
--- uspace/app/vol/vol.c	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/app/vol/vol.c	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2017 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -36,4 +36,5 @@
 #include <io/table.h>
 #include <loc.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -107,5 +108,5 @@
 }
 
-static errno_t vol_cmd_eject(const char *volspec)
+static errno_t vol_cmd_eject(const char *volspec, bool physical)
 {
 	vol_t *vol = NULL;
@@ -125,5 +126,6 @@
 	}
 
-	rc = vol_part_eject(vol, part_id);
+	rc = vol_part_eject(vol, part_id, physical ? vef_physical :
+	    vef_none);
 	if (rc != EOK) {
 		printf("Error ejecting volume.\n");
@@ -323,10 +325,11 @@
 {
 	printf("Syntax:\n");
-	printf("  %s                List present volumes\n", NAME);
-	printf("  %s -c             List volume configuration entries\n", NAME);
-	printf("  %s -h             Print help\n", NAME);
-	printf("  %s eject <mp>     Eject volume mounted in a directory\n", NAME);
-	printf("  %s insert <svc>   Insert volume based on service identifier\n", NAME);
-	printf("  %s insert -p <mp> Insert volume based on filesystem path\n", NAME);
+	printf("  %s                 List present volumes\n", NAME);
+	printf("  %s -c              List volume configuration entries\n", NAME);
+	printf("  %s -h              Print help\n", NAME);
+	printf("  %s eject [-s] <mp> Eject volume mounted in a directory\n", NAME);
+	printf("                     -s to eject physically\n");
+	printf("  %s insert <svc>    Insert volume based on service identifier\n", NAME);
+	printf("  %s insert -p <mp>  Insert volume based on filesystem path\n", NAME);
 }
 
@@ -336,4 +339,5 @@
 	char *volspec;
 	vol_cmd_t vcmd;
+	bool physical = false;
 	int i;
 	errno_t rc = EINVAL;
@@ -351,4 +355,9 @@
 		} else if (str_cmp(cmd, "eject") == 0) {
 			vcmd = vcmd_eject;
+			if (str_cmp(argv[i], "-s") == 0) {
+				physical = true;
+				++i;
+			}
+
 			if (argc <= i) {
 				printf("Parameter missing.\n");
@@ -382,5 +391,5 @@
 	switch (vcmd) {
 	case vcmd_eject:
-		rc = vol_cmd_eject(volspec);
+		rc = vol_cmd_eject(volspec, physical);
 		break;
 	case vcmd_insert:
Index: uspace/lib/ata/src/ata.c
===================================================================
--- uspace/lib/ata/src/ata.c	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/lib/ata/src/ata.c	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -96,4 +96,5 @@
 static errno_t ata_bd_get_block_size(bd_srv_t *, size_t *);
 static errno_t ata_bd_get_num_blocks(bd_srv_t *, aoff64_t *);
+static errno_t ata_bd_eject(bd_srv_t *);
 static errno_t ata_bd_sync_cache(bd_srv_t *, aoff64_t, size_t);
 
@@ -105,5 +106,6 @@
 static errno_t ata_identify_dev(ata_device_t *, void *);
 static errno_t ata_identify_pkt_dev(ata_device_t *, void *);
-static errno_t ata_cmd_packet(ata_device_t *, const void *, size_t, void *,
+static errno_t ata_cmd_packet_nondata(ata_device_t *, const void *, size_t);
+static errno_t ata_cmd_packet_din(ata_device_t *, const void *, size_t, void *,
     size_t, size_t *);
 static errno_t ata_pcmd_inquiry(ata_device_t *, void *, size_t, size_t *);
@@ -111,4 +113,5 @@
 static errno_t ata_pcmd_read_capacity(ata_device_t *, uint64_t *, size_t *);
 static errno_t ata_pcmd_read_toc(ata_device_t *, uint8_t, void *, size_t);
+static errno_t ata_pcmd_start_stop_unit(ata_device_t *, uint8_t);
 static void disk_print_summary(ata_device_t *);
 static size_t ata_disk_maxnb(ata_device_t *);
@@ -129,5 +132,6 @@
 	.get_block_size = ata_bd_get_block_size,
 	.get_num_blocks = ata_bd_get_num_blocks,
-	.sync_cache = ata_bd_sync_cache
+	.sync_cache = ata_bd_sync_cache,
+	.eject = ata_bd_eject
 };
 
@@ -758,4 +762,14 @@
 }
 
+/** Eject medium. */
+static errno_t ata_bd_eject(bd_srv_t *bd)
+{
+	ata_device_t *device = bd_srv_device(bd);
+
+	ata_msg_debug(device->chan, "ata_bd_eject()");
+	return ata_pcmd_start_stop_unit(device, ssf_pc_no_change |
+	    ssf_loej);
+}
+
 /** PIO data-in command protocol. */
 static errno_t ata_pio_data_in(ata_device_t *device, void *obuf, size_t obuf_size,
@@ -1088,9 +1102,66 @@
 }
 
-/** Issue packet command (i. e. write a command packet to the device).
- *
- * Only data-in commands are supported (e.g. inquiry, read).
+/** Issue packet command (i. e. write a command packet to the device)
+ * with no data transfer.
  *
  * @param device	Device
+ * @param cpkt		Command packet
+ * @param cpkt_size	Command packet size in bytes
+ *
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_cmd_packet_nondata(ata_device_t *device, const void *cpkt,
+    size_t cpkt_size)
+{
+	ata_channel_t *chan = device->chan;
+	uint8_t status;
+	uint8_t drv_head;
+	errno_t rc;
+
+	ata_msg_debug(chan, "ata_cmd_packet_nondata()");
+
+	fibril_mutex_lock(&chan->lock);
+
+	/* New value for Drive/Head register */
+	drv_head =
+	    ((disk_dev_idx(device) != 0) ? DHR_DRV : 0);
+
+	if (wait_status(chan, 0, ~SR_BSY, NULL, TIMEOUT_PROBE) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	ata_write_cmd_8(chan, REG_DRIVE_HEAD, drv_head);
+
+	if (wait_status(chan, 0, ~(SR_BSY | SR_DRQ), NULL, TIMEOUT_BSY) != EOK) {
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	ata_write_cmd_8(chan, REG_COMMAND, CMD_PACKET);
+
+	if (wait_status(chan, SR_DRQ, ~SR_BSY, &status, TIMEOUT_BSY) != EOK) {
+		if (chan->params.use_dma)
+			ata_dma_chan_teardown(device);
+		fibril_mutex_unlock(&chan->lock);
+		return EIO;
+	}
+
+	/* Write command packet. */
+	ata_write_data_16(chan, ((uint16_t *) cpkt), (cpkt_size + 1) / 2);
+
+	rc = ata_pio_nondata(device);
+
+	fibril_mutex_unlock(&chan->lock);
+
+	return rc;
+}
+
+/** Issue packet command (i. e. write a command packet to the device)
+ * performing data-in transfer.
+ *
+ * @param device	Device
+ * @param cpkt		Command packet
+ * @param cpkt_size	Command packet size in bytes
  * @param obuf		Buffer for storing data read from device
  * @param obuf_size	Size of obuf in bytes
@@ -1099,6 +1170,6 @@
  * @return EOK on success, EIO on error.
  */
-static errno_t ata_cmd_packet(ata_device_t *device, const void *cpkt, size_t cpkt_size,
-    void *obuf, size_t obuf_size, size_t *rcvd_size)
+static errno_t ata_cmd_packet_din(ata_device_t *device, const void *cpkt,
+    size_t cpkt_size, void *obuf, size_t obuf_size, size_t *rcvd_size)
 {
 	ata_channel_t *chan = device->chan;
@@ -1195,5 +1266,6 @@
 	cp->alloc_len = host2uint16_t_be(min(obuf_size, 0xff));
 
-	rc = ata_cmd_packet(device, cpb, sizeof(cpb), obuf, obuf_size, rcvd_size);
+	rc = ata_cmd_packet_din(device, cpb, sizeof(cpb), obuf, obuf_size,
+	    rcvd_size);
 	if (rc != EOK)
 		return rc;
@@ -1221,5 +1293,6 @@
 	cdb.op_code = SCSI_CMD_READ_CAPACITY_10;
 
-	rc = ata_cmd_packet(device, &cdb, sizeof(cdb), &data, sizeof(data), &rsize);
+	rc = ata_cmd_packet_din(device, &cdb, sizeof(cdb), &data, sizeof(data),
+	    &rsize);
 	if (rc != EOK)
 		return rc;
@@ -1262,5 +1335,5 @@
 	cp.xfer_len = host2uint32_t_be(cnt);
 
-	rc = ata_cmd_packet(device, &cp, sizeof(cp), obuf, obuf_size, NULL);
+	rc = ata_cmd_packet_din(device, &cp, sizeof(cp), obuf, obuf_size, NULL);
 	if (rc != EOK)
 		return rc;
@@ -1302,7 +1375,43 @@
 	cp->control = 0x40; /* 0x01 = multi-session mode (shifted to MSB) */
 
-	rc = ata_cmd_packet(device, cpb, sizeof(cpb), obuf, obuf_size, NULL);
+	rc = ata_cmd_packet_din(device, cpb, sizeof(cpb), obuf, obuf_size,
+	    NULL);
 	if (rc != EOK)
 		return rc;
+
+	return EOK;
+}
+
+/** Issue Start Stop Unit command.
+ *
+ * @param device	Device
+ * @param flags		Flags field of Start Stop Unit command (ssf_*)
+ * @return EOK on success, EIO on error.
+ */
+static errno_t ata_pcmd_start_stop_unit(ata_device_t *device, uint8_t flags)
+{
+	uint8_t cpb[12];
+	scsi_cdb_start_stop_unit_t *cp = (scsi_cdb_start_stop_unit_t *)cpb;
+	errno_t rc;
+
+	ata_msg_debug(device->chan, "ata_pcmd_start_stop_unit(device, 0x%x)",
+	    flags);
+
+	memset(cpb, 0, sizeof(cpb));
+
+	/*
+	 * For SFF 8020 compliance the command must be padded to 12 bytes.
+	 */
+	cp->op_code = SCSI_CMD_START_STOP_UNIT;
+	cp->immed = 0;
+	cp->flags = flags;
+	cp->control = 0;
+
+	rc = ata_cmd_packet_nondata(device, cpb, sizeof(cpb));
+	if (rc != EOK)
+		return rc;
+
+	ata_msg_debug(device->chan, "ata_pcmd_start_stop_unit(): "
+	    "ata_cmd_packet_nondata -> %d", rc);
 
 	return EOK;
Index: uspace/lib/device/include/bd.h
===================================================================
--- uspace/lib/device/include/bd.h	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/lib/device/include/bd.h	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2012 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -51,4 +51,5 @@
 extern errno_t bd_get_block_size(bd_t *, size_t *);
 extern errno_t bd_get_num_blocks(bd_t *, aoff64_t *);
+extern errno_t bd_eject(bd_t *);
 
 #endif
Index: uspace/lib/device/include/bd_srv.h
===================================================================
--- uspace/lib/device/include/bd_srv.h	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/lib/device/include/bd_srv.h	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2012 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -66,4 +66,5 @@
 	errno_t (*get_block_size)(bd_srv_t *, size_t *);
 	errno_t (*get_num_blocks)(bd_srv_t *, aoff64_t *);
+	errno_t (*eject)(bd_srv_t *);
 };
 
Index: uspace/lib/device/include/ipc/bd.h
===================================================================
--- uspace/lib/device/include/ipc/bd.h	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/lib/device/include/ipc/bd.h	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2009 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -44,5 +44,6 @@
 	BD_SYNC_CACHE,
 	BD_WRITE_BLOCKS,
-	BD_READ_TOC
+	BD_READ_TOC,
+	BD_EJECT
 } bd_request_t;
 
Index: uspace/lib/device/include/ipc/vol.h
===================================================================
--- uspace/lib/device/include/ipc/vol.h	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/lib/device/include/ipc/vol.h	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -54,4 +54,12 @@
 } vol_request_t;
 
+/** Volume eject flags */
+typedef enum {
+	/** No flags */
+	vef_none = 0x0,
+	/** Physically eject medium */
+	vef_physical = 0x1
+} vol_eject_flags_t;
+
 #endif
 
Index: uspace/lib/device/include/types/vol.h
===================================================================
--- uspace/lib/device/include/types/vol.h	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/lib/device/include/types/vol.h	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
Index: uspace/lib/device/include/vol.h
===================================================================
--- uspace/lib/device/include/vol.h	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/lib/device/include/vol.h	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -48,5 +48,5 @@
 extern errno_t vol_part_add(vol_t *, service_id_t);
 extern errno_t vol_part_info(vol_t *, service_id_t, vol_part_info_t *);
-extern errno_t vol_part_eject(vol_t *, service_id_t);
+extern errno_t vol_part_eject(vol_t *, service_id_t, vol_eject_flags_t);
 extern errno_t vol_part_empty(vol_t *, service_id_t);
 extern errno_t vol_part_insert(vol_t *, service_id_t);
Index: uspace/lib/device/src/bd.c
===================================================================
--- uspace/lib/device/src/bd.c	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/lib/device/src/bd.c	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2012 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -196,4 +196,14 @@
 }
 
+errno_t bd_eject(bd_t *bd)
+{
+	async_exch_t *exch = async_exchange_begin(bd->sess);
+
+	errno_t rc = async_req_0_0(exch, BD_EJECT);
+	async_exchange_end(exch);
+
+	return rc;
+}
+
 static void bd_cb_conn(ipc_call_t *icall, void *arg)
 {
Index: uspace/lib/device/src/bd_srv.c
===================================================================
--- uspace/lib/device/src/bd_srv.c	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/lib/device/src/bd_srv.c	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -206,4 +206,17 @@
 }
 
+static void bd_eject_srv(bd_srv_t *srv, ipc_call_t *call)
+{
+	errno_t rc;
+
+	if (srv->srvs->ops->eject == NULL) {
+		async_answer_0(call, ENOTSUP);
+		return;
+	}
+
+	rc = srv->srvs->ops->eject(srv);
+	async_answer_0(call, rc);
+}
+
 static bd_srv_t *bd_srv_create(bd_srvs_t *srvs)
 {
@@ -276,4 +289,7 @@
 			bd_get_num_blocks_srv(srv, &call);
 			break;
+		case BD_EJECT:
+			bd_eject_srv(srv, &call);
+			break;
 		default:
 			async_answer_0(&call, EINVAL);
Index: uspace/lib/device/src/vol.c
===================================================================
--- uspace/lib/device/src/vol.c	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/lib/device/src/vol.c	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -253,13 +253,14 @@
  * @param vol Volume service
  * @param sid Service ID of the partition
- * @return EOK on success or an error code
- */
-errno_t vol_part_eject(vol_t *vol, service_id_t sid)
-{
-	async_exch_t *exch;
-	errno_t retval;
-
-	exch = async_exchange_begin(vol->sess);
-	retval = async_req_1_0(exch, VOL_PART_EJECT, sid);
+ * @param flags Ejection flags
+ * @return EOK on success or an error code
+ */
+errno_t vol_part_eject(vol_t *vol, service_id_t sid, vol_eject_flags_t flags)
+{
+	async_exch_t *exch;
+	errno_t retval;
+
+	exch = async_exchange_begin(vol->sess);
+	retval = async_req_2_0(exch, VOL_PART_EJECT, sid, flags);
 	async_exchange_end(exch);
 
Index: uspace/lib/fdisk/src/fdisk.c
===================================================================
--- uspace/lib/fdisk/src/fdisk.c	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/lib/fdisk/src/fdisk.c	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2024 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -819,5 +819,5 @@
 	errno_t rc;
 
-	rc = vol_part_eject(part->dev->fdisk->vol, part->svc_id);
+	rc = vol_part_eject(part->dev->fdisk->vol, part->svc_id, vef_none);
 	if (rc != EOK)
 		return EIO;
Index: uspace/lib/scsi/include/scsi/sbc.h
===================================================================
--- uspace/lib/scsi/include/scsi/sbc.h	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/lib/scsi/include/scsi/sbc.h	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2011 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -50,4 +50,6 @@
 	SCSI_CMD_READ_CAPACITY_16	= 0x9e,
 
+	SCSI_CMD_START_STOP_UNIT	= 0x1b,
+
 	SCSI_CMD_SYNC_CACHE_10		= 0x35,
 	SCSI_CMD_SYNC_CACHE_16		= 0x91,
@@ -214,4 +216,30 @@
 } __attribute__((packed)) scsi_cdb_write_16_t;
 
+/** SCSI Start Stop Unit command */
+typedef struct {
+	/** Operation code (SCSI_CMD_START_STOP_UNIT) */
+	uint8_t op_code;
+	/** Immediate */
+	uint8_t immed;
+	/** Reserved */
+	uint8_t reserved_2;
+	/** Reserved */
+	uint8_t reserved_3;
+	/** Power Conditions | Reserved | LoEj | Start */
+	uint8_t flags;
+	/** Control */
+	uint8_t control;
+} __attribute__((packed)) scsi_cdb_start_stop_unit_t;
+
+/** Constants for values in sccsi_cdb_start_stop_unit_t.flags */
+enum scsi_start_stop_flags {
+	ssf_pc_no_change = 0x00,
+	ssf_pc_idle = 0x10,
+	ssf_pc_standby = 0x20,
+	ssf_pc_sleep = 0x50,
+	ssf_loej = 0x02,
+	ssf_start = 0x01
+};
+
 #endif
 
Index: uspace/srv/bd/vbd/disk.c
===================================================================
--- uspace/srv/bd/vbd/disk.c	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/srv/bd/vbd/disk.c	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2024 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -35,4 +35,5 @@
 
 #include <adt/list.h>
+#include <bd.h>
 #include <bd_srv.h>
 #include <block.h>
@@ -71,4 +72,5 @@
 static errno_t vbds_bd_get_block_size(bd_srv_t *, size_t *);
 static errno_t vbds_bd_get_num_blocks(bd_srv_t *, aoff64_t *);
+static errno_t vbds_bd_eject(bd_srv_t *);
 
 static errno_t vbds_bsa_translate(vbds_part_t *, aoff64_t, size_t, aoff64_t *);
@@ -93,5 +95,6 @@
 	.write_blocks = vbds_bd_write_blocks,
 	.get_block_size = vbds_bd_get_block_size,
-	.get_num_blocks = vbds_bd_get_num_blocks
+	.get_num_blocks = vbds_bd_get_num_blocks,
+	.eject = vbds_bd_eject
 };
 
@@ -1070,4 +1073,41 @@
 
 	return EOK;
+}
+
+static errno_t vbds_bd_eject(bd_srv_t *bd)
+{
+	vbds_part_t *part = bd_srv_part(bd);
+	async_sess_t *sess;
+	bd_t *bdc;
+	errno_t rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "vbds_bd_eject()");
+
+	fibril_rwlock_read_lock(&part->lock);
+
+	sess = loc_service_connect(part->disk->svc_id, INTERFACE_BLOCK, 0);
+	if (sess == NULL) {
+		log_msg(LOG_DEFAULT, LVL_WARN,
+		    "vbds_bd_eject() - failed connect");
+		fibril_rwlock_read_unlock(&part->lock);
+		return EIO;
+	}
+
+	rc = bd_open(sess, &bdc);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_WARN,
+		    "vbds_bd_eject() - failed open");
+		async_hangup(sess);
+		fibril_rwlock_read_unlock(&part->lock);
+		return EIO;
+	}
+
+	rc = bd_eject(bdc);
+
+	bd_close(bdc);
+	async_hangup(sess);
+
+	fibril_rwlock_read_unlock(&part->lock);
+	return rc;
 }
 
Index: uspace/srv/system/system.c
===================================================================
--- uspace/srv/system/system.c	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/srv/system/system.c	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -533,5 +533,5 @@
 
 	for (i = 0; i < nparts; i++) {
-		rc = vol_part_eject(vol, part_ids[i]);
+		rc = vol_part_eject(vol, part_ids[i], vef_none);
 		if (rc != EOK) {
 			log_msg(LOG_DEFAULT, LVL_ERROR, "Error ejecting "
Index: uspace/srv/volsrv/meson.build
===================================================================
--- uspace/srv/volsrv/meson.build	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/srv/volsrv/meson.build	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2015 Jiri Svoboda
+# Copyright (c) 2025 Jiri Svoboda
 # All rights reserved.
 #
@@ -27,5 +27,5 @@
 #
 
-deps = [ 'block', 'label', 'sif' ]
+deps = [ 'block', 'device', 'label', 'sif' ]
 
 src = files(
Index: uspace/srv/volsrv/part.c
===================================================================
--- uspace/srv/volsrv/part.c	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/srv/volsrv/part.c	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2024 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -36,4 +36,5 @@
 
 #include <adt/list.h>
+#include <bd.h>
 #include <errno.h>
 #include <fibril_synch.h>
@@ -642,5 +643,38 @@
 }
 
-errno_t vol_part_eject_part(vol_part_t *part)
+static errno_t vol_part_eject_device(service_id_t svcid)
+{
+	async_sess_t *sess;
+	errno_t rc;
+	bd_t *bd;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "vol_part_eject_device(%zu)",
+	    (size_t)svcid);
+
+	sess = loc_service_connect(svcid, INTERFACE_BLOCK, 0);
+	if (sess == NULL)
+		return EIO;
+
+	rc = bd_open(sess, &bd);
+	if (rc != EOK) {
+		async_hangup(sess);
+		return EIO;
+	}
+
+	rc = bd_eject(bd);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_WARN, "vol_part_eject_device(): "
+		    "eject fail");
+		bd_close(bd);
+		async_hangup(sess);
+		return EIO;
+	}
+
+	bd_close(bd);
+	async_hangup(sess);
+	return EOK;
+}
+
+errno_t vol_part_eject_part(vol_part_t *part, vol_eject_flags_t flags)
 {
 	int rc;
@@ -667,4 +701,12 @@
 			log_msg(LOG_DEFAULT, LVL_ERROR, "Failed deleting "
 			    "mount directory %s.", part->cur_mp);
+		}
+	}
+
+	if ((flags & vef_physical) != 0) {
+		rc = vol_part_eject_device(part->svc_id);
+		if (rc != EOK) {
+			log_msg(LOG_DEFAULT, LVL_ERROR, "Failed physically "
+			    "ejecting device %s.", part->svc_name);
 		}
 	}
@@ -819,5 +861,5 @@
 
 	if (part->cur_mp != NULL) {
-		rc = vol_part_eject_part(part);
+		rc = vol_part_eject_part(part, vef_none);
 		if (rc != EOK)
 			return rc;
Index: uspace/srv/volsrv/part.h
===================================================================
--- uspace/srv/volsrv/part.h	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/srv/volsrv/part.h	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -55,5 +55,5 @@
     vol_part_t **);
 extern void vol_part_del_ref(vol_part_t *);
-extern errno_t vol_part_eject_part(vol_part_t *);
+extern errno_t vol_part_eject_part(vol_part_t *, vol_eject_flags_t);
 extern errno_t vol_part_empty_part(vol_part_t *);
 extern errno_t vol_part_insert_part(vol_part_t *);
Index: uspace/srv/volsrv/volsrv.c
===================================================================
--- uspace/srv/volsrv/volsrv.c	(revision d231a544e788294c23491fd976ae0ea08a44237e)
+++ uspace/srv/volsrv/volsrv.c	(revision 4285f384e4a854f4435e5f334456da415d9e1134)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2024 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -205,9 +205,13 @@
 {
 	service_id_t sid;
+	vol_eject_flags_t flags;
 	vol_part_t *part;
 	errno_t rc;
 
 	sid = ipc_get_arg1(icall);
-	log_msg(LOG_DEFAULT, LVL_DEBUG, "vol_part_eject_srv(%zu)", sid);
+	flags = ipc_get_arg2(icall);
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "vol_part_eject_srv(%zu, %x)",
+	    sid, flags);
 
 	rc = vol_part_find_by_id_ref(parts, sid, &part);
@@ -217,5 +221,5 @@
 	}
 
-	rc = vol_part_eject_part(part);
+	rc = vol_part_eject_part(part, flags);
 	if (rc != EOK) {
 		async_answer_0(icall, EIO);
