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

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