blob: ebc37f61c31bd4c0d40142bdadb25b95a1ca921f [file] [log] [blame]
/* Copyright (c) 2016, 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/device.h>
#include <linux/err.h>
#include <linux/sched.h>
#include <linux/ratelimit.h>
#include <linux/workqueue.h>
#include <linux/pm_runtime.h>
#include <linux/delay.h>
#include <linux/atomic.h>
#include <linux/diagchar.h>
#include <linux/of.h>
#include <linux/kmemleak.h>
#include <soc/qcom/glink.h>
#include "diagchar.h"
#include "diagfwd.h"
#include "diagfwd_peripheral.h"
#include "diagfwd_glink.h"
#include "diag_ipc_logging.h"
struct diag_glink_info glink_data[NUM_PERIPHERALS] = {
{
.peripheral = PERIPHERAL_MODEM,
.type = TYPE_DATA,
.edge = "mpss",
.name = "DIAG_DATA",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_LPASS,
.type = TYPE_DATA,
.edge = "lpass",
.name = "DIAG_DATA",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_WCNSS,
.type = TYPE_DATA,
.edge = "wcnss",
.name = "DIAG_DATA",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_SENSORS,
.type = TYPE_DATA,
.edge = "dsps",
.name = "DIAG_DATA",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_WDSP,
.type = TYPE_DATA,
.edge = "wdsp",
.name = "DIAG_DATA",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_CDSP,
.type = TYPE_DATA,
.edge = "cdsp",
.name = "DIAG_DATA",
.hdl = NULL
}
};
struct diag_glink_info glink_cntl[NUM_PERIPHERALS] = {
{
.peripheral = PERIPHERAL_MODEM,
.type = TYPE_CNTL,
.edge = "mpss",
.name = "DIAG_CTRL",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_LPASS,
.type = TYPE_CNTL,
.edge = "lpass",
.name = "DIAG_CTRL",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_WCNSS,
.type = TYPE_CNTL,
.edge = "wcnss",
.name = "DIAG_CTRL",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_SENSORS,
.type = TYPE_CNTL,
.edge = "dsps",
.name = "DIAG_CTRL",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_WDSP,
.type = TYPE_CNTL,
.edge = "wdsp",
.name = "DIAG_CTRL",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_CDSP,
.type = TYPE_CNTL,
.edge = "cdsp",
.name = "DIAG_CTRL",
.hdl = NULL
}
};
struct diag_glink_info glink_dci[NUM_PERIPHERALS] = {
{
.peripheral = PERIPHERAL_MODEM,
.type = TYPE_DCI,
.edge = "mpss",
.name = "DIAG_DCI_DATA",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_LPASS,
.type = TYPE_DCI,
.edge = "lpass",
.name = "DIAG_DCI_DATA",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_WCNSS,
.type = TYPE_DCI,
.edge = "wcnss",
.name = "DIAG_DCI_DATA",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_SENSORS,
.type = TYPE_DCI,
.edge = "dsps",
.name = "DIAG_DCI_DATA",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_WDSP,
.type = TYPE_DCI,
.edge = "wdsp",
.name = "DIAG_DCI_DATA",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_CDSP,
.type = TYPE_DCI,
.edge = "cdsp",
.name = "DIAG_DCI_DATA",
.hdl = NULL
}
};
struct diag_glink_info glink_cmd[NUM_PERIPHERALS] = {
{
.peripheral = PERIPHERAL_MODEM,
.type = TYPE_CMD,
.edge = "mpss",
.name = "DIAG_CMD",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_LPASS,
.type = TYPE_CMD,
.edge = "lpass",
.name = "DIAG_CMD",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_WCNSS,
.type = TYPE_CMD,
.edge = "wcnss",
.name = "DIAG_CMD",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_SENSORS,
.type = TYPE_CMD,
.edge = "dsps",
.name = "DIAG_CMD",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_WDSP,
.type = TYPE_CMD,
.edge = "wdsp",
.name = "DIAG_CMD",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_CDSP,
.type = TYPE_CMD,
.edge = "cdsp",
.name = "DIAG_CMD",
.hdl = NULL
}
};
struct diag_glink_info glink_dci_cmd[NUM_PERIPHERALS] = {
{
.peripheral = PERIPHERAL_MODEM,
.type = TYPE_DCI_CMD,
.edge = "mpss",
.name = "DIAG_DCI_CMD",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_LPASS,
.type = TYPE_DCI_CMD,
.edge = "lpass",
.name = "DIAG_DCI_CMD",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_WCNSS,
.type = TYPE_DCI_CMD,
.edge = "wcnss",
.name = "DIAG_DCI_CMD",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_SENSORS,
.type = TYPE_DCI_CMD,
.edge = "dsps",
.name = "DIAG_DCI_CMD",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_WDSP,
.type = TYPE_DCI_CMD,
.edge = "wdsp",
.name = "DIAG_DCI_CMD",
.hdl = NULL
},
{
.peripheral = PERIPHERAL_CDSP,
.type = TYPE_DCI_CMD,
.edge = "cdsp",
.name = "DIAG_DCI_CMD",
.hdl = NULL
}
};
static void diag_state_open_glink(void *ctxt);
static void diag_state_close_glink(void *ctxt);
static int diag_glink_write(void *ctxt, unsigned char *buf, int len);
static int diag_glink_read(void *ctxt, unsigned char *buf, int buf_len);
static void diag_glink_queue_read(void *ctxt);
static struct diag_peripheral_ops glink_ops = {
.open = diag_state_open_glink,
.close = diag_state_close_glink,
.write = diag_glink_write,
.read = diag_glink_read,
.queue_read = diag_glink_queue_read
};
static void diag_state_open_glink(void *ctxt)
{
struct diag_glink_info *glink_info = NULL;
if (!ctxt)
return;
glink_info = (struct diag_glink_info *)(ctxt);
atomic_set(&glink_info->diag_state, 1);
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"%s setting diag state to 1", glink_info->name);
}
static void diag_glink_queue_read(void *ctxt)
{
struct diag_glink_info *glink_info = NULL;
if (!ctxt)
return;
glink_info = (struct diag_glink_info *)ctxt;
if (glink_info->hdl && glink_info->wq &&
atomic_read(&glink_info->opened))
queue_work(glink_info->wq, &(glink_info->read_work));
}
static void diag_state_close_glink(void *ctxt)
{
struct diag_glink_info *glink_info = NULL;
if (!ctxt)
return;
glink_info = (struct diag_glink_info *)(ctxt);
atomic_set(&glink_info->diag_state, 0);
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"%s setting diag state to 0", glink_info->name);
wake_up_interruptible(&glink_info->read_wait_q);
flush_workqueue(glink_info->wq);
}
int diag_glink_check_state(void *ctxt)
{
struct diag_glink_info *info = NULL;
if (!ctxt)
return 0;
info = (struct diag_glink_info *)ctxt;
return (int)(atomic_read(&info->diag_state));
}
static int diag_glink_read(void *ctxt, unsigned char *buf, int buf_len)
{
struct diag_glink_info *glink_info = NULL;
int ret_val = 0;
if (!ctxt || !buf || buf_len <= 0)
return -EIO;
glink_info = (struct diag_glink_info *)ctxt;
if (!glink_info || !atomic_read(&glink_info->opened) ||
!glink_info->hdl || !glink_info->inited) {
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"diag:Glink channel not opened");
return -EIO;
}
ret_val = glink_queue_rx_intent(glink_info->hdl, buf, buf_len);
if (ret_val == 0)
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"diag: queued an rx intent ch:%s perip:%d buf:%pK of len:%d\n",
glink_info->name, glink_info->peripheral, buf, buf_len);
return ret_val;
}
static void diag_glink_read_work_fn(struct work_struct *work)
{
struct diag_glink_info *glink_info = container_of(work,
struct diag_glink_info,
read_work);
if (!glink_info || !atomic_read(&glink_info->opened))
return;
if (!glink_info->inited) {
diag_ws_release();
return;
}
diagfwd_channel_read(glink_info->fwd_ctxt);
}
static void diag_glink_notify_rx(void *hdl, const void *priv,
const void *pkt_priv, const void *ptr,
size_t size)
{
struct diag_glink_info *glink_info = (struct diag_glink_info *)priv;
int err = 0;
if (!glink_info || !glink_info->hdl || !ptr || !pkt_priv || !hdl)
return;
if (size <= 0)
return;
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"diag: received a packet %pK of len:%d from periph:%d ch:%d\n",
ptr, (int)size, glink_info->peripheral, glink_info->type);
memcpy((void *)pkt_priv, ptr, size);
err = diagfwd_channel_read_done(glink_info->fwd_ctxt,
(unsigned char *)pkt_priv, size);
glink_rx_done(glink_info->hdl, ptr, false);
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"diag: Rx done for packet %pK of len:%d periph:%d ch:%d\n",
ptr, (int)size, glink_info->peripheral, glink_info->type);
}
static void diag_glink_notify_remote_rx_intent(void *hdl, const void *priv,
size_t size)
{
struct diag_glink_info *glink_info = (struct diag_glink_info *)priv;
if (!glink_info)
return;
atomic_inc(&glink_info->tx_intent_ready);
wake_up_interruptible(&glink_info->wait_q);
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"diag:received remote rx intent for %d type %d\n",
glink_info->peripheral, glink_info->type);
}
static void diag_glink_notify_tx_done(void *hdl, const void *priv,
const void *pkt_priv,
const void *ptr)
{
struct diag_glink_info *glink_info = NULL;
struct diagfwd_info *fwd_info = NULL;
int found = 0;
glink_info = (struct diag_glink_info *)priv;
if (!glink_info)
return;
fwd_info = glink_info->fwd_ctxt;
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"diag: Received glink tx done notify for ptr%pK pkt_priv %pK\n",
ptr, pkt_priv);
found = diagfwd_write_buffer_done(fwd_info, ptr);
if (!found)
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"Received Tx done on invalid buffer ptr %pK\n", ptr);
}
static int diag_glink_write(void *ctxt, unsigned char *buf, int len)
{
struct diag_glink_info *glink_info = NULL;
int err = 0;
uint32_t tx_flags = GLINK_TX_REQ_INTENT;
if (!ctxt || !buf)
return -EIO;
glink_info = (struct diag_glink_info *)ctxt;
if (!glink_info || len <= 0) {
pr_err_ratelimited("diag: In %s, invalid params, glink_info: %pK, buf: %pK, len: %d\n",
__func__, glink_info, buf, len);
return -EINVAL;
}
if (!glink_info->inited || !glink_info->hdl ||
!atomic_read(&glink_info->opened)) {
pr_err_ratelimited("diag: In %s, glink not inited, glink_info: %pK, buf: %pK, len: %d\n",
__func__, glink_info, buf, len);
return -ENODEV;
}
if (atomic_read(&glink_info->tx_intent_ready)) {
atomic_dec(&glink_info->tx_intent_ready);
err = glink_tx(glink_info->hdl, glink_info, buf, len, tx_flags);
if (!err) {
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"%s wrote to glink, len: %d\n",
glink_info->name, len);
}
} else
err = -ENOMEM;
return err;
}
static void diag_glink_transport_notify_state(void *handle, const void *priv,
unsigned int event)
{
struct diag_glink_info *glink_info = (struct diag_glink_info *)priv;
if (!glink_info)
return;
switch (event) {
case GLINK_CONNECTED:
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"%s received channel connect for periph:%d\n",
glink_info->name, glink_info->peripheral);
atomic_set(&glink_info->opened, 1);
diagfwd_channel_open(glink_info->fwd_ctxt);
diagfwd_late_open(glink_info->fwd_ctxt);
break;
case GLINK_LOCAL_DISCONNECTED:
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"%s received channel disconnect for periph:%d\n",
glink_info->name, glink_info->peripheral);
break;
case GLINK_REMOTE_DISCONNECTED:
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"%s received channel remote disconnect for periph:%d\n",
glink_info->name, glink_info->peripheral);
atomic_set(&glink_info->opened, 0);
diagfwd_channel_close(glink_info->fwd_ctxt);
atomic_set(&glink_info->tx_intent_ready, 0);
break;
default:
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"%s received invalid notification\n",
glink_info->name);
break;
}
}
static void diag_glink_open_work_fn(struct work_struct *work)
{
struct diag_glink_info *glink_info = container_of(work,
struct diag_glink_info,
open_work);
struct glink_open_config open_cfg;
void *handle = NULL;
if (!glink_info || glink_info->hdl)
return;
memset(&open_cfg, 0, sizeof(struct glink_open_config));
open_cfg.priv = glink_info;
open_cfg.edge = glink_info->edge;
open_cfg.name = glink_info->name;
open_cfg.notify_rx = diag_glink_notify_rx;
open_cfg.notify_tx_done = diag_glink_notify_tx_done;
open_cfg.notify_state = diag_glink_transport_notify_state;
open_cfg.notify_remote_rx_intent = diag_glink_notify_remote_rx_intent;
handle = glink_open(&open_cfg);
if (IS_ERR_OR_NULL(handle)) {
DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "error opening channel %s",
glink_info->name);
} else
glink_info->hdl = handle;
}
static void diag_glink_close_work_fn(struct work_struct *work)
{
struct diag_glink_info *glink_info = container_of(work,
struct diag_glink_info,
close_work);
if (!glink_info || !glink_info->inited || !glink_info->hdl)
return;
glink_close(glink_info->hdl);
atomic_set(&glink_info->opened, 0);
atomic_set(&glink_info->tx_intent_ready, 0);
glink_info->hdl = NULL;
diagfwd_channel_close(glink_info->fwd_ctxt);
}
static void diag_glink_notify_cb(struct glink_link_state_cb_info *cb_info,
void *priv)
{
struct diag_glink_info *glink_info = NULL;
glink_info = (struct diag_glink_info *)priv;
if (!glink_info)
return;
if (!cb_info)
return;
switch (cb_info->link_state) {
case GLINK_LINK_STATE_UP:
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"%s channel opened for periph:%d\n",
glink_info->name, glink_info->peripheral);
queue_work(glink_info->wq, &glink_info->open_work);
break;
case GLINK_LINK_STATE_DOWN:
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"%s channel closed for periph:%d\n",
glink_info->name, glink_info->peripheral);
queue_work(glink_info->wq, &glink_info->close_work);
break;
default:
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"Invalid link state notification for ch:%s\n",
glink_info->name);
break;
}
}
static void glink_late_init(struct diag_glink_info *glink_info)
{
struct diagfwd_info *fwd_info = NULL;
if (!glink_info)
return;
DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "%s entering\n",
glink_info->name);
diagfwd_register(TRANSPORT_GLINK, glink_info->peripheral,
glink_info->type, (void *)glink_info,
&glink_ops, &glink_info->fwd_ctxt);
fwd_info = glink_info->fwd_ctxt;
if (!fwd_info)
return;
glink_info->inited = 1;
if (atomic_read(&glink_info->opened))
diagfwd_channel_open(glink_info->fwd_ctxt);
DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "%s exiting\n",
glink_info->name);
}
int diag_glink_init_peripheral(uint8_t peripheral)
{
if (peripheral >= NUM_PERIPHERALS) {
pr_err("diag: In %s, invalid peripheral %d\n",
__func__, peripheral);
return -EINVAL;
}
glink_late_init(&glink_data[peripheral]);
glink_late_init(&glink_dci[peripheral]);
glink_late_init(&glink_cmd[peripheral]);
glink_late_init(&glink_dci_cmd[peripheral]);
return 0;
}
static void __diag_glink_init(struct diag_glink_info *glink_info)
{
char wq_name[DIAG_GLINK_NAME_SZ + 12];
struct glink_link_info link_info;
void *link_state_handle = NULL;
if (!glink_info)
return;
init_waitqueue_head(&glink_info->wait_q);
init_waitqueue_head(&glink_info->read_wait_q);
mutex_init(&glink_info->lock);
strlcpy(wq_name, "DIAG_GLINK_", 12);
strlcat(wq_name, glink_info->name, sizeof(glink_info->name));
glink_info->wq = create_singlethread_workqueue(wq_name);
if (!glink_info->wq) {
pr_err("diag: In %s, unable to create workqueue for glink ch:%s\n",
__func__, glink_info->name);
return;
}
INIT_WORK(&(glink_info->open_work), diag_glink_open_work_fn);
INIT_WORK(&(glink_info->close_work), diag_glink_close_work_fn);
INIT_WORK(&(glink_info->read_work), diag_glink_read_work_fn);
link_info.glink_link_state_notif_cb = diag_glink_notify_cb;
link_info.transport = NULL;
link_info.edge = glink_info->edge;
glink_info->link_state_handle = NULL;
link_state_handle = glink_register_link_state_cb(&link_info,
(void *)glink_info);
if (IS_ERR_OR_NULL(link_state_handle)) {
pr_err("diag: In %s, unable to register for glink channel %s\n",
__func__, glink_info->name);
destroy_workqueue(glink_info->wq);
return;
}
glink_info->link_state_handle = link_state_handle;
glink_info->fwd_ctxt = NULL;
atomic_set(&glink_info->tx_intent_ready, 0);
atomic_set(&glink_info->opened, 0);
atomic_set(&glink_info->diag_state, 0);
DIAG_LOG(DIAG_DEBUG_PERIPHERALS,
"%s initialized fwd_ctxt: %pK hdl: %pK\n",
glink_info->name, glink_info->fwd_ctxt,
glink_info->link_state_handle);
}
void diag_glink_invalidate(void *ctxt, struct diagfwd_info *fwd_ctxt)
{
struct diag_glink_info *info = NULL;
if (!ctxt || !fwd_ctxt)
return;
info = (struct diag_glink_info *)ctxt;
info->fwd_ctxt = fwd_ctxt;
}
int diag_glink_init(void)
{
uint8_t peripheral;
struct diag_glink_info *glink_info = NULL;
for (peripheral = 0; peripheral < NUM_PERIPHERALS; peripheral++) {
glink_info = &glink_cntl[peripheral];
__diag_glink_init(glink_info);
diagfwd_cntl_register(TRANSPORT_GLINK, glink_info->peripheral,
(void *)glink_info, &glink_ops,
&(glink_info->fwd_ctxt));
glink_info->inited = 1;
__diag_glink_init(&glink_data[peripheral]);
__diag_glink_init(&glink_cmd[peripheral]);
__diag_glink_init(&glink_dci[peripheral]);
__diag_glink_init(&glink_dci_cmd[peripheral]);
}
return 0;
}
static void __diag_glink_exit(struct diag_glink_info *glink_info)
{
if (!glink_info)
return;
DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "%s entering\n",
glink_info->name);
diagfwd_deregister(glink_info->peripheral, glink_info->type,
(void *)glink_info);
glink_info->fwd_ctxt = NULL;
glink_info->hdl = NULL;
if (glink_info->wq)
destroy_workqueue(glink_info->wq);
DIAG_LOG(DIAG_DEBUG_PERIPHERALS, "%s exiting\n",
glink_info->name);
}
void diag_glink_early_exit(void)
{
int peripheral = 0;
for (peripheral = 0; peripheral < NUM_PERIPHERALS; peripheral++) {
__diag_glink_exit(&glink_cntl[peripheral]);
glink_unregister_link_state_cb(&glink_cntl[peripheral].hdl);
}
}
void diag_glink_exit(void)
{
int peripheral = 0;
for (peripheral = 0; peripheral < NUM_PERIPHERALS; peripheral++) {
__diag_glink_exit(&glink_data[peripheral]);
__diag_glink_exit(&glink_cmd[peripheral]);
__diag_glink_exit(&glink_dci[peripheral]);
__diag_glink_exit(&glink_dci_cmd[peripheral]);
glink_unregister_link_state_cb(&glink_data[peripheral].hdl);
glink_unregister_link_state_cb(&glink_cmd[peripheral].hdl);
glink_unregister_link_state_cb(&glink_dci[peripheral].hdl);
glink_unregister_link_state_cb(&glink_dci_cmd[peripheral].hdl);
}
}