blob: 40d6d84b2a7954f11d4ed8b66bb7cf9ba2cbc2fd [file] [log] [blame]
/* Copyright (c) 2012-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 <linux/slab.h>
#include <linux/delay.h>
#include <linux/diagchar.h>
#include <linux/kmemleak.h>
#include <linux/err.h>
#include <linux/workqueue.h>
#include <linux/ratelimit.h>
#include <linux/platform_device.h>
#include "diag_mux.h"
#include "diagfwd_bridge.h"
#include "diagfwd_hsic.h"
#include "diagfwd_mhi.h"
#include "diag_dci.h"
#include "diag_ipc_logging.h"
#include <linux/of.h>
#define BRIDGE_TO_MUX(x) (x + DIAG_MUX_BRIDGE_BASE)
/* variable to identify which interface is selected to bridging with mdm */
static bool hsic_interface_active;
struct diagfwd_bridge_info bridge_info[NUM_REMOTE_DEV] = {
{
.id = DIAGFWD_MDM,
.type = DIAG_DATA_TYPE,
.name = "MDM",
.inited = 0,
.ctxt = 0,
.dev_ops = NULL,
.dci_read_ptr = NULL,
.dci_read_buf = NULL,
.dci_read_len = 0,
.dci_wq = NULL,
},
{
.id = DIAGFWD_MDM_2,
.type = DIAG_DATA_TYPE,
.name = "MDM2",
.inited = 0,
.ctxt = 0,
.dev_ops = NULL,
.dci_read_ptr = NULL,
.dci_read_buf = NULL,
.dci_read_len = 0,
.dci_wq = NULL,
},
{
.id = DIAGFWD_MDM_DCI,
.type = DIAG_DCI_TYPE,
.name = "MDM_DCI",
.inited = 0,
.ctxt = 0,
.dci_read_ptr = NULL,
.dev_ops = NULL,
.dci_read_buf = NULL,
.dci_read_len = 0,
.dci_wq = NULL,
},
};
static int diagfwd_bridge_mux_connect(int id, int mode)
{
if (id < 0 || id >= NUM_REMOTE_DEV)
return -EINVAL;
return 0;
}
static int diagfwd_bridge_mux_disconnect(int id, int mode)
{
if (id < 0 || id >= NUM_REMOTE_DEV)
return -EINVAL;
return 0;
}
static int diagfwd_bridge_mux_read_done(unsigned char *buf, int len, int id)
{
return diagfwd_bridge_write(id, buf, len);
}
static int diagfwd_bridge_mux_write_done(unsigned char *buf, int len,
int buf_ctx, int id)
{
struct diagfwd_bridge_info *ch = NULL;
if (id < 0 || id >= NUM_REMOTE_DEV)
return -EINVAL;
ch = &bridge_info[buf_ctx];
if (ch->dev_ops && ch->dev_ops->fwd_complete) {
DIAG_LOG(DIAG_DEBUG_MHI,
"Write done completion received for buf %pK len:%d\n",
buf, len);
ch->dev_ops->fwd_complete(ch->ctxt, buf, len, 0);
}
return 0;
}
static struct diag_mux_ops diagfwd_bridge_mux_ops = {
.open = diagfwd_bridge_mux_connect,
.close = diagfwd_bridge_mux_disconnect,
.read_done = diagfwd_bridge_mux_read_done,
.write_done = diagfwd_bridge_mux_write_done
};
static void bridge_dci_read_work_fn(struct work_struct *work)
{
struct diagfwd_bridge_info *ch = container_of(work,
struct diagfwd_bridge_info,
dci_read_work);
if (!ch)
return;
diag_process_remote_dci_read_data(ch->id, ch->dci_read_buf,
ch->dci_read_len);
if (ch->dev_ops && ch->dev_ops->fwd_complete) {
ch->dev_ops->fwd_complete(ch->ctxt, ch->dci_read_ptr,
ch->dci_read_len, 0);
}
}
int diagfwd_bridge_register(int id, int ctxt, struct diag_remote_dev_ops *ops)
{
int err = 0;
struct diagfwd_bridge_info *ch = NULL;
char wq_name[DIAG_BRIDGE_NAME_SZ + 10];
if (!ops) {
pr_err("diag: Invalid pointers ops: %pK ctxt: %d\n", ops, ctxt);
return -EINVAL;
}
if (id < 0 || id >= NUM_REMOTE_DEV)
return -EINVAL;
ch = &bridge_info[id];
ch->ctxt = ctxt;
ch->dev_ops = ops;
switch (ch->type) {
case DIAG_DATA_TYPE:
err = diag_mux_register(BRIDGE_TO_MUX(id), id,
&diagfwd_bridge_mux_ops);
if (err)
return err;
break;
case DIAG_DCI_TYPE:
ch->dci_read_buf = kzalloc(DIAG_MDM_BUF_SIZE, GFP_KERNEL);
if (!ch->dci_read_buf)
return -ENOMEM;
ch->dci_read_len = 0;
strlcpy(wq_name, "diag_dci_", sizeof(wq_name));
strlcat(wq_name, ch->name, sizeof(wq_name));
INIT_WORK(&(ch->dci_read_work), bridge_dci_read_work_fn);
ch->dci_wq = create_singlethread_workqueue(wq_name);
if (!ch->dci_wq) {
kfree(ch->dci_read_buf);
return -ENOMEM;
}
break;
default:
pr_err("diag: Invalid channel type %d in %s\n", ch->type,
__func__);
return -EINVAL;
}
return 0;
}
int diag_remote_dev_open(int id)
{
if (id < 0 || id >= NUM_REMOTE_DEV)
return -EINVAL;
bridge_info[id].inited = 1;
if (bridge_info[id].type == DIAG_DATA_TYPE)
return diag_mux_queue_read(BRIDGE_TO_MUX(id));
else if (bridge_info[id].type == DIAG_DCI_TYPE)
return diag_dci_send_handshake_pkt(bridge_info[id].id);
return 0;
}
void diag_remote_dev_close(int id)
{
if (id < 0 || id >= NUM_REMOTE_DEV)
return;
diag_mux_close_device(BRIDGE_TO_MUX(id));
}
int diag_remote_dev_read_done(int id, unsigned char *buf, int len)
{
int err = 0;
struct diagfwd_bridge_info *ch = NULL;
if (id < 0 || id >= NUM_REMOTE_DEV)
return -EINVAL;
ch = &bridge_info[id];
if (ch->type == DIAG_DATA_TYPE) {
err = diag_mux_write(BRIDGE_TO_MUX(id), buf, len, id);
if (ch->dev_ops && ch->dev_ops->queue_read)
ch->dev_ops->queue_read(ch->ctxt);
return err;
}
/*
* For DCI channels copy to the internal buffer. Don't queue any
* further reads. A read should be queued once we are done processing
* the current packet
*/
if (len <= 0 || len > DIAG_MDM_BUF_SIZE) {
pr_err_ratelimited("diag: Invalid len %d in %s, ch: %s\n",
len, __func__, ch->name);
return -EINVAL;
}
ch->dci_read_ptr = buf;
memcpy(ch->dci_read_buf, buf, len);
ch->dci_read_len = len;
queue_work(ch->dci_wq, &ch->dci_read_work);
return 0;
}
int diag_remote_dev_write_done(int id, unsigned char *buf, int len, int ctxt)
{
int err = 0;
if (id < 0 || id >= NUM_REMOTE_DEV)
return -EINVAL;
if (bridge_info[id].type == DIAG_DATA_TYPE) {
if (buf == driver->hdlc_encode_buf)
driver->hdlc_encode_buf_len = 0;
/*
* For remote processor, the token offset is stripped from the
* buffer. Account for the token offset while checking against
* the original buffer
*/
if (buf == (driver->user_space_data_buf + sizeof(int)))
driver->user_space_data_busy = 0;
err = diag_mux_queue_read(BRIDGE_TO_MUX(id));
} else {
err = diag_dci_write_done_bridge(id, buf, len);
}
return err;
}
int diagfwd_bridge_close(int id)
{
if (id < 0 || id >= NUM_REMOTE_DEV)
return -EINVAL;
if (bridge_info[id].dev_ops && bridge_info[id].dev_ops->close)
return bridge_info[id].dev_ops->close(bridge_info[id].ctxt);
return 0;
}
int diagfwd_bridge_write(int id, unsigned char *buf, int len)
{
if (id < 0 || id >= NUM_REMOTE_DEV)
return -EINVAL;
if (bridge_info[id].dev_ops && bridge_info[id].dev_ops->write) {
return bridge_info[id].dev_ops->write(bridge_info[id].ctxt,
buf, len, 0);
}
return 0;
}
uint16_t diag_get_remote_device_mask(void)
{
int i;
uint16_t remote_dev = 0;
for (i = 0; i < NUM_REMOTE_DEV; i++) {
if (bridge_info[i].inited &&
bridge_info[i].type == DIAG_DATA_TYPE &&
(bridge_info[i].dev_ops->remote_proc_check &&
bridge_info[i].dev_ops->remote_proc_check(i))) {
remote_dev |= 1 << i;
}
}
return remote_dev;
}
void diag_register_with_bridge(void)
{
struct device_node *dev_node;
if (IS_ENABLED(CONFIG_USB_QTI_DIAG_BRIDGE) &&
IS_ENABLED(CONFIG_MHI_BUS)) {
dev_node = of_find_node_by_name(NULL, "qcom,diag");
if (dev_node) {
hsic_interface_active = of_property_read_bool(dev_node,
"qcom,usb-enabled");
if (hsic_interface_active) {
diag_register_with_hsic();
return;
}
}
diag_register_with_mhi();
} else if (IS_ENABLED(CONFIG_USB_QTI_DIAG_BRIDGE)) {
hsic_interface_active = true;
diag_register_with_hsic();
} else if (IS_ENABLED(CONFIG_MHI_BUS))
diag_register_with_mhi();
}
void diag_unregister_bridge(void)
{
if (hsic_interface_active)
diag_unregister_hsic();
else if (IS_ENABLED(CONFIG_MHI_BUS))
diag_unregister_mhi();
}
EXPORT_SYMBOL_GPL(diag_unregister_bridge);