/* 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" #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, (uint32_t)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 = NULL; struct hab_device *dev = pchan->habdev; struct hab_open_request *request = NULL; struct timeval tv = {0}; if (sizebytes > sizeof(request->xdata)) { 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); spin_lock_bh(&dev->openlock); list_add_tail(&node->node, &dev->openq_list); dev->openq_cnt++; spin_unlock_bh(&dev->openlock); 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 = NULL, *tmp = NULL; struct hab_open_request *request = NULL; struct timeval tv = {0}; 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); if (hab_is_forbidden(ctx, dev, listen->xdata.sub_id)) ret = 1; 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 (hab_is_forbidden(ctx, dev, listen->xdata.sub_id)) { pr_warn("local open cancelled ret %d\n", ret); ret = -ENXIO; } 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; } else if (hab_is_forbidden(ctx, dev, listen->xdata.sub_id)) { pr_warn("local open cancelled ret %d\n", ret); ret = -ENXIO; } } 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 = {0}; struct hab_open_request *request = NULL; struct hab_open_node *node = NULL, *tmp = NULL; int bfound = 0; struct timeval tv = {0}; if (sizebytes > sizeof(data)) { pr_err("pchan %s cancel size too large %zd header %zd\n", pchan->name, sizebytes, sizeof(data)); return -EINVAL; } if (physical_channel_read(pchan, &data, sizebytes) != sizebytes) return -EIO; spin_lock_bh(&dev->openlock); 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; } } spin_unlock_bh(&dev->openlock); 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; 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); spin_lock_bh(&dev->openlock); list_add_tail(&node->node, &dev->openq_list); dev->openq_cnt++; spin_unlock_bh(&dev->openlock); 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, (uint32_t)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) { (void)pchan; 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 = NULL, *tmp = NULL; int ret = -ENOENT; (void)pchan; 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; }