qemu/hw/virtio/virtio-crypto.c
Markus Armbruster b69c3c21a5 qdev: Unrealize must not fail
Devices may have component devices and buses.

Device realization may fail.  Realization is recursive: a device's
realize() method realizes its components, and device_set_realized()
realizes its buses (which should in turn realize the devices on that
bus, except bus_set_realized() doesn't implement that, yet).

When realization of a component or bus fails, we need to roll back:
unrealize everything we realized so far.  If any of these unrealizes
failed, the device would be left in an inconsistent state.  Must not
happen.

device_set_realized() lets it happen: it ignores errors in the roll
back code starting at label child_realize_fail.

Since realization is recursive, unrealization must be recursive, too.
But how could a partly failed unrealize be rolled back?  We'd have to
re-realize, which can fail.  This design is fundamentally broken.

device_set_realized() does not roll back at all.  Instead, it keeps
unrealizing, ignoring further errors.

It can screw up even for a device with no buses: if the lone
dc->unrealize() fails, it still unregisters vmstate, and calls
listeners' unrealize() callback.

bus_set_realized() does not roll back either.  Instead, it stops
unrealizing.

Fortunately, no unrealize method can fail, as we'll see below.

To fix the design error, drop parameter @errp from all the unrealize
methods.

Any unrealize method that uses @errp now needs an update.  This leads
us to unrealize() methods that can fail.  Merely passing it to another
unrealize method cannot cause failure, though.  Here are the ones that
do other things with @errp:

* virtio_serial_device_unrealize()

  Fails when qbus_set_hotplug_handler() fails, but still does all the
  other work.  On failure, the device would stay realized with its
  resources completely gone.  Oops.  Can't happen, because
  qbus_set_hotplug_handler() can't actually fail here.  Pass
  &error_abort to qbus_set_hotplug_handler() instead.

* hw/ppc/spapr_drc.c's unrealize()

  Fails when object_property_del() fails, but all the other work is
  already done.  On failure, the device would stay realized with its
  vmstate registration gone.  Oops.  Can't happen, because
  object_property_del() can't actually fail here.  Pass &error_abort
  to object_property_del() instead.

* spapr_phb_unrealize()

  Fails and bails out when remove_drcs() fails, but other work is
  already done.  On failure, the device would stay realized with some
  of its resources gone.  Oops.  remove_drcs() fails only when
  chassis_from_bus()'s object_property_get_uint() fails, and it can't
  here.  Pass &error_abort to remove_drcs() instead.

Therefore, no unrealize method can fail before this patch.

device_set_realized()'s recursive unrealization via bus uses
object_property_set_bool().  Can't drop @errp there, so pass
&error_abort.

We similarly unrealize with object_property_set_bool() elsewhere,
always ignoring errors.  Pass &error_abort instead.

Several unrealize methods no longer handle errors from other unrealize
methods: virtio_9p_device_unrealize(),
virtio_input_device_unrealize(), scsi_qdev_unrealize(), ...
Much of the deleted error handling looks wrong anyway.

One unrealize methods no longer ignore such errors:
usb_ehci_pci_exit().

Several realize methods no longer ignore errors when rolling back:
v9fs_device_realize_common(), pci_qdev_unrealize(),
spapr_phb_realize(), usb_qdev_realize(), vfio_ccw_realize(),
virtio_device_realize().

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
Message-Id: <20200505152926.18877-17-armbru@redhat.com>
2020-05-15 07:08:14 +02:00

997 lines
33 KiB
C

/*
* Virtio crypto Support
*
* Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD.
*
* Authors:
* Gonglei <arei.gonglei@huawei.com>
*
* This work is licensed under the terms of the GNU GPL, version 2 or
* (at your option) any later version. See the COPYING file in the
* top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu/iov.h"
#include "qemu/main-loop.h"
#include "qemu/module.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "hw/virtio/virtio.h"
#include "hw/virtio/virtio-crypto.h"
#include "hw/qdev-properties.h"
#include "hw/virtio/virtio-access.h"
#include "standard-headers/linux/virtio_ids.h"
#include "sysemu/cryptodev-vhost.h"
#define VIRTIO_CRYPTO_VM_VERSION 1
/*
* Transfer virtqueue index to crypto queue index.
* The control virtqueue is after the data virtqueues
* so the input value doesn't need to be adjusted
*/
static inline int virtio_crypto_vq2q(int queue_index)
{
return queue_index;
}
static int
virtio_crypto_cipher_session_helper(VirtIODevice *vdev,
CryptoDevBackendSymSessionInfo *info,
struct virtio_crypto_cipher_session_para *cipher_para,
struct iovec **iov, unsigned int *out_num)
{
VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(vdev);
unsigned int num = *out_num;
info->cipher_alg = ldl_le_p(&cipher_para->algo);
info->key_len = ldl_le_p(&cipher_para->keylen);
info->direction = ldl_le_p(&cipher_para->op);
DPRINTF("cipher_alg=%" PRIu32 ", info->direction=%" PRIu32 "\n",
info->cipher_alg, info->direction);
if (info->key_len > vcrypto->conf.max_cipher_key_len) {
error_report("virtio-crypto length of cipher key is too big: %u",
info->key_len);
return -VIRTIO_CRYPTO_ERR;
}
/* Get cipher key */
if (info->key_len > 0) {
size_t s;
DPRINTF("keylen=%" PRIu32 "\n", info->key_len);
info->cipher_key = g_malloc(info->key_len);
s = iov_to_buf(*iov, num, 0, info->cipher_key, info->key_len);
if (unlikely(s != info->key_len)) {
virtio_error(vdev, "virtio-crypto cipher key incorrect");
return -EFAULT;
}
iov_discard_front(iov, &num, info->key_len);
*out_num = num;
}
return 0;
}
static int64_t
virtio_crypto_create_sym_session(VirtIOCrypto *vcrypto,
struct virtio_crypto_sym_create_session_req *sess_req,
uint32_t queue_id,
uint32_t opcode,
struct iovec *iov, unsigned int out_num)
{
VirtIODevice *vdev = VIRTIO_DEVICE(vcrypto);
CryptoDevBackendSymSessionInfo info;
int64_t session_id;
int queue_index;
uint32_t op_type;
Error *local_err = NULL;
int ret;
memset(&info, 0, sizeof(info));
op_type = ldl_le_p(&sess_req->op_type);
info.op_type = op_type;
info.op_code = opcode;
if (op_type == VIRTIO_CRYPTO_SYM_OP_CIPHER) {
ret = virtio_crypto_cipher_session_helper(vdev, &info,
&sess_req->u.cipher.para,
&iov, &out_num);
if (ret < 0) {
goto err;
}
} else if (op_type == VIRTIO_CRYPTO_SYM_OP_ALGORITHM_CHAINING) {
size_t s;
/* cipher part */
ret = virtio_crypto_cipher_session_helper(vdev, &info,
&sess_req->u.chain.para.cipher_param,
&iov, &out_num);
if (ret < 0) {
goto err;
}
/* hash part */
info.alg_chain_order = ldl_le_p(
&sess_req->u.chain.para.alg_chain_order);
info.add_len = ldl_le_p(&sess_req->u.chain.para.aad_len);
info.hash_mode = ldl_le_p(&sess_req->u.chain.para.hash_mode);
if (info.hash_mode == VIRTIO_CRYPTO_SYM_HASH_MODE_AUTH) {
info.hash_alg = ldl_le_p(&sess_req->u.chain.para.u.mac_param.algo);
info.auth_key_len = ldl_le_p(
&sess_req->u.chain.para.u.mac_param.auth_key_len);
info.hash_result_len = ldl_le_p(
&sess_req->u.chain.para.u.mac_param.hash_result_len);
if (info.auth_key_len > vcrypto->conf.max_auth_key_len) {
error_report("virtio-crypto length of auth key is too big: %u",
info.auth_key_len);
ret = -VIRTIO_CRYPTO_ERR;
goto err;
}
/* get auth key */
if (info.auth_key_len > 0) {
DPRINTF("auth_keylen=%" PRIu32 "\n", info.auth_key_len);
info.auth_key = g_malloc(info.auth_key_len);
s = iov_to_buf(iov, out_num, 0, info.auth_key,
info.auth_key_len);
if (unlikely(s != info.auth_key_len)) {
virtio_error(vdev,
"virtio-crypto authenticated key incorrect");
ret = -EFAULT;
goto err;
}
iov_discard_front(&iov, &out_num, info.auth_key_len);
}
} else if (info.hash_mode == VIRTIO_CRYPTO_SYM_HASH_MODE_PLAIN) {
info.hash_alg = ldl_le_p(
&sess_req->u.chain.para.u.hash_param.algo);
info.hash_result_len = ldl_le_p(
&sess_req->u.chain.para.u.hash_param.hash_result_len);
} else {
/* VIRTIO_CRYPTO_SYM_HASH_MODE_NESTED */
error_report("unsupported hash mode");
ret = -VIRTIO_CRYPTO_NOTSUPP;
goto err;
}
} else {
/* VIRTIO_CRYPTO_SYM_OP_NONE */
error_report("unsupported cipher op_type: VIRTIO_CRYPTO_SYM_OP_NONE");
ret = -VIRTIO_CRYPTO_NOTSUPP;
goto err;
}
queue_index = virtio_crypto_vq2q(queue_id);
session_id = cryptodev_backend_sym_create_session(
vcrypto->cryptodev,
&info, queue_index, &local_err);
if (session_id >= 0) {
DPRINTF("create session_id=%" PRIu64 " successfully\n",
session_id);
ret = session_id;
} else {
if (local_err) {
error_report_err(local_err);
}
ret = -VIRTIO_CRYPTO_ERR;
}
err:
g_free(info.cipher_key);
g_free(info.auth_key);
return ret;
}
static uint8_t
virtio_crypto_handle_close_session(VirtIOCrypto *vcrypto,
struct virtio_crypto_destroy_session_req *close_sess_req,
uint32_t queue_id)
{
int ret;
uint64_t session_id;
uint32_t status;
Error *local_err = NULL;
session_id = ldq_le_p(&close_sess_req->session_id);
DPRINTF("close session, id=%" PRIu64 "\n", session_id);
ret = cryptodev_backend_sym_close_session(
vcrypto->cryptodev, session_id, queue_id, &local_err);
if (ret == 0) {
status = VIRTIO_CRYPTO_OK;
} else {
if (local_err) {
error_report_err(local_err);
} else {
error_report("destroy session failed");
}
status = VIRTIO_CRYPTO_ERR;
}
return status;
}
static void virtio_crypto_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq)
{
VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(vdev);
struct virtio_crypto_op_ctrl_req ctrl;
VirtQueueElement *elem;
struct iovec *in_iov;
struct iovec *out_iov;
unsigned in_num;
unsigned out_num;
uint32_t queue_id;
uint32_t opcode;
struct virtio_crypto_session_input input;
int64_t session_id;
uint8_t status;
size_t s;
for (;;) {
elem = virtqueue_pop(vq, sizeof(VirtQueueElement));
if (!elem) {
break;
}
if (elem->out_num < 1 || elem->in_num < 1) {
virtio_error(vdev, "virtio-crypto ctrl missing headers");
virtqueue_detach_element(vq, elem, 0);
g_free(elem);
break;
}
out_num = elem->out_num;
out_iov = elem->out_sg;
in_num = elem->in_num;
in_iov = elem->in_sg;
if (unlikely(iov_to_buf(out_iov, out_num, 0, &ctrl, sizeof(ctrl))
!= sizeof(ctrl))) {
virtio_error(vdev, "virtio-crypto request ctrl_hdr too short");
virtqueue_detach_element(vq, elem, 0);
g_free(elem);
break;
}
iov_discard_front(&out_iov, &out_num, sizeof(ctrl));
opcode = ldl_le_p(&ctrl.header.opcode);
queue_id = ldl_le_p(&ctrl.header.queue_id);
switch (opcode) {
case VIRTIO_CRYPTO_CIPHER_CREATE_SESSION:
memset(&input, 0, sizeof(input));
session_id = virtio_crypto_create_sym_session(vcrypto,
&ctrl.u.sym_create_session,
queue_id, opcode,
out_iov, out_num);
/* Serious errors, need to reset virtio crypto device */
if (session_id == -EFAULT) {
virtqueue_detach_element(vq, elem, 0);
break;
} else if (session_id == -VIRTIO_CRYPTO_NOTSUPP) {
stl_le_p(&input.status, VIRTIO_CRYPTO_NOTSUPP);
} else if (session_id == -VIRTIO_CRYPTO_ERR) {
stl_le_p(&input.status, VIRTIO_CRYPTO_ERR);
} else {
/* Set the session id */
stq_le_p(&input.session_id, session_id);
stl_le_p(&input.status, VIRTIO_CRYPTO_OK);
}
s = iov_from_buf(in_iov, in_num, 0, &input, sizeof(input));
if (unlikely(s != sizeof(input))) {
virtio_error(vdev, "virtio-crypto input incorrect");
virtqueue_detach_element(vq, elem, 0);
break;
}
virtqueue_push(vq, elem, sizeof(input));
virtio_notify(vdev, vq);
break;
case VIRTIO_CRYPTO_CIPHER_DESTROY_SESSION:
case VIRTIO_CRYPTO_HASH_DESTROY_SESSION:
case VIRTIO_CRYPTO_MAC_DESTROY_SESSION:
case VIRTIO_CRYPTO_AEAD_DESTROY_SESSION:
status = virtio_crypto_handle_close_session(vcrypto,
&ctrl.u.destroy_session, queue_id);
/* The status only occupy one byte, we can directly use it */
s = iov_from_buf(in_iov, in_num, 0, &status, sizeof(status));
if (unlikely(s != sizeof(status))) {
virtio_error(vdev, "virtio-crypto status incorrect");
virtqueue_detach_element(vq, elem, 0);
break;
}
virtqueue_push(vq, elem, sizeof(status));
virtio_notify(vdev, vq);
break;
case VIRTIO_CRYPTO_HASH_CREATE_SESSION:
case VIRTIO_CRYPTO_MAC_CREATE_SESSION:
case VIRTIO_CRYPTO_AEAD_CREATE_SESSION:
default:
error_report("virtio-crypto unsupported ctrl opcode: %d", opcode);
memset(&input, 0, sizeof(input));
stl_le_p(&input.status, VIRTIO_CRYPTO_NOTSUPP);
s = iov_from_buf(in_iov, in_num, 0, &input, sizeof(input));
if (unlikely(s != sizeof(input))) {
virtio_error(vdev, "virtio-crypto input incorrect");
virtqueue_detach_element(vq, elem, 0);
break;
}
virtqueue_push(vq, elem, sizeof(input));
virtio_notify(vdev, vq);
break;
} /* end switch case */
g_free(elem);
} /* end for loop */
}
static void virtio_crypto_init_request(VirtIOCrypto *vcrypto, VirtQueue *vq,
VirtIOCryptoReq *req)
{
req->vcrypto = vcrypto;
req->vq = vq;
req->in = NULL;
req->in_iov = NULL;
req->in_num = 0;
req->in_len = 0;
req->flags = CRYPTODEV_BACKEND_ALG__MAX;
req->u.sym_op_info = NULL;
}
static void virtio_crypto_free_request(VirtIOCryptoReq *req)
{
if (req) {
if (req->flags == CRYPTODEV_BACKEND_ALG_SYM) {
size_t max_len;
CryptoDevBackendSymOpInfo *op_info = req->u.sym_op_info;
max_len = op_info->iv_len +
op_info->aad_len +
op_info->src_len +
op_info->dst_len +
op_info->digest_result_len;
/* Zeroize and free request data structure */
memset(op_info, 0, sizeof(*op_info) + max_len);
g_free(op_info);
}
g_free(req);
}
}
static void
virtio_crypto_sym_input_data_helper(VirtIODevice *vdev,
VirtIOCryptoReq *req,
uint32_t status,
CryptoDevBackendSymOpInfo *sym_op_info)
{
size_t s, len;
if (status != VIRTIO_CRYPTO_OK) {
return;
}
len = sym_op_info->src_len;
/* Save the cipher result */
s = iov_from_buf(req->in_iov, req->in_num, 0, sym_op_info->dst, len);
if (s != len) {
virtio_error(vdev, "virtio-crypto dest data incorrect");
return;
}
iov_discard_front(&req->in_iov, &req->in_num, len);
if (sym_op_info->op_type ==
VIRTIO_CRYPTO_SYM_OP_ALGORITHM_CHAINING) {
/* Save the digest result */
s = iov_from_buf(req->in_iov, req->in_num, 0,
sym_op_info->digest_result,
sym_op_info->digest_result_len);
if (s != sym_op_info->digest_result_len) {
virtio_error(vdev, "virtio-crypto digest result incorrect");
}
}
}
static void virtio_crypto_req_complete(VirtIOCryptoReq *req, uint8_t status)
{
VirtIOCrypto *vcrypto = req->vcrypto;
VirtIODevice *vdev = VIRTIO_DEVICE(vcrypto);
if (req->flags == CRYPTODEV_BACKEND_ALG_SYM) {
virtio_crypto_sym_input_data_helper(vdev, req, status,
req->u.sym_op_info);
}
stb_p(&req->in->status, status);
virtqueue_push(req->vq, &req->elem, req->in_len);
virtio_notify(vdev, req->vq);
}
static VirtIOCryptoReq *
virtio_crypto_get_request(VirtIOCrypto *s, VirtQueue *vq)
{
VirtIOCryptoReq *req = virtqueue_pop(vq, sizeof(VirtIOCryptoReq));
if (req) {
virtio_crypto_init_request(s, vq, req);
}
return req;
}
static CryptoDevBackendSymOpInfo *
virtio_crypto_sym_op_helper(VirtIODevice *vdev,
struct virtio_crypto_cipher_para *cipher_para,
struct virtio_crypto_alg_chain_data_para *alg_chain_para,
struct iovec *iov, unsigned int out_num)
{
VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(vdev);
CryptoDevBackendSymOpInfo *op_info;
uint32_t src_len = 0, dst_len = 0;
uint32_t iv_len = 0;
uint32_t aad_len = 0, hash_result_len = 0;
uint32_t hash_start_src_offset = 0, len_to_hash = 0;
uint32_t cipher_start_src_offset = 0, len_to_cipher = 0;
uint64_t max_len, curr_size = 0;
size_t s;
/* Plain cipher */
if (cipher_para) {
iv_len = ldl_le_p(&cipher_para->iv_len);
src_len = ldl_le_p(&cipher_para->src_data_len);
dst_len = ldl_le_p(&cipher_para->dst_data_len);
} else if (alg_chain_para) { /* Algorithm chain */
iv_len = ldl_le_p(&alg_chain_para->iv_len);
src_len = ldl_le_p(&alg_chain_para->src_data_len);
dst_len = ldl_le_p(&alg_chain_para->dst_data_len);
aad_len = ldl_le_p(&alg_chain_para->aad_len);
hash_result_len = ldl_le_p(&alg_chain_para->hash_result_len);
hash_start_src_offset = ldl_le_p(
&alg_chain_para->hash_start_src_offset);
cipher_start_src_offset = ldl_le_p(
&alg_chain_para->cipher_start_src_offset);
len_to_cipher = ldl_le_p(&alg_chain_para->len_to_cipher);
len_to_hash = ldl_le_p(&alg_chain_para->len_to_hash);
} else {
return NULL;
}
max_len = (uint64_t)iv_len + aad_len + src_len + dst_len + hash_result_len;
if (unlikely(max_len > vcrypto->conf.max_size)) {
virtio_error(vdev, "virtio-crypto too big length");
return NULL;
}
op_info = g_malloc0(sizeof(CryptoDevBackendSymOpInfo) + max_len);
op_info->iv_len = iv_len;
op_info->src_len = src_len;
op_info->dst_len = dst_len;
op_info->aad_len = aad_len;
op_info->digest_result_len = hash_result_len;
op_info->hash_start_src_offset = hash_start_src_offset;
op_info->len_to_hash = len_to_hash;
op_info->cipher_start_src_offset = cipher_start_src_offset;
op_info->len_to_cipher = len_to_cipher;
/* Handle the initilization vector */
if (op_info->iv_len > 0) {
DPRINTF("iv_len=%" PRIu32 "\n", op_info->iv_len);
op_info->iv = op_info->data + curr_size;
s = iov_to_buf(iov, out_num, 0, op_info->iv, op_info->iv_len);
if (unlikely(s != op_info->iv_len)) {
virtio_error(vdev, "virtio-crypto iv incorrect");
goto err;
}
iov_discard_front(&iov, &out_num, op_info->iv_len);
curr_size += op_info->iv_len;
}
/* Handle additional authentication data if exists */
if (op_info->aad_len > 0) {
DPRINTF("aad_len=%" PRIu32 "\n", op_info->aad_len);
op_info->aad_data = op_info->data + curr_size;
s = iov_to_buf(iov, out_num, 0, op_info->aad_data, op_info->aad_len);
if (unlikely(s != op_info->aad_len)) {
virtio_error(vdev, "virtio-crypto additional auth data incorrect");
goto err;
}
iov_discard_front(&iov, &out_num, op_info->aad_len);
curr_size += op_info->aad_len;
}
/* Handle the source data */
if (op_info->src_len > 0) {
DPRINTF("src_len=%" PRIu32 "\n", op_info->src_len);
op_info->src = op_info->data + curr_size;
s = iov_to_buf(iov, out_num, 0, op_info->src, op_info->src_len);
if (unlikely(s != op_info->src_len)) {
virtio_error(vdev, "virtio-crypto source data incorrect");
goto err;
}
iov_discard_front(&iov, &out_num, op_info->src_len);
curr_size += op_info->src_len;
}
/* Handle the destination data */
op_info->dst = op_info->data + curr_size;
curr_size += op_info->dst_len;
DPRINTF("dst_len=%" PRIu32 "\n", op_info->dst_len);
/* Handle the hash digest result */
if (hash_result_len > 0) {
DPRINTF("hash_result_len=%" PRIu32 "\n", hash_result_len);
op_info->digest_result = op_info->data + curr_size;
}
return op_info;
err:
g_free(op_info);
return NULL;
}
static int
virtio_crypto_handle_sym_req(VirtIOCrypto *vcrypto,
struct virtio_crypto_sym_data_req *req,
CryptoDevBackendSymOpInfo **sym_op_info,
struct iovec *iov, unsigned int out_num)
{
VirtIODevice *vdev = VIRTIO_DEVICE(vcrypto);
uint32_t op_type;
CryptoDevBackendSymOpInfo *op_info;
op_type = ldl_le_p(&req->op_type);
if (op_type == VIRTIO_CRYPTO_SYM_OP_CIPHER) {
op_info = virtio_crypto_sym_op_helper(vdev, &req->u.cipher.para,
NULL, iov, out_num);
if (!op_info) {
return -EFAULT;
}
op_info->op_type = op_type;
} else if (op_type == VIRTIO_CRYPTO_SYM_OP_ALGORITHM_CHAINING) {
op_info = virtio_crypto_sym_op_helper(vdev, NULL,
&req->u.chain.para,
iov, out_num);
if (!op_info) {
return -EFAULT;
}
op_info->op_type = op_type;
} else {
/* VIRTIO_CRYPTO_SYM_OP_NONE */
error_report("virtio-crypto unsupported cipher type");
return -VIRTIO_CRYPTO_NOTSUPP;
}
*sym_op_info = op_info;
return 0;
}
static int
virtio_crypto_handle_request(VirtIOCryptoReq *request)
{
VirtIOCrypto *vcrypto = request->vcrypto;
VirtIODevice *vdev = VIRTIO_DEVICE(vcrypto);
VirtQueueElement *elem = &request->elem;
int queue_index = virtio_crypto_vq2q(virtio_get_queue_index(request->vq));
struct virtio_crypto_op_data_req req;
int ret;
struct iovec *in_iov;
struct iovec *out_iov;
unsigned in_num;
unsigned out_num;
uint32_t opcode;
uint8_t status = VIRTIO_CRYPTO_ERR;
uint64_t session_id;
CryptoDevBackendSymOpInfo *sym_op_info = NULL;
Error *local_err = NULL;
if (elem->out_num < 1 || elem->in_num < 1) {
virtio_error(vdev, "virtio-crypto dataq missing headers");
return -1;
}
out_num = elem->out_num;
out_iov = elem->out_sg;
in_num = elem->in_num;
in_iov = elem->in_sg;
if (unlikely(iov_to_buf(out_iov, out_num, 0, &req, sizeof(req))
!= sizeof(req))) {
virtio_error(vdev, "virtio-crypto request outhdr too short");
return -1;
}
iov_discard_front(&out_iov, &out_num, sizeof(req));
if (in_iov[in_num - 1].iov_len <
sizeof(struct virtio_crypto_inhdr)) {
virtio_error(vdev, "virtio-crypto request inhdr too short");
return -1;
}
/* We always touch the last byte, so just see how big in_iov is. */
request->in_len = iov_size(in_iov, in_num);
request->in = (void *)in_iov[in_num - 1].iov_base
+ in_iov[in_num - 1].iov_len
- sizeof(struct virtio_crypto_inhdr);
iov_discard_back(in_iov, &in_num, sizeof(struct virtio_crypto_inhdr));
/*
* The length of operation result, including dest_data
* and digest_result if exists.
*/
request->in_num = in_num;
request->in_iov = in_iov;
opcode = ldl_le_p(&req.header.opcode);
session_id = ldq_le_p(&req.header.session_id);
switch (opcode) {
case VIRTIO_CRYPTO_CIPHER_ENCRYPT:
case VIRTIO_CRYPTO_CIPHER_DECRYPT:
ret = virtio_crypto_handle_sym_req(vcrypto,
&req.u.sym_req,
&sym_op_info,
out_iov, out_num);
/* Serious errors, need to reset virtio crypto device */
if (ret == -EFAULT) {
return -1;
} else if (ret == -VIRTIO_CRYPTO_NOTSUPP) {
virtio_crypto_req_complete(request, VIRTIO_CRYPTO_NOTSUPP);
virtio_crypto_free_request(request);
} else {
sym_op_info->session_id = session_id;
/* Set request's parameter */
request->flags = CRYPTODEV_BACKEND_ALG_SYM;
request->u.sym_op_info = sym_op_info;
ret = cryptodev_backend_crypto_operation(vcrypto->cryptodev,
request, queue_index, &local_err);
if (ret < 0) {
status = -ret;
if (local_err) {
error_report_err(local_err);
}
} else { /* ret == VIRTIO_CRYPTO_OK */
status = ret;
}
virtio_crypto_req_complete(request, status);
virtio_crypto_free_request(request);
}
break;
case VIRTIO_CRYPTO_HASH:
case VIRTIO_CRYPTO_MAC:
case VIRTIO_CRYPTO_AEAD_ENCRYPT:
case VIRTIO_CRYPTO_AEAD_DECRYPT:
default:
error_report("virtio-crypto unsupported dataq opcode: %u",
opcode);
virtio_crypto_req_complete(request, VIRTIO_CRYPTO_NOTSUPP);
virtio_crypto_free_request(request);
}
return 0;
}
static void virtio_crypto_handle_dataq(VirtIODevice *vdev, VirtQueue *vq)
{
VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(vdev);
VirtIOCryptoReq *req;
while ((req = virtio_crypto_get_request(vcrypto, vq))) {
if (virtio_crypto_handle_request(req) < 0) {
virtqueue_detach_element(req->vq, &req->elem, 0);
virtio_crypto_free_request(req);
break;
}
}
}
static void virtio_crypto_dataq_bh(void *opaque)
{
VirtIOCryptoQueue *q = opaque;
VirtIOCrypto *vcrypto = q->vcrypto;
VirtIODevice *vdev = VIRTIO_DEVICE(vcrypto);
/* This happens when device was stopped but BH wasn't. */
if (!vdev->vm_running) {
return;
}
/* Just in case the driver is not ready on more */
if (unlikely(!(vdev->status & VIRTIO_CONFIG_S_DRIVER_OK))) {
return;
}
for (;;) {
virtio_crypto_handle_dataq(vdev, q->dataq);
virtio_queue_set_notification(q->dataq, 1);
/* Are we done or did the guest add more buffers? */
if (virtio_queue_empty(q->dataq)) {
break;
}
virtio_queue_set_notification(q->dataq, 0);
}
}
static void
virtio_crypto_handle_dataq_bh(VirtIODevice *vdev, VirtQueue *vq)
{
VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(vdev);
VirtIOCryptoQueue *q =
&vcrypto->vqs[virtio_crypto_vq2q(virtio_get_queue_index(vq))];
/* This happens when device was stopped but VCPU wasn't. */
if (!vdev->vm_running) {
return;
}
virtio_queue_set_notification(vq, 0);
qemu_bh_schedule(q->dataq_bh);
}
static uint64_t virtio_crypto_get_features(VirtIODevice *vdev,
uint64_t features,
Error **errp)
{
return features;
}
static void virtio_crypto_reset(VirtIODevice *vdev)
{
VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(vdev);
/* multiqueue is disabled by default */
vcrypto->curr_queues = 1;
if (!cryptodev_backend_is_ready(vcrypto->cryptodev)) {
vcrypto->status &= ~VIRTIO_CRYPTO_S_HW_READY;
} else {
vcrypto->status |= VIRTIO_CRYPTO_S_HW_READY;
}
}
static void virtio_crypto_init_config(VirtIODevice *vdev)
{
VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(vdev);
vcrypto->conf.crypto_services =
vcrypto->conf.cryptodev->conf.crypto_services;
vcrypto->conf.cipher_algo_l =
vcrypto->conf.cryptodev->conf.cipher_algo_l;
vcrypto->conf.cipher_algo_h =
vcrypto->conf.cryptodev->conf.cipher_algo_h;
vcrypto->conf.hash_algo = vcrypto->conf.cryptodev->conf.hash_algo;
vcrypto->conf.mac_algo_l = vcrypto->conf.cryptodev->conf.mac_algo_l;
vcrypto->conf.mac_algo_h = vcrypto->conf.cryptodev->conf.mac_algo_h;
vcrypto->conf.aead_algo = vcrypto->conf.cryptodev->conf.aead_algo;
vcrypto->conf.max_cipher_key_len =
vcrypto->conf.cryptodev->conf.max_cipher_key_len;
vcrypto->conf.max_auth_key_len =
vcrypto->conf.cryptodev->conf.max_auth_key_len;
vcrypto->conf.max_size = vcrypto->conf.cryptodev->conf.max_size;
}
static void virtio_crypto_device_realize(DeviceState *dev, Error **errp)
{
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(dev);
int i;
vcrypto->cryptodev = vcrypto->conf.cryptodev;
if (vcrypto->cryptodev == NULL) {
error_setg(errp, "'cryptodev' parameter expects a valid object");
return;
} else if (cryptodev_backend_is_used(vcrypto->cryptodev)) {
char *path = object_get_canonical_path_component(OBJECT(vcrypto->conf.cryptodev));
error_setg(errp, "can't use already used cryptodev backend: %s", path);
g_free(path);
return;
}
vcrypto->max_queues = MAX(vcrypto->cryptodev->conf.peers.queues, 1);
if (vcrypto->max_queues + 1 > VIRTIO_QUEUE_MAX) {
error_setg(errp, "Invalid number of queues (= %" PRIu32 "), "
"must be a positive integer less than %d.",
vcrypto->max_queues, VIRTIO_QUEUE_MAX);
return;
}
virtio_init(vdev, "virtio-crypto", VIRTIO_ID_CRYPTO, vcrypto->config_size);
vcrypto->curr_queues = 1;
vcrypto->vqs = g_malloc0(sizeof(VirtIOCryptoQueue) * vcrypto->max_queues);
for (i = 0; i < vcrypto->max_queues; i++) {
vcrypto->vqs[i].dataq =
virtio_add_queue(vdev, 1024, virtio_crypto_handle_dataq_bh);
vcrypto->vqs[i].dataq_bh =
qemu_bh_new(virtio_crypto_dataq_bh, &vcrypto->vqs[i]);
vcrypto->vqs[i].vcrypto = vcrypto;
}
vcrypto->ctrl_vq = virtio_add_queue(vdev, 64, virtio_crypto_handle_ctrl);
if (!cryptodev_backend_is_ready(vcrypto->cryptodev)) {
vcrypto->status &= ~VIRTIO_CRYPTO_S_HW_READY;
} else {
vcrypto->status |= VIRTIO_CRYPTO_S_HW_READY;
}
virtio_crypto_init_config(vdev);
cryptodev_backend_set_used(vcrypto->cryptodev, true);
}
static void virtio_crypto_device_unrealize(DeviceState *dev)
{
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(dev);
VirtIOCryptoQueue *q;
int i, max_queues;
max_queues = vcrypto->multiqueue ? vcrypto->max_queues : 1;
for (i = 0; i < max_queues; i++) {
virtio_delete_queue(vcrypto->vqs[i].dataq);
q = &vcrypto->vqs[i];
qemu_bh_delete(q->dataq_bh);
}
g_free(vcrypto->vqs);
virtio_delete_queue(vcrypto->ctrl_vq);
virtio_cleanup(vdev);
cryptodev_backend_set_used(vcrypto->cryptodev, false);
}
static const VMStateDescription vmstate_virtio_crypto = {
.name = "virtio-crypto",
.unmigratable = 1,
.minimum_version_id = VIRTIO_CRYPTO_VM_VERSION,
.version_id = VIRTIO_CRYPTO_VM_VERSION,
.fields = (VMStateField[]) {
VMSTATE_VIRTIO_DEVICE,
VMSTATE_END_OF_LIST()
},
};
static Property virtio_crypto_properties[] = {
DEFINE_PROP_LINK("cryptodev", VirtIOCrypto, conf.cryptodev,
TYPE_CRYPTODEV_BACKEND, CryptoDevBackend *),
DEFINE_PROP_END_OF_LIST(),
};
static void virtio_crypto_get_config(VirtIODevice *vdev, uint8_t *config)
{
VirtIOCrypto *c = VIRTIO_CRYPTO(vdev);
struct virtio_crypto_config crypto_cfg = {};
/*
* Virtio-crypto device conforms to VIRTIO 1.0 which is always LE,
* so we can use LE accessors directly.
*/
stl_le_p(&crypto_cfg.status, c->status);
stl_le_p(&crypto_cfg.max_dataqueues, c->max_queues);
stl_le_p(&crypto_cfg.crypto_services, c->conf.crypto_services);
stl_le_p(&crypto_cfg.cipher_algo_l, c->conf.cipher_algo_l);
stl_le_p(&crypto_cfg.cipher_algo_h, c->conf.cipher_algo_h);
stl_le_p(&crypto_cfg.hash_algo, c->conf.hash_algo);
stl_le_p(&crypto_cfg.mac_algo_l, c->conf.mac_algo_l);
stl_le_p(&crypto_cfg.mac_algo_h, c->conf.mac_algo_h);
stl_le_p(&crypto_cfg.aead_algo, c->conf.aead_algo);
stl_le_p(&crypto_cfg.max_cipher_key_len, c->conf.max_cipher_key_len);
stl_le_p(&crypto_cfg.max_auth_key_len, c->conf.max_auth_key_len);
stq_le_p(&crypto_cfg.max_size, c->conf.max_size);
memcpy(config, &crypto_cfg, c->config_size);
}
static bool virtio_crypto_started(VirtIOCrypto *c, uint8_t status)
{
VirtIODevice *vdev = VIRTIO_DEVICE(c);
return (status & VIRTIO_CONFIG_S_DRIVER_OK) &&
(c->status & VIRTIO_CRYPTO_S_HW_READY) && vdev->vm_running;
}
static void virtio_crypto_vhost_status(VirtIOCrypto *c, uint8_t status)
{
VirtIODevice *vdev = VIRTIO_DEVICE(c);
int queues = c->multiqueue ? c->max_queues : 1;
CryptoDevBackend *b = c->cryptodev;
CryptoDevBackendClient *cc = b->conf.peers.ccs[0];
if (!cryptodev_get_vhost(cc, b, 0)) {
return;
}
if ((virtio_crypto_started(c, status)) == !!c->vhost_started) {
return;
}
if (!c->vhost_started) {
int r;
c->vhost_started = 1;
r = cryptodev_vhost_start(vdev, queues);
if (r < 0) {
error_report("unable to start vhost crypto: %d: "
"falling back on userspace virtio", -r);
c->vhost_started = 0;
}
} else {
cryptodev_vhost_stop(vdev, queues);
c->vhost_started = 0;
}
}
static void virtio_crypto_set_status(VirtIODevice *vdev, uint8_t status)
{
VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(vdev);
virtio_crypto_vhost_status(vcrypto, status);
}
static void virtio_crypto_guest_notifier_mask(VirtIODevice *vdev, int idx,
bool mask)
{
VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(vdev);
int queue = virtio_crypto_vq2q(idx);
assert(vcrypto->vhost_started);
cryptodev_vhost_virtqueue_mask(vdev, queue, idx, mask);
}
static bool virtio_crypto_guest_notifier_pending(VirtIODevice *vdev, int idx)
{
VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(vdev);
int queue = virtio_crypto_vq2q(idx);
assert(vcrypto->vhost_started);
return cryptodev_vhost_virtqueue_pending(vdev, queue, idx);
}
static void virtio_crypto_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
device_class_set_props(dc, virtio_crypto_properties);
dc->vmsd = &vmstate_virtio_crypto;
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
vdc->realize = virtio_crypto_device_realize;
vdc->unrealize = virtio_crypto_device_unrealize;
vdc->get_config = virtio_crypto_get_config;
vdc->get_features = virtio_crypto_get_features;
vdc->reset = virtio_crypto_reset;
vdc->set_status = virtio_crypto_set_status;
vdc->guest_notifier_mask = virtio_crypto_guest_notifier_mask;
vdc->guest_notifier_pending = virtio_crypto_guest_notifier_pending;
}
static void virtio_crypto_instance_init(Object *obj)
{
VirtIOCrypto *vcrypto = VIRTIO_CRYPTO(obj);
/*
* The default config_size is sizeof(struct virtio_crypto_config).
* Can be overriden with virtio_crypto_set_config_size.
*/
vcrypto->config_size = sizeof(struct virtio_crypto_config);
}
static const TypeInfo virtio_crypto_info = {
.name = TYPE_VIRTIO_CRYPTO,
.parent = TYPE_VIRTIO_DEVICE,
.instance_size = sizeof(VirtIOCrypto),
.instance_init = virtio_crypto_instance_init,
.class_init = virtio_crypto_class_init,
};
static void virtio_register_types(void)
{
type_register_static(&virtio_crypto_info);
}
type_init(virtio_register_types)