diff --git a/cmd/fs.c b/cmd/fs.c
index 944d15b4520..e3fad83d68c 100644
--- a/cmd/fs.c
+++ b/cmd/fs.c
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- * VFS-based filesystem commands - mount, umount, ls, load, size
+ * VFS-based filesystem commands - mount, umount, ls, load, save, size
  *
  * These replace the legacy commands in cmd/fs_legacy.c with versions that
  * use absolute paths through the virtual filesystem layer.
@@ -213,6 +213,126 @@ U_BOOT_CMD_COMPLETE(
 	vfs_cmd_complete
 );
 
+#define COPY_BUF_SIZE	0x1000
+
+int do_cp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
+{
+	struct file_uc_priv *uc_priv;
+	struct udevice *src, *dst;
+	char buf[COPY_BUF_SIZE];
+	loff_t remaining, pos;
+	long total = 0;
+	int ret;
+
+	if (argc < 3)
+		return CMD_RET_USAGE;
+
+	ret = vfs_open_file(argv[1], DIR_O_RDONLY, &src);
+	if (ret) {
+		printf("Source '%s' not found: %dE\n", argv[1], ret);
+		return CMD_RET_FAILURE;
+	}
+
+	ret = vfs_open_file(argv[2], DIR_O_WRONLY, &dst);
+	if (ret) {
+		printf("Dest '%s' failed: %dE\n", argv[2], ret);
+		return CMD_RET_FAILURE;
+	}
+
+	uc_priv = dev_get_uclass_priv(src);
+	remaining = uc_priv->size;
+	pos = 0;
+
+	while (remaining > 0) {
+		long chunk = min((loff_t)COPY_BUF_SIZE, remaining);
+		long nread, nwritten;
+
+		nread = file_read_at(src, buf, pos, chunk);
+		if (nread < 0) {
+			printf("Read failed: %ldE\n", nread);
+			return CMD_RET_FAILURE;
+		}
+		if (!nread)
+			break;
+
+		nwritten = file_write_at(dst, buf, pos, nread);
+		if (nwritten < 0) {
+			printf("Write failed: %ldE\n", nwritten);
+			return CMD_RET_FAILURE;
+		}
+
+		pos += nread;
+		remaining -= nread;
+		total += nwritten;
+	}
+
+	printf("%ld bytes copied\n", total);
+
+	return CMD_RET_SUCCESS;
+}
+
+static const char *fs_type_name(unsigned int type)
+{
+	switch (type) {
+	case FS_DT_DIR:
+		return "directory";
+	case FS_DT_REG:
+		return "regular file";
+	case FS_DT_LNK:
+		return "symbolic link";
+	default:
+		return "unknown";
+	}
+}
+
+static int do_stat(struct cmd_tbl *cmdtp, int flag, int argc,
+			   char *const argv[])
+{
+	struct fs_dirent dent;
+	int ret;
+
+	if (argc < 2)
+		return CMD_RET_USAGE;
+
+	ret = vfs_stat(argv[1], &dent);
+	if (ret) {
+		printf("Error: %dE\n", ret);
+		return CMD_RET_FAILURE;
+	}
+
+	printf("  File: %s\n", dent.name);
+	printf("  Size: %llu\n", dent.size);
+	printf("  Type: %s\n", fs_type_name(dent.type));
+	if (dent.change_time.tm_year) {
+		printf("Modify: %04d-%02d-%02d %02d:%02d:%02d\n",
+		       dent.change_time.tm_year, dent.change_time.tm_mon,
+		       dent.change_time.tm_mday, dent.change_time.tm_hour,
+		       dent.change_time.tm_min, dent.change_time.tm_sec);
+	}
+	if (dent.access_time.tm_year) {
+		printf("Access: %04d-%02d-%02d %02d:%02d:%02d\n",
+		       dent.access_time.tm_year, dent.access_time.tm_mon,
+		       dent.access_time.tm_mday, dent.access_time.tm_hour,
+		       dent.access_time.tm_min, dent.access_time.tm_sec);
+	}
+	if (dent.create_time.tm_year) {
+		printf(" Birth: %04d-%02d-%02d %02d:%02d:%02d\n",
+		       dent.create_time.tm_year, dent.create_time.tm_mon,
+		       dent.create_time.tm_mday, dent.create_time.tm_hour,
+		       dent.create_time.tm_min, dent.create_time.tm_sec);
+	}
+
+	return CMD_RET_SUCCESS;
+}
+
+U_BOOT_CMD_COMPLETE(
+	stat,	2,	1,	do_stat,
+	"display file status",
+	"<path>\n"
+	"    - Show type, size and timestamps of a file or directory",
+	vfs_cmd_complete
+);
+
 static int do_size(struct cmd_tbl *cmdtp, int flag, int argc,
 		   char *const argv[])
 {
@@ -233,12 +353,13 @@ static int do_size(struct cmd_tbl *cmdtp, int flag, int argc,
 	return CMD_RET_SUCCESS;
 }
 
-U_BOOT_CMD(
+U_BOOT_CMD_COMPLETE(
 	size,	2,	0,	do_size,
 	"determine a file's size",
 	"<path>\n"
 	"    - Find file at 'path' in the VFS, determine its size,\n"
-	"      and store in the 'filesize' variable."
+	"      and store in the 'filesize' variable.",
+	vfs_cmd_complete
 );
 
 static int do_vfs_load(struct cmd_tbl *cmdtp, int flag, int argc,
@@ -330,3 +451,51 @@ U_BOOT_CMD_COMPLETE(
 	"    - Legacy: load from block device interface",
 	vfs_cmd_complete
 );
+
+static int do_save(struct cmd_tbl *cmdtp, int flag, int argc,
+		   char *const argv[])
+{
+	struct udevice *fil;
+	long bytes, written;
+	unsigned long addr;
+	loff_t pos = 0;
+	void *buf;
+	int ret;
+
+	if (argc < 4)
+		return CMD_RET_USAGE;
+
+	addr = hextoul(argv[1], NULL);
+	bytes = hextoul(argv[3], NULL);
+	if (argc >= 5)
+		pos = hextoul(argv[4], NULL);
+
+	ret = vfs_open_file(argv[2], DIR_O_WRONLY, &fil);
+	if (ret) {
+		printf("Error: %dE\n", ret);
+		return CMD_RET_FAILURE;
+	}
+
+	buf = map_sysmem(addr, bytes);
+	written = file_write_at(fil, buf, pos, bytes);
+	unmap_sysmem(buf);
+
+	if (written < 0) {
+		printf("Write failed: %ldE\n", written);
+		return CMD_RET_FAILURE;
+	}
+
+	printf("%ld bytes written\n", written);
+
+	return CMD_RET_SUCCESS;
+}
+
+U_BOOT_CMD_COMPLETE(
+	save,	5,	0,	do_save,
+	"save memory to a file",
+	"<addr> <path> <bytes> [pos]\n"
+	"    - Save 'bytes' from address 'addr' to 'path' in the VFS.\n"
+	"      'pos' gives the file byte position to start writing to.\n"
+	"      If 'pos' is 0 or omitted, the file is written from the start.",
+	vfs_cmd_complete
+);
diff --git a/cmd/vfs.c b/cmd/vfs.c
index 5779f3f098d..33407da4594 100644
--- a/cmd/vfs.c
+++ b/cmd/vfs.c
@@ -104,11 +104,14 @@ static int do_fs_ls(struct cmd_tbl *cmdtp, int flag, int argc,
 
 U_BOOT_LONGHELP(fs,
 	"mount [<dev> <mountpoint>]                   - list or create mounts\n"
-	"fs mount -t <type> <iface> <dev:part> <path>  - mount from block device\n"
+	"fs mount <iface> <dev:part> <path>             - auto-detect and mount\n"
+	"fs mount -t <type> <iface> <dev:part> <path>  - mount specific type\n"
 	"fs umount <mountpoint>                        - unmount a filesystem\n"
-	"fs ls [<path>]                                - list directory (default /)");
+	"fs ls [<path>]                                - list directory (default /)\n"
+	"fs cp <source> <dest>                         - copy a file");
 
 U_BOOT_CMD_WITH_SUBCMDS(fs, "Filesystem operations", fs_help_text,
 	U_BOOT_SUBCMD_MKENT(mount, 6, 1, do_fs_mount),
 	U_BOOT_SUBCMD_MKENT(umount, 2, 1, do_fs_umount),
-	U_BOOT_SUBCMD_MKENT(ls, 2, 1, do_fs_ls));
+	U_BOOT_SUBCMD_MKENT(ls, 2, 1, do_fs_ls),
+	U_BOOT_SUBCMD_MKENT(cp, 3, 0, do_cp));
diff --git a/include/vfs.h b/include/vfs.h
index 7c31ded7796..04cbd5d0ca9 100644
--- a/include/vfs.h
+++ b/include/vfs.h
@@ -292,6 +292,14 @@ int fs_mount_blkdev(const char *type, struct blk_desc *desc, int part_num,
  */
 void vfs_print_mounts(void);
 
+/**
+ * do_cp() - Copy a file within the VFS
+ *
+ * Implements the 'fs cp' subcommand. Defined in cmd/fs.c, declared here
+ * so that cmd/vfs.c can reference it.
+ */
+int do_cp(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]);
+
 #ifdef CONFIG_AUTO_COMPLETE
 /**
  * vfs_complete() - Complete a partial VFS path
diff --git a/test/dm/fs.c b/test/dm/fs.c
index 0ef52ca0ee4..8bba5263546 100644
--- a/test/dm/fs.c
+++ b/test/dm/fs.c
@@ -14,6 +14,7 @@
 #include <os.h>
 #include <vfs.h>
 #include <dm/test.h>
+#include <dm/uclass-internal.h>
 #include <test/ut.h>
 
 #define READ_SIZE	0x20
@@ -232,6 +233,15 @@ static int dm_test_vfs_complete(struct unit_test_state *uts)
 	/* Non-matching prefix should return 0 */
 	ut_asserteq(0, vfs_complete(buf, "/z", 16, cmdv));
 
+	/* Create a known file to complete against */
+	memcpy(map_sysmem(0x10000, 5), "hello", 5);
+	ut_assertok(run_command("save 10000 /host/.comp_test 5", 0));
+	console_record_reset_enable();
+
+	/* Complete inside mount */
+	ut_assert(vfs_complete(buf, "/host/.comp_", 16, cmdv) >= 1);
+	ut_asserteq_str(".comp_test", cmdv[0]);
+
 	/* Complete with empty prefix shows entries */
 	ut_assert(vfs_complete(buf, "/host/", 16, cmdv) >= 1);
 
@@ -460,4 +470,186 @@ static int dm_test_vfs_cwd(struct unit_test_state *uts)
 }
 DM_TEST(dm_test_vfs_cwd, UTF_SCAN_FDT);
 
+/* Test cd and pwd commands */
+static int dm_test_vfs_cd(struct unit_test_state *uts)
+{
+	ut_assertok(vfs_init());
+
+	ut_assertok(run_command("mount hostfs /host", 0));
+	ut_assert_console_end();
+
+	/* Default cwd is root */
+	ut_assertok(run_command("pwd", 0));
+	ut_assert_nextline("/");
+	ut_assert_console_end();
+
+	/* cd to an absolute path */
+	ut_assertok(run_command("cd /host", 0));
+	ut_assert_console_end();
+	ut_assertok(run_command("pwd", 0));
+	ut_assert_nextline("/host");
+	ut_assert_console_end();
+
+	/* ls without a path should list cwd */
+	ut_assertok(run_command("ls", 0));
+	ut_assert_skip_to_linen("DIR ");
+	console_record_reset_enable();
+
+	/* cd back to root */
+	ut_assertok(run_command("cd /", 0));
+	ut_assert_console_end();
+	ut_assertok(run_command("pwd", 0));
+	ut_assert_nextline("/");
+	ut_assert_console_end();
+
+	ut_assertok(run_command("umount /host", 0));
+	ut_assert_console_end();
+
+	return 0;
+}
+DM_TEST(dm_test_vfs_cd, UTF_SCAN_FDT);
+
+/* Test multi-component path resolution (files in subdirectories) */
+static int dm_test_vfs_path(struct unit_test_state *uts)
+{
+	ut_assertok(vfs_init());
+
+	ut_assertok(run_command("mount hostfs /host", 0));
+	ut_assert_console_end();
+
+	/* stat a file in a subdirectory */
+	ut_assertok(run_command("stat /host/cmd/vfs.c", 0));
+	ut_assert_nextline("  File: vfs.c");
+	ut_assert_nextlinen("  Size: ");
+	ut_assert_nextline("  Type: regular file");
+	console_record_reset_enable();
+
+	/* load from a subdirectory */
+	ut_assertok(run_command("load 1000000 /host/cmd/vfs.c", 0));
+	ut_assert_nextlinen("%s", "");
+	ut_assert_console_end();
+
+	/* ls a nested subdirectory */
+	ut_assertok(run_command("ls /host/arch", 0));
+	ut_assert_skip_to_linen("DIR ");
+	console_record_reset_enable();
+
+	ut_assertok(run_command("umount /host", 0));
+	ut_assert_console_end();
+
+	return 0;
+}
+DM_TEST(dm_test_vfs_path, UTF_SCAN_FDT);
+
+/* Test the cp command via VFS */
+static int dm_test_vfs_cp(struct unit_test_state *uts)
+{
+	char buf[32];
+
+	ut_assertok(vfs_init());
+	ut_assertok(run_command("mount hostfs /host", 0));
+	ut_assert_console_end();
+
+	/* Write a source file */
+	memcpy(map_sysmem(0x1000, 5), "hello", 5);
+	ut_assertok(run_command("save 1000 /host/.cp_src 5", 0));
+	ut_assert_nextline("5 bytes written");
+	ut_assert_console_end();
+
+	/* Copy it */
+	ut_assertok(run_command("fs cp /host/.cp_src /host/.cp_dst", 0));
+	ut_assert_nextline("5 bytes copied");
+	ut_assert_console_end();
+
+	/* Read back the copy and verify */
+	memset(map_sysmem(0x2000, 8), 0, 8);
+	ut_assertok(run_command("load 2000 /host/.cp_dst", 0));
+	ut_assert_nextline("5 bytes read");
+	ut_assert_console_end();
+
+	memcpy(buf, map_sysmem(0x2000, 5), 5);
+	buf[5] = '\0';
+	ut_asserteq_str("hello", buf);
+
+	os_unlink(".cp_src");
+	os_unlink(".cp_dst");
+
+	ut_assertok(run_command("umount /host", 0));
+	ut_assert_console_end();
+
+	return 0;
+}
+DM_TEST(dm_test_vfs_cp, UTF_SCAN_FDT);
+
+/* Test the stat command via VFS */
+static int dm_test_vfs_stat(struct unit_test_state *uts)
+{
+	ut_assertok(vfs_init());
+
+	ut_assertok(run_command("mount hostfs /host", 0));
+	ut_assert_console_end();
+
+	/* stat a regular file */
+	ut_assertok(run_command("stat /host/README", 0));
+	ut_assert_nextline("  File: README");
+	ut_assert_nextlinen("  Size: ");
+	ut_assert_nextline("  Type: regular file");
+	console_record_reset_enable();
+
+	/* stat a directory */
+	ut_assertok(run_command("stat /host/cmd", 0));
+	ut_assert_nextline("  File: cmd");
+	ut_assert_nextlinen("  Size: ");
+	ut_assert_nextline("  Type: directory");
+	console_record_reset_enable();
+
+	/* stat non-existent file */
+	ut_asserteq(1, run_command("stat /host/does-not-exist", 0));
+	console_record_reset_enable();
+
+	ut_assertok(run_command("umount /host", 0));
+	ut_assert_console_end();
+
+	return 0;
+}
+DM_TEST(dm_test_vfs_stat, UTF_SCAN_FDT);
+
+/* Test save and load round-trip via VFS */
+static int dm_test_vfs_save(struct unit_test_state *uts)
+{
+	char buf[32];
+
+	ut_assertok(vfs_init());
+
+	/* Mount hostfs */
+	ut_assertok(run_command("mount hostfs /host", 0));
+	ut_assert_console_end();
+
+	/* Write a known pattern to a temp file */
+	memset(map_sysmem(0x1000, 16), 0, 16);
+	memcpy(map_sysmem(0x1000, 11), "hello world", 11);
+	ut_assertok(run_command("save 1000 /host/.vfs_test_tmp 0xb", 0));
+	ut_assert_nextline("11 bytes written");
+	ut_assert_console_end();
+
+	/* Read it back and verify */
+	memset(map_sysmem(0x2000, 16), 0, 16);
+	ut_assertok(run_command("load 2000 /host/.vfs_test_tmp", 0));
+	ut_assert_nextline("11 bytes read");
+	ut_assert_console_end();
+
+	memcpy(buf, map_sysmem(0x2000, 11), 11);
+	buf[11] = '\0';
+	ut_asserteq_str("hello world", buf);
+
+	/* Clean up the temp file */
+	os_unlink(".vfs_test_tmp");
+
+	ut_assertok(run_command("umount /host", 0));
+	ut_assert_console_end();
+
+	return 0;
+}
+DM_TEST(dm_test_vfs_save, UTF_SCAN_FDT);
+
 #endif
