diff --git a/fs/cachefiles/content-map.c b/fs/cachefiles/content-map.c
index 68fcab313361..4ff8c645817c 100644
--- a/fs/cachefiles/content-map.c
+++ b/fs/cachefiles/content-map.c
@@ -10,6 +10,7 @@
 #include <linux/file.h>
 #include <linux/swap.h>
 #include <linux/xattr.h>
+#include <linux/netfs.h>
 #include "internal.h"
 
 static const char cachefiles_xattr_content_map[] =
@@ -40,6 +41,169 @@ static size_t cachefiles_map_size(loff_t i_size)
 	return map_size;
 }
 
+static bool cachefiles_granule_is_present(struct cachefiles_object *object,
+					  long granule)
+{
+	bool res;
+
+	if (granule / 8 >= object->content_map_size)
+		return false;
+	read_lock_bh(&object->content_map_lock);
+	res = test_bit_le(granule, object->content_map);
+	read_unlock_bh(&object->content_map_lock);
+	return res;
+}
+
+static long cachefiles_find_next_granule(struct cachefiles_object *object,
+					 long start_from, long *_limit)
+{
+	long result, limit;
+
+	read_lock_bh(&object->content_map_lock);
+	*_limit = limit = object->content_map_size * 8;
+	result = find_next_bit_le(object->content_map, limit, start_from);
+	read_unlock_bh(&object->content_map_lock);
+	return result;
+}
+
+static long cachefiles_find_next_hole(struct cachefiles_object *object,
+				      long start_from, long *_limit)
+{
+	long result, limit;
+
+	read_lock_bh(&object->content_map_lock);
+	*_limit = limit = object->content_map_size * 8;
+	result = find_next_zero_bit_le(object->content_map, limit, start_from);
+	read_unlock_bh(&object->content_map_lock);
+	return result;
+}
+
+/*
+ * Expand a readahead proposal from the VM to align with cache limits
+ * and granularity.
+ */
+void cachefiles_expand_readahead(struct fscache_op_resources *opr,
+				 loff_t *_start, size_t *_len, loff_t i_size)
+{
+	loff_t start = *_start, delta;
+	size_t len = *_len;
+
+	if (start >= CACHEFILES_SIZE_LIMIT)
+		return;
+
+	if (len > CACHEFILES_SIZE_LIMIT - start)
+		len = *_len = CACHEFILES_SIZE_LIMIT - start;
+
+	delta = start & (CACHEFILES_GRAN_SIZE - 1);
+	if (start - delta < i_size) {
+		start -= delta;
+		len = round_up(len + delta, CACHEFILES_GRAN_SIZE);
+		if (len > i_size - start) {
+			_debug("overshot eof");
+			len = i_size - start;
+		}
+	}
+
+	*_start = start;
+	*_len = len;
+}
+
+/*
+ * Prepare a I/O subrequest of a read request.  We're asked to retrieve all the
+ * remaining data in the read request, but we are allowed to shrink that and we
+ * set flags to indicate where we want it read from.
+ */
+enum netfs_read_source cachefiles_prepare_read(struct netfs_read_subrequest *subreq,
+					       loff_t i_size)
+{
+	struct cachefiles_object *object =
+		container_of(subreq->rreq->cache_resources.object,
+			     struct cachefiles_object, fscache);
+	loff_t start = subreq->start, len = subreq->len, boundary;
+	long granule, next, limit;
+
+	_enter("%llx,%llx", start, len);
+
+	if (start >= CACHEFILES_SIZE_LIMIT) {
+		if (start >= i_size)
+			goto zero_pages_nocache;
+		goto on_server_nocache;
+	}
+	if (len > CACHEFILES_SIZE_LIMIT - start)
+		len = CACHEFILES_SIZE_LIMIT - start;
+
+	granule = start / CACHEFILES_GRAN_SIZE;
+	if (granule / 8 >= object->content_map_size) {
+		cachefiles_expand_content_map(object, i_size);
+		if (granule / 8 >= object->content_map_size)
+			goto maybe_on_server_nocache;
+	}
+
+	if (start >= i_size)
+		goto zero_pages;
+
+	if (cachefiles_granule_is_present(object, granule)) {
+		/* The start of the request is present in the cache - restrict
+		 * the length to what's available.
+		 */
+		if (start & (CACHEFILES_DIO_BLOCK_SIZE - 1)) {
+			/* We should never see DIO-unaligned requests here. */
+			WARN_ON_ONCE(1);
+			len &= CACHEFILES_DIO_BLOCK_SIZE - 1;
+			goto maybe_on_server;
+		}
+
+		next = cachefiles_find_next_hole(object, granule + 1, &limit);
+		_debug("present %lx %lx", granule, limit);
+		if (granule >= limit)
+			goto maybe_on_server;
+		boundary = next * CACHEFILES_GRAN_SIZE;
+		if (len > boundary - start)
+			len = boundary - start;
+		goto in_cache;
+	} else {
+		/* The start of the request is not present in the cache -
+		 * restrict the length to the size of the hole.
+		 */
+		next = cachefiles_find_next_granule(object, granule + 1, &limit);
+		_debug("hole %lx %lx", granule, limit);
+		if (granule >= limit)
+			goto maybe_on_server;
+		boundary = next * CACHEFILES_GRAN_SIZE;
+		if (len > boundary - start)
+			len = boundary - start;
+		goto maybe_on_server;
+	}
+
+maybe_on_server:
+	/* If the start of the request is beyond the original EOF of the file
+	 * on the server then it's not going to be found on the server.
+	 */
+	if (start >= object->fscache.cookie->zero_point)
+		goto zero_pages;
+	goto on_server;
+maybe_on_server_nocache:
+	if (start >= object->fscache.cookie->zero_point)
+		goto zero_pages_nocache;
+	goto on_server_nocache;
+on_server:
+	__set_bit(NETFS_SREQ_WRITE_TO_CACHE, &subreq->flags);
+on_server_nocache:
+	subreq->len = len;
+	_leave(" = down %llx", len);
+	return NETFS_DOWNLOAD_FROM_SERVER;
+zero_pages:
+	__set_bit(NETFS_SREQ_WRITE_TO_CACHE, &subreq->flags);
+zero_pages_nocache:
+	subreq->len = len;
+	_leave(" = zero %llx", len);
+	return NETFS_FILL_WITH_ZEROES;
+in_cache:
+	subreq->len = len;
+	_leave(" = read %llx", len);
+	return NETFS_READ_FROM_CACHE;
+}
+
 /*
  * Mark the content map to indicate stored granule.
  */
diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c
index 1d06cf72eeb0..a20917cb4667 100644
--- a/fs/cachefiles/interface.c
+++ b/fs/cachefiles/interface.c
@@ -462,6 +462,8 @@ static const struct fscache_op_ops cachefiles_io_ops = {
 	.end_operation		= __fscache_end_operation,
 	.read			= cachefiles_read,
 	.write			= cachefiles_write,
+	.expand_readahead	= cachefiles_expand_readahead,
+	.prepare_read		= cachefiles_prepare_read,
 };
 
 static void cachefiles_begin_operation(struct fscache_op_resources *opr)
diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h
index e065550a4bc0..e9f45d5053b1 100644
--- a/fs/cachefiles/internal.h
+++ b/fs/cachefiles/internal.h
@@ -125,6 +125,10 @@ extern void cachefiles_daemon_unbind(struct cachefiles_cache *cache);
 /*
  * content-map.c
  */
+extern void cachefiles_expand_readahead(struct fscache_op_resources *opr,
+					loff_t *_start, size_t *_len, loff_t i_size);
+extern enum netfs_read_source cachefiles_prepare_read(struct netfs_read_subrequest *subreq,
+						      loff_t i_size);
 extern void cachefiles_mark_content_map(struct cachefiles_object *object,
 					loff_t start, loff_t len);
 extern void cachefiles_expand_content_map(struct cachefiles_object *object, loff_t size);
