Files

409 lines
11 KiB
C

/* Copyright (c) 2016-2019, 2021, 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"
static int hab_rx_queue_empty(struct virtual_channel *vchan)
{
int ret;
int irqs_disabled = irqs_disabled();
hab_spin_lock(&vchan->rx_lock, irqs_disabled);
ret = list_empty(&vchan->rx_list);
hab_spin_unlock(&vchan->rx_lock, irqs_disabled);
return ret;
}
static struct hab_message*
hab_msg_alloc(struct physical_channel *pchan, size_t sizebytes)
{
struct hab_message *message;
if (sizebytes > HAB_HEADER_SIZE_MASK) {
pr_err("pchan %s send size too large %zd\n",
pchan->name, sizebytes);
return NULL;
}
message = kzalloc(sizeof(*message) + sizebytes, GFP_ATOMIC);
if (!message)
return NULL;
message->sizebytes =
physical_channel_read(pchan, message->data, sizebytes);
return message;
}
void hab_msg_free(struct hab_message *message)
{
kfree(message);
}
int
hab_msg_dequeue(struct virtual_channel *vchan, struct hab_message **msg,
int *rsize, unsigned int flags)
{
struct hab_message *message = NULL;
int ret = 0;
int wait = !(flags & HABMM_SOCKET_RECV_FLAGS_NON_BLOCKING);
int interruptible = !(flags & HABMM_SOCKET_RECV_FLAGS_UNINTERRUPTIBLE);
int irqs_disabled = irqs_disabled();
if (wait) {
if (hab_rx_queue_empty(vchan)) {
if (interruptible)
ret = wait_event_interruptible(vchan->rx_queue,
!hab_rx_queue_empty(vchan) ||
vchan->otherend_closed);
else
wait_event(vchan->rx_queue,
!hab_rx_queue_empty(vchan) ||
vchan->otherend_closed);
}
}
/*
* return all the received messages before the remote close,
* and need empty check again in case the list is empty now due to
* dequeue by other threads
*/
hab_spin_lock(&vchan->rx_lock, irqs_disabled);
if ((!ret || (ret == -ERESTARTSYS)) && !list_empty(&vchan->rx_list)) {
message = list_first_entry(&vchan->rx_list,
struct hab_message, node);
if (message) {
if (*rsize >= message->sizebytes) {
/* msg can be safely retrieved in full */
list_del(&message->node);
ret = 0;
*rsize = message->sizebytes;
} else {
pr_err("vcid %x rcv buf too small %d < %zd\n",
vchan->id, *rsize,
message->sizebytes);
*rsize = message->sizebytes;
message = NULL;
ret = -EOVERFLOW; /* come back again */
}
}
} else
/* no message received, retain the original status */
*rsize = 0;
hab_spin_unlock(&vchan->rx_lock, irqs_disabled);
*msg = message;
return ret;
}
static void hab_msg_queue(struct virtual_channel *vchan,
struct hab_message *message)
{
int irqs_disabled = irqs_disabled();
hab_spin_lock(&vchan->rx_lock, irqs_disabled);
list_add_tail(&message->node, &vchan->rx_list);
hab_spin_unlock(&vchan->rx_lock, irqs_disabled);
wake_up(&vchan->rx_queue);
}
static int hab_export_enqueue(struct virtual_channel *vchan,
struct export_desc *exp)
{
struct uhab_context *ctx = vchan->ctx;
int irqs_disabled = irqs_disabled();
hab_spin_lock(&ctx->imp_lock, irqs_disabled);
list_add_tail(&exp->node, &ctx->imp_whse);
ctx->import_total++;
hab_spin_unlock(&ctx->imp_lock, irqs_disabled);
return 0;
}
static int hab_send_export_ack(struct virtual_channel *vchan,
struct physical_channel *pchan,
struct export_desc *exp)
{
struct hab_export_ack exp_ack = {
.export_id = exp->export_id,
.vcid_local = exp->vcid_local,
.vcid_remote = exp->vcid_remote
};
struct hab_header header = HAB_HEADER_INITIALIZER;
HAB_HEADER_SET_SIZE(header, sizeof(exp_ack));
HAB_HEADER_SET_TYPE(header, HAB_PAYLOAD_TYPE_EXPORT_ACK);
HAB_HEADER_SET_ID(header, exp->vcid_local);
HAB_HEADER_SET_SESSION_ID(header, vchan->session_id);
return physical_channel_send(pchan, &header, &exp_ack);
}
static int hab_receive_create_export_ack(struct physical_channel *pchan,
struct uhab_context *ctx, size_t sizebytes)
{
struct hab_export_ack_recvd *ack_recvd =
kzalloc(sizeof(*ack_recvd), GFP_ATOMIC);
int irqs_disabled = irqs_disabled();
if (!ack_recvd)
return -ENOMEM;
if (sizeof(ack_recvd->ack) != sizebytes)
pr_err("%s exp ack size %zu is not as arrived %zu\n",
pchan->name, sizeof(ack_recvd->ack), sizebytes);
if (sizebytes > sizeof(ack_recvd->ack)) {
pr_err("pchan %s read size too large %zd %zd\n",
pchan->name, sizebytes, sizeof(ack_recvd->ack));
kfree(ack_recvd);
return -EINVAL;
}
if (physical_channel_read(pchan,
&ack_recvd->ack,
sizebytes) != sizebytes) {
kfree(ack_recvd);
return -EIO;
}
hab_spin_lock(&ctx->expq_lock, irqs_disabled);
list_add_tail(&ack_recvd->node, &ctx->exp_rxq);
hab_spin_unlock(&ctx->expq_lock, irqs_disabled);
return 0;
}
static void hab_msg_drop(struct physical_channel *pchan, size_t sizebytes)
{
uint8_t *data = NULL;
if (sizebytes > HAB_HEADER_SIZE_MASK) {
pr_err("%s read size too large %zd\n", pchan->name, sizebytes);
return;
}
data = kmalloc(sizebytes, GFP_ATOMIC);
if (data == NULL)
return;
physical_channel_read(pchan, data, sizebytes);
kfree(data);
}
int hab_msg_recv(struct physical_channel *pchan,
struct hab_header *header)
{
int ret = 0;
struct hab_message *message;
struct hab_device *dev = pchan->habdev;
size_t sizebytes = HAB_HEADER_GET_SIZE(*header);
uint32_t payload_type = HAB_HEADER_GET_TYPE(*header);
uint32_t vchan_id = HAB_HEADER_GET_ID(*header);
uint32_t session_id = HAB_HEADER_GET_SESSION_ID(*header);
struct virtual_channel *vchan = NULL;
struct export_desc *exp_desc;
struct timeval tv;
unsigned long long rx_mpm_tv;
/* get the local virtual channel if it isn't an open message */
if (payload_type != HAB_PAYLOAD_TYPE_INIT &&
payload_type != HAB_PAYLOAD_TYPE_INIT_ACK &&
payload_type != HAB_PAYLOAD_TYPE_INIT_DONE &&
payload_type != HAB_PAYLOAD_TYPE_INIT_CANCEL) {
/* sanity check the received message */
if (payload_type >= HAB_PAYLOAD_TYPE_MAX ||
vchan_id > (HAB_HEADER_ID_MASK >> HAB_HEADER_ID_SHIFT)
|| !vchan_id || !session_id) {
pr_err("@@ %s Invalid msg type %d vcid %x bytes %zx sn %d\n",
pchan->name, payload_type,
vchan_id, sizebytes, session_id);
dump_hab_wq(pchan->hyp_data);
}
/*
* need both vcid and session_id to be accurate.
* this is from pchan instead of ctx
*/
vchan = hab_vchan_get(pchan, header);
if (!vchan) {
pr_debug("vchan not found type %d vcid %x sz %zx sesn %d\n",
payload_type, vchan_id, sizebytes, session_id);
if (sizebytes) {
hab_msg_drop(pchan, sizebytes);
pr_err("%s msg dropped type %d size %d vcid %X session id %d\n",
pchan->name, payload_type,
sizebytes, vchan_id,
session_id);
}
return -EINVAL;
} else if (vchan->otherend_closed) {
hab_vchan_put(vchan);
pr_info("vchan remote is closed payload type %d, vchan id %x, sizebytes %zx, session %d\n",
payload_type, vchan_id,
sizebytes, session_id);
if (sizebytes) {
hab_msg_drop(pchan, sizebytes);
pr_err("%s message %d dropped remote close, session id %d\n",
pchan->name, payload_type,
session_id);
}
return -ENODEV;
}
} else {
if (sizebytes != sizeof(struct hab_open_send_data)) {
pr_err("%s Invalid open req type %d vcid %x bytes %zx session %d\n",
pchan->name, payload_type, vchan_id,
sizebytes, session_id);
if (sizebytes) {
hab_msg_drop(pchan, sizebytes);
pr_err("%s msg %d dropped unknown reason session id %d\n",
pchan->name,
payload_type,
session_id);
dump_hab_wq(pchan->hyp_data);
}
return -ENODEV;
}
}
switch (payload_type) {
case HAB_PAYLOAD_TYPE_MSG:
case HAB_PAYLOAD_TYPE_SCHE_RESULT_REQ:
case HAB_PAYLOAD_TYPE_SCHE_RESULT_RSP:
message = hab_msg_alloc(pchan, sizebytes);
if (!message)
break;
hab_msg_queue(vchan, message);
break;
case HAB_PAYLOAD_TYPE_INIT:
case HAB_PAYLOAD_TYPE_INIT_ACK:
case HAB_PAYLOAD_TYPE_INIT_DONE:
ret = hab_open_request_add(pchan, sizebytes, payload_type);
if (ret) {
pr_err("%s open request add failed, ret %d, payload type %d, sizebytes %zx\n",
pchan->name, ret, payload_type, sizebytes);
break;
}
wake_up_interruptible(&dev->openq);
break;
case HAB_PAYLOAD_TYPE_INIT_CANCEL:
pr_info("remote open cancel header vcid %X session %d local %d remote %d\n",
vchan_id, session_id, pchan->vmid_local,
pchan->vmid_remote);
ret = hab_open_receive_cancel(pchan, sizebytes);
if (ret)
pr_err("%s open cancel handling failed ret %d vcid %X session %d\n",
pchan->name, ret, vchan_id, session_id);
break;
case HAB_PAYLOAD_TYPE_EXPORT:
if (sizebytes > HAB_HEADER_SIZE_MASK) {
pr_err("%s exp size too large %zd header %zd\n",
pchan->name, sizebytes, sizeof(*exp_desc));
break;
}
exp_desc = kzalloc(sizebytes, GFP_ATOMIC);
if (!exp_desc)
break;
if (physical_channel_read(pchan, exp_desc, sizebytes) !=
sizebytes) {
pr_err("%s corrupted exp expect %zd bytes vcid %X remote %X open %d!\n",
pchan->name, sizebytes, vchan->id,
vchan->otherend_id, vchan->session_id);
kfree(exp_desc);
break;
}
if (pchan->vmid_local != exp_desc->domid_remote ||
pchan->vmid_remote != exp_desc->domid_local)
pr_err("%s corrupted vmid %d != %d %d != %d\n",
pchan->name, pchan->vmid_local, exp_desc->domid_remote,
pchan->vmid_remote, exp_desc->domid_local);
exp_desc->domid_remote = pchan->vmid_remote;
exp_desc->domid_local = pchan->vmid_local;
exp_desc->pchan = pchan;
hab_export_enqueue(vchan, exp_desc);
hab_send_export_ack(vchan, pchan, exp_desc);
break;
case HAB_PAYLOAD_TYPE_EXPORT_ACK:
ret = hab_receive_create_export_ack(pchan, vchan->ctx,
sizebytes);
if (ret) {
pr_err("%s failed to handled export ack %d\n",
pchan->name, ret);
break;
}
wake_up_interruptible(&vchan->ctx->exp_wq);
break;
case HAB_PAYLOAD_TYPE_CLOSE:
/* remote request close */
pr_debug("remote close vcid %pK %X other id %X session %d refcnt %d\n",
vchan, vchan->id, vchan->otherend_id,
session_id, get_refcnt(vchan->refcount));
hab_vchan_stop(vchan);
break;
case HAB_PAYLOAD_TYPE_PROFILE:
do_gettimeofday(&tv);
/* pull down the incoming data */
message = hab_msg_alloc(pchan, sizebytes);
if (!message)
pr_err("%s failed to allocate msg Arrived msg will be lost\n",
pchan->name);
else {
struct habmm_xing_vm_stat *pstat =
(struct habmm_xing_vm_stat *)message->data;
pstat->rx_sec = tv.tv_sec;
pstat->rx_usec = tv.tv_usec;
hab_msg_queue(vchan, message);
}
break;
case HAB_PAYLOAD_TYPE_SCHE_MSG:
case HAB_PAYLOAD_TYPE_SCHE_MSG_ACK:
rx_mpm_tv = msm_timer_get_sclk_ticks();
/* pull down the incoming data */
message = hab_msg_alloc(pchan, sizebytes);
if (!message)
pr_err("%s failed to allocate msg Arrived msg will be lost\n",
pchan->name);
else {
((unsigned long long *)message->data)[0] = rx_mpm_tv;
hab_msg_queue(vchan, message);
}
break;
default:
pr_err("%s unknown msg received, payload type %d, vchan id %x, sizebytes %zx, session %d\n",
pchan->name, payload_type, vchan_id,
sizebytes, session_id);
break;
}
if (vchan)
hab_vchan_put(vchan);
return ret;
}