AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/27 16:58 flow patch-triage
7m Args:
null

Results:
{
  "EnableConfigs": [
    "VIRTBLK_CRYPTO_VIRTUALIZATION",
    "BLK_CRYPTO_PROXY",
    "QCOM_ICE_SLOTS",
    "QCOM_CRYPTO_VIRT",
    "BLK_INLINE_ENCRYPTION"
  ],
  "FocusSymbols": [
    "virtblk_init_crypto",
    "virtblk_get_crypto_modes",
    "virtblk_init_inline_crypto",
    "virtblk_crypto_keyslot_program",
    "bcp_ioctl",
    "qcom_ice_slots_probe",
    "ufs_qcom_ice_parse_slot_table"
  ],
  "KMSANReasoning": "The patch series introduces a new proxy driver `blk-crypto-proxy` for inline encryption virtualization, a virtio-blk extension for inline encryption, and a Qualcomm-specific crypto virtualization driver.\n\nA thorough review of the data structures exchanged with user space and hardware reveals no uninitialized memory risks:\n1. The UAPI structures (`struct bcp_get_crypto_caps_arg`, `struct bcp_bind_context_arg`, and `struct bcp_submit_io_by_vslot_arg`) are carefully designed with explicit `__aligned_u64` fields to ensure identical layout between 32-bit and 64-bit architectures and to avoid any implicit compiler padding. All fields are fully initialized before being copied to user space.\n2. The virtio structures (`struct virtio_blk_crypto_msg` and the extended `virtio_blk_outhdr`) have no padding and are fully explicitly initialized in `virtblk_setup_cmd` before being sent to the virtio device.\n3. The `virtblk_cmodes` buffer used in `virtblk_get_crypto_modes` is allocated with `kzalloc` before being mapped to the device, preventing any uninitialized memory from being sent or read.\n4. Internal structures like `struct bio_crypt_ctx` and `struct blk_crypto_slot` are fully initialized when allocated (e.g., via `bio_crypt_set_ctx_by_slot`).\n5. The I/O data itself is handled via direct I/O (`bio_iov_iter_get_pages`), which pins user pages directly for DMA, avoiding intermediate kernel buffers that could leak uninitialized memory.\n\nSince all structures are fully initialized and contain no padding, there is no risk of uninitialized memory being used in control flow or leaked to user space/hardware. Any potential bugs introduced by these changes (e.g., out-of-bounds accesses, use-after-free, logic errors) would be caught by standard KASAN and other bug detectors. A dedicated KMSAN session is not justified.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch introduces inline encryption support for virtio-blk guests and a host-side proxy device. While the host-side proxy (/dev/blk-crypto-proxy) is mostly unreachable because it relies on hypervisor ops that are not registered in-tree, and the guest-side I/O path is unreachable because key programming requires Qualcomm TrustZone SMC calls that fail in standard QEMU, the virtio-blk device probe path is fully reachable. When a virtio-blk device with VIRTIO_BLK_F_INLINE_ENCRYPTION is probed, the driver reads the virtio config space and sends a VIRTIO_BLK_T_GET_CRYPTO_MODES request to initialize the crypto profile. This probe path can be fuzzed by syzkaller's virtio fuzzer. Additionally, the patch adds DT parsing logic for Qualcomm ICE slot partitioning, which is reachable if the corresponding DT nodes are injected.",
  "WorthFuzzing": true
}

1/1 2026/08/27 16:58 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit b864a4f707165a5c167ea55c41d670ae48e4993e\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Aug 27 16:58:51 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,crypto-virt.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,crypto-virt.yaml\nnew file mode 100644\nindex 0000000000000..5eb204f297627\n--- /dev/null\n+++ b/Documentation/devicetree/bindings/soc/qcom/qcom,crypto-virt.yaml\n@@ -0,0 +1,39 @@\n+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)\n+%YAML 1.2\n+---\n+$id: http://devicetree.org/schemas/soc/qcom/qcom,crypto-virt.yaml#\n+$schema: http://devicetree.org/meta-schemas/core.yaml#\n+\n+title: QCOM GVM Crypto Virt driver\n+\n+description: |\n+  GVM-side hardware-wrapped-key SCM operations backing virtio_blk's inline\n+  crypto layer. The host does not report a wrapped key size over virtio, so\n+  it is provided here instead.\n+\n+maintainers:\n+  - Linlin Zhang \u003clinlin.zhang@oss.qualcomm.com\u003e\n+\n+properties:\n+  compatible:\n+    const: qcom,crypto-virt\n+\n+  qcom,wrapped-key-size:\n+    description: |\n+      Size, in bytes, of the hardware-wrapped key generated/prepared/imported\n+      via SCM calls.\n+    $ref: /schemas/types.yaml#/definitions/uint32\n+    minimum: 64\n+    maximum: 128\n+\n+required:\n+  - compatible\n+\n+additionalProperties: false\n+\n+examples:\n+  - |\n+    crypto_virt {\n+        compatible = \"qcom,crypto-virt\";\n+        qcom,wrapped-key-size = \u003c100\u003e;\n+    };\ndiff --git a/block/blk-crypto-internal.h b/block/blk-crypto-internal.h\nindex 2c7a0446572aa..32c720aabba7f 100644\n--- a/block/blk-crypto-internal.h\n+++ b/block/blk-crypto-internal.h\n@@ -26,9 +26,6 @@ int blk_crypto_sysfs_register(struct gendisk *disk);\n \n void blk_crypto_sysfs_unregister(struct gendisk *disk);\n \n-void bio_crypt_dun_increment(u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE],\n-\t\t\t     unsigned int inc);\n-\n bool bio_crypt_rq_ctx_compatible(struct request *rq, struct bio *bio);\n \n bool bio_crypt_ctx_mergeable(struct bio_crypt_ctx *bc1, unsigned int bc1_bytes,\n@@ -176,7 +173,7 @@ static inline void bio_crypt_do_front_merge(struct request *rq,\n blk_status_t __blk_crypto_rq_get_keyslot(struct request *rq);\n static inline blk_status_t blk_crypto_rq_get_keyslot(struct request *rq)\n {\n-\tif (blk_crypto_rq_is_encrypted(rq))\n+\tif (blk_crypto_rq_is_encrypted(rq) \u0026\u0026 rq-\u003ecrypt_ctx-\u003ebc_key)\n \t\treturn __blk_crypto_rq_get_keyslot(rq);\n \treturn BLK_STS_OK;\n }\ndiff --git a/block/blk-crypto-profile.c b/block/blk-crypto-profile.c\nindex 53126c091b0be..64202d64a0182 100644\n--- a/block/blk-crypto-profile.c\n+++ b/block/blk-crypto-profile.c\n@@ -218,14 +218,15 @@ blk_crypto_find_and_grab_keyslot(struct blk_crypto_profile *profile,\n }\n \n /**\n- * blk_crypto_keyslot_index() - Get the index of a keyslot\n+ * blk_crypto_keyslot_index() - Get the physical index of a keyslot\n  * @slot: a keyslot that blk_crypto_get_keyslot() returned\n  *\n- * Return: the 0-based index of the keyslot within the device's keyslots.\n+ * Return: the physical ICE keyslot index, i.e. the 0-based position of @slot\n+ * within the profile's keyslot array plus @slot-\u003eprofile-\u003eslot_offset.\n  */\n unsigned int blk_crypto_keyslot_index(struct blk_crypto_keyslot *slot)\n {\n-\treturn slot - slot-\u003eprofile-\u003eslots;\n+\treturn (slot - slot-\u003eprofile-\u003eslots) + slot-\u003eprofile-\u003eslot_offset;\n }\n EXPORT_SYMBOL_GPL(blk_crypto_keyslot_index);\n \ndiff --git a/block/blk-crypto.c b/block/blk-crypto.c\nindex bc3a9f59574b1..2212d06d3c112 100644\n--- a/block/blk-crypto.c\n+++ b/block/blk-crypto.c\n@@ -113,11 +113,31 @@ void bio_crypt_set_ctx(struct bio *bio, const struct blk_crypto_key *key,\n \n \tbc-\u003ebc_key = key;\n \tmemcpy(bc-\u003ebc_dun, dun, sizeof(bc-\u003ebc_dun));\n+\tmemset(\u0026bc-\u003ebc_slot, 0, sizeof(bc-\u003ebc_slot));\n \n \tbio-\u003ebi_crypt_context = bc;\n }\n EXPORT_SYMBOL_GPL(bio_crypt_set_ctx);\n \n+void bio_crypt_set_ctx_by_slot(struct bio *bio,\n+\t\t\t       const struct blk_crypto_slot *slot,\n+\t\t\t       const u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE],\n+\t\t\t       gfp_t gfp_mask)\n+{\n+\tstruct bio_crypt_ctx *bc;\n+\n+\tWARN_ON_ONCE(!(gfp_mask \u0026 __GFP_DIRECT_RECLAIM));\n+\n+\tbc = mempool_alloc(bio_crypt_ctx_pool, gfp_mask);\n+\n+\tbc-\u003ebc_key = NULL;\n+\tbc-\u003ebc_slot = *slot;\n+\tmemcpy(bc-\u003ebc_dun, dun, sizeof(bc-\u003ebc_dun));\n+\n+\tbio-\u003ebi_crypt_context = bc;\n+}\n+EXPORT_SYMBOL_GPL(bio_crypt_set_ctx_by_slot);\n+\n void __bio_crypt_free_ctx(struct bio *bio)\n {\n \tmempool_free(bio-\u003ebi_crypt_context, bio_crypt_ctx_pool);\n@@ -156,8 +176,12 @@ void __bio_crypt_advance(struct bio *bio, unsigned int bytes)\n {\n \tstruct bio_crypt_ctx *bc = bio-\u003ebi_crypt_context;\n \n-\tbio_crypt_dun_increment(bc-\u003ebc_dun,\n-\t\t\t\tbytes \u003e\u003e bc-\u003ebc_key-\u003edata_unit_size_bits);\n+\tif (bc-\u003ebc_key)\n+\t\tbio_crypt_dun_increment(bc-\u003ebc_dun,\n+\t\t\t\t\tbytes \u003e\u003e bc-\u003ebc_key-\u003edata_unit_size_bits);\n+\telse if (bc-\u003ebc_slot.data_unit_size_bits)\n+\t\tbio_crypt_dun_increment(bc-\u003ebc_dun,\n+\t\t\t\t\tbytes \u003e\u003e bc-\u003ebc_slot.data_unit_size_bits);\n }\n \n /*\n@@ -169,7 +193,14 @@ bool bio_crypt_dun_is_contiguous(const struct bio_crypt_ctx *bc,\n \t\t\t\t const u64 next_dun[BLK_CRYPTO_DUN_ARRAY_SIZE])\n {\n \tint i;\n-\tunsigned int carry = bytes \u003e\u003e bc-\u003ebc_key-\u003edata_unit_size_bits;\n+\tunsigned int carry;\n+\n+\tif (bc-\u003ebc_key)\n+\t\tcarry = bytes \u003e\u003e bc-\u003ebc_key-\u003edata_unit_size_bits;\n+\telse if (bc-\u003ebc_slot.data_unit_size_bits) {\n+\t\tcarry = bytes \u003e\u003e bc-\u003ebc_slot.data_unit_size_bits;\n+\t} else\n+\t\treturn false;\n \n \tfor (i = 0; i \u003c BLK_CRYPTO_DUN_ARRAY_SIZE; i++) {\n \t\tif (bc-\u003ebc_dun[i] + carry != next_dun[i])\n@@ -198,7 +229,12 @@ static bool bio_crypt_ctx_compatible(struct bio_crypt_ctx *bc1,\n \tif (!bc1)\n \t\treturn !bc2;\n \n-\treturn bc2 \u0026\u0026 bc1-\u003ebc_key == bc2-\u003ebc_key;\n+\tif (bc1-\u003ebc_key)\n+\t\treturn bc2 \u0026\u0026 bc1-\u003ebc_key == bc2-\u003ebc_key;\n+\telse\n+\t\treturn bc2 \u0026\u0026 !bc2-\u003ebc_key \u0026\u0026\n+\t\t       bc1-\u003ebc_slot.phy_slot == bc2-\u003ebc_slot.phy_slot \u0026\u0026\n+\t\t       bc1-\u003ebc_slot.data_unit_size_bits == bc2-\u003ebc_slot.data_unit_size_bits;\n }\n \n bool bio_crypt_rq_ctx_compatible(struct request *rq, struct bio *bio)\n@@ -260,6 +296,19 @@ bool __blk_crypto_submit_bio(struct bio *bio)\n \t\treturn false;\n \t}\n \n+\tif (!bc_key) {\n+\t\t/*\n+\t\t* Slot path: the ICE keyslot was pre-programmed by the\n+\t\t* hypervisor. The target device must natively support inline\n+\t\t* encryption; there is no fallback for slot-based crypto.\n+\t\t*/\n+\t\tif (!bdev_get_queue(bdev)-\u003ecrypto_profile) {\n+\t\t\tbio_endio_status(bio, BLK_STS_NOTSUPP);\n+\t\t\treturn false;\n+\t\t}\n+\t\treturn true;\n+\t}\n+\n \t/*\n \t * If the device does not natively support the encryption context, try to use\n \t * the fallback if available.\ndiff --git a/drivers/block/Kconfig b/drivers/block/Kconfig\nindex 858320b6ebb7e..48ad79734c09e 100644\n--- a/drivers/block/Kconfig\n+++ b/drivers/block/Kconfig\n@@ -176,6 +176,21 @@ config BLK_DEV_LOOP\n \n \t  Most users will answer N here.\n \n+config BLK_CRYPTO_PROXY\n+\ttristate \"Inline encryption proxy for virtio-blk guests\"\n+\tdepends on BLK_INLINE_ENCRYPTION\n+\thelp\n+\t  Provides /dev/blk-crypto-proxy, a misc character device that allows a\n+\t  userspace virtio-blk backend to submit inline-encrypted block I/O\n+\t  on behalf of guest virtual machines.\n+\n+\t  Guests supply a virtual keyslot index and data unit number with\n+\t  each encrypted request.  The host kernel translates the virtual\n+\t  slot to a physical hardware keyslot and issues the bio to the\n+\t  storage controller with the correct inline encryption context.\n+\n+\t  If unsure, say N.\n+\n config BLK_DEV_LOOP_MIN_COUNT\n \tint \"Number of loop devices to pre-create at init time\"\n \tdepends on BLK_DEV_LOOP\n@@ -312,6 +327,19 @@ config VIRTIO_BLK\n \t  This is the virtual block driver for virtio.  It can be used with\n           QEMU based VMMs (like KVM or Xen).  Say Y or M.\n \n+config VIRTBLK_CRYPTO_VIRTUALIZATION\n+\ttristate \"Virtio block inline encryption virtualization support\"\n+\tdepends on VIRTIO_BLK \u0026\u0026 BLK_INLINE_ENCRYPTION\n+\thelp\n+\t  Say 'Y or M' to enable routing of crypto requests to a different\n+\t  operating system in a virtualized environment. This option by\n+\t  itself does not provide a working backend: enable a\n+\t  platform-specific driver that implements struct\n+\t  virtblk_crypto_variant_ops as well (e.g. QCOM_CRYPTO_VIRT on\n+\t  Qualcomm platforms). Without one, VIRTIO_BLK_F_INLINE_ENCRYPTION is\n+\t  still negotiated and advertised to the block layer, but every\n+\t  inline-crypto operation fails with -EOPNOTSUPP at runtime.\n+\n config BLK_DEV_RBD\n \ttristate \"Rados block device (RBD)\"\n \tdepends on INET \u0026\u0026 BLOCK\ndiff --git a/drivers/block/Makefile b/drivers/block/Makefile\nindex 2d8096eb8cdf8..636137248d0d1 100644\n--- a/drivers/block/Makefile\n+++ b/drivers/block/Makefile\n@@ -23,8 +23,11 @@ obj-$(CONFIG_BLK_DEV_LOOP)\t+= loop.o\n obj-$(CONFIG_SUNVDC)\t\t+= sunvdc.o\n \n obj-$(CONFIG_BLK_DEV_NBD)\t+= nbd.o\n+obj-$(CONFIG_BLK_CRYPTO_PROXY)\t+= blk-crypto-proxy.o\n obj-$(CONFIG_VIRTIO_BLK)\t+= virtio_blk.o\n \n+obj-$(CONFIG_VIRTBLK_CRYPTO_VIRTUALIZATION) += virtio_blk_crypto_ext.o\n+\n obj-$(CONFIG_XEN_BLKDEV_FRONTEND)\t+= xen-blkfront.o\n obj-$(CONFIG_XEN_BLKDEV_BACKEND)\t+= xen-blkback/\n obj-$(CONFIG_BLK_DEV_DRBD)     += drbd/\ndiff --git a/drivers/block/blk-crypto-proxy.c b/drivers/block/blk-crypto-proxy.c\nnew file mode 100644\nindex 0000000000000..60722884dbcd7\n--- /dev/null\n+++ b/drivers/block/blk-crypto-proxy.c\n@@ -0,0 +1,667 @@\n+// SPDX-License-Identifier: GPL-2.0\n+\n+#define pr_fmt(fmt) \"blk-crypto-proxy: \" fmt\n+\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/miscdevice.h\u003e\n+#include \u003clinux/fs.h\u003e\n+#include \u003clinux/uaccess.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/blkdev.h\u003e\n+#include \u003clinux/bio.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/overflow.h\u003e\n+#include \u003clinux/file.h\u003e\n+#include \u003clinux/mm.h\u003e\n+#include \u003clinux/pagemap.h\u003e\n+#include \u003clinux/rcupdate.h\u003e\n+#include \u003clinux/uio.h\u003e\n+#include \u003clinux/blk-crypto.h\u003e\n+#include \u003clinux/blk-crypto-profile.h\u003e\n+#include \u003clinux/blk-crypto-proxy.h\u003e\n+#include \u003clinux/virtio_blk.h\u003e\n+\n+static const struct bcp_hypervisor_ops __rcu *g_hypervisor_ops;\n+static DEFINE_MUTEX(g_hypervisor_ops_lock);\n+\n+static const struct bcp_slot_virt_ops __rcu *g_slot_virt_ops;\n+static DEFINE_MUTEX(g_slot_virt_ops_lock);\n+\n+int bcp_register_hypervisor_ops(const struct bcp_hypervisor_ops *ops)\n+{\n+\tint ret = 0;\n+\n+\tmutex_lock(\u0026g_hypervisor_ops_lock);\n+\tif (rcu_access_pointer(g_hypervisor_ops))\n+\t\tret = -EBUSY;\n+\telse\n+\t\trcu_assign_pointer(g_hypervisor_ops, ops);\n+\tmutex_unlock(\u0026g_hypervisor_ops_lock);\n+\treturn ret;\n+}\n+EXPORT_SYMBOL_GPL(bcp_register_hypervisor_ops);\n+\n+void bcp_unregister_hypervisor_ops(const struct bcp_hypervisor_ops *ops)\n+{\n+\tmutex_lock(\u0026g_hypervisor_ops_lock);\n+\tif (rcu_access_pointer(g_hypervisor_ops) == ops)\n+\t\trcu_assign_pointer(g_hypervisor_ops, NULL);\n+\tmutex_unlock(\u0026g_hypervisor_ops_lock);\n+\tsynchronize_rcu();\n+}\n+EXPORT_SYMBOL_GPL(bcp_unregister_hypervisor_ops);\n+\n+int bcp_register_slot_virt_ops(const struct bcp_slot_virt_ops *ops)\n+{\n+\tint ret = 0;\n+\n+\tmutex_lock(\u0026g_slot_virt_ops_lock);\n+\tif (rcu_access_pointer(g_slot_virt_ops))\n+\t\tret = -EBUSY;\n+\telse\n+\t\trcu_assign_pointer(g_slot_virt_ops, ops);\n+\tmutex_unlock(\u0026g_slot_virt_ops_lock);\n+\treturn ret;\n+}\n+EXPORT_SYMBOL_GPL(bcp_register_slot_virt_ops);\n+\n+void bcp_unregister_slot_virt_ops(const struct bcp_slot_virt_ops *ops)\n+{\n+\tmutex_lock(\u0026g_slot_virt_ops_lock);\n+\tif (rcu_access_pointer(g_slot_virt_ops) == ops)\n+\t\trcu_assign_pointer(g_slot_virt_ops, NULL);\n+\tmutex_unlock(\u0026g_slot_virt_ops_lock);\n+\tsynchronize_rcu();\n+}\n+EXPORT_SYMBOL_GPL(bcp_unregister_slot_virt_ops);\n+\n+/**\n+ * struct bcp_ctx - per-fd state for /dev/blk-crypto-proxy\n+ * @bdev_file: file handle for the bound block device; NULL until BCP_BIND_CONTEXT.\n+ *             Published with smp_store_release() so hot-path ioctls can read it\n+ *             lock-free via smp_load_acquire() in bcp_ctx_bound().\n+ * @guest_id: guest identifier resolved from vm_fd at bind time.\n+ * @bdev_writable: block_dev_fd was opened with write access.\n+ * @bind_lock: serializes concurrent BCP_BIND_CONTEXT calls on this fd.\n+ */\n+struct bcp_ctx {\n+\tstruct file *bdev_file;\n+\tu32 guest_id;\n+\tbool bdev_writable;\n+\tstruct mutex bind_lock;\n+};\n+\n+/*\n+ * True once BCP_BIND_CONTEXT has published ctx-\u003ebdev_file.  The acquire pairs\n+ * with smp_store_release() in bcp_ioctl_bind_context(), ensuring ctx-\u003eguest_id\n+ * and ctx-\u003ebdev_writable are visible to any caller that observes true.\n+ */\n+static bool bcp_ctx_bound(struct bcp_ctx *ctx)\n+{\n+\treturn smp_load_acquire(\u0026ctx-\u003ebdev_file) != NULL;\n+}\n+\n+static int bcp_open(struct inode *inode, struct file *file)\n+{\n+\tstruct bcp_ctx *ctx;\n+\n+\tctx = kzalloc_obj(*ctx, GFP_KERNEL);\n+\tif (!ctx)\n+\t\treturn -ENOMEM;\n+\tmutex_init(\u0026ctx-\u003ebind_lock);\n+\tfile-\u003eprivate_data = ctx;\n+\treturn 0;\n+}\n+\n+static int bcp_release(struct inode *inode, struct file *file)\n+{\n+\tstruct bcp_ctx *ctx = file-\u003eprivate_data;\n+\n+\tif (ctx) {\n+\t\tif (ctx-\u003ebdev_file)\n+\t\t\tbdev_fput(ctx-\u003ebdev_file);\n+\t\tmutex_destroy(\u0026ctx-\u003ebind_lock);\n+\t\tkfree(ctx);\n+\t\tfile-\u003eprivate_data = NULL;\n+\t}\n+\treturn 0;\n+}\n+\n+/*\n+ * Resolve a userspace block device fd to a struct file holding a reference\n+ * to the block device, opened with the same access mode as @fd so that a\n+ * read-only fd cannot gain write access via BCP_SUBMIT_IO_BY_VSLOT.\n+ */\n+static struct file *bcp_bdev_from_fd(int fd, bool *writable)\n+{\n+\tstruct file *f;\n+\tstruct inode *inode;\n+\tdev_t dev;\n+\tblk_mode_t mode = 0;\n+\n+\tf = fget(fd);\n+\tif (!f)\n+\t\treturn ERR_PTR(-EBADF);\n+\tinode = file_inode(f);\n+\tif (!S_ISBLK(inode-\u003ei_mode)) {\n+\t\tfput(f);\n+\t\treturn ERR_PTR(-ENOTBLK);\n+\t}\n+\tif (f-\u003ef_mode \u0026 FMODE_READ)\n+\t\tmode |= BLK_OPEN_READ;\n+\tif (f-\u003ef_mode \u0026 FMODE_WRITE)\n+\t\tmode |= BLK_OPEN_WRITE;\n+\tif (!mode) {\n+\t\tfput(f);\n+\t\treturn ERR_PTR(-EACCES);\n+\t}\n+\t*writable = !!(mode \u0026 BLK_OPEN_WRITE);\n+\tdev = inode-\u003ei_rdev;\n+\tfput(f);\n+\treturn bdev_file_open_by_dev(dev, mode, NULL, NULL);\n+}\n+\n+static long bcp_ioctl_bind_context(struct file *file,\n+\t\t\t\t   struct bcp_bind_context_arg __user *argp)\n+{\n+\tstruct bcp_ctx *ctx = file-\u003eprivate_data;\n+\tstruct bcp_bind_context_arg arg;\n+\tconst struct bcp_hypervisor_ops *hv_ops;\n+\tstruct file *bdev_file;\n+\tu32 guest_id;\n+\tbool writable = false;\n+\tint ret;\n+\n+\tif (!ctx)\n+\t\treturn -EINVAL;\n+\tif (copy_from_user(\u0026arg, argp, sizeof(arg)))\n+\t\treturn -EFAULT;\n+\tif (arg.reserved)\n+\t\treturn -EINVAL;\n+\n+\t/*\n+\t * get_guest_id() may sleep; call it before taking bind_lock.\n+\t */\n+\trcu_read_lock();\n+\thv_ops = rcu_dereference(g_hypervisor_ops);\n+\tif (!hv_ops) {\n+\t\trcu_read_unlock();\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\tret = hv_ops-\u003eget_guest_id(arg.vm_fd, \u0026guest_id);\n+\trcu_read_unlock();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Serialize against concurrent BCP_BIND_CONTEXT calls: two callers\n+\t * could both pass the ctx-\u003ebdev_file == NULL check before either stores.\n+\t */\n+\tguard(mutex)(\u0026ctx-\u003ebind_lock);\n+\n+\tif (ctx-\u003ebdev_file)\n+\t\treturn -EBUSY;\n+\n+\tbdev_file = bcp_bdev_from_fd(arg.block_dev_fd, \u0026writable);\n+\tif (IS_ERR(bdev_file))\n+\t\treturn PTR_ERR(bdev_file);\n+\n+\tctx-\u003eguest_id = guest_id;\n+\tctx-\u003ebdev_writable = writable;\n+\t/*\n+\t * Publish ctx-\u003ebdev_file last with a release barrier; bcp_ctx_bound()\n+\t * reads it with smp_load_acquire() without taking @bind_lock.\n+\t */\n+\tsmp_store_release(\u0026ctx-\u003ebdev_file, bdev_file);\n+\treturn 0;\n+}\n+\n+/*\n+ * Maps VIRTIO_BLK_CRYPTO_MODE_* to enum blk_crypto_mode_num.  The two index\n+ * spaces do not coincide, so modes_supported[] must not be copied positionally.\n+ * Keep in sync with virtio_blk_crypto_mode_map[] in drivers/block/virtio_blk.c.\n+ */\n+static const enum blk_crypto_mode_num\n+\tbcp_virtio_crypto_mode_map[VIRTIO_BLK_CRYPTO_MODE_MAX + 1] = {\n+\t[VIRTIO_BLK_CRYPTO_MODE_INVALID]\t= BLK_ENCRYPTION_MODE_INVALID,\n+\t[VIRTIO_BLK_CRYPTO_MODE_AES_256_XTS]\t= BLK_ENCRYPTION_MODE_AES_256_XTS,\n+};\n+\n+static long bcp_ioctl_get_crypto_caps(struct file *file,\n+\t\t\t\t      struct bcp_get_crypto_caps_arg __user *argp)\n+{\n+\tstruct bcp_ctx *ctx = file-\u003eprivate_data;\n+\tstruct bcp_get_crypto_caps_arg arg;\n+\tstruct block_device *bdev;\n+\tu32 modes[VIRTIO_BLK_CRYPTO_MODE_MAX + 1] = {0};\n+\tstruct blk_crypto_profile *profile;\n+\tunsigned int i, n;\n+\tu32 cap, written;\n+\n+\tif (!ctx || !bcp_ctx_bound(ctx))\n+\t\treturn -ENXIO;\n+\n+\tif (copy_from_user(\u0026arg, argp, sizeof(arg)))\n+\t\treturn -EFAULT;\n+\tif (arg.num_modes \u0026\u0026 !arg.modes_ptr)\n+\t\treturn -EINVAL;\n+\n+\tbdev = file_bdev(ctx-\u003ebdev_file);\n+\tprofile = bdev_get_queue(bdev)-\u003ecrypto_profile;\n+\tif (!profile)\n+\t\treturn -EOPNOTSUPP;\n+\n+\targ.key_types_supported = profile-\u003ekey_types_supported;\n+\t/*\n+\t * Clamp to 8: the @dun wire field is a single __aligned_u64 so nothing\n+\t * upstream can deliver a wider DUN regardless of what the profile claims.\n+\t */\n+\targ.max_dun_bytes = min_t(u32, profile-\u003emax_dun_bytes_supported, 8);\n+\n+\t/*\n+\t * arg.modes_supported[] is indexed by VIRTIO_BLK_CRYPTO_MODE_* (index 0\n+\t * is always 0 per the virtio spec).  Translate via the map above; do not\n+\t * copy profile-\u003emodes_supported[] positionally.\n+\t */\n+\tn = VIRTIO_BLK_CRYPTO_MODE_MAX + 1;\n+\tcap = arg.num_modes;\n+\twritten = min_t(u32, cap, n);\n+\n+\tfor (i = 1; i \u003c n; i++) {\n+\t\tenum blk_crypto_mode_num kmode = bcp_virtio_crypto_mode_map[i];\n+\n+\t\tif (!kmode)\n+\t\t\tcontinue;\n+\t\tmodes[i] = profile-\u003emodes_supported[kmode];\n+\t}\n+\n+\tif (written \u0026\u0026\n+\t    copy_to_user(u64_to_user_ptr(arg.modes_ptr), modes,\n+\t\t\t written * sizeof(modes[0])))\n+\t\treturn -EFAULT;\n+\targ.num_modes = written;\n+\n+\targ.max_slots = 0;\n+\trcu_read_lock();\n+\t{\n+\t\tconst struct bcp_slot_virt_ops *sv_ops =\n+\t\t\t\trcu_dereference(g_slot_virt_ops);\n+\t\tif (sv_ops) {\n+\t\t\tint nslots = sv_ops-\u003eget_guest_slots(profile, ctx-\u003eguest_id);\n+\n+\t\t\tif (nslots \u003e 0)\n+\t\t\t\targ.max_slots = nslots;\n+\t\t}\n+\t}\n+\trcu_read_unlock();\n+\n+\tif (copy_to_user(argp, \u0026arg, sizeof(arg)))\n+\t\treturn -EFAULT;\n+\treturn 0;\n+}\n+\n+/*\n+ * Compute the number of pages needed for up to @want_bytes of iovec data\n+ * starting at cursor (@start_idx, @start_off).  The page count is capped at\n+ * @cap to bound the arithmetic; *bytes_out receives the actual byte count.\n+ */\n+static unsigned int bcp_iov_pages_for_bytes(const struct iovec *iov, u32 iov_cnt,\n+\t\t\t\t\t    u32 start_idx, u64 start_off,\n+\t\t\t\t\t      u64 want_bytes, unsigned int cap,\n+\t\t\t\t\t      u64 *bytes_out)\n+{\n+\tu64 pages = 0, taken = 0;\n+\tu32 i;\n+\n+\tfor (i = start_idx; i \u003c iov_cnt \u0026\u0026 taken \u003c want_bytes; i++) {\n+\t\tu64 base, len;\n+\n+\t\tif (i == start_idx) {\n+\t\t\tbase = (u64)(uintptr_t)iov[i].iov_base + start_off;\n+\t\t\tlen  = iov[i].iov_len - start_off;\n+\t\t} else {\n+\t\t\tbase = (u64)(uintptr_t)iov[i].iov_base;\n+\t\t\tlen  = iov[i].iov_len;\n+\t\t}\n+\t\tif (len == 0)\n+\t\t\tcontinue;\n+\t\tif (len \u003e want_bytes - taken)\n+\t\t\tlen = want_bytes - taken;\n+\t\tpages += DIV_ROUND_UP(len + offset_in_page(base), PAGE_SIZE);\n+\t\ttaken += len;\n+\t\tif (pages \u003e= cap) {\n+\t\t\t*bytes_out = taken;\n+\t\t\treturn cap;\n+\t\t}\n+\t}\n+\t*bytes_out = taken;\n+\treturn (unsigned int)pages;\n+}\n+\n+/*\n+ * Advance cursor (*idx, *off) forward by @bytes within @iov[0..iov_cnt).\n+ */\n+static void bcp_iov_advance_cursor(const struct iovec *iov, u32 iov_cnt,\n+\t\t\t\t   u32 *idx, u64 *off, u64 bytes)\n+{\n+\twhile (bytes \u003e 0 \u0026\u0026 *idx \u003c iov_cnt) {\n+\t\tu64 seg_remaining = iov[*idx].iov_len - *off;\n+\t\tu64 take = min_t(u64, seg_remaining, bytes);\n+\n+\t\t*off += take;\n+\t\tbytes -= take;\n+\t\tif (*off == iov[*idx].iov_len) {\n+\t\t\t(*idx)++;\n+\t\t\t*off = 0;\n+\t\t}\n+\t}\n+}\n+\n+static long bcp_ioctl_submit_io_by_vslot(struct file *file,\n+\t\t\t\t\t struct bcp_submit_io_by_vslot_arg __user *argp)\n+{\n+\tstruct bcp_ctx *ctx = file-\u003eprivate_data;\n+\tstruct bcp_submit_io_by_vslot_arg arg;\n+\tstruct block_device *bdev;\n+\tstruct blk_crypto_profile *profile;\n+\tstruct iovec *iov = NULL;\n+\tstruct iov_iter iter;\n+\tstruct blk_crypto_slot slot;\n+\tu64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE];\n+\tu64 bytes_done = 0;\n+\tu64 align, stride;\n+\tu64 total_bytes;\n+\tu32 seg_idx = 0;\n+\tu64 seg_off = 0;\n+\tunsigned int phy_slot;\n+\tint ret = -EFAULT;\n+\n+\tif (!ctx || !bcp_ctx_bound(ctx))\n+\t\treturn -ENXIO;\n+\n+\tif (copy_from_user(\u0026arg, argp, sizeof(arg)))\n+\t\treturn -EFAULT;\n+\tif (arg.reserved2)\n+\t\treturn -EINVAL;\n+\n+\tbdev = file_bdev(ctx-\u003ebdev_file);\n+\n+\tif (arg.direction != BCP_DIR_READ \u0026\u0026 arg.direction != BCP_DIR_WRITE)\n+\t\treturn -EINVAL;\n+\t/*\n+\t * blk_mode_t does not stop submit_bio() from writing; enforce the\n+\t * caller's original fd permission explicitly.\n+\t */\n+\tif (arg.direction == BCP_DIR_WRITE \u0026\u0026 !ctx-\u003ebdev_writable)\n+\t\treturn -EACCES;\n+\t/*\n+\t * bdev_read_only() can change after bind time; submit_bio_noacct()'s\n+\t * bio_check_ro() only warns rather than errors in this kernel.\n+\t */\n+\tif (arg.direction == BCP_DIR_WRITE \u0026\u0026 bdev_read_only(bdev))\n+\t\treturn -EROFS;\n+\tif (arg.flags != BCP_SUBMIT_IO_F_IOV)\n+\t\treturn -EINVAL;\n+\tif (arg.iov_cnt == 0 || arg.iov_cnt \u003e BCP_MAX_IOV)\n+\t\treturn -EINVAL;\n+\t/* A shift amount \u003e= 64 would be undefined behavior. */\n+\tif (arg.data_unit_size_bits \u003e= 64)\n+\t\treturn -EINVAL;\n+\n+\tprofile = bdev_get_queue(bdev)-\u003ecrypto_profile;\n+\tif (!profile)\n+\t\treturn -EOPNOTSUPP;\n+\n+\t/* Resolve virt_slot → phy_slot. */\n+\trcu_read_lock();\n+\t{\n+\t\tconst struct bcp_slot_virt_ops *sv_ops =\n+\t\t\t\trcu_dereference(g_slot_virt_ops);\n+\t\tif (!sv_ops) {\n+\t\t\trcu_read_unlock();\n+\t\t\treturn -EOPNOTSUPP;\n+\t\t}\n+\t\tret = sv_ops-\u003evslot_to_pslot(profile, ctx-\u003eguest_id,\n+\t\t\t\t\t     arg.virt_slot, \u0026phy_slot);\n+\t}\n+\trcu_read_unlock();\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tmemset(dun, 0, sizeof(dun));\n+\tdun[0] = arg.dun;\n+\n+\tslot.phy_slot            = phy_slot;\n+\tslot.data_unit_size_bits = arg.data_unit_size_bits;\n+\n+\talign = 1ULL \u003c\u003c arg.data_unit_size_bits;\n+\t/*\n+\t * Split bios at stride (smallest multiple of the data unit size \u003e=\n+\t * PAGE_SIZE) boundaries so each bio ends on a whole data unit.\n+\t * bio_crypt_check_alignment() is skipped for slot-based bios (bc_key\n+\t * == NULL), so a mid-unit split would silently mis-encrypt/mis-decrypt.\n+\t */\n+\tstride = DIV_ROUND_UP(PAGE_SIZE, align) * align;\n+\n+\t/*\n+\t * Import the caller's iovec once.  import_iovec() validates every\n+\t * segment with access_ok(), returns the total byte count, and takes a\n+\t * private kernel copy that eliminates TOCTOU from a guest mutating its\n+\t * own iovec array mid-ioctl.\n+\t */\n+\tret = import_iovec(arg.direction == BCP_DIR_READ ? ITER_DEST : ITER_SOURCE,\n+\t\t\t   (const struct iovec __user *)u64_to_user_ptr(arg.iov_ptr),\n+\t\t\t   arg.iov_cnt, 0, \u0026iov, \u0026iter);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\ttotal_bytes = ret;\n+\n+\t/*\n+\t * Reject a misaligned total length up front: bio_crypt_check_alignment()\n+\t * is skipped for slot-based bios so nothing downstream will catch it.\n+\t */\n+\tif (total_bytes == 0 || (total_bytes \u0026 (align - 1)) ||\n+\t    (total_bytes \u0026 (SECTOR_SIZE - 1))) {\n+\t\tret = -EINVAL;\n+\t\tgoto out;\n+\t}\n+\n+\t/*\n+\t * Fail fast if the request exceeds the device.  bio_check_eod() would\n+\t * also catch this, but only on the last bio after earlier bios have\n+\t * already done real I/O.\n+\t */\n+\t{\n+\t\tsector_t nr_sectors = total_bytes \u003e\u003e SECTOR_SHIFT;\n+\t\tsector_t maxsector = bdev_nr_sectors(bdev);\n+\n+\t\tif (nr_sectors \u003e maxsector || arg.sector \u003e maxsector - nr_sectors) {\n+\t\t\tret = -EIO;\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Reject an out-of-range DUN: slot-based bios skip\n+\t * bio_crypt_check_alignment(), so an overflow would silently truncate\n+\t * in the hardware DUN field rather than error out.\n+\t */\n+\t{\n+\t\tu64 total_units = total_bytes \u003e\u003e arg.data_unit_size_bits;\n+\t\tu64 max_dun_used, dun_limit;\n+\n+\t\tif (check_add_overflow(arg.dun, total_units - 1, \u0026max_dun_used)) {\n+\t\t\tret = -EINVAL;\n+\t\t\tgoto out;\n+\t\t}\n+\t\tdun_limit = profile-\u003emax_dun_bytes_supported \u003e= 8 ? U64_MAX :\n+\t\t\t(1ULL \u003c\u003c (8 * profile-\u003emax_dun_bytes_supported)) - 1;\n+\t\tif (max_dun_used \u003e dun_limit) {\n+\t\t\tret = -EINVAL;\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Submit the request as a sequence of bios (submit_bio_wait() per\n+\t * bio), each holding at most BIO_MAX_VECS pages.  Sequential\n+\t * submission avoids DUN/IV correctness concerns across concurrent\n+\t * in-flight bios.\n+\t */\n+\twhile (seg_idx \u003c arg.iov_cnt) {\n+\t\tunsigned int pages_used = 0;\n+\t\tu64 bio_bytes = 0;\n+\t\tu32 la_idx = seg_idx;\n+\t\tu64 la_off = seg_off;\n+\t\tu64 remaining_before;\n+\t\tstruct bio *bio;\n+\n+\t\t/*\n+\t\t * Lookahead: count how many whole stride units fit within a\n+\t\t * fresh bio's BIO_MAX_VECS page budget.\n+\t\t */\n+\t\tfor (;;) {\n+\t\t\tu64 unit_bytes;\n+\t\t\tunsigned int unit_pages;\n+\n+\t\t\tunit_pages = bcp_iov_pages_for_bytes(iov, arg.iov_cnt,\n+\t\t\t\t\t\t\t     la_idx, la_off, stride,\n+\t\t\t\t\t\t\tBIO_MAX_VECS + 1,\n+\t\t\t\t\t\t\t\u0026unit_bytes);\n+\t\t\tif (unit_bytes == 0)\n+\t\t\t\tbreak; /* only empty segments remain */\n+\n+\t\t\tif (pages_used + unit_pages \u003e BIO_MAX_VECS) {\n+\t\t\t\tif (pages_used == 0) {\n+\t\t\t\t\t/* data_unit_size_bits too large to fit one unit. */\n+\t\t\t\t\tret = -EINVAL;\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n+\t\t\t\tbreak; /* finalize this bio; unit deferred to next */\n+\t\t\t}\n+\n+\t\t\tpages_used += unit_pages;\n+\t\t\tbio_bytes  += unit_bytes;\n+\t\t\tbcp_iov_advance_cursor(iov, arg.iov_cnt, \u0026la_idx, \u0026la_off,\n+\t\t\t\t\t       unit_bytes);\n+\t\t}\n+\n+\t\tif (bio_bytes == 0)\n+\t\t\tbreak;\n+\n+\t\tbio = bio_alloc(bdev, pages_used,\n+\t\t\t\targ.direction == BCP_DIR_WRITE ?\n+\t\t\t\t\tREQ_OP_WRITE : REQ_OP_READ,\n+\t\t\t\tGFP_KERNEL);\n+\t\tif (!bio) {\n+\t\t\tret = -ENOMEM;\n+\t\t\tgoto out;\n+\t\t}\n+\t\tbio-\u003ebi_iter.bi_sector = arg.sector + (bytes_done \u003e\u003e SECTOR_SHIFT);\n+\n+\t\t/*\n+\t\t * Use bio_iov_iter_get_pages() to pin pages into the bio,\n+\t\t * the same as the O_DIRECT path.  Truncate the iter to this\n+\t\t * bio's byte budget, then reexpand for the next iteration.\n+\t\t */\n+\t\tremaining_before = iov_iter_count(\u0026iter);\n+\t\tiov_iter_truncate(\u0026iter, bio_bytes);\n+\t\tret = bio_iov_iter_get_pages(bio, \u0026iter, 0, 0);\n+\t\tif (ret \u003c 0) {\n+\t\t\tbio_put(bio);\n+\t\t\tgoto out;\n+\t\t}\n+\t\tif (iov_iter_count(\u0026iter) != 0) {\n+\t\t\t/*\n+\t\t\t * The lookahead verified bio_bytes fits in BIO_MAX_VECS;\n+\t\t\t * if bio_iov_iter_get_pages() stopped early, its page\n+\t\t\t * accounting disagreed with bcp_iov_pages_for_bytes().\n+\t\t\t */\n+\t\t\tbio_put(bio);\n+\t\t\tret = -EIO;\n+\t\t\tgoto out;\n+\t\t}\n+\t\tiov_iter_reexpand(\u0026iter, remaining_before - bio_bytes);\n+\n+\t\t/*\n+\t\t * Match __blkdev_direct_IO(): mark pages dirty on reads into\n+\t\t * user-backed memory.\n+\t\t */\n+\t\tif (arg.direction == BCP_DIR_READ \u0026\u0026 user_backed_iter(\u0026iter))\n+\t\t\tbio_set_pages_dirty(bio);\n+\n+\t\tbcp_iov_advance_cursor(iov, arg.iov_cnt, \u0026seg_idx, \u0026seg_off,\n+\t\t\t\t       bio_bytes);\n+\n+\t\tbio_crypt_set_ctx_by_slot(bio, \u0026slot, dun, GFP_KERNEL);\n+\n+\t\tret = submit_bio_wait(bio);\n+\t\tbio_put(bio);\n+\t\tif (ret)\n+\t\t\tgoto out;\n+\n+\t\t/*\n+\t\t * Advance dun by this bio's contribution only, not by\n+\t\t * recomputing from arg.dun + bytes_done, to avoid silent\n+\t\t * truncation when bytes_done grows past UINT_MAX data units.\n+\t\t */\n+\t\tbio_crypt_dun_increment(dun, (unsigned int)(bio_bytes \u003e\u003e arg.data_unit_size_bits));\n+\t\tbytes_done += bio_bytes;\n+\t}\n+\tret = 0;\n+\n+out:\n+\tkfree(iov);\n+\treturn ret;\n+}\n+\n+static long bcp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)\n+{\n+\tvoid __user *argp = (void __user *)arg;\n+\n+\tswitch (cmd) {\n+\tcase BCP_BIND_CONTEXT:\n+\t\treturn bcp_ioctl_bind_context(file, argp);\n+\tcase BCP_GET_CRYPTO_CAPS:\n+\t\treturn bcp_ioctl_get_crypto_caps(file, argp);\n+\tcase BCP_SUBMIT_IO_BY_VSLOT:\n+\t\treturn bcp_ioctl_submit_io_by_vslot(file, argp);\n+\tdefault:\n+\t\treturn -ENOTTY;\n+\t}\n+}\n+\n+static const struct file_operations bcp_fops = {\n+\t.owner\t\t= THIS_MODULE,\n+\t.open\t\t= bcp_open,\n+\t.release\t= bcp_release,\n+\t.unlocked_ioctl\t= bcp_ioctl,\n+\t.compat_ioctl\t= compat_ptr_ioctl,\n+};\n+\n+static struct miscdevice bcp_misc = {\n+\t.minor\t= MISC_DYNAMIC_MINOR,\n+\t.name\t= \"blk-crypto-proxy\",\n+\t.fops\t= \u0026bcp_fops,\n+};\n+\n+static int __init blk_crypto_proxy_init(void)\n+{\n+\tint ret;\n+\n+\tret = misc_register(\u0026bcp_misc);\n+\tif (ret)\n+\t\treturn ret;\n+\treturn 0;\n+}\n+\n+static void __exit blk_crypto_proxy_exit(void)\n+{\n+\tmisc_deregister(\u0026bcp_misc);\n+}\n+\n+module_init(blk_crypto_proxy_init);\n+module_exit(blk_crypto_proxy_exit);\n+\n+MODULE_LICENSE(\"GPL\");\n+MODULE_DESCRIPTION(\"Host-side inline crypto proxy for virtio-blk guests\");\ndiff --git a/drivers/block/virtio_blk.c b/drivers/block/virtio_blk.c\nindex 32bf3ba07a9dc..61a3967bb4dfe 100644\n--- a/drivers/block/virtio_blk.c\n+++ b/drivers/block/virtio_blk.c\n@@ -16,6 +16,8 @@\n #include \u003clinux/numa.h\u003e\n #include \u003clinux/vmalloc.h\u003e\n #include \u003cuapi/linux/virtio_ring.h\u003e\n+#include \u003clinux/blk-crypto-profile.h\u003e\n+#include \u003clinux/virtio_blk_crypto_ext.h\u003e\n \n #define PART_BITS 4\n #define VQ_NAME_LEN 16\n@@ -87,7 +89,14 @@ struct virtio_blk {\n \n struct virtblk_req {\n \t/* Out header */\n-\tstruct virtio_blk_outhdr out_hdr;\n+\tunion {\n+\t\tstruct virtio_blk_outhdr base;\n+\t\tstruct {\n+\t\t\tstruct virtio_blk_outhdr base;\n+\t\t\t/* Crypto message (if VIRTIO_BLK_F_INLINE_ENCRYPTION) */\n+\t\t\tstruct virtio_blk_crypto_msg msg;\n+\t\t} crypto_append;\n+\t} out_hdr;\n \n \t/* In header */\n \tunion {\n@@ -140,12 +149,17 @@ static int virtblk_add_req(struct virtqueue *vq, struct virtblk_req *vbr)\n {\n \tstruct scatterlist out_hdr, in_hdr, *sgs[3];\n \tunsigned int num_out = 0, num_in = 0;\n+\tsize_t out_hdr_len = sizeof(vbr-\u003eout_hdr.base);\n+\n+\tif (vbr-\u003eout_hdr.base.type == cpu_to_virtio32(vq-\u003evdev, VIRTIO_BLK_T_CRYPTO_IN) ||\n+\t    vbr-\u003eout_hdr.base.type == cpu_to_virtio32(vq-\u003evdev, VIRTIO_BLK_T_CRYPTO_OUT))\n+\t\tout_hdr_len = sizeof(vbr-\u003eout_hdr.crypto_append);\n \n-\tsg_init_one(\u0026out_hdr, \u0026vbr-\u003eout_hdr, sizeof(vbr-\u003eout_hdr));\n+\tsg_init_one(\u0026out_hdr, \u0026vbr-\u003eout_hdr, out_hdr_len);\n \tsgs[num_out++] = \u0026out_hdr;\n \n \tif (vbr-\u003esg_table.nents) {\n-\t\tif (vbr-\u003eout_hdr.type \u0026 cpu_to_virtio32(vq-\u003evdev, VIRTIO_BLK_T_OUT))\n+\t\tif (vbr-\u003eout_hdr.base.type \u0026 cpu_to_virtio32(vq-\u003evdev, VIRTIO_BLK_T_OUT))\n \t\t\tsgs[num_out++] = vbr-\u003esg_table.sgl;\n \t\telse\n \t\t\tsgs[num_out + num_in++] = vbr-\u003esg_table.sgl;\n@@ -235,6 +249,15 @@ static void virtblk_cleanup_cmd(struct request *req)\n \t\tkfree(bvec_virt(\u0026req-\u003especial_vec));\n }\n \n+static bool is_crypto_request(struct virtio_device *vdev, struct request *req)\n+{\n+\tif (!IS_ENABLED(CONFIG_VIRTBLK_CRYPTO_VIRTUALIZATION) ||\n+\t    !virtio_has_feature(vdev, VIRTIO_BLK_F_INLINE_ENCRYPTION))\n+\t\treturn false;\n+\n+\treturn req-\u003ecrypt_ctx \u0026\u0026 req-\u003ecrypt_keyslot;\n+}\n+\n static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,\n \t\t\t\t      struct request *req,\n \t\t\t\t      struct virtblk_req *vbr)\n@@ -248,15 +271,21 @@ static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,\n \t\treturn BLK_STS_NOTSUPP;\n \n \t/* Set fields for all request types */\n-\tvbr-\u003eout_hdr.ioprio = cpu_to_virtio32(vdev, req_get_ioprio(req));\n+\tvbr-\u003eout_hdr.base.ioprio = cpu_to_virtio32(vdev, req_get_ioprio(req));\n \n \tswitch (req_op(req)) {\n \tcase REQ_OP_READ:\n-\t\ttype = VIRTIO_BLK_T_IN;\n+\t\tif (is_crypto_request(vdev, req))\n+\t\t\ttype = VIRTIO_BLK_T_CRYPTO_IN;\n+\t\telse\n+\t\t\ttype = VIRTIO_BLK_T_IN;\n \t\tsector = blk_rq_pos(req);\n \t\tbreak;\n \tcase REQ_OP_WRITE:\n-\t\ttype = VIRTIO_BLK_T_OUT;\n+\t\tif (is_crypto_request(vdev, req))\n+\t\t\ttype = VIRTIO_BLK_T_CRYPTO_OUT;\n+\t\telse\n+\t\t\ttype = VIRTIO_BLK_T_OUT;\n \t\tsector = blk_rq_pos(req);\n \t\tbreak;\n \tcase REQ_OP_FLUSH:\n@@ -298,8 +327,9 @@ static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,\n \t\tbreak;\n \tcase REQ_OP_DRV_IN:\n \t\t/*\n-\t\t * Out header has already been prepared by the caller (virtblk_get_id()\n-\t\t * or virtblk_submit_zone_report()), nothing to do here.\n+\t\t * Out header has already been prepared by the caller (virtblk_get_id(),\n+\t\t * virtblk_submit_zone_report() or virtblk_get_crypto_modes()), nothing\n+\t\t * to do here.\n \t\t */\n \t\treturn 0;\n \tdefault:\n@@ -309,8 +339,8 @@ static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,\n \n \t/* Set fields for non-REQ_OP_DRV_IN request types */\n \tvbr-\u003ein_hdr_len = in_hdr_len;\n-\tvbr-\u003eout_hdr.type = cpu_to_virtio32(vdev, type);\n-\tvbr-\u003eout_hdr.sector = cpu_to_virtio64(vdev, sector);\n+\tvbr-\u003eout_hdr.base.type = cpu_to_virtio32(vdev, type);\n+\tvbr-\u003eout_hdr.base.sector = cpu_to_virtio64(vdev, sector);\n \n \tif (type == VIRTIO_BLK_T_DISCARD || type == VIRTIO_BLK_T_WRITE_ZEROES ||\n \t    type == VIRTIO_BLK_T_SECURE_ERASE) {\n@@ -318,6 +348,17 @@ static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,\n \t\t\treturn BLK_STS_RESOURCE;\n \t}\n \n+\tif (type == VIRTIO_BLK_T_CRYPTO_IN || type == VIRTIO_BLK_T_CRYPTO_OUT) {\n+\t\tunsigned int slot = blk_crypto_keyslot_index(req-\u003ecrypt_keyslot);\n+\t\tunsigned int data_unit_size_bits = req-\u003ecrypt_ctx-\u003ebc_key-\u003edata_unit_size_bits;\n+\t\tu64 dun = req-\u003ecrypt_ctx-\u003ebc_dun[0];\n+\n+\t\tvbr-\u003eout_hdr.crypto_append.msg.slot = cpu_to_virtio32(vdev, slot);\n+\t\tvbr-\u003eout_hdr.crypto_append.msg.data_unit_size_bits =\n+\t\t\t\tcpu_to_virtio32(vdev, data_unit_size_bits);\n+\t\tvbr-\u003eout_hdr.crypto_append.msg.dun = cpu_to_virtio64(vdev, dun);\n+\t}\n+\n \treturn 0;\n }\n \n@@ -568,8 +609,8 @@ static int virtblk_submit_zone_report(struct virtio_blk *vblk,\n \n \tvbr = blk_mq_rq_to_pdu(req);\n \tvbr-\u003ein_hdr_len = sizeof(vbr-\u003ein_hdr.status);\n-\tvbr-\u003eout_hdr.type = cpu_to_virtio32(vblk-\u003evdev, VIRTIO_BLK_T_ZONE_REPORT);\n-\tvbr-\u003eout_hdr.sector = cpu_to_virtio64(vblk-\u003evdev, sector);\n+\tvbr-\u003eout_hdr.base.type = cpu_to_virtio32(vblk-\u003evdev, VIRTIO_BLK_T_ZONE_REPORT);\n+\tvbr-\u003eout_hdr.base.sector = cpu_to_virtio64(vblk-\u003evdev, sector);\n \n \terr = blk_rq_map_kern(req, report_buf, report_len, GFP_KERNEL);\n \tif (err)\n@@ -817,8 +858,8 @@ static int virtblk_get_id(struct gendisk *disk, char *id_str)\n \n \tvbr = blk_mq_rq_to_pdu(req);\n \tvbr-\u003ein_hdr_len = sizeof(vbr-\u003ein_hdr.status);\n-\tvbr-\u003eout_hdr.type = cpu_to_virtio32(vblk-\u003evdev, VIRTIO_BLK_T_GET_ID);\n-\tvbr-\u003eout_hdr.sector = 0;\n+\tvbr-\u003eout_hdr.base.type = cpu_to_virtio32(vblk-\u003evdev, VIRTIO_BLK_T_GET_ID);\n+\tvbr-\u003eout_hdr.base.sector = 0;\n \n \terr = blk_rq_map_kern(req, id_str, VIRTIO_BLK_ID_BYTES, GFP_KERNEL);\n \tif (err)\n@@ -863,6 +904,70 @@ static int virtblk_getgeo(struct gendisk *disk, struct hd_geometry *geo)\n \treturn ret;\n }\n \n+/* Maps VIRTIO_BLK_CRYPTO_MODE_* values to the kernel's internal enum. */\n+static const enum blk_crypto_mode_num\n+\tvirtio_blk_crypto_mode_map[VIRTIO_BLK_CRYPTO_MODE_MAX + 1] = {\n+\t[VIRTIO_BLK_CRYPTO_MODE_INVALID]\t= BLK_ENCRYPTION_MODE_INVALID,\n+\t[VIRTIO_BLK_CRYPTO_MODE_AES_256_XTS]\t= BLK_ENCRYPTION_MODE_AES_256_XTS,\n+};\n+\n+static int virtblk_get_crypto_modes(struct virtio_blk *vblk,\n+\t\t\t\t    unsigned int *crypto_modes_supported)\n+{\n+\tstruct request_queue *q = vblk-\u003edisk-\u003equeue;\n+\tunsigned int nr_modes = VIRTIO_BLK_CRYPTO_MODE_MAX + 1;\n+\tsize_t buf_size = sizeof(struct virtio_blk_crypto_modes);\n+\tstruct virtio_blk_crypto_modes *virtblk_cmodes;\n+\tstruct request *req;\n+\tstruct virtblk_req *vbr;\n+\tunsigned int i;\n+\tint err;\n+\n+\tvirtblk_cmodes = kzalloc(buf_size, GFP_KERNEL);\n+\tif (!virtblk_cmodes)\n+\t\treturn -ENOMEM;\n+\n+\treq = blk_mq_alloc_request(q, REQ_OP_DRV_IN, 0);\n+\tif (IS_ERR(req)) {\n+\t\terr = PTR_ERR(req);\n+\t\tgoto out_free;\n+\t}\n+\n+\tvbr = blk_mq_rq_to_pdu(req);\n+\tvbr-\u003ein_hdr_len = sizeof(vbr-\u003ein_hdr.status);\n+\tvbr-\u003eout_hdr.base.type = cpu_to_virtio32(vblk-\u003evdev,\n+\t\t\t\t\t\t VIRTIO_BLK_T_GET_CRYPTO_MODES);\n+\tvbr-\u003eout_hdr.base.sector = 0;\n+\n+\terr = blk_rq_map_kern(req, virtblk_cmodes, buf_size, GFP_KERNEL);\n+\tif (err)\n+\t\tgoto out_req;\n+\n+\tblk_execute_rq(req, false);\n+\terr = blk_status_to_errno(virtblk_result(vbr-\u003ein_hdr.status));\n+\tif (err)\n+\t\tgoto out_req;\n+\n+\tfor (i = 1; i \u003c nr_modes; i++) {\n+\t\tu32 mode_mask = virtio32_to_cpu(vblk-\u003evdev, virtblk_cmodes-\u003emodes[i]);\n+\t\tenum blk_crypto_mode_num mode = virtio_blk_crypto_mode_map[i];\n+\n+\t\tif (!mode_mask)\n+\t\t\tcontinue;\n+\t\tif (!mode) {\n+\t\t\tdev_warn(\u0026vblk-\u003evdev-\u003edev,\n+\t\t\t\t \"ignoring unknown crypto mode %u\\n\", i);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tcrypto_modes_supported[mode] = mode_mask;\n+\t}\n+out_req:\n+\tblk_mq_free_request(req);\n+out_free:\n+\tkfree(virtblk_cmodes);\n+\treturn err;\n+}\n+\n static void virtblk_free_disk(struct gendisk *disk)\n {\n \tstruct virtio_blk *vblk = disk-\u003eprivate_data;\n@@ -1435,6 +1540,51 @@ static int virtblk_read_limits(struct virtio_blk *vblk,\n \treturn 0;\n }\n \n+static int virtblk_init_crypto(struct virtio_blk *vblk)\n+{\n+\tstruct virtio_device *vdev = vblk-\u003evdev;\n+\tunsigned int crypto_modes_supported[BLK_ENCRYPTION_MODE_MAX] = { 0 };\n+\t/* virtio_cread() requires the variable size to match the config field exactly */\n+\tu16 max_slots;\n+\tu8 max_dun_bytes, key_types;\n+\tint err;\n+\n+\tvirtio_cread(vdev, struct virtio_blk_config,\n+\t\t     enc_characteristics.max_slots, \u0026max_slots);\n+\tvirtio_cread(vdev, struct virtio_blk_config,\n+\t\t     enc_characteristics.max_dun_bytes, \u0026max_dun_bytes);\n+\tvirtio_cread(vdev, struct virtio_blk_config,\n+\t\t     enc_characteristics.key_types, \u0026key_types);\n+\n+\tdev_dbg(\u0026vdev-\u003edev,\n+\t\t \"max_slots = %u, max_dun_bytes = %u, key_types = 0x%x\\n\",\n+\t\t max_slots, max_dun_bytes, key_types);\n+\n+\tif (!max_slots)\n+\t\treturn -EINVAL;\n+\tif (!(key_types \u0026 (VIRTIO_BLK_CRYPTO_KEY_TYPE_RAW |\n+\t\t\t   VIRTIO_BLK_CRYPTO_KEY_TYPE_HW_WRAPPED)))\n+\t\treturn -EINVAL;\n+\t/*\n+\t * struct virtio_blk_crypto_msg.dun is a fixed __virtio64, i.e. this\n+\t * driver can only ever transmit 8 bytes of DUN per request. Refuse\n+\t * to advertise more than that as supported, or blk-crypto could\n+\t * negotiate a larger dun_bytes with the filesystem and have the\n+\t * high-order bytes of req-\u003ecrypt_ctx-\u003ebc_dun silently dropped in\n+\t * virtblk_setup_cmd(), reusing the same IV across data units that\n+\t * only differ in those high-order bytes.\n+\t */\n+\tif (max_dun_bytes \u003e sizeof(u64))\n+\t\treturn -EINVAL;\n+\n+\terr = virtblk_get_crypto_modes(vblk, crypto_modes_supported);\n+\tif (err)\n+\t\treturn err;\n+\n+\treturn virtblk_init_inline_crypto(max_slots, max_dun_bytes, key_types,\n+\t\t\t\t\t   crypto_modes_supported, \u0026vdev-\u003edev);\n+}\n+\n static int virtblk_probe(struct virtio_device *vdev)\n {\n \tstruct virtio_blk *vblk;\n@@ -1540,6 +1690,26 @@ static int virtblk_probe(struct virtio_device *vdev)\n \t\t\tgoto out_cleanup_disk;\n \t}\n \n+\tif (IS_ENABLED(CONFIG_VIRTBLK_CRYPTO_VIRTUALIZATION) \u0026\u0026\n+\t    virtio_has_feature(vblk-\u003evdev, VIRTIO_BLK_F_INLINE_ENCRYPTION)) {\n+\t\tif (virtio_has_feature(vblk-\u003evdev, VIRTIO_BLK_F_ZONED)) {\n+\t\t\tdev_warn(\u0026vdev-\u003edev,\n+\t\t\t\t \"zoned device does not support inline encryption, disabling it\\n\");\n+\t\t} else {\n+\t\t\t/* Initialize supported crypto capabilities */\n+\t\t\terr = virtblk_init_crypto(vblk);\n+\t\t\tif (!err) {\n+\t\t\t\tif (!virtblk_crypto_register(vblk-\u003edisk-\u003equeue))\n+\t\t\t\t\tdev_warn(\u0026vdev-\u003edev,\n+\t\t\t\t\t\t\"failed to register inline crypto profile with the block layer, continuing without inline crypto support\\n\");\n+\t\t\t} else {\n+\t\t\t\tdev_warn(\u0026vdev-\u003edev,\n+\t\t\t\t\t\"inline crypto init failed: %d, continuing without inline crypto support\\n\",\n+\t\t\t\t\terr);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n \terr = device_add_disk(\u0026vdev-\u003edev, vblk-\u003edisk, virtblk_attr_groups);\n \tif (err)\n \t\tgoto out_cleanup_disk;\n@@ -1672,6 +1842,7 @@ static unsigned int features[] = {\n \tVIRTIO_BLK_F_FLUSH, VIRTIO_BLK_F_TOPOLOGY, VIRTIO_BLK_F_CONFIG_WCE,\n \tVIRTIO_BLK_F_MQ, VIRTIO_BLK_F_DISCARD, VIRTIO_BLK_F_WRITE_ZEROES,\n \tVIRTIO_BLK_F_SECURE_ERASE, VIRTIO_BLK_F_ZONED,\n+\tVIRTIO_BLK_F_INLINE_ENCRYPTION,\n };\n \n static struct virtio_driver virtio_blk = {\ndiff --git a/drivers/block/virtio_blk_crypto_ext.c b/drivers/block/virtio_blk_crypto_ext.c\nnew file mode 100644\nindex 0000000000000..00b6d410d3036\n--- /dev/null\n+++ b/drivers/block/virtio_blk_crypto_ext.c\n@@ -0,0 +1,283 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+\n+#include \u003clinux/export.h\u003e\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/mutex.h\u003e\n+#include \u003clinux/container_of.h\u003e\n+#include \u003clinux/blk-crypto.h\u003e\n+#include \u003clinux/blk-crypto-profile.h\u003e\n+#include \u003clinux/virtio_blk.h\u003e\n+#include \u003clinux/virtio_blk_crypto_ext.h\u003e\n+\n+struct virtblk_crypto_profile {\n+\tstruct blk_crypto_profile profile;\n+\tstruct virtblk_crypto_variant_ops *ops;\n+};\n+\n+static struct virtblk_crypto_profile g_vdcp;\n+static bool g_crypto_profile_initialized;\n+static struct device *virtblk_profile_owner;\n+static unsigned int g_max_slots;\n+static unsigned int g_max_dun_bytes;\n+static unsigned int g_key_types;\n+static DEFINE_MUTEX(virtblk_crypto_init_lock);\n+static DEFINE_MUTEX(virtblk_crypto_ops_lock);\n+\n+bool virtblk_crypto_register(struct request_queue *q)\n+{\n+\treturn blk_crypto_register(\u0026g_vdcp.profile, q);\n+}\n+EXPORT_SYMBOL_GPL(virtblk_crypto_register);\n+\n+/*\n+ * Returns the variant ops registered for @profile's virtblk_crypto_profile\n+ * with a module reference held on ops-\u003eowner, or NULL if none are\n+ * registered. Pairs with virtblk_crypto_ops_put().\n+ *\n+ * Holding a module reference for the duration of each dispatch call (rather\n+ * than just dereferencing the raw pointer) is what makes it safe for the\n+ * module that implements these ops (e.g. drivers/soc/qcom/crypto_virt.c) to\n+ * be rmmod'd: module removal will fail/block until every in-flight dispatch\n+ * call has released its reference, instead of racing with a concurrent\n+ * virtblk_set_crypto_ops(NULL) and the ops table disappearing mid-call.\n+ */\n+static struct virtblk_crypto_variant_ops *\n+virtblk_crypto_ops_get(struct blk_crypto_profile *profile)\n+{\n+\tstruct virtblk_crypto_profile *vdcp =\n+\t\tcontainer_of(profile, struct virtblk_crypto_profile, profile);\n+\tstruct virtblk_crypto_variant_ops *ops;\n+\n+\tmutex_lock(\u0026virtblk_crypto_ops_lock);\n+\tops = vdcp-\u003eops;\n+\tif (ops \u0026\u0026 !try_module_get(ops-\u003eowner))\n+\t\tops = NULL;\n+\tmutex_unlock(\u0026virtblk_crypto_ops_lock);\n+\n+\treturn ops;\n+}\n+\n+static void virtblk_crypto_ops_put(struct virtblk_crypto_variant_ops *ops)\n+{\n+\tmodule_put(ops-\u003eowner);\n+}\n+\n+static int virtblk_crypto_keyslot_program(struct blk_crypto_profile *profile,\n+\t\t\t\t\t   const struct blk_crypto_key *key,\n+\t\t\t\t\t   unsigned int slot)\n+{\n+\tstruct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);\n+\tint ret;\n+\n+\tif (!ops || !ops-\u003eprogram_key) {\n+\t\tif (ops)\n+\t\t\tvirtblk_crypto_ops_put(ops);\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tret = ops-\u003eprogram_key(key, slot);\n+\tvirtblk_crypto_ops_put(ops);\n+\tif (ret)\n+\t\tpr_err(\"program hardware wrapped key failed: slot=%u ret=%d\\n\", slot, ret);\n+\n+\treturn ret;\n+}\n+\n+static int virtblk_crypto_keyslot_evict(struct blk_crypto_profile *profile,\n+\t\t\t\t\t const struct blk_crypto_key *key,\n+\t\t\t\t\t unsigned int slot)\n+{\n+\tstruct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);\n+\tint ret;\n+\n+\tif (!ops || !ops-\u003eevict_key) {\n+\t\tif (ops)\n+\t\t\tvirtblk_crypto_ops_put(ops);\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tret = ops-\u003eevict_key(slot);\n+\tvirtblk_crypto_ops_put(ops);\n+\tif (ret)\n+\t\tpr_err(\"evict keyslot %u failed: %d\\n\", slot, ret);\n+\n+\treturn ret;\n+}\n+\n+static int virtblk_crypto_derive_sw_secret(struct blk_crypto_profile *profile,\n+\t\t\t\t\t    const u8 *eph_key,\n+\t\t\t\t\t    size_t eph_key_size,\n+\t\t\t\t\t    u8 sw_secret[BLK_CRYPTO_SW_SECRET_SIZE])\n+{\n+\tstruct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);\n+\tint ret;\n+\n+\tif (!ops || !ops-\u003ederive_sw_secret_key) {\n+\t\tif (ops)\n+\t\t\tvirtblk_crypto_ops_put(ops);\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tret = ops-\u003ederive_sw_secret_key(eph_key, eph_key_size, sw_secret);\n+\tvirtblk_crypto_ops_put(ops);\n+\tif (ret)\n+\t\tpr_err(\"derive software secret failed: %d\\n\", ret);\n+\n+\treturn ret;\n+}\n+\n+static int virtblk_crypto_generate_key(struct blk_crypto_profile *profile,\n+\t\t\t\t\tu8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])\n+{\n+\tstruct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);\n+\tint ret;\n+\n+\tif (!ops || !ops-\u003egenerate_key) {\n+\t\tif (ops)\n+\t\t\tvirtblk_crypto_ops_put(ops);\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tret = ops-\u003egenerate_key(lt_key);\n+\tvirtblk_crypto_ops_put(ops);\n+\tif (ret \u003c 0)\n+\t\tpr_err(\"generate hardware wrapped key failed: %d\\n\", ret);\n+\n+\treturn ret;\n+}\n+\n+static int virtblk_crypto_prepare_key(struct blk_crypto_profile *profile,\n+\t\t\t\t       const u8 *lt_key, size_t lt_key_size,\n+\t\t\t\t       u8 eph_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])\n+{\n+\tstruct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);\n+\tint ret;\n+\n+\tif (!ops || !ops-\u003eprepare_key) {\n+\t\tif (ops)\n+\t\t\tvirtblk_crypto_ops_put(ops);\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tret = ops-\u003eprepare_key(lt_key, lt_key_size, eph_key);\n+\tvirtblk_crypto_ops_put(ops);\n+\tif (ret \u003c 0)\n+\t\tpr_err(\"prepare hardware wrapped key failed: %d\\n\", ret);\n+\n+\treturn ret;\n+}\n+\n+static int virtblk_crypto_import_key(struct blk_crypto_profile *profile,\n+\t\t\t\t      const u8 *raw_key, size_t raw_key_size,\n+\t\t\t\t      u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])\n+{\n+\tstruct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);\n+\tint ret;\n+\n+\tif (!ops || !ops-\u003eimport_key) {\n+\t\tif (ops)\n+\t\t\tvirtblk_crypto_ops_put(ops);\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tret = ops-\u003eimport_key(raw_key, raw_key_size, lt_key);\n+\tvirtblk_crypto_ops_put(ops);\n+\tif (ret \u003c 0)\n+\t\tpr_err(\"import hardware wrapped key failed: %d\\n\", ret);\n+\n+\treturn ret;\n+}\n+\n+static const struct blk_crypto_ll_ops virtblk_crypto_ops = {\n+\t.keyslot_program\t= virtblk_crypto_keyslot_program,\n+\t.keyslot_evict\t\t= virtblk_crypto_keyslot_evict,\n+\t.derive_sw_secret\t= virtblk_crypto_derive_sw_secret,\n+\t.generate_key\t\t= virtblk_crypto_generate_key,\n+\t.prepare_key\t\t= virtblk_crypto_prepare_key,\n+\t.import_key\t\t= virtblk_crypto_import_key,\n+};\n+\n+int virtblk_init_inline_crypto(unsigned int max_slots, unsigned int max_dun_bytes,\n+\t\t\t       unsigned int key_types,\n+\t\t\t       const unsigned int crypto_modes_supported[BLK_ENCRYPTION_MODE_MAX],\n+\t\t\t       struct device *dev)\n+{\n+\tstruct blk_crypto_profile *profile = \u0026g_vdcp.profile;\n+\tunsigned int key_type_supported = 0;\n+\tint err = 0;\n+\n+\tdev_info(dev, \"probing inline crypto capabilities\\n\");\n+\n+\tmutex_lock(\u0026virtblk_crypto_init_lock);\n+\n+\t/*\n+\t * profile is a single, process-wide blk_crypto_profile shared by every\n+\t * VIRTIO_BLK_F_INLINE_ENCRYPTION device. Only the first device to get\n+\t * here actually initializes it; any other device just reuses it as-is\n+\t * if its negotiated capabilities match. A mismatch means this device's\n+\t * capabilities don't actually correspond to what the shared profile\n+\t * was set up for (wrong keyslot count, DUN size, or key types), which\n+\t * is a correctness/security concern, not just a cosmetic one -- fail\n+\t * instead of silently registering a profile that doesn't match what\n+\t * this device supports.\n+\t */\n+\tif (g_crypto_profile_initialized) {\n+\t\tif (max_slots != g_max_slots || max_dun_bytes != g_max_dun_bytes ||\n+\t\t    key_types != g_key_types) {\n+\t\t\tdev_warn(dev,\n+\t\t\t\t \"inline crypto profile already initialized by %s (max_slots=%u max_dun_bytes=%u key_types=0x%x); \"\n+\t\t\t\t \"this device reports max_slots=%u max_dun_bytes=%u key_types=0x%x -- sharing one \"\n+\t\t\t\t \"blk_crypto_profile across multiple VIRTIO_BLK_F_INLINE_ENCRYPTION devices with differing \"\n+\t\t\t\t \"capabilities is not supported, refusing to enable inline crypto for this device\\n\",\n+\t\t\t\t dev_name(virtblk_profile_owner), g_max_slots, g_max_dun_bytes, g_key_types,\n+\t\t\t\t max_slots, max_dun_bytes, key_types);\n+\t\t\terr = -EINVAL;\n+\t\t}\n+\t\tgoto out_unlock;\n+\t}\n+\n+\tif (key_types \u0026 VIRTIO_BLK_CRYPTO_KEY_TYPE_RAW)\n+\t\tkey_type_supported |= BLK_CRYPTO_KEY_TYPE_RAW;\n+\tif (key_types \u0026 VIRTIO_BLK_CRYPTO_KEY_TYPE_HW_WRAPPED)\n+\t\tkey_type_supported |= BLK_CRYPTO_KEY_TYPE_HW_WRAPPED;\n+\n+\terr = blk_crypto_profile_init(profile, max_slots);\n+\tif (err) {\n+\t\tdev_err(dev, \"crypto profile initialization failed: %d\\n\", err);\n+\t\tgoto out_unlock;\n+\t}\n+\n+\tprofile-\u003ell_ops = virtblk_crypto_ops;\n+\tprofile-\u003emax_dun_bytes_supported = max_dun_bytes;\n+\tprofile-\u003ekey_types_supported = key_type_supported;\n+\tprofile-\u003edev = dev;\n+\tmemcpy(profile-\u003emodes_supported, crypto_modes_supported,\n+\t       BLK_ENCRYPTION_MODE_MAX * sizeof(unsigned int));\n+\n+\tvirtblk_profile_owner = dev;\n+\tg_max_slots = max_slots;\n+\tg_max_dun_bytes = max_dun_bytes;\n+\tg_key_types = key_types;\n+\tg_crypto_profile_initialized = true;\n+\n+\tdev_info(dev, \"inline crypto profile initialized\\n\");\n+\n+out_unlock:\n+\tmutex_unlock(\u0026virtblk_crypto_init_lock);\n+\treturn err;\n+}\n+EXPORT_SYMBOL_GPL(virtblk_init_inline_crypto);\n+\n+void virtblk_set_crypto_ops(struct virtblk_crypto_variant_ops *ops)\n+{\n+\tif (!g_crypto_profile_initialized)\n+\t\tpr_warn(\"virtio blk crypto profile hasn't been initialized\\n\");\n+\n+\tmutex_lock(\u0026virtblk_crypto_ops_lock);\n+\tg_vdcp.ops = ops;\n+\tmutex_unlock(\u0026virtblk_crypto_ops_lock);\n+}\n+EXPORT_SYMBOL_GPL(virtblk_set_crypto_ops);\n+\n+MODULE_DESCRIPTION(\"Virtio block inline crypto extension\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig\nindex 2b524154d9fb3..e1f383b4dc63d 100644\n--- a/drivers/soc/qcom/Kconfig\n+++ b/drivers/soc/qcom/Kconfig\n@@ -294,10 +294,40 @@ endif\n # Options selected by other drivers from different subsystems must be outside\n # of the menuconfig if-block:\n \n+config QCOM_ICE_SLOTS\n+\ttristate \"Qualcomm ICE keyslot partitioning for VM guests\"\n+\tdepends on ARCH_QCOM || COMPILE_TEST\n+\tdepends on BLK_CRYPTO_PROXY\n+\tdepends on BLK_INLINE_ENCRYPTION\n+\thelp\n+\t  Parses the qcom,ice-keyslot-map device-tree node and provides\n+\t  per-VM ICE keyslot accounting and virtual-to-physical slot\n+\t  translation for guest VMs sharing ICE hardware on Qualcomm\n+\t  platforms.\n+\n+\t  When enabled, guest virtual keyslot indices are mapped to the\n+\t  physical ICE keyslot range allocated to each VM, preventing one\n+\t  VM from accessing another VM's keyslots.\n+\n+\t  Say M here when multiple VMs share ICE keyslots on a Qualcomm\n+\t  platform.  If unsure, say N.\n+\n config QCOM_INLINE_CRYPTO_ENGINE\n \ttristate\n \tselect QCOM_SCM\n \n+config QCOM_CRYPTO_VIRT\n+\ttristate \"Qualcomm Technologies, Inc. Crypto Virt driver\"\n+\tdepends on VIRTBLK_CRYPTO_VIRTUALIZATION\n+\tdepends on QCOM_SCM\n+\tdefault VIRTBLK_CRYPTO_VIRTUALIZATION if ARCH_QCOM\n+\thelp\n+\t  GVM-side hardware-wrapped-key SCM operations exposed to\n+\t  virtio_blk's inline crypto layer: per-slot key programming and\n+\t  eviction, and key derive/generate/prepare/import.\n+\t  Say Y here to compile the driver as a part of kernel or M to compile\n+\t  as a module.\n+\n config QCOM_KRYO_L2_ACCESSORS\n \tbool\n \tdepends on ARM64\ndiff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile\nindex 798643be3590c..952a57554f9d9 100644\n--- a/drivers/soc/qcom/Makefile\n+++ b/drivers/soc/qcom/Makefile\n@@ -38,6 +38,8 @@ obj-$(CONFIG_QCOM_LLCC) += llcc-qcom.o\n obj-$(CONFIG_QCOM_KRYO_L2_ACCESSORS) +=\tkryo-l2-accessors.o\n obj-$(CONFIG_QCOM_ICC_BWMON)\t+= icc-bwmon.o\n qcom_ice-objs\t\t\t+= ice.o\n+obj-$(CONFIG_QCOM_ICE_SLOTS)\t+= qcom_ice_slots.o\n obj-$(CONFIG_QCOM_INLINE_CRYPTO_ENGINE)\t+= qcom_ice.o\n+obj-$(CONFIG_QCOM_CRYPTO_VIRT)\t+= crypto_virt.o\n obj-$(CONFIG_QCOM_PBS) +=\tqcom-pbs.o\n obj-$(CONFIG_QCOM_UBWC_CONFIG) += ubwc_config.o\ndiff --git a/drivers/soc/qcom/crypto_virt.c b/drivers/soc/qcom/crypto_virt.c\nnew file mode 100644\nindex 0000000000000..93c7993fb4a5d\n--- /dev/null\n+++ b/drivers/soc/qcom/crypto_virt.c\n@@ -0,0 +1,197 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/of.h\u003e\n+#include \u003clinux/platform_device.h\u003e\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/blk-crypto.h\u003e\n+#include \u003clinux/virtio_blk_crypto_ext.h\u003e\n+#include \u003clinux/firmware/qcom/qcom_scm.h\u003e\n+\n+static unsigned int g_wrapped_key_size;\n+\n+static int crypto_virt_program_key(const struct blk_crypto_key *key,\n+\t\t\t\t   unsigned int slot)\n+{\n+\tu32 dus_512_units;\n+\tint ret;\n+\n+\tif (!key || !key-\u003esize) {\n+\t\tpr_err(\"%s: invalid key\\n\", __func__);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* Only AES-256-XTS is supported so far. */\n+\tif (key-\u003ecrypto_cfg.crypto_mode !=\n+\t    BLK_ENCRYPTION_MODE_AES_256_XTS) {\n+\t\tpr_err_ratelimited(\"Unsupported crypto mode: %d\\n\",\n+\t\t\t\t    key-\u003ecrypto_cfg.crypto_mode);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/* qcom_scm_ice_set_key()'s data_unit_size is expressed in 512-byte units */\n+\tdus_512_units = key-\u003ecrypto_cfg.data_unit_size / 512;\n+\n+\tret = qcom_scm_ice_set_key(slot, key-\u003ebytes, key-\u003esize,\n+\t\tQCOM_SCM_ICE_CIPHER_AES_256_XTS, dus_512_units);\n+\tif (ret)\n+\t\tpr_err(\"%s: slot=%u ret=%d\\n\", __func__, slot, ret);\n+\n+\treturn ret;\n+}\n+\n+static int crypto_virt_invalidate_key(unsigned int slot)\n+{\n+\tint ret;\n+\n+\tret = qcom_scm_ice_invalidate_key(slot);\n+\tif (ret)\n+\t\tpr_err(\"%s: slot=%u ret=%d\\n\", __func__, slot, ret);\n+\n+\treturn ret;\n+}\n+\n+static int crypto_virt_derive_sw_secret_key(const u8 *eph_key, size_t eph_key_size,\n+\t\t\t\t\t    u8 sw_secret[BLK_CRYPTO_SW_SECRET_SIZE])\n+{\n+\tint ret;\n+\n+\tret = qcom_scm_derive_sw_secret(eph_key, eph_key_size,\n+\t\t\t\t\tsw_secret, BLK_CRYPTO_SW_SECRET_SIZE);\n+\tif (ret == -EIO || ret == -EINVAL)\n+\t\tret = -EBADMSG; /* probably invalid key */\n+\n+\tif (ret)\n+\t\tpr_err(\"%s: ret=%d\\n\", __func__, ret);\n+\n+\treturn ret;\n+}\n+\n+static int crypto_virt_generate_key(u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])\n+{\n+\tint ret;\n+\n+\tif (!g_wrapped_key_size) {\n+\t\tpr_err(\"%s: no expected wrapped key size\\n\", __func__);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tret = qcom_scm_generate_ice_key(lt_key, g_wrapped_key_size);\n+\tif (ret) {\n+\t\tpr_err(\"%s: generate hardware wrapped key failed: %d\\n\", __func__, ret);\n+\t\treturn ret;\n+\t}\n+\n+\treturn g_wrapped_key_size;\n+}\n+\n+static int crypto_virt_prepare_key(const u8 *lt_key, size_t lt_key_size,\n+\t\t\t\t   u8 eph_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])\n+{\n+\tint ret;\n+\n+\tif (!g_wrapped_key_size) {\n+\t\tpr_err(\"%s: no expected wrapped key size\\n\", __func__);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tret = qcom_scm_prepare_ice_key(lt_key, lt_key_size,\n+\t\t\t\t\teph_key, g_wrapped_key_size);\n+\tif (ret == -EIO || ret == -EINVAL)\n+\t\tret = -EBADMSG; /* probably invalid key */\n+\n+\tif (ret) {\n+\t\tpr_err(\"%s: prepare hardware wrapped key failed: %d\\n\", __func__, ret);\n+\t\treturn ret;\n+\t}\n+\n+\treturn g_wrapped_key_size;\n+}\n+\n+static int crypto_virt_import_key(const u8 *raw_key, size_t raw_key_size,\n+\t\t\t\t  u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])\n+{\n+\tint ret;\n+\n+\tif (!g_wrapped_key_size) {\n+\t\tpr_err(\"%s: no expected wrapped key size\\n\", __func__);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tret = qcom_scm_import_ice_key(raw_key, raw_key_size,\n+\t\t\t\t       lt_key, g_wrapped_key_size);\n+\tif (ret) {\n+\t\tpr_err(\"%s: import hardware wrapped key failed: %d\\n\", __func__, ret);\n+\t\treturn ret;\n+\t}\n+\n+\treturn g_wrapped_key_size;\n+}\n+\n+static struct virtblk_crypto_variant_ops virtblk_crypto_qcom_vops = {\n+\t.owner = THIS_MODULE,\n+\t.program_key = crypto_virt_program_key,\n+\t.evict_key = crypto_virt_invalidate_key,\n+\t.derive_sw_secret_key = crypto_virt_derive_sw_secret_key,\n+\t.generate_key = crypto_virt_generate_key,\n+\t.prepare_key = crypto_virt_prepare_key,\n+\t.import_key = crypto_virt_import_key,\n+};\n+\n+static int crypto_virt_probe(struct platform_device *pdev)\n+{\n+\tint ret;\n+\n+\tret = of_property_read_u32(pdev-\u003edev.of_node, \"qcom,wrapped-key-size\",\n+\t\t\t\t    \u0026g_wrapped_key_size);\n+\tif (ret)\n+\t\tdev_warn(\u0026pdev-\u003edev, \"qcom,wrapped-key-size not found\\n\");\n+\n+\tif (!g_wrapped_key_size ||\n+\t    g_wrapped_key_size \u003e BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE) {\n+\t\tdev_err(\u0026pdev-\u003edev,\n+\t\t\t\"invalid qcom,wrapped-key-size %u, won't support generate/import/prepare hardware wrapped key\\n\",\n+\t\t\tg_wrapped_key_size);\n+\t\tg_wrapped_key_size = 0;\n+\t}\n+\n+\tvirtblk_set_crypto_ops(\u0026virtblk_crypto_qcom_vops);\n+\treturn 0;\n+}\n+\n+static void crypto_virt_remove(struct platform_device *pdev)\n+{\n+\tvirtblk_set_crypto_ops(NULL);\n+}\n+\n+static const struct of_device_id crypto_virt_of_match[] = {\n+\t{ .compatible = \"qcom,crypto-virt\" },\n+\t{ }\n+};\n+MODULE_DEVICE_TABLE(of, crypto_virt_of_match);\n+\n+static struct platform_driver crypto_virt_driver = {\n+\t.probe = crypto_virt_probe,\n+\t.remove = crypto_virt_remove,\n+\t.driver = {\n+\t\t.name = \"crypto_virt\",\n+\t\t.of_match_table = crypto_virt_of_match,\n+\t},\n+};\n+\n+static int __init crypto_virt_init(void)\n+{\n+\treturn platform_driver_register(\u0026crypto_virt_driver);\n+}\n+module_init(crypto_virt_init);\n+\n+#if IS_MODULE(CONFIG_QCOM_CRYPTO_VIRT)\n+static void __exit crypto_virt_exit(void)\n+{\n+\tplatform_driver_unregister(\u0026crypto_virt_driver);\n+}\n+module_exit(crypto_virt_exit);\n+#endif\n+\n+MODULE_DESCRIPTION(\"Qualcomm Technologies, Inc. Crypto Virt Driver\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/soc/qcom/qcom_ice_slots.c b/drivers/soc/qcom/qcom_ice_slots.c\nnew file mode 100644\nindex 0000000000000..364ac93077c12\n--- /dev/null\n+++ b/drivers/soc/qcom/qcom_ice_slots.c\n@@ -0,0 +1,232 @@\n+// SPDX-License-Identifier: GPL-2.0-only\n+/*\n+ * qcom_ice_slots.c - Qualcomm ICE keyslot partitioning for guest VMs\n+ *\n+ * Implements bcp_slot_virt_ops: translates a (guest_id, virtual-slot) pair to\n+ * a physical ICE keyslot index using a per-VM allocation table parsed from\n+ * the device-tree node with compatible = \"qcom,ice-keyslot-map\".\n+ *\n+ * Device-tree layout:\n+ *\n+ *   ice_keyslot_map: ice-keyslot-map {\n+ *       compatible = \"qcom,ice-keyslot-map\";\n+ *       #address-cells = \u003c1\u003e;\n+ *       #size-cells = \u003c0\u003e;\n+ *\n+ *       vm@3  { reg = \u003c3\u003e;  qcom,max-ice-slots = \u003c16\u003e; qcom,ice-slot-offset = \u003c0\u003e;  };\n+ *       vm@52 { reg = \u003c52\u003e; qcom,max-ice-slots = \u003c32\u003e; qcom,ice-slot-offset = \u003c16\u003e; };\n+ *   };\n+ *\n+ * Each child entry maps a guest (reg = guest_id) to a contiguous physical keyslot\n+ * range [slot_offset .. slot_offset + max_ice_slots).\n+ *\n+ * Entry 0 is always the host's own reservation.  Entries 1+ are guest\n+ * reservations.  The host's entry is used by ufs-qcom to size its\n+ * blk_crypto_profile; it is excluded from the guest-facing translation table\n+ * so that blk-crypto-proxy can never accidentally route a guest request into\n+ * the host's physical keyslots.\n+ *\n+ * The ufs-qcom driver reads the host slot info and validates all entries\n+ * against the hardware slot count directly via the OF API, with no symbol\n+ * dependency on this module.\n+ */\n+\n+#include \u003clinux/module.h\u003e\n+#include \u003clinux/of.h\u003e\n+#include \u003clinux/platform_device.h\u003e\n+#include \u003clinux/rcupdate.h\u003e\n+#include \u003clinux/slab.h\u003e\n+#include \u003clinux/blk-crypto-profile.h\u003e\n+#include \u003clinux/blk-crypto-proxy.h\u003e\n+\n+#define QCOM_ICE_SLOTS_MAX_ENTRIES\t8\n+\n+struct qcom_ice_slot_entry {\n+\tu32 guest_id;\n+\tu32 max_slots;\n+\tu32 slot_offset;\n+};\n+\n+struct qcom_ice_slots {\n+\tstruct qcom_ice_slot_entry entries[QCOM_ICE_SLOTS_MAX_ENTRIES];\n+\tunsigned int num_entries;\n+};\n+\n+/*\n+ * There is at most one qcom,ice-keyslot-map platform node per SoC.  A single\n+ * global pointer is set at probe time and cleared at remove time.  The\n+ * hot-path read (from bcp_slot_virt_ops callbacks) is protected by RCU;\n+ * probe/remove serialise via the platform driver guarantee.\n+ */\n+static struct qcom_ice_slots __rcu *g_ice_slots;\n+\n+static struct qcom_ice_slots *virt_lookup(struct blk_crypto_profile *profile)\n+{\n+\t/* Single UFS controller: profile argument is not needed. */\n+\treturn rcu_dereference(g_ice_slots);\n+}\n+\n+static int qcom_ice_slots_get_guest_slots(struct blk_crypto_profile *profile,\n+\t\t\t\t\t  u32 guest_id)\n+{\n+\tstruct qcom_ice_slots *virt = virt_lookup(profile);\n+\tunsigned int i;\n+\n+\tif (!virt)\n+\t\treturn -ENOKEY;\n+\n+\t/* entries[0] is the host; guest entries start at index 1. */\n+\tfor (i = 1; i \u003c virt-\u003enum_entries; i++) {\n+\t\tif (virt-\u003eentries[i].guest_id == guest_id)\n+\t\t\treturn virt-\u003eentries[i].max_slots;\n+\t}\n+\treturn -ENOKEY;\n+}\n+\n+static int qcom_ice_slots_vslot_to_pslot(struct blk_crypto_profile *profile,\n+\t\t\t\t\t u32 guest_id, u32 virt_slot,\n+\t\t\t\t\t unsigned int *phy_slot_out)\n+{\n+\tstruct qcom_ice_slots *virt = virt_lookup(profile);\n+\tunsigned int i;\n+\n+\tif (!virt)\n+\t\treturn -ENOKEY;\n+\n+\tfor (i = 1; i \u003c virt-\u003enum_entries; i++) {\n+\t\tif (virt-\u003eentries[i].guest_id != guest_id)\n+\t\t\tcontinue;\n+\t\tif (virt_slot \u003e= virt-\u003eentries[i].max_slots)\n+\t\t\treturn -EINVAL;\n+\t\t*phy_slot_out = virt-\u003eentries[i].slot_offset + virt_slot;\n+\t\treturn 0;\n+\t}\n+\treturn -ENOKEY;\n+}\n+\n+static const struct bcp_slot_virt_ops qcom_slot_virt_ops = {\n+\t.get_guest_slots = qcom_ice_slots_get_guest_slots,\n+\t.vslot_to_pslot = qcom_ice_slots_vslot_to_pslot,\n+};\n+\n+static int qcom_ice_slots_probe(struct platform_device *pdev)\n+{\n+\tstruct device *dev = \u0026pdev-\u003edev;\n+\tstruct device_node *child;\n+\tstruct qcom_ice_slots *virt;\n+\tunsigned int idx = 0, total_slots = 0;\n+\tint ret = 0;\n+\n+\tvirt = devm_kzalloc(dev, sizeof(*virt), GFP_KERNEL);\n+\tif (!virt)\n+\t\treturn -ENOMEM;\n+\n+\tfor_each_child_of_node(dev-\u003eof_node, child) {\n+\t\tu32 guest_id, max_slots, slot_offset;\n+\t\tunsigned int j;\n+\n+\t\tif (idx \u003e= QCOM_ICE_SLOTS_MAX_ENTRIES) {\n+\t\t\tdev_err(dev, \"too many vm entries (\u003e %u)\\n\",\n+\t\t\t\tQCOM_ICE_SLOTS_MAX_ENTRIES);\n+\t\t\tret = -EINVAL;\n+\t\t\tof_node_put(child);\n+\t\t\tgoto err_free;\n+\t\t}\n+\n+\t\tif (of_property_read_u32(child, \"reg\", \u0026guest_id))\n+\t\t\tcontinue;\n+\t\tif (of_property_read_u32(child, \"qcom,max-ice-slots\", \u0026max_slots))\n+\t\t\tcontinue;\n+\t\tif (of_property_read_u32(child, \"qcom,ice-slot-offset\", \u0026slot_offset)) {\n+\t\t\tdev_err(dev, \"missing qcom,ice-slot-offset for guest_id=%u\\n\",\n+\t\t\t\tguest_id);\n+\t\t\tret = -EINVAL;\n+\t\t\tof_node_put(child);\n+\t\t\tgoto err_free;\n+\t\t}\n+\n+\t\tif (idx \u003e 0 \u0026\u0026\n+\t\t    slot_offset \u003c\n+\t\t    virt-\u003eentries[idx - 1].slot_offset +\n+\t\t    virt-\u003eentries[idx - 1].max_slots) {\n+\t\t\tdev_err(dev, \"slot overlap: guest_id=%u overlaps guest_id=%u\\n\",\n+\t\t\t\tguest_id, virt-\u003eentries[idx - 1].guest_id);\n+\t\t\tret = -EINVAL;\n+\t\t\tof_node_put(child);\n+\t\t\tgoto err_free;\n+\t\t}\n+\n+\t\tfor (j = 0; j \u003c idx; j++) {\n+\t\t\tif (virt-\u003eentries[j].guest_id == guest_id) {\n+\t\t\t\tdev_err(dev, \"duplicate guest_id=%u\\n\", guest_id);\n+\t\t\t\tret = -EINVAL;\n+\t\t\t\tof_node_put(child);\n+\t\t\t\tgoto err_free;\n+\t\t\t}\n+\t\t}\n+\n+\t\tvirt-\u003eentries[idx].guest_id = guest_id;\n+\t\tvirt-\u003eentries[idx].max_slots = max_slots;\n+\t\tvirt-\u003eentries[idx].slot_offset = slot_offset;\n+\t\ttotal_slots += max_slots;\n+\t\tidx++;\n+\t}\n+\n+\tif (idx == 0) {\n+\t\tdev_err(dev, \"no VM entries found in qcom,ice-keyslot-map\\n\");\n+\t\tret = -EINVAL;\n+\t\tgoto err_free;\n+\t}\n+\n+\tvirt-\u003enum_entries = idx;\n+\n+\t/*\n+\t * Publish the singleton.  From this point on, bcp_slot_virt_ops\n+\t * callbacks can resolve virt via rcu_dereference(g_ice_slots).\n+\t */\n+\trcu_assign_pointer(g_ice_slots, virt);\n+\n+\tret = bcp_register_slot_virt_ops(\u0026qcom_slot_virt_ops);\n+\tif (ret) {\n+\t\tdev_err(dev, \"failed to register slot_virt_ops: %d\\n\", ret);\n+\t\tgoto err_free;\n+\t}\n+\n+\tdev_info(dev, \"registered: %u VMs, %u total ICE slots\\n\",\n+\t\t idx, total_slots);\n+\treturn 0;\n+\n+err_free:\n+\treturn ret;\n+}\n+\n+static void qcom_ice_slots_remove(struct platform_device *pdev)\n+{\n+\tbcp_unregister_slot_virt_ops(\u0026qcom_slot_virt_ops);\n+\t/*\n+\t * Clear the singleton under RCU so that any concurrent ioctl that\n+\t * already took the read lock and is mid-lookup sees either the old\n+\t * valid pointer or NULL, never a freed pointer.\n+\t */\n+\trcu_assign_pointer(g_ice_slots, NULL);\n+\tsynchronize_rcu();\n+}\n+\n+static const struct of_device_id qcom_ice_slots_of_match[] = {\n+\t{ .compatible = \"qcom,ice-keyslot-map\" },\n+\t{}\n+};\n+MODULE_DEVICE_TABLE(of, qcom_ice_slots_of_match);\n+\n+static struct platform_driver qcom_ice_slots_driver = {\n+\t.probe  = qcom_ice_slots_probe,\n+\t.remove = qcom_ice_slots_remove,\n+\t.driver = {\n+\t\t.name           = \"qcom-ice-slots\",\n+\t\t.of_match_table = qcom_ice_slots_of_match,\n+\t},\n+};\n+module_platform_driver(qcom_ice_slots_driver);\n+\n+MODULE_DESCRIPTION(\"Qualcomm ICE keyslot partitioning for guest VMs\");\n+MODULE_LICENSE(\"GPL\");\ndiff --git a/drivers/ufs/core/ufshcd-crypto.h b/drivers/ufs/core/ufshcd-crypto.h\nindex 8f66db94e179a..2fc5601c0f769 100644\n--- a/drivers/ufs/core/ufshcd-crypto.h\n+++ b/drivers/ufs/core/ufshcd-crypto.h\n@@ -15,13 +15,21 @@\n static inline void ufshcd_prepare_lrbp_crypto(struct request *rq,\n \t\t\t\t\t      struct ufshcd_lrb *lrbp)\n {\n-\tif (!rq || !rq-\u003ecrypt_keyslot) {\n+\tif (!rq) {\n \t\tlrbp-\u003ecrypto_key_slot = -1;\n \t\treturn;\n \t}\n \n-\tlrbp-\u003ecrypto_key_slot = blk_crypto_keyslot_index(rq-\u003ecrypt_keyslot);\n-\tlrbp-\u003edata_unit_num = rq-\u003ecrypt_ctx-\u003ebc_dun[0];\n+\tif (rq-\u003ecrypt_keyslot) {\n+\t\tlrbp-\u003ecrypto_key_slot = blk_crypto_keyslot_index(rq-\u003ecrypt_keyslot);\n+\t\tlrbp-\u003edata_unit_num = rq-\u003ecrypt_ctx-\u003ebc_dun[0];\n+\t} else if (rq-\u003ecrypt_ctx \u0026\u0026 rq-\u003ecrypt_ctx-\u003ebc_slot.data_unit_size_bits) {\n+\t\tlrbp-\u003ecrypto_key_slot = rq-\u003ecrypt_ctx-\u003ebc_slot.phy_slot;\n+\t\tlrbp-\u003edata_unit_num = rq-\u003ecrypt_ctx-\u003ebc_dun[0];\n+\t} else {\n+\t\tlrbp-\u003ecrypto_key_slot = -1;\n+\t}\n+\n }\n \n static inline void\ndiff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c\nindex 62396212a0a73..0611ab50f4cc9 100644\n--- a/drivers/ufs/host/ufs-qcom.c\n+++ b/drivers/ufs/host/ufs-qcom.c\n@@ -163,6 +163,74 @@ static inline void ufs_qcom_ice_enable(struct ufs_qcom_host *host)\n \t\tqcom_ice_enable(host-\u003eice);\n }\n \n+/**\n+ * ufs_qcom_ice_parse_slot_table() - parse qcom,ice-keyslot-map DT node\n+ * @dev:          UFS controller device\n+ * @hw_max_slots: total physical ICE keyslots reported by REG_UFS_CCAP\n+ * @num_slots:    receives host max_ice_slots (0 = no partitioning)\n+ * @slot_offset:  receives host ice-slot-offset\n+ *\n+ * Parses the qcom,ice-keyslot-map device-tree node.  The first child entry\n+ * is the host's own reservation; subsequent children are guest reservations.\n+ * Validates that no entry's range exceeds @hw_max_slots and that the sum of\n+ * all entries does not exceed @hw_max_slots.\n+ *\n+ * If no qcom,ice-keyslot-map phandle is present, sets @num_slots to 0 and\n+ * returns 0.  Returns -EINVAL if any entry or the total exceeds @hw_max_slots.\n+ */\n+static int ufs_qcom_ice_parse_slot_table(struct device *dev,\n+\t\t\t\t\t unsigned int hw_max_slots,\n+\t\t\t\t\t unsigned int *num_slots,\n+\t\t\t\t\t unsigned int *slot_offset)\n+{\n+\tstruct device_node *slots_np, *child;\n+\tunsigned int total_slots = 0;\n+\tbool first = true;\n+\tint ret = 0;\n+\n+\t*num_slots   = 0;\n+\t*slot_offset = 0;\n+\n+\tslots_np = of_parse_phandle(dev-\u003eof_node, \"qcom,ice-keyslot-map\", 0);\n+\tif (!slots_np)\n+\t\treturn 0;\n+\n+\tfor_each_child_of_node(slots_np, child) {\n+\t\tu32 off, max;\n+\n+\t\tif (of_property_read_u32(child, \"qcom,ice-slot-offset\", \u0026off) ||\n+\t\t    of_property_read_u32(child, \"qcom,max-ice-slots\", \u0026max))\n+\t\t\tcontinue;\n+\n+\t\tif (off + max \u003e hw_max_slots) {\n+\t\t\tdev_err(dev,\n+\t\t\t\t\"ice-keyslot-map: slots [%u..%u) exceed hw max %u\\n\",\n+\t\t\t\toff, off + max, hw_max_slots);\n+\t\t\tof_node_put(child);\n+\t\t\tret = -EINVAL;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (first) {\n+\t\t\t*num_slots   = max;\n+\t\t\t*slot_offset = off;\n+\t\t\tfirst = false;\n+\t\t}\n+\t\ttotal_slots += max;\n+\t}\n+\n+\tof_node_put(slots_np);\n+\n+\tif (!ret \u0026\u0026 total_slots \u003e hw_max_slots) {\n+\t\tdev_err(dev,\n+\t\t\t\"ice-keyslot-map: total slots %u exceed hw max %u\\n\",\n+\t\t\ttotal_slots, hw_max_slots);\n+\t\tret = -EINVAL;\n+\t}\n+\n+\treturn ret;\n+}\n+\n static const struct blk_crypto_ll_ops ufs_qcom_crypto_ops; /* forward decl */\n \n static int ufs_qcom_ice_init(struct ufs_qcom_host *host)\n@@ -173,6 +241,8 @@ static int ufs_qcom_ice_init(struct ufs_qcom_host *host)\n \tstruct qcom_ice *ice;\n \tunion ufs_crypto_capabilities caps;\n \tunion ufs_crypto_cap_entry cap;\n+\tunsigned int num_slots, slot_offset;\n+\tunsigned int hw_max_slots;\n \tint err;\n \tint i;\n \n@@ -192,7 +262,23 @@ static int ufs_qcom_ice_init(struct ufs_qcom_host *host)\n \tcaps.reg_val = cpu_to_le32(ufshcd_readl(hba, REG_UFS_CCAP));\n \n \t/* The number of keyslots supported is (CFGC+1) */\n-\terr = devm_blk_crypto_profile_init(dev, profile, caps.config_count + 1);\n+\thw_max_slots = caps.config_count + 1;\n+\n+\t/*\n+\t * Parse the qcom,ice-keyslot-map DT node: validate all entries against\n+\t * the hardware slot count and read the host's own reservation.  If no\n+\t * partitioning is configured (num_slots == 0), the profile manages the\n+\t * full hardware slot range.\n+\t */\n+\terr = ufs_qcom_ice_parse_slot_table(dev, hw_max_slots,\n+\t\t\t\t\t    \u0026num_slots, \u0026slot_offset);\n+\tif (err) {\n+\t\tdev_err(dev, \"failed to parse ICE slot table: %d\\n\", err);\n+\t\treturn err;\n+\t}\n+\n+\terr = devm_blk_crypto_profile_init(dev, profile,\n+\t\t\t\t\t   num_slots ? num_slots : hw_max_slots);\n \tif (err)\n \t\treturn err;\n \n@@ -201,6 +287,9 @@ static int ufs_qcom_ice_init(struct ufs_qcom_host *host)\n \tprofile-\u003ekey_types_supported = qcom_ice_get_supported_key_type(ice);\n \tprofile-\u003edev = dev;\n \n+\tif (num_slots)\n+\t\tprofile-\u003eslot_offset = slot_offset;\n+\n \t/*\n \t * Currently this driver only supports AES-256-XTS.  All known versions\n \t * of ICE support it, but to be safe make sure it is really declared in\ndiff --git a/include/linux/blk-crypto-profile.h b/include/linux/blk-crypto-profile.h\nindex 4f39e9cd75766..a9bdc05abfa3c 100644\n--- a/include/linux/blk-crypto-profile.h\n+++ b/include/linux/blk-crypto-profile.h\n@@ -162,6 +162,15 @@ struct blk_crypto_profile {\n \t */\n \tstruct device *dev;\n \n+\t/**\n+\t * @slot_offset: offset added to the slot array index to obtain the\n+\t * physical ICE keyslot number.  Zero in the common case.  Set to a\n+\t * non-zero value by storage drivers that share ICE hardware across\n+\t * multiple guests, where the host's keyslots do not start at physical\n+\t * slot 0.\n+\t */\n+\tunsigned int slot_offset;\n+\n \t/* private: The following fields shouldn't be accessed by drivers. */\n \n \t/* Number of keyslots, or 0 if not applicable */\ndiff --git a/include/linux/blk-crypto-proxy.h b/include/linux/blk-crypto-proxy.h\nnew file mode 100644\nindex 0000000000000..6cf1ff0703e9f\n--- /dev/null\n+++ b/include/linux/blk-crypto-proxy.h\n@@ -0,0 +1,100 @@\n+/* SPDX-License-Identifier: GPL-2.0-only */\n+\n+#ifndef __LINUX_BLK_CRYPTO_PROXY_H\n+#define __LINUX_BLK_CRYPTO_PROXY_H\n+\n+#include \u003cuapi/linux/blk-crypto-proxy.h\u003e\n+#include \u003clinux/types.h\u003e\n+\n+struct blk_crypto_profile;\n+\n+/**\n+ * struct bcp_hypervisor_ops - hypervisor VM identity operations\n+ *\n+ * Translates a hypervisor-specific VM fd to the opaque u32 vm_id used\n+ * throughout blk-crypto-proxy.  Register once at module init time.\n+ */\n+struct bcp_hypervisor_ops {\n+\t/**\n+\t * @get_guest_id: Resolve @vm_fd to an opaque guest identifier.\n+\t *\n+\t * Verify the caller is permitted to act on behalf of the VM and write\n+\t * its u32 id to @guest_id_out.  The value is passed verbatim to\n+\t * bcp_slot_virt_ops callbacks.\n+\t *\n+\t * Returns 0 on success, -errno on failure.\n+\t */\n+\tint (*get_guest_id)(int vm_fd, u32 *guest_id_out);\n+};\n+\n+/**\n+ * bcp_register_hypervisor_ops() - register the hypervisor op-set\n+ * @ops: op-set to register; must remain valid until unregistered.\n+ *\n+ * Returns 0 on success, -EBUSY if an op-set is already registered.\n+ */\n+int bcp_register_hypervisor_ops(const struct bcp_hypervisor_ops *ops);\n+\n+/**\n+ * bcp_unregister_hypervisor_ops() - unregister the hypervisor op-set\n+ * @ops: must be the pointer that was passed to bcp_register_hypervisor_ops().\n+ *\n+ * Blocks until all in-flight callers have finished, then clears the\n+ * registration.  Safe to call from module exit.\n+ */\n+void bcp_unregister_hypervisor_ops(const struct bcp_hypervisor_ops *ops);\n+\n+/**\n+ * struct bcp_slot_virt_ops - ICE keyslot virtualization operations\n+ *\n+ * Per-VM ICE keyslot accounting and virtual-to-physical slot translation.\n+ * The implementation owns the slot allocation table and is registered once\n+ * at platform driver probe time.\n+ *\n+ * @profile is passed to every callback so an implementation supporting\n+ * multiple storage controllers can distinguish between them.\n+ *\n+ * All callbacks may be called concurrently and must not sleep (called\n+ * under RCU read lock).\n+ */\n+struct bcp_slot_virt_ops {\n+\t/**\n+\t * @get_guest_slots: Return the number of ICE keyslots allocated to @guest_id.\n+\t *\n+\t * Returns the slot count (≥ 1) on success, -ENOKEY if @guest_id is\n+\t * not in the allocation table.\n+\t */\n+\tint (*get_guest_slots)(struct blk_crypto_profile *profile, u32 guest_id);\n+\n+\t/**\n+\t * @vslot_to_pslot: Translate a VM-local virtual slot to a physical slot.\n+\t * @guest_id:       hypervisor-assigned VM identifier.\n+\t * @virt_slot:   0-based slot index within @guest_id's allocation.\n+\t * @phy_slot_out: receives the physical ICE keyslot index on success.\n+\t *\n+\t * Returns 0 on success, -ENOKEY if @guest_id is unknown, -EINVAL if\n+\t * @virt_slot \u003e= the VM's allocation.\n+\t */\n+\tint (*vslot_to_pslot)(struct blk_crypto_profile *profile,\n+\t\t\t      u32 guest_id, u32 virt_slot,\n+\t\t\t      unsigned int *phy_slot_out);\n+};\n+\n+/**\n+ * bcp_register_slot_virt_ops() - register the slot-virt op-set\n+ * @ops: op-set to register; must remain valid until unregistered.\n+ *\n+ * Returns 0 on success, -EBUSY if an op-set is already registered.\n+ */\n+int bcp_register_slot_virt_ops(const struct bcp_slot_virt_ops *ops);\n+\n+/**\n+ * bcp_unregister_slot_virt_ops() - unregister the slot-virt op-set\n+ * @ops: must be the pointer passed to bcp_register_slot_virt_ops().\n+ *\n+ * Blocks until all in-flight callers have finished, then clears the\n+ * registration.  Safe to call from module exit.\n+ */\n+void bcp_unregister_slot_virt_ops(const struct bcp_slot_virt_ops *ops);\n+\n+#endif /* __LINUX_BLK_CRYPTO_PROXY_H */\ndiff --git a/include/linux/blk-crypto.h b/include/linux/blk-crypto.h\nindex 938ff536838c7..c9f436b6b2ef4 100644\n--- a/include/linux/blk-crypto.h\n+++ b/include/linux/blk-crypto.h\n@@ -119,9 +119,28 @@ struct blk_crypto_key {\n #define BLK_CRYPTO_MAX_IV_SIZE\t\t32\n #define BLK_CRYPTO_DUN_ARRAY_SIZE\t(BLK_CRYPTO_MAX_IV_SIZE / sizeof(u64))\n \n+/**\n+ * struct blk_crypto_slot - physical slot context for slot-based inline crypto\n+ * @phy_slot:           Physical ICE keyslot index (already resolved from virt).\n+ * @data_unit_size_bits: log2 of the encryption data unit size; used by\n+ *                       __bio_crypt_advance() to increment the DUN correctly\n+ *                       when a bio is split.  0 means unknown/unset.\n+ *\n+ * Used when a bio carries inline crypto context by physical slot index rather\n+ * than by a blk_crypto_key pointer (i.e. bc_key == NULL in bio_crypt_ctx).\n+ * Set by crypto_vblk when building the bio for a GVM VIRTIO_BLK_T_CRYPTO_IN/OUT\n+ * request; left zeroed for all other bio types.\n+ */\n+struct blk_crypto_slot {\n+\tunsigned int phy_slot;\n+\tunsigned int data_unit_size_bits;\n+};\n+\n /**\n  * struct bio_crypt_ctx - an inline encryption context\n  * @bc_key: the key, algorithm, and data unit size to use\n+ * @bc_slot: physical slot + data_unit_size_bits for slot-based crypto\n+ *           (used when bc_key == NULL)\n  * @bc_dun: the data unit number (starting IV) to use\n  *\n  * A bio_crypt_ctx specifies that the contents of the bio will be encrypted (for\n@@ -130,6 +149,7 @@ struct blk_crypto_key {\n  */\n struct bio_crypt_ctx {\n \tconst struct blk_crypto_key\t*bc_key;\n+\tstruct blk_crypto_slot\t\t bc_slot;\n \tu64\t\t\t\tbc_dun[BLK_CRYPTO_DUN_ARRAY_SIZE];\n };\n \n@@ -152,10 +172,18 @@ void bio_crypt_set_ctx(struct bio *bio, const struct blk_crypto_key *key,\n \t\t       const u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE],\n \t\t       gfp_t gfp_mask);\n \n+void bio_crypt_set_ctx_by_slot(struct bio *bio,\n+\t\t\t       const struct blk_crypto_slot *slot,\n+\t\t\t       const u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE],\n+\t\t\t       gfp_t gfp_mask);\n+\n bool bio_crypt_dun_is_contiguous(const struct bio_crypt_ctx *bc,\n \t\t\t\t unsigned int bytes,\n \t\t\t\t const u64 next_dun[BLK_CRYPTO_DUN_ARRAY_SIZE]);\n \n+void bio_crypt_dun_increment(u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE],\n+\t\t\t     unsigned int inc);\n+\n int blk_crypto_init_key(struct blk_crypto_key *blk_key,\n \t\t\tconst u8 *key_bytes, size_t key_size,\n \t\t\tenum blk_crypto_key_type key_type,\ndiff --git a/include/linux/virtio_blk_crypto_ext.h b/include/linux/virtio_blk_crypto_ext.h\nnew file mode 100644\nindex 0000000000000..3fe66029ac0d4\n--- /dev/null\n+++ b/include/linux/virtio_blk_crypto_ext.h\n@@ -0,0 +1,78 @@\n+/* SPDX-License-Identifier: GPL-2.0 */\n+\n+#ifndef __LINUX_VIRTIO_BLK_CRYPTO_EXT_H\n+#define __LINUX_VIRTIO_BLK_CRYPTO_EXT_H\n+\n+#include \u003clinux/blk-crypto.h\u003e\n+\n+struct blk_crypto_profile;\n+struct blk_crypto_key;\n+struct device;\n+struct request_queue;\n+\n+#if IS_ENABLED(CONFIG_VIRTBLK_CRYPTO_VIRTUALIZATION)\n+struct virtblk_crypto_variant_ops {\n+\t/*\n+\t * Module providing the function pointers below. virtio_blk_crypto_ext.c\n+\t * pins it with try_module_get()/module_put() around every call, so that\n+\t * the module implementing these ops can be safely rmmod'd: the unload\n+\t * will simply block/fail until no dispatch call is in flight, instead\n+\t * of racing with one.\n+\t */\n+\tstruct module *owner;\n+\tint (*program_key)(const struct blk_crypto_key *key,\n+\t\t\t   unsigned int slot);\n+\tint (*evict_key)(unsigned int slot);\n+\tint (*derive_sw_secret_key)(const u8 *eph_key, size_t eph_key_size,\n+\t\t\t\t    u8 sw_secret[BLK_CRYPTO_SW_SECRET_SIZE]);\n+\tint (*generate_key)(u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);\n+\tint (*prepare_key)(const u8 *lt_key, size_t lt_key_size,\n+\t\t\t   u8 eph_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);\n+\tint (*import_key)(const u8 *raw_key, size_t raw_key_size,\n+\t\t\t  u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);\n+};\n+\n+/*\n+ * Probes the platform's inline-encryption capabilities and initializes the\n+ * shared blk_crypto_profile singleton (once) with the keyslot/DUN limits,\n+ * supported key types, wrapped key size, and supported crypto modes reported\n+ * by the device over virtio config space / VIRTIO_BLK_T_GET_CRYPTO_MODES.\n+ *\n+ * Safe to call from multiple devices, including concurrently: only the\n+ * first caller actually initializes the shared profile, every other caller\n+ * just validates its own capabilities against what was already negotiated.\n+ */\n+int virtblk_init_inline_crypto(unsigned int max_slots, unsigned int max_dun_bytes,\n+\t\t\t\tunsigned int key_types,\n+\t\t\t\tconst unsigned int crypto_modes_supported[BLK_ENCRYPTION_MODE_MAX],\n+\t\t\t\tstruct device *dev);\n+\n+/*\n+ * Registers the (already-initialized) shared blk_crypto_profile with the\n+ * given request queue. Returns false (and leaves the queue without inline\n+ * crypto) if the queue's block-integrity support conflicts with inline\n+ * encryption; see blk_crypto_register().\n+ */\n+bool virtblk_crypto_register(struct request_queue *q);\n+\n+void virtblk_set_crypto_ops(struct virtblk_crypto_variant_ops *ops);\n+\n+#else\n+\n+static inline int virtblk_init_inline_crypto(unsigned int max_slots,\n+\t\tunsigned int max_dun_bytes, unsigned int key_types,\n+\t\tconst unsigned int crypto_modes_supported[BLK_ENCRYPTION_MODE_MAX],\n+\t\tstruct device *dev)\n+{\n+\treturn -EOPNOTSUPP;\n+}\n+\n+static inline bool virtblk_crypto_register(struct request_queue *q)\n+{\n+\treturn false;\n+}\n+\n+\n+#endif\n+\n+#endif /* __LINUX_VIRTIO_BLK_CRYPTO_EXT_H */\ndiff --git a/include/uapi/linux/blk-crypto-proxy.h b/include/uapi/linux/blk-crypto-proxy.h\nnew file mode 100644\nindex 0000000000000..dc8adc8ef5edb\n--- /dev/null\n+++ b/include/uapi/linux/blk-crypto-proxy.h\n@@ -0,0 +1,122 @@\n+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */\n+\n+#ifndef __UAPI_LINUX_BLK_CRYPTO_PROXY_H\n+#define __UAPI_LINUX_BLK_CRYPTO_PROXY_H\n+\n+#include \u003clinux/types.h\u003e\n+#include \u003clinux/ioctl.h\u003e\n+\n+#define BCP_DIR_READ     0\n+#define BCP_DIR_WRITE    1\n+\n+/*\n+ * BCP_BIND_CONTEXT - bind a host block device and hypervisor VM fd.\n+ *\n+ * Must be called once after open(), before any other ioctl.\n+ * Returns -EBUSY if already bound, -EOPNOTSUPP if no hypervisor op-set\n+ * is registered.\n+ *\n+ * @block_dev_fd: fd of the host block device to bind.\n+ * @vm_fd:        hypervisor VM fd identifying the guest.\n+ * @reserved:     must be zero.\n+ */\n+struct bcp_bind_context_arg {\n+\t__s32 block_dev_fd;\n+\t__s32 vm_fd;\n+\t__u32 reserved;\n+};\n+\n+/*\n+ * BCP_GET_CRYPTO_CAPS - query crypto capabilities of the bound block device.\n+ *\n+ * Requires BCP_BIND_CONTEXT; returns -ENXIO otherwise.\n+ *\n+ * @key_types_supported:  [out] BLK_CRYPTO_KEY_TYPE_* bitmask.\n+ * @max_dun_bytes:        [out] maximum DUN bytes supported.\n+ * @max_slots:            [out] maximum ICE keyslots available for the bound VM;\n+ *                              0 if the VM is not found in the table.\n+ * @num_modes:            [in] capacity of the buffer pointed to by @modes_ptr,\n+ *                              in entries. [out] number of entries actually\n+ *                              written to @modes_ptr (may be less than the\n+ *                              given capacity; the caller must use this\n+ *                              value, not its own capacity, to know how many\n+ *                              entries are valid).\n+ * @modes_ptr:             [in] pointer to a caller-allocated __u32 array of\n+ *                              at least @num_modes (as given) entries. Must\n+ *                              be non-NULL if @num_modes (as given) is \u003e 0.\n+ *                              On return, holds a per-mode data_unit_size\n+ *                              bitmask array indexed by VIRTIO_BLK_CRYPTO_MODE_*\n+ *                              (virtio wire numbering, uapi/linux/virtio_blk.h)\n+ *                              -- NOT by enum blk_crypto_mode_num. Index 0 is\n+ *                              reserved and always 0, matching struct\n+ *                              virtio_blk_crypto_modes.modes[].\n+ *\n+ * @modes_ptr is a pointer + count rather than a fixed-size array embedded in\n+ * this struct so that sizeof(struct bcp_get_crypto_caps_arg) -- and hence the\n+ * _IOWR-encoded ioctl number -- does not depend on VIRTIO_BLK_CRYPTO_MODE_MAX.\n+ * The caller and this kernel may be built against different virtio_blk.h\n+ * versions (and thus different values of that constant); embedding a\n+ * VIRTIO_BLK_CRYPTO_MODE_MAX-sized array directly in this struct would make\n+ * the ioctl fail to even dispatch (-ENOTTY) whenever the two disagree.\n+ */\n+\n+struct bcp_get_crypto_caps_arg {\n+\t__u32 key_types_supported;\n+\t__u32 max_dun_bytes;\n+\t__u32 max_slots;\n+\t__u32 num_modes;\n+\t__aligned_u64 modes_ptr;\n+};\n+\n+/*\n+ * BCP_SUBMIT_IO_BY_VSLOT - submit an encrypted bio using a virtual slot.\n+ *\n+ * The kernel resolves virt_slot to a physical ICE keyslot and submits the\n+ * I/O synchronously.  Large requests are split at data-unit boundaries\n+ * (BIO_MAX_VECS pages per bio).  Requires BCP_BIND_CONTEXT; returns -ENXIO\n+ * otherwise.\n+ *\n+ * @virt_slot:           guest-visible slot index (0-based within the VM's range).\n+ * @direction:           BCP_DIR_READ or BCP_DIR_WRITE.\n+ * @flags:               must be BCP_SUBMIT_IO_F_IOV.\n+ * @data_unit_size_bits: log2 of the encryption data unit size in bytes.\n+ * @sector:              start sector (512-byte units).\n+ * @dun:                 data unit number (single 64-bit limb, little-endian).\n+ * @iov_ptr:             pointer to scatter-gather array of struct bcp_iovec.\n+ * @iov_cnt:             number of entries in @iov_ptr[].\n+ * @reserved2:           must be zero.\n+ *\n+ * @sector, @dun and @iov_ptr use __aligned_u64 to guarantee identical struct\n+ * layout between 32-bit and 64-bit callers, as required by\n+ * .compat_ioctl = compat_ptr_ioctl.\n+ */\n+\n+/* Maximum iovec segments per BCP_SUBMIT_IO_BY_VSLOT call (matches UIO_MAXIOV). */\n+#define BCP_MAX_IOV            1024\n+\n+#define BCP_SUBMIT_IO_F_IOV    (1U \u003c\u003c 0)\t/* scatter-gather mode; must always be set */\n+\n+struct bcp_iovec {\n+\t__u64 iov_base;\n+\t__u64 iov_len;\n+};\n+\n+struct bcp_submit_io_by_vslot_arg {\n+\t__u32 virt_slot;\n+\t__u32 direction;\n+\t__u32 flags;\n+\t__u32 data_unit_size_bits;\n+\t__aligned_u64 sector;\n+\t__aligned_u64 dun;\n+\t__aligned_u64 iov_ptr;\n+\t__u32 iov_cnt;\n+\t__u32 reserved2;\n+};\n+\n+#define BCP_IOC_MAGIC    0xC7\n+\n+#define BCP_BIND_CONTEXT       _IOW(BCP_IOC_MAGIC, 1, struct bcp_bind_context_arg)\n+#define BCP_GET_CRYPTO_CAPS    _IOWR(BCP_IOC_MAGIC, 2, struct bcp_get_crypto_caps_arg)\n+#define BCP_SUBMIT_IO_BY_VSLOT  _IOW(BCP_IOC_MAGIC, 3, struct bcp_submit_io_by_vslot_arg)\n+\n+#endif /* __UAPI_LINUX_BLK_CRYPTO_PROXY_H */\ndiff --git a/include/uapi/linux/virtio_blk.h b/include/uapi/linux/virtio_blk.h\nindex 3744e4da1b2a7..c1941f7fe0196 100644\n--- a/include/uapi/linux/virtio_blk.h\n+++ b/include/uapi/linux/virtio_blk.h\n@@ -42,6 +42,7 @@\n #define VIRTIO_BLK_F_WRITE_ZEROES\t14\t/* WRITE ZEROES is supported */\n #define VIRTIO_BLK_F_SECURE_ERASE\t16 /* Secure Erase is supported */\n #define VIRTIO_BLK_F_ZONED\t\t17\t/* Zoned block device */\n+#define VIRTIO_BLK_F_INLINE_ENCRYPTION\t\t22\t/* Inline Encryption is supported */\n \n /* Legacy feature bits */\n #ifndef VIRTIO_BLK_NO_LEGACY\n@@ -148,6 +149,18 @@ struct virtio_blk_config {\n \t\t__u8 model;\n \t\t__u8 unused2[3];\n \t} zoned;\n+\n+\t/* Inline Encryption device characteristics (if VIRTIO_BLK_F_INLINE_ENCRYPTION) */\n+\tstruct virtio_blk_enc_characteristics {\n+\t\t__virtio16 max_slots;\n+\t\t__u8 max_dun_bytes;\n+/* Bitmask values for virtio_blk_enc_characteristics.key_types */\n+#define VIRTIO_BLK_CRYPTO_KEY_TYPE_RAW\t(1 \u003c\u003c 0)\n+#define VIRTIO_BLK_CRYPTO_KEY_TYPE_HW_WRAPPED\t(1 \u003c\u003c 1)\n+\t\t/* Bitmask of supported key types: VIRTIO_BLK_CRYPTO_KEY_TYPE_* */\n+\t\t__u8 key_types;\n+\t\t__virtio32 unused3;\n+\t} enc_characteristics;\n } __attribute__((packed));\n \n /*\n@@ -206,6 +219,15 @@ struct virtio_blk_config {\n /* Reset All zones command */\n #define VIRTIO_BLK_T_ZONE_RESET_ALL 26\n \n+/* Inline-encrypted write: crypto_msg set in outhdr */\n+#define VIRTIO_BLK_T_CRYPTO_OUT\t\t27\n+\n+/* Inline-encrypted read: crypto_msg set in outhdr */\n+#define VIRTIO_BLK_T_CRYPTO_IN\t\t28\n+\n+/* Get inline crypto modes */\n+#define VIRTIO_BLK_T_GET_CRYPTO_MODES\t30\n+\n #ifndef VIRTIO_BLK_NO_LEGACY\n /* Barrier before this op. */\n #define VIRTIO_BLK_T_BARRIER\t0x80000000\n@@ -225,6 +247,46 @@ struct virtio_blk_outhdr {\n \t__virtio64 sector;\n };\n \n+/*\n+ * Crypto message descriptor, appended to the outhdr of a\n+ * VIRTIO_BLK_T_CRYPTO_OUT or VIRTIO_BLK_T_CRYPTO_IN request.\n+ */\n+struct virtio_blk_crypto_msg {\n+\t/* virtual key slot index */\n+\t__virtio32 slot;\n+\t/* log2 of the data unit size in bytes */\n+\t__virtio32 data_unit_size_bits;\n+\t/* data unit number (DUN / IV) for this request */\n+\t__virtio64 dun;\n+};\n+\n+/*\n+ * Crypto mode numbers used in VIRTIO_BLK_T_GET_CRYPTO_MODES replies and in\n+ * indexing struct virtio_blk_crypto_modes.modes[] below. These numbers are\n+ * assigned by the virtio spec and are stable: a number is never reused for\n+ * a different crypto mode, and additional crypto modes are assigned new,\n+ * higher numbers.\n+ */\n+enum {\n+\tVIRTIO_BLK_CRYPTO_MODE_INVALID,\n+\tVIRTIO_BLK_CRYPTO_MODE_AES_256_XTS,\n+\t__VIRTIO_BLK_CRYPTO_MODE_MAX,\t/* sentinel: always one past the last real mode */\n+};\n+\n+/* Highest crypto mode number defined by this version of the header. */\n+#define VIRTIO_BLK_CRYPTO_MODE_MAX (__VIRTIO_BLK_CRYPTO_MODE_MAX - 1)\n+\n+/* Reply to a VIRTIO_BLK_T_GET_CRYPTO_MODES request. */\n+struct virtio_blk_crypto_modes {\n+\t/*\n+\t * modes[N], for crypto mode number N \u003c= VIRTIO_BLK_CRYPTO_MODE_MAX, is\n+\t * a bitmask of the data unit sizes with which crypto mode N can be\n+\t * used: bit i is set if a data unit size of (1 \u003c\u003c i) bytes is\n+\t * supported. modes[0] is reserved and always 0.\n+\t */\n+\t__virtio32 modes[__VIRTIO_BLK_CRYPTO_MODE_MAX];\n+};\n+\n /*\n  * Supported zoned device models.\n  */\n"
}

2/1 2026/08/27 16:58 agent patch-evaluator
5m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "VIRTBLK_CRYPTO_VIRTUALIZATION",
    "BLK_CRYPTO_PROXY",
    "QCOM_ICE_SLOTS",
    "QCOM_CRYPTO_VIRT",
    "BLK_INLINE_ENCRYPTION"
  ],
  "FocusSymbols": [
    "virtblk_init_crypto",
    "virtblk_get_crypto_modes",
    "virtblk_init_inline_crypto",
    "virtblk_crypto_keyslot_program",
    "bcp_ioctl",
    "qcom_ice_slots_probe",
    "ufs_qcom_ice_parse_slot_table"
  ],
  "Reasoning": "The patch introduces inline encryption support for virtio-blk guests and a host-side proxy device. While the host-side proxy (/dev/blk-crypto-proxy) is mostly unreachable because it relies on hypervisor ops that are not registered in-tree, and the guest-side I/O path is unreachable because key programming requires Qualcomm TrustZone SMC calls that fail in standard QEMU, the virtio-blk device probe path is fully reachable. When a virtio-blk device with VIRTIO_BLK_F_INLINE_ENCRYPTION is probed, the driver reads the virtio config space and sends a VIRTIO_BLK_T_GET_CRYPTO_MODES request to initialize the crypto profile. This probe path can be fuzzed by syzkaller's virtio fuzzer. Additionally, the patch adds DT parsing logic for Qualcomm ICE slot partitioning, which is reachable if the corresponding DT nodes are injected.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit b864a4f707165a5c167ea55c41d670ae48e4993e
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Aug 27 16:58:51 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,crypto-virt.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,crypto-virt.yaml
new file mode 100644
index 0000000000000..5eb204f297627
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/qcom/qcom,crypto-virt.yaml
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/soc/qcom/qcom,crypto-virt.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: QCOM GVM Crypto Virt driver
+
+description: |
+  GVM-side hardware-wrapped-key SCM operations backing virtio_blk's inline
+  crypto layer. The host does not report a wrapped key size over virtio, so
+  it is provided here instead.
+
+maintainers:
+  - Linlin Zhang <linlin.zhang@oss.qualcomm.com>
+
+properties:
+  compatible:
+    const: qcom,crypto-virt
+
+  qcom,wrapped-key-size:
+    description: |
+      Size, in bytes, of the hardware-wrapped key generated/prepared/imported
+      via SCM calls.
+    $ref: /schemas/types.yaml#/definitions/uint32
+    minimum: 64
+    maximum: 128
+
+required:
+  - compatible
+
+additionalProperties: false
+
+examples:
+  - |
+    crypto_virt {
+        compatible = "qcom,crypto-virt";
+        qcom,wrapped-key-size = <100>;
+    };
diff --git a/block/blk-crypto-internal.h b/block/blk-crypto-internal.h
index 2c7a0446572aa..32c720aabba7f 100644
--- a/block/blk-crypto-internal.h
+++ b/block/blk-crypto-internal.h
@@ -26,9 +26,6 @@ int blk_crypto_sysfs_register(struct gendisk *disk);
 
 void blk_crypto_sysfs_unregister(struct gendisk *disk);
 
-void bio_crypt_dun_increment(u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE],
-			     unsigned int inc);
-
 bool bio_crypt_rq_ctx_compatible(struct request *rq, struct bio *bio);
 
 bool bio_crypt_ctx_mergeable(struct bio_crypt_ctx *bc1, unsigned int bc1_bytes,
@@ -176,7 +173,7 @@ static inline void bio_crypt_do_front_merge(struct request *rq,
 blk_status_t __blk_crypto_rq_get_keyslot(struct request *rq);
 static inline blk_status_t blk_crypto_rq_get_keyslot(struct request *rq)
 {
-	if (blk_crypto_rq_is_encrypted(rq))
+	if (blk_crypto_rq_is_encrypted(rq) && rq->crypt_ctx->bc_key)
 		return __blk_crypto_rq_get_keyslot(rq);
 	return BLK_STS_OK;
 }
diff --git a/block/blk-crypto-profile.c b/block/blk-crypto-profile.c
index 53126c091b0be..64202d64a0182 100644
--- a/block/blk-crypto-profile.c
+++ b/block/blk-crypto-profile.c
@@ -218,14 +218,15 @@ blk_crypto_find_and_grab_keyslot(struct blk_crypto_profile *profile,
 }
 
 /**
- * blk_crypto_keyslot_index() - Get the index of a keyslot
+ * blk_crypto_keyslot_index() - Get the physical index of a keyslot
  * @slot: a keyslot that blk_crypto_get_keyslot() returned
  *
- * Return: the 0-based index of the keyslot within the device's keyslots.
+ * Return: the physical ICE keyslot index, i.e. the 0-based position of @slot
+ * within the profile's keyslot array plus @slot->profile->slot_offset.
  */
 unsigned int blk_crypto_keyslot_index(struct blk_crypto_keyslot *slot)
 {
-	return slot - slot->profile->slots;
+	return (slot - slot->profile->slots) + slot->profile->slot_offset;
 }
 EXPORT_SYMBOL_GPL(blk_crypto_keyslot_index);
 
diff --git a/block/blk-crypto.c b/block/blk-crypto.c
index bc3a9f59574b1..2212d06d3c112 100644
--- a/block/blk-crypto.c
+++ b/block/blk-crypto.c
@@ -113,11 +113,31 @@ void bio_crypt_set_ctx(struct bio *bio, const struct blk_crypto_key *key,
 
 	bc->bc_key = key;
 	memcpy(bc->bc_dun, dun, sizeof(bc->bc_dun));
+	memset(&bc->bc_slot, 0, sizeof(bc->bc_slot));
 
 	bio->bi_crypt_context = bc;
 }
 EXPORT_SYMBOL_GPL(bio_crypt_set_ctx);
 
+void bio_crypt_set_ctx_by_slot(struct bio *bio,
+			       const struct blk_crypto_slot *slot,
+			       const u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE],
+			       gfp_t gfp_mask)
+{
+	struct bio_crypt_ctx *bc;
+
+	WARN_ON_ONCE(!(gfp_mask & __GFP_DIRECT_RECLAIM));
+
+	bc = mempool_alloc(bio_crypt_ctx_pool, gfp_mask);
+
+	bc->bc_key = NULL;
+	bc->bc_slot = *slot;
+	memcpy(bc->bc_dun, dun, sizeof(bc->bc_dun));
+
+	bio->bi_crypt_context = bc;
+}
+EXPORT_SYMBOL_GPL(bio_crypt_set_ctx_by_slot);
+
 void __bio_crypt_free_ctx(struct bio *bio)
 {
 	mempool_free(bio->bi_crypt_context, bio_crypt_ctx_pool);
@@ -156,8 +176,12 @@ void __bio_crypt_advance(struct bio *bio, unsigned int bytes)
 {
 	struct bio_crypt_ctx *bc = bio->bi_crypt_context;
 
-	bio_crypt_dun_increment(bc->bc_dun,
-				bytes >> bc->bc_key->data_unit_size_bits);
+	if (bc->bc_key)
+		bio_crypt_dun_increment(bc->bc_dun,
+					bytes >> bc->bc_key->data_unit_size_bits);
+	else if (bc->bc_slot.data_unit_size_bits)
+		bio_crypt_dun_increment(bc->bc_dun,
+					bytes >> bc->bc_slot.data_unit_size_bits);
 }
 
 /*
@@ -169,7 +193,14 @@ bool bio_crypt_dun_is_contiguous(const struct bio_crypt_ctx *bc,
 				 const u64 next_dun[BLK_CRYPTO_DUN_ARRAY_SIZE])
 {
 	int i;
-	unsigned int carry = bytes >> bc->bc_key->data_unit_size_bits;
+	unsigned int carry;
+
+	if (bc->bc_key)
+		carry = bytes >> bc->bc_key->data_unit_size_bits;
+	else if (bc->bc_slot.data_unit_size_bits) {
+		carry = bytes >> bc->bc_slot.data_unit_size_bits;
+	} else
+		return false;
 
 	for (i = 0; i < BLK_CRYPTO_DUN_ARRAY_SIZE; i++) {
 		if (bc->bc_dun[i] + carry != next_dun[i])
@@ -198,7 +229,12 @@ static bool bio_crypt_ctx_compatible(struct bio_crypt_ctx *bc1,
 	if (!bc1)
 		return !bc2;
 
-	return bc2 && bc1->bc_key == bc2->bc_key;
+	if (bc1->bc_key)
+		return bc2 && bc1->bc_key == bc2->bc_key;
+	else
+		return bc2 && !bc2->bc_key &&
+		       bc1->bc_slot.phy_slot == bc2->bc_slot.phy_slot &&
+		       bc1->bc_slot.data_unit_size_bits == bc2->bc_slot.data_unit_size_bits;
 }
 
 bool bio_crypt_rq_ctx_compatible(struct request *rq, struct bio *bio)
@@ -260,6 +296,19 @@ bool __blk_crypto_submit_bio(struct bio *bio)
 		return false;
 	}
 
+	if (!bc_key) {
+		/*
+		* Slot path: the ICE keyslot was pre-programmed by the
+		* hypervisor. The target device must natively support inline
+		* encryption; there is no fallback for slot-based crypto.
+		*/
+		if (!bdev_get_queue(bdev)->crypto_profile) {
+			bio_endio_status(bio, BLK_STS_NOTSUPP);
+			return false;
+		}
+		return true;
+	}
+
 	/*
 	 * If the device does not natively support the encryption context, try to use
 	 * the fallback if available.
diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
index 858320b6ebb7e..48ad79734c09e 100644
--- a/drivers/block/Kconfig
+++ b/drivers/block/Kconfig
@@ -176,6 +176,21 @@ config BLK_DEV_LOOP
 
 	  Most users will answer N here.
 
+config BLK_CRYPTO_PROXY
+	tristate "Inline encryption proxy for virtio-blk guests"
+	depends on BLK_INLINE_ENCRYPTION
+	help
+	  Provides /dev/blk-crypto-proxy, a misc character device that allows a
+	  userspace virtio-blk backend to submit inline-encrypted block I/O
+	  on behalf of guest virtual machines.
+
+	  Guests supply a virtual keyslot index and data unit number with
+	  each encrypted request.  The host kernel translates the virtual
+	  slot to a physical hardware keyslot and issues the bio to the
+	  storage controller with the correct inline encryption context.
+
+	  If unsure, say N.
+
 config BLK_DEV_LOOP_MIN_COUNT
 	int "Number of loop devices to pre-create at init time"
 	depends on BLK_DEV_LOOP
@@ -312,6 +327,19 @@ config VIRTIO_BLK
 	  This is the virtual block driver for virtio.  It can be used with
           QEMU based VMMs (like KVM or Xen).  Say Y or M.
 
+config VIRTBLK_CRYPTO_VIRTUALIZATION
+	tristate "Virtio block inline encryption virtualization support"
+	depends on VIRTIO_BLK && BLK_INLINE_ENCRYPTION
+	help
+	  Say 'Y or M' to enable routing of crypto requests to a different
+	  operating system in a virtualized environment. This option by
+	  itself does not provide a working backend: enable a
+	  platform-specific driver that implements struct
+	  virtblk_crypto_variant_ops as well (e.g. QCOM_CRYPTO_VIRT on
+	  Qualcomm platforms). Without one, VIRTIO_BLK_F_INLINE_ENCRYPTION is
+	  still negotiated and advertised to the block layer, but every
+	  inline-crypto operation fails with -EOPNOTSUPP at runtime.
+
 config BLK_DEV_RBD
 	tristate "Rados block device (RBD)"
 	depends on INET && BLOCK
diff --git a/drivers/block/Makefile b/drivers/block/Makefile
index 2d8096eb8cdf8..636137248d0d1 100644
--- a/drivers/block/Makefile
+++ b/drivers/block/Makefile
@@ -23,8 +23,11 @@ obj-$(CONFIG_BLK_DEV_LOOP)	+= loop.o
 obj-$(CONFIG_SUNVDC)		+= sunvdc.o
 
 obj-$(CONFIG_BLK_DEV_NBD)	+= nbd.o
+obj-$(CONFIG_BLK_CRYPTO_PROXY)	+= blk-crypto-proxy.o
 obj-$(CONFIG_VIRTIO_BLK)	+= virtio_blk.o
 
+obj-$(CONFIG_VIRTBLK_CRYPTO_VIRTUALIZATION) += virtio_blk_crypto_ext.o
+
 obj-$(CONFIG_XEN_BLKDEV_FRONTEND)	+= xen-blkfront.o
 obj-$(CONFIG_XEN_BLKDEV_BACKEND)	+= xen-blkback/
 obj-$(CONFIG_BLK_DEV_DRBD)     += drbd/
diff --git a/drivers/block/blk-crypto-proxy.c b/drivers/block/blk-crypto-proxy.c
new file mode 100644
index 0000000000000..60722884dbcd7
--- /dev/null
+++ b/drivers/block/blk-crypto-proxy.c
@@ -0,0 +1,667 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define pr_fmt(fmt) "blk-crypto-proxy: " fmt
+
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/blkdev.h>
+#include <linux/bio.h>
+#include <linux/mutex.h>
+#include <linux/overflow.h>
+#include <linux/file.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/rcupdate.h>
+#include <linux/uio.h>
+#include <linux/blk-crypto.h>
+#include <linux/blk-crypto-profile.h>
+#include <linux/blk-crypto-proxy.h>
+#include <linux/virtio_blk.h>
+
+static const struct bcp_hypervisor_ops __rcu *g_hypervisor_ops;
+static DEFINE_MUTEX(g_hypervisor_ops_lock);
+
+static const struct bcp_slot_virt_ops __rcu *g_slot_virt_ops;
+static DEFINE_MUTEX(g_slot_virt_ops_lock);
+
+int bcp_register_hypervisor_ops(const struct bcp_hypervisor_ops *ops)
+{
+	int ret = 0;
+
+	mutex_lock(&g_hypervisor_ops_lock);
+	if (rcu_access_pointer(g_hypervisor_ops))
+		ret = -EBUSY;
+	else
+		rcu_assign_pointer(g_hypervisor_ops, ops);
+	mutex_unlock(&g_hypervisor_ops_lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(bcp_register_hypervisor_ops);
+
+void bcp_unregister_hypervisor_ops(const struct bcp_hypervisor_ops *ops)
+{
+	mutex_lock(&g_hypervisor_ops_lock);
+	if (rcu_access_pointer(g_hypervisor_ops) == ops)
+		rcu_assign_pointer(g_hypervisor_ops, NULL);
+	mutex_unlock(&g_hypervisor_ops_lock);
+	synchronize_rcu();
+}
+EXPORT_SYMBOL_GPL(bcp_unregister_hypervisor_ops);
+
+int bcp_register_slot_virt_ops(const struct bcp_slot_virt_ops *ops)
+{
+	int ret = 0;
+
+	mutex_lock(&g_slot_virt_ops_lock);
+	if (rcu_access_pointer(g_slot_virt_ops))
+		ret = -EBUSY;
+	else
+		rcu_assign_pointer(g_slot_virt_ops, ops);
+	mutex_unlock(&g_slot_virt_ops_lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(bcp_register_slot_virt_ops);
+
+void bcp_unregister_slot_virt_ops(const struct bcp_slot_virt_ops *ops)
+{
+	mutex_lock(&g_slot_virt_ops_lock);
+	if (rcu_access_pointer(g_slot_virt_ops) == ops)
+		rcu_assign_pointer(g_slot_virt_ops, NULL);
+	mutex_unlock(&g_slot_virt_ops_lock);
+	synchronize_rcu();
+}
+EXPORT_SYMBOL_GPL(bcp_unregister_slot_virt_ops);
+
+/**
+ * struct bcp_ctx - per-fd state for /dev/blk-crypto-proxy
+ * @bdev_file: file handle for the bound block device; NULL until BCP_BIND_CONTEXT.
+ *             Published with smp_store_release() so hot-path ioctls can read it
+ *             lock-free via smp_load_acquire() in bcp_ctx_bound().
+ * @guest_id: guest identifier resolved from vm_fd at bind time.
+ * @bdev_writable: block_dev_fd was opened with write access.
+ * @bind_lock: serializes concurrent BCP_BIND_CONTEXT calls on this fd.
+ */
+struct bcp_ctx {
+	struct file *bdev_file;
+	u32 guest_id;
+	bool bdev_writable;
+	struct mutex bind_lock;
+};
+
+/*
+ * True once BCP_BIND_CONTEXT has published ctx->bdev_file.  The acquire pairs
+ * with smp_store_release() in bcp_ioctl_bind_context(), ensuring ctx->guest_id
+ * and ctx->bdev_writable are visible to any caller that observes true.
+ */
+static bool bcp_ctx_bound(struct bcp_ctx *ctx)
+{
+	return smp_load_acquire(&ctx->bdev_file) != NULL;
+}
+
+static int bcp_open(struct inode *inode, struct file *file)
+{
+	struct bcp_ctx *ctx;
+
+	ctx = kzalloc_obj(*ctx, GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+	mutex_init(&ctx->bind_lock);
+	file->private_data = ctx;
+	return 0;
+}
+
+static int bcp_release(struct inode *inode, struct file *file)
+{
+	struct bcp_ctx *ctx = file->private_data;
+
+	if (ctx) {
+		if (ctx->bdev_file)
+			bdev_fput(ctx->bdev_file);
+		mutex_destroy(&ctx->bind_lock);
+		kfree(ctx);
+		file->private_data = NULL;
+	}
+	return 0;
+}
+
+/*
+ * Resolve a userspace block device fd to a struct file holding a reference
+ * to the block device, opened with the same access mode as @fd so that a
+ * read-only fd cannot gain write access via BCP_SUBMIT_IO_BY_VSLOT.
+ */
+static struct file *bcp_bdev_from_fd(int fd, bool *writable)
+{
+	struct file *f;
+	struct inode *inode;
+	dev_t dev;
+	blk_mode_t mode = 0;
+
+	f = fget(fd);
+	if (!f)
+		return ERR_PTR(-EBADF);
+	inode = file_inode(f);
+	if (!S_ISBLK(inode->i_mode)) {
+		fput(f);
+		return ERR_PTR(-ENOTBLK);
+	}
+	if (f->f_mode & FMODE_READ)
+		mode |= BLK_OPEN_READ;
+	if (f->f_mode & FMODE_WRITE)
+		mode |= BLK_OPEN_WRITE;
+	if (!mode) {
+		fput(f);
+		return ERR_PTR(-EACCES);
+	}
+	*writable = !!(mode & BLK_OPEN_WRITE);
+	dev = inode->i_rdev;
+	fput(f);
+	return bdev_file_open_by_dev(dev, mode, NULL, NULL);
+}
+
+static long bcp_ioctl_bind_context(struct file *file,
+				   struct bcp_bind_context_arg __user *argp)
+{
+	struct bcp_ctx *ctx = file->private_data;
+	struct bcp_bind_context_arg arg;
+	const struct bcp_hypervisor_ops *hv_ops;
+	struct file *bdev_file;
+	u32 guest_id;
+	bool writable = false;
+	int ret;
+
+	if (!ctx)
+		return -EINVAL;
+	if (copy_from_user(&arg, argp, sizeof(arg)))
+		return -EFAULT;
+	if (arg.reserved)
+		return -EINVAL;
+
+	/*
+	 * get_guest_id() may sleep; call it before taking bind_lock.
+	 */
+	rcu_read_lock();
+	hv_ops = rcu_dereference(g_hypervisor_ops);
+	if (!hv_ops) {
+		rcu_read_unlock();
+		return -EOPNOTSUPP;
+	}
+	ret = hv_ops->get_guest_id(arg.vm_fd, &guest_id);
+	rcu_read_unlock();
+	if (ret)
+		return ret;
+
+	/*
+	 * Serialize against concurrent BCP_BIND_CONTEXT calls: two callers
+	 * could both pass the ctx->bdev_file == NULL check before either stores.
+	 */
+	guard(mutex)(&ctx->bind_lock);
+
+	if (ctx->bdev_file)
+		return -EBUSY;
+
+	bdev_file = bcp_bdev_from_fd(arg.block_dev_fd, &writable);
+	if (IS_ERR(bdev_file))
+		return PTR_ERR(bdev_file);
+
+	ctx->guest_id = guest_id;
+	ctx->bdev_writable = writable;
+	/*
+	 * Publish ctx->bdev_file last with a release barrier; bcp_ctx_bound()
+	 * reads it with smp_load_acquire() without taking @bind_lock.
+	 */
+	smp_store_release(&ctx->bdev_file, bdev_file);
+	return 0;
+}
+
+/*
+ * Maps VIRTIO_BLK_CRYPTO_MODE_* to enum blk_crypto_mode_num.  The two index
+ * spaces do not coincide, so modes_supported[] must not be copied positionally.
+ * Keep in sync with virtio_blk_crypto_mode_map[] in drivers/block/virtio_blk.c.
+ */
+static const enum blk_crypto_mode_num
+	bcp_virtio_crypto_mode_map[VIRTIO_BLK_CRYPTO_MODE_MAX + 1] = {
+	[VIRTIO_BLK_CRYPTO_MODE_INVALID]	= BLK_ENCRYPTION_MODE_INVALID,
+	[VIRTIO_BLK_CRYPTO_MODE_AES_256_XTS]	= BLK_ENCRYPTION_MODE_AES_256_XTS,
+};
+
+static long bcp_ioctl_get_crypto_caps(struct file *file,
+				      struct bcp_get_crypto_caps_arg __user *argp)
+{
+	struct bcp_ctx *ctx = file->private_data;
+	struct bcp_get_crypto_caps_arg arg;
+	struct block_device *bdev;
+	u32 modes[VIRTIO_BLK_CRYPTO_MODE_MAX + 1] = {0};
+	struct blk_crypto_profile *profile;
+	unsigned int i, n;
+	u32 cap, written;
+
+	if (!ctx || !bcp_ctx_bound(ctx))
+		return -ENXIO;
+
+	if (copy_from_user(&arg, argp, sizeof(arg)))
+		return -EFAULT;
+	if (arg.num_modes && !arg.modes_ptr)
+		return -EINVAL;
+
+	bdev = file_bdev(ctx->bdev_file);
+	profile = bdev_get_queue(bdev)->crypto_profile;
+	if (!profile)
+		return -EOPNOTSUPP;
+
+	arg.key_types_supported = profile->key_types_supported;
+	/*
+	 * Clamp to 8: the @dun wire field is a single __aligned_u64 so nothing
+	 * upstream can deliver a wider DUN regardless of what the profile claims.
+	 */
+	arg.max_dun_bytes = min_t(u32, profile->max_dun_bytes_supported, 8);
+
+	/*
+	 * arg.modes_supported[] is indexed by VIRTIO_BLK_CRYPTO_MODE_* (index 0
+	 * is always 0 per the virtio spec).  Translate via the map above; do not
+	 * copy profile->modes_supported[] positionally.
+	 */
+	n = VIRTIO_BLK_CRYPTO_MODE_MAX + 1;
+	cap = arg.num_modes;
+	written = min_t(u32, cap, n);
+
+	for (i = 1; i < n; i++) {
+		enum blk_crypto_mode_num kmode = bcp_virtio_crypto_mode_map[i];
+
+		if (!kmode)
+			continue;
+		modes[i] = profile->modes_supported[kmode];
+	}
+
+	if (written &&
+	    copy_to_user(u64_to_user_ptr(arg.modes_ptr), modes,
+			 written * sizeof(modes[0])))
+		return -EFAULT;
+	arg.num_modes = written;
+
+	arg.max_slots = 0;
+	rcu_read_lock();
+	{
+		const struct bcp_slot_virt_ops *sv_ops =
+				rcu_dereference(g_slot_virt_ops);
+		if (sv_ops) {
+			int nslots = sv_ops->get_guest_slots(profile, ctx->guest_id);
+
+			if (nslots > 0)
+				arg.max_slots = nslots;
+		}
+	}
+	rcu_read_unlock();
+
+	if (copy_to_user(argp, &arg, sizeof(arg)))
+		return -EFAULT;
+	return 0;
+}
+
+/*
+ * Compute the number of pages needed for up to @want_bytes of iovec data
+ * starting at cursor (@start_idx, @start_off).  The page count is capped at
+ * @cap to bound the arithmetic; *bytes_out receives the actual byte count.
+ */
+static unsigned int bcp_iov_pages_for_bytes(const struct iovec *iov, u32 iov_cnt,
+					    u32 start_idx, u64 start_off,
+					      u64 want_bytes, unsigned int cap,
+					      u64 *bytes_out)
+{
+	u64 pages = 0, taken = 0;
+	u32 i;
+
+	for (i = start_idx; i < iov_cnt && taken < want_bytes; i++) {
+		u64 base, len;
+
+		if (i == start_idx) {
+			base = (u64)(uintptr_t)iov[i].iov_base + start_off;
+			len  = iov[i].iov_len - start_off;
+		} else {
+			base = (u64)(uintptr_t)iov[i].iov_base;
+			len  = iov[i].iov_len;
+		}
+		if (len == 0)
+			continue;
+		if (len > want_bytes - taken)
+			len = want_bytes - taken;
+		pages += DIV_ROUND_UP(len + offset_in_page(base), PAGE_SIZE);
+		taken += len;
+		if (pages >= cap) {
+			*bytes_out = taken;
+			return cap;
+		}
+	}
+	*bytes_out = taken;
+	return (unsigned int)pages;
+}
+
+/*
+ * Advance cursor (*idx, *off) forward by @bytes within @iov[0..iov_cnt).
+ */
+static void bcp_iov_advance_cursor(const struct iovec *iov, u32 iov_cnt,
+				   u32 *idx, u64 *off, u64 bytes)
+{
+	while (bytes > 0 && *idx < iov_cnt) {
+		u64 seg_remaining = iov[*idx].iov_len - *off;
+		u64 take = min_t(u64, seg_remaining, bytes);
+
+		*off += take;
+		bytes -= take;
+		if (*off == iov[*idx].iov_len) {
+			(*idx)++;
+			*off = 0;
+		}
+	}
+}
+
+static long bcp_ioctl_submit_io_by_vslot(struct file *file,
+					 struct bcp_submit_io_by_vslot_arg __user *argp)
+{
+	struct bcp_ctx *ctx = file->private_data;
+	struct bcp_submit_io_by_vslot_arg arg;
+	struct block_device *bdev;
+	struct blk_crypto_profile *profile;
+	struct iovec *iov = NULL;
+	struct iov_iter iter;
+	struct blk_crypto_slot slot;
+	u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE];
+	u64 bytes_done = 0;
+	u64 align, stride;
+	u64 total_bytes;
+	u32 seg_idx = 0;
+	u64 seg_off = 0;
+	unsigned int phy_slot;
+	int ret = -EFAULT;
+
+	if (!ctx || !bcp_ctx_bound(ctx))
+		return -ENXIO;
+
+	if (copy_from_user(&arg, argp, sizeof(arg)))
+		return -EFAULT;
+	if (arg.reserved2)
+		return -EINVAL;
+
+	bdev = file_bdev(ctx->bdev_file);
+
+	if (arg.direction != BCP_DIR_READ && arg.direction != BCP_DIR_WRITE)
+		return -EINVAL;
+	/*
+	 * blk_mode_t does not stop submit_bio() from writing; enforce the
+	 * caller's original fd permission explicitly.
+	 */
+	if (arg.direction == BCP_DIR_WRITE && !ctx->bdev_writable)
+		return -EACCES;
+	/*
+	 * bdev_read_only() can change after bind time; submit_bio_noacct()'s
+	 * bio_check_ro() only warns rather than errors in this kernel.
+	 */
+	if (arg.direction == BCP_DIR_WRITE && bdev_read_only(bdev))
+		return -EROFS;
+	if (arg.flags != BCP_SUBMIT_IO_F_IOV)
+		return -EINVAL;
+	if (arg.iov_cnt == 0 || arg.iov_cnt > BCP_MAX_IOV)
+		return -EINVAL;
+	/* A shift amount >= 64 would be undefined behavior. */
+	if (arg.data_unit_size_bits >= 64)
+		return -EINVAL;
+
+	profile = bdev_get_queue(bdev)->crypto_profile;
+	if (!profile)
+		return -EOPNOTSUPP;
+
+	/* Resolve virt_slot → phy_slot. */
+	rcu_read_lock();
+	{
+		const struct bcp_slot_virt_ops *sv_ops =
+				rcu_dereference(g_slot_virt_ops);
+		if (!sv_ops) {
+			rcu_read_unlock();
+			return -EOPNOTSUPP;
+		}
+		ret = sv_ops->vslot_to_pslot(profile, ctx->guest_id,
+					     arg.virt_slot, &phy_slot);
+	}
+	rcu_read_unlock();
+	if (ret)
+		return ret;
+
+	memset(dun, 0, sizeof(dun));
+	dun[0] = arg.dun;
+
+	slot.phy_slot            = phy_slot;
+	slot.data_unit_size_bits = arg.data_unit_size_bits;
+
+	align = 1ULL << arg.data_unit_size_bits;
+	/*
+	 * Split bios at stride (smallest multiple of the data unit size >=
+	 * PAGE_SIZE) boundaries so each bio ends on a whole data unit.
+	 * bio_crypt_check_alignment() is skipped for slot-based bios (bc_key
+	 * == NULL), so a mid-unit split would silently mis-encrypt/mis-decrypt.
+	 */
+	stride = DIV_ROUND_UP(PAGE_SIZE, align) * align;
+
+	/*
+	 * Import the caller's iovec once.  import_iovec() validates every
+	 * segment with access_ok(), returns the total byte count, and takes a
+	 * private kernel copy that eliminates TOCTOU from a guest mutating its
+	 * own iovec array mid-ioctl.
+	 */
+	ret = import_iovec(arg.direction == BCP_DIR_READ ? ITER_DEST : ITER_SOURCE,
+			   (const struct iovec __user *)u64_to_user_ptr(arg.iov_ptr),
+			   arg.iov_cnt, 0, &iov, &iter);
+	if (ret < 0)
+		return ret;
+	total_bytes = ret;
+
+	/*
+	 * Reject a misaligned total length up front: bio_crypt_check_alignment()
+	 * is skipped for slot-based bios so nothing downstream will catch it.
+	 */
+	if (total_bytes == 0 || (total_bytes & (align - 1)) ||
+	    (total_bytes & (SECTOR_SIZE - 1))) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/*
+	 * Fail fast if the request exceeds the device.  bio_check_eod() would
+	 * also catch this, but only on the last bio after earlier bios have
+	 * already done real I/O.
+	 */
+	{
+		sector_t nr_sectors = total_bytes >> SECTOR_SHIFT;
+		sector_t maxsector = bdev_nr_sectors(bdev);
+
+		if (nr_sectors > maxsector || arg.sector > maxsector - nr_sectors) {
+			ret = -EIO;
+			goto out;
+		}
+	}
+
+	/*
+	 * Reject an out-of-range DUN: slot-based bios skip
+	 * bio_crypt_check_alignment(), so an overflow would silently truncate
+	 * in the hardware DUN field rather than error out.
+	 */
+	{
+		u64 total_units = total_bytes >> arg.data_unit_size_bits;
+		u64 max_dun_used, dun_limit;
+
+		if (check_add_overflow(arg.dun, total_units - 1, &max_dun_used)) {
+			ret = -EINVAL;
+			goto out;
+		}
+		dun_limit = profile->max_dun_bytes_supported >= 8 ? U64_MAX :
+			(1ULL << (8 * profile->max_dun_bytes_supported)) - 1;
+		if (max_dun_used > dun_limit) {
+			ret = -EINVAL;
+			goto out;
+		}
+	}
+
+	/*
+	 * Submit the request as a sequence of bios (submit_bio_wait() per
+	 * bio), each holding at most BIO_MAX_VECS pages.  Sequential
+	 * submission avoids DUN/IV correctness concerns across concurrent
+	 * in-flight bios.
+	 */
+	while (seg_idx < arg.iov_cnt) {
+		unsigned int pages_used = 0;
+		u64 bio_bytes = 0;
+		u32 la_idx = seg_idx;
+		u64 la_off = seg_off;
+		u64 remaining_before;
+		struct bio *bio;
+
+		/*
+		 * Lookahead: count how many whole stride units fit within a
+		 * fresh bio's BIO_MAX_VECS page budget.
+		 */
+		for (;;) {
+			u64 unit_bytes;
+			unsigned int unit_pages;
+
+			unit_pages = bcp_iov_pages_for_bytes(iov, arg.iov_cnt,
+							     la_idx, la_off, stride,
+							BIO_MAX_VECS + 1,
+							&unit_bytes);
+			if (unit_bytes == 0)
+				break; /* only empty segments remain */
+
+			if (pages_used + unit_pages > BIO_MAX_VECS) {
+				if (pages_used == 0) {
+					/* data_unit_size_bits too large to fit one unit. */
+					ret = -EINVAL;
+					goto out;
+				}
+				break; /* finalize this bio; unit deferred to next */
+			}
+
+			pages_used += unit_pages;
+			bio_bytes  += unit_bytes;
+			bcp_iov_advance_cursor(iov, arg.iov_cnt, &la_idx, &la_off,
+					       unit_bytes);
+		}
+
+		if (bio_bytes == 0)
+			break;
+
+		bio = bio_alloc(bdev, pages_used,
+				arg.direction == BCP_DIR_WRITE ?
+					REQ_OP_WRITE : REQ_OP_READ,
+				GFP_KERNEL);
+		if (!bio) {
+			ret = -ENOMEM;
+			goto out;
+		}
+		bio->bi_iter.bi_sector = arg.sector + (bytes_done >> SECTOR_SHIFT);
+
+		/*
+		 * Use bio_iov_iter_get_pages() to pin pages into the bio,
+		 * the same as the O_DIRECT path.  Truncate the iter to this
+		 * bio's byte budget, then reexpand for the next iteration.
+		 */
+		remaining_before = iov_iter_count(&iter);
+		iov_iter_truncate(&iter, bio_bytes);
+		ret = bio_iov_iter_get_pages(bio, &iter, 0, 0);
+		if (ret < 0) {
+			bio_put(bio);
+			goto out;
+		}
+		if (iov_iter_count(&iter) != 0) {
+			/*
+			 * The lookahead verified bio_bytes fits in BIO_MAX_VECS;
+			 * if bio_iov_iter_get_pages() stopped early, its page
+			 * accounting disagreed with bcp_iov_pages_for_bytes().
+			 */
+			bio_put(bio);
+			ret = -EIO;
+			goto out;
+		}
+		iov_iter_reexpand(&iter, remaining_before - bio_bytes);
+
+		/*
+		 * Match __blkdev_direct_IO(): mark pages dirty on reads into
+		 * user-backed memory.
+		 */
+		if (arg.direction == BCP_DIR_READ && user_backed_iter(&iter))
+			bio_set_pages_dirty(bio);
+
+		bcp_iov_advance_cursor(iov, arg.iov_cnt, &seg_idx, &seg_off,
+				       bio_bytes);
+
+		bio_crypt_set_ctx_by_slot(bio, &slot, dun, GFP_KERNEL);
+
+		ret = submit_bio_wait(bio);
+		bio_put(bio);
+		if (ret)
+			goto out;
+
+		/*
+		 * Advance dun by this bio's contribution only, not by
+		 * recomputing from arg.dun + bytes_done, to avoid silent
+		 * truncation when bytes_done grows past UINT_MAX data units.
+		 */
+		bio_crypt_dun_increment(dun, (unsigned int)(bio_bytes >> arg.data_unit_size_bits));
+		bytes_done += bio_bytes;
+	}
+	ret = 0;
+
+out:
+	kfree(iov);
+	return ret;
+}
+
+static long bcp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+
+	switch (cmd) {
+	case BCP_BIND_CONTEXT:
+		return bcp_ioctl_bind_context(file, argp);
+	case BCP_GET_CRYPTO_CAPS:
+		return bcp_ioctl_get_crypto_caps(file, argp);
+	case BCP_SUBMIT_IO_BY_VSLOT:
+		return bcp_ioctl_submit_io_by_vslot(file, argp);
+	default:
+		return -ENOTTY;
+	}
+}
+
+static const struct file_operations bcp_fops = {
+	.owner		= THIS_MODULE,
+	.open		= bcp_open,
+	.release	= bcp_release,
+	.unlocked_ioctl	= bcp_ioctl,
+	.compat_ioctl	= compat_ptr_ioctl,
+};
+
+static struct miscdevice bcp_misc = {
+	.minor	= MISC_DYNAMIC_MINOR,
+	.name	= "blk-crypto-proxy",
+	.fops	= &bcp_fops,
+};
+
+static int __init blk_crypto_proxy_init(void)
+{
+	int ret;
+
+	ret = misc_register(&bcp_misc);
+	if (ret)
+		return ret;
+	return 0;
+}
+
+static void __exit blk_crypto_proxy_exit(void)
+{
+	misc_deregister(&bcp_misc);
+}
+
+module_init(blk_crypto_proxy_init);
+module_exit(blk_crypto_proxy_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Host-side inline crypto proxy for virtio-blk guests");
diff --git a/drivers/block/virtio_blk.c b/drivers/block/virtio_blk.c
index 32bf3ba07a9dc..61a3967bb4dfe 100644
--- a/drivers/block/virtio_blk.c
+++ b/drivers/block/virtio_blk.c
@@ -16,6 +16,8 @@
 #include <linux/numa.h>
 #include <linux/vmalloc.h>
 #include <uapi/linux/virtio_ring.h>
+#include <linux/blk-crypto-profile.h>
+#include <linux/virtio_blk_crypto_ext.h>
 
 #define PART_BITS 4
 #define VQ_NAME_LEN 16
@@ -87,7 +89,14 @@ struct virtio_blk {
 
 struct virtblk_req {
 	/* Out header */
-	struct virtio_blk_outhdr out_hdr;
+	union {
+		struct virtio_blk_outhdr base;
+		struct {
+			struct virtio_blk_outhdr base;
+			/* Crypto message (if VIRTIO_BLK_F_INLINE_ENCRYPTION) */
+			struct virtio_blk_crypto_msg msg;
+		} crypto_append;
+	} out_hdr;
 
 	/* In header */
 	union {
@@ -140,12 +149,17 @@ static int virtblk_add_req(struct virtqueue *vq, struct virtblk_req *vbr)
 {
 	struct scatterlist out_hdr, in_hdr, *sgs[3];
 	unsigned int num_out = 0, num_in = 0;
+	size_t out_hdr_len = sizeof(vbr->out_hdr.base);
+
+	if (vbr->out_hdr.base.type == cpu_to_virtio32(vq->vdev, VIRTIO_BLK_T_CRYPTO_IN) ||
+	    vbr->out_hdr.base.type == cpu_to_virtio32(vq->vdev, VIRTIO_BLK_T_CRYPTO_OUT))
+		out_hdr_len = sizeof(vbr->out_hdr.crypto_append);
 
-	sg_init_one(&out_hdr, &vbr->out_hdr, sizeof(vbr->out_hdr));
+	sg_init_one(&out_hdr, &vbr->out_hdr, out_hdr_len);
 	sgs[num_out++] = &out_hdr;
 
 	if (vbr->sg_table.nents) {
-		if (vbr->out_hdr.type & cpu_to_virtio32(vq->vdev, VIRTIO_BLK_T_OUT))
+		if (vbr->out_hdr.base.type & cpu_to_virtio32(vq->vdev, VIRTIO_BLK_T_OUT))
 			sgs[num_out++] = vbr->sg_table.sgl;
 		else
 			sgs[num_out + num_in++] = vbr->sg_table.sgl;
@@ -235,6 +249,15 @@ static void virtblk_cleanup_cmd(struct request *req)
 		kfree(bvec_virt(&req->special_vec));
 }
 
+static bool is_crypto_request(struct virtio_device *vdev, struct request *req)
+{
+	if (!IS_ENABLED(CONFIG_VIRTBLK_CRYPTO_VIRTUALIZATION) ||
+	    !virtio_has_feature(vdev, VIRTIO_BLK_F_INLINE_ENCRYPTION))
+		return false;
+
+	return req->crypt_ctx && req->crypt_keyslot;
+}
+
 static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,
 				      struct request *req,
 				      struct virtblk_req *vbr)
@@ -248,15 +271,21 @@ static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,
 		return BLK_STS_NOTSUPP;
 
 	/* Set fields for all request types */
-	vbr->out_hdr.ioprio = cpu_to_virtio32(vdev, req_get_ioprio(req));
+	vbr->out_hdr.base.ioprio = cpu_to_virtio32(vdev, req_get_ioprio(req));
 
 	switch (req_op(req)) {
 	case REQ_OP_READ:
-		type = VIRTIO_BLK_T_IN;
+		if (is_crypto_request(vdev, req))
+			type = VIRTIO_BLK_T_CRYPTO_IN;
+		else
+			type = VIRTIO_BLK_T_IN;
 		sector = blk_rq_pos(req);
 		break;
 	case REQ_OP_WRITE:
-		type = VIRTIO_BLK_T_OUT;
+		if (is_crypto_request(vdev, req))
+			type = VIRTIO_BLK_T_CRYPTO_OUT;
+		else
+			type = VIRTIO_BLK_T_OUT;
 		sector = blk_rq_pos(req);
 		break;
 	case REQ_OP_FLUSH:
@@ -298,8 +327,9 @@ static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,
 		break;
 	case REQ_OP_DRV_IN:
 		/*
-		 * Out header has already been prepared by the caller (virtblk_get_id()
-		 * or virtblk_submit_zone_report()), nothing to do here.
+		 * Out header has already been prepared by the caller (virtblk_get_id(),
+		 * virtblk_submit_zone_report() or virtblk_get_crypto_modes()), nothing
+		 * to do here.
 		 */
 		return 0;
 	default:
@@ -309,8 +339,8 @@ static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,
 
 	/* Set fields for non-REQ_OP_DRV_IN request types */
 	vbr->in_hdr_len = in_hdr_len;
-	vbr->out_hdr.type = cpu_to_virtio32(vdev, type);
-	vbr->out_hdr.sector = cpu_to_virtio64(vdev, sector);
+	vbr->out_hdr.base.type = cpu_to_virtio32(vdev, type);
+	vbr->out_hdr.base.sector = cpu_to_virtio64(vdev, sector);
 
 	if (type == VIRTIO_BLK_T_DISCARD || type == VIRTIO_BLK_T_WRITE_ZEROES ||
 	    type == VIRTIO_BLK_T_SECURE_ERASE) {
@@ -318,6 +348,17 @@ static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,
 			return BLK_STS_RESOURCE;
 	}
 
+	if (type == VIRTIO_BLK_T_CRYPTO_IN || type == VIRTIO_BLK_T_CRYPTO_OUT) {
+		unsigned int slot = blk_crypto_keyslot_index(req->crypt_keyslot);
+		unsigned int data_unit_size_bits = req->crypt_ctx->bc_key->data_unit_size_bits;
+		u64 dun = req->crypt_ctx->bc_dun[0];
+
+		vbr->out_hdr.crypto_append.msg.slot = cpu_to_virtio32(vdev, slot);
+		vbr->out_hdr.crypto_append.msg.data_unit_size_bits =
+				cpu_to_virtio32(vdev, data_unit_size_bits);
+		vbr->out_hdr.crypto_append.msg.dun = cpu_to_virtio64(vdev, dun);
+	}
+
 	return 0;
 }
 
@@ -568,8 +609,8 @@ static int virtblk_submit_zone_report(struct virtio_blk *vblk,
 
 	vbr = blk_mq_rq_to_pdu(req);
 	vbr->in_hdr_len = sizeof(vbr->in_hdr.status);
-	vbr->out_hdr.type = cpu_to_virtio32(vblk->vdev, VIRTIO_BLK_T_ZONE_REPORT);
-	vbr->out_hdr.sector = cpu_to_virtio64(vblk->vdev, sector);
+	vbr->out_hdr.base.type = cpu_to_virtio32(vblk->vdev, VIRTIO_BLK_T_ZONE_REPORT);
+	vbr->out_hdr.base.sector = cpu_to_virtio64(vblk->vdev, sector);
 
 	err = blk_rq_map_kern(req, report_buf, report_len, GFP_KERNEL);
 	if (err)
@@ -817,8 +858,8 @@ static int virtblk_get_id(struct gendisk *disk, char *id_str)
 
 	vbr = blk_mq_rq_to_pdu(req);
 	vbr->in_hdr_len = sizeof(vbr->in_hdr.status);
-	vbr->out_hdr.type = cpu_to_virtio32(vblk->vdev, VIRTIO_BLK_T_GET_ID);
-	vbr->out_hdr.sector = 0;
+	vbr->out_hdr.base.type = cpu_to_virtio32(vblk->vdev, VIRTIO_BLK_T_GET_ID);
+	vbr->out_hdr.base.sector = 0;
 
 	err = blk_rq_map_kern(req, id_str, VIRTIO_BLK_ID_BYTES, GFP_KERNEL);
 	if (err)
@@ -863,6 +904,70 @@ static int virtblk_getgeo(struct gendisk *disk, struct hd_geometry *geo)
 	return ret;
 }
 
+/* Maps VIRTIO_BLK_CRYPTO_MODE_* values to the kernel's internal enum. */
+static const enum blk_crypto_mode_num
+	virtio_blk_crypto_mode_map[VIRTIO_BLK_CRYPTO_MODE_MAX + 1] = {
+	[VIRTIO_BLK_CRYPTO_MODE_INVALID]	= BLK_ENCRYPTION_MODE_INVALID,
+	[VIRTIO_BLK_CRYPTO_MODE_AES_256_XTS]	= BLK_ENCRYPTION_MODE_AES_256_XTS,
+};
+
+static int virtblk_get_crypto_modes(struct virtio_blk *vblk,
+				    unsigned int *crypto_modes_supported)
+{
+	struct request_queue *q = vblk->disk->queue;
+	unsigned int nr_modes = VIRTIO_BLK_CRYPTO_MODE_MAX + 1;
+	size_t buf_size = sizeof(struct virtio_blk_crypto_modes);
+	struct virtio_blk_crypto_modes *virtblk_cmodes;
+	struct request *req;
+	struct virtblk_req *vbr;
+	unsigned int i;
+	int err;
+
+	virtblk_cmodes = kzalloc(buf_size, GFP_KERNEL);
+	if (!virtblk_cmodes)
+		return -ENOMEM;
+
+	req = blk_mq_alloc_request(q, REQ_OP_DRV_IN, 0);
+	if (IS_ERR(req)) {
+		err = PTR_ERR(req);
+		goto out_free;
+	}
+
+	vbr = blk_mq_rq_to_pdu(req);
+	vbr->in_hdr_len = sizeof(vbr->in_hdr.status);
+	vbr->out_hdr.base.type = cpu_to_virtio32(vblk->vdev,
+						 VIRTIO_BLK_T_GET_CRYPTO_MODES);
+	vbr->out_hdr.base.sector = 0;
+
+	err = blk_rq_map_kern(req, virtblk_cmodes, buf_size, GFP_KERNEL);
+	if (err)
+		goto out_req;
+
+	blk_execute_rq(req, false);
+	err = blk_status_to_errno(virtblk_result(vbr->in_hdr.status));
+	if (err)
+		goto out_req;
+
+	for (i = 1; i < nr_modes; i++) {
+		u32 mode_mask = virtio32_to_cpu(vblk->vdev, virtblk_cmodes->modes[i]);
+		enum blk_crypto_mode_num mode = virtio_blk_crypto_mode_map[i];
+
+		if (!mode_mask)
+			continue;
+		if (!mode) {
+			dev_warn(&vblk->vdev->dev,
+				 "ignoring unknown crypto mode %u\n", i);
+			continue;
+		}
+		crypto_modes_supported[mode] = mode_mask;
+	}
+out_req:
+	blk_mq_free_request(req);
+out_free:
+	kfree(virtblk_cmodes);
+	return err;
+}
+
 static void virtblk_free_disk(struct gendisk *disk)
 {
 	struct virtio_blk *vblk = disk->private_data;
@@ -1435,6 +1540,51 @@ static int virtblk_read_limits(struct virtio_blk *vblk,
 	return 0;
 }
 
+static int virtblk_init_crypto(struct virtio_blk *vblk)
+{
+	struct virtio_device *vdev = vblk->vdev;
+	unsigned int crypto_modes_supported[BLK_ENCRYPTION_MODE_MAX] = { 0 };
+	/* virtio_cread() requires the variable size to match the config field exactly */
+	u16 max_slots;
+	u8 max_dun_bytes, key_types;
+	int err;
+
+	virtio_cread(vdev, struct virtio_blk_config,
+		     enc_characteristics.max_slots, &max_slots);
+	virtio_cread(vdev, struct virtio_blk_config,
+		     enc_characteristics.max_dun_bytes, &max_dun_bytes);
+	virtio_cread(vdev, struct virtio_blk_config,
+		     enc_characteristics.key_types, &key_types);
+
+	dev_dbg(&vdev->dev,
+		 "max_slots = %u, max_dun_bytes = %u, key_types = 0x%x\n",
+		 max_slots, max_dun_bytes, key_types);
+
+	if (!max_slots)
+		return -EINVAL;
+	if (!(key_types & (VIRTIO_BLK_CRYPTO_KEY_TYPE_RAW |
+			   VIRTIO_BLK_CRYPTO_KEY_TYPE_HW_WRAPPED)))
+		return -EINVAL;
+	/*
+	 * struct virtio_blk_crypto_msg.dun is a fixed __virtio64, i.e. this
+	 * driver can only ever transmit 8 bytes of DUN per request. Refuse
+	 * to advertise more than that as supported, or blk-crypto could
+	 * negotiate a larger dun_bytes with the filesystem and have the
+	 * high-order bytes of req->crypt_ctx->bc_dun silently dropped in
+	 * virtblk_setup_cmd(), reusing the same IV across data units that
+	 * only differ in those high-order bytes.
+	 */
+	if (max_dun_bytes > sizeof(u64))
+		return -EINVAL;
+
+	err = virtblk_get_crypto_modes(vblk, crypto_modes_supported);
+	if (err)
+		return err;
+
+	return virtblk_init_inline_crypto(max_slots, max_dun_bytes, key_types,
+					   crypto_modes_supported, &vdev->dev);
+}
+
 static int virtblk_probe(struct virtio_device *vdev)
 {
 	struct virtio_blk *vblk;
@@ -1540,6 +1690,26 @@ static int virtblk_probe(struct virtio_device *vdev)
 			goto out_cleanup_disk;
 	}
 
+	if (IS_ENABLED(CONFIG_VIRTBLK_CRYPTO_VIRTUALIZATION) &&
+	    virtio_has_feature(vblk->vdev, VIRTIO_BLK_F_INLINE_ENCRYPTION)) {
+		if (virtio_has_feature(vblk->vdev, VIRTIO_BLK_F_ZONED)) {
+			dev_warn(&vdev->dev,
+				 "zoned device does not support inline encryption, disabling it\n");
+		} else {
+			/* Initialize supported crypto capabilities */
+			err = virtblk_init_crypto(vblk);
+			if (!err) {
+				if (!virtblk_crypto_register(vblk->disk->queue))
+					dev_warn(&vdev->dev,
+						"failed to register inline crypto profile with the block layer, continuing without inline crypto support\n");
+			} else {
+				dev_warn(&vdev->dev,
+					"inline crypto init failed: %d, continuing without inline crypto support\n",
+					err);
+			}
+		}
+	}
+
 	err = device_add_disk(&vdev->dev, vblk->disk, virtblk_attr_groups);
 	if (err)
 		goto out_cleanup_disk;
@@ -1672,6 +1842,7 @@ static unsigned int features[] = {
 	VIRTIO_BLK_F_FLUSH, VIRTIO_BLK_F_TOPOLOGY, VIRTIO_BLK_F_CONFIG_WCE,
 	VIRTIO_BLK_F_MQ, VIRTIO_BLK_F_DISCARD, VIRTIO_BLK_F_WRITE_ZEROES,
 	VIRTIO_BLK_F_SECURE_ERASE, VIRTIO_BLK_F_ZONED,
+	VIRTIO_BLK_F_INLINE_ENCRYPTION,
 };
 
 static struct virtio_driver virtio_blk = {
diff --git a/drivers/block/virtio_blk_crypto_ext.c b/drivers/block/virtio_blk_crypto_ext.c
new file mode 100644
index 0000000000000..00b6d410d3036
--- /dev/null
+++ b/drivers/block/virtio_blk_crypto_ext.c
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/container_of.h>
+#include <linux/blk-crypto.h>
+#include <linux/blk-crypto-profile.h>
+#include <linux/virtio_blk.h>
+#include <linux/virtio_blk_crypto_ext.h>
+
+struct virtblk_crypto_profile {
+	struct blk_crypto_profile profile;
+	struct virtblk_crypto_variant_ops *ops;
+};
+
+static struct virtblk_crypto_profile g_vdcp;
+static bool g_crypto_profile_initialized;
+static struct device *virtblk_profile_owner;
+static unsigned int g_max_slots;
+static unsigned int g_max_dun_bytes;
+static unsigned int g_key_types;
+static DEFINE_MUTEX(virtblk_crypto_init_lock);
+static DEFINE_MUTEX(virtblk_crypto_ops_lock);
+
+bool virtblk_crypto_register(struct request_queue *q)
+{
+	return blk_crypto_register(&g_vdcp.profile, q);
+}
+EXPORT_SYMBOL_GPL(virtblk_crypto_register);
+
+/*
+ * Returns the variant ops registered for @profile's virtblk_crypto_profile
+ * with a module reference held on ops->owner, or NULL if none are
+ * registered. Pairs with virtblk_crypto_ops_put().
+ *
+ * Holding a module reference for the duration of each dispatch call (rather
+ * than just dereferencing the raw pointer) is what makes it safe for the
+ * module that implements these ops (e.g. drivers/soc/qcom/crypto_virt.c) to
+ * be rmmod'd: module removal will fail/block until every in-flight dispatch
+ * call has released its reference, instead of racing with a concurrent
+ * virtblk_set_crypto_ops(NULL) and the ops table disappearing mid-call.
+ */
+static struct virtblk_crypto_variant_ops *
+virtblk_crypto_ops_get(struct blk_crypto_profile *profile)
+{
+	struct virtblk_crypto_profile *vdcp =
+		container_of(profile, struct virtblk_crypto_profile, profile);
+	struct virtblk_crypto_variant_ops *ops;
+
+	mutex_lock(&virtblk_crypto_ops_lock);
+	ops = vdcp->ops;
+	if (ops && !try_module_get(ops->owner))
+		ops = NULL;
+	mutex_unlock(&virtblk_crypto_ops_lock);
+
+	return ops;
+}
+
+static void virtblk_crypto_ops_put(struct virtblk_crypto_variant_ops *ops)
+{
+	module_put(ops->owner);
+}
+
+static int virtblk_crypto_keyslot_program(struct blk_crypto_profile *profile,
+					   const struct blk_crypto_key *key,
+					   unsigned int slot)
+{
+	struct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);
+	int ret;
+
+	if (!ops || !ops->program_key) {
+		if (ops)
+			virtblk_crypto_ops_put(ops);
+		return -EOPNOTSUPP;
+	}
+
+	ret = ops->program_key(key, slot);
+	virtblk_crypto_ops_put(ops);
+	if (ret)
+		pr_err("program hardware wrapped key failed: slot=%u ret=%d\n", slot, ret);
+
+	return ret;
+}
+
+static int virtblk_crypto_keyslot_evict(struct blk_crypto_profile *profile,
+					 const struct blk_crypto_key *key,
+					 unsigned int slot)
+{
+	struct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);
+	int ret;
+
+	if (!ops || !ops->evict_key) {
+		if (ops)
+			virtblk_crypto_ops_put(ops);
+		return -EOPNOTSUPP;
+	}
+
+	ret = ops->evict_key(slot);
+	virtblk_crypto_ops_put(ops);
+	if (ret)
+		pr_err("evict keyslot %u failed: %d\n", slot, ret);
+
+	return ret;
+}
+
+static int virtblk_crypto_derive_sw_secret(struct blk_crypto_profile *profile,
+					    const u8 *eph_key,
+					    size_t eph_key_size,
+					    u8 sw_secret[BLK_CRYPTO_SW_SECRET_SIZE])
+{
+	struct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);
+	int ret;
+
+	if (!ops || !ops->derive_sw_secret_key) {
+		if (ops)
+			virtblk_crypto_ops_put(ops);
+		return -EOPNOTSUPP;
+	}
+
+	ret = ops->derive_sw_secret_key(eph_key, eph_key_size, sw_secret);
+	virtblk_crypto_ops_put(ops);
+	if (ret)
+		pr_err("derive software secret failed: %d\n", ret);
+
+	return ret;
+}
+
+static int virtblk_crypto_generate_key(struct blk_crypto_profile *profile,
+					u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
+{
+	struct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);
+	int ret;
+
+	if (!ops || !ops->generate_key) {
+		if (ops)
+			virtblk_crypto_ops_put(ops);
+		return -EOPNOTSUPP;
+	}
+
+	ret = ops->generate_key(lt_key);
+	virtblk_crypto_ops_put(ops);
+	if (ret < 0)
+		pr_err("generate hardware wrapped key failed: %d\n", ret);
+
+	return ret;
+}
+
+static int virtblk_crypto_prepare_key(struct blk_crypto_profile *profile,
+				       const u8 *lt_key, size_t lt_key_size,
+				       u8 eph_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
+{
+	struct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);
+	int ret;
+
+	if (!ops || !ops->prepare_key) {
+		if (ops)
+			virtblk_crypto_ops_put(ops);
+		return -EOPNOTSUPP;
+	}
+
+	ret = ops->prepare_key(lt_key, lt_key_size, eph_key);
+	virtblk_crypto_ops_put(ops);
+	if (ret < 0)
+		pr_err("prepare hardware wrapped key failed: %d\n", ret);
+
+	return ret;
+}
+
+static int virtblk_crypto_import_key(struct blk_crypto_profile *profile,
+				      const u8 *raw_key, size_t raw_key_size,
+				      u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
+{
+	struct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);
+	int ret;
+
+	if (!ops || !ops->import_key) {
+		if (ops)
+			virtblk_crypto_ops_put(ops);
+		return -EOPNOTSUPP;
+	}
+
+	ret = ops->import_key(raw_key, raw_key_size, lt_key);
+	virtblk_crypto_ops_put(ops);
+	if (ret < 0)
+		pr_err("import hardware wrapped key failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct blk_crypto_ll_ops virtblk_crypto_ops = {
+	.keyslot_program	= virtblk_crypto_keyslot_program,
+	.keyslot_evict		= virtblk_crypto_keyslot_evict,
+	.derive_sw_secret	= virtblk_crypto_derive_sw_secret,
+	.generate_key		= virtblk_crypto_generate_key,
+	.prepare_key		= virtblk_crypto_prepare_key,
+	.import_key		= virtblk_crypto_import_key,
+};
+
+int virtblk_init_inline_crypto(unsigned int max_slots, unsigned int max_dun_bytes,
+			       unsigned int key_types,
+			       const unsigned int crypto_modes_supported[BLK_ENCRYPTION_MODE_MAX],
+			       struct device *dev)
+{
+	struct blk_crypto_profile *profile = &g_vdcp.profile;
+	unsigned int key_type_supported = 0;
+	int err = 0;
+
+	dev_info(dev, "probing inline crypto capabilities\n");
+
+	mutex_lock(&virtblk_crypto_init_lock);
+
+	/*
+	 * profile is a single, process-wide blk_crypto_profile shared by every
+	 * VIRTIO_BLK_F_INLINE_ENCRYPTION device. Only the first device to get
+	 * here actually initializes it; any other device just reuses it as-is
+	 * if its negotiated capabilities match. A mismatch means this device's
+	 * capabilities don't actually correspond to what the shared profile
+	 * was set up for (wrong keyslot count, DUN size, or key types), which
+	 * is a correctness/security concern, not just a cosmetic one -- fail
+	 * instead of silently registering a profile that doesn't match what
+	 * this device supports.
+	 */
+	if (g_crypto_profile_initialized) {
+		if (max_slots != g_max_slots || max_dun_bytes != g_max_dun_bytes ||
+		    key_types != g_key_types) {
+			dev_warn(dev,
+				 "inline crypto profile already initialized by %s (max_slots=%u max_dun_bytes=%u key_types=0x%x); "
+				 "this device reports max_slots=%u max_dun_bytes=%u key_types=0x%x -- sharing one "
+				 "blk_crypto_profile across multiple VIRTIO_BLK_F_INLINE_ENCRYPTION devices with differing "
+				 "capabilities is not supported, refusing to enable inline crypto for this device\n",
+				 dev_name(virtblk_profile_owner), g_max_slots, g_max_dun_bytes, g_key_types,
+				 max_slots, max_dun_bytes, key_types);
+			err = -EINVAL;
+		}
+		goto out_unlock;
+	}
+
+	if (key_types & VIRTIO_BLK_CRYPTO_KEY_TYPE_RAW)
+		key_type_supported |= BLK_CRYPTO_KEY_TYPE_RAW;
+	if (key_types & VIRTIO_BLK_CRYPTO_KEY_TYPE_HW_WRAPPED)
+		key_type_supported |= BLK_CRYPTO_KEY_TYPE_HW_WRAPPED;
+
+	err = blk_crypto_profile_init(profile, max_slots);
+	if (err) {
+		dev_err(dev, "crypto profile initialization failed: %d\n", err);
+		goto out_unlock;
+	}
+
+	profile->ll_ops = virtblk_crypto_ops;
+	profile->max_dun_bytes_supported = max_dun_bytes;
+	profile->key_types_supported = key_type_supported;
+	profile->dev = dev;
+	memcpy(profile->modes_supported, crypto_modes_supported,
+	       BLK_ENCRYPTION_MODE_MAX * sizeof(unsigned int));
+
+	virtblk_profile_owner = dev;
+	g_max_slots = max_slots;
+	g_max_dun_bytes = max_dun_bytes;
+	g_key_types = key_types;
+	g_crypto_profile_initialized = true;
+
+	dev_info(dev, "inline crypto profile initialized\n");
+
+out_unlock:
+	mutex_unlock(&virtblk_crypto_init_lock);
+	return err;
+}
+EXPORT_SYMBOL_GPL(virtblk_init_inline_crypto);
+
+void virtblk_set_crypto_ops(struct virtblk_crypto_variant_ops *ops)
+{
+	if (!g_crypto_profile_initialized)
+		pr_warn("virtio blk crypto profile hasn't been initialized\n");
+
+	mutex_lock(&virtblk_crypto_ops_lock);
+	g_vdcp.ops = ops;
+	mutex_unlock(&virtblk_crypto_ops_lock);
+}
+EXPORT_SYMBOL_GPL(virtblk_set_crypto_ops);
+
+MODULE_DESCRIPTION("Virtio block inline crypto extension");
+MODULE_LICENSE("GPL");
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 2b524154d9fb3..e1f383b4dc63d 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -294,10 +294,40 @@ endif
 # Options selected by other drivers from different subsystems must be outside
 # of the menuconfig if-block:
 
+config QCOM_ICE_SLOTS
+	tristate "Qualcomm ICE keyslot partitioning for VM guests"
+	depends on ARCH_QCOM || COMPILE_TEST
+	depends on BLK_CRYPTO_PROXY
+	depends on BLK_INLINE_ENCRYPTION
+	help
+	  Parses the qcom,ice-keyslot-map device-tree node and provides
+	  per-VM ICE keyslot accounting and virtual-to-physical slot
+	  translation for guest VMs sharing ICE hardware on Qualcomm
+	  platforms.
+
+	  When enabled, guest virtual keyslot indices are mapped to the
+	  physical ICE keyslot range allocated to each VM, preventing one
+	  VM from accessing another VM's keyslots.
+
+	  Say M here when multiple VMs share ICE keyslots on a Qualcomm
+	  platform.  If unsure, say N.
+
 config QCOM_INLINE_CRYPTO_ENGINE
 	tristate
 	select QCOM_SCM
 
+config QCOM_CRYPTO_VIRT
+	tristate "Qualcomm Technologies, Inc. Crypto Virt driver"
+	depends on VIRTBLK_CRYPTO_VIRTUALIZATION
+	depends on QCOM_SCM
+	default VIRTBLK_CRYPTO_VIRTUALIZATION if ARCH_QCOM
+	help
+	  GVM-side hardware-wrapped-key SCM operations exposed to
+	  virtio_blk's inline crypto layer: per-slot key programming and
+	  eviction, and key derive/generate/prepare/import.
+	  Say Y here to compile the driver as a part of kernel or M to compile
+	  as a module.
+
 config QCOM_KRYO_L2_ACCESSORS
 	bool
 	depends on ARM64
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 798643be3590c..952a57554f9d9 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -38,6 +38,8 @@ obj-$(CONFIG_QCOM_LLCC) += llcc-qcom.o
 obj-$(CONFIG_QCOM_KRYO_L2_ACCESSORS) +=	kryo-l2-accessors.o
 obj-$(CONFIG_QCOM_ICC_BWMON)	+= icc-bwmon.o
 qcom_ice-objs			+= ice.o
+obj-$(CONFIG_QCOM_ICE_SLOTS)	+= qcom_ice_slots.o
 obj-$(CONFIG_QCOM_INLINE_CRYPTO_ENGINE)	+= qcom_ice.o
+obj-$(CONFIG_QCOM_CRYPTO_VIRT)	+= crypto_virt.o
 obj-$(CONFIG_QCOM_PBS) +=	qcom-pbs.o
 obj-$(CONFIG_QCOM_UBWC_CONFIG) += ubwc_config.o
diff --git a/drivers/soc/qcom/crypto_virt.c b/drivers/soc/qcom/crypto_virt.c
new file mode 100644
index 0000000000000..93c7993fb4a5d
--- /dev/null
+++ b/drivers/soc/qcom/crypto_virt.c
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/blk-crypto.h>
+#include <linux/virtio_blk_crypto_ext.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+
+static unsigned int g_wrapped_key_size;
+
+static int crypto_virt_program_key(const struct blk_crypto_key *key,
+				   unsigned int slot)
+{
+	u32 dus_512_units;
+	int ret;
+
+	if (!key || !key->size) {
+		pr_err("%s: invalid key\n", __func__);
+		return -EINVAL;
+	}
+
+	/* Only AES-256-XTS is supported so far. */
+	if (key->crypto_cfg.crypto_mode !=
+	    BLK_ENCRYPTION_MODE_AES_256_XTS) {
+		pr_err_ratelimited("Unsupported crypto mode: %d\n",
+				    key->crypto_cfg.crypto_mode);
+		return -EINVAL;
+	}
+
+	/* qcom_scm_ice_set_key()'s data_unit_size is expressed in 512-byte units */
+	dus_512_units = key->crypto_cfg.data_unit_size / 512;
+
+	ret = qcom_scm_ice_set_key(slot, key->bytes, key->size,
+		QCOM_SCM_ICE_CIPHER_AES_256_XTS, dus_512_units);
+	if (ret)
+		pr_err("%s: slot=%u ret=%d\n", __func__, slot, ret);
+
+	return ret;
+}
+
+static int crypto_virt_invalidate_key(unsigned int slot)
+{
+	int ret;
+
+	ret = qcom_scm_ice_invalidate_key(slot);
+	if (ret)
+		pr_err("%s: slot=%u ret=%d\n", __func__, slot, ret);
+
+	return ret;
+}
+
+static int crypto_virt_derive_sw_secret_key(const u8 *eph_key, size_t eph_key_size,
+					    u8 sw_secret[BLK_CRYPTO_SW_SECRET_SIZE])
+{
+	int ret;
+
+	ret = qcom_scm_derive_sw_secret(eph_key, eph_key_size,
+					sw_secret, BLK_CRYPTO_SW_SECRET_SIZE);
+	if (ret == -EIO || ret == -EINVAL)
+		ret = -EBADMSG; /* probably invalid key */
+
+	if (ret)
+		pr_err("%s: ret=%d\n", __func__, ret);
+
+	return ret;
+}
+
+static int crypto_virt_generate_key(u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
+{
+	int ret;
+
+	if (!g_wrapped_key_size) {
+		pr_err("%s: no expected wrapped key size\n", __func__);
+		return -EINVAL;
+	}
+
+	ret = qcom_scm_generate_ice_key(lt_key, g_wrapped_key_size);
+	if (ret) {
+		pr_err("%s: generate hardware wrapped key failed: %d\n", __func__, ret);
+		return ret;
+	}
+
+	return g_wrapped_key_size;
+}
+
+static int crypto_virt_prepare_key(const u8 *lt_key, size_t lt_key_size,
+				   u8 eph_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
+{
+	int ret;
+
+	if (!g_wrapped_key_size) {
+		pr_err("%s: no expected wrapped key size\n", __func__);
+		return -EINVAL;
+	}
+
+	ret = qcom_scm_prepare_ice_key(lt_key, lt_key_size,
+					eph_key, g_wrapped_key_size);
+	if (ret == -EIO || ret == -EINVAL)
+		ret = -EBADMSG; /* probably invalid key */
+
+	if (ret) {
+		pr_err("%s: prepare hardware wrapped key failed: %d\n", __func__, ret);
+		return ret;
+	}
+
+	return g_wrapped_key_size;
+}
+
+static int crypto_virt_import_key(const u8 *raw_key, size_t raw_key_size,
+				  u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
+{
+	int ret;
+
+	if (!g_wrapped_key_size) {
+		pr_err("%s: no expected wrapped key size\n", __func__);
+		return -EINVAL;
+	}
+
+	ret = qcom_scm_import_ice_key(raw_key, raw_key_size,
+				       lt_key, g_wrapped_key_size);
+	if (ret) {
+		pr_err("%s: import hardware wrapped key failed: %d\n", __func__, ret);
+		return ret;
+	}
+
+	return g_wrapped_key_size;
+}
+
+static struct virtblk_crypto_variant_ops virtblk_crypto_qcom_vops = {
+	.owner = THIS_MODULE,
+	.program_key = crypto_virt_program_key,
+	.evict_key = crypto_virt_invalidate_key,
+	.derive_sw_secret_key = crypto_virt_derive_sw_secret_key,
+	.generate_key = crypto_virt_generate_key,
+	.prepare_key = crypto_virt_prepare_key,
+	.import_key = crypto_virt_import_key,
+};
+
+static int crypto_virt_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = of_property_read_u32(pdev->dev.of_node, "qcom,wrapped-key-size",
+				    &g_wrapped_key_size);
+	if (ret)
+		dev_warn(&pdev->dev, "qcom,wrapped-key-size not found\n");
+
+	if (!g_wrapped_key_size ||
+	    g_wrapped_key_size > BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE) {
+		dev_err(&pdev->dev,
+			"invalid qcom,wrapped-key-size %u, won't support generate/import/prepare hardware wrapped key\n",
+			g_wrapped_key_size);
+		g_wrapped_key_size = 0;
+	}
+
+	virtblk_set_crypto_ops(&virtblk_crypto_qcom_vops);
+	return 0;
+}
+
+static void crypto_virt_remove(struct platform_device *pdev)
+{
+	virtblk_set_crypto_ops(NULL);
+}
+
+static const struct of_device_id crypto_virt_of_match[] = {
+	{ .compatible = "qcom,crypto-virt" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, crypto_virt_of_match);
+
+static struct platform_driver crypto_virt_driver = {
+	.probe = crypto_virt_probe,
+	.remove = crypto_virt_remove,
+	.driver = {
+		.name = "crypto_virt",
+		.of_match_table = crypto_virt_of_match,
+	},
+};
+
+static int __init crypto_virt_init(void)
+{
+	return platform_driver_register(&crypto_virt_driver);
+}
+module_init(crypto_virt_init);
+
+#if IS_MODULE(CONFIG_QCOM_CRYPTO_VIRT)
+static void __exit crypto_virt_exit(void)
+{
+	platform_driver_unregister(&crypto_virt_driver);
+}
+module_exit(crypto_virt_exit);
+#endif
+
+MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Crypto Virt Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/soc/qcom/qcom_ice_slots.c b/drivers/soc/qcom/qcom_ice_slots.c
new file mode 100644
index 0000000000000..364ac93077c12
--- /dev/null
+++ b/drivers/soc/qcom/qcom_ice_slots.c
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * qcom_ice_slots.c - Qualcomm ICE keyslot partitioning for guest VMs
+ *
+ * Implements bcp_slot_virt_ops: translates a (guest_id, virtual-slot) pair to
+ * a physical ICE keyslot index using a per-VM allocation table parsed from
+ * the device-tree node with compatible = "qcom,ice-keyslot-map".
+ *
+ * Device-tree layout:
+ *
+ *   ice_keyslot_map: ice-keyslot-map {
+ *       compatible = "qcom,ice-keyslot-map";
+ *       #address-cells = <1>;
+ *       #size-cells = <0>;
+ *
+ *       vm@3  { reg = <3>;  qcom,max-ice-slots = <16>; qcom,ice-slot-offset = <0>;  };
+ *       vm@52 { reg = <52>; qcom,max-ice-slots = <32>; qcom,ice-slot-offset = <16>; };
+ *   };
+ *
+ * Each child entry maps a guest (reg = guest_id) to a contiguous physical keyslot
+ * range [slot_offset .. slot_offset + max_ice_slots).
+ *
+ * Entry 0 is always the host's own reservation.  Entries 1+ are guest
+ * reservations.  The host's entry is used by ufs-qcom to size its
+ * blk_crypto_profile; it is excluded from the guest-facing translation table
+ * so that blk-crypto-proxy can never accidentally route a guest request into
+ * the host's physical keyslots.
+ *
+ * The ufs-qcom driver reads the host slot info and validates all entries
+ * against the hardware slot count directly via the OF API, with no symbol
+ * dependency on this module.
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/rcupdate.h>
+#include <linux/slab.h>
+#include <linux/blk-crypto-profile.h>
+#include <linux/blk-crypto-proxy.h>
+
+#define QCOM_ICE_SLOTS_MAX_ENTRIES	8
+
+struct qcom_ice_slot_entry {
+	u32 guest_id;
+	u32 max_slots;
+	u32 slot_offset;
+};
+
+struct qcom_ice_slots {
+	struct qcom_ice_slot_entry entries[QCOM_ICE_SLOTS_MAX_ENTRIES];
+	unsigned int num_entries;
+};
+
+/*
+ * There is at most one qcom,ice-keyslot-map platform node per SoC.  A single
+ * global pointer is set at probe time and cleared at remove time.  The
+ * hot-path read (from bcp_slot_virt_ops callbacks) is protected by RCU;
+ * probe/remove serialise via the platform driver guarantee.
+ */
+static struct qcom_ice_slots __rcu *g_ice_slots;
+
+static struct qcom_ice_slots *virt_lookup(struct blk_crypto_profile *profile)
+{
+	/* Single UFS controller: profile argument is not needed. */
+	return rcu_dereference(g_ice_slots);
+}
+
+static int qcom_ice_slots_get_guest_slots(struct blk_crypto_profile *profile,
+					  u32 guest_id)
+{
+	struct qcom_ice_slots *virt = virt_lookup(profile);
+	unsigned int i;
+
+	if (!virt)
+		return -ENOKEY;
+
+	/* entries[0] is the host; guest entries start at index 1. */
+	for (i = 1; i < virt->num_entries; i++) {
+		if (virt->entries[i].guest_id == guest_id)
+			return virt->entries[i].max_slots;
+	}
+	return -ENOKEY;
+}
+
+static int qcom_ice_slots_vslot_to_pslot(struct blk_crypto_profile *profile,
+					 u32 guest_id, u32 virt_slot,
+					 unsigned int *phy_slot_out)
+{
+	struct qcom_ice_slots *virt = virt_lookup(profile);
+	unsigned int i;
+
+	if (!virt)
+		return -ENOKEY;
+
+	for (i = 1; i < virt->num_entries; i++) {
+		if (virt->entries[i].guest_id != guest_id)
+			continue;
+		if (virt_slot >= virt->entries[i].max_slots)
+			return -EINVAL;
+		*phy_slot_out = virt->entries[i].slot_offset + virt_slot;
+		return 0;
+	}
+	return -ENOKEY;
+}
+
+static const struct bcp_slot_virt_ops qcom_slot_virt_ops = {
+	.get_guest_slots = qcom_ice_slots_get_guest_slots,
+	.vslot_to_pslot = qcom_ice_slots_vslot_to_pslot,
+};
+
+static int qcom_ice_slots_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *child;
+	struct qcom_ice_slots *virt;
+	unsigned int idx = 0, total_slots = 0;
+	int ret = 0;
+
+	virt = devm_kzalloc(dev, sizeof(*virt), GFP_KERNEL);
+	if (!virt)
+		return -ENOMEM;
+
+	for_each_child_of_node(dev->of_node, child) {
+		u32 guest_id, max_slots, slot_offset;
+		unsigned int j;
+
+		if (idx >= QCOM_ICE_SLOTS_MAX_ENTRIES) {
+			dev_err(dev, "too many vm entries (> %u)\n",
+				QCOM_ICE_SLOTS_MAX_ENTRIES);
+			ret = -EINVAL;
+			of_node_put(child);
+			goto err_free;
+		}
+
+		if (of_property_read_u32(child, "reg", &guest_id))
+			continue;
+		if (of_property_read_u32(child, "qcom,max-ice-slots", &max_slots))
+			continue;
+		if (of_property_read_u32(child, "qcom,ice-slot-offset", &slot_offset)) {
+			dev_err(dev, "missing qcom,ice-slot-offset for guest_id=%u\n",
+				guest_id);
+			ret = -EINVAL;
+			of_node_put(child);
+			goto err_free;
+		}
+
+		if (idx > 0 &&
+		    slot_offset <
+		    virt->entries[idx - 1].slot_offset +
+		    virt->entries[idx - 1].max_slots) {
+			dev_err(dev, "slot overlap: guest_id=%u overlaps guest_id=%u\n",
+				guest_id, virt->entries[idx - 1].guest_id);
+			ret = -EINVAL;
+			of_node_put(child);
+			goto err_free;
+		}
+
+		for (j = 0; j < idx; j++) {
+			if (virt->entries[j].guest_id == guest_id) {
+				dev_err(dev, "duplicate guest_id=%u\n", guest_id);
+				ret = -EINVAL;
+				of_node_put(child);
+				goto err_free;
+			}
+		}
+
+		virt->entries[idx].guest_id = guest_id;
+		virt->entries[idx].max_slots = max_slots;
+		virt->entries[idx].slot_offset = slot_offset;
+		total_slots += max_slots;
+		idx++;
+	}
+
+	if (idx == 0) {
+		dev_err(dev, "no VM entries found in qcom,ice-keyslot-map\n");
+		ret = -EINVAL;
+		goto err_free;
+	}
+
+	virt->num_entries = idx;
+
+	/*
+	 * Publish the singleton.  From this point on, bcp_slot_virt_ops
+	 * callbacks can resolve virt via rcu_dereference(g_ice_slots).
+	 */
+	rcu_assign_pointer(g_ice_slots, virt);
+
+	ret = bcp_register_slot_virt_ops(&qcom_slot_virt_ops);
+	if (ret) {
+		dev_err(dev, "failed to register slot_virt_ops: %d\n", ret);
+		goto err_free;
+	}
+
+	dev_info(dev, "registered: %u VMs, %u total ICE slots\n",
+		 idx, total_slots);
+	return 0;
+
+err_free:
+	return ret;
+}
+
+static void qcom_ice_slots_remove(struct platform_device *pdev)
+{
+	bcp_unregister_slot_virt_ops(&qcom_slot_virt_ops);
+	/*
+	 * Clear the singleton under RCU so that any concurrent ioctl that
+	 * already took the read lock and is mid-lookup sees either the old
+	 * valid pointer or NULL, never a freed pointer.
+	 */
+	rcu_assign_pointer(g_ice_slots, NULL);
+	synchronize_rcu();
+}
+
+static const struct of_device_id qcom_ice_slots_of_match[] = {
+	{ .compatible = "qcom,ice-keyslot-map" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, qcom_ice_slots_of_match);
+
+static struct platform_driver qcom_ice_slots_driver = {
+	.probe  = qcom_ice_slots_probe,
+	.remove = qcom_ice_slots_remove,
+	.driver = {
+		.name           = "qcom-ice-slots",
+		.of_match_table = qcom_ice_slots_of_match,
+	},
+};
+module_platform_driver(qcom_ice_slots_driver);
+
+MODULE_DESCRIPTION("Qualcomm ICE keyslot partitioning for guest VMs");
+MODULE_LICENSE("GPL");
diff --git a/drivers/ufs/core/ufshcd-crypto.h b/drivers/ufs/core/ufshcd-crypto.h
index 8f66db94e179a..2fc5601c0f769 100644
--- a/drivers/ufs/core/ufshcd-crypto.h
+++ b/drivers/ufs/core/ufshcd-crypto.h
@@ -15,13 +15,21 @@
 static inline void ufshcd_prepare_lrbp_crypto(struct request *rq,
 					      struct ufshcd_lrb *lrbp)
 {
-	if (!rq || !rq->crypt_keyslot) {
+	if (!rq) {
 		lrbp->crypto_key_slot = -1;
 		return;
 	}
 
-	lrbp->crypto_key_slot = blk_crypto_keyslot_index(rq->crypt_keyslot);
-	lrbp->data_unit_num = rq->crypt_ctx->bc_dun[0];
+	if (rq->crypt_keyslot) {
+		lrbp->crypto_key_slot = blk_crypto_keyslot_index(rq->crypt_keyslot);
+		lrbp->data_unit_num = rq->crypt_ctx->bc_dun[0];
+	} else if (rq->crypt_ctx && rq->crypt_ctx->bc_slot.data_unit_size_bits) {
+		lrbp->crypto_key_slot = rq->crypt_ctx->bc_slot.phy_slot;
+		lrbp->data_unit_num = rq->crypt_ctx->bc_dun[0];
+	} else {
+		lrbp->crypto_key_slot = -1;
+	}
+
 }
 
 static inline void
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index 62396212a0a73..0611ab50f4cc9 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -163,6 +163,74 @@ static inline void ufs_qcom_ice_enable(struct ufs_qcom_host *host)
 		qcom_ice_enable(host->ice);
 }
 
+/**
+ * ufs_qcom_ice_parse_slot_table() - parse qcom,ice-keyslot-map DT node
+ * @dev:          UFS controller device
+ * @hw_max_slots: total physical ICE keyslots reported by REG_UFS_CCAP
+ * @num_slots:    receives host max_ice_slots (0 = no partitioning)
+ * @slot_offset:  receives host ice-slot-offset
+ *
+ * Parses the qcom,ice-keyslot-map device-tree node.  The first child entry
+ * is the host's own reservation; subsequent children are guest reservations.
+ * Validates that no entry's range exceeds @hw_max_slots and that the sum of
+ * all entries does not exceed @hw_max_slots.
+ *
+ * If no qcom,ice-keyslot-map phandle is present, sets @num_slots to 0 and
+ * returns 0.  Returns -EINVAL if any entry or the total exceeds @hw_max_slots.
+ */
+static int ufs_qcom_ice_parse_slot_table(struct device *dev,
+					 unsigned int hw_max_slots,
+					 unsigned int *num_slots,
+					 unsigned int *slot_offset)
+{
+	struct device_node *slots_np, *child;
+	unsigned int total_slots = 0;
+	bool first = true;
+	int ret = 0;
+
+	*num_slots   = 0;
+	*slot_offset = 0;
+
+	slots_np = of_parse_phandle(dev->of_node, "qcom,ice-keyslot-map", 0);
+	if (!slots_np)
+		return 0;
+
+	for_each_child_of_node(slots_np, child) {
+		u32 off, max;
+
+		if (of_property_read_u32(child, "qcom,ice-slot-offset", &off) ||
+		    of_property_read_u32(child, "qcom,max-ice-slots", &max))
+			continue;
+
+		if (off + max > hw_max_slots) {
+			dev_err(dev,
+				"ice-keyslot-map: slots [%u..%u) exceed hw max %u\n",
+				off, off + max, hw_max_slots);
+			of_node_put(child);
+			ret = -EINVAL;
+			break;
+		}
+
+		if (first) {
+			*num_slots   = max;
+			*slot_offset = off;
+			first = false;
+		}
+		total_slots += max;
+	}
+
+	of_node_put(slots_np);
+
+	if (!ret && total_slots > hw_max_slots) {
+		dev_err(dev,
+			"ice-keyslot-map: total slots %u exceed hw max %u\n",
+			total_slots, hw_max_slots);
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
 static const struct blk_crypto_ll_ops ufs_qcom_crypto_ops; /* forward decl */
 
 static int ufs_qcom_ice_init(struct ufs_qcom_host *host)
@@ -173,6 +241,8 @@ static int ufs_qcom_ice_init(struct ufs_qcom_host *host)
 	struct qcom_ice *ice;
 	union ufs_crypto_capabilities caps;
 	union ufs_crypto_cap_entry cap;
+	unsigned int num_slots, slot_offset;
+	unsigned int hw_max_slots;
 	int err;
 	int i;
 
@@ -192,7 +262,23 @@ static int ufs_qcom_ice_init(struct ufs_qcom_host *host)
 	caps.reg_val = cpu_to_le32(ufshcd_readl(hba, REG_UFS_CCAP));
 
 	/* The number of keyslots supported is (CFGC+1) */
-	err = devm_blk_crypto_profile_init(dev, profile, caps.config_count + 1);
+	hw_max_slots = caps.config_count + 1;
+
+	/*
+	 * Parse the qcom,ice-keyslot-map DT node: validate all entries against
+	 * the hardware slot count and read the host's own reservation.  If no
+	 * partitioning is configured (num_slots == 0), the profile manages the
+	 * full hardware slot range.
+	 */
+	err = ufs_qcom_ice_parse_slot_table(dev, hw_max_slots,
+					    &num_slots, &slot_offset);
+	if (err) {
+		dev_err(dev, "failed to parse ICE slot table: %d\n", err);
+		return err;
+	}
+
+	err = devm_blk_crypto_profile_init(dev, profile,
+					   num_slots ? num_slots : hw_max_slots);
 	if (err)
 		return err;
 
@@ -201,6 +287,9 @@ static int ufs_qcom_ice_init(struct ufs_qcom_host *host)
 	profile->key_types_supported = qcom_ice_get_supported_key_type(ice);
 	profile->dev = dev;
 
+	if (num_slots)
+		profile->slot_offset = slot_offset;
+
 	/*
 	 * Currently this driver only supports AES-256-XTS.  All known versions
 	 * of ICE support it, but to be safe make sure it is really declared in
diff --git a/include/linux/blk-crypto-profile.h b/include/linux/blk-crypto-profile.h
index 4f39e9cd75766..a9bdc05abfa3c 100644
--- a/include/linux/blk-crypto-profile.h
+++ b/include/linux/blk-crypto-profile.h
@@ -162,6 +162,15 @@ struct blk_crypto_profile {
 	 */
 	struct device *dev;
 
+	/**
+	 * @slot_offset: offset added to the slot array index to obtain the
+	 * physical ICE keyslot number.  Zero in the common case.  Set to a
+	 * non-zero value by storage drivers that share ICE hardware across
+	 * multiple guests, where the host's keyslots do not start at physical
+	 * slot 0.
+	 */
+	unsigned int slot_offset;
+
 	/* private: The following fields shouldn't be accessed by drivers. */
 
 	/* Number of keyslots, or 0 if not applicable */
diff --git a/include/linux/blk-crypto-proxy.h b/include/linux/blk-crypto-proxy.h
new file mode 100644
index 0000000000000..6cf1ff0703e9f
--- /dev/null
+++ b/include/linux/blk-crypto-proxy.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __LINUX_BLK_CRYPTO_PROXY_H
+#define __LINUX_BLK_CRYPTO_PROXY_H
+
+#include <uapi/linux/blk-crypto-proxy.h>
+#include <linux/types.h>
+
+struct blk_crypto_profile;
+
+/**
+ * struct bcp_hypervisor_ops - hypervisor VM identity operations
+ *
+ * Translates a hypervisor-specific VM fd to the opaque u32 vm_id used
+ * throughout blk-crypto-proxy.  Register once at module init time.
+ */
+struct bcp_hypervisor_ops {
+	/**
+	 * @get_guest_id: Resolve @vm_fd to an opaque guest identifier.
+	 *
+	 * Verify the caller is permitted to act on behalf of the VM and write
+	 * its u32 id to @guest_id_out.  The value is passed verbatim to
+	 * bcp_slot_virt_ops callbacks.
+	 *
+	 * Returns 0 on success, -errno on failure.
+	 */
+	int (*get_guest_id)(int vm_fd, u32 *guest_id_out);
+};
+
+/**
+ * bcp_register_hypervisor_ops() - register the hypervisor op-set
+ * @ops: op-set to register; must remain valid until unregistered.
+ *
+ * Returns 0 on success, -EBUSY if an op-set is already registered.
+ */
+int bcp_register_hypervisor_ops(const struct bcp_hypervisor_ops *ops);
+
+/**
+ * bcp_unregister_hypervisor_ops() - unregister the hypervisor op-set
+ * @ops: must be the pointer that was passed to bcp_register_hypervisor_ops().
+ *
+ * Blocks until all in-flight callers have finished, then clears the
+ * registration.  Safe to call from module exit.
+ */
+void bcp_unregister_hypervisor_ops(const struct bcp_hypervisor_ops *ops);
+
+/**
+ * struct bcp_slot_virt_ops - ICE keyslot virtualization operations
+ *
+ * Per-VM ICE keyslot accounting and virtual-to-physical slot translation.
+ * The implementation owns the slot allocation table and is registered once
+ * at platform driver probe time.
+ *
+ * @profile is passed to every callback so an implementation supporting
+ * multiple storage controllers can distinguish between them.
+ *
+ * All callbacks may be called concurrently and must not sleep (called
+ * under RCU read lock).
+ */
+struct bcp_slot_virt_ops {
+	/**
+	 * @get_guest_slots: Return the number of ICE keyslots allocated to @guest_id.
+	 *
+	 * Returns the slot count (≥ 1) on success, -ENOKEY if @guest_id is
+	 * not in the allocation table.
+	 */
+	int (*get_guest_slots)(struct blk_crypto_profile *profile, u32 guest_id);
+
+	/**
+	 * @vslot_to_pslot: Translate a VM-local virtual slot to a physical slot.
+	 * @guest_id:       hypervisor-assigned VM identifier.
+	 * @virt_slot:   0-based slot index within @guest_id's allocation.
+	 * @phy_slot_out: receives the physical ICE keyslot index on success.
+	 *
+	 * Returns 0 on success, -ENOKEY if @guest_id is unknown, -EINVAL if
+	 * @virt_slot >= the VM's allocation.
+	 */
+	int (*vslot_to_pslot)(struct blk_crypto_profile *profile,
+			      u32 guest_id, u32 virt_slot,
+			      unsigned int *phy_slot_out);
+};
+
+/**
+ * bcp_register_slot_virt_ops() - register the slot-virt op-set
+ * @ops: op-set to register; must remain valid until unregistered.
+ *
+ * Returns 0 on success, -EBUSY if an op-set is already registered.
+ */
+int bcp_register_slot_virt_ops(const struct bcp_slot_virt_ops *ops);
+
+/**
+ * bcp_unregister_slot_virt_ops() - unregister the slot-virt op-set
+ * @ops: must be the pointer passed to bcp_register_slot_virt_ops().
+ *
+ * Blocks until all in-flight callers have finished, then clears the
+ * registration.  Safe to call from module exit.
+ */
+void bcp_unregister_slot_virt_ops(const struct bcp_slot_virt_ops *ops);
+
+#endif /* __LINUX_BLK_CRYPTO_PROXY_H */
diff --git a/include/linux/blk-crypto.h b/include/linux/blk-crypto.h
index 938ff536838c7..c9f436b6b2ef4 100644
--- a/include/linux/blk-crypto.h
+++ b/include/linux/blk-crypto.h
@@ -119,9 +119,28 @@ struct blk_crypto_key {
 #define BLK_CRYPTO_MAX_IV_SIZE		32
 #define BLK_CRYPTO_DUN_ARRAY_SIZE	(BLK_CRYPTO_MAX_IV_SIZE / sizeof(u64))
 
+/**
+ * struct blk_crypto_slot - physical slot context for slot-based inline crypto
+ * @phy_slot:           Physical ICE keyslot index (already resolved from virt).
+ * @data_unit_size_bits: log2 of the encryption data unit size; used by
+ *                       __bio_crypt_advance() to increment the DUN correctly
+ *                       when a bio is split.  0 means unknown/unset.
+ *
+ * Used when a bio carries inline crypto context by physical slot index rather
+ * than by a blk_crypto_key pointer (i.e. bc_key == NULL in bio_crypt_ctx).
+ * Set by crypto_vblk when building the bio for a GVM VIRTIO_BLK_T_CRYPTO_IN/OUT
+ * request; left zeroed for all other bio types.
+ */
+struct blk_crypto_slot {
+	unsigned int phy_slot;
+	unsigned int data_unit_size_bits;
+};
+
 /**
  * struct bio_crypt_ctx - an inline encryption context
  * @bc_key: the key, algorithm, and data unit size to use
+ * @bc_slot: physical slot + data_unit_size_bits for slot-based crypto
+ *           (used when bc_key == NULL)
  * @bc_dun: the data unit number (starting IV) to use
  *
  * A bio_crypt_ctx specifies that the contents of the bio will be encrypted (for
@@ -130,6 +149,7 @@ struct blk_crypto_key {
  */
 struct bio_crypt_ctx {
 	const struct blk_crypto_key	*bc_key;
+	struct blk_crypto_slot		 bc_slot;
 	u64				bc_dun[BLK_CRYPTO_DUN_ARRAY_SIZE];
 };
 
@@ -152,10 +172,18 @@ void bio_crypt_set_ctx(struct bio *bio, const struct blk_crypto_key *key,
 		       const u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE],
 		       gfp_t gfp_mask);
 
+void bio_crypt_set_ctx_by_slot(struct bio *bio,
+			       const struct blk_crypto_slot *slot,
+			       const u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE],
+			       gfp_t gfp_mask);
+
 bool bio_crypt_dun_is_contiguous(const struct bio_crypt_ctx *bc,
 				 unsigned int bytes,
 				 const u64 next_dun[BLK_CRYPTO_DUN_ARRAY_SIZE]);
 
+void bio_crypt_dun_increment(u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE],
+			     unsigned int inc);
+
 int blk_crypto_init_key(struct blk_crypto_key *blk_key,
 			const u8 *key_bytes, size_t key_size,
 			enum blk_crypto_key_type key_type,
diff --git a/include/linux/virtio_blk_crypto_ext.h b/include/linux/virtio_blk_crypto_ext.h
new file mode 100644
index 0000000000000..3fe66029ac0d4
--- /dev/null
+++ b/include/linux/virtio_blk_crypto_ext.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __LINUX_VIRTIO_BLK_CRYPTO_EXT_H
+#define __LINUX_VIRTIO_BLK_CRYPTO_EXT_H
+
+#include <linux/blk-crypto.h>
+
+struct blk_crypto_profile;
+struct blk_crypto_key;
+struct device;
+struct request_queue;
+
+#if IS_ENABLED(CONFIG_VIRTBLK_CRYPTO_VIRTUALIZATION)
+struct virtblk_crypto_variant_ops {
+	/*
+	 * Module providing the function pointers below. virtio_blk_crypto_ext.c
+	 * pins it with try_module_get()/module_put() around every call, so that
+	 * the module implementing these ops can be safely rmmod'd: the unload
+	 * will simply block/fail until no dispatch call is in flight, instead
+	 * of racing with one.
+	 */
+	struct module *owner;
+	int (*program_key)(const struct blk_crypto_key *key,
+			   unsigned int slot);
+	int (*evict_key)(unsigned int slot);
+	int (*derive_sw_secret_key)(const u8 *eph_key, size_t eph_key_size,
+				    u8 sw_secret[BLK_CRYPTO_SW_SECRET_SIZE]);
+	int (*generate_key)(u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);
+	int (*prepare_key)(const u8 *lt_key, size_t lt_key_size,
+			   u8 eph_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);
+	int (*import_key)(const u8 *raw_key, size_t raw_key_size,
+			  u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);
+};
+
+/*
+ * Probes the platform's inline-encryption capabilities and initializes the
+ * shared blk_crypto_profile singleton (once) with the keyslot/DUN limits,
+ * supported key types, wrapped key size, and supported crypto modes reported
+ * by the device over virtio config space / VIRTIO_BLK_T_GET_CRYPTO_MODES.
+ *
+ * Safe to call from multiple devices, including concurrently: only the
+ * first caller actually initializes the shared profile, every other caller
+ * just validates its own capabilities against what was already negotiated.
+ */
+int virtblk_init_inline_crypto(unsigned int max_slots, unsigned int max_dun_bytes,
+				unsigned int key_types,
+				const unsigned int crypto_modes_supported[BLK_ENCRYPTION_MODE_MAX],
+				struct device *dev);
+
+/*
+ * Registers the (already-initialized) shared blk_crypto_profile with the
+ * given request queue. Returns false (and leaves the queue without inline
+ * crypto) if the queue's block-integrity support conflicts with inline
+ * encryption; see blk_crypto_register().
+ */
+bool virtblk_crypto_register(struct request_queue *q);
+
+void virtblk_set_crypto_ops(struct virtblk_crypto_variant_ops *ops);
+
+#else
+
+static inline int virtblk_init_inline_crypto(unsigned int max_slots,
+		unsigned int max_dun_bytes, unsigned int key_types,
+		const unsigned int crypto_modes_supported[BLK_ENCRYPTION_MODE_MAX],
+		struct device *dev)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline bool virtblk_crypto_register(struct request_queue *q)
+{
+	return false;
+}
+
+
+#endif
+
+#endif /* __LINUX_VIRTIO_BLK_CRYPTO_EXT_H */
diff --git a/include/uapi/linux/blk-crypto-proxy.h b/include/uapi/linux/blk-crypto-proxy.h
new file mode 100644
index 0000000000000..dc8adc8ef5edb
--- /dev/null
+++ b/include/uapi/linux/blk-crypto-proxy.h
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef __UAPI_LINUX_BLK_CRYPTO_PROXY_H
+#define __UAPI_LINUX_BLK_CRYPTO_PROXY_H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define BCP_DIR_READ     0
+#define BCP_DIR_WRITE    1
+
+/*
+ * BCP_BIND_CONTEXT - bind a host block device and hypervisor VM fd.
+ *
+ * Must be called once after open(), before any other ioctl.
+ * Returns -EBUSY if already bound, -EOPNOTSUPP if no hypervisor op-set
+ * is registered.
+ *
+ * @block_dev_fd: fd of the host block device to bind.
+ * @vm_fd:        hypervisor VM fd identifying the guest.
+ * @reserved:     must be zero.
+ */
+struct bcp_bind_context_arg {
+	__s32 block_dev_fd;
+	__s32 vm_fd;
+	__u32 reserved;
+};
+
+/*
+ * BCP_GET_CRYPTO_CAPS - query crypto capabilities of the bound block device.
+ *
+ * Requires BCP_BIND_CONTEXT; returns -ENXIO otherwise.
+ *
+ * @key_types_supported:  [out] BLK_CRYPTO_KEY_TYPE_* bitmask.
+ * @max_dun_bytes:        [out] maximum DUN bytes supported.
+ * @max_slots:            [out] maximum ICE keyslots available for the bound VM;
+ *                              0 if the VM is not found in the table.
+ * @num_modes:            [in] capacity of the buffer pointed to by @modes_ptr,
+ *                              in entries. [out] number of entries actually
+ *                              written to @modes_ptr (may be less than the
+ *                              given capacity; the caller must use this
+ *                              value, not its own capacity, to know how many
+ *                              entries are valid).
+ * @modes_ptr:             [in] pointer to a caller-allocated __u32 array of
+ *                              at least @num_modes (as given) entries. Must
+ *                              be non-NULL if @num_modes (as given) is > 0.
+ *                              On return, holds a per-mode data_unit_size
+ *                              bitmask array indexed by VIRTIO_BLK_CRYPTO_MODE_*
+ *                              (virtio wire numbering, uapi/linux/virtio_blk.h)
+ *                              -- NOT by enum blk_crypto_mode_num. Index 0 is
+ *                              reserved and always 0, matching struct
+ *                              virtio_blk_crypto_modes.modes[].
+ *
+ * @modes_ptr is a pointer + count rather than a fixed-size array embedded in
+ * this struct so that sizeof(struct bcp_get_crypto_caps_arg) -- and hence the
+ * _IOWR-encoded ioctl number -- does not depend on VIRTIO_BLK_CRYPTO_MODE_MAX.
+ * The caller and this kernel may be built against different virtio_blk.h
+ * versions (and thus different values of that constant); embedding a
+ * VIRTIO_BLK_CRYPTO_MODE_MAX-sized array directly in this struct would make
+ * the ioctl fail to even dispatch (-ENOTTY) whenever the two disagree.
+ */
+
+struct bcp_get_crypto_caps_arg {
+	__u32 key_types_supported;
+	__u32 max_dun_bytes;
+	__u32 max_slots;
+	__u32 num_modes;
+	__aligned_u64 modes_ptr;
+};
+
+/*
+ * BCP_SUBMIT_IO_BY_VSLOT - submit an encrypted bio using a virtual slot.
+ *
+ * The kernel resolves virt_slot to a physical ICE keyslot and submits the
+ * I/O synchronously.  Large requests are split at data-unit boundaries
+ * (BIO_MAX_VECS pages per bio).  Requires BCP_BIND_CONTEXT; returns -ENXIO
+ * otherwise.
+ *
+ * @virt_slot:           guest-visible slot index (0-based within the VM's range).
+ * @direction:           BCP_DIR_READ or BCP_DIR_WRITE.
+ * @flags:               must be BCP_SUBMIT_IO_F_IOV.
+ * @data_unit_size_bits: log2 of the encryption data unit size in bytes.
+ * @sector:              start sector (512-byte units).
+ * @dun:                 data unit number (single 64-bit limb, little-endian).
+ * @iov_ptr:             pointer to scatter-gather array of struct bcp_iovec.
+ * @iov_cnt:             number of entries in @iov_ptr[].
+ * @reserved2:           must be zero.
+ *
+ * @sector, @dun and @iov_ptr use __aligned_u64 to guarantee identical struct
+ * layout between 32-bit and 64-bit callers, as required by
+ * .compat_ioctl = compat_ptr_ioctl.
+ */
+
+/* Maximum iovec segments per BCP_SUBMIT_IO_BY_VSLOT call (matches UIO_MAXIOV). */
+#define BCP_MAX_IOV            1024
+
+#define BCP_SUBMIT_IO_F_IOV    (1U << 0)	/* scatter-gather mode; must always be set */
+
+struct bcp_iovec {
+	__u64 iov_base;
+	__u64 iov_len;
+};
+
+struct bcp_submit_io_by_vslot_arg {
+	__u32 virt_slot;
+	__u32 direction;
+	__u32 flags;
+	__u32 data_unit_size_bits;
+	__aligned_u64 sector;
+	__aligned_u64 dun;
+	__aligned_u64 iov_ptr;
+	__u32 iov_cnt;
+	__u32 reserved2;
+};
+
+#define BCP_IOC_MAGIC    0xC7
+
+#define BCP_BIND_CONTEXT       _IOW(BCP_IOC_MAGIC, 1, struct bcp_bind_context_arg)
+#define BCP_GET_CRYPTO_CAPS    _IOWR(BCP_IOC_MAGIC, 2, struct bcp_get_crypto_caps_arg)
+#define BCP_SUBMIT_IO_BY_VSLOT  _IOW(BCP_IOC_MAGIC, 3, struct bcp_submit_io_by_vslot_arg)
+
+#endif /* __UAPI_LINUX_BLK_CRYPTO_PROXY_H */
diff --git a/include/uapi/linux/virtio_blk.h b/include/uapi/linux/virtio_blk.h
index 3744e4da1b2a7..c1941f7fe0196 100644
--- a/include/uapi/linux/virtio_blk.h
+++ b/include/uapi/linux/virtio_blk.h
@@ -42,6 +42,7 @@
 #define VIRTIO_BLK_F_WRITE_ZEROES	14	/* WRITE ZEROES is supported */
 #define VIRTIO_BLK_F_SECURE_ERASE	16 /* Secure Erase is supported */
 #define VIRTIO_BLK_F_ZONED		17	/* Zoned block device */
+#define VIRTIO_BLK_F_INLINE_ENCRYPTION		22	/* Inline Encryption is supported */
 
 /* Legacy feature bits */
 #ifndef VIRTIO_BLK_NO_LEGACY
@@ -148,6 +149,18 @@ struct virtio_blk_config {
 		__u8 model;
 		__u8 unused2[3];
 	} zoned;
+
+	/* Inline Encryption device characteristics (if VIRTIO_BLK_F_INLINE_ENCRYPTION) */
+	struct virtio_blk_enc_characteristics {
+		__virtio16 max_slots;
+		__u8 max_dun_bytes;
+/* Bitmask values for virtio_blk_enc_characteristics.key_types */
+#define VIRTIO_BLK_CRYPTO_KEY_TYPE_RAW	(1 << 0)
+#define VIRTIO_BLK_CRYPTO_KEY_TYPE_HW_WRAPPED	(1 << 1)
+		/* Bitmask of supported key types: VIRTIO_BLK_CRYPTO_KEY_TYPE_* */
+		__u8 key_types;
+		__virtio32 unused3;
+	} enc_characteristics;
 } __attribute__((packed));
 
 /*
@@ -206,6 +219,15 @@ struct virtio_blk_config {
 /* Reset All zones command */
 #define VIRTIO_BLK_T_ZONE_RESET_ALL 26
 
+/* Inline-encrypted write: crypto_msg set in outhdr */
+#define VIRTIO_BLK_T_CRYPTO_OUT		27
+
+/* Inline-encrypted read: crypto_msg set in outhdr */
+#define VIRTIO_BLK_T_CRYPTO_IN		28
+
+/* Get inline crypto modes */
+#define VIRTIO_BLK_T_GET_CRYPTO_MODES	30
+
 #ifndef VIRTIO_BLK_NO_LEGACY
 /* Barrier before this op. */
 #define VIRTIO_BLK_T_BARRIER	0x80000000
@@ -225,6 +247,46 @@ struct virtio_blk_outhdr {
 	__virtio64 sector;
 };
 
+/*
+ * Crypto message descriptor, appended to the outhdr of a
+ * VIRTIO_BLK_T_CRYPTO_OUT or VIRTIO_BLK_T_CRYPTO_IN request.
+ */
+struct virtio_blk_crypto_msg {
+	/* virtual key slot index */
+	__virtio32 slot;
+	/* log2 of the data unit size in bytes */
+	__virtio32 data_unit_size_bits;
+	/* data unit number (DUN / IV) for this request */
+	__virtio64 dun;
+};
+
+/*
+ * Crypto mode numbers used in VIRTIO_BLK_T_GET_CRYPTO_MODES replies and in
+ * indexing struct virtio_blk_crypto_modes.modes[] below. These numbers are
+ * assigned by the virtio spec and are stable: a number is never reused for
+ * a different crypto mode, and additional crypto modes are assigned new,
+ * higher numbers.
+ */
+enum {
+	VIRTIO_BLK_CRYPTO_MODE_INVALID,
+	VIRTIO_BLK_CRYPTO_MODE_AES_256_XTS,
+	__VIRTIO_BLK_CRYPTO_MODE_MAX,	/* sentinel: always one past the last real mode */
+};
+
+/* Highest crypto mode number defined by this version of the header. */
+#define VIRTIO_BLK_CRYPTO_MODE_MAX (__VIRTIO_BLK_CRYPTO_MODE_MAX - 1)
+
+/* Reply to a VIRTIO_BLK_T_GET_CRYPTO_MODES request. */
+struct virtio_blk_crypto_modes {
+	/*
+	 * modes[N], for crypto mode number N <= VIRTIO_BLK_CRYPTO_MODE_MAX, is
+	 * a bitmask of the data unit sizes with which crypto mode N can be
+	 * used: bit i is set if a data unit size of (1 << i) bytes is
+	 * supported. modes[0] is reserved and always 0.
+	 */
+	__virtio32 modes[__VIRTIO_BLK_CRYPTO_MODE_MAX];
+};
+
 /*
  * Supported zoned device models.
  */

3/2 2026/08/27 16:58 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35601
							output: 23
							thoughts: 676
						

Thoughts:
**Fuzzing Target Identification: blk-crypto-proxy and Virtio-blk Inline Encryption**

Okay, here's the breakdown. We've got a patch series, and the goal is to figure out if it introduces functional changes in the kernel that *need* fuzzing. My initial impression is... yes, absolutely.

This series introduces a brand new `/dev/blk-crypto-proxy` misc character device. That's a huge flashing sign screaming "fuzz me!" right there. It looks like this device is providing an inline encryption proxy specifically designed for virtio-blk guests. The added `VIRTBLK_CRYPTO_VIRTUALIZATION`, `QCOM_CRYPTO_VIRT`, and `QCOM_ICE_SLOTS` bits seem to indicate a new virtualization support.

The crucial point here is reachability. We need to determine if this new code path can be triggered in a virtualized environment like GCE or QEMU. The answer is a resounding *yes*. The `/dev/blk-crypto-proxy` device is exposed to userspace, meaning a fuzzer has direct access. It supports ioctls: `BCP_BIND_CONTEXT`, `BCP_GET_CRYPTO_CAPS`, and `BCP_SUBMIT_IO_BY_VSLOT`. I'm already imagining crafting some clever test cases for those!

The `virtio_blk` changes are also interesting. The patch adds support for `VIRTIO_BLK_F_INLINE_ENCRYPTION`, which is a virtio feature. This means it can be enabled in QEMU if QEMU supports it or if syzkaller is properly configured. From the code, it looks like syzkaller could already directly target `/dev/blk-crypto-proxy`.

Let's dive deeper into the code. `blk-crypto-proxy.c` registers the misc device, and that's solid proof it's a userspace target. The ioctls, as I suspected, define a brand new userspace API. Perfect fuzzing material!

The `virtio_blk.c` changes are focused on enabling the inline encryption feature. We have feature advertisements and the handling of the new `VIRTIO_BLK_T_CRYPTO_IN` and `VIRTIO_BLK_T_CRYPTO_OUT` requests. `virtblk_init_crypto` and `virtblk_get_crypto_modes` are also relevant.

The `qcom_ice_slots.c` and `crypto_virt.c` files are platform drivers, respectively for `qcom,ice-keyslot-map` and `qcom,crypto-virt`. It doesn't appear these drivers directly interact with the generic misc device, but they could be interesting to fuzz on their own.

Now, a slightly concerning thought: `bcp_ioctl_bind_context` calls `hv_ops->get_guest_id(arg.vm_fd, &guest_id)`. `hv_ops` are registered by a call to `bcp_register_hypervisor_ops`. I need to figure out which driver actually calls `bcp_register_hypervisor_ops` and how it is used. This is a very critical interaction as it is a gatekeeper for the `blk-crypto-proxy` function. The fuzzer must be able to hit this point.

In short, `/dev/blk-crypto-proxy` is a prime candidate for fuzzing, and the virtio-blk inline encryption feature opens up a potentially complex attack surface. It looks like there's a good chunk of new code, and all of it, including the interactions with virtualization features, needs thorough fuzzing.



4/2 2026/08/27 16:59 tool grepper
0m Args:
{
  "Expression": "bcp_register_hypervisor_ops"
}

Results:
{
  "Output": "drivers/block/blk-crypto-proxy.c=28=static DEFINE_MUTEX(g_slot_virt_ops_lock);\ndrivers/block/blk-crypto-proxy.c-29-\ndrivers/block/blk-crypto-proxy.c:30:int bcp_register_hypervisor_ops(const struct bcp_hypervisor_ops *ops)\ndrivers/block/blk-crypto-proxy.c-31-{\n--\ndrivers/block/blk-crypto-proxy.c-41-}\ndrivers/block/blk-crypto-proxy.c:42:EXPORT_SYMBOL_GPL(bcp_register_hypervisor_ops);\ndrivers/block/blk-crypto-proxy.c-43-\n--\ninclude/linux/blk-crypto-proxy.h=17=struct bcp_hypervisor_ops {\n--\ninclude/linux/blk-crypto-proxy.h-30-/**\ninclude/linux/blk-crypto-proxy.h:31: * bcp_register_hypervisor_ops() - register the hypervisor op-set\ninclude/linux/blk-crypto-proxy.h-32- * @ops: op-set to register; must remain valid until unregistered.\n--\ninclude/linux/blk-crypto-proxy.h-35- */\ninclude/linux/blk-crypto-proxy.h:36:int bcp_register_hypervisor_ops(const struct bcp_hypervisor_ops *ops);\ninclude/linux/blk-crypto-proxy.h-37-\n--\ninclude/linux/blk-crypto-proxy.h-39- * bcp_unregister_hypervisor_ops() - unregister the hypervisor op-set\ninclude/linux/blk-crypto-proxy.h:40: * @ops: must be the pointer that was passed to bcp_register_hypervisor_ops().\ninclude/linux/blk-crypto-proxy.h-41- *\n"
}

5/2 2026/08/27 16:59 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 37554
							output: 23
							thoughts: 889
						

Thoughts:
**Analyzing the blk-crypto-proxy Module**

Okay, so I'm digging into this `blk-crypto-proxy` module, and the first thing that jumps out is the lack of a direct call to `bcp_register_hypervisor_ops` within the kernel tree. That's a bit odd. I briefly considered that it might be tucked away in KVM, but a quick grep ruled that out.

Now, if `bcp_register_hypervisor_ops` *isn't* called, then `g_hypervisor_ops` remains NULL. This means `bcp_ioctl_bind_context` will immediately return `-EOPNOTSUPP` due to the check against `hv_ops`. And if `BCP_BIND_CONTEXT` fails, subsequent calls to `BCP_GET_CRYPTO_CAPS` and `BCP_SUBMIT_IO_BY_VSLOT` will return `-ENXIO`. Basically, the ioctls are dead in the water, returning errors.

However, `bcp_ioctl_bind_context` itself *is* reachable, and that's the interesting part. So, the question is, how else can we reach this code? I started thinking about `virtio_blk`. The `virtio_blk` driver handles `VIRTIO_BLK_F_INLINE_ENCRYPTION`, and if a virtio-blk device exposes this feature, then `virtblk_init_crypto` gets called. This in turn leads to a `VIRTIO_BLK_T_GET_CRYPTO_MODES` request. So, it appears that this code path is definitely reachable during virtio device initialization.

I'm looking at `virtio_blk.c` to confirm this. The relevant code snippets are there, including the call to `virtblk_init_crypto`. This path is definitely triggered when `VIRTIO_BLK_F_INLINE_ENCRYPTION` is present. Syzkaller has the mock virtio devices, but I'm not sure if it handles this feature, but it is core kernel logic.

Then I considered `blk-crypto-proxy` itself. Even if `hv_ops` is never set, the ioctl handler `bcp_ioctl` is accessible, and therefore `bcp_ioctl_bind_context` is reachable as well. Is this worth fuzzing? Yes, this seems like a new misc device that's an attack surface.

Peeking into `bcp_ioctl_bind_context`, it's just a few lines of code before that `-EOPNOTSUPP` return. But, and this is a crucial point, I have to consider the possibility that `bcp_register_hypervisor_ops` might be called by an out-of-tree module. But I'm only interested in the in-tree code. So, the question remains: if `hv_ops` is never registered in-tree, is `blk-crypto-proxy` effectively dead code? I need to double-check that no in-tree modules call `bcp_register_hypervisor_ops`, as that's the key to understanding this module's significance.



6/2 2026/08/27 16:59 tool grepper
0m Args:
{
  "Expression": "bcp_register_hypervisor_ops"
}

Results:
{
  "Output": "drivers/block/blk-crypto-proxy.c=28=static DEFINE_MUTEX(g_slot_virt_ops_lock);\ndrivers/block/blk-crypto-proxy.c-29-\ndrivers/block/blk-crypto-proxy.c:30:int bcp_register_hypervisor_ops(const struct bcp_hypervisor_ops *ops)\ndrivers/block/blk-crypto-proxy.c-31-{\n--\ndrivers/block/blk-crypto-proxy.c-41-}\ndrivers/block/blk-crypto-proxy.c:42:EXPORT_SYMBOL_GPL(bcp_register_hypervisor_ops);\ndrivers/block/blk-crypto-proxy.c-43-\n--\ninclude/linux/blk-crypto-proxy.h=17=struct bcp_hypervisor_ops {\n--\ninclude/linux/blk-crypto-proxy.h-30-/**\ninclude/linux/blk-crypto-proxy.h:31: * bcp_register_hypervisor_ops() - register the hypervisor op-set\ninclude/linux/blk-crypto-proxy.h-32- * @ops: op-set to register; must remain valid until unregistered.\n--\ninclude/linux/blk-crypto-proxy.h-35- */\ninclude/linux/blk-crypto-proxy.h:36:int bcp_register_hypervisor_ops(const struct bcp_hypervisor_ops *ops);\ninclude/linux/blk-crypto-proxy.h-37-\n--\ninclude/linux/blk-crypto-proxy.h-39- * bcp_unregister_hypervisor_ops() - unregister the hypervisor op-set\ninclude/linux/blk-crypto-proxy.h:40: * @ops: must be the pointer that was passed to bcp_register_hypervisor_ops().\ninclude/linux/blk-crypto-proxy.h-41- *\n"
}

7/2 2026/08/27 16:59 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 39613
							output: 24
							thoughts: 21
						

Thoughts:
Okay, here's my interpretation of those thoughts, summarized from a professional, technical perspective:

**Empty Call List and Checking a Critical Registration**

Alright, so the call log is clear, which is good. No immediate issues popping up there. But that raises another point... what about the virtual operation registration? I need to check something important. Specifically, I need to make sure `bcp_register_slot_virt_ops` is behaving as expected. This function is critical for proper operation; if that's not functioning, it could explain a lot of potential problems.



8/2 2026/08/27 16:59 tool grepper
0m Args:
{
  "Expression": "bcp_register_slot_virt_ops"
}

Results:
{
  "Output": "drivers/block/blk-crypto-proxy.c=52=EXPORT_SYMBOL_GPL(bcp_unregister_hypervisor_ops);\ndrivers/block/blk-crypto-proxy.c-53-\ndrivers/block/blk-crypto-proxy.c:54:int bcp_register_slot_virt_ops(const struct bcp_slot_virt_ops *ops)\ndrivers/block/blk-crypto-proxy.c-55-{\n--\ndrivers/block/blk-crypto-proxy.c-65-}\ndrivers/block/blk-crypto-proxy.c:66:EXPORT_SYMBOL_GPL(bcp_register_slot_virt_ops);\ndrivers/block/blk-crypto-proxy.c-67-\n--\ndrivers/soc/qcom/qcom_ice_slots.c=112=static int qcom_ice_slots_probe(struct platform_device *pdev)\n--\ndrivers/soc/qcom/qcom_ice_slots.c-188-\ndrivers/soc/qcom/qcom_ice_slots.c:189:\tret = bcp_register_slot_virt_ops(\u0026qcom_slot_virt_ops);\ndrivers/soc/qcom/qcom_ice_slots.c-190-\tif (ret) {\n--\ninclude/linux/blk-crypto-proxy.h=60=struct bcp_slot_virt_ops {\n--\ninclude/linux/blk-crypto-proxy.h-83-/**\ninclude/linux/blk-crypto-proxy.h:84: * bcp_register_slot_virt_ops() - register the slot-virt op-set\ninclude/linux/blk-crypto-proxy.h-85- * @ops: op-set to register; must remain valid until unregistered.\n--\ninclude/linux/blk-crypto-proxy.h-88- */\ninclude/linux/blk-crypto-proxy.h:89:int bcp_register_slot_virt_ops(const struct bcp_slot_virt_ops *ops);\ninclude/linux/blk-crypto-proxy.h-90-\n--\ninclude/linux/blk-crypto-proxy.h-92- * bcp_unregister_slot_virt_ops() - unregister the slot-virt op-set\ninclude/linux/blk-crypto-proxy.h:93: * @ops: must be the pointer passed to bcp_register_slot_virt_ops().\ninclude/linux/blk-crypto-proxy.h-94- *\n"
}

9/2 2026/08/27 16:59 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 40386
							output: 22
							thoughts: 223
						

Thoughts:
**Unreachable Code in `blk-crypto-proxy`**

Okay, so `qcom_ice_slots.c` registers slot virtual operations via `bcp_register_slot_virt_ops`. That seems straightforward enough. But, hang on a second, if `hv_ops` isn't set, then `bcp_ioctl_bind_context` is guaranteed to fail. And that means `BCP_SUBMIT_IO_BY_VSLOT` is going to *always* return `-ENXIO`. Hmm... I'm a bit concerned about this. Is there some other, sneaky way to actually utilize the `blk-crypto-proxy` driver? No, it's a misc device; it doesn't appear so. That suggests the core logic within `blk-crypto-proxy` is mostly unreachable, at least in its intended configuration. That's not ideal.

But... wait a minute, the `virtio_blk` changes *are* reachable. We should investigate those. Let me check `virtio_blk.c`. Aha, `virtblk_init_crypto` is there, and it calls `virtblk_get_crypto_modes`, which then sends a request to the virtio device. If the virtio device responds to this request, we hit `virtblk_init_inline_crypto`. That's interesting, and it looks like it's defined in `virtio_blk_crypto_ext.c`. Okay, let's dive into `virtblk_init_inline_crypto`.



10/2 2026/08/27 17:00 tool grepper
0m Args:
{
  "Expression": "virtblk_init_inline_crypto"
}

Results:
{
  "Output": "drivers/block/virtio_blk.c=1543=static int virtblk_init_crypto(struct virtio_blk *vblk)\n--\ndrivers/block/virtio_blk.c-1583-\ndrivers/block/virtio_blk.c:1584:\treturn virtblk_init_inline_crypto(max_slots, max_dun_bytes, key_types,\ndrivers/block/virtio_blk.c-1585-\t\t\t\t\t   crypto_modes_supported, \u0026vdev-\u003edev);\n--\ndrivers/block/virtio_blk_crypto_ext.c=191=static const struct blk_crypto_ll_ops virtblk_crypto_ops = {\n--\ndrivers/block/virtio_blk_crypto_ext.c-199-\ndrivers/block/virtio_blk_crypto_ext.c:200:int virtblk_init_inline_crypto(unsigned int max_slots, unsigned int max_dun_bytes,\ndrivers/block/virtio_blk_crypto_ext.c-201-\t\t\t       unsigned int key_types,\n--\ndrivers/block/virtio_blk_crypto_ext.c-268-}\ndrivers/block/virtio_blk_crypto_ext.c:269:EXPORT_SYMBOL_GPL(virtblk_init_inline_crypto);\ndrivers/block/virtio_blk_crypto_ext.c-270-\n--\ninclude/linux/virtio_blk_crypto_ext.h=14=struct virtblk_crypto_variant_ops {\n--\ninclude/linux/virtio_blk_crypto_ext.h-44- */\ninclude/linux/virtio_blk_crypto_ext.h:45:int virtblk_init_inline_crypto(unsigned int max_slots, unsigned int max_dun_bytes,\ninclude/linux/virtio_blk_crypto_ext.h-46-\t\t\t\tunsigned int key_types,\n--\ninclude/linux/virtio_blk_crypto_ext.h=58=void virtblk_set_crypto_ops(struct virtblk_crypto_variant_ops *ops);\n--\ninclude/linux/virtio_blk_crypto_ext.h-61-\ninclude/linux/virtio_blk_crypto_ext.h:62:static inline int virtblk_init_inline_crypto(unsigned int max_slots,\ninclude/linux/virtio_blk_crypto_ext.h-63-\t\tunsigned int max_dun_bytes, unsigned int key_types,\n"
}

11/2 2026/08/27 17:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 41568
							output: 25
							thoughts: 1285
						

Thoughts:
**Reaching and Fuzzing virtio_blk Crypto Features**

Okay, so I've been digging into the `virtio_blk` code, specifically the new inline crypto integration. It seems pretty straightforward on the surface: `virtio_blk` initializes crypto, registers a profile with the block layer using `virtblk_crypto_register`, and then the block layer can start submitting encrypted bios to `virtio_blk`. The `virtblk_setup_cmd` function handles crypto requests by setting the appropriate flags (`VIRTIO_BLK_T_CRYPTO_IN`/`OUT`) and appending a `crypto_append` to the output header. This looks like a prime target for fuzzing, given that it's all reachable via standard block layer crypto APIs like `fscrypt`. `virtio_blk` being a block device, userspace can theoretically use `fscrypt` on it or use `blk-crypto` directly if there's an API, making these crypto changes highly relevant.

My initial thought was to examine the relevant configurations: `CONFIG_BLK_CRYPTO_PROXY`, `CONFIG_VIRTBLK_CRYPTO_VIRTUALIZATION`, `CONFIG_QCOM_ICE_SLOTS`, and `CONFIG_QCOM_CRYPTO_VIRT`. That last one is interesting because it calls `virtblk_set_crypto_ops` with `virtblk_crypto_qcom_vops`. But then I realized a potential gotcha: if `CONFIG_QCOM_CRYPTO_VIRT` is *not* enabled, `virtblk_crypto_ops_get` returns NULL, and `virtblk_crypto_keyslot_program` returns `-EOPNOTSUPP`. This means that even if the crypto profile *registers* successfully, any attempt to actually program a key will fail, preventing any encrypted I/O from being submitted. The implication is that if key programming fails, `fscrypt` setup will also fail.

I double-checked `virtblk_crypto_keyslot_program` and confirmed the NULL check. We absolutely need the `ops` registered to test the I/O path. The issue, though, is that `QCOM_CRYPTO_VIRT` is specific to Qualcomm and uses `qcom_scm_ice_set_key`, which is a TrustZone call. This might pose an issue when emulating for fuzzing. Does `qcom_scm` require physical hardware?

Then I went back to first principles: what happens if key programming *does* fail? Will it block `virtio_blk` from submitting crypto requests? The answer is yes. But wait, then I remembered the `blk-crypto-proxy`. It submits bios with `bc_key == NULL` for slot-based crypto. In this case, `blk_crypto_keyslot_program` is *not* called. And there's a quick check in `__blk_crypto_submit_bio`. If `bc_key` is null, it bypasses key programming. This makes a direct path to the target. It seems this would enable inline encryption.

The challenge now is how do we submit a bio with `bc_key == NULL`? Only through `blk-crypto-proxy`, but `blk-crypto-proxy` requires `bcp_register_hypervisor_ops`, and that function is *never* called in the current kernel. That's a dead end. Is there any other way to submit with a `NULL bc_key`? It seems my task now is to search for calls to `bio_crypt_set_ctx_by_slot`, and see if this bypass mechanism can actually be triggered.



12/2 2026/08/27 17:00 tool grepper
0m Args:
{
  "Expression": "bio_crypt_set_ctx_by_slot"
}

Results:
{
  "Output": "block/blk-crypto.c=120=EXPORT_SYMBOL_GPL(bio_crypt_set_ctx);\nblock/blk-crypto.c-121-\nblock/blk-crypto.c:122:void bio_crypt_set_ctx_by_slot(struct bio *bio,\nblock/blk-crypto.c-123-\t\t\t       const struct blk_crypto_slot *slot,\n--\nblock/blk-crypto.c-138-}\nblock/blk-crypto.c:139:EXPORT_SYMBOL_GPL(bio_crypt_set_ctx_by_slot);\nblock/blk-crypto.c-140-\n--\ndrivers/block/blk-crypto-proxy.c=360=static long bcp_ioctl_submit_io_by_vslot(struct file *file,\n--\ndrivers/block/blk-crypto-proxy.c-595-\ndrivers/block/blk-crypto-proxy.c:596:\t\tbio_crypt_set_ctx_by_slot(bio, \u0026slot, dun, GFP_KERNEL);\ndrivers/block/blk-crypto-proxy.c-597-\n--\ninclude/linux/blk-crypto.h=171=void bio_crypt_set_ctx(struct bio *bio, const struct blk_crypto_key *key,\n--\ninclude/linux/blk-crypto.h-174-\ninclude/linux/blk-crypto.h:175:void bio_crypt_set_ctx_by_slot(struct bio *bio,\ninclude/linux/blk-crypto.h-176-\t\t\t       const struct blk_crypto_slot *slot,\n"
}

13/2 2026/08/27 17:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 44064
							output: 22
							thoughts: 746
						

Thoughts:
**My Analysis of the virtio-blk Crypto Path**

Okay, let's break this down. It seems the code is structured such that only `blk-crypto-proxy` calls `bio_crypt_set_ctx_by_slot`. And the proxy relies on `hv_ops` being registered, which isn't happening anywhere in the kernel's normal operation. Let's revisit `bcp_ioctl_bind_context`... yep, if `g_hypervisor_ops` is NULL, it immediately returns `-EOPNOTSUPP`. So, `BCP_BIND_CONTEXT` is *always* going to fail. And if that fails, `ctx->bdev_file` remains unset. Consequently, `BCP_SUBMIT_IO_BY_VSLOT` will also fail, because the context isn't bound, so `bcp_ioctl_submit_io_by_vslot` is completely unreachable using this path. That's a dead end.

Now, regarding `fscrypt`: If userspace uses `fscrypt` on a virtio-blk device, it calls `blk_crypto_keyslot_program`. If `virtblk_crypto_ops_get` returns NULL, it's back to `-EOPNOTSUPP`, and `blk_crypto_init_key` fails. Unless software crypto is enabled, in which case it *falls back* to software crypto. But if it falls back, `virtio_blk` *won't* receive `VIRTIO_BLK_T_CRYPTO_IN/OUT` requests. So, the `virtio_blk` crypto I/O path is effectively unreachable unless `program_key` succeeds.

Can `program_key` actually succeed? It depends on `QCOM_CRYPTO_VIRT` being registered. That's a platform driver for `qcom,crypto-virt`. If a platform device with that name exists, the driver probes. Specifically, `crypto_virt_probe` calls `virtblk_set_crypto_ops` with `virtblk_crypto_qcom_vops`. That sets everything up.

If it probes, then `program_key` eventually calls `crypto_virt_program_key`, which in turn calls `qcom_scm_ice_set_key`. Here's the crux: `qcom_scm_ice_set_key` makes an SMC call to TrustZone. Since this is a virtio-blk guest (I am working on virtio-blk!), `qcom_scm` *might* be supported if the hypervisor emulates it, or if it's passed through. But on a typical syzkaller QEMU setup, those `qcom_scm` SMC calls are likely going to crash or fail. So, if `qcom_scm_ice_set_key` fails, `program_key` fails, and we still can't submit crypto bios to virtio-blk. This looks to be the current root cause.

Wait a second… Is there ANY other way `virtblk_set_crypto_ops` gets called? I need to check for other possible entry points to this code path to see if I'm missing something.



14/2 2026/08/27 17:00 tool grepper
0m Args:
{
  "Expression": "virtblk_set_crypto_ops"
}

Results:
{
  "Output": "drivers/block/virtio_blk_crypto_ext.c=30=EXPORT_SYMBOL_GPL(virtblk_crypto_register);\n--\ndrivers/block/virtio_blk_crypto_ext.c-41- * call has released its reference, instead of racing with a concurrent\ndrivers/block/virtio_blk_crypto_ext.c:42: * virtblk_set_crypto_ops(NULL) and the ops table disappearing mid-call.\ndrivers/block/virtio_blk_crypto_ext.c-43- */\n--\ndrivers/block/virtio_blk_crypto_ext.c=269=EXPORT_SYMBOL_GPL(virtblk_init_inline_crypto);\ndrivers/block/virtio_blk_crypto_ext.c-270-\ndrivers/block/virtio_blk_crypto_ext.c:271:void virtblk_set_crypto_ops(struct virtblk_crypto_variant_ops *ops)\ndrivers/block/virtio_blk_crypto_ext.c-272-{\n--\ndrivers/block/virtio_blk_crypto_ext.c-279-}\ndrivers/block/virtio_blk_crypto_ext.c:280:EXPORT_SYMBOL_GPL(virtblk_set_crypto_ops);\ndrivers/block/virtio_blk_crypto_ext.c-281-\n--\ndrivers/soc/qcom/crypto_virt.c=141=static int crypto_virt_probe(struct platform_device *pdev)\n--\ndrivers/soc/qcom/crypto_virt.c-157-\ndrivers/soc/qcom/crypto_virt.c:158:\tvirtblk_set_crypto_ops(\u0026virtblk_crypto_qcom_vops);\ndrivers/soc/qcom/crypto_virt.c-159-\treturn 0;\n--\ndrivers/soc/qcom/crypto_virt.c=162=static void crypto_virt_remove(struct platform_device *pdev)\ndrivers/soc/qcom/crypto_virt.c-163-{\ndrivers/soc/qcom/crypto_virt.c:164:\tvirtblk_set_crypto_ops(NULL);\ndrivers/soc/qcom/crypto_virt.c-165-}\n--\ninclude/linux/virtio_blk_crypto_ext.h=56=bool virtblk_crypto_register(struct request_queue *q);\ninclude/linux/virtio_blk_crypto_ext.h-57-\ninclude/linux/virtio_blk_crypto_ext.h:58:void virtblk_set_crypto_ops(struct virtblk_crypto_variant_ops *ops);\ninclude/linux/virtio_blk_crypto_ext.h-59-\n"
}

15/2 2026/08/27 17:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 46194
							output: 23
							thoughts: 2266
						

Thoughts:
**Analysis of virtio-blk Crypto Vulnerabilities**

Okay, let's break this down. My initial thought process revolves around understanding the conditions required to trigger the crypto path in `virtio-blk` and, more importantly, where it might fail and potentially be vulnerable. The core logic hinges on `crypto_virt.c`, which, as I see it, acts as a critical link.

First, I realize that `crypto_virt.c` is the only file that calls `virtblk_set_crypto_ops`. This sets the stage. But, the real kicker is that it *requires* `qcom_scm` to be available. This immediately raises a red flag; if `qcom_scm` fails, so does key programming. And if key programming fails, the crypto bio submission to `virtio_blk` will never happen.

The immediate question then becomes: can `qcom_scm` *ever* succeed in a virtual machine environment, without the underlying physical hardware? I'm pretty sure it can't, because of the SMC calls. This means we are starting from a place where it will very often not work. The hypervisor needs to emulate this SMC call, otherwise we will get an error like `-EOPNOTSUPP` or `-EINVAL`.

However, I started to think, "What if `blk-crypto-fallback` isn't enabled?" If key programming fails, `fscrypt` will fail to set the policy and no I/O will be submitted. So this seems like an important branch to consider.

I was also concerned about the case of `BLK_CRYPTO_KEY_TYPE_RAW`. Does `crypto_virt_program_key` even support these types? I then analyzed the `crypto_virt_program_key` function and I quickly identified the use of `qcom_scm_ice_set_key` which only supports AES-256-XTS and it immediately calls `qcom_scm_ice_set_key`. That won't be supported by just raw keys.

The analysis then took me down the path of understanding how keys are added. `blk_crypto_keyslot_program` is the key. If this fails, the key isn't added and `bio_crypt_set_ctx` cannot use it.

I then considered `blk-crypto-proxy`. I quickly realized it needs `hv_ops`, which doesn't seem to be registered. This path seems essentially unreachable, which is fine.

I then considered other ways a bio could be submitted with `bc_key == NULL`. It looked like `bio_crypt_set_ctx_by_slot` is only called by `blk-crypto-proxy`, making this unreachable too.

After a lot of thought, my analysis shifted to the actual setup and initialization, starting with the `ufs-qcom.c` module that now parsed the `qcom,ice-keyslot-map` DT node. Can this be fuzzed? Well, syzkaller *can* inject DT nodes, if it's fuzzing DT, but it usually doesn't. If it does, it just changes the number of slots initialized in `blk_crypto_profile_init`, and doesn't change much. So that seemed less interesting.

Then, the focus shifted to `virtio_blk` crypto initialization using `VIRTIO_BLK_F_INLINE_ENCRYPTION`. Syzkaller *could* emulate a virtio device with this feature. It provides config space and responds to `VIRTIO_BLK_T_GET_CRYPTO_MODES` requests, so this path is actually reachable. This would call `virtblk_init_inline_crypto` which initializes `g_vdcp.profile` and calls `virtblk_crypto_register`, and `blk_crypto_register`. This probe path *is* reachable during device probe. This is important: it's a few lines of code, but the initialization *is* reachable, and that might be interesting.

Here I looked further. If `blk_crypto_register` succeeds, userspace can try `fscrypt` on the device. However, `virtblk_crypto_ops_get` returns NULL, which causes `-EOPNOTSUPP` so `fscrypt` fails. No I/O is submitted with a crypto context. This means the I/O path itself (e.g. `virtblk_setup_cmd` with `VIRTIO_BLK_T_CRYPTO_IN/OUT`) is ultimately unreachable unless `qcom_scm` can work.

I then went back to `QCOM_CRYPTO_VIRT` being probed. Syzkaller might create a platform device for it. But even if it does, `qcom_scm_ice_set_


16/2 2026/08/27 17:01 tool grepper
0m Args:
{
  "Expression": "bcp_register_hypervisor_ops"
}

Results:
{
  "Output": "drivers/block/blk-crypto-proxy.c=28=static DEFINE_MUTEX(g_slot_virt_ops_lock);\ndrivers/block/blk-crypto-proxy.c-29-\ndrivers/block/blk-crypto-proxy.c:30:int bcp_register_hypervisor_ops(const struct bcp_hypervisor_ops *ops)\ndrivers/block/blk-crypto-proxy.c-31-{\n--\ndrivers/block/blk-crypto-proxy.c-41-}\ndrivers/block/blk-crypto-proxy.c:42:EXPORT_SYMBOL_GPL(bcp_register_hypervisor_ops);\ndrivers/block/blk-crypto-proxy.c-43-\n--\ninclude/linux/blk-crypto-proxy.h=17=struct bcp_hypervisor_ops {\n--\ninclude/linux/blk-crypto-proxy.h-30-/**\ninclude/linux/blk-crypto-proxy.h:31: * bcp_register_hypervisor_ops() - register the hypervisor op-set\ninclude/linux/blk-crypto-proxy.h-32- * @ops: op-set to register; must remain valid until unregistered.\n--\ninclude/linux/blk-crypto-proxy.h-35- */\ninclude/linux/blk-crypto-proxy.h:36:int bcp_register_hypervisor_ops(const struct bcp_hypervisor_ops *ops);\ninclude/linux/blk-crypto-proxy.h-37-\n--\ninclude/linux/blk-crypto-proxy.h-39- * bcp_unregister_hypervisor_ops() - unregister the hypervisor op-set\ninclude/linux/blk-crypto-proxy.h:40: * @ops: must be the pointer that was passed to bcp_register_hypervisor_ops().\ninclude/linux/blk-crypto-proxy.h-41- *\n"
}

17/2 2026/08/27 17:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 49971
							output: 24
							thoughts: 734
						

Thoughts:
**Analyzing the `blk-crypto-proxy` Dead Code and Virtio-blk Crypto I/O**

Okay, so I'm digging into this `blk-crypto-proxy` code, and it's looking pretty clear that it's essentially dead. Not a surprise, really. Now I need to figure out the implications for `virtio_blk` and its crypto I/O path. The question is, can we somehow bypass the intended key programming flow, that is, the `program_key` function and submit a bio with a `bc_key` that's *not* been properly initialized?

If `program_key` fails, `blk_crypto_init_key` fails as well, that I know for sure. But wait a minute, `blk_crypto_init_key` doesn't *actually* call `program_key`. `program_key` gets invoked when a key is assigned to a specific device profile, right? That happens via something like `blk_crypto_start_using_key`. That function, in turn, calls `blk_crypto_profile_program_key`, which ultimately calls `profile->ll_ops.keyslot_program`. If that keyslot programming fails, `blk_crypto_start_using_key` returns an error. Right, and if *that* happens, `fscrypt` would fail to even set up the key. And, if the key setup fails, then `fscrypt` won't submit any bios with this key. The `bio_crypt_set_ctx` function, therefore, will never be invoked for that device. Thus, `virtblk_setup_cmd` will never see a request with `is_crypto_request(vdev, req) == true`.

Now, can a user construct a bio and manually call `bio_crypt_set_ctx`? Nope, that's internal kernel API, used by `fscrypt` and `device-mapper`. And if `device-mapper` (thinking specifically `dm-crypt` with the `inline_crypt` option) is in the picture, it calls `blk_crypto_start_using_key`. So, if the key setup within `dm-crypt` fails, the `dm-crypt` table load fails. Therefore, no bios get submitted at all.

So, how *else* could this happen? What if `virtblk_crypto_ops_get` returns a valid pointer to a set of crypto operations? That only happens if `virtblk_set_crypto_ops` is called. That one is only called by the `crypto_virt.c` driver, which is a platform driver specifically for the `qcom,crypto-virt` device.

So, if we can actually instantiate *that* device somehow, `virtblk_set_crypto_ops` will be called. Then the intended key programming, `program_key`, will call `crypto_virt_program_key`, which in turn calls `qcom_scm_ice_set_key`. If that fails, it will return an error, as per the code. My real question is, can `qcom_scm_ice_set_key` *actually* return 0 (success) in a VM environment? Let's go look at that function's implementation directly to be sure.



18/2 2026/08/27 17:01 tool grepper
0m Args:
{
  "Expression": "qcom_scm_ice_set_key"
}

Results:
{
  "Output": "drivers/firmware/qcom/qcom_scm.c=1473=EXPORT_SYMBOL_GPL(qcom_scm_ocmem_unlock);\n--\ndrivers/firmware/qcom/qcom_scm.c-1478- * Return: true iff the SCM calls wrapped by qcom_scm_ice_invalidate_key() and\ndrivers/firmware/qcom/qcom_scm.c:1479: *\t   qcom_scm_ice_set_key() are available.\ndrivers/firmware/qcom/qcom_scm.c-1480- */\n--\ndrivers/firmware/qcom/qcom_scm.c=1514=EXPORT_SYMBOL_GPL(qcom_scm_ice_invalidate_key);\n--\ndrivers/firmware/qcom/qcom_scm.c-1516-/**\ndrivers/firmware/qcom/qcom_scm.c:1517: * qcom_scm_ice_set_key() - Set an inline encryption key\ndrivers/firmware/qcom/qcom_scm.c-1518- * @index: the keyslot into which to set the key\n--\ndrivers/firmware/qcom/qcom_scm.c-1536- */\ndrivers/firmware/qcom/qcom_scm.c:1537:int qcom_scm_ice_set_key(u32 index, const u8 *key, u32 key_size,\ndrivers/firmware/qcom/qcom_scm.c-1538-\t\t\t enum qcom_scm_ice_cipher cipher, u32 data_unit_size)\n--\ndrivers/firmware/qcom/qcom_scm.c-1568-}\ndrivers/firmware/qcom/qcom_scm.c:1569:EXPORT_SYMBOL_GPL(qcom_scm_ice_set_key);\ndrivers/firmware/qcom/qcom_scm.c-1570-\n--\ndrivers/soc/qcom/crypto_virt.c=13=static int crypto_virt_program_key(const struct blk_crypto_key *key,\n--\ndrivers/soc/qcom/crypto_virt.c-31-\ndrivers/soc/qcom/crypto_virt.c:32:\t/* qcom_scm_ice_set_key()'s data_unit_size is expressed in 512-byte units */\ndrivers/soc/qcom/crypto_virt.c-33-\tdus_512_units = key-\u003ecrypto_cfg.data_unit_size / 512;\ndrivers/soc/qcom/crypto_virt.c-34-\ndrivers/soc/qcom/crypto_virt.c:35:\tret = qcom_scm_ice_set_key(slot, key-\u003ebytes, key-\u003esize,\ndrivers/soc/qcom/crypto_virt.c-36-\t\tQCOM_SCM_ICE_CIPHER_AES_256_XTS, dus_512_units);\n--\ndrivers/soc/qcom/ice.c=349=static int qcom_ice_program_wrapped_key(struct qcom_ice *ice, unsigned int slot,\n--\ndrivers/soc/qcom/ice.c-372-\t/* Call into TrustZone to program the wrapped key using HWKM. */\ndrivers/soc/qcom/ice.c:373:\terr = qcom_scm_ice_set_key(translate_hwkm_slot(ice, slot), bkey-\u003ebytes,\ndrivers/soc/qcom/ice.c-374-\t\t\t\t   bkey-\u003esize, cfg.capidx, cfg.dusize);\n--\ndrivers/soc/qcom/ice.c-376-\t\tdev_err_ratelimited(dev,\ndrivers/soc/qcom/ice.c:377:\t\t\t\t    \"qcom_scm_ice_set_key failed; err=%d, slot=%u\\n\",\ndrivers/soc/qcom/ice.c-378-\t\t\t\t    err, slot);\n--\ndrivers/soc/qcom/ice.c=388=int qcom_ice_program_key(struct qcom_ice *ice, unsigned int slot,\n--\ndrivers/soc/qcom/ice.c-424-\ndrivers/soc/qcom/ice.c:425:\terr = qcom_scm_ice_set_key(slot, key.bytes, AES_256_XTS_KEY_SIZE,\ndrivers/soc/qcom/ice.c-426-\t\t\t\t   QCOM_SCM_ICE_CIPHER_AES_256_XTS,\n--\ninclude/linux/firmware/qcom/qcom_scm.h=119=int qcom_scm_ice_invalidate_key(u32 index);\ninclude/linux/firmware/qcom/qcom_scm.h:120:int qcom_scm_ice_set_key(u32 index, const u8 *key, u32 key_size,\ninclude/linux/firmware/qcom/qcom_scm.h-121-\t\t\t enum qcom_scm_ice_cipher cipher, u32 data_unit_size);\n"
}

19/2 2026/08/27 17:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 52713
							output: 43
							thoughts: 34
						

Thoughts:
Here's my summary, as though I'm thinking through this process:

**Diving into the QCOM SCM Key Setting Function**

Alright, let's get down to brass tacks. I need to understand how these keys are being set within the QCOM Secure Code Module (SCM) framework. The first place to start is, as always, the source code. Let's dig into `drivers/firmware/qcom/qcom_scm.c` and specifically hone in on `qcom_scm_ice_set_key`. That function name itself is a pretty good clue; it suggests we're dealing with setting keys related to ICE (maybe Internal Configuration Engine, or something similar – I'll need to confirm that). I'm expecting this function to be the core of the key provisioning process, likely interacting directly with the hardware abstraction or the SCM's internal APIs. I'll be looking for how it handles key lengths, access permissions, error conditions, and the specifics of the secure key storage mechanism. I'm hoping to see clear separation of concerns, and well-commented code, given its crucial role in the system. Let's see what we find.



20/2 2026/08/27 17:01 tool read-file
0m Args:
{
  "File": "drivers/firmware/qcom/qcom_scm.c",
  "FirstLine": 1537,
  "LineCount": 30
}

Results:
{
  "Contents": "1537:\tint qcom_scm_ice_set_key(u32 index, const u8 *key, u32 key_size,\n1538:\t\t\t\t enum qcom_scm_ice_cipher cipher, u32 data_unit_size)\n1539:\t{\n1540:\t\tstruct qcom_scm_desc desc = {\n1541:\t\t\t.svc = QCOM_SCM_SVC_ES,\n1542:\t\t\t.cmd = QCOM_SCM_ES_CONFIG_SET_ICE_KEY,\n1543:\t\t\t.arginfo = QCOM_SCM_ARGS(5, QCOM_SCM_VAL, QCOM_SCM_RW,\n1544:\t\t\t\t\t\t QCOM_SCM_VAL, QCOM_SCM_VAL,\n1545:\t\t\t\t\t\t QCOM_SCM_VAL),\n1546:\t\t\t.args[0] = index,\n1547:\t\t\t.args[2] = key_size,\n1548:\t\t\t.args[3] = cipher,\n1549:\t\t\t.args[4] = data_unit_size,\n1550:\t\t\t.owner = ARM_SMCCC_OWNER_SIP,\n1551:\t\t};\n1552:\t\n1553:\t\tint ret;\n1554:\t\n1555:\t\tvoid *keybuf __free(qcom_tzmem) = qcom_tzmem_alloc(__scm-\u003emempool,\n1556:\t\t\t\t\t\t\t\t   key_size,\n1557:\t\t\t\t\t\t\t\t   GFP_KERNEL);\n1558:\t\tif (!keybuf)\n1559:\t\t\treturn -ENOMEM;\n1560:\t\tmemcpy(keybuf, key, key_size);\n1561:\t\tdesc.args[1] = qcom_tzmem_to_phys(keybuf);\n1562:\t\n1563:\t\tret = qcom_scm_call(__scm-\u003edev, \u0026desc, NULL);\n1564:\t\n1565:\t\tmemzero_explicit(keybuf, key_size);\n1566:\t\n"
}

21/2 2026/08/27 17:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 53578
							output: 35
							thoughts: 2026
						

Thoughts:
**Analyzing the Crypto Bio Path to virtio_blk**

Okay, let's break this down step-by-step. My initial thought is that `qcom_scm_call` will lead to an SMC call, which, if not emulated by the hypervisor, is going to fail and cause `qcom_scm_ice_set_key` to fail. Consequently, `program_key` will fail, and this cascade will take down `fscrypt`'s key setup. This, in turn, kills the submission of crypto bios to `virtio_blk`, so `virtblk_setup_cmd` won't ever see a crypto request.

But wait, is that *truly* the only path? I need to explore alternatives. `blk-crypto-proxy` comes to mind, but its reliance on `hv_ops`, which isn't registered, makes it a dead end.

Okay, what about a zero `max_slots` in `virtblk_init_inline_crypto`? That immediately returns an error. It has to be > 0. Then, `blk_crypto_profile_init` kicks off, followed by `virtblk_crypto_register`. Any other bypasses exist?

Hardware-wrapped keys, perhaps? `fscrypt` *can* use these. If it does, there are calls to `blk_crypto_derive_sw_secret`, `blk_crypto_prepare_key`, and `blk_crypto_import_key`, all of which will then call `virtblk_crypto_ops_get`. If `QCOM_CRYPTO_VIRT` isn't probed, we'll get `-EOPNOTSUPP`. And if it *is* probed, we're back to `qcom_scm` calls, which will fail without a TrustZone. So, hardware-wrapped keys seem to suffer the same fate.

Okay, what if `virtblk_crypto_ops_get` returns NULL, but `fscrypt` ignores it? Not possible! `fscrypt` depends on `keyslot_program`, and if that's unsupported, `blk_crypto_start_using_key` returns `-EOPNOTSUPP`. That's important.

Ah, I see. In `virtio_blk_crypto_ext.c`, `virtblk_crypto_ops` does set `keyslot_program`. So, `profile->ll_ops.keyslot_program` is NOT NULL, but it *will* return `-EOPNOTSUPP` if either `ops` is NULL or `ops->program_key` is NULL. So `blk_crypto_start_using_key` will still result in `-EOPNOTSUPP`, and, therefore, `fscrypt` will fall back to software crypto (if it's enabled) or fail completely. Then the `bio` gets encrypted in software, leaving the `bio->bi_crypt_context` as NULL when it reaches `virtio_blk`, so `is_crypto_request(vdev, req)` will be false and `VIRTIO_BLK_T_CRYPTO_IN/OUT` won't be set.

But wait a minute! What about a mock virtio device that supports `VIRTIO_BLK_F_INLINE_ENCRYPTION`? Syzkaller can, in fact, do this. But the problem is that `virtblk_crypto_keyslot_program` *always* returns `-EOPNOTSUPP` unless `QCOM_CRYPTO_VIRT` is probed and `qcom_scm_ice_set_key` succeeds. Can `qcom_scm_ice_set_key` succeed? No, because syzkaller doesn't emulate the Qualcomm TrustZone SMC calls. So, still no joy.

Is there *any* way to submit a crypto bio without `blk_crypto_start_using_key`? `bio_crypt_set_ctx` *requires* a `blk_crypto_key` that's been programmed into a keyslot. So, that's a dead end, too, right? *Wait*, `bio_crypt_set_ctx` doesn't check if the key is programmed! Interesting...

Who calls `bio_crypt_set_ctx`? `fscrypt` does, but only if `fscrypt_setup_encryption_info` succeeds, and that's the one that calls `fscrypt_setup_inline_crypt`, which *does* call `blk_crypto_start_using_key`. If that fails, `fscrypt` doesn't use inline crypto. What about `dm-crypt`? That's the same story: it calls `blk_crypto_start_using_key`


22/2 2026/08/27 17:02 tool read-file
0m Args:
{
  "File": "block/blk-crypto.c",
  "FirstLine": 290,
  "LineCount": 40
}

Results:
{
  "Contents": " 290:\t\tconst struct blk_crypto_key *bc_key = bio-\u003ebi_crypt_context-\u003ebc_key;\n 291:\t\tstruct block_device *bdev = bio-\u003ebi_bdev;\n 292:\t\n 293:\t\t/* Error if bio has no data. */\n 294:\t\tif (WARN_ON_ONCE(!bio_has_data(bio))) {\n 295:\t\t\tbio_io_error(bio);\n 296:\t\t\treturn false;\n 297:\t\t}\n 298:\t\n 299:\t\tif (!bc_key) {\n 300:\t\t\t/*\n 301:\t\t\t* Slot path: the ICE keyslot was pre-programmed by the\n 302:\t\t\t* hypervisor. The target device must natively support inline\n 303:\t\t\t* encryption; there is no fallback for slot-based crypto.\n 304:\t\t\t*/\n 305:\t\t\tif (!bdev_get_queue(bdev)-\u003ecrypto_profile) {\n 306:\t\t\t\tbio_endio_status(bio, BLK_STS_NOTSUPP);\n 307:\t\t\t\treturn false;\n 308:\t\t\t}\n 309:\t\t\treturn true;\n 310:\t\t}\n 311:\t\n 312:\t\t/*\n 313:\t\t * If the device does not natively support the encryption context, try to use\n 314:\t\t * the fallback if available.\n 315:\t\t */\n 316:\t\tif (!blk_crypto_config_supported_natively(bdev, \u0026bc_key-\u003ecrypto_cfg)) {\n 317:\t\t\tif (!IS_ENABLED(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK)) {\n 318:\t\t\t\tpr_warn_once(\"%pg: crypto API fallback disabled; failing request.\\n\",\n 319:\t\t\t\t\tbdev);\n 320:\t\t\t\tbio_endio_status(bio, BLK_STS_NOTSUPP);\n 321:\t\t\t\treturn false;\n 322:\t\t\t}\n 323:\t\t\treturn blk_crypto_fallback_bio_prep(bio);\n 324:\t\t}\n 325:\t\n 326:\t\treturn true;\n 327:\t}\n 328:\tEXPORT_SYMBOL_GPL(__blk_crypto_submit_bio);\n 329:\t\n"
}

23/2 2026/08/27 17:02 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 57271
							output: 24
							thoughts: 83
						

Thoughts:
Okay, here's my take on those thoughts, framed as my own mental processing:

**Digging into the Block Crypto Subsystem**

Alright, a key realization hits me: `__blk_crypto_submit_bio` *doesn't* actually call `blk_crypto_get_keyslot`. That's a crucial piece of information. Where *does* that function get called, then? It’s a pretty vital part of the encryption setup.

Let's see… I thought it was in `blk_crypto_rq_get_keyslot`, but let me double-check that. Wait a second, no, that isn't quite right. Okay, time to dive deeper into `blk_crypto_rq_get_keyslot` and see where this leads. I need to understand this key flow properly.



24/2 2026/08/27 17:02 tool grepper
0m Args:
{
  "Expression": "blk_crypto_rq_get_keyslot"
}

Results:
{
  "Output": "block/blk-crypto-internal.h=163=static inline void bio_crypt_do_front_merge(struct request *rq,\n--\nblock/blk-crypto-internal.h-172-\nblock/blk-crypto-internal.h:173:blk_status_t __blk_crypto_rq_get_keyslot(struct request *rq);\nblock/blk-crypto-internal.h:174:static inline blk_status_t blk_crypto_rq_get_keyslot(struct request *rq)\nblock/blk-crypto-internal.h-175-{\nblock/blk-crypto-internal.h-176-\tif (blk_crypto_rq_is_encrypted(rq) \u0026\u0026 rq-\u003ecrypt_ctx-\u003ebc_key)\nblock/blk-crypto-internal.h:177:\t\treturn __blk_crypto_rq_get_keyslot(rq);\nblock/blk-crypto-internal.h-178-\treturn BLK_STS_OK;\n--\nblock/blk-crypto.c=250=bool bio_crypt_ctx_mergeable(struct bio_crypt_ctx *bc1, unsigned int bc1_bytes,\n--\nblock/blk-crypto.c-258-\nblock/blk-crypto.c:259:blk_status_t __blk_crypto_rq_get_keyslot(struct request *rq)\nblock/blk-crypto.c-260-{\n--\nblock/blk-mq.c=3093=void blk_mq_submit_bio(struct bio *bio)\n--\nblock/blk-mq.c-3183-\nblock/blk-mq.c:3184:\tret = blk_crypto_rq_get_keyslot(rq);\nblock/blk-mq.c-3185-\tif (ret != BLK_STS_OK) {\n--\nblock/blk-mq.c=3224=blk_status_t blk_insert_cloned_request(struct request *rq)\n--\nblock/blk-mq.c-3282-\nblock/blk-mq.c:3283:\tret = blk_crypto_rq_get_keyslot(rq);\nblock/blk-mq.c-3284-\tif (ret != BLK_STS_OK)\n"
}

25/2 2026/08/27 17:02 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 58123
							output: 23
							thoughts: 256
						

Thoughts:
**My Line of Reasoning on `blk_crypto_rq_get_keyslot`**

Okay, so I'm tracing the crypto request flow here. It looks like `blk_crypto_rq_get_keyslot` is called within `blk_mq_submit_bio`, which makes sense. Diving deeper, `__blk_crypto_rq_get_keyslot` then calls `blk_crypto_get_keyslot`. If *that* call to `blk_crypto_get_keyslot` fails, the request gets failed, that much is clear. Hmm, and `blk_crypto_get_keyslot` itself invokes `blk_crypto_keyslot_program`…

But, hang on a second. I'm remembering that `fscrypt` specifically calls `blk_crypto_start_using_key` *before* it even submits any bios. Now, if *that* call to `blk_crypto_start_using_key` fails, `fscrypt` should prevent any bios using inline crypto from being submitted. Which means, at least under the typical use case, `blk_crypto_rq_get_keyslot` will never actually be hit on the device with `fscrypt`.

That's an interesting catch. The design is logically sound.

But what about a corner case? Let me think here... what if we have `fscrypt` *and* a virtio-blk device, and `blk_crypto_start_using_key` is called? That call invokes `blk_crypto_profile_program_key`, I need to double-check that part. Let me go back and have a closer look at `blk_crypto_start_using_key`.



26/2 2026/08/27 17:02 tool grepper
0m Args:
{
  "Expression": "blk_crypto_start_using_key"
}

Results:
{
  "Output": "Documentation/block/inline-encryption.rst=185=API presented to users of the block layer\n--\nDocumentation/block/inline-encryption.rst-189-\nDocumentation/block/inline-encryption.rst:190:Users must call ``blk_crypto_start_using_key()`` before actually starting to use\nDocumentation/block/inline-encryption.rst-191-a blk_crypto_key on a block_device.  This is needed to initialize\n--\nDocumentation/block/inline-encryption.rst=213=as follows:\n--\nDocumentation/block/inline-encryption.rst-215-1. ``blk_crypto_init_key()``\nDocumentation/block/inline-encryption.rst:216:2. ``blk_crypto_start_using_key()``\nDocumentation/block/inline-encryption.rst-217-3. ``bio_crypt_set_ctx()`` (potentially many times)\n--\nDocumentation/block/inline-encryption.rst=221=If a blk_crypto_key is being used on multiple block_devices, then\nDocumentation/block/inline-encryption.rst:222:``blk_crypto_start_using_key()`` and ``blk_crypto_evict_key()`` must be called\nDocumentation/block/inline-encryption.rst-223-on each block_device.\n--\nblock/blk-crypto-fallback.c=488=bool blk_crypto_fallback_bio_prep(struct bio *bio)\n--\nblock/blk-crypto-fallback.c-493-\tif (WARN_ON_ONCE(!tfms_inited[bc-\u003ebc_key-\u003ecrypto_cfg.crypto_mode])) {\nblock/blk-crypto-fallback.c:494:\t\t/* User didn't call blk_crypto_start_using_key() first */\nblock/blk-crypto-fallback.c-495-\t\tbio_io_error(bio);\n--\nblock/blk-crypto.c=419=bool blk_crypto_config_supported_natively(struct block_device *bdev,\n--\nblock/blk-crypto.c-438-/**\nblock/blk-crypto.c:439: * blk_crypto_start_using_key() - Start using a blk_crypto_key on a device\nblock/blk-crypto.c-440- * @bdev: block device to operate on\n--\nblock/blk-crypto.c-454- */\nblock/blk-crypto.c:455:int blk_crypto_start_using_key(struct block_device *bdev,\nblock/blk-crypto.c-456-\t\t\t       const struct blk_crypto_key *key)\n--\nblock/blk-crypto.c-465-}\nblock/blk-crypto.c:466:EXPORT_SYMBOL_GPL(blk_crypto_start_using_key);\nblock/blk-crypto.c-467-\n--\ndrivers/md/dm-inlinecrypt.c=310=static int inlinecrypt_ctr(struct dm_target *ti, unsigned int argc, char **argv)\n--\ndrivers/md/dm-inlinecrypt.c-416-\ndrivers/md/dm-inlinecrypt.c:417:\terr = blk_crypto_start_using_key(ctx-\u003edev-\u003ebdev, \u0026ctx-\u003ekey);\ndrivers/md/dm-inlinecrypt.c-418-\tif (err) {\n--\nfs/crypto/block.c=78=int fscrypt_prepare_inline_crypt_key(struct fscrypt_prepared_key *prep_key,\n--\nfs/crypto/block.c-122-\tfor (i = 0; i \u003c num_devs; i++) {\nfs/crypto/block.c:123:\t\terr = blk_crypto_start_using_key(devs[i], blk_key);\nfs/crypto/block.c-124-\t\tif (err)\n--\ninclude/linux/blk-crypto.h=187=int blk_crypto_init_key(struct blk_crypto_key *blk_key,\n--\ninclude/linux/blk-crypto.h-193-\ninclude/linux/blk-crypto.h:194:int blk_crypto_start_using_key(struct block_device *bdev,\ninclude/linux/blk-crypto.h-195-\t\t\t       const struct blk_crypto_key *key);\n"
}

27/2 2026/08/27 17:02 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 59853
							output: 35
							thoughts: 25
						

Thoughts:
Here's my take on the thought process, as if it were my own:

**Diving into Block Crypto Implementation**

Okay, time to dig in. The task at hand involves understanding how block crypto keys are utilized, so let's jump straight into the code. My gut tells me the place to start is `blk_crypto_start_using_key` within `block/blk-crypto.c`. That function's name is pretty explicit, so it should be the logical entry point for tracing the lifecycle of a key within the block layer's cryptographic operations. I'll need to carefully examine what it does – how it's called, what data structures it manipulates, and what other functions it interacts with. This will be the key to piecing together the flow of operations around starting the use of a key.



28/2 2026/08/27 17:02 tool read-file
0m Args:
{
  "File": "block/blk-crypto.c",
  "FirstLine": 439,
  "LineCount": 30
}

Results:
{
  "Contents": " 439:\t * blk_crypto_start_using_key() - Start using a blk_crypto_key on a device\n 440:\t * @bdev: block device to operate on\n 441:\t * @key: A key to use on the device\n 442:\t *\n 443:\t * Upper layers must call this function to ensure that either the hardware\n 444:\t * supports the key's crypto settings, or the crypto API fallback has transforms\n 445:\t * for the needed mode allocated and ready to go. This function may allocate\n 446:\t * an skcipher, and *should not* be called from the data path, since that might\n 447:\t * cause a deadlock\n 448:\t *\n 449:\t * Return: 0 on success; -EOPNOTSUPP if the key is wrapped but the hardware does\n 450:\t *\t   not support wrapped keys; -ENOPKG if the key is a raw key but the\n 451:\t *\t   hardware does not support raw keys and blk-crypto-fallback is either\n 452:\t *\t   disabled or the needed algorithm is disabled in the crypto API; or\n 453:\t *\t   another -errno code if something else went wrong.\n 454:\t */\n 455:\tint blk_crypto_start_using_key(struct block_device *bdev,\n 456:\t\t\t\t       const struct blk_crypto_key *key)\n 457:\t{\n 458:\t\tif (blk_crypto_config_supported_natively(bdev, \u0026key-\u003ecrypto_cfg))\n 459:\t\t\treturn 0;\n 460:\t\tif (key-\u003ecrypto_cfg.key_type != BLK_CRYPTO_KEY_TYPE_RAW) {\n 461:\t\t\tpr_warn_ratelimited(\"%pg: no support for wrapped keys\\n\", bdev);\n 462:\t\t\treturn -EOPNOTSUPP;\n 463:\t\t}\n 464:\t\treturn blk_crypto_fallback_start_using_mode(key-\u003ecrypto_cfg.crypto_mode);\n 465:\t}\n 466:\tEXPORT_SYMBOL_GPL(blk_crypto_start_using_key);\n 467:\t\n 468:\t/**\n"
}

29/2 2026/08/27 17:02 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 60676
							output: 35
							thoughts: 293
						

Thoughts:
**My Current Thinking on the Block Crypto Key Setup**

Okay, hold on a second! I was just tracing through `blk_crypto_start_using_key`, and I need to clarify something. It *doesn't* call `keyslot_program` directly, as I initially assumed. Instead, it calls `blk_crypto_config_supported_natively`. And that function, as I recall, only verifies if the current crypto profile (mode, data unit size, etc.) is supported by the device. It's a support *check*, not an *implementation* check. Crucially, it doesn't confirm whether `keyslot_program` is actually implemented or if it'll execute successfully.

So, in the case where `blk_crypto_config_supported_natively` returns true, `blk_crypto_start_using_key` will also return 0, which implies success, even if the key slot programming might ultimately fail. Following the code path, `fscrypt` would then proceed with setting up the key and starts submitting bio's with `bio_crypt_set_ctx`. Now, the `__blk_crypto_submit_bio` function would be called. If `blk_crypto_config_supported_natively` is true, then it returns true, which means the bio can continue. After that the bio makes its way to the block layer, and `blk_mq_submit_bio` is invoked, and ultimately calls `blk_crypto_rq_get_keyslot`, which, in turn calls `__blk_crypto_rq_get_keyslot`. Time to dive deeper into `__blk_crypto_rq_get_keyslot`.



30/2 2026/08/27 17:02 tool read-file
0m Args:
{
  "File": "block/blk-crypto.c",
  "FirstLine": 259,
  "LineCount": 30
}

Results:
{
  "Contents": " 259:\tblk_status_t __blk_crypto_rq_get_keyslot(struct request *rq)\n 260:\t{\n 261:\t\treturn blk_crypto_get_keyslot(rq-\u003eq-\u003ecrypto_profile,\n 262:\t\t\t\t\t      rq-\u003ecrypt_ctx-\u003ebc_key,\n 263:\t\t\t\t\t      \u0026rq-\u003ecrypt_keyslot);\n 264:\t}\n 265:\t\n 266:\tvoid __blk_crypto_rq_put_keyslot(struct request *rq)\n 267:\t{\n 268:\t\tblk_crypto_put_keyslot(rq-\u003ecrypt_keyslot);\n 269:\t\trq-\u003ecrypt_keyslot = NULL;\n 270:\t}\n 271:\t\n 272:\tvoid __blk_crypto_free_request(struct request *rq)\n 273:\t{\n 274:\t\t/* The keyslot, if one was needed, should have been released earlier. */\n 275:\t\tif (WARN_ON_ONCE(rq-\u003ecrypt_keyslot))\n 276:\t\t\t__blk_crypto_rq_put_keyslot(rq);\n 277:\t\n 278:\t\tmempool_free(rq-\u003ecrypt_ctx, bio_crypt_ctx_pool);\n 279:\t\trq-\u003ecrypt_ctx = NULL;\n 280:\t}\n 281:\t\n 282:\t/*\n 283:\t * Process a bio with a crypto context.  Returns true if the caller should\n 284:\t * submit the passed in bio, false if the bio is consumed.\n 285:\t *\n 286:\t * See the kerneldoc comment for blk_crypto_submit_bio for further details.\n 287:\t */\n 288:\tbool __blk_crypto_submit_bio(struct bio *bio)\n"
}

31/2 2026/08/27 17:02 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 61854
							output: 37
							thoughts: 51
						

Thoughts:
Here's my summary, as though I'm thinking through this process:

**Diving into Key Slot Retrieval**

Okay, so `__blk_crypto_rq_get_keyslot` is the function we're looking at. The name itself suggests we're retrieving a specific key slot, presumably associated with a crypto request. Right, so it looks like `__blk_crypto_rq_get_keyslot` calls `blk_crypto_get_keyslot`. Makes sense, the underscores usually indicate an internal helper function.

Now, I need to understand what `blk_crypto_get_keyslot` actually *does*. That's where the real magic happens, probably involving profile lookups or key cache management. Let's jump into `block/blk-crypto-profile.c` and dig into `blk_crypto_get_keyslot`. That's where this key slot retrieval logic should reside. Time to see how the system identifies and fetches the right keys!



32/2 2026/08/27 17:03 tool read-file
0m Args:
{
  "File": "block/blk-crypto-profile.c",
  "FirstLine": 317,
  "LineCount": 40
}

Results:
{
  "Contents": " 317:\t\treturn BLK_STS_OK;\n 318:\t}\n 319:\t\n 320:\t/**\n 321:\t * blk_crypto_put_keyslot() - Release a reference to a keyslot\n 322:\t * @slot: The keyslot to release the reference of\n 323:\t *\n 324:\t * Context: Any context.\n 325:\t */\n 326:\tvoid blk_crypto_put_keyslot(struct blk_crypto_keyslot *slot)\n 327:\t{\n 328:\t\tstruct blk_crypto_profile *profile = slot-\u003eprofile;\n 329:\t\tunsigned long flags;\n 330:\t\n 331:\t\tif (atomic_dec_and_lock_irqsave(\u0026slot-\u003eslot_refs,\n 332:\t\t\t\t\t\t\u0026profile-\u003eidle_slots_lock, flags)) {\n 333:\t\t\tlist_add_tail(\u0026slot-\u003eidle_slot_node, \u0026profile-\u003eidle_slots);\n 334:\t\t\tspin_unlock_irqrestore(\u0026profile-\u003eidle_slots_lock, flags);\n 335:\t\t\twake_up(\u0026profile-\u003eidle_slots_wait_queue);\n 336:\t\t}\n 337:\t}\n 338:\t\n 339:\t/*\n 340:\t * This is an internal function that evicts a key from an inline encryption\n 341:\t * device that can be either a real device or the blk-crypto-fallback \"device\".\n 342:\t * It is used only by blk_crypto_evict_key(); see that function for details.\n 343:\t */\n 344:\tint __blk_crypto_evict_key(struct blk_crypto_profile *profile,\n 345:\t\t\t\t   const struct blk_crypto_key *key)\n 346:\t{\n 347:\t\tstruct blk_crypto_keyslot *slot;\n 348:\t\tint err;\n 349:\t\n 350:\t\tif (profile-\u003enum_slots == 0) {\n 351:\t\t\tif (profile-\u003ell_ops.keyslot_evict) {\n 352:\t\t\t\tblk_crypto_hw_enter(profile);\n 353:\t\t\t\terr = profile-\u003ell_ops.keyslot_evict(profile, key, -1);\n 354:\t\t\t\tblk_crypto_hw_exit(profile);\n 355:\t\t\t\treturn err;\n 356:\t\t\t}\n"
}

33/2 2026/08/27 17:03 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 62800
							output: 37
							thoughts: 16
						

Thoughts:
Here's my attempt at summarizing the thought process, tailored for an expert audience:

**Analyzing the Key Slot Function**

Okay, hold on. Before I proceed any further with this line of reasoning, I need to take a closer look at the `blk_crypto_get_keyslot` function. I can't just assume its behavior based on context; I need to understand its exact implementation and interactions with the block layer. Specifically, I'm thinking about how it retrieves or allocates a keyslot and the potential implications this might have on things like:

*   **Security:** Are there potential race conditions in key retrieval or slot assignment? Does it handle key derivation or storage securely?
*   **Performance:** How expensive is this function call? Does it involve any locking that could introduce bottlenecks in the critical path? I need to consider it's influence on write latency, too.
*   **Concurrency:** How does it handle concurrent access from different threads or processes? Does it use appropriate synchronization primitives? Is it scalable?
*   **Error handling:** What happens if the keyslot is unavailable, invalid, or otherwise fails? How are errors propagated and handled?

I need to understand the function’s input parameters, return values, and any side effects. Without a proper understanding of this key function, I can't be sure about the assumptions I'm making here, and might be missing something crucial. This could influence the next step in my analysis. It's time to dive into the code...



34/2 2026/08/27 17:03 tool read-file
0m Args:
{
  "File": "block/blk-crypto-profile.c",
  "FirstLine": 270,
  "LineCount": 40
}

Results:
{
  "Contents": " 270:\t\t\tgoto success;\n 271:\t\n 272:\t\tfor (;;) {\n 273:\t\t\tblk_crypto_hw_enter(profile);\n 274:\t\t\tslot = blk_crypto_find_and_grab_keyslot(profile, key);\n 275:\t\t\tif (slot) {\n 276:\t\t\t\tblk_crypto_hw_exit(profile);\n 277:\t\t\t\tgoto success;\n 278:\t\t\t}\n 279:\t\n 280:\t\t\t/*\n 281:\t\t\t * If we're here, that means there wasn't a slot that was\n 282:\t\t\t * already programmed with the key. So try to program it.\n 283:\t\t\t */\n 284:\t\t\tif (!list_empty(\u0026profile-\u003eidle_slots))\n 285:\t\t\t\tbreak;\n 286:\t\n 287:\t\t\tblk_crypto_hw_exit(profile);\n 288:\t\t\twait_event(profile-\u003eidle_slots_wait_queue,\n 289:\t\t\t\t   !list_empty(\u0026profile-\u003eidle_slots));\n 290:\t\t}\n 291:\t\n 292:\t\tslot = list_first_entry(\u0026profile-\u003eidle_slots, struct blk_crypto_keyslot,\n 293:\t\t\t\t\tidle_slot_node);\n 294:\t\tslot_idx = blk_crypto_keyslot_index(slot);\n 295:\t\n 296:\t\terr = profile-\u003ell_ops.keyslot_program(profile, key, slot_idx);\n 297:\t\tif (err) {\n 298:\t\t\twake_up(\u0026profile-\u003eidle_slots_wait_queue);\n 299:\t\t\tblk_crypto_hw_exit(profile);\n 300:\t\t\treturn errno_to_blk_status(err);\n 301:\t\t}\n 302:\t\n 303:\t\t/* Move this slot to the hash list for the new key. */\n 304:\t\tif (slot-\u003ekey)\n 305:\t\t\thlist_del(\u0026slot-\u003ehash_node);\n 306:\t\tslot-\u003ekey = key;\n 307:\t\thlist_add_head(\u0026slot-\u003ehash_node,\n 308:\t\t\t       blk_crypto_hash_bucket_for_key(profile, key));\n 309:\t\n"
}

35/2 2026/08/27 17:03 llm patch-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 63772
							output: 356
							thoughts: 8589
						

Thoughts:
**Inline Encryption Support for Virtio Block and Proxy Device - Reachable Code Paths for Fuzzing**

Alright, let's break this down. It looks like this patch introduces inline encryption support for virtio block devices, along with a proxy device that sits on the host side. My initial thought was that the I/O path itself is going to be tough to trigger, primarily because the key setup procedure (`blk_crypto_rq_get_keyslot`) is likely to fail in standard QEMU environments. This is due to the reliance on `QCOM_CRYPTO_VIRT` and an SMC call that won't work without a proper TrustZone emulation, or if the `QCOM_CRYPTO_VIRT` is not enabled, leading to an early failure of key programming, thus preventing a successful "crypto request" from reaching the `virtio_blk` driver.

However, the probe path—the code executed when the virtio block device is initialized—looks far more promising. The `virtblk_init_crypto`, `virtblk_get_crypto_modes`, and `virtblk_init_inline_crypto` functions are called when the device is probed, and this is where we can have some fun. These functions are responsible for reading config space, sending a `VIRTIO_BLK_T_GET_CRYPTO_MODES` request to the virtio device, and initializing the crypto profile. This means syzkaller can easily emulate the hardware, read malformed config space and craft bad virtio device replies, providing plenty of interesting fuzzing targets, especially around how the driver parses replies for supported modes. Additionally, I need to check the interactions between those probe functions and `blk_crypto_profile_init` and `blk_crypto_register`.

The `blk-crypto-proxy` part is trickier. It's meant for the host side, but it relies on `hv_ops`, which is not registered in the upstream kernel. This limits the usefulness of this code but doesn't make it 100% unreachable. This still means that the entry point `bcp_ioctl` can be fuzzed for testing purposes.

Additionally, this patch includes Qualcomm ICE keyslot partitioning, which relies on a DT node for `qcom,ice-keyslot-map`. I can enable the parsing of `qcom_ice_slots_probe`, and the slot table parser by fuzzing the DT node to test that path if the DT node is injected. Specifically, I'd need to fuzz the `ufs_qcom_ice_parse_slot_table`.

Therefore, while the I/O path is somewhat blocked due to the key setup issues, the probe path for the `virtio_blk` device and the DT parsing logic provides several entry points for fuzzing. These include key functions like `virtblk_init_crypto`, `virtblk_get_crypto_modes`, `virtblk_init_inline_crypto` along with other potential targets such as the ioctl entry point of the crypto proxy device and code involved in the parsing of ICE slots.

All things considered, this patch offers reachable code paths with testable interfaces. So I'll return `WorthFuzzing=true`, but highlight these focus symbols.



36/2 2026/08/27 17:04 tool set-results
0m Args:
{
  "EnableConfigs": [
    "VIRTBLK_CRYPTO_VIRTUALIZATION",
    "BLK_CRYPTO_PROXY",
    "QCOM_ICE_SLOTS",
    "QCOM_CRYPTO_VIRT",
    "BLK_INLINE_ENCRYPTION"
  ],
  "FocusSymbols": [
    "virtblk_init_crypto",
    "virtblk_get_crypto_modes",
    "virtblk_init_inline_crypto",
    "virtblk_crypto_keyslot_program",
    "bcp_ioctl",
    "qcom_ice_slots_probe",
    "ufs_qcom_ice_parse_slot_table"
  ],
  "Reasoning": "The patch introduces inline encryption support for virtio-blk guests and a host-side proxy device. While the host-side proxy (/dev/blk-crypto-proxy) is mostly unreachable because it relies on hypervisor ops that are not registered in-tree, and the guest-side I/O path is unreachable because key programming requires Qualcomm TrustZone SMC calls that fail in standard QEMU, the virtio-blk device probe path is fully reachable. When a virtio-blk device with VIRTIO_BLK_F_INLINE_ENCRYPTION is probed, the driver reads the virtio config space and sends a VIRTIO_BLK_T_GET_CRYPTO_MODES request to initialize the crypto profile. This probe path can be fuzzed by syzkaller's virtio fuzzer. Additionally, the patch adds DT parsing logic for Qualcomm ICE slot partitioning, which is reachable if the corresponding DT nodes are injected.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "VIRTBLK_CRYPTO_VIRTUALIZATION",
    "BLK_CRYPTO_PROXY",
    "QCOM_ICE_SLOTS",
    "QCOM_CRYPTO_VIRT",
    "BLK_INLINE_ENCRYPTION"
  ],
  "FocusSymbols": [
    "virtblk_init_crypto",
    "virtblk_get_crypto_modes",
    "virtblk_init_inline_crypto",
    "virtblk_crypto_keyslot_program",
    "bcp_ioctl",
    "qcom_ice_slots_probe",
    "ufs_qcom_ice_parse_slot_table"
  ],
  "Reasoning": "The patch introduces inline encryption support for virtio-blk guests and a host-side proxy device. While the host-side proxy (/dev/blk-crypto-proxy) is mostly unreachable because it relies on hypervisor ops that are not registered in-tree, and the guest-side I/O path is unreachable because key programming requires Qualcomm TrustZone SMC calls that fail in standard QEMU, the virtio-blk device probe path is fully reachable. When a virtio-blk device with VIRTIO_BLK_F_INLINE_ENCRYPTION is probed, the driver reads the virtio config space and sends a VIRTIO_BLK_T_GET_CRYPTO_MODES request to initialize the crypto profile. This probe path can be fuzzed by syzkaller's virtio fuzzer. Additionally, the patch adds DT parsing logic for Qualcomm ICE slot partitioning, which is reachable if the corresponding DT nodes are injected.",
  "WorthFuzzing": true
}

37/1 2026/08/27 17:04 action If
2m Args:
{
  "WorthFuzzing": true
}

Results:
null

38/2 2026/08/27 17:04 agent kmsan-evaluator
2m Model:
best-expensive

Args:
null

Results:
{
  "KMSANReasoning": "The patch series introduces a new proxy driver `blk-crypto-proxy` for inline encryption virtualization, a virtio-blk extension for inline encryption, and a Qualcomm-specific crypto virtualization driver.\n\nA thorough review of the data structures exchanged with user space and hardware reveals no uninitialized memory risks:\n1. The UAPI structures (`struct bcp_get_crypto_caps_arg`, `struct bcp_bind_context_arg`, and `struct bcp_submit_io_by_vslot_arg`) are carefully designed with explicit `__aligned_u64` fields to ensure identical layout between 32-bit and 64-bit architectures and to avoid any implicit compiler padding. All fields are fully initialized before being copied to user space.\n2. The virtio structures (`struct virtio_blk_crypto_msg` and the extended `virtio_blk_outhdr`) have no padding and are fully explicitly initialized in `virtblk_setup_cmd` before being sent to the virtio device.\n3. The `virtblk_cmodes` buffer used in `virtblk_get_crypto_modes` is allocated with `kzalloc` before being mapped to the device, preventing any uninitialized memory from being sent or read.\n4. Internal structures like `struct bio_crypt_ctx` and `struct blk_crypto_slot` are fully initialized when allocated (e.g., via `bio_crypt_set_ctx_by_slot`).\n5. The I/O data itself is handled via direct I/O (`bio_iov_iter_get_pages`), which pins user pages directly for DMA, avoiding intermediate kernel buffers that could leak uninitialized memory.\n\nSince all structures are fully initialized and contain no padding, there is no risk of uninitialized memory being used in control flow or leaked to user space/hardware. Any potential bugs introduced by these changes (e.g., out-of-bounds accesses, use-after-free, logic errors) would be caught by standard KASAN and other bug detectors. A dedicated KMSAN session is not justified.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit b864a4f707165a5c167ea55c41d670ae48e4993e
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Aug 27 16:58:51 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,crypto-virt.yaml b/Documentation/devicetree/bindings/soc/qcom/qcom,crypto-virt.yaml
new file mode 100644
index 0000000000000..5eb204f297627
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/qcom/qcom,crypto-virt.yaml
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/soc/qcom/qcom,crypto-virt.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: QCOM GVM Crypto Virt driver
+
+description: |
+  GVM-side hardware-wrapped-key SCM operations backing virtio_blk's inline
+  crypto layer. The host does not report a wrapped key size over virtio, so
+  it is provided here instead.
+
+maintainers:
+  - Linlin Zhang <linlin.zhang@oss.qualcomm.com>
+
+properties:
+  compatible:
+    const: qcom,crypto-virt
+
+  qcom,wrapped-key-size:
+    description: |
+      Size, in bytes, of the hardware-wrapped key generated/prepared/imported
+      via SCM calls.
+    $ref: /schemas/types.yaml#/definitions/uint32
+    minimum: 64
+    maximum: 128
+
+required:
+  - compatible
+
+additionalProperties: false
+
+examples:
+  - |
+    crypto_virt {
+        compatible = "qcom,crypto-virt";
+        qcom,wrapped-key-size = <100>;
+    };
diff --git a/block/blk-crypto-internal.h b/block/blk-crypto-internal.h
index 2c7a0446572aa..32c720aabba7f 100644
--- a/block/blk-crypto-internal.h
+++ b/block/blk-crypto-internal.h
@@ -26,9 +26,6 @@ int blk_crypto_sysfs_register(struct gendisk *disk);
 
 void blk_crypto_sysfs_unregister(struct gendisk *disk);
 
-void bio_crypt_dun_increment(u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE],
-			     unsigned int inc);
-
 bool bio_crypt_rq_ctx_compatible(struct request *rq, struct bio *bio);
 
 bool bio_crypt_ctx_mergeable(struct bio_crypt_ctx *bc1, unsigned int bc1_bytes,
@@ -176,7 +173,7 @@ static inline void bio_crypt_do_front_merge(struct request *rq,
 blk_status_t __blk_crypto_rq_get_keyslot(struct request *rq);
 static inline blk_status_t blk_crypto_rq_get_keyslot(struct request *rq)
 {
-	if (blk_crypto_rq_is_encrypted(rq))
+	if (blk_crypto_rq_is_encrypted(rq) && rq->crypt_ctx->bc_key)
 		return __blk_crypto_rq_get_keyslot(rq);
 	return BLK_STS_OK;
 }
diff --git a/block/blk-crypto-profile.c b/block/blk-crypto-profile.c
index 53126c091b0be..64202d64a0182 100644
--- a/block/blk-crypto-profile.c
+++ b/block/blk-crypto-profile.c
@@ -218,14 +218,15 @@ blk_crypto_find_and_grab_keyslot(struct blk_crypto_profile *profile,
 }
 
 /**
- * blk_crypto_keyslot_index() - Get the index of a keyslot
+ * blk_crypto_keyslot_index() - Get the physical index of a keyslot
  * @slot: a keyslot that blk_crypto_get_keyslot() returned
  *
- * Return: the 0-based index of the keyslot within the device's keyslots.
+ * Return: the physical ICE keyslot index, i.e. the 0-based position of @slot
+ * within the profile's keyslot array plus @slot->profile->slot_offset.
  */
 unsigned int blk_crypto_keyslot_index(struct blk_crypto_keyslot *slot)
 {
-	return slot - slot->profile->slots;
+	return (slot - slot->profile->slots) + slot->profile->slot_offset;
 }
 EXPORT_SYMBOL_GPL(blk_crypto_keyslot_index);
 
diff --git a/block/blk-crypto.c b/block/blk-crypto.c
index bc3a9f59574b1..2212d06d3c112 100644
--- a/block/blk-crypto.c
+++ b/block/blk-crypto.c
@@ -113,11 +113,31 @@ void bio_crypt_set_ctx(struct bio *bio, const struct blk_crypto_key *key,
 
 	bc->bc_key = key;
 	memcpy(bc->bc_dun, dun, sizeof(bc->bc_dun));
+	memset(&bc->bc_slot, 0, sizeof(bc->bc_slot));
 
 	bio->bi_crypt_context = bc;
 }
 EXPORT_SYMBOL_GPL(bio_crypt_set_ctx);
 
+void bio_crypt_set_ctx_by_slot(struct bio *bio,
+			       const struct blk_crypto_slot *slot,
+			       const u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE],
+			       gfp_t gfp_mask)
+{
+	struct bio_crypt_ctx *bc;
+
+	WARN_ON_ONCE(!(gfp_mask & __GFP_DIRECT_RECLAIM));
+
+	bc = mempool_alloc(bio_crypt_ctx_pool, gfp_mask);
+
+	bc->bc_key = NULL;
+	bc->bc_slot = *slot;
+	memcpy(bc->bc_dun, dun, sizeof(bc->bc_dun));
+
+	bio->bi_crypt_context = bc;
+}
+EXPORT_SYMBOL_GPL(bio_crypt_set_ctx_by_slot);
+
 void __bio_crypt_free_ctx(struct bio *bio)
 {
 	mempool_free(bio->bi_crypt_context, bio_crypt_ctx_pool);
@@ -156,8 +176,12 @@ void __bio_crypt_advance(struct bio *bio, unsigned int bytes)
 {
 	struct bio_crypt_ctx *bc = bio->bi_crypt_context;
 
-	bio_crypt_dun_increment(bc->bc_dun,
-				bytes >> bc->bc_key->data_unit_size_bits);
+	if (bc->bc_key)
+		bio_crypt_dun_increment(bc->bc_dun,
+					bytes >> bc->bc_key->data_unit_size_bits);
+	else if (bc->bc_slot.data_unit_size_bits)
+		bio_crypt_dun_increment(bc->bc_dun,
+					bytes >> bc->bc_slot.data_unit_size_bits);
 }
 
 /*
@@ -169,7 +193,14 @@ bool bio_crypt_dun_is_contiguous(const struct bio_crypt_ctx *bc,
 				 const u64 next_dun[BLK_CRYPTO_DUN_ARRAY_SIZE])
 {
 	int i;
-	unsigned int carry = bytes >> bc->bc_key->data_unit_size_bits;
+	unsigned int carry;
+
+	if (bc->bc_key)
+		carry = bytes >> bc->bc_key->data_unit_size_bits;
+	else if (bc->bc_slot.data_unit_size_bits) {
+		carry = bytes >> bc->bc_slot.data_unit_size_bits;
+	} else
+		return false;
 
 	for (i = 0; i < BLK_CRYPTO_DUN_ARRAY_SIZE; i++) {
 		if (bc->bc_dun[i] + carry != next_dun[i])
@@ -198,7 +229,12 @@ static bool bio_crypt_ctx_compatible(struct bio_crypt_ctx *bc1,
 	if (!bc1)
 		return !bc2;
 
-	return bc2 && bc1->bc_key == bc2->bc_key;
+	if (bc1->bc_key)
+		return bc2 && bc1->bc_key == bc2->bc_key;
+	else
+		return bc2 && !bc2->bc_key &&
+		       bc1->bc_slot.phy_slot == bc2->bc_slot.phy_slot &&
+		       bc1->bc_slot.data_unit_size_bits == bc2->bc_slot.data_unit_size_bits;
 }
 
 bool bio_crypt_rq_ctx_compatible(struct request *rq, struct bio *bio)
@@ -260,6 +296,19 @@ bool __blk_crypto_submit_bio(struct bio *bio)
 		return false;
 	}
 
+	if (!bc_key) {
+		/*
+		* Slot path: the ICE keyslot was pre-programmed by the
+		* hypervisor. The target device must natively support inline
+		* encryption; there is no fallback for slot-based crypto.
+		*/
+		if (!bdev_get_queue(bdev)->crypto_profile) {
+			bio_endio_status(bio, BLK_STS_NOTSUPP);
+			return false;
+		}
+		return true;
+	}
+
 	/*
 	 * If the device does not natively support the encryption context, try to use
 	 * the fallback if available.
diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
index 858320b6ebb7e..48ad79734c09e 100644
--- a/drivers/block/Kconfig
+++ b/drivers/block/Kconfig
@@ -176,6 +176,21 @@ config BLK_DEV_LOOP
 
 	  Most users will answer N here.
 
+config BLK_CRYPTO_PROXY
+	tristate "Inline encryption proxy for virtio-blk guests"
+	depends on BLK_INLINE_ENCRYPTION
+	help
+	  Provides /dev/blk-crypto-proxy, a misc character device that allows a
+	  userspace virtio-blk backend to submit inline-encrypted block I/O
+	  on behalf of guest virtual machines.
+
+	  Guests supply a virtual keyslot index and data unit number with
+	  each encrypted request.  The host kernel translates the virtual
+	  slot to a physical hardware keyslot and issues the bio to the
+	  storage controller with the correct inline encryption context.
+
+	  If unsure, say N.
+
 config BLK_DEV_LOOP_MIN_COUNT
 	int "Number of loop devices to pre-create at init time"
 	depends on BLK_DEV_LOOP
@@ -312,6 +327,19 @@ config VIRTIO_BLK
 	  This is the virtual block driver for virtio.  It can be used with
           QEMU based VMMs (like KVM or Xen).  Say Y or M.
 
+config VIRTBLK_CRYPTO_VIRTUALIZATION
+	tristate "Virtio block inline encryption virtualization support"
+	depends on VIRTIO_BLK && BLK_INLINE_ENCRYPTION
+	help
+	  Say 'Y or M' to enable routing of crypto requests to a different
+	  operating system in a virtualized environment. This option by
+	  itself does not provide a working backend: enable a
+	  platform-specific driver that implements struct
+	  virtblk_crypto_variant_ops as well (e.g. QCOM_CRYPTO_VIRT on
+	  Qualcomm platforms). Without one, VIRTIO_BLK_F_INLINE_ENCRYPTION is
+	  still negotiated and advertised to the block layer, but every
+	  inline-crypto operation fails with -EOPNOTSUPP at runtime.
+
 config BLK_DEV_RBD
 	tristate "Rados block device (RBD)"
 	depends on INET && BLOCK
diff --git a/drivers/block/Makefile b/drivers/block/Makefile
index 2d8096eb8cdf8..636137248d0d1 100644
--- a/drivers/block/Makefile
+++ b/drivers/block/Makefile
@@ -23,8 +23,11 @@ obj-$(CONFIG_BLK_DEV_LOOP)	+= loop.o
 obj-$(CONFIG_SUNVDC)		+= sunvdc.o
 
 obj-$(CONFIG_BLK_DEV_NBD)	+= nbd.o
+obj-$(CONFIG_BLK_CRYPTO_PROXY)	+= blk-crypto-proxy.o
 obj-$(CONFIG_VIRTIO_BLK)	+= virtio_blk.o
 
+obj-$(CONFIG_VIRTBLK_CRYPTO_VIRTUALIZATION) += virtio_blk_crypto_ext.o
+
 obj-$(CONFIG_XEN_BLKDEV_FRONTEND)	+= xen-blkfront.o
 obj-$(CONFIG_XEN_BLKDEV_BACKEND)	+= xen-blkback/
 obj-$(CONFIG_BLK_DEV_DRBD)     += drbd/
diff --git a/drivers/block/blk-crypto-proxy.c b/drivers/block/blk-crypto-proxy.c
new file mode 100644
index 0000000000000..60722884dbcd7
--- /dev/null
+++ b/drivers/block/blk-crypto-proxy.c
@@ -0,0 +1,667 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define pr_fmt(fmt) "blk-crypto-proxy: " fmt
+
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/blkdev.h>
+#include <linux/bio.h>
+#include <linux/mutex.h>
+#include <linux/overflow.h>
+#include <linux/file.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/rcupdate.h>
+#include <linux/uio.h>
+#include <linux/blk-crypto.h>
+#include <linux/blk-crypto-profile.h>
+#include <linux/blk-crypto-proxy.h>
+#include <linux/virtio_blk.h>
+
+static const struct bcp_hypervisor_ops __rcu *g_hypervisor_ops;
+static DEFINE_MUTEX(g_hypervisor_ops_lock);
+
+static const struct bcp_slot_virt_ops __rcu *g_slot_virt_ops;
+static DEFINE_MUTEX(g_slot_virt_ops_lock);
+
+int bcp_register_hypervisor_ops(const struct bcp_hypervisor_ops *ops)
+{
+	int ret = 0;
+
+	mutex_lock(&g_hypervisor_ops_lock);
+	if (rcu_access_pointer(g_hypervisor_ops))
+		ret = -EBUSY;
+	else
+		rcu_assign_pointer(g_hypervisor_ops, ops);
+	mutex_unlock(&g_hypervisor_ops_lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(bcp_register_hypervisor_ops);
+
+void bcp_unregister_hypervisor_ops(const struct bcp_hypervisor_ops *ops)
+{
+	mutex_lock(&g_hypervisor_ops_lock);
+	if (rcu_access_pointer(g_hypervisor_ops) == ops)
+		rcu_assign_pointer(g_hypervisor_ops, NULL);
+	mutex_unlock(&g_hypervisor_ops_lock);
+	synchronize_rcu();
+}
+EXPORT_SYMBOL_GPL(bcp_unregister_hypervisor_ops);
+
+int bcp_register_slot_virt_ops(const struct bcp_slot_virt_ops *ops)
+{
+	int ret = 0;
+
+	mutex_lock(&g_slot_virt_ops_lock);
+	if (rcu_access_pointer(g_slot_virt_ops))
+		ret = -EBUSY;
+	else
+		rcu_assign_pointer(g_slot_virt_ops, ops);
+	mutex_unlock(&g_slot_virt_ops_lock);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(bcp_register_slot_virt_ops);
+
+void bcp_unregister_slot_virt_ops(const struct bcp_slot_virt_ops *ops)
+{
+	mutex_lock(&g_slot_virt_ops_lock);
+	if (rcu_access_pointer(g_slot_virt_ops) == ops)
+		rcu_assign_pointer(g_slot_virt_ops, NULL);
+	mutex_unlock(&g_slot_virt_ops_lock);
+	synchronize_rcu();
+}
+EXPORT_SYMBOL_GPL(bcp_unregister_slot_virt_ops);
+
+/**
+ * struct bcp_ctx - per-fd state for /dev/blk-crypto-proxy
+ * @bdev_file: file handle for the bound block device; NULL until BCP_BIND_CONTEXT.
+ *             Published with smp_store_release() so hot-path ioctls can read it
+ *             lock-free via smp_load_acquire() in bcp_ctx_bound().
+ * @guest_id: guest identifier resolved from vm_fd at bind time.
+ * @bdev_writable: block_dev_fd was opened with write access.
+ * @bind_lock: serializes concurrent BCP_BIND_CONTEXT calls on this fd.
+ */
+struct bcp_ctx {
+	struct file *bdev_file;
+	u32 guest_id;
+	bool bdev_writable;
+	struct mutex bind_lock;
+};
+
+/*
+ * True once BCP_BIND_CONTEXT has published ctx->bdev_file.  The acquire pairs
+ * with smp_store_release() in bcp_ioctl_bind_context(), ensuring ctx->guest_id
+ * and ctx->bdev_writable are visible to any caller that observes true.
+ */
+static bool bcp_ctx_bound(struct bcp_ctx *ctx)
+{
+	return smp_load_acquire(&ctx->bdev_file) != NULL;
+}
+
+static int bcp_open(struct inode *inode, struct file *file)
+{
+	struct bcp_ctx *ctx;
+
+	ctx = kzalloc_obj(*ctx, GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+	mutex_init(&ctx->bind_lock);
+	file->private_data = ctx;
+	return 0;
+}
+
+static int bcp_release(struct inode *inode, struct file *file)
+{
+	struct bcp_ctx *ctx = file->private_data;
+
+	if (ctx) {
+		if (ctx->bdev_file)
+			bdev_fput(ctx->bdev_file);
+		mutex_destroy(&ctx->bind_lock);
+		kfree(ctx);
+		file->private_data = NULL;
+	}
+	return 0;
+}
+
+/*
+ * Resolve a userspace block device fd to a struct file holding a reference
+ * to the block device, opened with the same access mode as @fd so that a
+ * read-only fd cannot gain write access via BCP_SUBMIT_IO_BY_VSLOT.
+ */
+static struct file *bcp_bdev_from_fd(int fd, bool *writable)
+{
+	struct file *f;
+	struct inode *inode;
+	dev_t dev;
+	blk_mode_t mode = 0;
+
+	f = fget(fd);
+	if (!f)
+		return ERR_PTR(-EBADF);
+	inode = file_inode(f);
+	if (!S_ISBLK(inode->i_mode)) {
+		fput(f);
+		return ERR_PTR(-ENOTBLK);
+	}
+	if (f->f_mode & FMODE_READ)
+		mode |= BLK_OPEN_READ;
+	if (f->f_mode & FMODE_WRITE)
+		mode |= BLK_OPEN_WRITE;
+	if (!mode) {
+		fput(f);
+		return ERR_PTR(-EACCES);
+	}
+	*writable = !!(mode & BLK_OPEN_WRITE);
+	dev = inode->i_rdev;
+	fput(f);
+	return bdev_file_open_by_dev(dev, mode, NULL, NULL);
+}
+
+static long bcp_ioctl_bind_context(struct file *file,
+				   struct bcp_bind_context_arg __user *argp)
+{
+	struct bcp_ctx *ctx = file->private_data;
+	struct bcp_bind_context_arg arg;
+	const struct bcp_hypervisor_ops *hv_ops;
+	struct file *bdev_file;
+	u32 guest_id;
+	bool writable = false;
+	int ret;
+
+	if (!ctx)
+		return -EINVAL;
+	if (copy_from_user(&arg, argp, sizeof(arg)))
+		return -EFAULT;
+	if (arg.reserved)
+		return -EINVAL;
+
+	/*
+	 * get_guest_id() may sleep; call it before taking bind_lock.
+	 */
+	rcu_read_lock();
+	hv_ops = rcu_dereference(g_hypervisor_ops);
+	if (!hv_ops) {
+		rcu_read_unlock();
+		return -EOPNOTSUPP;
+	}
+	ret = hv_ops->get_guest_id(arg.vm_fd, &guest_id);
+	rcu_read_unlock();
+	if (ret)
+		return ret;
+
+	/*
+	 * Serialize against concurrent BCP_BIND_CONTEXT calls: two callers
+	 * could both pass the ctx->bdev_file == NULL check before either stores.
+	 */
+	guard(mutex)(&ctx->bind_lock);
+
+	if (ctx->bdev_file)
+		return -EBUSY;
+
+	bdev_file = bcp_bdev_from_fd(arg.block_dev_fd, &writable);
+	if (IS_ERR(bdev_file))
+		return PTR_ERR(bdev_file);
+
+	ctx->guest_id = guest_id;
+	ctx->bdev_writable = writable;
+	/*
+	 * Publish ctx->bdev_file last with a release barrier; bcp_ctx_bound()
+	 * reads it with smp_load_acquire() without taking @bind_lock.
+	 */
+	smp_store_release(&ctx->bdev_file, bdev_file);
+	return 0;
+}
+
+/*
+ * Maps VIRTIO_BLK_CRYPTO_MODE_* to enum blk_crypto_mode_num.  The two index
+ * spaces do not coincide, so modes_supported[] must not be copied positionally.
+ * Keep in sync with virtio_blk_crypto_mode_map[] in drivers/block/virtio_blk.c.
+ */
+static const enum blk_crypto_mode_num
+	bcp_virtio_crypto_mode_map[VIRTIO_BLK_CRYPTO_MODE_MAX + 1] = {
+	[VIRTIO_BLK_CRYPTO_MODE_INVALID]	= BLK_ENCRYPTION_MODE_INVALID,
+	[VIRTIO_BLK_CRYPTO_MODE_AES_256_XTS]	= BLK_ENCRYPTION_MODE_AES_256_XTS,
+};
+
+static long bcp_ioctl_get_crypto_caps(struct file *file,
+				      struct bcp_get_crypto_caps_arg __user *argp)
+{
+	struct bcp_ctx *ctx = file->private_data;
+	struct bcp_get_crypto_caps_arg arg;
+	struct block_device *bdev;
+	u32 modes[VIRTIO_BLK_CRYPTO_MODE_MAX + 1] = {0};
+	struct blk_crypto_profile *profile;
+	unsigned int i, n;
+	u32 cap, written;
+
+	if (!ctx || !bcp_ctx_bound(ctx))
+		return -ENXIO;
+
+	if (copy_from_user(&arg, argp, sizeof(arg)))
+		return -EFAULT;
+	if (arg.num_modes && !arg.modes_ptr)
+		return -EINVAL;
+
+	bdev = file_bdev(ctx->bdev_file);
+	profile = bdev_get_queue(bdev)->crypto_profile;
+	if (!profile)
+		return -EOPNOTSUPP;
+
+	arg.key_types_supported = profile->key_types_supported;
+	/*
+	 * Clamp to 8: the @dun wire field is a single __aligned_u64 so nothing
+	 * upstream can deliver a wider DUN regardless of what the profile claims.
+	 */
+	arg.max_dun_bytes = min_t(u32, profile->max_dun_bytes_supported, 8);
+
+	/*
+	 * arg.modes_supported[] is indexed by VIRTIO_BLK_CRYPTO_MODE_* (index 0
+	 * is always 0 per the virtio spec).  Translate via the map above; do not
+	 * copy profile->modes_supported[] positionally.
+	 */
+	n = VIRTIO_BLK_CRYPTO_MODE_MAX + 1;
+	cap = arg.num_modes;
+	written = min_t(u32, cap, n);
+
+	for (i = 1; i < n; i++) {
+		enum blk_crypto_mode_num kmode = bcp_virtio_crypto_mode_map[i];
+
+		if (!kmode)
+			continue;
+		modes[i] = profile->modes_supported[kmode];
+	}
+
+	if (written &&
+	    copy_to_user(u64_to_user_ptr(arg.modes_ptr), modes,
+			 written * sizeof(modes[0])))
+		return -EFAULT;
+	arg.num_modes = written;
+
+	arg.max_slots = 0;
+	rcu_read_lock();
+	{
+		const struct bcp_slot_virt_ops *sv_ops =
+				rcu_dereference(g_slot_virt_ops);
+		if (sv_ops) {
+			int nslots = sv_ops->get_guest_slots(profile, ctx->guest_id);
+
+			if (nslots > 0)
+				arg.max_slots = nslots;
+		}
+	}
+	rcu_read_unlock();
+
+	if (copy_to_user(argp, &arg, sizeof(arg)))
+		return -EFAULT;
+	return 0;
+}
+
+/*
+ * Compute the number of pages needed for up to @want_bytes of iovec data
+ * starting at cursor (@start_idx, @start_off).  The page count is capped at
+ * @cap to bound the arithmetic; *bytes_out receives the actual byte count.
+ */
+static unsigned int bcp_iov_pages_for_bytes(const struct iovec *iov, u32 iov_cnt,
+					    u32 start_idx, u64 start_off,
+					      u64 want_bytes, unsigned int cap,
+					      u64 *bytes_out)
+{
+	u64 pages = 0, taken = 0;
+	u32 i;
+
+	for (i = start_idx; i < iov_cnt && taken < want_bytes; i++) {
+		u64 base, len;
+
+		if (i == start_idx) {
+			base = (u64)(uintptr_t)iov[i].iov_base + start_off;
+			len  = iov[i].iov_len - start_off;
+		} else {
+			base = (u64)(uintptr_t)iov[i].iov_base;
+			len  = iov[i].iov_len;
+		}
+		if (len == 0)
+			continue;
+		if (len > want_bytes - taken)
+			len = want_bytes - taken;
+		pages += DIV_ROUND_UP(len + offset_in_page(base), PAGE_SIZE);
+		taken += len;
+		if (pages >= cap) {
+			*bytes_out = taken;
+			return cap;
+		}
+	}
+	*bytes_out = taken;
+	return (unsigned int)pages;
+}
+
+/*
+ * Advance cursor (*idx, *off) forward by @bytes within @iov[0..iov_cnt).
+ */
+static void bcp_iov_advance_cursor(const struct iovec *iov, u32 iov_cnt,
+				   u32 *idx, u64 *off, u64 bytes)
+{
+	while (bytes > 0 && *idx < iov_cnt) {
+		u64 seg_remaining = iov[*idx].iov_len - *off;
+		u64 take = min_t(u64, seg_remaining, bytes);
+
+		*off += take;
+		bytes -= take;
+		if (*off == iov[*idx].iov_len) {
+			(*idx)++;
+			*off = 0;
+		}
+	}
+}
+
+static long bcp_ioctl_submit_io_by_vslot(struct file *file,
+					 struct bcp_submit_io_by_vslot_arg __user *argp)
+{
+	struct bcp_ctx *ctx = file->private_data;
+	struct bcp_submit_io_by_vslot_arg arg;
+	struct block_device *bdev;
+	struct blk_crypto_profile *profile;
+	struct iovec *iov = NULL;
+	struct iov_iter iter;
+	struct blk_crypto_slot slot;
+	u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE];
+	u64 bytes_done = 0;
+	u64 align, stride;
+	u64 total_bytes;
+	u32 seg_idx = 0;
+	u64 seg_off = 0;
+	unsigned int phy_slot;
+	int ret = -EFAULT;
+
+	if (!ctx || !bcp_ctx_bound(ctx))
+		return -ENXIO;
+
+	if (copy_from_user(&arg, argp, sizeof(arg)))
+		return -EFAULT;
+	if (arg.reserved2)
+		return -EINVAL;
+
+	bdev = file_bdev(ctx->bdev_file);
+
+	if (arg.direction != BCP_DIR_READ && arg.direction != BCP_DIR_WRITE)
+		return -EINVAL;
+	/*
+	 * blk_mode_t does not stop submit_bio() from writing; enforce the
+	 * caller's original fd permission explicitly.
+	 */
+	if (arg.direction == BCP_DIR_WRITE && !ctx->bdev_writable)
+		return -EACCES;
+	/*
+	 * bdev_read_only() can change after bind time; submit_bio_noacct()'s
+	 * bio_check_ro() only warns rather than errors in this kernel.
+	 */
+	if (arg.direction == BCP_DIR_WRITE && bdev_read_only(bdev))
+		return -EROFS;
+	if (arg.flags != BCP_SUBMIT_IO_F_IOV)
+		return -EINVAL;
+	if (arg.iov_cnt == 0 || arg.iov_cnt > BCP_MAX_IOV)
+		return -EINVAL;
+	/* A shift amount >= 64 would be undefined behavior. */
+	if (arg.data_unit_size_bits >= 64)
+		return -EINVAL;
+
+	profile = bdev_get_queue(bdev)->crypto_profile;
+	if (!profile)
+		return -EOPNOTSUPP;
+
+	/* Resolve virt_slot → phy_slot. */
+	rcu_read_lock();
+	{
+		const struct bcp_slot_virt_ops *sv_ops =
+				rcu_dereference(g_slot_virt_ops);
+		if (!sv_ops) {
+			rcu_read_unlock();
+			return -EOPNOTSUPP;
+		}
+		ret = sv_ops->vslot_to_pslot(profile, ctx->guest_id,
+					     arg.virt_slot, &phy_slot);
+	}
+	rcu_read_unlock();
+	if (ret)
+		return ret;
+
+	memset(dun, 0, sizeof(dun));
+	dun[0] = arg.dun;
+
+	slot.phy_slot            = phy_slot;
+	slot.data_unit_size_bits = arg.data_unit_size_bits;
+
+	align = 1ULL << arg.data_unit_size_bits;
+	/*
+	 * Split bios at stride (smallest multiple of the data unit size >=
+	 * PAGE_SIZE) boundaries so each bio ends on a whole data unit.
+	 * bio_crypt_check_alignment() is skipped for slot-based bios (bc_key
+	 * == NULL), so a mid-unit split would silently mis-encrypt/mis-decrypt.
+	 */
+	stride = DIV_ROUND_UP(PAGE_SIZE, align) * align;
+
+	/*
+	 * Import the caller's iovec once.  import_iovec() validates every
+	 * segment with access_ok(), returns the total byte count, and takes a
+	 * private kernel copy that eliminates TOCTOU from a guest mutating its
+	 * own iovec array mid-ioctl.
+	 */
+	ret = import_iovec(arg.direction == BCP_DIR_READ ? ITER_DEST : ITER_SOURCE,
+			   (const struct iovec __user *)u64_to_user_ptr(arg.iov_ptr),
+			   arg.iov_cnt, 0, &iov, &iter);
+	if (ret < 0)
+		return ret;
+	total_bytes = ret;
+
+	/*
+	 * Reject a misaligned total length up front: bio_crypt_check_alignment()
+	 * is skipped for slot-based bios so nothing downstream will catch it.
+	 */
+	if (total_bytes == 0 || (total_bytes & (align - 1)) ||
+	    (total_bytes & (SECTOR_SIZE - 1))) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/*
+	 * Fail fast if the request exceeds the device.  bio_check_eod() would
+	 * also catch this, but only on the last bio after earlier bios have
+	 * already done real I/O.
+	 */
+	{
+		sector_t nr_sectors = total_bytes >> SECTOR_SHIFT;
+		sector_t maxsector = bdev_nr_sectors(bdev);
+
+		if (nr_sectors > maxsector || arg.sector > maxsector - nr_sectors) {
+			ret = -EIO;
+			goto out;
+		}
+	}
+
+	/*
+	 * Reject an out-of-range DUN: slot-based bios skip
+	 * bio_crypt_check_alignment(), so an overflow would silently truncate
+	 * in the hardware DUN field rather than error out.
+	 */
+	{
+		u64 total_units = total_bytes >> arg.data_unit_size_bits;
+		u64 max_dun_used, dun_limit;
+
+		if (check_add_overflow(arg.dun, total_units - 1, &max_dun_used)) {
+			ret = -EINVAL;
+			goto out;
+		}
+		dun_limit = profile->max_dun_bytes_supported >= 8 ? U64_MAX :
+			(1ULL << (8 * profile->max_dun_bytes_supported)) - 1;
+		if (max_dun_used > dun_limit) {
+			ret = -EINVAL;
+			goto out;
+		}
+	}
+
+	/*
+	 * Submit the request as a sequence of bios (submit_bio_wait() per
+	 * bio), each holding at most BIO_MAX_VECS pages.  Sequential
+	 * submission avoids DUN/IV correctness concerns across concurrent
+	 * in-flight bios.
+	 */
+	while (seg_idx < arg.iov_cnt) {
+		unsigned int pages_used = 0;
+		u64 bio_bytes = 0;
+		u32 la_idx = seg_idx;
+		u64 la_off = seg_off;
+		u64 remaining_before;
+		struct bio *bio;
+
+		/*
+		 * Lookahead: count how many whole stride units fit within a
+		 * fresh bio's BIO_MAX_VECS page budget.
+		 */
+		for (;;) {
+			u64 unit_bytes;
+			unsigned int unit_pages;
+
+			unit_pages = bcp_iov_pages_for_bytes(iov, arg.iov_cnt,
+							     la_idx, la_off, stride,
+							BIO_MAX_VECS + 1,
+							&unit_bytes);
+			if (unit_bytes == 0)
+				break; /* only empty segments remain */
+
+			if (pages_used + unit_pages > BIO_MAX_VECS) {
+				if (pages_used == 0) {
+					/* data_unit_size_bits too large to fit one unit. */
+					ret = -EINVAL;
+					goto out;
+				}
+				break; /* finalize this bio; unit deferred to next */
+			}
+
+			pages_used += unit_pages;
+			bio_bytes  += unit_bytes;
+			bcp_iov_advance_cursor(iov, arg.iov_cnt, &la_idx, &la_off,
+					       unit_bytes);
+		}
+
+		if (bio_bytes == 0)
+			break;
+
+		bio = bio_alloc(bdev, pages_used,
+				arg.direction == BCP_DIR_WRITE ?
+					REQ_OP_WRITE : REQ_OP_READ,
+				GFP_KERNEL);
+		if (!bio) {
+			ret = -ENOMEM;
+			goto out;
+		}
+		bio->bi_iter.bi_sector = arg.sector + (bytes_done >> SECTOR_SHIFT);
+
+		/*
+		 * Use bio_iov_iter_get_pages() to pin pages into the bio,
+		 * the same as the O_DIRECT path.  Truncate the iter to this
+		 * bio's byte budget, then reexpand for the next iteration.
+		 */
+		remaining_before = iov_iter_count(&iter);
+		iov_iter_truncate(&iter, bio_bytes);
+		ret = bio_iov_iter_get_pages(bio, &iter, 0, 0);
+		if (ret < 0) {
+			bio_put(bio);
+			goto out;
+		}
+		if (iov_iter_count(&iter) != 0) {
+			/*
+			 * The lookahead verified bio_bytes fits in BIO_MAX_VECS;
+			 * if bio_iov_iter_get_pages() stopped early, its page
+			 * accounting disagreed with bcp_iov_pages_for_bytes().
+			 */
+			bio_put(bio);
+			ret = -EIO;
+			goto out;
+		}
+		iov_iter_reexpand(&iter, remaining_before - bio_bytes);
+
+		/*
+		 * Match __blkdev_direct_IO(): mark pages dirty on reads into
+		 * user-backed memory.
+		 */
+		if (arg.direction == BCP_DIR_READ && user_backed_iter(&iter))
+			bio_set_pages_dirty(bio);
+
+		bcp_iov_advance_cursor(iov, arg.iov_cnt, &seg_idx, &seg_off,
+				       bio_bytes);
+
+		bio_crypt_set_ctx_by_slot(bio, &slot, dun, GFP_KERNEL);
+
+		ret = submit_bio_wait(bio);
+		bio_put(bio);
+		if (ret)
+			goto out;
+
+		/*
+		 * Advance dun by this bio's contribution only, not by
+		 * recomputing from arg.dun + bytes_done, to avoid silent
+		 * truncation when bytes_done grows past UINT_MAX data units.
+		 */
+		bio_crypt_dun_increment(dun, (unsigned int)(bio_bytes >> arg.data_unit_size_bits));
+		bytes_done += bio_bytes;
+	}
+	ret = 0;
+
+out:
+	kfree(iov);
+	return ret;
+}
+
+static long bcp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+
+	switch (cmd) {
+	case BCP_BIND_CONTEXT:
+		return bcp_ioctl_bind_context(file, argp);
+	case BCP_GET_CRYPTO_CAPS:
+		return bcp_ioctl_get_crypto_caps(file, argp);
+	case BCP_SUBMIT_IO_BY_VSLOT:
+		return bcp_ioctl_submit_io_by_vslot(file, argp);
+	default:
+		return -ENOTTY;
+	}
+}
+
+static const struct file_operations bcp_fops = {
+	.owner		= THIS_MODULE,
+	.open		= bcp_open,
+	.release	= bcp_release,
+	.unlocked_ioctl	= bcp_ioctl,
+	.compat_ioctl	= compat_ptr_ioctl,
+};
+
+static struct miscdevice bcp_misc = {
+	.minor	= MISC_DYNAMIC_MINOR,
+	.name	= "blk-crypto-proxy",
+	.fops	= &bcp_fops,
+};
+
+static int __init blk_crypto_proxy_init(void)
+{
+	int ret;
+
+	ret = misc_register(&bcp_misc);
+	if (ret)
+		return ret;
+	return 0;
+}
+
+static void __exit blk_crypto_proxy_exit(void)
+{
+	misc_deregister(&bcp_misc);
+}
+
+module_init(blk_crypto_proxy_init);
+module_exit(blk_crypto_proxy_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Host-side inline crypto proxy for virtio-blk guests");
diff --git a/drivers/block/virtio_blk.c b/drivers/block/virtio_blk.c
index 32bf3ba07a9dc..61a3967bb4dfe 100644
--- a/drivers/block/virtio_blk.c
+++ b/drivers/block/virtio_blk.c
@@ -16,6 +16,8 @@
 #include <linux/numa.h>
 #include <linux/vmalloc.h>
 #include <uapi/linux/virtio_ring.h>
+#include <linux/blk-crypto-profile.h>
+#include <linux/virtio_blk_crypto_ext.h>
 
 #define PART_BITS 4
 #define VQ_NAME_LEN 16
@@ -87,7 +89,14 @@ struct virtio_blk {
 
 struct virtblk_req {
 	/* Out header */
-	struct virtio_blk_outhdr out_hdr;
+	union {
+		struct virtio_blk_outhdr base;
+		struct {
+			struct virtio_blk_outhdr base;
+			/* Crypto message (if VIRTIO_BLK_F_INLINE_ENCRYPTION) */
+			struct virtio_blk_crypto_msg msg;
+		} crypto_append;
+	} out_hdr;
 
 	/* In header */
 	union {
@@ -140,12 +149,17 @@ static int virtblk_add_req(struct virtqueue *vq, struct virtblk_req *vbr)
 {
 	struct scatterlist out_hdr, in_hdr, *sgs[3];
 	unsigned int num_out = 0, num_in = 0;
+	size_t out_hdr_len = sizeof(vbr->out_hdr.base);
+
+	if (vbr->out_hdr.base.type == cpu_to_virtio32(vq->vdev, VIRTIO_BLK_T_CRYPTO_IN) ||
+	    vbr->out_hdr.base.type == cpu_to_virtio32(vq->vdev, VIRTIO_BLK_T_CRYPTO_OUT))
+		out_hdr_len = sizeof(vbr->out_hdr.crypto_append);
 
-	sg_init_one(&out_hdr, &vbr->out_hdr, sizeof(vbr->out_hdr));
+	sg_init_one(&out_hdr, &vbr->out_hdr, out_hdr_len);
 	sgs[num_out++] = &out_hdr;
 
 	if (vbr->sg_table.nents) {
-		if (vbr->out_hdr.type & cpu_to_virtio32(vq->vdev, VIRTIO_BLK_T_OUT))
+		if (vbr->out_hdr.base.type & cpu_to_virtio32(vq->vdev, VIRTIO_BLK_T_OUT))
 			sgs[num_out++] = vbr->sg_table.sgl;
 		else
 			sgs[num_out + num_in++] = vbr->sg_table.sgl;
@@ -235,6 +249,15 @@ static void virtblk_cleanup_cmd(struct request *req)
 		kfree(bvec_virt(&req->special_vec));
 }
 
+static bool is_crypto_request(struct virtio_device *vdev, struct request *req)
+{
+	if (!IS_ENABLED(CONFIG_VIRTBLK_CRYPTO_VIRTUALIZATION) ||
+	    !virtio_has_feature(vdev, VIRTIO_BLK_F_INLINE_ENCRYPTION))
+		return false;
+
+	return req->crypt_ctx && req->crypt_keyslot;
+}
+
 static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,
 				      struct request *req,
 				      struct virtblk_req *vbr)
@@ -248,15 +271,21 @@ static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,
 		return BLK_STS_NOTSUPP;
 
 	/* Set fields for all request types */
-	vbr->out_hdr.ioprio = cpu_to_virtio32(vdev, req_get_ioprio(req));
+	vbr->out_hdr.base.ioprio = cpu_to_virtio32(vdev, req_get_ioprio(req));
 
 	switch (req_op(req)) {
 	case REQ_OP_READ:
-		type = VIRTIO_BLK_T_IN;
+		if (is_crypto_request(vdev, req))
+			type = VIRTIO_BLK_T_CRYPTO_IN;
+		else
+			type = VIRTIO_BLK_T_IN;
 		sector = blk_rq_pos(req);
 		break;
 	case REQ_OP_WRITE:
-		type = VIRTIO_BLK_T_OUT;
+		if (is_crypto_request(vdev, req))
+			type = VIRTIO_BLK_T_CRYPTO_OUT;
+		else
+			type = VIRTIO_BLK_T_OUT;
 		sector = blk_rq_pos(req);
 		break;
 	case REQ_OP_FLUSH:
@@ -298,8 +327,9 @@ static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,
 		break;
 	case REQ_OP_DRV_IN:
 		/*
-		 * Out header has already been prepared by the caller (virtblk_get_id()
-		 * or virtblk_submit_zone_report()), nothing to do here.
+		 * Out header has already been prepared by the caller (virtblk_get_id(),
+		 * virtblk_submit_zone_report() or virtblk_get_crypto_modes()), nothing
+		 * to do here.
 		 */
 		return 0;
 	default:
@@ -309,8 +339,8 @@ static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,
 
 	/* Set fields for non-REQ_OP_DRV_IN request types */
 	vbr->in_hdr_len = in_hdr_len;
-	vbr->out_hdr.type = cpu_to_virtio32(vdev, type);
-	vbr->out_hdr.sector = cpu_to_virtio64(vdev, sector);
+	vbr->out_hdr.base.type = cpu_to_virtio32(vdev, type);
+	vbr->out_hdr.base.sector = cpu_to_virtio64(vdev, sector);
 
 	if (type == VIRTIO_BLK_T_DISCARD || type == VIRTIO_BLK_T_WRITE_ZEROES ||
 	    type == VIRTIO_BLK_T_SECURE_ERASE) {
@@ -318,6 +348,17 @@ static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,
 			return BLK_STS_RESOURCE;
 	}
 
+	if (type == VIRTIO_BLK_T_CRYPTO_IN || type == VIRTIO_BLK_T_CRYPTO_OUT) {
+		unsigned int slot = blk_crypto_keyslot_index(req->crypt_keyslot);
+		unsigned int data_unit_size_bits = req->crypt_ctx->bc_key->data_unit_size_bits;
+		u64 dun = req->crypt_ctx->bc_dun[0];
+
+		vbr->out_hdr.crypto_append.msg.slot = cpu_to_virtio32(vdev, slot);
+		vbr->out_hdr.crypto_append.msg.data_unit_size_bits =
+				cpu_to_virtio32(vdev, data_unit_size_bits);
+		vbr->out_hdr.crypto_append.msg.dun = cpu_to_virtio64(vdev, dun);
+	}
+
 	return 0;
 }
 
@@ -568,8 +609,8 @@ static int virtblk_submit_zone_report(struct virtio_blk *vblk,
 
 	vbr = blk_mq_rq_to_pdu(req);
 	vbr->in_hdr_len = sizeof(vbr->in_hdr.status);
-	vbr->out_hdr.type = cpu_to_virtio32(vblk->vdev, VIRTIO_BLK_T_ZONE_REPORT);
-	vbr->out_hdr.sector = cpu_to_virtio64(vblk->vdev, sector);
+	vbr->out_hdr.base.type = cpu_to_virtio32(vblk->vdev, VIRTIO_BLK_T_ZONE_REPORT);
+	vbr->out_hdr.base.sector = cpu_to_virtio64(vblk->vdev, sector);
 
 	err = blk_rq_map_kern(req, report_buf, report_len, GFP_KERNEL);
 	if (err)
@@ -817,8 +858,8 @@ static int virtblk_get_id(struct gendisk *disk, char *id_str)
 
 	vbr = blk_mq_rq_to_pdu(req);
 	vbr->in_hdr_len = sizeof(vbr->in_hdr.status);
-	vbr->out_hdr.type = cpu_to_virtio32(vblk->vdev, VIRTIO_BLK_T_GET_ID);
-	vbr->out_hdr.sector = 0;
+	vbr->out_hdr.base.type = cpu_to_virtio32(vblk->vdev, VIRTIO_BLK_T_GET_ID);
+	vbr->out_hdr.base.sector = 0;
 
 	err = blk_rq_map_kern(req, id_str, VIRTIO_BLK_ID_BYTES, GFP_KERNEL);
 	if (err)
@@ -863,6 +904,70 @@ static int virtblk_getgeo(struct gendisk *disk, struct hd_geometry *geo)
 	return ret;
 }
 
+/* Maps VIRTIO_BLK_CRYPTO_MODE_* values to the kernel's internal enum. */
+static const enum blk_crypto_mode_num
+	virtio_blk_crypto_mode_map[VIRTIO_BLK_CRYPTO_MODE_MAX + 1] = {
+	[VIRTIO_BLK_CRYPTO_MODE_INVALID]	= BLK_ENCRYPTION_MODE_INVALID,
+	[VIRTIO_BLK_CRYPTO_MODE_AES_256_XTS]	= BLK_ENCRYPTION_MODE_AES_256_XTS,
+};
+
+static int virtblk_get_crypto_modes(struct virtio_blk *vblk,
+				    unsigned int *crypto_modes_supported)
+{
+	struct request_queue *q = vblk->disk->queue;
+	unsigned int nr_modes = VIRTIO_BLK_CRYPTO_MODE_MAX + 1;
+	size_t buf_size = sizeof(struct virtio_blk_crypto_modes);
+	struct virtio_blk_crypto_modes *virtblk_cmodes;
+	struct request *req;
+	struct virtblk_req *vbr;
+	unsigned int i;
+	int err;
+
+	virtblk_cmodes = kzalloc(buf_size, GFP_KERNEL);
+	if (!virtblk_cmodes)
+		return -ENOMEM;
+
+	req = blk_mq_alloc_request(q, REQ_OP_DRV_IN, 0);
+	if (IS_ERR(req)) {
+		err = PTR_ERR(req);
+		goto out_free;
+	}
+
+	vbr = blk_mq_rq_to_pdu(req);
+	vbr->in_hdr_len = sizeof(vbr->in_hdr.status);
+	vbr->out_hdr.base.type = cpu_to_virtio32(vblk->vdev,
+						 VIRTIO_BLK_T_GET_CRYPTO_MODES);
+	vbr->out_hdr.base.sector = 0;
+
+	err = blk_rq_map_kern(req, virtblk_cmodes, buf_size, GFP_KERNEL);
+	if (err)
+		goto out_req;
+
+	blk_execute_rq(req, false);
+	err = blk_status_to_errno(virtblk_result(vbr->in_hdr.status));
+	if (err)
+		goto out_req;
+
+	for (i = 1; i < nr_modes; i++) {
+		u32 mode_mask = virtio32_to_cpu(vblk->vdev, virtblk_cmodes->modes[i]);
+		enum blk_crypto_mode_num mode = virtio_blk_crypto_mode_map[i];
+
+		if (!mode_mask)
+			continue;
+		if (!mode) {
+			dev_warn(&vblk->vdev->dev,
+				 "ignoring unknown crypto mode %u\n", i);
+			continue;
+		}
+		crypto_modes_supported[mode] = mode_mask;
+	}
+out_req:
+	blk_mq_free_request(req);
+out_free:
+	kfree(virtblk_cmodes);
+	return err;
+}
+
 static void virtblk_free_disk(struct gendisk *disk)
 {
 	struct virtio_blk *vblk = disk->private_data;
@@ -1435,6 +1540,51 @@ static int virtblk_read_limits(struct virtio_blk *vblk,
 	return 0;
 }
 
+static int virtblk_init_crypto(struct virtio_blk *vblk)
+{
+	struct virtio_device *vdev = vblk->vdev;
+	unsigned int crypto_modes_supported[BLK_ENCRYPTION_MODE_MAX] = { 0 };
+	/* virtio_cread() requires the variable size to match the config field exactly */
+	u16 max_slots;
+	u8 max_dun_bytes, key_types;
+	int err;
+
+	virtio_cread(vdev, struct virtio_blk_config,
+		     enc_characteristics.max_slots, &max_slots);
+	virtio_cread(vdev, struct virtio_blk_config,
+		     enc_characteristics.max_dun_bytes, &max_dun_bytes);
+	virtio_cread(vdev, struct virtio_blk_config,
+		     enc_characteristics.key_types, &key_types);
+
+	dev_dbg(&vdev->dev,
+		 "max_slots = %u, max_dun_bytes = %u, key_types = 0x%x\n",
+		 max_slots, max_dun_bytes, key_types);
+
+	if (!max_slots)
+		return -EINVAL;
+	if (!(key_types & (VIRTIO_BLK_CRYPTO_KEY_TYPE_RAW |
+			   VIRTIO_BLK_CRYPTO_KEY_TYPE_HW_WRAPPED)))
+		return -EINVAL;
+	/*
+	 * struct virtio_blk_crypto_msg.dun is a fixed __virtio64, i.e. this
+	 * driver can only ever transmit 8 bytes of DUN per request. Refuse
+	 * to advertise more than that as supported, or blk-crypto could
+	 * negotiate a larger dun_bytes with the filesystem and have the
+	 * high-order bytes of req->crypt_ctx->bc_dun silently dropped in
+	 * virtblk_setup_cmd(), reusing the same IV across data units that
+	 * only differ in those high-order bytes.
+	 */
+	if (max_dun_bytes > sizeof(u64))
+		return -EINVAL;
+
+	err = virtblk_get_crypto_modes(vblk, crypto_modes_supported);
+	if (err)
+		return err;
+
+	return virtblk_init_inline_crypto(max_slots, max_dun_bytes, key_types,
+					   crypto_modes_supported, &vdev->dev);
+}
+
 static int virtblk_probe(struct virtio_device *vdev)
 {
 	struct virtio_blk *vblk;
@@ -1540,6 +1690,26 @@ static int virtblk_probe(struct virtio_device *vdev)
 			goto out_cleanup_disk;
 	}
 
+	if (IS_ENABLED(CONFIG_VIRTBLK_CRYPTO_VIRTUALIZATION) &&
+	    virtio_has_feature(vblk->vdev, VIRTIO_BLK_F_INLINE_ENCRYPTION)) {
+		if (virtio_has_feature(vblk->vdev, VIRTIO_BLK_F_ZONED)) {
+			dev_warn(&vdev->dev,
+				 "zoned device does not support inline encryption, disabling it\n");
+		} else {
+			/* Initialize supported crypto capabilities */
+			err = virtblk_init_crypto(vblk);
+			if (!err) {
+				if (!virtblk_crypto_register(vblk->disk->queue))
+					dev_warn(&vdev->dev,
+						"failed to register inline crypto profile with the block layer, continuing without inline crypto support\n");
+			} else {
+				dev_warn(&vdev->dev,
+					"inline crypto init failed: %d, continuing without inline crypto support\n",
+					err);
+			}
+		}
+	}
+
 	err = device_add_disk(&vdev->dev, vblk->disk, virtblk_attr_groups);
 	if (err)
 		goto out_cleanup_disk;
@@ -1672,6 +1842,7 @@ static unsigned int features[] = {
 	VIRTIO_BLK_F_FLUSH, VIRTIO_BLK_F_TOPOLOGY, VIRTIO_BLK_F_CONFIG_WCE,
 	VIRTIO_BLK_F_MQ, VIRTIO_BLK_F_DISCARD, VIRTIO_BLK_F_WRITE_ZEROES,
 	VIRTIO_BLK_F_SECURE_ERASE, VIRTIO_BLK_F_ZONED,
+	VIRTIO_BLK_F_INLINE_ENCRYPTION,
 };
 
 static struct virtio_driver virtio_blk = {
diff --git a/drivers/block/virtio_blk_crypto_ext.c b/drivers/block/virtio_blk_crypto_ext.c
new file mode 100644
index 0000000000000..00b6d410d3036
--- /dev/null
+++ b/drivers/block/virtio_blk_crypto_ext.c
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/container_of.h>
+#include <linux/blk-crypto.h>
+#include <linux/blk-crypto-profile.h>
+#include <linux/virtio_blk.h>
+#include <linux/virtio_blk_crypto_ext.h>
+
+struct virtblk_crypto_profile {
+	struct blk_crypto_profile profile;
+	struct virtblk_crypto_variant_ops *ops;
+};
+
+static struct virtblk_crypto_profile g_vdcp;
+static bool g_crypto_profile_initialized;
+static struct device *virtblk_profile_owner;
+static unsigned int g_max_slots;
+static unsigned int g_max_dun_bytes;
+static unsigned int g_key_types;
+static DEFINE_MUTEX(virtblk_crypto_init_lock);
+static DEFINE_MUTEX(virtblk_crypto_ops_lock);
+
+bool virtblk_crypto_register(struct request_queue *q)
+{
+	return blk_crypto_register(&g_vdcp.profile, q);
+}
+EXPORT_SYMBOL_GPL(virtblk_crypto_register);
+
+/*
+ * Returns the variant ops registered for @profile's virtblk_crypto_profile
+ * with a module reference held on ops->owner, or NULL if none are
+ * registered. Pairs with virtblk_crypto_ops_put().
+ *
+ * Holding a module reference for the duration of each dispatch call (rather
+ * than just dereferencing the raw pointer) is what makes it safe for the
+ * module that implements these ops (e.g. drivers/soc/qcom/crypto_virt.c) to
+ * be rmmod'd: module removal will fail/block until every in-flight dispatch
+ * call has released its reference, instead of racing with a concurrent
+ * virtblk_set_crypto_ops(NULL) and the ops table disappearing mid-call.
+ */
+static struct virtblk_crypto_variant_ops *
+virtblk_crypto_ops_get(struct blk_crypto_profile *profile)
+{
+	struct virtblk_crypto_profile *vdcp =
+		container_of(profile, struct virtblk_crypto_profile, profile);
+	struct virtblk_crypto_variant_ops *ops;
+
+	mutex_lock(&virtblk_crypto_ops_lock);
+	ops = vdcp->ops;
+	if (ops && !try_module_get(ops->owner))
+		ops = NULL;
+	mutex_unlock(&virtblk_crypto_ops_lock);
+
+	return ops;
+}
+
+static void virtblk_crypto_ops_put(struct virtblk_crypto_variant_ops *ops)
+{
+	module_put(ops->owner);
+}
+
+static int virtblk_crypto_keyslot_program(struct blk_crypto_profile *profile,
+					   const struct blk_crypto_key *key,
+					   unsigned int slot)
+{
+	struct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);
+	int ret;
+
+	if (!ops || !ops->program_key) {
+		if (ops)
+			virtblk_crypto_ops_put(ops);
+		return -EOPNOTSUPP;
+	}
+
+	ret = ops->program_key(key, slot);
+	virtblk_crypto_ops_put(ops);
+	if (ret)
+		pr_err("program hardware wrapped key failed: slot=%u ret=%d\n", slot, ret);
+
+	return ret;
+}
+
+static int virtblk_crypto_keyslot_evict(struct blk_crypto_profile *profile,
+					 const struct blk_crypto_key *key,
+					 unsigned int slot)
+{
+	struct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);
+	int ret;
+
+	if (!ops || !ops->evict_key) {
+		if (ops)
+			virtblk_crypto_ops_put(ops);
+		return -EOPNOTSUPP;
+	}
+
+	ret = ops->evict_key(slot);
+	virtblk_crypto_ops_put(ops);
+	if (ret)
+		pr_err("evict keyslot %u failed: %d\n", slot, ret);
+
+	return ret;
+}
+
+static int virtblk_crypto_derive_sw_secret(struct blk_crypto_profile *profile,
+					    const u8 *eph_key,
+					    size_t eph_key_size,
+					    u8 sw_secret[BLK_CRYPTO_SW_SECRET_SIZE])
+{
+	struct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);
+	int ret;
+
+	if (!ops || !ops->derive_sw_secret_key) {
+		if (ops)
+			virtblk_crypto_ops_put(ops);
+		return -EOPNOTSUPP;
+	}
+
+	ret = ops->derive_sw_secret_key(eph_key, eph_key_size, sw_secret);
+	virtblk_crypto_ops_put(ops);
+	if (ret)
+		pr_err("derive software secret failed: %d\n", ret);
+
+	return ret;
+}
+
+static int virtblk_crypto_generate_key(struct blk_crypto_profile *profile,
+					u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
+{
+	struct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);
+	int ret;
+
+	if (!ops || !ops->generate_key) {
+		if (ops)
+			virtblk_crypto_ops_put(ops);
+		return -EOPNOTSUPP;
+	}
+
+	ret = ops->generate_key(lt_key);
+	virtblk_crypto_ops_put(ops);
+	if (ret < 0)
+		pr_err("generate hardware wrapped key failed: %d\n", ret);
+
+	return ret;
+}
+
+static int virtblk_crypto_prepare_key(struct blk_crypto_profile *profile,
+				       const u8 *lt_key, size_t lt_key_size,
+				       u8 eph_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
+{
+	struct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);
+	int ret;
+
+	if (!ops || !ops->prepare_key) {
+		if (ops)
+			virtblk_crypto_ops_put(ops);
+		return -EOPNOTSUPP;
+	}
+
+	ret = ops->prepare_key(lt_key, lt_key_size, eph_key);
+	virtblk_crypto_ops_put(ops);
+	if (ret < 0)
+		pr_err("prepare hardware wrapped key failed: %d\n", ret);
+
+	return ret;
+}
+
+static int virtblk_crypto_import_key(struct blk_crypto_profile *profile,
+				      const u8 *raw_key, size_t raw_key_size,
+				      u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
+{
+	struct virtblk_crypto_variant_ops *ops = virtblk_crypto_ops_get(profile);
+	int ret;
+
+	if (!ops || !ops->import_key) {
+		if (ops)
+			virtblk_crypto_ops_put(ops);
+		return -EOPNOTSUPP;
+	}
+
+	ret = ops->import_key(raw_key, raw_key_size, lt_key);
+	virtblk_crypto_ops_put(ops);
+	if (ret < 0)
+		pr_err("import hardware wrapped key failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct blk_crypto_ll_ops virtblk_crypto_ops = {
+	.keyslot_program	= virtblk_crypto_keyslot_program,
+	.keyslot_evict		= virtblk_crypto_keyslot_evict,
+	.derive_sw_secret	= virtblk_crypto_derive_sw_secret,
+	.generate_key		= virtblk_crypto_generate_key,
+	.prepare_key		= virtblk_crypto_prepare_key,
+	.import_key		= virtblk_crypto_import_key,
+};
+
+int virtblk_init_inline_crypto(unsigned int max_slots, unsigned int max_dun_bytes,
+			       unsigned int key_types,
+			       const unsigned int crypto_modes_supported[BLK_ENCRYPTION_MODE_MAX],
+			       struct device *dev)
+{
+	struct blk_crypto_profile *profile = &g_vdcp.profile;
+	unsigned int key_type_supported = 0;
+	int err = 0;
+
+	dev_info(dev, "probing inline crypto capabilities\n");
+
+	mutex_lock(&virtblk_crypto_init_lock);
+
+	/*
+	 * profile is a single, process-wide blk_crypto_profile shared by every
+	 * VIRTIO_BLK_F_INLINE_ENCRYPTION device. Only the first device to get
+	 * here actually initializes it; any other device just reuses it as-is
+	 * if its negotiated capabilities match. A mismatch means this device's
+	 * capabilities don't actually correspond to what the shared profile
+	 * was set up for (wrong keyslot count, DUN size, or key types), which
+	 * is a correctness/security concern, not just a cosmetic one -- fail
+	 * instead of silently registering a profile that doesn't match what
+	 * this device supports.
+	 */
+	if (g_crypto_profile_initialized) {
+		if (max_slots != g_max_slots || max_dun_bytes != g_max_dun_bytes ||
+		    key_types != g_key_types) {
+			dev_warn(dev,
+				 "inline crypto profile already initialized by %s (max_slots=%u max_dun_bytes=%u key_types=0x%x); "
+				 "this device reports max_slots=%u max_dun_bytes=%u key_types=0x%x -- sharing one "
+				 "blk_crypto_profile across multiple VIRTIO_BLK_F_INLINE_ENCRYPTION devices with differing "
+				 "capabilities is not supported, refusing to enable inline crypto for this device\n",
+				 dev_name(virtblk_profile_owner), g_max_slots, g_max_dun_bytes, g_key_types,
+				 max_slots, max_dun_bytes, key_types);
+			err = -EINVAL;
+		}
+		goto out_unlock;
+	}
+
+	if (key_types & VIRTIO_BLK_CRYPTO_KEY_TYPE_RAW)
+		key_type_supported |= BLK_CRYPTO_KEY_TYPE_RAW;
+	if (key_types & VIRTIO_BLK_CRYPTO_KEY_TYPE_HW_WRAPPED)
+		key_type_supported |= BLK_CRYPTO_KEY_TYPE_HW_WRAPPED;
+
+	err = blk_crypto_profile_init(profile, max_slots);
+	if (err) {
+		dev_err(dev, "crypto profile initialization failed: %d\n", err);
+		goto out_unlock;
+	}
+
+	profile->ll_ops = virtblk_crypto_ops;
+	profile->max_dun_bytes_supported = max_dun_bytes;
+	profile->key_types_supported = key_type_supported;
+	profile->dev = dev;
+	memcpy(profile->modes_supported, crypto_modes_supported,
+	       BLK_ENCRYPTION_MODE_MAX * sizeof(unsigned int));
+
+	virtblk_profile_owner = dev;
+	g_max_slots = max_slots;
+	g_max_dun_bytes = max_dun_bytes;
+	g_key_types = key_types;
+	g_crypto_profile_initialized = true;
+
+	dev_info(dev, "inline crypto profile initialized\n");
+
+out_unlock:
+	mutex_unlock(&virtblk_crypto_init_lock);
+	return err;
+}
+EXPORT_SYMBOL_GPL(virtblk_init_inline_crypto);
+
+void virtblk_set_crypto_ops(struct virtblk_crypto_variant_ops *ops)
+{
+	if (!g_crypto_profile_initialized)
+		pr_warn("virtio blk crypto profile hasn't been initialized\n");
+
+	mutex_lock(&virtblk_crypto_ops_lock);
+	g_vdcp.ops = ops;
+	mutex_unlock(&virtblk_crypto_ops_lock);
+}
+EXPORT_SYMBOL_GPL(virtblk_set_crypto_ops);
+
+MODULE_DESCRIPTION("Virtio block inline crypto extension");
+MODULE_LICENSE("GPL");
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 2b524154d9fb3..e1f383b4dc63d 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -294,10 +294,40 @@ endif
 # Options selected by other drivers from different subsystems must be outside
 # of the menuconfig if-block:
 
+config QCOM_ICE_SLOTS
+	tristate "Qualcomm ICE keyslot partitioning for VM guests"
+	depends on ARCH_QCOM || COMPILE_TEST
+	depends on BLK_CRYPTO_PROXY
+	depends on BLK_INLINE_ENCRYPTION
+	help
+	  Parses the qcom,ice-keyslot-map device-tree node and provides
+	  per-VM ICE keyslot accounting and virtual-to-physical slot
+	  translation for guest VMs sharing ICE hardware on Qualcomm
+	  platforms.
+
+	  When enabled, guest virtual keyslot indices are mapped to the
+	  physical ICE keyslot range allocated to each VM, preventing one
+	  VM from accessing another VM's keyslots.
+
+	  Say M here when multiple VMs share ICE keyslots on a Qualcomm
+	  platform.  If unsure, say N.
+
 config QCOM_INLINE_CRYPTO_ENGINE
 	tristate
 	select QCOM_SCM
 
+config QCOM_CRYPTO_VIRT
+	tristate "Qualcomm Technologies, Inc. Crypto Virt driver"
+	depends on VIRTBLK_CRYPTO_VIRTUALIZATION
+	depends on QCOM_SCM
+	default VIRTBLK_CRYPTO_VIRTUALIZATION if ARCH_QCOM
+	help
+	  GVM-side hardware-wrapped-key SCM operations exposed to
+	  virtio_blk's inline crypto layer: per-slot key programming and
+	  eviction, and key derive/generate/prepare/import.
+	  Say Y here to compile the driver as a part of kernel or M to compile
+	  as a module.
+
 config QCOM_KRYO_L2_ACCESSORS
 	bool
 	depends on ARM64
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 798643be3590c..952a57554f9d9 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -38,6 +38,8 @@ obj-$(CONFIG_QCOM_LLCC) += llcc-qcom.o
 obj-$(CONFIG_QCOM_KRYO_L2_ACCESSORS) +=	kryo-l2-accessors.o
 obj-$(CONFIG_QCOM_ICC_BWMON)	+= icc-bwmon.o
 qcom_ice-objs			+= ice.o
+obj-$(CONFIG_QCOM_ICE_SLOTS)	+= qcom_ice_slots.o
 obj-$(CONFIG_QCOM_INLINE_CRYPTO_ENGINE)	+= qcom_ice.o
+obj-$(CONFIG_QCOM_CRYPTO_VIRT)	+= crypto_virt.o
 obj-$(CONFIG_QCOM_PBS) +=	qcom-pbs.o
 obj-$(CONFIG_QCOM_UBWC_CONFIG) += ubwc_config.o
diff --git a/drivers/soc/qcom/crypto_virt.c b/drivers/soc/qcom/crypto_virt.c
new file mode 100644
index 0000000000000..93c7993fb4a5d
--- /dev/null
+++ b/drivers/soc/qcom/crypto_virt.c
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/blk-crypto.h>
+#include <linux/virtio_blk_crypto_ext.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+
+static unsigned int g_wrapped_key_size;
+
+static int crypto_virt_program_key(const struct blk_crypto_key *key,
+				   unsigned int slot)
+{
+	u32 dus_512_units;
+	int ret;
+
+	if (!key || !key->size) {
+		pr_err("%s: invalid key\n", __func__);
+		return -EINVAL;
+	}
+
+	/* Only AES-256-XTS is supported so far. */
+	if (key->crypto_cfg.crypto_mode !=
+	    BLK_ENCRYPTION_MODE_AES_256_XTS) {
+		pr_err_ratelimited("Unsupported crypto mode: %d\n",
+				    key->crypto_cfg.crypto_mode);
+		return -EINVAL;
+	}
+
+	/* qcom_scm_ice_set_key()'s data_unit_size is expressed in 512-byte units */
+	dus_512_units = key->crypto_cfg.data_unit_size / 512;
+
+	ret = qcom_scm_ice_set_key(slot, key->bytes, key->size,
+		QCOM_SCM_ICE_CIPHER_AES_256_XTS, dus_512_units);
+	if (ret)
+		pr_err("%s: slot=%u ret=%d\n", __func__, slot, ret);
+
+	return ret;
+}
+
+static int crypto_virt_invalidate_key(unsigned int slot)
+{
+	int ret;
+
+	ret = qcom_scm_ice_invalidate_key(slot);
+	if (ret)
+		pr_err("%s: slot=%u ret=%d\n", __func__, slot, ret);
+
+	return ret;
+}
+
+static int crypto_virt_derive_sw_secret_key(const u8 *eph_key, size_t eph_key_size,
+					    u8 sw_secret[BLK_CRYPTO_SW_SECRET_SIZE])
+{
+	int ret;
+
+	ret = qcom_scm_derive_sw_secret(eph_key, eph_key_size,
+					sw_secret, BLK_CRYPTO_SW_SECRET_SIZE);
+	if (ret == -EIO || ret == -EINVAL)
+		ret = -EBADMSG; /* probably invalid key */
+
+	if (ret)
+		pr_err("%s: ret=%d\n", __func__, ret);
+
+	return ret;
+}
+
+static int crypto_virt_generate_key(u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
+{
+	int ret;
+
+	if (!g_wrapped_key_size) {
+		pr_err("%s: no expected wrapped key size\n", __func__);
+		return -EINVAL;
+	}
+
+	ret = qcom_scm_generate_ice_key(lt_key, g_wrapped_key_size);
+	if (ret) {
+		pr_err("%s: generate hardware wrapped key failed: %d\n", __func__, ret);
+		return ret;
+	}
+
+	return g_wrapped_key_size;
+}
+
+static int crypto_virt_prepare_key(const u8 *lt_key, size_t lt_key_size,
+				   u8 eph_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
+{
+	int ret;
+
+	if (!g_wrapped_key_size) {
+		pr_err("%s: no expected wrapped key size\n", __func__);
+		return -EINVAL;
+	}
+
+	ret = qcom_scm_prepare_ice_key(lt_key, lt_key_size,
+					eph_key, g_wrapped_key_size);
+	if (ret == -EIO || ret == -EINVAL)
+		ret = -EBADMSG; /* probably invalid key */
+
+	if (ret) {
+		pr_err("%s: prepare hardware wrapped key failed: %d\n", __func__, ret);
+		return ret;
+	}
+
+	return g_wrapped_key_size;
+}
+
+static int crypto_virt_import_key(const u8 *raw_key, size_t raw_key_size,
+				  u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE])
+{
+	int ret;
+
+	if (!g_wrapped_key_size) {
+		pr_err("%s: no expected wrapped key size\n", __func__);
+		return -EINVAL;
+	}
+
+	ret = qcom_scm_import_ice_key(raw_key, raw_key_size,
+				       lt_key, g_wrapped_key_size);
+	if (ret) {
+		pr_err("%s: import hardware wrapped key failed: %d\n", __func__, ret);
+		return ret;
+	}
+
+	return g_wrapped_key_size;
+}
+
+static struct virtblk_crypto_variant_ops virtblk_crypto_qcom_vops = {
+	.owner = THIS_MODULE,
+	.program_key = crypto_virt_program_key,
+	.evict_key = crypto_virt_invalidate_key,
+	.derive_sw_secret_key = crypto_virt_derive_sw_secret_key,
+	.generate_key = crypto_virt_generate_key,
+	.prepare_key = crypto_virt_prepare_key,
+	.import_key = crypto_virt_import_key,
+};
+
+static int crypto_virt_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = of_property_read_u32(pdev->dev.of_node, "qcom,wrapped-key-size",
+				    &g_wrapped_key_size);
+	if (ret)
+		dev_warn(&pdev->dev, "qcom,wrapped-key-size not found\n");
+
+	if (!g_wrapped_key_size ||
+	    g_wrapped_key_size > BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE) {
+		dev_err(&pdev->dev,
+			"invalid qcom,wrapped-key-size %u, won't support generate/import/prepare hardware wrapped key\n",
+			g_wrapped_key_size);
+		g_wrapped_key_size = 0;
+	}
+
+	virtblk_set_crypto_ops(&virtblk_crypto_qcom_vops);
+	return 0;
+}
+
+static void crypto_virt_remove(struct platform_device *pdev)
+{
+	virtblk_set_crypto_ops(NULL);
+}
+
+static const struct of_device_id crypto_virt_of_match[] = {
+	{ .compatible = "qcom,crypto-virt" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, crypto_virt_of_match);
+
+static struct platform_driver crypto_virt_driver = {
+	.probe = crypto_virt_probe,
+	.remove = crypto_virt_remove,
+	.driver = {
+		.name = "crypto_virt",
+		.of_match_table = crypto_virt_of_match,
+	},
+};
+
+static int __init crypto_virt_init(void)
+{
+	return platform_driver_register(&crypto_virt_driver);
+}
+module_init(crypto_virt_init);
+
+#if IS_MODULE(CONFIG_QCOM_CRYPTO_VIRT)
+static void __exit crypto_virt_exit(void)
+{
+	platform_driver_unregister(&crypto_virt_driver);
+}
+module_exit(crypto_virt_exit);
+#endif
+
+MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Crypto Virt Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/soc/qcom/qcom_ice_slots.c b/drivers/soc/qcom/qcom_ice_slots.c
new file mode 100644
index 0000000000000..364ac93077c12
--- /dev/null
+++ b/drivers/soc/qcom/qcom_ice_slots.c
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * qcom_ice_slots.c - Qualcomm ICE keyslot partitioning for guest VMs
+ *
+ * Implements bcp_slot_virt_ops: translates a (guest_id, virtual-slot) pair to
+ * a physical ICE keyslot index using a per-VM allocation table parsed from
+ * the device-tree node with compatible = "qcom,ice-keyslot-map".
+ *
+ * Device-tree layout:
+ *
+ *   ice_keyslot_map: ice-keyslot-map {
+ *       compatible = "qcom,ice-keyslot-map";
+ *       #address-cells = <1>;
+ *       #size-cells = <0>;
+ *
+ *       vm@3  { reg = <3>;  qcom,max-ice-slots = <16>; qcom,ice-slot-offset = <0>;  };
+ *       vm@52 { reg = <52>; qcom,max-ice-slots = <32>; qcom,ice-slot-offset = <16>; };
+ *   };
+ *
+ * Each child entry maps a guest (reg = guest_id) to a contiguous physical keyslot
+ * range [slot_offset .. slot_offset + max_ice_slots).
+ *
+ * Entry 0 is always the host's own reservation.  Entries 1+ are guest
+ * reservations.  The host's entry is used by ufs-qcom to size its
+ * blk_crypto_profile; it is excluded from the guest-facing translation table
+ * so that blk-crypto-proxy can never accidentally route a guest request into
+ * the host's physical keyslots.
+ *
+ * The ufs-qcom driver reads the host slot info and validates all entries
+ * against the hardware slot count directly via the OF API, with no symbol
+ * dependency on this module.
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/rcupdate.h>
+#include <linux/slab.h>
+#include <linux/blk-crypto-profile.h>
+#include <linux/blk-crypto-proxy.h>
+
+#define QCOM_ICE_SLOTS_MAX_ENTRIES	8
+
+struct qcom_ice_slot_entry {
+	u32 guest_id;
+	u32 max_slots;
+	u32 slot_offset;
+};
+
+struct qcom_ice_slots {
+	struct qcom_ice_slot_entry entries[QCOM_ICE_SLOTS_MAX_ENTRIES];
+	unsigned int num_entries;
+};
+
+/*
+ * There is at most one qcom,ice-keyslot-map platform node per SoC.  A single
+ * global pointer is set at probe time and cleared at remove time.  The
+ * hot-path read (from bcp_slot_virt_ops callbacks) is protected by RCU;
+ * probe/remove serialise via the platform driver guarantee.
+ */
+static struct qcom_ice_slots __rcu *g_ice_slots;
+
+static struct qcom_ice_slots *virt_lookup(struct blk_crypto_profile *profile)
+{
+	/* Single UFS controller: profile argument is not needed. */
+	return rcu_dereference(g_ice_slots);
+}
+
+static int qcom_ice_slots_get_guest_slots(struct blk_crypto_profile *profile,
+					  u32 guest_id)
+{
+	struct qcom_ice_slots *virt = virt_lookup(profile);
+	unsigned int i;
+
+	if (!virt)
+		return -ENOKEY;
+
+	/* entries[0] is the host; guest entries start at index 1. */
+	for (i = 1; i < virt->num_entries; i++) {
+		if (virt->entries[i].guest_id == guest_id)
+			return virt->entries[i].max_slots;
+	}
+	return -ENOKEY;
+}
+
+static int qcom_ice_slots_vslot_to_pslot(struct blk_crypto_profile *profile,
+					 u32 guest_id, u32 virt_slot,
+					 unsigned int *phy_slot_out)
+{
+	struct qcom_ice_slots *virt = virt_lookup(profile);
+	unsigned int i;
+
+	if (!virt)
+		return -ENOKEY;
+
+	for (i = 1; i < virt->num_entries; i++) {
+		if (virt->entries[i].guest_id != guest_id)
+			continue;
+		if (virt_slot >= virt->entries[i].max_slots)
+			return -EINVAL;
+		*phy_slot_out = virt->entries[i].slot_offset + virt_slot;
+		return 0;
+	}
+	return -ENOKEY;
+}
+
+static const struct bcp_slot_virt_ops qcom_slot_virt_ops = {
+	.get_guest_slots = qcom_ice_slots_get_guest_slots,
+	.vslot_to_pslot = qcom_ice_slots_vslot_to_pslot,
+};
+
+static int qcom_ice_slots_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *child;
+	struct qcom_ice_slots *virt;
+	unsigned int idx = 0, total_slots = 0;
+	int ret = 0;
+
+	virt = devm_kzalloc(dev, sizeof(*virt), GFP_KERNEL);
+	if (!virt)
+		return -ENOMEM;
+
+	for_each_child_of_node(dev->of_node, child) {
+		u32 guest_id, max_slots, slot_offset;
+		unsigned int j;
+
+		if (idx >= QCOM_ICE_SLOTS_MAX_ENTRIES) {
+			dev_err(dev, "too many vm entries (> %u)\n",
+				QCOM_ICE_SLOTS_MAX_ENTRIES);
+			ret = -EINVAL;
+			of_node_put(child);
+			goto err_free;
+		}
+
+		if (of_property_read_u32(child, "reg", &guest_id))
+			continue;
+		if (of_property_read_u32(child, "qcom,max-ice-slots", &max_slots))
+			continue;
+		if (of_property_read_u32(child, "qcom,ice-slot-offset", &slot_offset)) {
+			dev_err(dev, "missing qcom,ice-slot-offset for guest_id=%u\n",
+				guest_id);
+			ret = -EINVAL;
+			of_node_put(child);
+			goto err_free;
+		}
+
+		if (idx > 0 &&
+		    slot_offset <
+		    virt->entries[idx - 1].slot_offset +
+		    virt->entries[idx - 1].max_slots) {
+			dev_err(dev, "slot overlap: guest_id=%u overlaps guest_id=%u\n",
+				guest_id, virt->entries[idx - 1].guest_id);
+			ret = -EINVAL;
+			of_node_put(child);
+			goto err_free;
+		}
+
+		for (j = 0; j < idx; j++) {
+			if (virt->entries[j].guest_id == guest_id) {
+				dev_err(dev, "duplicate guest_id=%u\n", guest_id);
+				ret = -EINVAL;
+				of_node_put(child);
+				goto err_free;
+			}
+		}
+
+		virt->entries[idx].guest_id = guest_id;
+		virt->entries[idx].max_slots = max_slots;
+		virt->entries[idx].slot_offset = slot_offset;
+		total_slots += max_slots;
+		idx++;
+	}
+
+	if (idx == 0) {
+		dev_err(dev, "no VM entries found in qcom,ice-keyslot-map\n");
+		ret = -EINVAL;
+		goto err_free;
+	}
+
+	virt->num_entries = idx;
+
+	/*
+	 * Publish the singleton.  From this point on, bcp_slot_virt_ops
+	 * callbacks can resolve virt via rcu_dereference(g_ice_slots).
+	 */
+	rcu_assign_pointer(g_ice_slots, virt);
+
+	ret = bcp_register_slot_virt_ops(&qcom_slot_virt_ops);
+	if (ret) {
+		dev_err(dev, "failed to register slot_virt_ops: %d\n", ret);
+		goto err_free;
+	}
+
+	dev_info(dev, "registered: %u VMs, %u total ICE slots\n",
+		 idx, total_slots);
+	return 0;
+
+err_free:
+	return ret;
+}
+
+static void qcom_ice_slots_remove(struct platform_device *pdev)
+{
+	bcp_unregister_slot_virt_ops(&qcom_slot_virt_ops);
+	/*
+	 * Clear the singleton under RCU so that any concurrent ioctl that
+	 * already took the read lock and is mid-lookup sees either the old
+	 * valid pointer or NULL, never a freed pointer.
+	 */
+	rcu_assign_pointer(g_ice_slots, NULL);
+	synchronize_rcu();
+}
+
+static const struct of_device_id qcom_ice_slots_of_match[] = {
+	{ .compatible = "qcom,ice-keyslot-map" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, qcom_ice_slots_of_match);
+
+static struct platform_driver qcom_ice_slots_driver = {
+	.probe  = qcom_ice_slots_probe,
+	.remove = qcom_ice_slots_remove,
+	.driver = {
+		.name           = "qcom-ice-slots",
+		.of_match_table = qcom_ice_slots_of_match,
+	},
+};
+module_platform_driver(qcom_ice_slots_driver);
+
+MODULE_DESCRIPTION("Qualcomm ICE keyslot partitioning for guest VMs");
+MODULE_LICENSE("GPL");
diff --git a/drivers/ufs/core/ufshcd-crypto.h b/drivers/ufs/core/ufshcd-crypto.h
index 8f66db94e179a..2fc5601c0f769 100644
--- a/drivers/ufs/core/ufshcd-crypto.h
+++ b/drivers/ufs/core/ufshcd-crypto.h
@@ -15,13 +15,21 @@
 static inline void ufshcd_prepare_lrbp_crypto(struct request *rq,
 					      struct ufshcd_lrb *lrbp)
 {
-	if (!rq || !rq->crypt_keyslot) {
+	if (!rq) {
 		lrbp->crypto_key_slot = -1;
 		return;
 	}
 
-	lrbp->crypto_key_slot = blk_crypto_keyslot_index(rq->crypt_keyslot);
-	lrbp->data_unit_num = rq->crypt_ctx->bc_dun[0];
+	if (rq->crypt_keyslot) {
+		lrbp->crypto_key_slot = blk_crypto_keyslot_index(rq->crypt_keyslot);
+		lrbp->data_unit_num = rq->crypt_ctx->bc_dun[0];
+	} else if (rq->crypt_ctx && rq->crypt_ctx->bc_slot.data_unit_size_bits) {
+		lrbp->crypto_key_slot = rq->crypt_ctx->bc_slot.phy_slot;
+		lrbp->data_unit_num = rq->crypt_ctx->bc_dun[0];
+	} else {
+		lrbp->crypto_key_slot = -1;
+	}
+
 }
 
 static inline void
diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index 62396212a0a73..0611ab50f4cc9 100644
--- a/drivers/ufs/host/ufs-qcom.c
+++ b/drivers/ufs/host/ufs-qcom.c
@@ -163,6 +163,74 @@ static inline void ufs_qcom_ice_enable(struct ufs_qcom_host *host)
 		qcom_ice_enable(host->ice);
 }
 
+/**
+ * ufs_qcom_ice_parse_slot_table() - parse qcom,ice-keyslot-map DT node
+ * @dev:          UFS controller device
+ * @hw_max_slots: total physical ICE keyslots reported by REG_UFS_CCAP
+ * @num_slots:    receives host max_ice_slots (0 = no partitioning)
+ * @slot_offset:  receives host ice-slot-offset
+ *
+ * Parses the qcom,ice-keyslot-map device-tree node.  The first child entry
+ * is the host's own reservation; subsequent children are guest reservations.
+ * Validates that no entry's range exceeds @hw_max_slots and that the sum of
+ * all entries does not exceed @hw_max_slots.
+ *
+ * If no qcom,ice-keyslot-map phandle is present, sets @num_slots to 0 and
+ * returns 0.  Returns -EINVAL if any entry or the total exceeds @hw_max_slots.
+ */
+static int ufs_qcom_ice_parse_slot_table(struct device *dev,
+					 unsigned int hw_max_slots,
+					 unsigned int *num_slots,
+					 unsigned int *slot_offset)
+{
+	struct device_node *slots_np, *child;
+	unsigned int total_slots = 0;
+	bool first = true;
+	int ret = 0;
+
+	*num_slots   = 0;
+	*slot_offset = 0;
+
+	slots_np = of_parse_phandle(dev->of_node, "qcom,ice-keyslot-map", 0);
+	if (!slots_np)
+		return 0;
+
+	for_each_child_of_node(slots_np, child) {
+		u32 off, max;
+
+		if (of_property_read_u32(child, "qcom,ice-slot-offset", &off) ||
+		    of_property_read_u32(child, "qcom,max-ice-slots", &max))
+			continue;
+
+		if (off + max > hw_max_slots) {
+			dev_err(dev,
+				"ice-keyslot-map: slots [%u..%u) exceed hw max %u\n",
+				off, off + max, hw_max_slots);
+			of_node_put(child);
+			ret = -EINVAL;
+			break;
+		}
+
+		if (first) {
+			*num_slots   = max;
+			*slot_offset = off;
+			first = false;
+		}
+		total_slots += max;
+	}
+
+	of_node_put(slots_np);
+
+	if (!ret && total_slots > hw_max_slots) {
+		dev_err(dev,
+			"ice-keyslot-map: total slots %u exceed hw max %u\n",
+			total_slots, hw_max_slots);
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
 static const struct blk_crypto_ll_ops ufs_qcom_crypto_ops; /* forward decl */
 
 static int ufs_qcom_ice_init(struct ufs_qcom_host *host)
@@ -173,6 +241,8 @@ static int ufs_qcom_ice_init(struct ufs_qcom_host *host)
 	struct qcom_ice *ice;
 	union ufs_crypto_capabilities caps;
 	union ufs_crypto_cap_entry cap;
+	unsigned int num_slots, slot_offset;
+	unsigned int hw_max_slots;
 	int err;
 	int i;
 
@@ -192,7 +262,23 @@ static int ufs_qcom_ice_init(struct ufs_qcom_host *host)
 	caps.reg_val = cpu_to_le32(ufshcd_readl(hba, REG_UFS_CCAP));
 
 	/* The number of keyslots supported is (CFGC+1) */
-	err = devm_blk_crypto_profile_init(dev, profile, caps.config_count + 1);
+	hw_max_slots = caps.config_count + 1;
+
+	/*
+	 * Parse the qcom,ice-keyslot-map DT node: validate all entries against
+	 * the hardware slot count and read the host's own reservation.  If no
+	 * partitioning is configured (num_slots == 0), the profile manages the
+	 * full hardware slot range.
+	 */
+	err = ufs_qcom_ice_parse_slot_table(dev, hw_max_slots,
+					    &num_slots, &slot_offset);
+	if (err) {
+		dev_err(dev, "failed to parse ICE slot table: %d\n", err);
+		return err;
+	}
+
+	err = devm_blk_crypto_profile_init(dev, profile,
+					   num_slots ? num_slots : hw_max_slots);
 	if (err)
 		return err;
 
@@ -201,6 +287,9 @@ static int ufs_qcom_ice_init(struct ufs_qcom_host *host)
 	profile->key_types_supported = qcom_ice_get_supported_key_type(ice);
 	profile->dev = dev;
 
+	if (num_slots)
+		profile->slot_offset = slot_offset;
+
 	/*
 	 * Currently this driver only supports AES-256-XTS.  All known versions
 	 * of ICE support it, but to be safe make sure it is really declared in
diff --git a/include/linux/blk-crypto-profile.h b/include/linux/blk-crypto-profile.h
index 4f39e9cd75766..a9bdc05abfa3c 100644
--- a/include/linux/blk-crypto-profile.h
+++ b/include/linux/blk-crypto-profile.h
@@ -162,6 +162,15 @@ struct blk_crypto_profile {
 	 */
 	struct device *dev;
 
+	/**
+	 * @slot_offset: offset added to the slot array index to obtain the
+	 * physical ICE keyslot number.  Zero in the common case.  Set to a
+	 * non-zero value by storage drivers that share ICE hardware across
+	 * multiple guests, where the host's keyslots do not start at physical
+	 * slot 0.
+	 */
+	unsigned int slot_offset;
+
 	/* private: The following fields shouldn't be accessed by drivers. */
 
 	/* Number of keyslots, or 0 if not applicable */
diff --git a/include/linux/blk-crypto-proxy.h b/include/linux/blk-crypto-proxy.h
new file mode 100644
index 0000000000000..6cf1ff0703e9f
--- /dev/null
+++ b/include/linux/blk-crypto-proxy.h
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef __LINUX_BLK_CRYPTO_PROXY_H
+#define __LINUX_BLK_CRYPTO_PROXY_H
+
+#include <uapi/linux/blk-crypto-proxy.h>
+#include <linux/types.h>
+
+struct blk_crypto_profile;
+
+/**
+ * struct bcp_hypervisor_ops - hypervisor VM identity operations
+ *
+ * Translates a hypervisor-specific VM fd to the opaque u32 vm_id used
+ * throughout blk-crypto-proxy.  Register once at module init time.
+ */
+struct bcp_hypervisor_ops {
+	/**
+	 * @get_guest_id: Resolve @vm_fd to an opaque guest identifier.
+	 *
+	 * Verify the caller is permitted to act on behalf of the VM and write
+	 * its u32 id to @guest_id_out.  The value is passed verbatim to
+	 * bcp_slot_virt_ops callbacks.
+	 *
+	 * Returns 0 on success, -errno on failure.
+	 */
+	int (*get_guest_id)(int vm_fd, u32 *guest_id_out);
+};
+
+/**
+ * bcp_register_hypervisor_ops() - register the hypervisor op-set
+ * @ops: op-set to register; must remain valid until unregistered.
+ *
+ * Returns 0 on success, -EBUSY if an op-set is already registered.
+ */
+int bcp_register_hypervisor_ops(const struct bcp_hypervisor_ops *ops);
+
+/**
+ * bcp_unregister_hypervisor_ops() - unregister the hypervisor op-set
+ * @ops: must be the pointer that was passed to bcp_register_hypervisor_ops().
+ *
+ * Blocks until all in-flight callers have finished, then clears the
+ * registration.  Safe to call from module exit.
+ */
+void bcp_unregister_hypervisor_ops(const struct bcp_hypervisor_ops *ops);
+
+/**
+ * struct bcp_slot_virt_ops - ICE keyslot virtualization operations
+ *
+ * Per-VM ICE keyslot accounting and virtual-to-physical slot translation.
+ * The implementation owns the slot allocation table and is registered once
+ * at platform driver probe time.
+ *
+ * @profile is passed to every callback so an implementation supporting
+ * multiple storage controllers can distinguish between them.
+ *
+ * All callbacks may be called concurrently and must not sleep (called
+ * under RCU read lock).
+ */
+struct bcp_slot_virt_ops {
+	/**
+	 * @get_guest_slots: Return the number of ICE keyslots allocated to @guest_id.
+	 *
+	 * Returns the slot count (≥ 1) on success, -ENOKEY if @guest_id is
+	 * not in the allocation table.
+	 */
+	int (*get_guest_slots)(struct blk_crypto_profile *profile, u32 guest_id);
+
+	/**
+	 * @vslot_to_pslot: Translate a VM-local virtual slot to a physical slot.
+	 * @guest_id:       hypervisor-assigned VM identifier.
+	 * @virt_slot:   0-based slot index within @guest_id's allocation.
+	 * @phy_slot_out: receives the physical ICE keyslot index on success.
+	 *
+	 * Returns 0 on success, -ENOKEY if @guest_id is unknown, -EINVAL if
+	 * @virt_slot >= the VM's allocation.
+	 */
+	int (*vslot_to_pslot)(struct blk_crypto_profile *profile,
+			      u32 guest_id, u32 virt_slot,
+			      unsigned int *phy_slot_out);
+};
+
+/**
+ * bcp_register_slot_virt_ops() - register the slot-virt op-set
+ * @ops: op-set to register; must remain valid until unregistered.
+ *
+ * Returns 0 on success, -EBUSY if an op-set is already registered.
+ */
+int bcp_register_slot_virt_ops(const struct bcp_slot_virt_ops *ops);
+
+/**
+ * bcp_unregister_slot_virt_ops() - unregister the slot-virt op-set
+ * @ops: must be the pointer passed to bcp_register_slot_virt_ops().
+ *
+ * Blocks until all in-flight callers have finished, then clears the
+ * registration.  Safe to call from module exit.
+ */
+void bcp_unregister_slot_virt_ops(const struct bcp_slot_virt_ops *ops);
+
+#endif /* __LINUX_BLK_CRYPTO_PROXY_H */
diff --git a/include/linux/blk-crypto.h b/include/linux/blk-crypto.h
index 938ff536838c7..c9f436b6b2ef4 100644
--- a/include/linux/blk-crypto.h
+++ b/include/linux/blk-crypto.h
@@ -119,9 +119,28 @@ struct blk_crypto_key {
 #define BLK_CRYPTO_MAX_IV_SIZE		32
 #define BLK_CRYPTO_DUN_ARRAY_SIZE	(BLK_CRYPTO_MAX_IV_SIZE / sizeof(u64))
 
+/**
+ * struct blk_crypto_slot - physical slot context for slot-based inline crypto
+ * @phy_slot:           Physical ICE keyslot index (already resolved from virt).
+ * @data_unit_size_bits: log2 of the encryption data unit size; used by
+ *                       __bio_crypt_advance() to increment the DUN correctly
+ *                       when a bio is split.  0 means unknown/unset.
+ *
+ * Used when a bio carries inline crypto context by physical slot index rather
+ * than by a blk_crypto_key pointer (i.e. bc_key == NULL in bio_crypt_ctx).
+ * Set by crypto_vblk when building the bio for a GVM VIRTIO_BLK_T_CRYPTO_IN/OUT
+ * request; left zeroed for all other bio types.
+ */
+struct blk_crypto_slot {
+	unsigned int phy_slot;
+	unsigned int data_unit_size_bits;
+};
+
 /**
  * struct bio_crypt_ctx - an inline encryption context
  * @bc_key: the key, algorithm, and data unit size to use
+ * @bc_slot: physical slot + data_unit_size_bits for slot-based crypto
+ *           (used when bc_key == NULL)
  * @bc_dun: the data unit number (starting IV) to use
  *
  * A bio_crypt_ctx specifies that the contents of the bio will be encrypted (for
@@ -130,6 +149,7 @@ struct blk_crypto_key {
  */
 struct bio_crypt_ctx {
 	const struct blk_crypto_key	*bc_key;
+	struct blk_crypto_slot		 bc_slot;
 	u64				bc_dun[BLK_CRYPTO_DUN_ARRAY_SIZE];
 };
 
@@ -152,10 +172,18 @@ void bio_crypt_set_ctx(struct bio *bio, const struct blk_crypto_key *key,
 		       const u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE],
 		       gfp_t gfp_mask);
 
+void bio_crypt_set_ctx_by_slot(struct bio *bio,
+			       const struct blk_crypto_slot *slot,
+			       const u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE],
+			       gfp_t gfp_mask);
+
 bool bio_crypt_dun_is_contiguous(const struct bio_crypt_ctx *bc,
 				 unsigned int bytes,
 				 const u64 next_dun[BLK_CRYPTO_DUN_ARRAY_SIZE]);
 
+void bio_crypt_dun_increment(u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE],
+			     unsigned int inc);
+
 int blk_crypto_init_key(struct blk_crypto_key *blk_key,
 			const u8 *key_bytes, size_t key_size,
 			enum blk_crypto_key_type key_type,
diff --git a/include/linux/virtio_blk_crypto_ext.h b/include/linux/virtio_blk_crypto_ext.h
new file mode 100644
index 0000000000000..3fe66029ac0d4
--- /dev/null
+++ b/include/linux/virtio_blk_crypto_ext.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __LINUX_VIRTIO_BLK_CRYPTO_EXT_H
+#define __LINUX_VIRTIO_BLK_CRYPTO_EXT_H
+
+#include <linux/blk-crypto.h>
+
+struct blk_crypto_profile;
+struct blk_crypto_key;
+struct device;
+struct request_queue;
+
+#if IS_ENABLED(CONFIG_VIRTBLK_CRYPTO_VIRTUALIZATION)
+struct virtblk_crypto_variant_ops {
+	/*
+	 * Module providing the function pointers below. virtio_blk_crypto_ext.c
+	 * pins it with try_module_get()/module_put() around every call, so that
+	 * the module implementing these ops can be safely rmmod'd: the unload
+	 * will simply block/fail until no dispatch call is in flight, instead
+	 * of racing with one.
+	 */
+	struct module *owner;
+	int (*program_key)(const struct blk_crypto_key *key,
+			   unsigned int slot);
+	int (*evict_key)(unsigned int slot);
+	int (*derive_sw_secret_key)(const u8 *eph_key, size_t eph_key_size,
+				    u8 sw_secret[BLK_CRYPTO_SW_SECRET_SIZE]);
+	int (*generate_key)(u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);
+	int (*prepare_key)(const u8 *lt_key, size_t lt_key_size,
+			   u8 eph_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);
+	int (*import_key)(const u8 *raw_key, size_t raw_key_size,
+			  u8 lt_key[BLK_CRYPTO_MAX_HW_WRAPPED_KEY_SIZE]);
+};
+
+/*
+ * Probes the platform's inline-encryption capabilities and initializes the
+ * shared blk_crypto_profile singleton (once) with the keyslot/DUN limits,
+ * supported key types, wrapped key size, and supported crypto modes reported
+ * by the device over virtio config space / VIRTIO_BLK_T_GET_CRYPTO_MODES.
+ *
+ * Safe to call from multiple devices, including concurrently: only the
+ * first caller actually initializes the shared profile, every other caller
+ * just validates its own capabilities against what was already negotiated.
+ */
+int virtblk_init_inline_crypto(unsigned int max_slots, unsigned int max_dun_bytes,
+				unsigned int key_types,
+				const unsigned int crypto_modes_supported[BLK_ENCRYPTION_MODE_MAX],
+				struct device *dev);
+
+/*
+ * Registers the (already-initialized) shared blk_crypto_profile with the
+ * given request queue. Returns false (and leaves the queue without inline
+ * crypto) if the queue's block-integrity support conflicts with inline
+ * encryption; see blk_crypto_register().
+ */
+bool virtblk_crypto_register(struct request_queue *q);
+
+void virtblk_set_crypto_ops(struct virtblk_crypto_variant_ops *ops);
+
+#else
+
+static inline int virtblk_init_inline_crypto(unsigned int max_slots,
+		unsigned int max_dun_bytes, unsigned int key_types,
+		const unsigned int crypto_modes_supported[BLK_ENCRYPTION_MODE_MAX],
+		struct device *dev)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline bool virtblk_crypto_register(struct request_queue *q)
+{
+	return false;
+}
+
+
+#endif
+
+#endif /* __LINUX_VIRTIO_BLK_CRYPTO_EXT_H */
diff --git a/include/uapi/linux/blk-crypto-proxy.h b/include/uapi/linux/blk-crypto-proxy.h
new file mode 100644
index 0000000000000..dc8adc8ef5edb
--- /dev/null
+++ b/include/uapi/linux/blk-crypto-proxy.h
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+
+#ifndef __UAPI_LINUX_BLK_CRYPTO_PROXY_H
+#define __UAPI_LINUX_BLK_CRYPTO_PROXY_H
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+#define BCP_DIR_READ     0
+#define BCP_DIR_WRITE    1
+
+/*
+ * BCP_BIND_CONTEXT - bind a host block device and hypervisor VM fd.
+ *
+ * Must be called once after open(), before any other ioctl.
+ * Returns -EBUSY if already bound, -EOPNOTSUPP if no hypervisor op-set
+ * is registered.
+ *
+ * @block_dev_fd: fd of the host block device to bind.
+ * @vm_fd:        hypervisor VM fd identifying the guest.
+ * @reserved:     must be zero.
+ */
+struct bcp_bind_context_arg {
+	__s32 block_dev_fd;
+	__s32 vm_fd;
+	__u32 reserved;
+};
+
+/*
+ * BCP_GET_CRYPTO_CAPS - query crypto capabilities of the bound block device.
+ *
+ * Requires BCP_BIND_CONTEXT; returns -ENXIO otherwise.
+ *
+ * @key_types_supported:  [out] BLK_CRYPTO_KEY_TYPE_* bitmask.
+ * @max_dun_bytes:        [out] maximum DUN bytes supported.
+ * @max_slots:            [out] maximum ICE keyslots available for the bound VM;
+ *                              0 if the VM is not found in the table.
+ * @num_modes:            [in] capacity of the buffer pointed to by @modes_ptr,
+ *                              in entries. [out] number of entries actually
+ *                              written to @modes_ptr (may be less than the
+ *                              given capacity; the caller must use this
+ *                              value, not its own capacity, to know how many
+ *                              entries are valid).
+ * @modes_ptr:             [in] pointer to a caller-allocated __u32 array of
+ *                              at least @num_modes (as given) entries. Must
+ *                              be non-NULL if @num_modes (as given) is > 0.
+ *                              On return, holds a per-mode data_unit_size
+ *                              bitmask array indexed by VIRTIO_BLK_CRYPTO_MODE_*
+ *                              (virtio wire numbering, uapi/linux/virtio_blk.h)
+ *                              -- NOT by enum blk_crypto_mode_num. Index 0 is
+ *                              reserved and always 0, matching struct
+ *                              virtio_blk_crypto_modes.modes[].
+ *
+ * @modes_ptr is a pointer + count rather than a fixed-size array embedded in
+ * this struct so that sizeof(struct bcp_get_crypto_caps_arg) -- and hence the
+ * _IOWR-encoded ioctl number -- does not depend on VIRTIO_BLK_CRYPTO_MODE_MAX.
+ * The caller and this kernel may be built against different virtio_blk.h
+ * versions (and thus different values of that constant); embedding a
+ * VIRTIO_BLK_CRYPTO_MODE_MAX-sized array directly in this struct would make
+ * the ioctl fail to even dispatch (-ENOTTY) whenever the two disagree.
+ */
+
+struct bcp_get_crypto_caps_arg {
+	__u32 key_types_supported;
+	__u32 max_dun_bytes;
+	__u32 max_slots;
+	__u32 num_modes;
+	__aligned_u64 modes_ptr;
+};
+
+/*
+ * BCP_SUBMIT_IO_BY_VSLOT - submit an encrypted bio using a virtual slot.
+ *
+ * The kernel resolves virt_slot to a physical ICE keyslot and submits the
+ * I/O synchronously.  Large requests are split at data-unit boundaries
+ * (BIO_MAX_VECS pages per bio).  Requires BCP_BIND_CONTEXT; returns -ENXIO
+ * otherwise.
+ *
+ * @virt_slot:           guest-visible slot index (0-based within the VM's range).
+ * @direction:           BCP_DIR_READ or BCP_DIR_WRITE.
+ * @flags:               must be BCP_SUBMIT_IO_F_IOV.
+ * @data_unit_size_bits: log2 of the encryption data unit size in bytes.
+ * @sector:              start sector (512-byte units).
+ * @dun:                 data unit number (single 64-bit limb, little-endian).
+ * @iov_ptr:             pointer to scatter-gather array of struct bcp_iovec.
+ * @iov_cnt:             number of entries in @iov_ptr[].
+ * @reserved2:           must be zero.
+ *
+ * @sector, @dun and @iov_ptr use __aligned_u64 to guarantee identical struct
+ * layout between 32-bit and 64-bit callers, as required by
+ * .compat_ioctl = compat_ptr_ioctl.
+ */
+
+/* Maximum iovec segments per BCP_SUBMIT_IO_BY_VSLOT call (matches UIO_MAXIOV). */
+#define BCP_MAX_IOV            1024
+
+#define BCP_SUBMIT_IO_F_IOV    (1U << 0)	/* scatter-gather mode; must always be set */
+
+struct bcp_iovec {
+	__u64 iov_base;
+	__u64 iov_len;
+};
+
+struct bcp_submit_io_by_vslot_arg {
+	__u32 virt_slot;
+	__u32 direction;
+	__u32 flags;
+	__u32 data_unit_size_bits;
+	__aligned_u64 sector;
+	__aligned_u64 dun;
+	__aligned_u64 iov_ptr;
+	__u32 iov_cnt;
+	__u32 reserved2;
+};
+
+#define BCP_IOC_MAGIC    0xC7
+
+#define BCP_BIND_CONTEXT       _IOW(BCP_IOC_MAGIC, 1, struct bcp_bind_context_arg)
+#define BCP_GET_CRYPTO_CAPS    _IOWR(BCP_IOC_MAGIC, 2, struct bcp_get_crypto_caps_arg)
+#define BCP_SUBMIT_IO_BY_VSLOT  _IOW(BCP_IOC_MAGIC, 3, struct bcp_submit_io_by_vslot_arg)
+
+#endif /* __UAPI_LINUX_BLK_CRYPTO_PROXY_H */
diff --git a/include/uapi/linux/virtio_blk.h b/include/uapi/linux/virtio_blk.h
index 3744e4da1b2a7..c1941f7fe0196 100644
--- a/include/uapi/linux/virtio_blk.h
+++ b/include/uapi/linux/virtio_blk.h
@@ -42,6 +42,7 @@
 #define VIRTIO_BLK_F_WRITE_ZEROES	14	/* WRITE ZEROES is supported */
 #define VIRTIO_BLK_F_SECURE_ERASE	16 /* Secure Erase is supported */
 #define VIRTIO_BLK_F_ZONED		17	/* Zoned block device */
+#define VIRTIO_BLK_F_INLINE_ENCRYPTION		22	/* Inline Encryption is supported */
 
 /* Legacy feature bits */
 #ifndef VIRTIO_BLK_NO_LEGACY
@@ -148,6 +149,18 @@ struct virtio_blk_config {
 		__u8 model;
 		__u8 unused2[3];
 	} zoned;
+
+	/* Inline Encryption device characteristics (if VIRTIO_BLK_F_INLINE_ENCRYPTION) */
+	struct virtio_blk_enc_characteristics {
+		__virtio16 max_slots;
+		__u8 max_dun_bytes;
+/* Bitmask values for virtio_blk_enc_characteristics.key_types */
+#define VIRTIO_BLK_CRYPTO_KEY_TYPE_RAW	(1 << 0)
+#define VIRTIO_BLK_CRYPTO_KEY_TYPE_HW_WRAPPED	(1 << 1)
+		/* Bitmask of supported key types: VIRTIO_BLK_CRYPTO_KEY_TYPE_* */
+		__u8 key_types;
+		__virtio32 unused3;
+	} enc_characteristics;
 } __attribute__((packed));
 
 /*
@@ -206,6 +219,15 @@ struct virtio_blk_config {
 /* Reset All zones command */
 #define VIRTIO_BLK_T_ZONE_RESET_ALL 26
 
+/* Inline-encrypted write: crypto_msg set in outhdr */
+#define VIRTIO_BLK_T_CRYPTO_OUT		27
+
+/* Inline-encrypted read: crypto_msg set in outhdr */
+#define VIRTIO_BLK_T_CRYPTO_IN		28
+
+/* Get inline crypto modes */
+#define VIRTIO_BLK_T_GET_CRYPTO_MODES	30
+
 #ifndef VIRTIO_BLK_NO_LEGACY
 /* Barrier before this op. */
 #define VIRTIO_BLK_T_BARRIER	0x80000000
@@ -225,6 +247,46 @@ struct virtio_blk_outhdr {
 	__virtio64 sector;
 };
 
+/*
+ * Crypto message descriptor, appended to the outhdr of a
+ * VIRTIO_BLK_T_CRYPTO_OUT or VIRTIO_BLK_T_CRYPTO_IN request.
+ */
+struct virtio_blk_crypto_msg {
+	/* virtual key slot index */
+	__virtio32 slot;
+	/* log2 of the data unit size in bytes */
+	__virtio32 data_unit_size_bits;
+	/* data unit number (DUN / IV) for this request */
+	__virtio64 dun;
+};
+
+/*
+ * Crypto mode numbers used in VIRTIO_BLK_T_GET_CRYPTO_MODES replies and in
+ * indexing struct virtio_blk_crypto_modes.modes[] below. These numbers are
+ * assigned by the virtio spec and are stable: a number is never reused for
+ * a different crypto mode, and additional crypto modes are assigned new,
+ * higher numbers.
+ */
+enum {
+	VIRTIO_BLK_CRYPTO_MODE_INVALID,
+	VIRTIO_BLK_CRYPTO_MODE_AES_256_XTS,
+	__VIRTIO_BLK_CRYPTO_MODE_MAX,	/* sentinel: always one past the last real mode */
+};
+
+/* Highest crypto mode number defined by this version of the header. */
+#define VIRTIO_BLK_CRYPTO_MODE_MAX (__VIRTIO_BLK_CRYPTO_MODE_MAX - 1)
+
+/* Reply to a VIRTIO_BLK_T_GET_CRYPTO_MODES request. */
+struct virtio_blk_crypto_modes {
+	/*
+	 * modes[N], for crypto mode number N <= VIRTIO_BLK_CRYPTO_MODE_MAX, is
+	 * a bitmask of the data unit sizes with which crypto mode N can be
+	 * used: bit i is set if a data unit size of (1 << i) bytes is
+	 * supported. modes[0] is reserved and always 0.
+	 */
+	__virtio32 modes[__VIRTIO_BLK_CRYPTO_MODE_MAX];
+};
+
 /*
  * Supported zoned device models.
  */

39/3 2026/08/27 17:04 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35598
							output: 18
							thoughts: 3510
						

Thoughts:
**KMSAN Fuzzing Assessment for Inline Encryption Virtualization Patch Series**

Right, let's dissect this patch series and determine the KMSAN implications. My initial thought is to meticulously trace data flow, specifically focusing on any potential leakage of uninitialized memory to user space, the hardware interface or any place that an attacker could read it.

First, the `blk-crypto-proxy.c` driver: `bcp_ioctl_bind_context` and `bcp_ioctl_submit_io_by_vslot` both copy data *from* userspace. The critical function is `bcp_ioctl_get_crypto_caps`, which has a read and write cycle. The user-provided `arg` in `bcp_ioctl_get_crypto_caps` *is* copied back to userspace after modifications to some of its fields and the `modes` array. However, the struct layout shows there's no padding, and the `modes` array is initialized before it gets copied back, so that's safe.

Moving on to `virtio_blk.c`, I'm looking at `virtblk_get_crypto_modes`, which allocates a `virtio_blk_crypto_modes` struct with `kzalloc`. This ensures it's zeroed, so no worries.

`virtio_blk_crypto_ext.c`, `crypto_virt.c`, `qcom_ice_slots.c`, and `ufs-qcom.c` seem straightforward; no user space interactions, so unlikely to be issues.

Let me double-check the `bcp_get_crypto_caps_arg` struct padding – just to be absolutely sure. Yep, 4 x `__u32` (16 bytes) and an `__aligned_u64` (8 bytes), making a clean 24-byte struct with no room for padding at the end. That's good. The other structs used in user space interactions, `bcp_bind_context_arg` and `bcp_submit_io_by_vslot_arg`, are also free of padding. `bcp_submit_io_by_vslot_arg` is a slightly larger struct, but is still safe.

Now, let's re-examine `bcp_ioctl_get_crypto_caps`. It copies the struct from user, initializes certain fields, and copies back. Since the struct is fully initialized, and no padding exists, this is also safe.

Next, I am analyzing `virtblk_setup_cmd` in `virtio_blk.c`. The `out_hdr` union and associated structs (`virtio_blk_outhdr` and `virtio_blk_crypto_msg`) must be checked for potential uninitialized memory exposures. The `out_hdr` union gets different values set depending on the `type` of request, but all relevant values are initialized. The `out_hdr` union size calculation for the `crypto_append` case looks correct as well.

The `bio_crypt_set_ctx_by_slot` function uses `mempool_alloc` to allocate `bio_crypt_ctx`, then initializes its fields `bc_key`, `bc_slot`, and `bc_dun`. The allocated memory comes from a pool, the fields are initialized before use, and this isn't shared with userspace, so it looks secure.

`bcp_ioctl_submit_io_by_vslot` seems fine. I verify all buffers being passed between user/kernel are initialized. `virtblk_get_crypto_modes` also appears safe because it uses `kzalloc`. The index calculation in `blk_crypto_keyslot_index` is also straightforward.

Finally, in `bcp_open`, I noticed a potential typo or custom macro: `kzalloc_obj(*ctx, GFP_KERNEL)`. This looks a bit odd, perhaps it is actually a typo: `kzalloc(sizeof(*ctx), GFP_KERNEL)`. I need to confirm the actual meaning of the macro, but `kzalloc` is the expected behavior, which is safe.

In summary, after this thorough review, I'm confident that this patch series doesn't introduce any obvious uninitialized memory risks that would justify a dedicated KMSAN fuzzing session, at least from this initial analysis. No immediate red flags. Still, I'd suggest automated static analysis tools to verify this manually.



40/3 2026/08/27 17:05 tool grepper
0m Args:
{
  "Expression": "kzalloc_obj"
}

Results:
{
  "Output": "\nFull output is too long, showing 500 out of 79400 lines.\nUse more precise expression if possible.\n\nDocumentation/core-api/list.rst=103=list:\n--\nDocumentation/core-api/list.rst-114-\nDocumentation/core-api/list.rst:115:          grock = kzalloc_obj(*grock);\nDocumentation/core-api/list.rst-116-          if (!grock)\n--\nDocumentation/core-api/list.rst-125-\nDocumentation/core-api/list.rst:126:          dimitri = kzalloc_obj(*dimitri);\nDocumentation/core-api/list.rst-127-          if (!dimitri)\n--\nDocumentation/driver-api/mailbox.rst=45=a message and a callback function to the API and return immediately).\n--\nDocumentation/driver-api/mailbox.rst-89-\nDocumentation/driver-api/mailbox.rst:90:\t\tdc_sync = kzalloc_obj(*dc_sync);\nDocumentation/driver-api/mailbox.rst:91:\t\tdc_async = kzalloc_obj(*dc_async);\nDocumentation/driver-api/mailbox.rst-92-\n--\nDocumentation/driver-api/media/v4l2-fh.rst=26=Example:\n--\nDocumentation/driver-api/media/v4l2-fh.rst-44-\nDocumentation/driver-api/media/v4l2-fh.rst:45:\t\tmy_fh = kzalloc_obj(*my_fh);\nDocumentation/driver-api/media/v4l2-fh.rst-46-\n--\nDocumentation/process/coding-style.rst=938=The kernel provides the following general purpose memory allocators:\nDocumentation/process/coding-style.rst:939:kmalloc(), kzalloc(), kmalloc_objs(), kzalloc_objs(), vmalloc(), and\nDocumentation/process/coding-style.rst-940-vzalloc().  Please refer to the API documentation for further information\n--\nDocumentation/process/coding-style.rst=964=The preferred form for allocating a zeroed array is the following:\n--\nDocumentation/process/coding-style.rst-967-\nDocumentation/process/coding-style.rst:968:\tp = kzalloc_objs(*p, n, ...);\nDocumentation/process/coding-style.rst-969-\n--\nDocumentation/process/deprecated.rst=398=become, respectively::\n--\nDocumentation/process/deprecated.rst-400-\tptr = kmalloc_obj(*ptr [, gfp] );\nDocumentation/process/deprecated.rst:401:\tptr = kzalloc_obj(*ptr [, gfp] );\nDocumentation/process/deprecated.rst-402-\tptr = kmalloc_objs(*ptr, count [, gfp] );\nDocumentation/process/deprecated.rst:403:\tptr = kzalloc_objs(*ptr, count [, gfp] );\nDocumentation/process/deprecated.rst-404-\tptr = kmalloc_flex(*ptr, flex_member, count [, gfp] );\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=208=to details explained in the following section.\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-268-              /* allocate a chip-specific data with zero filled */\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:269:              chip = kzalloc_obj(*chip);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-270-              if (chip == NULL)\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=623=After allocating a card instance via :c:func:`snd_card_new()`\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-630-  .....\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:631:  chip = kzalloc_obj(*chip);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-632-\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=698=destructor and PCI entries. Example code is shown first, below::\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-749-\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:750:              chip = kzalloc_obj(*chip);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-751-              if (chip == NULL) {\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=3823=chip data individually::\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3835-          ....\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:3836:          chip = kzalloc_obj(*chip);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3837-          ....\n--\nDocumentation/translations/pt_BR/process/deprecated.rst=410=tornam-se, respectivamente::\n--\nDocumentation/translations/pt_BR/process/deprecated.rst-412-        ptr = kmalloc_obj(*ptr [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst:413:        ptr = kzalloc_obj(*ptr [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst-414-        ptr = kmalloc_objs(*ptr, count [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst:415:        ptr = kzalloc_objs(*ptr, count [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst-416-        ptr = kmalloc_flex(*ptr, flex_member, count [, gfp] );\n--\nDocumentation/translations/zh_CN/video4linux/v4l2-framework.txt=794=int my_open(struct file *file)\n--\nDocumentation/translations/zh_CN/video4linux/v4l2-framework.txt-801-\nDocumentation/translations/zh_CN/video4linux/v4l2-framework.txt:802:\tmy_fh = kzalloc_obj(*my_fh);\nDocumentation/translations/zh_CN/video4linux/v4l2-framework.txt-803-\n--\narch/alpha/kernel/module.c=64=module_frob_arch_sections(Elf64_Ehdr *hdr, Elf64_Shdr *sechdrs,\n--\narch/alpha/kernel/module.c-95-\tnsyms = symtab-\u003esh_size / sizeof(Elf64_Sym);\narch/alpha/kernel/module.c:96:\tchains = kzalloc_objs(struct got_entry, nsyms);\narch/alpha/kernel/module.c-97-\tif (!chains) {\n--\narch/alpha/kernel/setup.c=390=register_cpus(void)\n--\narch/alpha/kernel/setup.c-394-\tfor_each_possible_cpu(i) {\narch/alpha/kernel/setup.c:395:\t\tstruct cpu *p = kzalloc_obj(*p);\narch/alpha/kernel/setup.c-396-\t\tif (!p)\n--\narch/arc/net/bpf_jit_core.c=1120=static int jit_prepare_final_mem_alloc(struct jit_context *ctx)\n--\narch/arc/net/bpf_jit_core.c-1131-\tif (ctx-\u003eneed_extra_pass) {\narch/arc/net/bpf_jit_core.c:1132:\t\tctx-\u003ejit_data = kzalloc_obj(*ctx-\u003ejit_data);\narch/arc/net/bpf_jit_core.c-1133-\t\tif (!ctx-\u003ejit_data)\n--\narch/arm/common/locomo.c=220=locomo_init_one_child(struct locomo *lchip, struct locomo_dev_info *info)\n--\narch/arm/common/locomo.c-224-\narch/arm/common/locomo.c:225:\tdev = kzalloc_obj(struct locomo_dev);\narch/arm/common/locomo.c-226-\tif (!dev) {\n--\narch/arm/common/locomo.c=356=__locomo_probe(struct device *me, struct resource *mem, int irq)\n--\narch/arm/common/locomo.c-362-\narch/arm/common/locomo.c:363:\tlchip = kzalloc_obj(struct locomo);\narch/arm/common/locomo.c-364-\tif (!lchip)\n--\narch/arm/common/sa1111.c=733=sa1111_init_one_child(struct sa1111 *sachip, struct resource *parent,\n--\narch/arm/common/sa1111.c-739-\narch/arm/common/sa1111.c:740:\tdev = kzalloc_obj(struct sa1111_dev);\narch/arm/common/sa1111.c-741-\tif (!dev) {\n--\narch/arm/common/scoop.c=178=static int scoop_probe(struct platform_device *pdev)\n--\narch/arm/common/scoop.c-187-\narch/arm/common/scoop.c:188:\tdevptr = kzalloc_obj(struct scoop_dev);\narch/arm/common/scoop.c-189-\tif (!devptr)\n--\narch/arm/kernel/smp.c=108=static int secondary_biglittle_prepare(unsigned int cpu)\n--\narch/arm/kernel/smp.c-110-\tif (!cpu_vtable[cpu])\narch/arm/kernel/smp.c:111:\t\tcpu_vtable[cpu] = kzalloc_obj(*cpu_vtable[cpu]);\narch/arm/kernel/smp.c-112-\n--\narch/arm/kernel/vdso.c=169=static int __init vdso_init(void)\n--\narch/arm/kernel/vdso.c-181-\t/* Allocate the VDSO text pagelist */\narch/arm/kernel/vdso.c:182:\tvdso_text_pagelist = kzalloc_objs(struct page *, text_pages);\narch/arm/kernel/vdso.c-183-\tif (vdso_text_pagelist == NULL)\n--\narch/arm/mach-footbridge/dc21285.c=261=int __init dc21285_setup(int nr, struct pci_sys_data *sys)\n--\narch/arm/mach-footbridge/dc21285.c-264-\narch/arm/mach-footbridge/dc21285.c:265:\tres = kzalloc_objs(struct resource, 2);\narch/arm/mach-footbridge/dc21285.c-266-\tif (!res) {\n--\narch/arm/mach-footbridge/ebsa285.c=69=static int __init ebsa285_leds_init(void)\n--\narch/arm/mach-footbridge/ebsa285.c-86-\narch/arm/mach-footbridge/ebsa285.c:87:\t\tled = kzalloc_obj(*led);\narch/arm/mach-footbridge/ebsa285.c-88-\t\tif (!led)\n--\narch/arm/mach-footbridge/netwinder-hw.c=720=static int __init netwinder_leds_init(void)\n--\narch/arm/mach-footbridge/netwinder-hw.c-729-\narch/arm/mach-footbridge/netwinder-hw.c:730:\t\tled = kzalloc_obj(*led);\narch/arm/mach-footbridge/netwinder-hw.c-731-\t\tif (!led)\n--\narch/arm/mach-imx/mmdc.c=473=static int imx_mmdc_perf_init(struct platform_device *pdev, void __iomem *mmdc_base,\n--\narch/arm/mach-imx/mmdc.c-479-\narch/arm/mach-imx/mmdc.c:480:\tpmu_mmdc = kzalloc_obj(*pmu_mmdc);\narch/arm/mach-imx/mmdc.c-481-\tif (!pmu_mmdc) {\n--\narch/arm/mach-mvebu/board-v7.c=114=static void __init i2c_quirk(void)\n--\narch/arm/mach-mvebu/board-v7.c-129-\narch/arm/mach-mvebu/board-v7.c:130:\t\tnew_compat = kzalloc_obj(*new_compat);\narch/arm/mach-mvebu/board-v7.c-131-\n--\narch/arm/mach-mvebu/coherency.c=163=static void __init armada_375_380_coherency_init(struct device_node *np)\n--\narch/arm/mach-mvebu/coherency.c-187-\narch/arm/mach-mvebu/coherency.c:188:\t\tp = kzalloc_obj(*p);\narch/arm/mach-mvebu/coherency.c-189-\t\tp-\u003ename = kstrdup(\"arm,io-coherent\", GFP_KERNEL);\n--\narch/arm/mach-mvebu/mvebu-soc-id.c=148=static int __init mvebu_soc_device(void)\n--\narch/arm/mach-mvebu/mvebu-soc-id.c-156-\narch/arm/mach-mvebu/mvebu-soc-id.c:157:\tsoc_dev_attr = kzalloc_obj(*soc_dev_attr);\narch/arm/mach-mvebu/mvebu-soc-id.c-158-\tif (!soc_dev_attr)\n--\narch/arm/mach-mxs/mach-mxs.c=380=static void __init mxs_machine_init(void)\n--\narch/arm/mach-mxs/mach-mxs.c-389-\narch/arm/mach-mxs/mach-mxs.c:390:\tsoc_dev_attr = kzalloc_obj(*soc_dev_attr);\narch/arm/mach-mxs/mach-mxs.c-391-\tif (!soc_dev_attr)\n--\narch/arm/mach-omap1/dma.c=294=static int __init omap1_system_dma_init(void)\n--\narch/arm/mach-omap1/dma.c-321-\narch/arm/mach-omap1/dma.c:322:\td = kzalloc_obj(*d);\narch/arm/mach-omap1/dma.c-323-\tif (!d) {\n--\narch/arm/mach-omap1/mcbsp.c=292=static void omap_mcbsp_register_board_cfg(struct resource *res, int res_count,\n--\narch/arm/mach-omap1/mcbsp.c-296-\narch/arm/mach-omap1/mcbsp.c:297:\tomap_mcbsp_devices = kzalloc_objs(struct platform_device *, size);\narch/arm/mach-omap1/mcbsp.c-298-\tif (!omap_mcbsp_devices) {\n--\narch/arm/mach-omap1/timer.c=51=static int __init omap1_dm_timer_init(void)\n--\narch/arm/mach-omap1/timer.c-127-\narch/arm/mach-omap1/timer.c:128:\t\tpdata = kzalloc_obj(*pdata);\narch/arm/mach-omap1/timer.c-129-\t\tif (!pdata) {\n--\narch/arm/mach-omap2/clkt2xxx_virt_prcm_set.c=230=void omap2xxx_clkt_vps_init(void)\n--\narch/arm/mach-omap2/clkt2xxx_virt_prcm_set.c-239-\narch/arm/mach-omap2/clkt2xxx_virt_prcm_set.c:240:\thw = kzalloc_obj(*hw);\narch/arm/mach-omap2/clkt2xxx_virt_prcm_set.c-241-\tif (!hw)\n--\narch/arm/mach-omap2/id.c=786=void __init omap_soc_device_init(void)\n--\narch/arm/mach-omap2/id.c-790-\narch/arm/mach-omap2/id.c:791:\tsoc_dev_attr = kzalloc_obj(*soc_dev_attr);\narch/arm/mach-omap2/id.c-792-\tif (!soc_dev_attr)\n--\narch/arm/mach-omap2/omap_device.c=131=static int omap_device_build_from_dt(struct platform_device *pdev)\n--\narch/arm/mach-omap2/omap_device.c-158-\narch/arm/mach-omap2/omap_device.c:159:\thwmods = kzalloc_objs(struct omap_hwmod *, oh_cnt);\narch/arm/mach-omap2/omap_device.c-160-\tif (!hwmods) {\n--\narch/arm/mach-omap2/omap_hwmod.c=3389=static int omap_hwmod_allocate_module(struct device *dev, struct omap_hwmod *oh,\n--\narch/arm/mach-omap2/omap_hwmod.c-3402-\narch/arm/mach-omap2/omap_hwmod.c:3403:\tsysc = kzalloc_obj(*sysc);\narch/arm/mach-omap2/omap_hwmod.c-3404-\tif (!sysc)\n--\narch/arm/mach-omap2/omap_hwmod.c-3432-\tif (list_empty(\u0026oh-\u003eslave_ports)) {\narch/arm/mach-omap2/omap_hwmod.c:3433:\t\toi = kzalloc_obj(*oi);\narch/arm/mach-omap2/omap_hwmod.c-3434-\t\tif (!oi)\n--\narch/arm/mach-omap2/omap_hwmod.c=3521=int omap_hwmod_init_module(struct device *dev,\n--\narch/arm/mach-omap2/omap_hwmod.c-3535-\tif (!oh) {\narch/arm/mach-omap2/omap_hwmod.c:3536:\t\toh = kzalloc_obj(*oh);\narch/arm/mach-omap2/omap_hwmod.c-3537-\t\tif (!oh)\n--\narch/arm/mach-omap2/omap_hwmod.c-3546-\narch/arm/mach-omap2/omap_hwmod.c:3547:\t\toh-\u003eclass = kzalloc_obj(*oh-\u003eclass);\narch/arm/mach-omap2/omap_hwmod.c-3548-\t\tif (!oh-\u003eclass) {\n--\narch/arm/mach-omap2/pm33xx-core.c=379=static int __init amx3_idle_init(struct device_node *cpu_node, int cpu)\n--\narch/arm/mach-omap2/pm33xx-core.c-412-\narch/arm/mach-omap2/pm33xx-core.c:413:\tidle_states = kzalloc_objs(*idle_states, state_count);\narch/arm/mach-omap2/pm33xx-core.c-414-\tif (!idle_states)\n--\narch/arm/mach-omap2/sr_device.c=30=static void __init sr_set_nvalues(struct omap_volt_data *volt_data,\n--\narch/arm/mach-omap2/sr_device.c-41-\narch/arm/mach-omap2/sr_device.c:42:\tnvalue_table = kzalloc_objs(*nvalue_table, count);\narch/arm/mach-omap2/sr_device.c-43-\tif (!nvalue_table)\n--\narch/arm/mach-orion5x/pci.c=139=static int __init pcie_setup(struct pci_sys_data *sys)\n--\narch/arm/mach-orion5x/pci.c-171-\t */\narch/arm/mach-orion5x/pci.c:172:\tres = kzalloc_obj(struct resource);\narch/arm/mach-orion5x/pci.c-173-\tif (!res)\n--\narch/arm/mach-orion5x/pci.c=466=static int __init pci_setup(struct pci_sys_data *sys)\n--\narch/arm/mach-orion5x/pci.c-492-\t */\narch/arm/mach-orion5x/pci.c:493:\tres = kzalloc_obj(struct resource);\narch/arm/mach-orion5x/pci.c-494-\tif (!res)\n--\narch/arm/mach-rpc/ecard.c=689=static struct expansion_card *__init ecard_alloc_card(int type, int slot)\n--\narch/arm/mach-rpc/ecard.c-694-\narch/arm/mach-rpc/ecard.c:695:\tec = kzalloc_obj(ecard_t);\narch/arm/mach-rpc/ecard.c-696-\tif (!ec) {\n--\narch/arm/mach-sa1100/clock.c=93=int __init sa11xx_clk_init(void)\n--\narch/arm/mach-sa1100/clock.c-109-\narch/arm/mach-sa1100/clock.c:110:\thw = kzalloc_obj(*hw);\narch/arm/mach-sa1100/clock.c-111-\tif (!hw)\n--\narch/arm/mach-sa1100/clock.c-131-\narch/arm/mach-sa1100/clock.c:132:\thw = kzalloc_obj(*hw);\narch/arm/mach-sa1100/clock.c-133-\tif (!hw)\n--\narch/arm/mach-sa1100/generic.c=317=int __init sa11x0_register_fixed_regulator(int n,\n--\narch/arm/mach-sa1100/generic.c-323-\narch/arm/mach-sa1100/generic.c:324:\tcfg-\u003einit_data = id = kzalloc_obj(*cfg-\u003einit_data);\narch/arm/mach-sa1100/generic.c-325-\tif (!cfg-\u003einit_data)\n--\narch/arm/mach-sa1100/neponset.c=225=static int neponset_probe(struct platform_device *dev)\n--\narch/arm/mach-sa1100/neponset.c-278-\narch/arm/mach-sa1100/neponset.c:279:\td = kzalloc_obj(*d);\narch/arm/mach-sa1100/neponset.c-280-\tif (!d) {\n--\narch/arm/mach-shmobile/regulator-quirk-rcar-gen2.c=141=static int __init rcar_gen2_regulator_quirk(void)\n--\narch/arm/mach-shmobile/regulator-quirk-rcar-gen2.c-166-\narch/arm/mach-shmobile/regulator-quirk-rcar-gen2.c:167:\t\tquirk = kzalloc_obj(*quirk);\narch/arm/mach-shmobile/regulator-quirk-rcar-gen2.c-168-\t\tif (!quirk) {\n--\narch/arm/mach-versatile/spc.c=393=static int ve_spc_populate_opps(uint32_t cluster)\n--\narch/arm/mach-versatile/spc.c-397-\narch/arm/mach-versatile/spc.c:398:\topps = kzalloc_objs(*opps, MAX_OPPS);\narch/arm/mach-versatile/spc.c-399-\tif (!opps)\n--\narch/arm/mach-versatile/spc.c=442=int __init ve_spc_init(void __iomem *baseaddr, u32 a15_clusid, int irq)\n--\narch/arm/mach-versatile/spc.c-444-\tint ret;\narch/arm/mach-versatile/spc.c:445:\tinfo = kzalloc_obj(*info);\narch/arm/mach-versatile/spc.c-446-\tif (!info)\n--\narch/arm/mach-versatile/spc.c=523=static struct clk *ve_spc_clk_register(struct device *cpu_dev)\n--\narch/arm/mach-versatile/spc.c-527-\narch/arm/mach-versatile/spc.c:528:\tspc = kzalloc_obj(*spc);\narch/arm/mach-versatile/spc.c-529-\tif (!spc)\n--\narch/arm/mach-versatile/versatile.c=123=static void __init versatile_dt_pci_init(void)\n--\narch/arm/mach-versatile/versatile.c-144-\narch/arm/mach-versatile/versatile.c:145:\tnewprop = kzalloc_obj(*newprop);\narch/arm/mach-versatile/versatile.c-146-\tif (!newprop)\n--\narch/arm/mach-zynq/common.c=105=static void __init zynq_init_machine(void)\n--\narch/arm/mach-zynq/common.c-110-\narch/arm/mach-zynq/common.c:111:\tsoc_dev_attr = kzalloc_obj(*soc_dev_attr);\narch/arm/mach-zynq/common.c-112-\tif (!soc_dev_attr)\n--\narch/arm/mm/cache-l2x0-pmu.c=503=static __init int l2x0_pmu_init(void)\n--\narch/arm/mm/cache-l2x0-pmu.c-509-\narch/arm/mm/cache-l2x0-pmu.c:510:\tl2x0_pmu = kzalloc_obj(*l2x0_pmu);\narch/arm/mm/cache-l2x0-pmu.c-511-\tif (!l2x0_pmu) {\n--\narch/arm/mm/cache-uniphier.c=315=static int __init __uniphier_cache_init(struct device_node *np,\n--\narch/arm/mm/cache-uniphier.c-344-\narch/arm/mm/cache-uniphier.c:345:\tdata = kzalloc_obj(*data);\narch/arm/mm/cache-uniphier.c-346-\tif (!data)\n--\narch/arm/mm/dma-mapping.c=533=static void *__dma_alloc(struct device *dev, size_t size, dma_addr_t *handle,\n--\narch/arm/mm/dma-mapping.c-560-\narch/arm/mm/dma-mapping.c:561:\tbuf = kzalloc_obj(*buf,\narch/arm/mm/dma-mapping.c-562-\t\t\t  gfp \u0026 ~(__GFP_DMA | __GFP_DMA32 | __GFP_HIGHMEM));\n--\narch/arm/mm/dma-mapping.c=1487=arm_iommu_create_mapping(struct device *dev, dma_addr_t base, u64 size)\n--\narch/arm/mm/dma-mapping.c-1506-\narch/arm/mm/dma-mapping.c:1507:\tmapping = kzalloc_obj(struct dma_iommu_mapping);\narch/arm/mm/dma-mapping.c-1508-\tif (!mapping)\n--\narch/arm/xen/enlighten.c=316=int __init arch_xen_unpopulated_init(struct resource **res)\n--\narch/arm/xen/enlighten.c-343-\narch/arm/xen/enlighten.c:344:\tregs = kzalloc_objs(*regs, nr_reg);\narch/arm/xen/enlighten.c-345-\tif (!regs) {\n--\narch/arm/xen/enlighten.c-387-\narch/arm/xen/enlighten.c:388:\t\ttmp_res = kzalloc_obj(*tmp_res);\narch/arm/xen/enlighten.c-389-\t\tif (!tmp_res) {\n--\narch/arm/xen/p2m.c=150=bool __set_phys_to_machine_multi(unsigned long pfn,\n--\narch/arm/xen/p2m.c-178-\narch/arm/xen/p2m.c:179:\tp2m_entry = kzalloc_obj(*p2m_entry, GFP_NOWAIT);\narch/arm/xen/p2m.c-180-\tif (!p2m_entry)\n--\narch/arm64/kernel/vdso.c=68=static int __init __vdso_init(enum vdso_abi abi)\n--\narch/arm64/kernel/vdso.c-83-\narch/arm64/kernel/vdso.c:84:\tvdso_pagelist = kzalloc_objs(struct page *, vdso_info[abi].vdso_pages);\narch/arm64/kernel/vdso.c-85-\tif (vdso_pagelist == NULL)\n--\narch/arm64/kvm/mmu.c=480=static int share_pfn_hyp(u64 pfn)\n--\narch/arm64/kvm/mmu.c-492-\narch/arm64/kvm/mmu.c:493:\tthis = kzalloc_obj(*this);\narch/arm64/kvm/mmu.c-494-\tif (!this) {\n--\narch/arm64/kvm/mmu.c=981=int kvm_init_stage2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu, unsigned long type)\n--\narch/arm64/kvm/mmu.c-1007-\narch/arm64/kvm/mmu.c:1008:\tpgt = kzalloc_obj(*pgt, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/mmu.c-1009-\tif (!pgt)\n--\narch/arm64/kvm/mmu.c=1180=int topup_hyp_memcache(struct kvm_hyp_memcache *mc, unsigned long min_pages)\n--\narch/arm64/kvm/mmu.c-1185-\tif (!mc-\u003emapping) {\narch/arm64/kvm/mmu.c:1186:\t\tmc-\u003emapping = kzalloc_obj(struct pkvm_mapping,\narch/arm64/kvm/mmu.c-1187-\t\t\t\t\t  GFP_KERNEL_ACCOUNT);\n--\narch/arm64/kvm/mmu.c=2510=int __init kvm_mmu_init(u32 hyp_va_bits)\n--\narch/arm64/kvm/mmu.c-2543-\narch/arm64/kvm/mmu.c:2544:\thyp_pgtable = kzalloc_obj(*hyp_pgtable);\narch/arm64/kvm/mmu.c-2545-\tif (!hyp_pgtable) {\n--\narch/arm64/kvm/nested.c=1328=int kvm_vcpu_allocate_vncr_tlb(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/nested.c-1333-\tif (!vcpu-\u003earch.vncr_tlb) {\narch/arm64/kvm/nested.c:1334:\t\tstruct vncr_tlb *vt = kzalloc_obj(*vcpu-\u003earch.vncr_tlb,\narch/arm64/kvm/nested.c-1335-\t\t\t\t\t\t  GFP_KERNEL_ACCOUNT);\n--\narch/arm64/kvm/nested.c=1793=int kvm_init_nv_sysregs(struct kvm_vcpu *vcpu)\n--\narch/arm64/kvm/nested.c-1802-\narch/arm64/kvm/nested.c:1803:\tkvm-\u003earch.sysreg_masks = kzalloc_obj(*(kvm-\u003earch.sysreg_masks),\narch/arm64/kvm/nested.c-1804-\t\t\t\t\t     GFP_KERNEL_ACCOUNT);\n--\narch/arm64/kvm/ptdump.c=116=static struct kvm_ptdump_guest_state *kvm_ptdump_parser_create(struct kvm_s2_mmu *mmu)\n--\narch/arm64/kvm/ptdump.c-121-\narch/arm64/kvm/ptdump.c:122:\tst = kzalloc_obj(struct kvm_ptdump_guest_state, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/ptdump.c-123-\tif (!st)\n--\narch/arm64/kvm/vgic/vgic-init.c=207=static int kvm_vgic_dist_init(struct kvm *kvm, unsigned int nr_spis)\n--\narch/arm64/kvm/vgic/vgic-init.c-213-\tdist-\u003eactive_spis = (atomic_t)ATOMIC_INIT(0);\narch/arm64/kvm/vgic/vgic-init.c:214:\tdist-\u003espis = kzalloc_objs(struct vgic_irq, nr_spis, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-init.c-215-\tif (!dist-\u003espis)\n--\narch/arm64/kvm/vgic/vgic-init.c=316=static int vgic_allocate_private_irqs_locked(struct kvm_vcpu *vcpu, u32 type)\n--\narch/arm64/kvm/vgic/vgic-init.c-331-\narch/arm64/kvm/vgic/vgic-init.c:332:\tvgic_cpu-\u003eprivate_irqs = kzalloc_objs(struct vgic_irq,\narch/arm64/kvm/vgic/vgic-init.c-333-\t\t\t\t\t      num_private_irqs,\n--\narch/arm64/kvm/vgic/vgic-irqfd.c=142=int kvm_vgic_setup_default_irq_routing(struct kvm *kvm)\n--\narch/arm64/kvm/vgic/vgic-irqfd.c-148-\narch/arm64/kvm/vgic/vgic-irqfd.c:149:\tentries = kzalloc_objs(*entries, nr, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-irqfd.c-150-\tif (!entries)\n--\narch/arm64/kvm/vgic/vgic-its.c=76=static struct vgic_irq *vgic_add_lpi(struct kvm *kvm, u32 intid,\n--\narch/arm64/kvm/vgic/vgic-its.c-87-\narch/arm64/kvm/vgic/vgic-its.c:88:\tirq = kzalloc_obj(struct vgic_irq, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-its.c-89-\tif (!irq)\n--\narch/arm64/kvm/vgic/vgic-its.c=971=static int vgic_its_alloc_collection(struct vgic_its *its,\n--\narch/arm64/kvm/vgic/vgic-its.c-976-\narch/arm64/kvm/vgic/vgic-its.c:977:\tcollection = kzalloc_obj(*collection, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-its.c-978-\tif (!collection)\n--\narch/arm64/kvm/vgic/vgic-its.c=1015=static struct its_ite *vgic_its_alloc_ite(struct its_device *device,\n--\narch/arm64/kvm/vgic/vgic-its.c-1020-\narch/arm64/kvm/vgic/vgic-its.c:1021:\tite = kzalloc_obj(*ite, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-its.c-1022-\tif (!ite)\n--\narch/arm64/kvm/vgic/vgic-its.c=1142=static struct its_device *vgic_its_alloc_device(struct vgic_its *its,\n--\narch/arm64/kvm/vgic/vgic-its.c-1147-\narch/arm64/kvm/vgic/vgic-its.c:1148:\tdevice = kzalloc_obj(*device, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-its.c-1149-\tif (!device)\n--\narch/arm64/kvm/vgic/vgic-its.c=1855=static int vgic_its_create(struct kvm_device *dev, u32 type)\n--\narch/arm64/kvm/vgic/vgic-its.c-1862-\narch/arm64/kvm/vgic/vgic-its.c:1863:\tits = kzalloc_obj(struct vgic_its, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-its.c-1864-\tif (!its)\n--\narch/arm64/kvm/vgic/vgic-mmio-v3.c=886=static int vgic_v3_alloc_redist_region(struct kvm *kvm, uint32_t index,\n--\narch/arm64/kvm/vgic/vgic-mmio-v3.c-931-\narch/arm64/kvm/vgic/vgic-mmio-v3.c:932:\trdreg = kzalloc_obj(*rdreg, GFP_KERNEL_ACCOUNT);\narch/arm64/kvm/vgic/vgic-mmio-v3.c-933-\tif (!rdreg)\n--\narch/arm64/kvm/vgic/vgic-v4.c=242=int vgic_v4_init(struct kvm *kvm)\n--\narch/arm64/kvm/vgic/vgic-v4.c-258-\narch/arm64/kvm/vgic/vgic-v4.c:259:\tdist-\u003eits_vm.vpes = kzalloc_objs(*dist-\u003eits_vm.vpes, nr_vcpus,\narch/arm64/kvm/vgic/vgic-v4.c-260-\t\t\t\t\t GFP_KERNEL_ACCOUNT);\n--\narch/arm64/net/bpf_jit_comp.c=2156=struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_prog *prog)\n--\narch/arm64/net/bpf_jit_comp.c-2177-\tif (!jit_data) {\narch/arm64/net/bpf_jit_comp.c:2178:\t\tjit_data = kzalloc_obj(*jit_data);\narch/arm64/net/bpf_jit_comp.c-2179-\t\tif (!jit_data)\n--\narch/csky/kernel/vdso.c=17=static int __init vdso_init(void)\n--\narch/csky/kernel/vdso.c-22-\tvdso_pagelist =\narch/csky/kernel/vdso.c:23:\t\tkzalloc_objs(struct page *, vdso_pages);\narch/csky/kernel/vdso.c-24-\tif (unlikely(vdso_pagelist == NULL)) {\n--\narch/loongarch/kernel/setup.c=466=static int __init add_legacy_isa_io(struct fwnode_handle *fwnode,\n--\narch/loongarch/kernel/setup.c-472-\narch/loongarch/kernel/setup.c:473:\trange = kzalloc_obj(*range, GFP_ATOMIC);\narch/loongarch/kernel/setup.c-474-\tif (!range)\n--\narch/loongarch/kernel/vdso.c=45=static int __init init_vdso(void)\n--\narch/loongarch/kernel/vdso.c-55-\tvdso_info.code_mapping.pages =\narch/loongarch/kernel/vdso.c:56:\t\tkzalloc_objs(struct page *, vdso_info.size / PAGE_SIZE);\narch/loongarch/kernel/vdso.c-57-\n--\narch/loongarch/kvm/intc/eiointc.c=614=static int kvm_eiointc_create(struct kvm_device *dev, u32 type)\n--\narch/loongarch/kvm/intc/eiointc.c-624-\n\n"
}

41/3 2026/08/27 17:05 llm kmsan-evaluator
1m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 50991
							output: 467
							thoughts: 9412
						

Thoughts:
**KMSAN Assessment: No Uninitialized Memory Leaks Detected**

Okay, so I've been digging through this code, focusing on potential uninitialized memory issues, especially with all these new crypto structures and the interactions between the host and guest, specifically the `blk-crypto-proxy` and virtio-blk components.

First, I confirmed that `kzalloc` is the preferred way to allocate memory for these new structs, so all fields are initialized to zero. I looked at `kzalloc_obj` being used for the `bcp_ctx` and similar allocators to confirm. Now, I examined the `bcp_ioctl_submit_io_by_vslot` function, tracing the I/O flow. It uses `copy_from_user` to get data, importantly, it uses direct I/O. The block device writes directly to user pages, which are pinned via `bio_iov_iter_get_pages`. This is critical: the kernel *doesn't* allocate intermediate buffers; thus, no uninitialized memory in intermediate buffers can be exposed, even if the block device doesn't write fully.

Then I considered the crypto side of things with `virtblk_setup_cmd`, which is where things got interesting. I noticed that the `req->crypt_ctx->bc_key` pointer could be NULL, and if it is, dereferencing `bc_key` is a potential NULL pointer dereference. But, digging deeper, if `bc_key` is null, it's a slot-based crypto I/O. The `blk_crypto_rq_get_keyslot` would not set the `crypt_keyslot` and the `is_crypto_request` function will make sure that the code path that uses `bc_key` is not executed. So the NULL pointer dereference is not a real issue in this context. And I checked that `crypt_keyslot` and `bc_key` can be NULL at the same time and how it is handled. I traced calls such as `blk_crypto_rq_get_keyslot` and `is_crypto_request` to verify the handling of `bc_key` being NULL. If `bc_key` is NULL, then the crypto path is NOT followed. The `ufshcd_prepare_lrbp_crypto` is an excellent example of how the slot can be taken into consideration, even if `crypt_keyslot` is NULL.

I went on to check the structures passed to/from user space and the virtio device. Specifically, the union `out_hdr` (containing a base and a `crypto_append`) and the `virtio_blk_crypto_msg`. And yes, I verified no padding, and all the fields are initialized before being sent to the virtio device by the function `virtblk_setup_cmd`.

I made sure I understood that `virtio_blk.c` handles the guest filesystem encryption (fscrypt) side, while `blk-crypto-proxy.c` handles host-side block device I/O. So they use different keying mechanisms and the `bc_key` will always be available in the guest. The structures from `virtblk_get_crypto_modes`, `bcp_ioctl_get_crypto_caps`, and `bcp_ioctl_submit_io_by_vslot` all have no uninitialized memory leaks.

I carefully checked the alignment of structures to make sure there are no padding bytes that might lead to uninitialized memory. `struct bcp_get_crypto_caps_arg`, `struct bcp_bind_context_arg`, `struct bcp_submit_io_by_vslot_arg`, `struct virtio_blk_crypto_msg` and `struct virtio_blk_outhdr` have all been double checked for this. They are all correctly aligned, and I don't see any possibility of uninitialized memory.

Finally, I concluded that, given the use of `kzalloc`, direct I/O, and careful initialization of all data structures, there are no uninitialized memory issues. Standard bug detectors like KASAN are enough to catch other errors like out-of-bounds reads and use-after-free conditions. Therefore, there's no need for KMSAN for this specific patch series.



42/3 2026/08/27 17:06 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch series introduces a new proxy driver `blk-crypto-proxy` for inline encryption virtualization, a virtio-blk extension for inline encryption, and a Qualcomm-specific crypto virtualization driver.\n\nA thorough review of the data structures exchanged with user space and hardware reveals no uninitialized memory risks:\n1. The UAPI structures (`struct bcp_get_crypto_caps_arg`, `struct bcp_bind_context_arg`, and `struct bcp_submit_io_by_vslot_arg`) are carefully designed with explicit `__aligned_u64` fields to ensure identical layout between 32-bit and 64-bit architectures and to avoid any implicit compiler padding. All fields are fully initialized before being copied to user space.\n2. The virtio structures (`struct virtio_blk_crypto_msg` and the extended `virtio_blk_outhdr`) have no padding and are fully explicitly initialized in `virtblk_setup_cmd` before being sent to the virtio device.\n3. The `virtblk_cmodes` buffer used in `virtblk_get_crypto_modes` is allocated with `kzalloc` before being mapped to the device, preventing any uninitialized memory from being sent or read.\n4. Internal structures like `struct bio_crypt_ctx` and `struct blk_crypto_slot` are fully initialized when allocated (e.g., via `bio_crypt_set_ctx_by_slot`).\n5. The I/O data itself is handled via direct I/O (`bio_iov_iter_get_pages`), which pins user pages directly for DMA, avoiding intermediate kernel buffers that could leak uninitialized memory.\n\nSince all structures are fully initialized and contain no padding, there is no risk of uninitialized memory being used in control flow or leaked to user space/hardware. Any potential bugs introduced by these changes (e.g., out-of-bounds accesses, use-after-free, logic errors) would be caught by standard KASAN and other bug detectors. A dedicated KMSAN session is not justified.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch series introduces a new proxy driver `blk-crypto-proxy` for inline encryption virtualization, a virtio-blk extension for inline encryption, and a Qualcomm-specific crypto virtualization driver.\n\nA thorough review of the data structures exchanged with user space and hardware reveals no uninitialized memory risks:\n1. The UAPI structures (`struct bcp_get_crypto_caps_arg`, `struct bcp_bind_context_arg`, and `struct bcp_submit_io_by_vslot_arg`) are carefully designed with explicit `__aligned_u64` fields to ensure identical layout between 32-bit and 64-bit architectures and to avoid any implicit compiler padding. All fields are fully initialized before being copied to user space.\n2. The virtio structures (`struct virtio_blk_crypto_msg` and the extended `virtio_blk_outhdr`) have no padding and are fully explicitly initialized in `virtblk_setup_cmd` before being sent to the virtio device.\n3. The `virtblk_cmodes` buffer used in `virtblk_get_crypto_modes` is allocated with `kzalloc` before being mapped to the device, preventing any uninitialized memory from being sent or read.\n4. Internal structures like `struct bio_crypt_ctx` and `struct blk_crypto_slot` are fully initialized when allocated (e.g., via `bio_crypt_set_ctx_by_slot`).\n5. The I/O data itself is handled via direct I/O (`bio_iov_iter_get_pages`), which pins user pages directly for DMA, avoiding intermediate kernel buffers that could leak uninitialized memory.\n\nSince all structures are fully initialized and contain no padding, there is no risk of uninitialized memory being used in control flow or leaked to user space/hardware. Any potential bugs introduced by these changes (e.g., out-of-bounds accesses, use-after-free, logic errors) would be caught by standard KASAN and other bug detectors. A dedicated KMSAN session is not justified.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)