457 lines
10 KiB
C
457 lines
10 KiB
C
/* Copyright (c) 2016-2020, 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"
|
|
#include "hab_grantable.h"
|
|
|
|
/*
|
|
* use physical channel to send export parcel
|
|
|
|
* local remote
|
|
* send(export) --> IRQ store to export warehouse
|
|
* wait(export ack) <-- send(export ack)
|
|
|
|
* the actual data consists the following 3 parts listed in order
|
|
* 1. header (uint32_t) vcid|type|size
|
|
* 2. export parcel (full struct)
|
|
* 3. full contents in export->pdata
|
|
*/
|
|
|
|
|
|
static int hab_export_ack_find(struct uhab_context *ctx,
|
|
struct hab_export_ack *expect_ack, struct virtual_channel *vchan)
|
|
{
|
|
int ret = 0;
|
|
struct hab_export_ack_recvd *ack_recvd, *tmp;
|
|
|
|
spin_lock_bh(&ctx->expq_lock);
|
|
|
|
list_for_each_entry_safe(ack_recvd, tmp, &ctx->exp_rxq, node) {
|
|
if ((ack_recvd->ack.export_id == expect_ack->export_id &&
|
|
ack_recvd->ack.vcid_local == expect_ack->vcid_local &&
|
|
ack_recvd->ack.vcid_remote == expect_ack->vcid_remote)
|
|
|| vchan->otherend_closed) {
|
|
list_del(&ack_recvd->node);
|
|
kfree(ack_recvd);
|
|
ret = 1;
|
|
break;
|
|
}
|
|
ack_recvd->age++;
|
|
if (ack_recvd->age > Q_AGE_THRESHOLD) {
|
|
list_del(&ack_recvd->node);
|
|
kfree(ack_recvd);
|
|
}
|
|
}
|
|
|
|
spin_unlock_bh(&ctx->expq_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int hab_export_ack_wait(struct uhab_context *ctx,
|
|
struct hab_export_ack *expect_ack, struct virtual_channel *vchan)
|
|
{
|
|
int ret;
|
|
|
|
ret = wait_event_interruptible_timeout(ctx->exp_wq,
|
|
hab_export_ack_find(ctx, expect_ack, vchan),
|
|
HAB_HS_TIMEOUT);
|
|
if (!ret || (ret == -ERESTARTSYS))
|
|
ret = -EAGAIN;
|
|
else if (vchan->otherend_closed)
|
|
ret = -ENODEV;
|
|
else if (ret > 0)
|
|
ret = 0;
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Get id from free list first. if not available, new id is generated.
|
|
* Once generated it will not be erased
|
|
* assumptions: no handshake or memory map/unmap in this helper function
|
|
*/
|
|
struct export_desc_super *habmem_add_export(
|
|
struct virtual_channel *vchan,
|
|
int sizebytes,
|
|
uint32_t flags)
|
|
{
|
|
struct uhab_context *ctx = NULL;
|
|
struct export_desc *exp = NULL;
|
|
struct export_desc_super *exp_super = NULL;
|
|
|
|
if (!vchan || !sizebytes)
|
|
return NULL;
|
|
|
|
exp_super = kzalloc(sizebytes, GFP_KERNEL);
|
|
if (!exp_super)
|
|
return NULL;
|
|
|
|
exp = &exp_super->exp;
|
|
idr_preload(GFP_KERNEL);
|
|
spin_lock(&vchan->pchan->expid_lock);
|
|
exp->export_id =
|
|
idr_alloc(&vchan->pchan->expid_idr, exp, 1, 0, GFP_NOWAIT);
|
|
spin_unlock(&vchan->pchan->expid_lock);
|
|
idr_preload_end();
|
|
|
|
exp->readonly = flags;
|
|
exp->vchan = vchan;
|
|
exp->vcid_local = vchan->id;
|
|
exp->vcid_remote = vchan->otherend_id;
|
|
exp->domid_local = vchan->pchan->vmid_local;
|
|
exp->domid_remote = vchan->pchan->vmid_remote;
|
|
exp->ctx = vchan->ctx;
|
|
exp->pchan = vchan->pchan;
|
|
|
|
ctx = vchan->ctx;
|
|
write_lock(&ctx->exp_lock);
|
|
ctx->export_total++;
|
|
list_add_tail(&exp->node, &ctx->exp_whse);
|
|
write_unlock(&ctx->exp_lock);
|
|
|
|
return exp_super;
|
|
}
|
|
|
|
void habmem_remove_export(struct export_desc *exp)
|
|
{
|
|
struct uhab_context *ctx = NULL;
|
|
struct export_desc_super *exp_super =
|
|
container_of(exp,
|
|
struct export_desc_super,
|
|
exp);
|
|
|
|
if (!exp || !exp->ctx) {
|
|
if (exp)
|
|
pr_err("invalid info in exp %pK ctx %pK\n",
|
|
exp, exp->ctx);
|
|
else
|
|
pr_err("invalid exp\n");
|
|
return;
|
|
}
|
|
|
|
ctx = exp->ctx;
|
|
ctx->export_total--;
|
|
exp->ctx = NULL;
|
|
|
|
habmem_export_put(exp_super);
|
|
}
|
|
|
|
static void habmem_export_destroy(struct kref *refcount)
|
|
{
|
|
struct physical_channel *pchan = NULL;
|
|
struct export_desc_super *exp_super =
|
|
container_of(
|
|
refcount,
|
|
struct export_desc_super,
|
|
refcount);
|
|
struct export_desc *exp = NULL;
|
|
|
|
if (!exp_super) {
|
|
pr_err("invalid exp_super\n");
|
|
return;
|
|
}
|
|
|
|
exp = &exp_super->exp;
|
|
if (!exp || !exp->pchan) {
|
|
if (exp)
|
|
pr_err("invalid info in exp %pK pchan %pK\n",
|
|
exp, exp->pchan);
|
|
else
|
|
pr_err("invalid exp\n");
|
|
return;
|
|
}
|
|
|
|
pchan = exp->pchan;
|
|
|
|
spin_lock(&pchan->expid_lock);
|
|
idr_remove(&pchan->expid_idr, exp->export_id);
|
|
spin_unlock(&pchan->expid_lock);
|
|
|
|
habmem_exp_release(exp_super);
|
|
kfree(exp_super);
|
|
}
|
|
|
|
/*
|
|
* store the parcel to the warehouse, then send the parcel to remote side
|
|
* both exporter composed export descriptor and the grantrefids are sent
|
|
* as one msg to the importer side
|
|
*/
|
|
static int habmem_export_vchan(struct uhab_context *ctx,
|
|
struct virtual_channel *vchan,
|
|
int payload_size,
|
|
uint32_t flags,
|
|
uint32_t export_id)
|
|
{
|
|
int ret;
|
|
struct export_desc *exp;
|
|
uint32_t sizebytes = sizeof(*exp) + payload_size;
|
|
struct hab_export_ack expected_ack = {0};
|
|
struct hab_header header = HAB_HEADER_INITIALIZER;
|
|
|
|
exp = idr_find(&vchan->pchan->expid_idr, export_id);
|
|
if (!exp) {
|
|
pr_err("export vchan failed: exp_id %d, pchan %s\n",
|
|
export_id, vchan->pchan->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
HAB_HEADER_SET_SIZE(header, sizebytes);
|
|
HAB_HEADER_SET_TYPE(header, HAB_PAYLOAD_TYPE_EXPORT);
|
|
HAB_HEADER_SET_ID(header, vchan->otherend_id);
|
|
HAB_HEADER_SET_SESSION_ID(header, vchan->session_id);
|
|
ret = physical_channel_send(vchan->pchan, &header, exp);
|
|
|
|
if (ret != 0) {
|
|
pr_err("failed to export payload to the remote %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
expected_ack.export_id = exp->export_id;
|
|
expected_ack.vcid_local = exp->vcid_local;
|
|
expected_ack.vcid_remote = exp->vcid_remote;
|
|
ret = hab_export_ack_wait(ctx, &expected_ack, vchan);
|
|
if (ret != 0) {
|
|
pr_err("failed to receive remote export ack %d on vc %x\n",
|
|
ret, vchan->id);
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void habmem_export_get(struct export_desc_super *exp_super)
|
|
{
|
|
kref_get(&exp_super->refcount);
|
|
}
|
|
|
|
int habmem_export_put(struct export_desc_super *exp_super)
|
|
{
|
|
return kref_put(&exp_super->refcount, habmem_export_destroy);
|
|
}
|
|
|
|
int hab_mem_export(struct uhab_context *ctx,
|
|
struct hab_export *param,
|
|
int kernel)
|
|
{
|
|
int ret = 0;
|
|
unsigned int payload_size = 0;
|
|
uint32_t export_id = 0;
|
|
struct virtual_channel *vchan;
|
|
int page_count;
|
|
int compressed = 0;
|
|
|
|
if (!ctx || !param || !param->buffer || !param->sizebytes
|
|
|| ((param->sizebytes % PAGE_SIZE) != 0))
|
|
return -EINVAL;
|
|
|
|
vchan = hab_get_vchan_fromvcid(param->vcid, ctx, 0);
|
|
if (!vchan || !vchan->pchan) {
|
|
ret = -ENODEV;
|
|
goto err;
|
|
}
|
|
|
|
page_count = param->sizebytes/PAGE_SIZE;
|
|
if (kernel) {
|
|
ret = habmem_hyp_grant(vchan,
|
|
(unsigned long)param->buffer,
|
|
page_count,
|
|
param->flags,
|
|
vchan->pchan->dom_id,
|
|
&compressed,
|
|
&payload_size,
|
|
&export_id);
|
|
} else {
|
|
ret = habmem_hyp_grant_user(vchan,
|
|
(unsigned long)param->buffer,
|
|
page_count,
|
|
param->flags,
|
|
vchan->pchan->dom_id,
|
|
&compressed,
|
|
&payload_size,
|
|
&export_id);
|
|
}
|
|
if (ret < 0) {
|
|
pr_err("habmem_hyp_grant vc %x failed size=%d ret=%d\n",
|
|
param->vcid, payload_size, ret);
|
|
goto err;
|
|
}
|
|
|
|
ret = habmem_export_vchan(ctx,
|
|
vchan,
|
|
payload_size,
|
|
param->flags,
|
|
export_id);
|
|
|
|
param->exportid = export_id;
|
|
err:
|
|
if (vchan)
|
|
hab_vchan_put(vchan);
|
|
return ret;
|
|
}
|
|
|
|
int hab_mem_unexport(struct uhab_context *ctx,
|
|
struct hab_unexport *param,
|
|
int kernel)
|
|
{
|
|
int ret = 0, found = 0;
|
|
struct export_desc *exp = NULL, *tmp = NULL;
|
|
struct virtual_channel *vchan;
|
|
|
|
if (!ctx || !param)
|
|
return -EINVAL;
|
|
|
|
/* refcnt on the access */
|
|
vchan = hab_get_vchan_fromvcid(param->vcid, ctx, 1);
|
|
if (!vchan || !vchan->pchan) {
|
|
ret = -ENODEV;
|
|
goto err_novchan;
|
|
}
|
|
|
|
write_lock(&ctx->exp_lock);
|
|
list_for_each_entry_safe(exp, tmp, &ctx->exp_whse, node) {
|
|
if (param->exportid == exp->export_id &&
|
|
vchan->pchan == exp->pchan) {
|
|
list_del(&exp->node);
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
write_unlock(&ctx->exp_lock);
|
|
|
|
if (!found) {
|
|
ret = -EINVAL;
|
|
goto err_novchan;
|
|
}
|
|
|
|
ret = habmem_hyp_revoke(exp->payload, exp->payload_count);
|
|
if (ret) {
|
|
pr_err("Error found in revoke grant with ret %d", ret);
|
|
goto err_novchan;
|
|
}
|
|
habmem_remove_export(exp);
|
|
|
|
err_novchan:
|
|
if (vchan)
|
|
hab_vchan_put(vchan);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int hab_mem_import(struct uhab_context *ctx,
|
|
struct hab_import *param,
|
|
int kernel)
|
|
{
|
|
int ret = 0, found = 0;
|
|
struct export_desc *exp = NULL;
|
|
struct virtual_channel *vchan;
|
|
|
|
if (!ctx || !param)
|
|
return -EINVAL;
|
|
|
|
vchan = hab_get_vchan_fromvcid(param->vcid, ctx, 0);
|
|
if (!vchan || !vchan->pchan) {
|
|
ret = -ENODEV;
|
|
goto err_imp;
|
|
}
|
|
|
|
spin_lock_bh(&ctx->imp_lock);
|
|
list_for_each_entry(exp, &ctx->imp_whse, node) {
|
|
if ((exp->export_id == param->exportid) &&
|
|
(exp->pchan == vchan->pchan)) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_bh(&ctx->imp_lock);
|
|
|
|
if ((exp->payload_count << PAGE_SHIFT) != param->sizebytes) {
|
|
pr_err("input size %d don't match buffer size %d\n",
|
|
param->sizebytes, exp->payload_count << PAGE_SHIFT);
|
|
ret = -EINVAL;
|
|
goto err_imp;
|
|
}
|
|
|
|
if (!found) {
|
|
pr_err("Fail to get export descriptor from export id %d\n",
|
|
param->exportid);
|
|
ret = -ENODEV;
|
|
goto err_imp;
|
|
}
|
|
|
|
ret = habmem_imp_hyp_map(ctx->import_ctx, param, exp, kernel);
|
|
|
|
if (ret) {
|
|
pr_err("Import fail ret:%d pcnt:%d rem:%d 1st_ref:0x%X\n",
|
|
ret, exp->payload_count,
|
|
exp->domid_local, *((uint32_t *)exp->payload));
|
|
goto err_imp;
|
|
}
|
|
|
|
exp->import_index = param->index;
|
|
exp->kva = kernel ? (void *)param->kva : NULL;
|
|
|
|
err_imp:
|
|
if (vchan)
|
|
hab_vchan_put(vchan);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int hab_mem_unimport(struct uhab_context *ctx,
|
|
struct hab_unimport *param,
|
|
int kernel)
|
|
{
|
|
int ret = 0, found = 0;
|
|
struct export_desc *exp = NULL, *exp_tmp;
|
|
struct virtual_channel *vchan;
|
|
|
|
if (!ctx || !param)
|
|
return -EINVAL;
|
|
|
|
vchan = hab_get_vchan_fromvcid(param->vcid, ctx, 1);
|
|
if (!vchan || !vchan->pchan) {
|
|
if (vchan)
|
|
hab_vchan_put(vchan);
|
|
return -ENODEV;
|
|
}
|
|
|
|
spin_lock_bh(&ctx->imp_lock);
|
|
list_for_each_entry_safe(exp, exp_tmp, &ctx->imp_whse, node) {
|
|
if (exp->export_id == param->exportid &&
|
|
exp->pchan == vchan->pchan) {
|
|
/* same pchan is expected here */
|
|
list_del(&exp->node);
|
|
ctx->import_total--;
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_bh(&ctx->imp_lock);
|
|
|
|
if (!found)
|
|
ret = -EINVAL;
|
|
else {
|
|
ret = habmm_imp_hyp_unmap(ctx->import_ctx, exp, kernel);
|
|
if (ret) {
|
|
pr_err("unmap fail id:%d pcnt:%d vcid:%d\n",
|
|
exp->export_id, exp->payload_count, exp->vcid_remote);
|
|
}
|
|
param->kva = (uint64_t)exp->kva;
|
|
kfree(exp);
|
|
}
|
|
|
|
if (vchan)
|
|
hab_vchan_put(vchan);
|
|
|
|
return ret;
|
|
}
|