Files
android_kernel_eebbk_sm6150/drivers/soc/qcom/hab/hab_vchan.c
T

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