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

313 lines
8.4 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"
#define HAB_OPEN_REQ_EXPIRE_TIME_S (3600*10)
void hab_open_request_init(struct hab_open_request *request,
int type,
struct physical_channel *pchan,
int vchan_id,
int sub_id,
int open_id)
{
request->type = type;
request->pchan = pchan;
request->xdata.vchan_id = vchan_id;
request->xdata.sub_id = sub_id;
request->xdata.open_id = open_id;
}
int hab_open_request_send(struct hab_open_request *request)
{
struct hab_header header = HAB_HEADER_INITIALIZER;
HAB_HEADER_SET_SIZE(header, sizeof(struct hab_open_send_data));
HAB_HEADER_SET_TYPE(header, request->type);
return physical_channel_send(request->pchan, &header, &request->xdata);
}
/* called when remote sends in open-request */
int hab_open_request_add(struct physical_channel *pchan,
size_t sizebytes, int request_type)
{
struct hab_open_node *node;
struct hab_device *dev = pchan->habdev;
struct hab_open_request *request;
struct timeval tv;
int irqs_disabled = irqs_disabled();
if (sizebytes > HAB_HEADER_SIZE_MASK) {
pr_err("pchan %s request size too large %zd\n",
pchan->name, sizebytes);
return -EINVAL;
}
node = kzalloc(sizeof(*node), GFP_ATOMIC);
if (!node)
return -ENOMEM;
request = &node->request;
if (physical_channel_read(pchan, &request->xdata, sizebytes)
!= sizebytes)
return -EIO;
request->type = request_type;
request->pchan = pchan;
do_gettimeofday(&tv);
node->age = tv.tv_sec + HAB_OPEN_REQ_EXPIRE_TIME_S +
tv.tv_usec/1000000;
hab_pchan_get(pchan);
hab_spin_lock(&dev->openlock, irqs_disabled);
list_add_tail(&node->node, &dev->openq_list);
dev->openq_cnt++;
hab_spin_unlock(&dev->openlock, irqs_disabled);
return 0;
}
/* local only */
static int hab_open_request_find(struct uhab_context *ctx,
struct hab_device *dev,
struct hab_open_request *listen,
struct hab_open_request **recv_request)
{
struct hab_open_node *node, *tmp;
struct hab_open_request *request;
struct timeval tv;
int ret = 0;
if (ctx->closing ||
(listen->pchan && listen->pchan->closed)) {
*recv_request = NULL;
return 1;
}
spin_lock_bh(&dev->openlock);
if (list_empty(&dev->openq_list))
goto done;
do_gettimeofday(&tv);
list_for_each_entry_safe(node, tmp, &dev->openq_list, node) {
request = (struct hab_open_request *)node;
if ((request->type == listen->type ||
request->type == HAB_PAYLOAD_TYPE_INIT_CANCEL) &&
(request->xdata.sub_id == listen->xdata.sub_id) &&
(!listen->xdata.open_id ||
request->xdata.open_id == listen->xdata.open_id) &&
(!listen->pchan ||
request->pchan == listen->pchan)) {
list_del(&node->node);
dev->openq_cnt--;
*recv_request = request;
ret = 1;
break;
}
if (node->age < (int64_t)tv.tv_sec + tv.tv_usec/1000000) {
pr_warn("open request type %d sub %d open %d\n",
request->type, request->xdata.sub_id,
request->xdata.sub_id);
list_del(&node->node);
hab_open_request_free(request);
}
}
done:
spin_unlock_bh(&dev->openlock);
return ret;
}
void hab_open_request_free(struct hab_open_request *request)
{
if (request) {
hab_pchan_put(request->pchan);
kfree(request);
} else
pr_err("empty request found\n");
}
int hab_open_listen(struct uhab_context *ctx,
struct hab_device *dev,
struct hab_open_request *listen,
struct hab_open_request **recv_request,
int ms_timeout)
{
int ret = 0;
if (!ctx || !listen || !recv_request) {
pr_err("listen failed ctx %pK listen %pK request %pK\n",
ctx, listen, recv_request);
return -EINVAL;
}
*recv_request = NULL;
if (ms_timeout > 0) { /* be case */
ms_timeout = msecs_to_jiffies(ms_timeout);
ret = wait_event_interruptible_timeout(dev->openq,
hab_open_request_find(ctx, dev, listen, recv_request),
ms_timeout);
if (!ret) {
pr_debug("%s timeout in open listen\n", dev->name);
ret = -EAGAIN; /* condition not met */
} else if (-ERESTARTSYS == ret) {
pr_warn("something failed in open listen ret %d\n",
ret);
ret = -EINTR; /* condition not met */
} else if (ret > 0)
ret = 0; /* condition met */
} else { /* fe case */
ret = wait_event_interruptible(dev->openq,
hab_open_request_find(ctx, dev, listen, recv_request));
if (ctx->closing) {
pr_warn("local closing during open ret %d\n", ret);
ret = -ENODEV;
} else if (-ERESTARTSYS == ret) {
pr_warn("local interrupted ret %d\n", ret);
ret = -EINTR;
}
}
return ret;
}
/* called when receives remote's cancel init from FE or init-ack from BE */
int hab_open_receive_cancel(struct physical_channel *pchan,
size_t sizebytes)
{
struct hab_device *dev = pchan->habdev;
struct hab_open_send_data data;
struct hab_open_request *request;
struct hab_open_node *node, *tmp;
int bfound = 0;
struct timeval tv;
int irqs_disabled = irqs_disabled();
if (sizebytes > HAB_HEADER_SIZE_MASK) {
pr_err("pchan %s cancel size too large %zd\n",
pchan->name, sizebytes);
return -EINVAL;
}
if (physical_channel_read(pchan, &data, sizebytes) != sizebytes)
return -EIO;
hab_spin_lock(&dev->openlock, irqs_disabled);
list_for_each_entry_safe(node, tmp, &dev->openq_list, node) {
request = &node->request;
/* check if open request has been serviced or not */
if ((request->type == HAB_PAYLOAD_TYPE_INIT ||
request->type == HAB_PAYLOAD_TYPE_INIT_ACK) &&
(request->xdata.sub_id == data.sub_id) &&
(request->xdata.open_id == data.open_id) &&
(request->xdata.vchan_id == data.vchan_id)) {
list_del(&node->node);
dev->openq_cnt--;
pr_info("open cancelled on pchan %s vcid %x subid %d openid %d\n",
pchan->name, data.vchan_id,
data.sub_id, data.open_id);
/* found un-serviced open request, delete it */
bfound = 1;
break;
}
}
hab_spin_unlock(&dev->openlock, irqs_disabled);
if (!bfound) {
pr_info("init waiting is in-flight. vcid %x sub %d open %d\n",
data.vchan_id, data.sub_id, data.open_id);
/* add cancel to the openq to let the waiting open bail out */
node = kzalloc(sizeof(*node), GFP_ATOMIC);
if (!node)
return -ENOMEM;
request = &node->request;
request->type = HAB_PAYLOAD_TYPE_INIT_CANCEL;
request->pchan = pchan;
request->xdata.vchan_id = data.vchan_id;
request->xdata.sub_id = data.sub_id;
request->xdata.open_id = data.open_id;
request->xdata.ver_fe = data.ver_fe;
request->xdata.ver_be = data.ver_be;
do_gettimeofday(&tv);
node->age = tv.tv_sec + HAB_OPEN_REQ_EXPIRE_TIME_S +
tv.tv_usec/1000000;
/* put when this node is handled in open path */
hab_pchan_get(pchan);
hab_spin_lock(&dev->openlock, irqs_disabled);
list_add_tail(&node->node, &dev->openq_list);
dev->openq_cnt++;
hab_spin_unlock(&dev->openlock, irqs_disabled);
wake_up_interruptible(&dev->openq);
}
return 0;
}
/* calls locally to send cancel pending open to remote */
int hab_open_cancel_notify(struct hab_open_request *request)
{
struct hab_header header = HAB_HEADER_INITIALIZER;
HAB_HEADER_SET_SIZE(header, sizeof(struct hab_open_send_data));
HAB_HEADER_SET_TYPE(header, HAB_PAYLOAD_TYPE_INIT_CANCEL);
return physical_channel_send(request->pchan, &header, &request->xdata);
}
int hab_open_pending_enter(struct uhab_context *ctx,
struct physical_channel *pchan,
struct hab_open_node *pending)
{
write_lock(&ctx->ctx_lock);
list_add_tail(&pending->node, &ctx->pending_open);
ctx->pending_cnt++;
write_unlock(&ctx->ctx_lock);
return 0;
}
int hab_open_pending_exit(struct uhab_context *ctx,
struct physical_channel *pchan,
struct hab_open_node *pending)
{
struct hab_open_node *node, *tmp;
int ret = -ENOENT;
write_lock(&ctx->ctx_lock);
list_for_each_entry_safe(node, tmp, &ctx->pending_open, node) {
if ((node->request.type == pending->request.type) &&
(node->request.pchan
== pending->request.pchan) &&
(node->request.xdata.vchan_id
== pending->request.xdata.vchan_id) &&
(node->request.xdata.sub_id
== pending->request.xdata.sub_id) &&
(node->request.xdata.open_id
== pending->request.xdata.open_id)) {
list_del(&node->node);
ctx->pending_cnt--;
ret = 0;
}
}
write_unlock(&ctx->ctx_lock);
return ret;
}