/*
* Copyright ( c ) 2015 , Google , Inc . All rights reserved
*
* Permission is hereby granted , free of charge , to any person obtaining
* a copy of this software and associated documentation files
* ( the " Software " ) , to deal in the Software without restriction ,
* including without limitation the rights to use , copy , modify , merge ,
* publish , distribute , sublicense , and / or sell copies of the Software ,
* and to permit persons to whom the Software is furnished to do so ,
* subject to the following conditions :
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND ,
* EXPRESS OR IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY , FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT .
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM , DAMAGES OR OTHER LIABILITY , WHETHER IN AN ACTION OF CONTRACT ,
* TORT OR OTHERWISE , ARISING FROM , OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE .
*/
#include <assert.h>
#include <err.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <trace.h>
#include <arch/arch_ops.h>
#include <inttypes.h>
#include <kernel/event.h>
#include <kernel/mutex.h>
#include <kernel/spinlock.h>
#include <kernel/vm.h>
#include <lib/binary_search_tree.h>
#include <lib/sm.h>
#include <lk/init.h>
#include <lk/reflist.h>
#include <remoteproc/remoteproc.h>
#include "trusty_virtio.h"
#define LOCAL_TRACE 0
/*
* FW resource version expected by us
*/
#define VIRTIO_FW_RSC_VER 1
enum {
VIRTIO_BUS_STATE_UNINITIALIZED = 0 ,
VIRTIO_BUS_STATE_IDLE,
VIRTIO_BUS_STATE_ACTIVATING,
VIRTIO_BUS_STATE_ACTIVE,
VIRTIO_BUS_STATE_DEACTIVATING,
};
struct trusty_virtio_bus {
uint vdev_cnt;
uint next_dev_id;
size_t descr_size;
volatile int state;
struct list_node vdev_list;
struct bst_node node;
ext_mem_client_id_t client_id;
struct obj refobj;
/*
* This is a reference to refobj in the same virtio bus and gets deleted
* after the first VIRTIO_STOP or a failed GET_DESCR SMC . It ' s needed to
* ensure that refobj has at least one reference even if there are no
* pending virtio SMCs and should only be deleted when a VM exits . After
* it ' s deleted , no further SMCs can get references to the bus .
*/
struct obj_ref tree_node_ref;
/*
* The last reference to the bus may get dropped from an interrupt - free
* context which can ' t free the bus so this event is used to signal that the
* bus may be freed .
*/
event_t free_bus_event;
struct sm_vm_notifier vm_notifier;
};
static spin_lock_t virtio_buses_tree_lock = SPIN_LOCK_INITIAL_VALUE;
static struct bst_root virtio_buses_tree = BST_ROOT_INITIAL_VALUE;
static int compare_client_ids(struct bst_node* a, struct bst_node* b) {
DEBUG_ASSERT(a);
DEBUG_ASSERT(b);
struct trusty_virtio_bus* bus_a =
containerof(a, struct trusty_virtio_bus, node);
struct trusty_virtio_bus* bus_b =
containerof(b, struct trusty_virtio_bus, node);
ext_mem_client_id_t id_a = bus_a->client_id;
ext_mem_client_id_t id_b = bus_b->client_id;
if (id_a < id_b) {
return 1 ;
} else if (id_a > id_b) {
return -1 ;
} else {
return 0 ;
}
}
static void signal_client_bus_free(struct obj* obj) {
struct trusty_virtio_bus* vb =
containerof_null_safe(obj, struct trusty_virtio_bus, refobj);
DEBUG_ASSERT(vb);
/*
* This function may be called with interrupts disabled , so signal that the
* bus may be freed instead of freeing it directly here .
*/
event_signal(&vb->free_bus_event, false );
}
static void release_bus_ref_locked(struct trusty_virtio_bus* vb,
struct obj_ref* ref) {
DEBUG_ASSERT(vb);
DEBUG_ASSERT(ref);
obj_del_ref(&vb->refobj, ref, signal_client_bus_free);
}
static void release_bus_ref(struct trusty_virtio_bus* vb, struct obj_ref* ref) {
DEBUG_ASSERT(ref);
DEBUG_ASSERT(vb);
spin_lock_saved_state_t state;
spin_lock_irqsave(&virtio_buses_tree_lock, state);
release_bus_ref_locked(vb, ref);
spin_unlock_irqrestore(&virtio_buses_tree_lock, state);
}
static struct trusty_virtio_bus* alloc_new_bus(ext_mem_client_id_t client_id,
struct obj_ref* ref) {
DEBUG_ASSERT(ref);
struct trusty_virtio_bus* new_bus = (struct trusty_virtio_bus*)calloc(
1 , sizeof (struct trusty_virtio_bus));
if (!new_bus) {
return NULL;
}
new_bus->state = VIRTIO_BUS_STATE_UNINITIALIZED;
new_bus->vdev_list =
(struct list_node)LIST_INITIAL_VALUE(new_bus->vdev_list);
new_bus->node = (struct bst_node)BST_NODE_INITIAL_VALUE;
new_bus->client_id = client_id;
obj_ref_init(&new_bus->tree_node_ref);
/*
* Initialize the refobj with the caller ' s reference and only add
* tree_node_ref after we ' ve added the bus to the tree
*/
obj_init(&new_bus->refobj, ref);
event_init(&new_bus->free_bus_event, 0 , EVENT_FLAG_AUTOUNSIGNAL);
return new_bus;
}
static status_t create_new_bus(ext_mem_client_id_t client_id,
struct trusty_virtio_bus** vb,
struct obj_ref* ref) {
DEBUG_ASSERT(vb);
DEBUG_ASSERT(ref);
struct obj_ref tmp_ref = OBJ_REF_INITIAL_VALUE(tmp_ref);
struct trusty_virtio_bus* new_bus = alloc_new_bus(client_id, &tmp_ref);
if (!new_bus) {
LTRACEF("Could not allocate memory for virtio bus for client %" PRId64
"\n" ,
client_id);
return ERR_NO_MEMORY;
}
spin_lock_saved_state_t state;
spin_lock_irqsave(&virtio_buses_tree_lock, state);
bool inserted =
bst_insert(&virtio_buses_tree, &new_bus->node, compare_client_ids);
if (inserted) {
/* Add tree_node_ref if the bus was inserted */
obj_add_ref(&new_bus->refobj, &new_bus->tree_node_ref);
/* Transfer the local reference to the parameter */
obj_ref_transfer(ref, &tmp_ref);
} else {
/* If the bus was not inserted delete the caller's reference */
release_bus_ref_locked(new_bus, &tmp_ref);
}
spin_unlock_irqrestore(&virtio_buses_tree_lock, state);
if (!inserted) {
DEBUG_ASSERT(!obj_has_ref(&new_bus->refobj));
free(new_bus);
return ERR_ALREADY_EXISTS;
}
*vb = new_bus;
return NO_ERROR;
}
static struct trusty_virtio_bus* get_client_bus_locked(
ext_mem_client_id_t client_id) {
struct trusty_virtio_bus bus = {
.node = BST_NODE_INITIAL_VALUE,
.client_id = client_id,
};
struct bst_node* node =
bst_search(&virtio_buses_tree, &bus.node, compare_client_ids);
return containerof_null_safe(node, struct trusty_virtio_bus, node);
}
static struct trusty_virtio_bus* get_client_bus(ext_mem_client_id_t client_id,
struct obj_ref* ref) {
DEBUG_ASSERT(ref);
spin_lock_saved_state_t state;
spin_lock_irqsave(&virtio_buses_tree_lock, state);
struct trusty_virtio_bus* vb = get_client_bus_locked(client_id);
if (vb) {
obj_add_ref(&vb->refobj, ref);
}
spin_unlock_irqrestore(&virtio_buses_tree_lock, state);
return vb;
}
/*
* Frees the client bus if it ' s in the virtio tree and deletes a reference to
* the bus held by the caller .
*/
static void remove_client_bus(struct trusty_virtio_bus* vb,
struct obj_ref* ref,
bool unregister_vm_notifier) {
DEBUG_ASSERT(vb);
DEBUG_ASSERT(ref);
spin_lock_saved_state_t state;
spin_lock_irqsave(&virtio_buses_tree_lock, state);
/*
* Check if the bus is still in the tree or if another call to
* remove_client_bus beat us
*/
bool bus_in_tree = obj_ref_active(&vb->tree_node_ref);
if (bus_in_tree) {
/*
* Remove the bus from the virtio tree to prevent further calls to
* get_client_bus_locked from succeeding
*/
bst_delete(&virtio_buses_tree, &vb->node);
release_bus_ref_locked(vb, &vb->tree_node_ref);
}
release_bus_ref_locked(vb, ref);
/*
* If there are other calls to remove_client_bus , we need to drop this lock
* before waiting on the free bus event because they may delete other
* references to the bus
*/
spin_unlock_irqrestore(&virtio_buses_tree_lock, state);
if (bus_in_tree) {
/* Blocks until the last reference to the bus is dropped */
event_wait(&vb->free_bus_event);
if (unregister_vm_notifier) {
/*
* We have to unregister the VM notifier if called from
* anywhere other than the destruction callback ,
* but only if it has been registered successfully .
*/
sm_vm_notifier_unregister(&vb->vm_notifier);
}
/*
* Only the first call to remove_client_bus will find the bus in the
* tree and end up freeing the bus
*/
DEBUG_ASSERT(!obj_has_ref(&vb->refobj));
free(vb);
}
}
static mutex_t virtio_bus_notifier_lock =
MUTEX_INITIAL_VALUE(virtio_bus_notifier_lock);
static struct list_node virtio_bus_notifier_list =
LIST_INITIAL_VALUE(virtio_bus_notifier_list);
void trusty_virtio_register_bus_notifier(struct trusty_virtio_bus_notifier* n) {
mutex_acquire(&virtio_bus_notifier_lock);
list_add_tail(&virtio_bus_notifier_list, &n->node);
mutex_release(&virtio_bus_notifier_lock);
}
static status_t on_create_virtio_bus(struct trusty_virtio_bus* vb) {
DEBUG_ASSERT(vb);
status_t ret = NO_ERROR;
struct trusty_virtio_bus_notifier* n;
mutex_acquire(&virtio_bus_notifier_lock);
list_for_every_entry(&virtio_bus_notifier_list, n,
struct trusty_virtio_bus_notifier, node) {
if (!n->on_create) {
continue ;
}
ret = n->on_create(vb);
if (ret != NO_ERROR) {
LTRACEF("call to on_create notifier failed (%d)\n" , ret);
goto on_create_err;
}
}
on_create_err:
mutex_release(&virtio_bus_notifier_lock);
return ret;
}
static status_t map_descr(ext_mem_client_id_t client_id,
ext_mem_obj_id_t buf_id,
void ** buf_va,
ns_size_t sz,
uint buf_mmu_flags) {
return ext_mem_map_obj_id(vmm_get_kernel_aspace(), "virtio" , client_id,
buf_id, 0 , 0 , round_up(sz, PAGE_SIZE), buf_va,
PAGE_SIZE_SHIFT, 0 , buf_mmu_flags);
}
static status_t unmap_descr(void * va, size_t sz) {
return vmm_free_region(vmm_get_kernel_aspace(), (vaddr_t)va);
}
static status_t validate_vdev(struct vdev* vd) {
/* check parameters */
if (!vd) {
LTRACEF("vd = %p\n" , vd);
return ERR_INVALID_ARGS;
}
/* check vdev_ops */
if (!vd->ops) {
LTRACEF("vd = %p: missing vdev ops\n" , vd);
return ERR_INVALID_ARGS;
}
/* all vdev_ops required */
const struct vdev_ops* ops = vd->ops;
if (!ops->descr_sz || !ops->get_descr || !ops->probe || !ops->reset ||
!ops->kick_vqueue) {
LTRACEF("vd = %p: misconfigured vdev ops\n" , vd);
return ERR_INVALID_ARGS;
}
return NO_ERROR;
}
/*
* Register virtio device
*/
status_t virtio_register_device(struct trusty_virtio_bus* vb, struct vdev* vd) {
status_t ret = ERR_BAD_STATE;
DEBUG_ASSERT(vb);
if (vb->state == VIRTIO_BUS_STATE_UNINITIALIZED) {
ret = validate_vdev(vd);
if (ret == NO_ERROR) {
vb->vdev_cnt++;
vd->devid = vb->next_dev_id++;
list_add_tail(&vb->vdev_list, &vd->node);
}
}
return ret;
}
/*
*
*/
static void finalize_vdev_registry(struct trusty_virtio_bus* vb) {
DEBUG_ASSERT(vb);
if (vb->state == VIRTIO_BUS_STATE_UNINITIALIZED) {
struct vdev* vd;
uint32_t offset =
sizeof (struct resource_table) + sizeof (uint32_t) * vb->vdev_cnt;
/*
* go through the list of vdev and calculate
* total descriptor size and offset withing descriptor
* buffer
*/
list_for_every_entry(&vb->vdev_list, vd, struct vdev, node) {
vd->descr_offset = offset;
offset += vd->ops->descr_sz(vd);
}
vb->descr_size = offset;
vb->state = VIRTIO_BUS_STATE_IDLE;
}
}
static status_t virtio_vm_destroy(struct sm_vm_notifier* notif) {
return virtio_stop(notif->client_id, 0 , 0 , 0 , false );
}
/*
* Retrieve device description to be shared with NS side
*/
ssize_t virtio_get_description(ext_mem_client_id_t client_id,
ext_mem_obj_id_t buf_id,
ns_size_t buf_sz,
uint buf_mmu_flags) {
status_t ret;
struct vdev* vd;
struct trusty_virtio_bus* vb = NULL;
struct obj_ref tmp_ref = OBJ_REF_INITIAL_VALUE(tmp_ref);
LTRACEF("descr_buf: %u bytes @ 0x%" PRIx64 "\n" , buf_sz, buf_id);
ret = create_new_bus(client_id, &vb, &tmp_ref);
if (ret == ERR_ALREADY_EXISTS) {
LTRACEF("Client %" PRId64 " may only call the VIRTIO_GET_DESCR once\n" ,
client_id);
return ERR_NOT_ALLOWED;
} else if (ret != NO_ERROR) {
LTRACEF("Could not create virtio bus for client %" PRId64 "\n" ,
client_id);
return ret;
}
ret = sm_vm_notifier_init(&vb->vm_notifier, client_id, virtio_vm_destroy);
if (!ret) {
ret = sm_vm_notifier_register(&vb->vm_notifier);
}
if (ret) {
LTRACEF("Could not register VM event notifier for client %" PRId64 "\n" ,
client_id);
remove_client_bus(vb, &tmp_ref, false );
return ret;
}
/* on_create notifiers must only be called if virtio bus is uninitialized */
if (vb->state == VIRTIO_BUS_STATE_UNINITIALIZED) {
ret = on_create_virtio_bus(vb);
/* If on_create notifiers failed remove the new virtio bus */
if (ret != NO_ERROR) {
goto err_failed_on_create;
}
}
/*
* finalize_vdev_registry in the first call to this function switches the
* bus state to idle so it should never be uninitialized after this point
*/
finalize_vdev_registry(vb);
ASSERT(vb->state != VIRTIO_BUS_STATE_UNINITIALIZED);
if ((size_t)buf_sz < vb->descr_size) {
LTRACEF("buffer (%zu bytes) is too small (%zu needed)\n" ,
(size_t)buf_sz, vb->descr_size);
ret = ERR_NOT_ENOUGH_BUFFER;
goto err_buffer;
}
/* map in NS memory */
void * va = NULL;
ret = map_descr(client_id, buf_id, &va, vb->descr_size, buf_mmu_flags);
if (ret != NO_ERROR) {
LTRACEF("failed (%d) to map in descriptor buffer\n" , (int )ret);
goto err_failed_map;
}
memset(va, 0 , vb->descr_size);
/* build resource table */
uint32_t vdev_idx = 0 ;
struct resource_table* descr = (struct resource_table*)va;
descr->ver = VIRTIO_FW_RSC_VER;
descr->num = vb->vdev_cnt;
list_for_every_entry(&vb->vdev_list, vd, struct vdev, node) {
DEBUG_ASSERT(vd->descr_offset <= vb->descr_size);
DEBUG_ASSERT(vd->descr_offset + vd->ops->descr_sz(vd) <=
vb->descr_size);
DEBUG_ASSERT(vdev_idx < vb->vdev_cnt);
descr->offset[vdev_idx++] = vd->descr_offset;
vd->client_id = client_id;
vd->ops->get_descr(vd, (uint8_t*)descr + vd->descr_offset);
}
unmap_descr(va, vb->descr_size);
release_bus_ref(vb, &tmp_ref);
return vb->descr_size;
err_failed_map:
err_buffer:
err_failed_on_create:
remove_client_bus(vb, &tmp_ref, true );
return ret;
}
/*
* Called by NS side to finalize device initialization
*/
status_t virtio_start(ext_mem_client_id_t client_id,
ext_mem_obj_id_t ns_descr_id,
ns_size_t descr_sz,
uint descr_mmu_flags) {
status_t ret;
int oldstate;
void * descr_va;
void * ns_descr_va = NULL;
struct vdev* vd;
struct obj_ref tmp_ref = OBJ_REF_INITIAL_VALUE(tmp_ref);
struct trusty_virtio_bus* vb = get_client_bus(client_id, &tmp_ref);
if (!vb) {
LTRACEF("Could not get virtio bus for client %" PRId64 "\n" , client_id);
return ERR_BAD_STATE;
}
LTRACEF("%u bytes @ 0x%" PRIx64 "\n" , descr_sz, ns_descr_id);
list_for_every_entry(&vb->vdev_list, vd, struct vdev, node) {
if (client_id != vd->client_id) {
LTRACEF("mismatched client id 0x%" PRIx64 " != 0x%" PRIx64 "\n" ,
client_id, vd->client_id);
ret = ERR_INVALID_ARGS;
goto err_invalid_args;
}
}
oldstate = atomic_cmpxchg(&vb->state, VIRTIO_BUS_STATE_IDLE,
VIRTIO_BUS_STATE_ACTIVATING);
if (oldstate != VIRTIO_BUS_STATE_IDLE) {
/* bus should be in initializing state */
LTRACEF("unexpected state state (%d)\n" , oldstate);
ret = ERR_BAD_STATE;
goto err_bad_state;
}
if ((size_t)descr_sz != vb->descr_size || descr_sz == 0 ) {
LTRACEF("unexpected descriptor size (%zd vs. %zd)\n" , (size_t)descr_sz,
vb->descr_size);
ret = ERR_INVALID_ARGS;
goto err_bad_params;
}
descr_va = malloc(descr_sz);
if (!descr_va) {
LTRACEF("not enough memory to store descr\n" );
ret = ERR_NO_MEMORY;
goto err_alloc_descr;
}
/* TODO: map read-only */
ret = map_descr(client_id, ns_descr_id, &ns_descr_va, vb->descr_size,
descr_mmu_flags);
if (ret != NO_ERROR) {
LTRACEF("failed (%d) to map in descriptor buffer\n" , ret);
goto err_map_in;
}
/* copy descriptor out of NS memory before parsing it */
memcpy(descr_va, ns_descr_va, descr_sz);
/* handle it */
list_for_every_entry(&vb->vdev_list, vd, struct vdev, node) {
vd->ops->probe(vd, (void *)((uint8_t*)descr_va + vd->descr_offset));
}
unmap_descr(ns_descr_va, vb->descr_size);
free(descr_va);
vb->state = VIRTIO_BUS_STATE_ACTIVE;
release_bus_ref(vb, &tmp_ref);
return NO_ERROR;
err_map_in:
free(descr_va);
err_alloc_descr:
err_bad_params:
vb->state = oldstate;
err_bad_state:
err_invalid_args:
release_bus_ref(vb, &tmp_ref);
return ret;
}
status_t virtio_stop(ext_mem_client_id_t client_id,
ext_mem_obj_id_t descr_id,
ns_size_t descr_sz,
uint descr_mmu_flags,
bool unregister_vm_notifier) {
status_t ret;
int oldstate;
struct vdev* vd;
struct obj_ref tmp_ref = OBJ_REF_INITIAL_VALUE(tmp_ref);
struct trusty_virtio_bus* vb = get_client_bus(client_id, &tmp_ref);
if (!vb) {
LTRACEF("Could not get virtio bus for client %" PRId64 "\n" , client_id);
return ERR_BAD_STATE;
}
LTRACEF("%u bytes @ 0x%" PRIx64 "\n" , descr_sz, descr_id);
list_for_every_entry(&vb->vdev_list, vd, struct vdev, node) {
if (client_id != vd->client_id) {
LTRACEF("mismatched client id 0x%" PRIx64 " != 0x%" PRIx64 "\n" ,
client_id, vd->client_id);
ret = ERR_INVALID_ARGS;
goto err_invalid_args;
}
}
oldstate = atomic_cmpxchg(&vb->state, VIRTIO_BUS_STATE_ACTIVE,
VIRTIO_BUS_STATE_DEACTIVATING);
if (oldstate != VIRTIO_BUS_STATE_ACTIVE) {
ret = ERR_BAD_STATE;
goto err_bad_state;
}
/* reset all devices */
list_for_every_entry(&vb->vdev_list, vd, struct vdev, node) {
vd->ops->reset(vd);
}
vb->state = VIRTIO_BUS_STATE_IDLE;
remove_client_bus(vb, &tmp_ref, unregister_vm_notifier);
return NO_ERROR;
err_bad_state:
/* Remove the bus even if it was not in the active state */
remove_client_bus(vb, &tmp_ref, unregister_vm_notifier);
return ret;
err_invalid_args:
release_bus_ref(vb, &tmp_ref);
return ret;
}
/*
* Reset virtio device with specified device id
*/
status_t virtio_device_reset(ext_mem_client_id_t client_id, uint devid) {
struct vdev* vd;
status_t ret = ERR_NOT_FOUND;
struct obj_ref tmp_ref = OBJ_REF_INITIAL_VALUE(tmp_ref);
struct trusty_virtio_bus* vb = get_client_bus(client_id, &tmp_ref);
if (!vb) {
LTRACEF("Could not get virtio bus for client %" PRId64 "\n" , client_id);
return ERR_BAD_STATE;
}
LTRACEF("dev=%d\n" , devid);
if (vb->state != VIRTIO_BUS_STATE_ACTIVE) {
ret = ERR_BAD_STATE;
goto err_bad_state;
}
list_for_every_entry(&vb->vdev_list, vd, struct vdev, node) {
if (vd->devid == devid) {
ret = vd->ops->reset(vd);
break ;
}
}
err_bad_state:
release_bus_ref(vb, &tmp_ref);
return ret;
}
/*
* Kick vq for virtio device with specified device id
*/
status_t virtio_kick_vq(ext_mem_client_id_t client_id, uint devid, uint vqid) {
struct vdev* vd;
status_t ret = ERR_NOT_FOUND;
struct obj_ref tmp_ref = OBJ_REF_INITIAL_VALUE(tmp_ref);
struct trusty_virtio_bus* vb = get_client_bus(client_id, &tmp_ref);
if (!vb) {
LTRACEF("Could not get virtio bus for client %" PRId64 "\n" , client_id);
return ERR_BAD_STATE;
}
#if WITH_CHATTY_LTRACE
LTRACEF("dev=%d\n" , devid);
#endif
if (vb->state != VIRTIO_BUS_STATE_ACTIVE) {
ret = ERR_BAD_STATE;
goto err_bad_state;
}
list_for_every_entry(&vb->vdev_list, vd, struct vdev, node) {
if (vd->devid == devid) {
ret = vd->ops->kick_vqueue(vd, vqid);
break ;
}
}
err_bad_state:
release_bus_ref(vb, &tmp_ref);
return ret;
}
Messung V0.5 in Prozent C=94 H=90 G=91
¤ Dauer der Verarbeitung: 0.4 Sekunden
(vorverarbeitet am 2026-06-27)
¤
*© Formatika GbR, Deutschland