308 lines
8.3 KiB
C
308 lines
8.3 KiB
C
/* Copyright (c) 2016-2019, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
#include "hab.h"
|
|
|
|
struct virtual_channel *
|
|
hab_vchan_alloc(struct uhab_context *ctx, struct physical_channel *pchan,
|
|
int openid)
|
|
{
|
|
int id;
|
|
struct virtual_channel *vchan;
|
|
|
|
if (!pchan || !ctx)
|
|
return NULL;
|
|
|
|
vchan = kzalloc(sizeof(*vchan), GFP_KERNEL);
|
|
if (!vchan)
|
|
return NULL;
|
|
|
|
/* This should be the first thing we do in this function */
|
|
idr_preload(GFP_KERNEL);
|
|
spin_lock_bh(&pchan->vid_lock);
|
|
id = idr_alloc(&pchan->vchan_idr, vchan, 1,
|
|
(HAB_VCID_ID_MASK >> HAB_VCID_ID_SHIFT) + 1, GFP_NOWAIT);
|
|
spin_unlock_bh(&pchan->vid_lock);
|
|
idr_preload_end();
|
|
|
|
if (id <= 0) {
|
|
pr_err("idr failed %d\n", id);
|
|
kfree(vchan);
|
|
return NULL;
|
|
}
|
|
mb(); /* id must be generated done before pchan_get */
|
|
|
|
hab_pchan_get(pchan);
|
|
vchan->pchan = pchan;
|
|
/* vchan need both vcid and openid to be properly located */
|
|
vchan->session_id = openid;
|
|
write_lock(&pchan->vchans_lock);
|
|
list_add_tail(&vchan->pnode, &pchan->vchannels);
|
|
pchan->vcnt++;
|
|
write_unlock(&pchan->vchans_lock);
|
|
vchan->id = ((id << HAB_VCID_ID_SHIFT) & HAB_VCID_ID_MASK) |
|
|
((pchan->habdev->id << HAB_VCID_MMID_SHIFT) &
|
|
HAB_VCID_MMID_MASK) |
|
|
((pchan->dom_id << HAB_VCID_DOMID_SHIFT) &
|
|
HAB_VCID_DOMID_MASK);
|
|
spin_lock_init(&vchan->rx_lock);
|
|
INIT_LIST_HEAD(&vchan->rx_list);
|
|
init_waitqueue_head(&vchan->rx_queue);
|
|
|
|
kref_init(&vchan->refcount);
|
|
|
|
vchan->otherend_closed = pchan->closed;
|
|
|
|
hab_ctx_get(ctx);
|
|
vchan->ctx = ctx;
|
|
|
|
return vchan;
|
|
}
|
|
|
|
static void
|
|
hab_vchan_free(struct kref *ref)
|
|
{
|
|
struct virtual_channel *vchan =
|
|
container_of(ref, struct virtual_channel, refcount);
|
|
struct hab_message *message, *msg_tmp;
|
|
struct physical_channel *pchan = vchan->pchan;
|
|
struct uhab_context *ctx = vchan->ctx;
|
|
struct virtual_channel *vc, *vc_tmp;
|
|
int irqs_disabled = irqs_disabled();
|
|
|
|
hab_spin_lock(&vchan->rx_lock, irqs_disabled);
|
|
list_for_each_entry_safe(message, msg_tmp, &vchan->rx_list, node) {
|
|
list_del(&message->node);
|
|
hab_msg_free(message);
|
|
}
|
|
hab_spin_unlock(&vchan->rx_lock, irqs_disabled);
|
|
|
|
/* release vchan from pchan. no more msg for this vchan */
|
|
hab_write_lock(&pchan->vchans_lock, irqs_disabled);
|
|
list_for_each_entry_safe(vc, vc_tmp, &pchan->vchannels, pnode) {
|
|
if (vchan == vc) {
|
|
list_del(&vc->pnode);
|
|
/* the ref is held in case of pchan is freed */
|
|
pchan->vcnt--;
|
|
break;
|
|
}
|
|
}
|
|
hab_write_unlock(&pchan->vchans_lock, irqs_disabled);
|
|
|
|
/* the release vchan from ctx was done earlier in vchan close() */
|
|
hab_ctx_put(ctx); /* now ctx is not needed from this vchan's view */
|
|
|
|
/* release idr at the last so same idr will not be used early */
|
|
hab_spin_lock(&pchan->vid_lock, irqs_disabled);
|
|
idr_remove(&pchan->vchan_idr, HAB_VCID_GET_ID(vchan->id));
|
|
hab_spin_unlock(&pchan->vid_lock, irqs_disabled);
|
|
|
|
hab_pchan_put(pchan); /* no more need for pchan from this vchan */
|
|
|
|
kfree(vchan);
|
|
}
|
|
|
|
/*
|
|
* only for msg recv path to retrieve vchan from vcid and openid based on
|
|
* pchan's vchan list
|
|
*/
|
|
struct virtual_channel*
|
|
hab_vchan_get(struct physical_channel *pchan, struct hab_header *header)
|
|
{
|
|
struct virtual_channel *vchan;
|
|
uint32_t vchan_id = HAB_HEADER_GET_ID(*header);
|
|
uint32_t session_id = HAB_HEADER_GET_SESSION_ID(*header);
|
|
size_t sizebytes = HAB_HEADER_GET_SIZE(*header);
|
|
uint32_t payload_type = HAB_HEADER_GET_TYPE(*header);
|
|
int irqs_disabled = irqs_disabled();
|
|
|
|
hab_spin_lock(&pchan->vid_lock, irqs_disabled);
|
|
vchan = idr_find(&pchan->vchan_idr, HAB_VCID_GET_ID(vchan_id));
|
|
if (vchan) {
|
|
if (vchan->session_id != session_id)
|
|
/*
|
|
* skipped if session is different even vcid
|
|
* is the same
|
|
*/
|
|
vchan = NULL;
|
|
else if (!vchan->otherend_id /*&& !vchan->session_id*/) {
|
|
/*
|
|
* not paired vchan can be fetched right after it is
|
|
* alloc'ed. so it has to be skipped during search
|
|
* for remote msg
|
|
*/
|
|
pr_warn("vcid %x is not paired yet session %d refcnt %d type %d sz %zd\n",
|
|
vchan->id, vchan->otherend_id,
|
|
get_refcnt(vchan->refcount),
|
|
payload_type, sizebytes);
|
|
vchan = NULL;
|
|
} else if (vchan->otherend_closed || vchan->closed) {
|
|
pr_debug("closed already remote %d local %d vcid %x remote %x session %d refcnt %d header %x session %d type %d sz %zd\n",
|
|
vchan->otherend_closed, vchan->closed,
|
|
vchan->id, vchan->otherend_id,
|
|
vchan->session_id, get_refcnt(vchan->refcount),
|
|
vchan_id, session_id, payload_type, sizebytes);
|
|
vchan = NULL;
|
|
} else if (!kref_get_unless_zero(&vchan->refcount)) {
|
|
/*
|
|
* this happens when refcnt is already zero
|
|
* (put from other thread) or there is an actual error
|
|
*/
|
|
pr_err("failed to inc vcid %pK %x remote %x session %d refcnt %d header %x session %d type %d sz %zd\n",
|
|
vchan, vchan->id, vchan->otherend_id,
|
|
vchan->session_id, get_refcnt(vchan->refcount),
|
|
vchan_id, session_id, payload_type, sizebytes);
|
|
vchan = NULL;
|
|
}
|
|
}
|
|
hab_spin_unlock(&pchan->vid_lock, irqs_disabled);
|
|
|
|
return vchan;
|
|
}
|
|
|
|
/* wake up local waiting Q, so stop-vchan can be processed */
|
|
void hab_vchan_stop(struct virtual_channel *vchan)
|
|
{
|
|
if (vchan) {
|
|
vchan->otherend_closed = 1;
|
|
wake_up(&vchan->rx_queue);
|
|
if (vchan->ctx)
|
|
wake_up_interruptible(&vchan->ctx->exp_wq);
|
|
else
|
|
pr_err("NULL ctx for vchan %x\n", vchan->id);
|
|
}
|
|
}
|
|
|
|
void hab_vchans_stop(struct physical_channel *pchan)
|
|
{
|
|
struct virtual_channel *vchan, *tmp;
|
|
|
|
read_lock(&pchan->vchans_lock);
|
|
list_for_each_entry_safe(vchan, tmp, &pchan->vchannels, pnode) {
|
|
hab_vchan_stop(vchan);
|
|
}
|
|
read_unlock(&pchan->vchans_lock);
|
|
}
|
|
|
|
/* send vchan close to remote and stop receiving anything locally */
|
|
void hab_vchan_stop_notify(struct virtual_channel *vchan)
|
|
{
|
|
hab_send_close_msg(vchan);
|
|
hab_vchan_stop(vchan);
|
|
}
|
|
|
|
static int hab_vchans_per_pchan_empty(struct physical_channel *pchan)
|
|
{
|
|
int empty;
|
|
|
|
read_lock(&pchan->vchans_lock);
|
|
empty = list_empty(&pchan->vchannels);
|
|
if (!empty) {
|
|
struct virtual_channel *vchan;
|
|
int vcnt = pchan->vcnt;
|
|
|
|
list_for_each_entry(vchan, &pchan->vchannels, pnode) {
|
|
/* discount open-pending unpaired vchan */
|
|
if (!vchan->session_id)
|
|
vcnt--;
|
|
else
|
|
pr_err("vchan %pK %x rm %x sn %d rf %d clsd %d rm clsd %d\n",
|
|
vchan, vchan->id,
|
|
vchan->otherend_id,
|
|
vchan->session_id,
|
|
get_refcnt(vchan->refcount),
|
|
vchan->closed, vchan->otherend_closed);
|
|
}
|
|
if (!vcnt)
|
|
empty = 1;/* unpaired vchan can exist at init time */
|
|
}
|
|
read_unlock(&pchan->vchans_lock);
|
|
|
|
return empty;
|
|
}
|
|
|
|
static int hab_vchans_empty(int vmid)
|
|
{
|
|
int i, empty = 1;
|
|
struct physical_channel *pchan;
|
|
struct hab_device *hab_dev;
|
|
|
|
for (i = 0; i < hab_driver.ndevices; i++) {
|
|
hab_dev = &hab_driver.devp[i];
|
|
|
|
spin_lock_bh(&hab_dev->pchan_lock);
|
|
list_for_each_entry(pchan, &hab_dev->pchannels, node) {
|
|
if (pchan->vmid_remote == vmid) {
|
|
if (!hab_vchans_per_pchan_empty(pchan)) {
|
|
empty = 0;
|
|
spin_unlock_bh(&hab_dev->pchan_lock);
|
|
pr_info("vmid %d %s's vchans are not closed\n",
|
|
vmid, pchan->name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
spin_unlock_bh(&hab_dev->pchan_lock);
|
|
}
|
|
|
|
return empty;
|
|
}
|
|
|
|
/*
|
|
* block until all vchans of a given GVM are explicitly closed
|
|
* with habmm_socket_close() by hab clients themselves
|
|
*/
|
|
void hab_vchans_empty_wait(int vmid)
|
|
{
|
|
pr_info("waiting for GVM%d's sockets closure\n", vmid);
|
|
|
|
while (!hab_vchans_empty(vmid))
|
|
usleep_range(10000, 12000);
|
|
|
|
pr_info("all of GVM%d's sockets are closed\n", vmid);
|
|
}
|
|
|
|
int hab_vchan_find_domid(struct virtual_channel *vchan)
|
|
{
|
|
return vchan ? vchan->pchan->dom_id : -1;
|
|
}
|
|
|
|
void hab_vchan_put(struct virtual_channel *vchan)
|
|
{
|
|
if (vchan)
|
|
kref_put(&vchan->refcount, hab_vchan_free);
|
|
}
|
|
|
|
int hab_vchan_query(struct uhab_context *ctx, int32_t vcid, uint64_t *ids,
|
|
char *names, size_t name_size, uint32_t flags)
|
|
{
|
|
struct virtual_channel *vchan;
|
|
|
|
vchan = hab_get_vchan_fromvcid(vcid, ctx, 1);
|
|
if (!vchan)
|
|
return -EINVAL;
|
|
|
|
if (vchan->otherend_closed) {
|
|
hab_vchan_put(vchan);
|
|
return -ENODEV;
|
|
}
|
|
|
|
*ids = vchan->pchan->vmid_local |
|
|
((uint64_t)vchan->pchan->vmid_remote) << 32;
|
|
names[0] = 0;
|
|
names[name_size/2] = 0;
|
|
|
|
hab_vchan_put(vchan);
|
|
|
|
return 0;
|
|
}
|