blob: 5de7897b31a23c96d802c362ca0e92879e6d8406 [file] [log] [blame]
/* Copyright (c) 2012-2017, 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>
#ifdef USB_QCOM_DIAG_BRIDGE
#include <linux/smux.h>
#endif
#include "diag_mux.h"
#include "diagfwd_bridge.h"
#ifdef USB_QCOM_DIAG_BRIDGE
#include "diagfwd_hsic.h"
#include "diagfwd_smux.h"
#endif
#include "diagfwd_mhi.h"
#include "diag_dci.h"
#ifdef CONFIG_MSM_MHI
#define diag_mdm_init diag_mhi_init
#else
#define diag_mdm_init diag_hsic_init
#endif
#define BRIDGE_TO_MUX(x) (x + DIAG_MUX_BRIDGE_BASE)
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_SMUX,
.type = DIAG_DATA_TYPE,
.name = "SMUX",
.inited = 0,
.ctxt = 0,
.dci_read_ptr = NULL,
.dev_ops = 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;
if (bridge_info[id].dev_ops && bridge_info[id].dev_ops->open)
bridge_info[id].dev_ops->open(bridge_info[id].ctxt);
return 0;
}
static int diagfwd_bridge_mux_disconnect(int id, int mode)
{
if (id < 0 || id >= NUM_REMOTE_DEV)
return -EINVAL;
if ((mode == DIAG_USB_MODE &&
driver->logging_mode == DIAG_MEMORY_DEVICE_MODE) ||
(mode == DIAG_MEMORY_DEVICE_MODE &&
driver->logging_mode == DIAG_USB_MODE)) {
/*
* Don't close the MHI channels when usb is disconnected
* and a process is running in memory device mode.
*/
return 0;
}
if (bridge_info[id].dev_ops && bridge_info[id].dev_ops->close)
bridge_info[id].dev_ops->close(bridge_info[id].ctxt);
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)
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_", 10);
strlcat(wq_name, ch->name, sizeof(ch->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)
{
}
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_init(void)
{
int err = 0;
err = diag_mdm_init();
if (err)
goto fail;
#ifdef USB_QCOM_DIAG_BRIDGE
err = diag_smux_init();
if (err)
goto fail;
#endif
return 0;
fail:
pr_err("diag: Unable to initialze diagfwd bridge, err: %d\n", err);
return err;
}
void diagfwd_bridge_exit(void)
{
#ifdef USB_QCOM_DIAG_BRIDGE
diag_hsic_exit();
diag_smux_exit();
#endif
}
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) {
remote_dev |= 1 << i;
}
}
return remote_dev;
}