devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests
@ 2026-08-27 16:07 Linlin Zhang
  2026-08-27 16:07 ` [PATCH v1 01/11] virtio_blk: add inline encryption support Linlin Zhang
                   ` (11 more replies)
  0 siblings, 12 replies; 24+ messages in thread
From: Linlin Zhang @ 2026-08-27 16:07 UTC (permalink / raw)
  To: ebiggers, axboe, mst, jasowangio, James.Bottomley,
	martin.petersen, robh, krzk+dt, conor+dt, linux-block,
	linux-crypto, linux-scsi, virtualization, devicetree,
	linux-arm-msm
  Cc: neeraj.soni, gaurav.kashyap, mani, andersson, konradybcio,
	bvanassche, alim.akhtar, avri.altman, stefanha, pbonzini,
	eperezma, xuanzhuo, linux-kernel

From: linlzhan <linlin.zhang@oss.qualcomm.com>

Current virtio-blk does not provide a mechanism for a guest to
program hardware keys or submit encrypted I/O using pre-programmed
keyslots.  It drops the crypto context when issuing a bio request
to the virtio-blk queue, preventing inline-encryption-based FBE
on virtio block devices.

This series enables File-Based Encryption in guest VMs on Qualcomm
GVM platforms where the ICE inline encryption hardware is shared
between the host and guests.  In this environment the guest kernel
has no access to the ICE hardware directly; it supplies a virtual
keyslot index and data unit number with each encrypted I/O request
via VIRTIO_BLK_F_INLINE_ENCRYPTION, and the host must translate the
virtual slot to a physical ICE keyslot and submit the bio — without
transferring raw key material across the VM boundary.

             +----------------------------------------------------------------+   
             |                                                                |   
             |               LA GVM                                           |   
             |                                                                |   
             |                                                       |        |   
             |                                                       |        |   
             |                                                       |        |   
             |                                                +------v------+ |   
             |                               +----y-----------+   blk-mq    | |   
             |                               |                +-------+-----+ |   
             |                               |                        |       |   
             |                               |                        |       |   
             |                    +----------v-----------+            |       |   
             |                    |blk-crypto-profile    +------------v       |   
             |                    +----------------------+            |       |   
             |                                                        |       |   
             |                                                        |       |   
             |            Control path:                               |       |   
             |            generate/import/prepare key                 |       |   
             |            program/evict/derive_sw_secret key          |       |   
+----------+ |                                                        |       |   
|          | |+-------------+     +-----------------------+     +-----v-----+ |   
|Trust Zone| || crypto-virt <-----+ virtio-blk-crypto-ext <-----+virtio-blk | |   
|          | |+------+------+     +-----------------------+     +-----+-----+ |   
|          | |       |                                         Data path: I/O |   
+-------^--+ +-------+------------------------------------------------+-------+   
        |         SMC|call                                (virt_slot, DUN, DUSize)
        |            |                                    appended in virtblk_req 
    +---+------------v------------------------------------------------v----------+
    |  +--------------+                             +--------------------------+ |
    |  | SMC trap     |                  Hypervisor | MMIO trap/VIRQ injection | |
    +--+--------------+-----------------------------+--------------------------+ +

    +--------------------------------------------------------------------------------------------+
    |                                                                    PVM                     |
    |                                                                                            |
    | Crypto IO    +--------------+                                                              |
  +-+-------------->  QEMU/Crosvm +-------+                                                      |
  | |              +--------------+  IO data                                                     |
  | |                             virt_slot, DUN, DUSize                                         |
  | |                                     |                                                      |
  | |+----------------+       +--------- -v-----+                                                |
  | ||  blk-crypto    <-------+ blk-crypto-proxy+-------------+----------------------+           |
  | |+----------------+       +-----------+-----+             |                      |           |
  | |                             slot path based             |                      |           |
  | |                               bio_crypt_ctx             |                      |           |
  | |+-----------------------+       +--- v----+              |                      |           |
  | || blk-crypto-profile    <---x---+  blk-mq |      bcp_hypervisor_ops             |           |
  | |+-----------------------+       +----+----+              |                      |           |
  | |                                     |                   |             bcp_slot_virt_ops    |
  | |                         +-----------v-----+             |                      |           |
  | |                         |         SCSI    |             |                      |           |
  | |                         +-----------+-----+             |                      |           |
  | |                                     |                   |                      |           |
  | |                 crypto msg in UTRD  |                   |                      |           |
  | |+----------------------+        +----v-----+  +----------v-- ---------+ +-------v--- ------+|
  | ||    ufs crypto        <--------+ ufs-core |  |blk-crypto-hyp-backend | | keyslot-partition||
  | |+----------------------+        +-----+----+  +-----------------------+ +------------------+|
  | +--------------------------------------+-----------------------------------------------------+
  |                                        |                                                      
  |                                        |                                                      
+-----------------------------------+   +--v---+-----------+                                      
| +------------------------+        |   |  ICE |    UFS    |                                      
||MMIO trap/VIRQ injection |  HYP   |   +------+-----------+                                      
+ +------------------------+--------+                                                             

Patches 1-3 land in the guest kernel.  Patch 1 negotiates
VIRTIO_BLK_F_INLINE_ENCRYPTION and wires it into blk-crypto.
Patches 2-3 add the Qualcomm GVM-side crypto backend, which routes
key programming and software-secret derivation through SCM calls to
TrustZone.  These patches are sent for review; the virtio-blk inline
encryption protocol is also under review (see
https://lore.kernel.org/all/20260814142306.3934029-1-linlin.zhang@oss.qualcomm.com/).
These patches and the virtio-spec depend on each other.  They must be
kept consistent for upstream merging.

Patch 4 adds the dt-binding for the Qualcomm crypto-virt node used by
the guest-side backend.

Patch 5 introduces a "slot path" in blk-crypto that lets a bio carry
a pre-programmed physical ICE keyslot index in bc_slot rather than a
blk_crypto_key pointer (bc_key == NULL).  This is needed on the host
side where the hypervisor has already programmed the keyslot; the host
kernel has no access to the raw key.  Patch 6 extends
ufshcd_prepare_lrbp_crypto() to handle this path.  Patch 7 moves
bio_crypt_dun_increment() to the public header so it can be called
from drivers/block/.

Patch 8 adds /dev/blk-crypto-proxy, a misc character device for
userspace virtio-blk backends.  The interface is three ioctls:
BCP_BIND_CONTEXT binds a host block device fd and a hypervisor VM fd;
BCP_GET_CRYPTO_CAPS queries the device's inline-crypto capabilities
and the VM's ICE keyslot allocation; BCP_SUBMIT_IO_BY_VSLOT resolves
a guest virtual slot to a physical ICE keyslot and submits the
inline-encrypted bio synchronously.  The driver is
hypervisor-agnostic and storage-vendor-agnostic, using two pluggable
op-sets registered by platform drivers at runtime.

Patch 9 implements bcp_slot_virt_ops for Qualcomm platforms: it parses
a qcom,ice-keyslot-map device-tree node that maps each guest_id to a
contiguous physical keyslot range.  Patch 10 adds slot_offset to struct
blk_crypto_profile so that blk_crypto_keyslot_index() returns the
correct physical slot number when the host's ICE range does not start
at slot 0.  Patch 11 extends ufs-qcom to read the host's own slot
reservation from the same DT node and initialize the
blk_crypto_profile accordingly.

Patches 1-4 are technically ready for review.  However, since they
depend on the proposed virtio-blk inline encryption protocol, progress
on these patches is expected to follow consensus on the protocol design.
Feedback on the overall architecture is therefore particularly valuable,
as it will also help advance the associated virtio-spec work.

Patches 5-8 implement the core host-side infrastructure and are
believed ready for review.

Patches 9-11 do not depend on any hypervisor-specific code.  Of them,
patches 9 and 11 provide the Qualcomm platform implementation based
on a static device-tree keyslot mapping; this may be revised in a future
version to use a TZ SCM query interface.

The kernel-internal header declares bcp_hypervisor_ops, which
translates a hypervisor VM fd to an opaque guest_id.  No upstream
implementation is included in this series because the series was
validated on a downstream Qualcomm GVM platform using the Gunyah
hypervisor, which provides a stable per-VM identifier but is not
yet upstream.  KVM does not currently expose an externally-visible
per-VM identifier that a kernel module could use for this purpose.
Input from KVM maintainers on whether and how such an interface
could be added, or whether an alternative identity mechanism is
preferred, would be welcome.

Known limitations
-------
  - Only AES-256-XTS has been tested.
  - virtio_blk_crypto_msg.dun is a fixed __virtio64; the driver refuses
    to enable inline crypto if the device advertises max_dun_bytes > 8 to
    prevent silent IV truncation and reuse.
  - Inline encryption is mutually exclusive with VIRTIO_BLK_F_ZONED.
  - The qcom_ice_slots driver uses a
    global singleton and ignores the blk_crypto_profile argument to its
    callbacks, so multiple storage controllers sharing a single slot table
    are not yet supported.
  - BCP_SUBMIT_IO_BY_VSLOT submits each bio synchronously with
    submit_bio_wait(); concurrent in-flight bios from multiple threads
    sharing one fd are not supported.

Testing
-------
Compilation pass on Linux-next.
End-to-end FBE virtualization with wrapped key enabled was validated
on top of gunyah hypervisor.  wrapped_key_test is a local utility to
get wrapped key and ephemeral wrapped key via storage ioctl interfaces.
  - /data/wrapped_key_test /dev/block/userdata generate
  - /data/wrapped_key_test /dev/block/userdata prepare /data/lt_key.bin
  - /data/fscryptctl insert_wrapped_key < /data/eph_key.bin
  - /data/fscryptctl set_policy --identifier=20f553802e64e36b43469211266a5f1c /data/testing
  - echo "data" > /data/testing/file.txt
  - sync and reboot
  - /data/wrapped_key_test /dev/block/userdata prepare /data/lt_key.bin
  - /data/fscryptctl insert_wrapped_key < /data/eph_key_2.bin
  - /data/fscryptctl set_policy --identifier=d8ca51d6d2094b73b2dae5ee7e3a10b6 /data/testing
  - cat /data/testing/file.txt


linlzhan (11):
  virtio_blk: add inline encryption support
  soc: qcom: add crypto_virt backend for virtio-blk inline crypto
  soc: qcom: crypto_virt: add support for create, prepare and import
    keys
  dt-bindings: soc: qcom: add binding for qcom,crypto-virt
  blk-crypto: add slot-based inline encryption path
  scsi: ufs: core: add slot path to ufshcd_prepare_lrbp_crypto
  blk-crypto: move bio_crypt_dun_increment() to the public header
  block: add /dev/blk-crypto-proxy for host-side virtio-blk inline
    encryption
  soc: qcom: add ICE keyslot partitioning driver for guest VMs
  blk-crypto: add slot_offset to blk_crypto_profile
  scsi: ufs: ufs-qcom: support ICE keyslot partitioning for guest VMs

 .../bindings/soc/qcom/qcom,crypto-virt.yaml   |  39 +
 block/blk-crypto-internal.h                   |   5 +-
 block/blk-crypto-profile.c                    |   7 +-
 block/blk-crypto.c                            |  57 +-
 drivers/block/Kconfig                         |  28 +
 drivers/block/Makefile                        |   3 +
 drivers/block/blk-crypto-proxy.c              | 667 ++++++++++++++++++
 drivers/block/virtio_blk.c                    | 199 +++++-
 drivers/block/virtio_blk_crypto_ext.c         | 283 ++++++++
 drivers/soc/qcom/Kconfig                      |  30 +
 drivers/soc/qcom/Makefile                     |   2 +
 drivers/soc/qcom/crypto_virt.c                | 197 ++++++
 drivers/soc/qcom/qcom_ice_slots.c             | 232 ++++++
 drivers/ufs/core/ufshcd-crypto.h              |  14 +-
 drivers/ufs/host/ufs-qcom.c                   |  91 ++-
 include/linux/blk-crypto-profile.h            |   9 +
 include/linux/blk-crypto-proxy.h              | 100 +++
 include/linux/blk-crypto.h                    |  28 +
 include/linux/virtio_blk_crypto_ext.h         |  78 ++
 include/uapi/linux/blk-crypto-proxy.h         | 122 ++++
 include/uapi/linux/virtio_blk.h               |  62 ++
 21 files changed, 2224 insertions(+), 29 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,crypto-virt.yaml
 create mode 100644 drivers/block/blk-crypto-proxy.c
 create mode 100644 drivers/block/virtio_blk_crypto_ext.c
 create mode 100644 drivers/soc/qcom/crypto_virt.c
 create mode 100644 drivers/soc/qcom/qcom_ice_slots.c
 create mode 100644 include/linux/blk-crypto-proxy.h
 create mode 100644 include/linux/virtio_blk_crypto_ext.h
 create mode 100644 include/uapi/linux/blk-crypto-proxy.h

-- 
2.34.1


^ permalink raw reply	[flat|nested] 24+ messages in thread

* [PATCH v1 01/11] virtio_blk: add inline encryption support
  2026-08-27 16:07 [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Linlin Zhang
@ 2026-08-27 16:07 ` Linlin Zhang
  2026-08-27 16:23   ` sashiko-bot
  2026-08-27 16:07 ` [PATCH v1 02/11] soc: qcom: add crypto_virt backend for virtio-blk inline crypto Linlin Zhang
                   ` (10 subsequent siblings)
  11 siblings, 1 reply; 24+ messages in thread
From: Linlin Zhang @ 2026-08-27 16:07 UTC (permalink / raw)
  To: ebiggers, axboe, mst, jasowangio, James.Bottomley,
	martin.petersen, robh, krzk+dt, conor+dt, linux-block,
	linux-crypto, linux-scsi, virtualization, devicetree,
	linux-arm-msm
  Cc: neeraj.soni, gaurav.kashyap, mani, andersson, konradybcio,
	bvanassche, alim.akhtar, avri.altman, stefanha, pbonzini,
	eperezma, xuanzhuo, linux-kernel

From: linlzhan <linlzhan@qti.qualcomm.com>

Negotiate VIRTIO_BLK_F_INLINE_ENCRYPTION with the host and wire it into
the block layer's inline-crypto framework to enable inline encryption
on virtio block device.

When the feature is present, the driver reads crypto characteristics from
virtio config space (key-slot count, DUN size, supported key types) and
issues VIRTIO_BLK_T_GET_CRYPTO_MODES to discover supported cipher and
data-unit-size combinations.  Encrypted requests use new request types
VIRTIO_BLK_T_CRYPTO_IN/OUT, which append a virtio_blk_crypto_msg
(keyslot index, DUN, data-unit-size-bits) to the standard outhdr.

A new virtio block crypto extension driver (virtio_blk_crypto_ext),
owns the blk_crypto_profile singleton and the blk_crypto_ll_ops dispatch
table. Actual key operations are forwarded to a platform-specific
backend registered via virtblk_set_crypto_ops(); without one,
VIRTIO_BLK_F_INLINE_ENCRYPTION is still negotiated and the
profile is registered, but every keyslot operation returns -EOPNOTSUPP.

The shared profile is a singleton as per blk_crypto_profile is
corresponding to one ICE hardware: the first device to negotiate the
feature initializes it; subsequent devices reuse it only when their
negotiated capabilities (slot count, DUN size, key types) match exactly.

Signed-off-by: linlzhan <linlin.zhang@oss.qualcomm.com>
---
 drivers/block/Kconfig                 |  13 ++
 drivers/block/Makefile                |   2 +
 drivers/block/virtio_blk.c            | 199 ++++++++++++++++--
 drivers/block/virtio_blk_crypto_ext.c | 283 ++++++++++++++++++++++++++
 include/linux/virtio_blk_crypto_ext.h |  78 +++++++
 include/uapi/linux/virtio_blk.h       |  62 ++++++
 6 files changed, 623 insertions(+), 14 deletions(-)
 create mode 100644 drivers/block/virtio_blk_crypto_ext.c
 create mode 100644 include/linux/virtio_blk_crypto_ext.h

diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
index 858320b6ebb7..7790ee2c700c 100644
--- a/drivers/block/Kconfig
+++ b/drivers/block/Kconfig
@@ -312,6 +312,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 2d8096eb8cdf..079c910d5fc9 100644
--- a/drivers/block/Makefile
+++ b/drivers/block/Makefile
@@ -25,6 +25,8 @@ obj-$(CONFIG_SUNVDC)		+= sunvdc.o
 obj-$(CONFIG_BLK_DEV_NBD)	+= nbd.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/virtio_blk.c b/drivers/block/virtio_blk.c
index 32bf3ba07a9d..61a3967bb4df 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 000000000000..00b6d410d303
--- /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/include/linux/virtio_blk_crypto_ext.h b/include/linux/virtio_blk_crypto_ext.h
new file mode 100644
index 000000000000..3fe66029ac0d
--- /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/virtio_blk.h b/include/uapi/linux/virtio_blk.h
index 3744e4da1b2a..c1941f7fe019 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.
  */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v1 02/11] soc: qcom: add crypto_virt backend for virtio-blk inline crypto
  2026-08-27 16:07 [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Linlin Zhang
  2026-08-27 16:07 ` [PATCH v1 01/11] virtio_blk: add inline encryption support Linlin Zhang
@ 2026-08-27 16:07 ` Linlin Zhang
  2026-08-27 16:24   ` sashiko-bot
  2026-08-27 16:07 ` [PATCH v1 03/11] soc: qcom: crypto_virt: add support for create, prepare and import keys Linlin Zhang
                   ` (9 subsequent siblings)
  11 siblings, 1 reply; 24+ messages in thread
From: Linlin Zhang @ 2026-08-27 16:07 UTC (permalink / raw)
  To: ebiggers, axboe, mst, jasowangio, James.Bottomley,
	martin.petersen, robh, krzk+dt, conor+dt, linux-block,
	linux-crypto, linux-scsi, virtualization, devicetree,
	linux-arm-msm
  Cc: neeraj.soni, gaurav.kashyap, mani, andersson, konradybcio,
	bvanassche, alim.akhtar, avri.altman, stefanha, pbonzini,
	eperezma, xuanzhuo, linux-kernel

From: linlzhan <linlin.zhang@oss.qualcomm.com>

In a Qualcomm GVM environment the ICE hardware is controlled by the
host, GVM has no direct access to it. So, key operation in GVM is
done through SCM calls rather than direct register access. In this
way the access to ICE registers are offloaded to Trust Zone.

Add QCOM_CRYPTO_VIRT, which implements struct virtblk_crypto_variant_ops
for the virtio_blk_crypto_ext dispatch layer. It maps keyslot
program/evict to qcom_scm_ice_set_key() and
qcom_scm_ice_invalidate_key(), and software-secret derivation to
qcom_scm_derive_sw_secret().

Signed-off-by: linlzhan <linlin.zhang@oss.qualcomm.com>
---
 drivers/soc/qcom/Kconfig       | 12 +++++
 drivers/soc/qcom/Makefile      |  1 +
 drivers/soc/qcom/crypto_virt.c | 89 ++++++++++++++++++++++++++++++++++
 3 files changed, 102 insertions(+)
 create mode 100644 drivers/soc/qcom/crypto_virt.c

diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 2b524154d9fb..6c632d114d45 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -298,6 +298,18 @@ 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 798643be3590..6d4b7546d1fb 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -39,5 +39,6 @@ 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_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 000000000000..4ee2a36af6c1
--- /dev/null
+++ b/drivers/soc/qcom/crypto_virt.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/module.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 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 has been tested 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 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,
+};
+
+static int __init crypto_virt_init(void)
+{
+	virtblk_set_crypto_ops(&virtblk_crypto_qcom_vops);
+	return 0;
+}
+module_init(crypto_virt_init);
+
+#if IS_MODULE(CONFIG_QCOM_CRYPTO_VIRT)
+static void __exit crypto_virt_exit(void)
+{
+	virtblk_set_crypto_ops(NULL);
+}
+module_exit(crypto_virt_exit);
+#endif
+
+MODULE_DESCRIPTION("Qualcomm Technologies, Inc. Crypto Virt Driver");
+MODULE_LICENSE("GPL");
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v1 03/11] soc: qcom: crypto_virt: add support for create, prepare and import keys
  2026-08-27 16:07 [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Linlin Zhang
  2026-08-27 16:07 ` [PATCH v1 01/11] virtio_blk: add inline encryption support Linlin Zhang
  2026-08-27 16:07 ` [PATCH v1 02/11] soc: qcom: add crypto_virt backend for virtio-blk inline crypto Linlin Zhang
@ 2026-08-27 16:07 ` Linlin Zhang
  2026-08-27 16:19   ` sashiko-bot
  2026-08-27 16:07 ` [PATCH v1 04/11] dt-bindings: soc: qcom: add binding for qcom,crypto-virt Linlin Zhang
                   ` (8 subsequent siblings)
  11 siblings, 1 reply; 24+ messages in thread
From: Linlin Zhang @ 2026-08-27 16:07 UTC (permalink / raw)
  To: ebiggers, axboe, mst, jasowangio, James.Bottomley,
	martin.petersen, robh, krzk+dt, conor+dt, linux-block,
	linux-crypto, linux-scsi, virtualization, devicetree,
	linux-arm-msm
  Cc: neeraj.soni, gaurav.kashyap, mani, andersson, konradybcio,
	bvanassche, alim.akhtar, avri.altman, stefanha, pbonzini,
	eperezma, xuanzhuo, linux-kernel

From: linlzhan <linlin.zhang@oss.qualcomm.com>

The SCM interfaces used to generate, prepare and import hardware
wrapped keys require the exact wrapped key size as input. The size
is ICE hardware specific and cannot be derived by the guest.

Since the guest does not have direct access to the ICE hardware and
the information is not exposed through virtio, obtain the wrapped
key size from the "wrapped-key-size" DT property.

Convert the driver to a platform_driver matching
"qcom,crypto-virt" and initialize the wrapped-key operations during
probe. When a valid wrapped key size is provided, enable the SCM
based wrapped-key helpers:

  generate_key -> qcom_scm_generate_ice_key()
  prepare_key  -> qcom_scm_prepare_ice_key()
  import_key   -> qcom_scm_import_ice_key()

If the property is missing or invalid, the driver still probes
successfully. In that case, only wrapped-key generation,
preparation and import are unavailable, while key programming and
eviction continue to work for keys provisioned through other
mechanisms.

Signed-off-by: linlzhan <linlin.zhang@oss.qualcomm.com>
---
 drivers/soc/qcom/crypto_virt.c | 114 ++++++++++++++++++++++++++++++++-
 1 file changed, 111 insertions(+), 3 deletions(-)

diff --git a/drivers/soc/qcom/crypto_virt.c b/drivers/soc/qcom/crypto_virt.c
index 4ee2a36af6c1..93c7993fb4a5 100644
--- a/drivers/soc/qcom/crypto_virt.c
+++ b/drivers/soc/qcom/crypto_virt.c
@@ -1,11 +1,15 @@
 // 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)
 {
@@ -17,7 +21,7 @@ static int crypto_virt_program_key(const struct blk_crypto_key *key,
 		return -EINVAL;
 	}
 
-	/* Only AES-256-XTS has been tested so far. */
+	/* 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",
@@ -63,24 +67,128 @@ static int crypto_virt_derive_sw_secret_key(const u8 *eph_key, size_t eph_key_si
 	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 __init crypto_virt_init(void)
+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)
 {
-	virtblk_set_crypto_ops(NULL);
+	platform_driver_unregister(&crypto_virt_driver);
 }
 module_exit(crypto_virt_exit);
 #endif
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v1 04/11] dt-bindings: soc: qcom: add binding for qcom,crypto-virt
  2026-08-27 16:07 [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Linlin Zhang
                   ` (2 preceding siblings ...)
  2026-08-27 16:07 ` [PATCH v1 03/11] soc: qcom: crypto_virt: add support for create, prepare and import keys Linlin Zhang
@ 2026-08-27 16:07 ` Linlin Zhang
  2026-08-27 16:14   ` sashiko-bot
  2026-08-27 16:07 ` [PATCH v1 05/11] blk-crypto: add slot-based inline encryption path Linlin Zhang
                   ` (7 subsequent siblings)
  11 siblings, 1 reply; 24+ messages in thread
From: Linlin Zhang @ 2026-08-27 16:07 UTC (permalink / raw)
  To: ebiggers, axboe, mst, jasowangio, James.Bottomley,
	martin.petersen, robh, krzk+dt, conor+dt, linux-block,
	linux-crypto, linux-scsi, virtualization, devicetree,
	linux-arm-msm
  Cc: neeraj.soni, gaurav.kashyap, mani, andersson, konradybcio,
	bvanassche, alim.akhtar, avri.altman, stefanha, pbonzini,
	eperezma, xuanzhuo, linux-kernel

From: linlzhan <linlin.zhang@oss.qualcomm.com>

Document the device tree binding for the Qualcomm GVM crypto-virt node,
which provides the wrapped-key buffer size needed by the QCOM_CRYPTO_VIRT
driver to perform hardware-wrapped-key SCM operations backing virtio_blk's
inline crypto layer.

The host does not report this size over virtio, so it is supplied via DT
through the optional qcom,wrapped-key-size property.

Signed-off-by: linlzhan <linlin.zhang@oss.qualcomm.com>
---
 .../bindings/soc/qcom/qcom,crypto-virt.yaml   | 39 +++++++++++++++++++
 1 file changed, 39 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,crypto-virt.yaml

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 000000000000..5eb204f29762
--- /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>;
+    };
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v1 05/11] blk-crypto: add slot-based inline encryption path
  2026-08-27 16:07 [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Linlin Zhang
                   ` (3 preceding siblings ...)
  2026-08-27 16:07 ` [PATCH v1 04/11] dt-bindings: soc: qcom: add binding for qcom,crypto-virt Linlin Zhang
@ 2026-08-27 16:07 ` Linlin Zhang
  2026-08-27 16:26   ` sashiko-bot
  2026-08-27 16:07 ` [PATCH v1 06/11] scsi: ufs: core: add slot path to ufshcd_prepare_lrbp_crypto Linlin Zhang
                   ` (6 subsequent siblings)
  11 siblings, 1 reply; 24+ messages in thread
From: Linlin Zhang @ 2026-08-27 16:07 UTC (permalink / raw)
  To: ebiggers, axboe, mst, jasowangio, James.Bottomley,
	martin.petersen, robh, krzk+dt, conor+dt, linux-block,
	linux-crypto, linux-scsi, virtualization, devicetree,
	linux-arm-msm
  Cc: neeraj.soni, gaurav.kashyap, mani, andersson, konradybcio,
	bvanassche, alim.akhtar, avri.altman, stefanha, pbonzini,
	eperezma, xuanzhuo, linux-kernel

From: linlzhan <linlin.zhang@oss.qualcomm.com>

For the virtio-blk inline encryption use case, the guest kernel goes
through the normal blk_crypto_key programming flow via SMC call in a
virtual slot format before I/O starts.  It then requests the host to
handle that I/O with the key programmed into the corresponding physical
keyslot.

Introduce a "slot path" that lets a bio carry a pre-programmed physical
ICE keyslot index rather than a blk_crypto_key pointer.  Add struct
blk_crypto_slot, containing the physical slot index (phy_slot) and
data_unit_size_bits, and embed it in struct bio_crypt_ctx alongside the
existing bc_key pointer.  A NULL bc_key indicates the slot path.

Provide bio_crypt_set_ctx_by_slot() as the caller-facing API for this
path.  Update the internal consumers of bio_crypt_ctx to handle both
paths:

  - __bio_crypt_advance() and bio_crypt_dun_is_contiguous() use
    bc_slot.data_unit_size_bits to update the DUN when bc_key is NULL.
  - bio_crypt_ctx_compatible() compares phy_slot and data_unit_size_bits
    when bc_key is NULL, preserving request-merging for slot-based bios.
  - __blk_crypto_submit_bio() short-circuits for the slot path: if the
    device exposes a crypto_profile the bio is passed through as-is;
    otherwise it fails with BLK_STS_NOTSUPP.  The software fallback is
    not attempted since the guest has no key material.
  - blk_crypto_rq_get_keyslot() skips kernel-side keyslot allocation
    when bc_key is NULL.

There is no functional change to the existing key-based path.

Signed-off-by: linlzhan <linlin.zhang@oss.qualcomm.com>
---
 block/blk-crypto-internal.h |  2 +-
 block/blk-crypto.c          | 57 ++++++++++++++++++++++++++++++++++---
 include/linux/blk-crypto.h  | 25 ++++++++++++++++
 3 files changed, 79 insertions(+), 5 deletions(-)

diff --git a/block/blk-crypto-internal.h b/block/blk-crypto-internal.h
index 2c7a0446572a..04035d237f03 100644
--- a/block/blk-crypto-internal.h
+++ b/block/blk-crypto-internal.h
@@ -176,7 +176,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.c b/block/blk-crypto.c
index bc3a9f59574b..2212d06d3c11 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/include/linux/blk-crypto.h b/include/linux/blk-crypto.h
index 938ff536838c..33ae52b77522 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,6 +172,11 @@ 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]);
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v1 06/11] scsi: ufs: core: add slot path to ufshcd_prepare_lrbp_crypto
  2026-08-27 16:07 [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Linlin Zhang
                   ` (4 preceding siblings ...)
  2026-08-27 16:07 ` [PATCH v1 05/11] blk-crypto: add slot-based inline encryption path Linlin Zhang
@ 2026-08-27 16:07 ` Linlin Zhang
  2026-08-27 16:20   ` sashiko-bot
  2026-08-27 16:07 ` [PATCH v1 07/11] blk-crypto: move bio_crypt_dun_increment() to the public header Linlin Zhang
                   ` (5 subsequent siblings)
  11 siblings, 1 reply; 24+ messages in thread
From: Linlin Zhang @ 2026-08-27 16:07 UTC (permalink / raw)
  To: ebiggers, axboe, mst, jasowangio, James.Bottomley,
	martin.petersen, robh, krzk+dt, conor+dt, linux-block,
	linux-crypto, linux-scsi, virtualization, devicetree,
	linux-arm-msm
  Cc: neeraj.soni, gaurav.kashyap, mani, andersson, konradybcio,
	bvanassche, alim.akhtar, avri.altman, stefanha, pbonzini,
	eperezma, xuanzhuo, linux-kernel

From: linlzhan <linlin.zhang@oss.qualcomm.com>

ufshcd_prepare_lrbp_crypto() programs crypto_key_slot in the LRB from
rq->crypt_keyslot.  With the blk-crypto slot path a bio can carry a
pre-programmed physical ICE keyslot index in crypt_ctx->bc_slot rather
than a blk_crypto_key pointer.  In that case rq->crypt_keyslot is NULL
and the existing code incorrectly falls through to the "no encryption"
path, leaving crypto_key_slot as -1.

Extend the function to handle both cases: when rq->crypt_keyslot is
set, derive the slot index via blk_crypto_keyslot_index() as before;
when bc_slot.data_unit_size_bits is non-zero, read crypto_key_slot
directly from bc_slot.phy_slot.

Signed-off-by: linlzhan <linlin.zhang@oss.qualcomm.com>
---
 drivers/ufs/core/ufshcd-crypto.h | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/drivers/ufs/core/ufshcd-crypto.h b/drivers/ufs/core/ufshcd-crypto.h
index 8f66db94e179..2fc5601c0f76 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
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v1 07/11] blk-crypto: move bio_crypt_dun_increment() to the public header
  2026-08-27 16:07 [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Linlin Zhang
                   ` (5 preceding siblings ...)
  2026-08-27 16:07 ` [PATCH v1 06/11] scsi: ufs: core: add slot path to ufshcd_prepare_lrbp_crypto Linlin Zhang
@ 2026-08-27 16:07 ` Linlin Zhang
  2026-08-27 16:18   ` sashiko-bot
  2026-08-27 16:07 ` [PATCH v1 08/11] block: add /dev/blk-crypto-proxy for host-side virtio-blk inline encryption Linlin Zhang
                   ` (4 subsequent siblings)
  11 siblings, 1 reply; 24+ messages in thread
From: Linlin Zhang @ 2026-08-27 16:07 UTC (permalink / raw)
  To: ebiggers, axboe, mst, jasowangio, James.Bottomley,
	martin.petersen, robh, krzk+dt, conor+dt, linux-block,
	linux-crypto, linux-scsi, virtualization, devicetree,
	linux-arm-msm
  Cc: neeraj.soni, gaurav.kashyap, mani, andersson, konradybcio,
	bvanassche, alim.akhtar, avri.altman, stefanha, pbonzini,
	eperezma, xuanzhuo, linux-kernel

From: linlzhan <linlin.zhang@oss.qualcomm.com>

bio_crypt_dun_increment() is currently declared only in
block/blk-crypto-internal.h, which is not accessible to code
outside of block/.  Drivers that manage inline-encrypted I/O
directly need to advance the DUN across bio_vecs within a bio,
or across multiple bios in a request, and have no other way
to call this function.

Move the declaration from block/blk-crypto-internal.h to
include/linux/blk-crypto.h alongside the existing
bio_crypt_dun_is_contiguous() and blk_crypto_init_key()
declarations. The implementation in block/blk-crypto.c is
unchanged.

Signed-off-by: linlzhan <linlin.zhang@oss.qualcomm.com>
---
 block/blk-crypto-internal.h | 3 ---
 include/linux/blk-crypto.h  | 3 +++
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/block/blk-crypto-internal.h b/block/blk-crypto-internal.h
index 04035d237f03..32c720aabba7 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,
diff --git a/include/linux/blk-crypto.h b/include/linux/blk-crypto.h
index 33ae52b77522..c9f436b6b2ef 100644
--- a/include/linux/blk-crypto.h
+++ b/include/linux/blk-crypto.h
@@ -181,6 +181,9 @@ 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,
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v1 08/11] block: add /dev/blk-crypto-proxy for host-side virtio-blk inline encryption
  2026-08-27 16:07 [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Linlin Zhang
                   ` (6 preceding siblings ...)
  2026-08-27 16:07 ` [PATCH v1 07/11] blk-crypto: move bio_crypt_dun_increment() to the public header Linlin Zhang
@ 2026-08-27 16:07 ` Linlin Zhang
  2026-08-27 16:24   ` sashiko-bot
  2026-08-27 16:07 ` [PATCH v1 09/11] soc: qcom: add ICE keyslot partitioning driver for guest VMs Linlin Zhang
                   ` (3 subsequent siblings)
  11 siblings, 1 reply; 24+ messages in thread
From: Linlin Zhang @ 2026-08-27 16:07 UTC (permalink / raw)
  To: ebiggers, axboe, mst, jasowangio, James.Bottomley,
	martin.petersen, robh, krzk+dt, conor+dt, linux-block,
	linux-crypto, linux-scsi, virtualization, devicetree,
	linux-arm-msm
  Cc: neeraj.soni, gaurav.kashyap, mani, andersson, konradybcio,
	bvanassche, alim.akhtar, avri.altman, stefanha, pbonzini,
	eperezma, xuanzhuo, linux-kernel

From: linlzhan <linlin.zhang@oss.qualcomm.com>

A userspace virtio-blk backend receives VIRTIO_BLK_T_CRYPTO_IN/OUT
requests from guests that carry a virtual ICE keyslot index and a data
unit number.  The backend must submit the bio to the host storage
controller with the correct inline encryption context, but it has
no in-kernel interface to do so.

Add /dev/blk-crypto-proxy, a misc character device that bridges a
userspace virtio-blk backend to the kernel blk-crypto layer.  The
interface is three ioctls:

  BCP_BIND_CONTEXT      — bind a host block device fd and a hypervisor
                          VM fd to this file descriptor; the kernel
                          resolves the VM fd to a guest id and holds
                          the bdev reference for the fd lifetime.
  BCP_GET_CRYPTO_CAPS   — query the bound device's blk_crypto_profile
                          capabilities (supported modes, key types, max
                          DUN bytes) and the number of ICE keyslots
                          allocated to the bound VM, so the backend can
                          populate the virtio config space crypto fields.
  BCP_SUBMIT_IO_BY_VSLOT — submit an inline-encrypted bio using the
                          virtual slot index supplied by the guest.  The
                          kernel resolves virt_slot to a physical ICE
                          keyslot via bcp_slot_virt_ops, calls
                          bio_crypt_set_ctx_by_slot(), and submits the
                          bio synchronously.  Large requests are split
                          at data-unit boundaries (BIO_MAX_VECS per bio)
                          to preserve DUN/IV correctness. The
                          implementation follows the block layer's
                          direct-I/O path, with two differences: each
                          bio carries an inline encryption context, and
                          multiple bios are submitted sequentially
                          rather than concurrently now.

The driver is hypervisor-agnostic and storage-vendor-agnostic.  Two
pluggable op-sets registered by platform drivers fill the gaps:

  bcp_hypervisor_ops  — translate a hypervisor VM fd to an opaque
                        guest_id; implemented by the hypervisor driver.
  bcp_slot_virt_ops   — map (profile, guest_id, virt_slot) to a
                        physical ICE keyslot; implemented by the
                        platform storage virtualization layer.

Both op-sets are RCU-protected singletons; the hot path reads them
lock-free.

Signed-off-by: linlzhan <linlin.zhang@oss.qualcomm.com>
---
 drivers/block/Kconfig                 |  15 +
 drivers/block/Makefile                |   1 +
 drivers/block/blk-crypto-proxy.c      | 667 ++++++++++++++++++++++++++
 include/linux/blk-crypto-proxy.h      | 100 ++++
 include/uapi/linux/blk-crypto-proxy.h | 122 +++++
 5 files changed, 905 insertions(+)
 create mode 100644 drivers/block/blk-crypto-proxy.c
 create mode 100644 include/linux/blk-crypto-proxy.h
 create mode 100644 include/uapi/linux/blk-crypto-proxy.h

diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
index 7790ee2c700c..48ad79734c09 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
diff --git a/drivers/block/Makefile b/drivers/block/Makefile
index 079c910d5fc9..636137248d0d 100644
--- a/drivers/block/Makefile
+++ b/drivers/block/Makefile
@@ -23,6 +23,7 @@ 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
diff --git a/drivers/block/blk-crypto-proxy.c b/drivers/block/blk-crypto-proxy.c
new file mode 100644
index 000000000000..60722884dbcd
--- /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/include/linux/blk-crypto-proxy.h b/include/linux/blk-crypto-proxy.h
new file mode 100644
index 000000000000..6cf1ff0703e9
--- /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/uapi/linux/blk-crypto-proxy.h b/include/uapi/linux/blk-crypto-proxy.h
new file mode 100644
index 000000000000..dc8adc8ef5ed
--- /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 */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v1 09/11] soc: qcom: add ICE keyslot partitioning driver for guest VMs
  2026-08-27 16:07 [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Linlin Zhang
                   ` (7 preceding siblings ...)
  2026-08-27 16:07 ` [PATCH v1 08/11] block: add /dev/blk-crypto-proxy for host-side virtio-blk inline encryption Linlin Zhang
@ 2026-08-27 16:07 ` Linlin Zhang
  2026-08-27 16:17   ` sashiko-bot
  2026-08-27 16:07 ` [PATCH v1 10/11] blk-crypto: add slot_offset to blk_crypto_profile Linlin Zhang
                   ` (2 subsequent siblings)
  11 siblings, 1 reply; 24+ messages in thread
From: Linlin Zhang @ 2026-08-27 16:07 UTC (permalink / raw)
  To: ebiggers, axboe, mst, jasowangio, James.Bottomley,
	martin.petersen, robh, krzk+dt, conor+dt, linux-block,
	linux-crypto, linux-scsi, virtualization, devicetree,
	linux-arm-msm
  Cc: neeraj.soni, gaurav.kashyap, mani, andersson, konradybcio,
	bvanassche, alim.akhtar, avri.altman, stefanha, pbonzini,
	eperezma, xuanzhuo, linux-kernel

From: linlzhan <linlin.zhang@oss.qualcomm.com>

On Qualcomm platforms the ICE hardware has a fixed number of physical
keyslots shared across the host and all guest VMs.  A userspace
virtio-blk backend handling VIRTIO_BLK_T_CRYPTO_IN/OUT requests needs
to translate a guest's virtual keyslot index to the corresponding
physical ICE keyslot without letting one VM access another VM's slots.

Add QCOM_ICE_SLOTS, a platform driver that implements bcp_slot_virt_ops
for the /dev/blk-crypto-proxy device.  It parses a
qcom,ice-keyslot-map device-tree node describing the per-VM keyslot
allocation table, where each child entry maps a guest_id to a
contiguous physical slot range [slot_offset .. slot_offset +
max_ice_slots).  Entry 0 is reserved for the host; guest entries start
at index 1 and are excluded from the guest-facing translation so that
blk-crypto-proxy cannot accidentally route a guest request into the
host's physical keyslots.

The driver exposes two callbacks:

  get_guest_slots()  — return the number of ICE keyslots allocated to
                       a given guest_id; used by BCP_GET_CRYPTO_CAPS to
                       populate the max_slots field in the virtio config
                       space.
  vslot_to_pslot()   — translate a (guest_id, virtual-slot) pair to the
                       corresponding physical ICE keyslot index; used by
                       BCP_SUBMIT_IO_BY_VSLOT before calling
                       bio_crypt_set_ctx_by_slot().

The singleton pointer to the parsed table is RCU-protected; the hot
path reads it lock-free.  Probe validates that no two VM entries share
a guest_id or overlapping physical slot ranges.

Note: This patch is submitted for visibility. The keyslot partitioning
is based on the current DT-based keyslot allocation with vm_id known.
We are aware this may be revised to use a TZ SCM query interface in a
future version of this series, submit it RFC for design discussion.

Signed-off-by: linlzhan <linlin.zhang@oss.qualcomm.com>
---
 drivers/soc/qcom/Kconfig          |  18 +++
 drivers/soc/qcom/Makefile         |   1 +
 drivers/soc/qcom/qcom_ice_slots.c | 232 ++++++++++++++++++++++++++++++
 3 files changed, 251 insertions(+)
 create mode 100644 drivers/soc/qcom/qcom_ice_slots.c

diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 6c632d114d45..e1f383b4dc63 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -294,6 +294,24 @@ 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
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 6d4b7546d1fb..952a57554f9d 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -38,6 +38,7 @@ 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
diff --git a/drivers/soc/qcom/qcom_ice_slots.c b/drivers/soc/qcom/qcom_ice_slots.c
new file mode 100644
index 000000000000..364ac93077c1
--- /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");
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v1 10/11] blk-crypto: add slot_offset to blk_crypto_profile
  2026-08-27 16:07 [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Linlin Zhang
                   ` (8 preceding siblings ...)
  2026-08-27 16:07 ` [PATCH v1 09/11] soc: qcom: add ICE keyslot partitioning driver for guest VMs Linlin Zhang
@ 2026-08-27 16:07 ` Linlin Zhang
  2026-08-27 16:23   ` sashiko-bot
  2026-08-27 16:07 ` [PATCH v1 11/11] scsi: ufs: ufs-qcom: support ICE keyslot partitioning for guest VMs Linlin Zhang
  2026-08-27 18:42 ` [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Eric Biggers
  11 siblings, 1 reply; 24+ messages in thread
From: Linlin Zhang @ 2026-08-27 16:07 UTC (permalink / raw)
  To: ebiggers, axboe, mst, jasowangio, James.Bottomley,
	martin.petersen, robh, krzk+dt, conor+dt, linux-block,
	linux-crypto, linux-scsi, virtualization, devicetree,
	linux-arm-msm
  Cc: neeraj.soni, gaurav.kashyap, mani, andersson, konradybcio,
	bvanassche, alim.akhtar, avri.altman, stefanha, pbonzini,
	eperezma, xuanzhuo, linux-kernel

From: linlzhan <linlin.zhang@oss.qualcomm.com>

On platforms where ICE keyslots are partitioned across the host and
guest VMs, the host's physical keyslot range does not necessarily start
at slot 0.  blk_crypto_keyslot_index() currently returns a 0-based
array index, which is wrong for such configurations — hardware
programming requires the physical slot number, not the array position.

Add an unsigned int slot_offset field to struct blk_crypto_profile.
It defaults to zero (no change for existing drivers) and is set by
storage drivers that share ICE hardware across guests.

Update blk_crypto_keyslot_index() to add slot_offset to the array
index so that callers always receive the correct physical ICE keyslot
number to program into hardware.

Signed-off-by: linlzhan <linlin.zhang@oss.qualcomm.com>
---
 block/blk-crypto-profile.c         | 7 ++++---
 include/linux/blk-crypto-profile.h | 9 +++++++++
 2 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/block/blk-crypto-profile.c b/block/blk-crypto-profile.c
index 53126c091b0b..64202d64a018 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/include/linux/blk-crypto-profile.h b/include/linux/blk-crypto-profile.h
index 4f39e9cd7576..a9bdc05abfa3 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 */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [PATCH v1 11/11] scsi: ufs: ufs-qcom: support ICE keyslot partitioning for guest VMs
  2026-08-27 16:07 [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Linlin Zhang
                   ` (9 preceding siblings ...)
  2026-08-27 16:07 ` [PATCH v1 10/11] blk-crypto: add slot_offset to blk_crypto_profile Linlin Zhang
@ 2026-08-27 16:07 ` Linlin Zhang
  2026-08-27 16:26   ` sashiko-bot
  2026-08-27 18:42 ` [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Eric Biggers
  11 siblings, 1 reply; 24+ messages in thread
From: Linlin Zhang @ 2026-08-27 16:07 UTC (permalink / raw)
  To: ebiggers, axboe, mst, jasowangio, James.Bottomley,
	martin.petersen, robh, krzk+dt, conor+dt, linux-block,
	linux-crypto, linux-scsi, virtualization, devicetree,
	linux-arm-msm
  Cc: neeraj.soni, gaurav.kashyap, mani, andersson, konradybcio,
	bvanassche, alim.akhtar, avri.altman, stefanha, pbonzini,
	eperezma, xuanzhuo, linux-kernel

From: linlzhan <linlin.zhang@oss.qualcomm.com>

On Qualcomm platforms where UFS inline encryption is shared between
the host and guest VMs, the ICE hardware keyslots must be partitioned
so that each VM operates only within its own physical slot range.
Without this, the host's blk_crypto_profile would manage all hardware
slots, conflicting with slots already allocated to guests.

Add ufs_qcom_ice_parse_slot_table() to read the qcom,ice-keyslot-map
device-tree node.  The function parses all child entries and validates
that no entry's slot range or the combined total exceeds the hardware
slot count from REG_UFS_CCAP.  The first child entry is taken as the
host's own reservation; its slot count and offset are returned to the
caller.

In ufs_qcom_ice_init(), use the parsed host reservation to initialize
the blk_crypto_profile with only the host's slot count rather than the
full hardware range.  Set profile->slot_offset so that
blk_crypto_keyslot_index() returns the correct physical ICE slot
number when programming hardware.  If no qcom,ice-keyslot-map node is
present, the existing behaviour (profile manages all slots) is
preserved.

Note: This patch is submitted for visibility. The ufs-qcom driver
gets its max_slots and slot_offset based on the the current
DT-based keyslot mechanis. we are aware this may need to be replaced
by a TZ SCM interface, submit it RFC for design discussion.

Signed-off-by: linlzhan <linlin.zhang@oss.qualcomm.com>
---
 drivers/ufs/host/ufs-qcom.c | 91 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 90 insertions(+), 1 deletion(-)

diff --git a/drivers/ufs/host/ufs-qcom.c b/drivers/ufs/host/ufs-qcom.c
index 62396212a0a7..0611ab50f4cc 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
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 24+ messages in thread

* Re: [PATCH v1 04/11] dt-bindings: soc: qcom: add binding for qcom,crypto-virt
  2026-08-27 16:07 ` [PATCH v1 04/11] dt-bindings: soc: qcom: add binding for qcom,crypto-virt Linlin Zhang
@ 2026-08-27 16:14   ` sashiko-bot
  0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-27 16:14 UTC (permalink / raw)
  To: Linlin Zhang; +Cc: devicetree, conor+dt, linux-scsi, robh

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The device tree binding uses a driver-centric title and an underscore in the example node name, which violates device tree conventions.
--

commit 76a2bbd8a4bf8175fb462a6f688e076226259b19
Author: linlzhan <linlin.zhang@oss.qualcomm.com>

dt-bindings: soc: qcom: add binding for qcom,crypto-virt

This commit documents the device tree binding for the Qualcomm GVM crypto-virt
node. It provides the wrapped-key buffer size needed by the driver to perform
hardware-wrapped-key SCM operations backing virtio_blk's inline crypto layer.

> 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

[Severity: Low]
Does this title violate device tree conventions by describing an OS driver
instead of the hardware or firmware interface?

[ ... ]

> +additionalProperties: false
> +
> +examples:
> +  - |
> +    crypto_virt {

[Severity: Low]
Should this example node name use a generic hyphenated name instead of an
underscore?

Using an underscore here propagates styling violations that trigger
'node_name_chars_strict' warnings during DT schema validation.

> +        compatible = "qcom,crypto-virt";
> +        qcom,wrapped-key-size = <100>;
> +    };

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260827160806.1295313-1-linlin.zhang@oss.qualcomm.com?part=4

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v1 09/11] soc: qcom: add ICE keyslot partitioning driver for guest VMs
  2026-08-27 16:07 ` [PATCH v1 09/11] soc: qcom: add ICE keyslot partitioning driver for guest VMs Linlin Zhang
@ 2026-08-27 16:17   ` sashiko-bot
  0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-27 16:17 UTC (permalink / raw)
  To: Linlin Zhang; +Cc: robh, devicetree, conor+dt, linux-scsi

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Probe error path leaves a dangling pointer in the global `g_ice_slots`, leading to a Use-After-Free.
- [Medium] Implicit assumptions about Device Tree node ordering falsely reject valid, disjoint keyslot allocations.
--

commit 298e1c705033929570b5634f841cfc46474229cd
Author: linlzhan <linlin.zhang@oss.qualcomm.com>

soc: qcom: add ICE keyslot partitioning driver for guest VMs

On Qualcomm platforms the ICE hardware has a fixed number of physical
keyslots shared across the host and all guest VMs. Add the QCOM_ICE_SLOTS
platform driver to parse qcom,ice-keyslot-map DT nodes and map guest 
virtual keyslot indices to physical keyslots allocated to each VM.

> 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
[ ... ]
> +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++) {

[Severity: Medium]
Does this loop skip index 0 by hardcoding the assumption that the very first
parsed DT child node corresponds to the host?

This hides the first parsed entry from guest translation regardless of its
actual properties, which breaks guest VM keyslot partitioning if the host is
not the first child node in the device tree.

> +		if (virt->entries[i].guest_id == guest_id)
> +			return virt->entries[i].max_slots;
> +	}
> +	return -ENOKEY;
> +}
[ ... ]
> +static int qcom_ice_slots_probe(struct platform_device *pdev)
> +{
[ ... ]
> +		if (idx > 0 &&
> +		    slot_offset <
> +		    virt->entries[idx - 1].slot_offset +
> +		    virt->entries[idx - 1].max_slots) {

[Severity: Medium]
Can this logic falsely abort the probe when the nodes are disjoint but appear
out-of-order in the Device Tree?

This check only verifies the current entry's offset against the immediate
predecessor's end. If the nodes are legitimately sorted by their unit-address
reg instead of slot_offset, this mathematically flawed check evaluates to true
and falsely rejects valid physical keyslot mappings.

> +			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;
> +		}
[ ... ]
> +	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;
> +	}

[Severity: High]
Is it possible for this error path to leave a dangling pointer in the global
g_ice_slots resulting in a use-after-free regression?

The devm-allocated virt state is published to the global g_ice_slots pointer
via rcu_assign_pointer(). If bcp_register_slot_virt_ops() fails, the function
returns via the err_free label without setting g_ice_slots back to NULL. 

When the devres framework subsequently frees virt, it leaves a dangling pointer
that can be dereferenced by the hot path if blk-crypto-proxy invokes the
still-registered operations.

> +
> +	dev_info(dev, "registered: %u VMs, %u total ICE slots\n",
> +		 idx, total_slots);
> +	return 0;
> +
> +err_free:
> +	return ret;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260827160806.1295313-1-linlin.zhang@oss.qualcomm.com?part=9

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v1 07/11] blk-crypto: move bio_crypt_dun_increment() to the public header
  2026-08-27 16:07 ` [PATCH v1 07/11] blk-crypto: move bio_crypt_dun_increment() to the public header Linlin Zhang
@ 2026-08-27 16:18   ` sashiko-bot
  0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-27 16:18 UTC (permalink / raw)
  To: Linlin Zhang; +Cc: devicetree, linux-scsi, conor+dt, robh

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Missing EXPORT_SYMBOL_GPL for bio_crypt_dun_increment causes module build failures.
--

commit a07e0dacccb884a51c1f14000f1404eb1f5c2cf7
Author: linlzhan <linlin.zhang@oss.qualcomm.com>
blk-crypto: move bio_crypt_dun_increment() to the public header

This commit moves the declaration of bio_crypt_dun_increment() from the
internal block header to the public blk-crypto.h header. This allows drivers
managing inline-encrypted I/O directly to advance the data unit number.

> diff --git a/include/linux/blk-crypto.h b/include/linux/blk-crypto.h
> index 33ae52b77522b..c9f436b6b2ef4 100644
> --- a/include/linux/blk-crypto.h
> +++ b/include/linux/blk-crypto.h
> @@ -181,6 +181,9 @@ 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);
> +

[Severity: Low]
Does the missing EXPORT_SYMBOL_GPL() for this function cause module build
failures?

Since this function is now exposed for external drivers to use, if a driver
using it is configured as a loadable module, it seems the kernel build will
fail at the modpost step with an undefined symbol error.

Should an EXPORT_SYMBOL_GPL(bio_crypt_dun_increment) be added alongside its
implementation in block/blk-crypto.c so that modules can successfully link
against it?

>  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,

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260827160806.1295313-1-linlin.zhang@oss.qualcomm.com?part=7

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v1 03/11] soc: qcom: crypto_virt: add support for create, prepare and import keys
  2026-08-27 16:07 ` [PATCH v1 03/11] soc: qcom: crypto_virt: add support for create, prepare and import keys Linlin Zhang
@ 2026-08-27 16:19   ` sashiko-bot
  0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-27 16:19 UTC (permalink / raw)
  To: Linlin Zhang; +Cc: robh, devicetree, linux-scsi, conor+dt

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Missing QCOM SCM dependency check leads to NULL pointer dereference
- [Medium] Global state inheritance of `g_wrapped_key_size` across devices
- [Low] Unconditional registration of unsupported hardware-wrapped key operations
--

commit 4b3400c30fba3c871f1ea78eace978c0fff73f5a
Author: linlzhan <linlin.zhang@oss.qualcomm.com>
soc: qcom: crypto_virt: add support for create, prepare and import keys

This commit adds support for generating, preparing, and importing hardware
wrapped keys in the crypto_virt driver. It obtains the wrapped key size
from the "qcom,wrapped-key-size" property and enables SCM helpers.

> diff --git a/drivers/soc/qcom/crypto_virt.c b/drivers/soc/qcom/crypto_virt.c
> index 4ee2a36af6c1f..93c7993fb4a5d 100644
> --- a/drivers/soc/qcom/crypto_virt.c
> +++ b/drivers/soc/qcom/crypto_virt.c
[ ... ]
> +static int crypto_virt_probe(struct platform_device *pdev)
>  {
> +	int ret;
> +

[Severity: High]
Should this probe function verify that the QCOM SCM driver is available
before proceeding?

Since the driver registers virtio block crypto operations here, userspace can
issue block crypto ioctls as soon as this completes. If SCM is not yet ready,
a call to a function like qcom_scm_generate_ice_key() will unconditionally
dereference the uninitialized __scm pointer:

qcom_scm_generate_ice_key()
   void *lt_key_buf = qcom_tzmem_alloc(__scm->mempool, ...);

Could we add a check for qcom_scm_is_available() and return -EPROBE_DEFER
if it is not yet ready?

> +	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");

[Severity: Medium]
Could this leak state between different probed devices?

Because g_wrapped_key_size is a static global variable, it retains its value
from previous probes. If a newly probed device is missing the
qcom,wrapped-key-size property, of_property_read_u32() returns an error
without modifying the output variable.

Would it be safer to zero-initialize g_wrapped_key_size before the read,
so that a device without this property doesn't accidentally inherit the
wrapped key size from a previously probed device?

> +
> +	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);

[Severity: Low]
Does this code unconditionally register the hardware-wrapped key operations,
even when they are unsupported?

By registering virtblk_crypto_qcom_vops with non-NULL function pointers for
generate_key, prepare_key, and import_key, the block layer framework will
assume these capabilities are supported.

When invoked, the unsupported operations check for !g_wrapped_key_size and
return -EINVAL:

crypto_virt_generate_key() {
    ...
    if (!g_wrapped_key_size) {
        ...
        return -EINVAL;
    }
}

Should these operations either return the standard -EOPNOTSUPP, or avoid
being registered altogether when the feature is locally disabled?

>  	return 0;
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260827160806.1295313-1-linlin.zhang@oss.qualcomm.com?part=3

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v1 06/11] scsi: ufs: core: add slot path to ufshcd_prepare_lrbp_crypto
  2026-08-27 16:07 ` [PATCH v1 06/11] scsi: ufs: core: add slot path to ufshcd_prepare_lrbp_crypto Linlin Zhang
@ 2026-08-27 16:20   ` sashiko-bot
  0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-27 16:20 UTC (permalink / raw)
  To: Linlin Zhang; +Cc: linux-scsi, conor+dt, devicetree, robh

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Malformed crypto contexts with `data_unit_size_bits == 0` silently bypass encryption instead of failing the request.
--

commit 406714defb60086f0a0900753b849815524e2e23
Author: linlzhan <linlin.zhang@oss.qualcomm.com>

scsi: ufs: core: add slot path to ufshcd_prepare_lrbp_crypto

This commit extends ufshcd_prepare_lrbp_crypto() to support bios that carry a
pre-programmed physical ICE keyslot index in crypt_ctx->bc_slot instead of
a blk_crypto_key pointer. It adds a check for bc_slot.data_unit_size_bits to
determine if the slot path should be used.

> 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;
> +	}
> +
>  }

[Severity: High]
Does this code silently bypass encryption if a malformed request is submitted?

If an IO request is submitted with a crypto context (rq->crypt_ctx != NULL) but
has data_unit_size_bits == 0, this logic falls through to the final else block
and sets crypto_key_slot to -1.

Since the block layer explicitly skips validation (bio_crypt_check_alignment())
for slot-based bios, could an untrusted guest VM submitting a virtio-blk
request with data_unit_size_bits == 0 cause an explicitly encrypted IO to be
written to the physical storage device as plaintext?

Should this path fail the request rather than falling open to plaintext,
perhaps by surfacing an error up to the caller so it can be failed with
BLK_STS_IOERR?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260827160806.1295313-1-linlin.zhang@oss.qualcomm.com?part=6

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v1 10/11] blk-crypto: add slot_offset to blk_crypto_profile
  2026-08-27 16:07 ` [PATCH v1 10/11] blk-crypto: add slot_offset to blk_crypto_profile Linlin Zhang
@ 2026-08-27 16:23   ` sashiko-bot
  0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-27 16:23 UTC (permalink / raw)
  To: Linlin Zhang; +Cc: conor+dt, robh, linux-scsi, devicetree

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] Loops iterating over keyslots in block and driver crypto initialization and reprogramming paths pass logical indices instead of physical indices to keyslot_program/keyslot_evict, failing to account for the new slot_offset.
--

commit 2859a14efba8a99150c0d93c45a6b7ad003d0250
Author: linlzhan <linlin.zhang@oss.qualcomm.com>

blk-crypto: add slot_offset to blk_crypto_profile
    
On platforms where ICE keyslots are partitioned across the host and
guest VMs, the host's physical keyslot range does not necessarily start
at slot 0.  blk_crypto_keyslot_index() currently returns a 0-based
array index, which is wrong for such configurations — hardware
programming requires the physical slot number, not the array position.

Add an unsigned int slot_offset field to struct blk_crypto_profile.
It defaults to zero (no change for existing drivers) and is set by
storage drivers that share ICE hardware across guests.

Update blk_crypto_keyslot_index() to add slot_offset to the array
index so that callers always receive the correct physical ICE keyslot
number to program into hardware.

> 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/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;
> +

[Severity: Critical]
Does this new slot_offset need to be applied in loops that manually iterate
over keyslots?

For example, in block/blk-crypto-profile.c:blk_crypto_reprogram_all_keys(),
the logical loop counter slot is passed directly to keyslot_program():

	for (slot = 0; slot < profile->num_slots; slot++) {
		...
		err = profile->ll_ops.keyslot_program(profile, key, slot);
		...
	}

Since this bypasses the new slot_offset, will it program keys into physical
slots starting at 0 during device resume, corrupting foreign keyslots belonging
to the host or other VMs?

Similarly, in drivers/ufs/core/ufshcd-crypto.c:ufshcd_init_crypto():

	for (slot = 0; slot < hba->crypto_profile.num_slots; slot++)
		hba->crypto_profile.ll_ops.keyslot_evict(&hba->crypto_profile,
							 NULL, slot);

And in drivers/mmc/host/cqhci-crypto.c:cqhci_crypto_init():

	for (slot = 0; slot < profile->num_slots; slot++)
		profile->ll_ops.keyslot_evict(profile, NULL, slot);

Do these initialization paths also incorrectly evict physical keyslots starting
at 0 because they ignore slot_offset, destroying host/guest ICE keyslots?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260827160806.1295313-1-linlin.zhang@oss.qualcomm.com?part=10

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v1 01/11] virtio_blk: add inline encryption support
  2026-08-27 16:07 ` [PATCH v1 01/11] virtio_blk: add inline encryption support Linlin Zhang
@ 2026-08-27 16:23   ` sashiko-bot
  0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-27 16:23 UTC (permalink / raw)
  To: Linlin Zhang; +Cc: robh, conor+dt, devicetree, linux-scsi

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] The singleton inline crypto profile stores a pointer to the first probed virtio-blk device, leading to a Use-After-Free when that device is unbound.
- [Medium] Missing validation of crypto_modes_supported when reusing the singleton blk_crypto_profile across multiple devices.
- [Low] Accessing `req->crypt_ctx` and `req->crypt_keyslot` without `#ifdef CONFIG_BLK_INLINE_ENCRYPTION` breaks compilation when inline encryption is disabled.
- [Low] A reversed Kconfig dependency between VIRTIO_BLK and VIRTBLK_CRYPTO_VIRTUALIZATION causes a vmlinux linkage failure when VIRTIO_BLK is built-in and the extension is a module.
--

commit a4edc1b4437c893d0130908c0c7ae81f9e6802b0
Author: linlzhan <linlzhan@qti.qualcomm.com>

virtio_blk: add inline encryption support

Negotiate VIRTIO_BLK_F_INLINE_ENCRYPTION with the host and wire it into
the block layer's inline-crypto framework to enable inline encryption
on virtio block device.

> diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig
> index 858320b6ebb7e..7790ee2c700c4 100644
> --- a/drivers/block/Kconfig
> +++ b/drivers/block/Kconfig
> @@ -312,6 +312,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

[Severity: Low]
Can this combination cause a linker error? If VIRTIO_BLK=y and
VIRTBLK_CRYPTO_VIRTUALIZATION=m, IS_ENABLED(CONFIG_VIRTBLK_CRYPTO_VIRTUALIZATION)
evaluates to true in the built-in virtio_blk.o, causing it to call
virtblk_init_inline_crypto(). Since that function is in a separate module,
would this result in an undefined reference during the vmlinux link?

> +	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/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

[ ... ]

> @@ -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;

[Severity: Low]
Does this cause a build failure when CONFIG_BLK_INLINE_ENCRYPTION is disabled?
Even though is_crypto_request() returns false, the compiler still evaluates
req->crypt_ctx and req->crypt_keyslot. Since these fields are conditionally
compiled out of struct request without inline encryption, accessing them here
and in virtblk_setup_cmd() might break the build.

> +}
> +
>  static blk_status_t virtblk_setup_cmd(struct virtio_device *vdev,
>  				      struct request *req,
>  				      struct virtblk_req *vbr)

[ ... ]

> @@ -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];

[Severity: Low]
Does this also cause a build failure when CONFIG_BLK_INLINE_ENCRYPTION is
disabled, since these conditionally compiled struct fields are directly
accessed without preprocessor guards?

> +
> +		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;
>  }
>  
> 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

[ ... ]

> +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) {

[Severity: Medium]
Does this logic skip validating the negotiated crypto_modes_supported array?
If a second device supports different cryptographic modes than the first one,
could it silently inherit the first device's modes, potentially causing the
block layer to send unsupported crypto requests?

> +			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;

[Severity: High]
Can this lead to a use-after-free?

profile->dev stores a pointer to the first probed virtio-blk device in a
globally shared singleton. If this device is hot-unplugged or unbound,
its struct device is freed.

When a subsequent device performs a crypto operation, could blk_crypto_hw_enter()
dereference this freed pointer when calling pm_runtime_get_sync(profile->dev)?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260827160806.1295313-1-linlin.zhang@oss.qualcomm.com?part=1

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v1 08/11] block: add /dev/blk-crypto-proxy for host-side virtio-blk inline encryption
  2026-08-27 16:07 ` [PATCH v1 08/11] block: add /dev/blk-crypto-proxy for host-side virtio-blk inline encryption Linlin Zhang
@ 2026-08-27 16:24   ` sashiko-bot
  0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-27 16:24 UTC (permalink / raw)
  To: Linlin Zhang; +Cc: conor+dt, robh, linux-scsi, devicetree

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] Kernel crash when calling import_iovec with iov_cnt == 1 and a NULL iov pointer.
- [High] Sleeping in Atomic Context inside bcp_ioctl_bind_context.
- [High] UAPI mismatch causes silent data corruption for 32-bit compat userspace processes.
- [High] Pinned user pages are leaked unconditionally on every successful or failed bio submission.
- [High] The driver passes guest-provided data_unit_size_bits directly to hardware without validating it against the block device's supported crypto profile.
- [High] Missing little-endian to host-endian conversion for arg.dun.
- [Low] Dead error handling path for bio_alloc() using GFP_KERNEL.
--

commit 654e63cd96fb66a7cdbe2a3c22748ad74f166ab2
Author: linlzhan <linlin.zhang@oss.qualcomm.com>

block: add /dev/blk-crypto-proxy for host-side virtio-blk inline encryption
    
This patch introduces a misc character device to allow userspace virtio-blk
backends to submit inline-encrypted block I/O on behalf of virtual machines.
It maps virtual keyslots to physical hardware keyslots and submits bios
synchronously.

> diff --git a/drivers/block/blk-crypto-proxy.c b/drivers/block/blk-crypto-proxy.c
> index 0000000000000..60722884dbcd7
> --- /dev/null
> +++ b/drivers/block/blk-crypto-proxy.c
[ ... ]
> @@ -180,11 +180,11 @@ static long bcp_ioctl_bind_context(struct file *file,
> +	/*
> +	 * 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);

[Severity: High]
Since the comment explicitly mentions that get_guest_id() may sleep, is it
safe to call it while holding the RCU read lock? Sleeping in an RCU
read-side critical section typically leads to scheduling while atomic
warnings or panics.

> +	rcu_read_unlock();
[ ... ]
> @@ -411,10 +411,10 @@ static long bcp_ioctl_submit_io_by_vslot(struct file *file,
[ ... ]
> +	memset(dun, 0, sizeof(dun));
> +	dun[0] = arg.dun;

[Severity: High]
Does this assignment need a little-endian to host-endian conversion? The
UAPI specifies arg.dun as little-endian, but it is being assigned verbatim
to dun[0] which the crypto subsystem expects to be host-endian. On a
big-endian system, this might result in an incorrect data unit number and
silent decryption failures.

> +
> +	slot.phy_slot            = phy_slot;
> +	slot.data_unit_size_bits = arg.data_unit_size_bits;

[Severity: High]
Is it safe to pass the guest-provided data_unit_size_bits directly into the
crypto slot configuration without validating it against the block device's
supported crypto profile? For slot-based bios, the block layer skips
blk_crypto_config_supported_natively(), meaning an invalid unit size could
be pushed directly to the hardware driver.

> +
> +	align = 1ULL << arg.data_unit_size_bits;
[ ... ]
> +	/*
> +	 * 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);

[Severity: Critical]
Can this crash if a userspace process provides iov_cnt == 1 and a NULL
iov_ptr? The fast_segs parameter is set to 0 and iov is initialized
to NULL. When nr_segs is 1, import_iovec() will clear the iov pointer and
pass it to copy_iovec_from_user(), which directly writes to the NULL pointer.

[Severity: High]
Does this also cause an issue for 32-bit compat userspace processes? The
UAPI bcp_iovec is exactly 16 bytes. However, when in_compat_syscall() is
true, import_iovec() parses the input using copy_compat_iovec_from_user(),
which assumes 8-byte array elements (struct compat_iovec). This UAPI
mismatch could lead to memory corruption or misdirected block I/O.

> +	if (ret < 0)
> +		return ret;
[ ... ]
> +	/*
> +	 * 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)) {

[Severity: High]
Could this overflow check yield incorrect results on big-endian hosts? It
performs host-endian arithmetic directly on arg.dun, which is a little-endian
value according to the UAPI.

> +			ret = -EINVAL;
> +			goto out;
> +		}
[ ... ]
> +		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;
> +		}

[Severity: Low]
Can this allocation ever fail? bio_alloc() is backed by a mempool and is
called here with GFP_KERNEL (which implies __GFP_DIRECT_RECLAIM). Standard
block subsystem guidelines suggest this failure path is unreachable. Treating
it as reachable can lead to incorrect assumptions about memory allocation
behaviors.

> +		bio->bi_iter.bi_sector = arg.sector + (bytes_done >> SECTOR_SHIFT);
[ ... ]
> +		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;
> +		}

[Severity: High]
When iov_iter_count is not zero, bio_put() is called, but it does not
implicitly release the pages pinned by bio_iov_iter_get_pages(). Should this
error path explicitly call bio_release_pages(bio, false) to prevent memory
leaks?

> +		iov_iter_reexpand(&iter, remaining_before - 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;

[Severity: High]
Similar to the error path above, should there be a call to
bio_release_pages(bio, false) before or after bio_put() finishes? Otherwise
the pinned user pages appear to be leaked unconditionally for every bio
submitted.

> +
> +		/*

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260827160806.1295313-1-linlin.zhang@oss.qualcomm.com?part=8

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v1 02/11] soc: qcom: add crypto_virt backend for virtio-blk inline crypto
  2026-08-27 16:07 ` [PATCH v1 02/11] soc: qcom: add crypto_virt backend for virtio-blk inline crypto Linlin Zhang
@ 2026-08-27 16:24   ` sashiko-bot
  0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-27 16:24 UTC (permalink / raw)
  To: Linlin Zhang; +Cc: linux-scsi, devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Missing `key_type` validation and endianness conversion in `crypto_virt_program_key()` allows raw keys to be programmed incorrectly.
- [High] `keyslot_program` callback triggers a `GFP_KERNEL` memory allocation on the bio submission path, causing a potential memory reclaim deadlock.
- [Medium] Kernel log spam (DoS) via unratelimited error printing on invalid key derivation.
--

commit 2e7401200408cdbaa703d51ece5e8968e39837e3
Author: linlzhan <linlin.zhang@oss.qualcomm.com>

soc: qcom: add crypto_virt backend for virtio-blk inline crypto

The commit adds the QCOM_CRYPTO_VIRT driver to support virtio-blk inline
crypto in a Qualcomm GVM environment. It maps keyslot programming and eviction
to SCM calls, offloading hardware operations to Trust Zone.

> diff --git a/drivers/soc/qcom/crypto_virt.c b/drivers/soc/qcom/crypto_virt.c
> new file mode 100644
> index 0000000000000..4ee2a36af6c1f
> --- /dev/null
> +++ b/drivers/soc/qcom/crypto_virt.c
> @@ -0,0 +1,89 @@

[ ... ]

> +static int crypto_virt_program_key(const struct blk_crypto_key *key,
> +				   unsigned int slot)
> +{
> +	u32 dus_512_units;
> +	int ret;

[ ... ]

> +	/* 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);

[Severity: High]
Does this code need to validate that the key is a hardware-wrapped key, or apply
the hardware-mandated endianness swap for standard keys before passing the key
buffer directly to the SCM call?

[Severity: High]
Could this SCM call, which allocates memory internally via
qcom_tzmem_alloc(..., GFP_KERNEL), trigger a memory reclaim deadlock?

Since this function serves as the keyslot_program profile callback, it executes
in the IO submission path where FS/IO reclaim is strictly forbidden. Should
this allocation be wrapped with memalloc_noio_save() to prevent direct reclaim
from attempting another bio submission?

> +	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);

[Severity: Medium]
Could this unratelimited error print in the direct execution path of a
user-triggerable key derivation failure allow an attacker to trivially spam
the kernel logs?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260827160806.1295313-1-linlin.zhang@oss.qualcomm.com?part=2

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v1 11/11] scsi: ufs: ufs-qcom: support ICE keyslot partitioning for guest VMs
  2026-08-27 16:07 ` [PATCH v1 11/11] scsi: ufs: ufs-qcom: support ICE keyslot partitioning for guest VMs Linlin Zhang
@ 2026-08-27 16:26   ` sashiko-bot
  0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-27 16:26 UTC (permalink / raw)
  To: Linlin Zhang; +Cc: devicetree, linux-scsi, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Integer overflow in ICE keyslot boundary checks allows out-of-bounds hardware register access.
- [High] Missing overlap validation for keyslot partitions causes silent data corruption across VMs.
- [High] Inability to distinguish between a missing DT node and a 0-slot host reservation overrides explicit 0-slot allocations, breaking VM partitioning.
--

commit f3bdc5333b93892b7c3cc455d6a3fd8e3b213cb4
Author: linlzhan <linlin.zhang@oss.qualcomm.com>

scsi: ufs: ufs-qcom: support ICE keyslot partitioning for guest VMs

This commit adds support for partitioning ICE hardware keyslots so that the
host and guest VMs can operate within their own physical slot ranges. It
introduces a new function to parse the keyslot map from the device tree and
initializes the crypto profile with the host's specific slot count.

> 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
[ ... ]
> +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) {

[Severity: High]
If a malformed device tree provides excessively large values for
qcom,ice-slot-offset and qcom,max-ice-slots, can off + max wrap around
the 32-bit limit? This could defeat the bounds check and potentially
allow out-of-bounds hardware register accesses.

> +			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;

[Severity: High]
Can total_slots overflow a 32-bit integer when adding max? This could
defeat the subsequent check that ensures the total allocated slots do not
exceed the hardware maximum.

[Severity: High]
Does this logic only sum the capacities and miss bounds collision detection
between different VM partition ranges?

If the explicit slot intervals overlap in the device tree configuration,
could the host and guests silently overwrite each other's keys because the
total sum still fits within the hardware maximum?

> +	}
> +
> +	of_node_put(slots_np);
[ ... ]
> @@ -192,7 +262,23 @@ static int ufs_qcom_ice_init(struct ufs_qcom_host *host)
[ ... ]
> +	/*
> +	 * 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);

[Severity: High]
Since ufs_qcom_ice_parse_slot_table() initializes num_slots to 0 and returns
0 when the device tree node is absent, is it possible to distinguish between
a missing node and a valid 0-slot host allocation?

If a device tree explicitly allocates 0 slots to the host to dedicate all
physical keyslots to VMs, does this ternary operation conflate the valid
empty partition with a missing configuration and forcefully assign all
hardware slots to the host's crypto profile?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260827160806.1295313-1-linlin.zhang@oss.qualcomm.com?part=11

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v1 05/11] blk-crypto: add slot-based inline encryption path
  2026-08-27 16:07 ` [PATCH v1 05/11] blk-crypto: add slot-based inline encryption path Linlin Zhang
@ 2026-08-27 16:26   ` sashiko-bot
  0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-08-27 16:26 UTC (permalink / raw)
  To: Linlin Zhang; +Cc: linux-scsi, conor+dt, robh, devicetree

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] Multiple core block layer and driver functions unconditionally dereference the `bc_key` pointer in a bio's crypto context for slot-based bios, leading to kernel panics.
- [High] Standard block drivers fail to retrieve a keyslot for slot-path bios and silently drop encryption, leading to incorrectly encrypted data being written to disk.
- [High] Bio splitting causes silent Initialization Vector (IV) reuse and breaks cryptographic confidentiality if `data_unit_size_bits` is 0.
- [High] Missing bounds check on `data_unit_size_bits` causes Undefined Behavior during bit shift operations.
--

commit ecf24121b79d01c98765828aa3d5cd87595d9349
Author: linlzhan <linlin.zhang@oss.qualcomm.com>

blk-crypto: add slot-based inline encryption path

For the virtio-blk inline encryption use case, the guest kernel goes
through the normal blk_crypto_key programming flow via SMC call in a
virtual slot format before I/O starts.  It then requests the host to
handle that I/O with the key programmed into the corresponding physical
keyslot.

> diff --git a/block/blk-crypto-internal.h b/block/blk-crypto-internal.h
> index 2c7a0446572aa..04035d237f039 100644
> --- a/block/blk-crypto-internal.h
> +++ b/block/blk-crypto-internal.h
> @@ -176,7 +176,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.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,
[ ... ]
> +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;

[Severity: Critical]
If we set bc_key to NULL here for slot-based bios, will this cause kernel
panics in core block layer and driver functions that unconditionally
dereference it?

For example, when submit_bio_noacct() calls blk_crypto_supported(), it
evaluates &bio->bi_crypt_context->bc_key->crypto_cfg and passes a NULL
pointer:

block/blk-crypto-internal.h:blk_crypto_supported() {
    ...
    return blk_crypto_config_supported_natively(bio->bi_bdev,
                    &bio->bi_crypt_context->bc_key->crypto_cfg);
}

The native configuration check then dereferences this config, causing an
immediate crash.

Similarly, won't functions like bio_split_io_at() in block/blk-merge.c
unconditionally dereference bc_key when constructing DMA alignment masks?

block/blk-merge.c:bio_split_io_at() {
    ...
    start_align_mask |= (bc->bc_key->crypto_cfg.data_unit_size - 1);
    ...
}

And virtblk_setup_cmd() in drivers/block/virtio_blk.c also appears to
blindly dereference it:

drivers/block/virtio_blk.c:virtblk_setup_cmd() {
    ...
    unsigned int data_unit_size_bits = req->crypt_ctx->bc_key->data_unit_size_bits;
    ...
}

Could a guest VM trigger this trivially via the host proxy?

> +	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);
> +

[ ... ]

> @@ -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)

[Severity: High]
If an untrusted guest proxy provides a data_unit_size_bits of 0, won't this
condition silently bypass incrementing the DUN array?

If a bio exceeds hardware limits, bio_split_io_at() splits it and calls
__bio_crypt_advance(). If the increment is bypassed, both halves of the
split bio would be submitted to hardware using the exact same Initialization
Vector (IV) for different data sectors. Does this silent IV reuse break
cryptographic confidentiality?

> +		bio_crypt_dun_increment(bc->bc_dun,
> +					bytes >> bc->bc_slot.data_unit_size_bits);

[Severity: High]
Additionally, is there a missing bounds check on data_unit_size_bits here?

If a guest provides a value of 32 or greater, this right shift on the 32-bit
bytes unsigned integer will trigger undefined behavior.

>  }

[ ... ]

> @@ -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;

[Severity: High]
If we allow the bio to proceed to the low-level driver here, are standard
block drivers prepared to handle a NULL keyslot for slot-based bios?

Because blk_crypto_rq_get_keyslot() bypasses allocation when bc_key is
NULL, req->crypt_keyslot will be uninitialized. In drivers like MMC,
this NULL keyslot is ignored, failing to populate the physical keyslot index:

drivers/mmc/core/crypto.c:mmc_crypto_prepare_req() {
    ...
    keyslot = req->crypt_keyslot;
    if (keyslot)
        mrq->crypto_key_slot = blk_crypto_keyslot_index(keyslot);
    ...
}

This leaves the internal keyslot index defaulting to 0. Will this hardware
proceed encrypting with keyslot 0, leading to data corruption or a silent
leak of poorly encrypted data?

> +	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260827160806.1295313-1-linlin.zhang@oss.qualcomm.com?part=5

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests
  2026-08-27 16:07 [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Linlin Zhang
                   ` (10 preceding siblings ...)
  2026-08-27 16:07 ` [PATCH v1 11/11] scsi: ufs: ufs-qcom: support ICE keyslot partitioning for guest VMs Linlin Zhang
@ 2026-08-27 18:42 ` Eric Biggers
  11 siblings, 0 replies; 24+ messages in thread
From: Eric Biggers @ 2026-08-27 18:42 UTC (permalink / raw)
  To: Linlin Zhang
  Cc: axboe, mst, jasowangio, James.Bottomley, martin.petersen, robh,
	krzk+dt, conor+dt, linux-block, linux-crypto, linux-scsi,
	virtualization, devicetree, linux-arm-msm, neeraj.soni,
	gaurav.kashyap, mani, andersson, konradybcio, bvanassche,
	alim.akhtar, avri.altman, stefanha, pbonzini, eperezma, xuanzhuo,
	linux-kernel

On Thu, Aug 27, 2026 at 09:07:09AM -0700, Linlin Zhang wrote:
> From: linlzhan <linlin.zhang@oss.qualcomm.com>
> 
> Current virtio-blk does not provide a mechanism for a guest to
> program hardware keys or submit encrypted I/O using pre-programmed
> keyslots.  It drops the crypto context when issuing a bio request
> to the virtio-blk queue, preventing inline-encryption-based FBE
> on virtio block devices.
> 
> This series enables File-Based Encryption in guest VMs on Qualcomm
> GVM platforms where the ICE inline encryption hardware is shared
> between the host and guests.  In this environment the guest kernel
> has no access to the ICE hardware directly; it supplies a virtual
> keyslot index and data unit number with each encrypted I/O request
> via VIRTIO_BLK_F_INLINE_ENCRYPTION, and the host must translate the
> virtual slot to a physical ICE keyslot and submit the bio — without
> transferring raw key material across the VM boundary.

This seems to be designed incorrectly by not making virtio-blk itself
support key programming and eviction.  That complicates things
significantly by then having to handle the key programming and eviction
out-of-band using Qualcomm-specific SCM calls.  It also means that
adding other implementations of this would be very difficult.

There are some claims that not transmitting keys across the VM boundary
is desirable.  But that doesn't seem meaningful, given that all the I/O
is transmitted across that boundary in plaintext anyway, and also it
seems that hardware-wrapped keys will be supported too.

Please make virtio-blk support the key programming, eviction, and
HW-wrapped key management operations that are needed for this to work.

- Eric

^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2026-08-27 18:42 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-27 16:07 [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Linlin Zhang
2026-08-27 16:07 ` [PATCH v1 01/11] virtio_blk: add inline encryption support Linlin Zhang
2026-08-27 16:23   ` sashiko-bot
2026-08-27 16:07 ` [PATCH v1 02/11] soc: qcom: add crypto_virt backend for virtio-blk inline crypto Linlin Zhang
2026-08-27 16:24   ` sashiko-bot
2026-08-27 16:07 ` [PATCH v1 03/11] soc: qcom: crypto_virt: add support for create, prepare and import keys Linlin Zhang
2026-08-27 16:19   ` sashiko-bot
2026-08-27 16:07 ` [PATCH v1 04/11] dt-bindings: soc: qcom: add binding for qcom,crypto-virt Linlin Zhang
2026-08-27 16:14   ` sashiko-bot
2026-08-27 16:07 ` [PATCH v1 05/11] blk-crypto: add slot-based inline encryption path Linlin Zhang
2026-08-27 16:26   ` sashiko-bot
2026-08-27 16:07 ` [PATCH v1 06/11] scsi: ufs: core: add slot path to ufshcd_prepare_lrbp_crypto Linlin Zhang
2026-08-27 16:20   ` sashiko-bot
2026-08-27 16:07 ` [PATCH v1 07/11] blk-crypto: move bio_crypt_dun_increment() to the public header Linlin Zhang
2026-08-27 16:18   ` sashiko-bot
2026-08-27 16:07 ` [PATCH v1 08/11] block: add /dev/blk-crypto-proxy for host-side virtio-blk inline encryption Linlin Zhang
2026-08-27 16:24   ` sashiko-bot
2026-08-27 16:07 ` [PATCH v1 09/11] soc: qcom: add ICE keyslot partitioning driver for guest VMs Linlin Zhang
2026-08-27 16:17   ` sashiko-bot
2026-08-27 16:07 ` [PATCH v1 10/11] blk-crypto: add slot_offset to blk_crypto_profile Linlin Zhang
2026-08-27 16:23   ` sashiko-bot
2026-08-27 16:07 ` [PATCH v1 11/11] scsi: ufs: ufs-qcom: support ICE keyslot partitioning for guest VMs Linlin Zhang
2026-08-27 16:26   ` sashiko-bot
2026-08-27 18:42 ` [PATCH v1 00/11] FBE virtualization: inline encryption for virtio-blk guests Eric Biggers

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).