blob: 143959b39a4bd94167ed870419428ce5edd060ea [file] [log] [blame]
/* Copyright (c) 2012-2013, 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 <linux/smux.h>
#ifdef CONFIG_DIAG_OVER_USB
#include <mach/usbdiag.h>
#endif
#include "diagchar.h"
#include "diagmem.h"
#include "diagfwd_cntl.h"
#include "diagfwd_smux.h"
#include "diagfwd_hsic.h"
#include "diag_masks.h"
#include "diagfwd_bridge.h"
struct diag_bridge_dev *diag_bridge;
/* diagfwd_connect_bridge is called when the USB mdm channel is connected */
int diagfwd_connect_bridge(int process_cable)
{
uint8_t i;
pr_debug("diag: in %s\n", __func__);
for (i = 0; i < MAX_BRIDGES; i++)
if (diag_bridge[i].enabled)
connect_bridge(process_cable, i);
return 0;
}
void connect_bridge(int process_cable, uint8_t index)
{
int err;
mutex_lock(&diag_bridge[index].bridge_mutex);
/* If the usb cable is being connected */
if (process_cable) {
err = usb_diag_alloc_req(diag_bridge[index].ch, N_MDM_WRITE,
N_MDM_READ);
if (err)
pr_err("diag: unable to alloc USB req for ch %d err:%d\n",
index, err);
diag_bridge[index].usb_connected = 1;
}
if (index == SMUX) {
if (driver->diag_smux_enabled) {
driver->in_busy_smux = 0;
diagfwd_connect_smux();
}
} else {
if (index >= MAX_HSIC_CH) {
pr_err("diag: Invalid hsic channel index %d in %s\n",
index, __func__);
mutex_unlock(&diag_bridge[index].bridge_mutex);
return;
}
if (diag_hsic[index].hsic_device_enabled &&
(driver->logging_mode != MEMORY_DEVICE_MODE ||
diag_hsic[index].hsic_data_requested)) {
diag_hsic[index].in_busy_hsic_read_on_device = 0;
diag_hsic[index].in_busy_hsic_write = 0;
/* If the HSIC (diag_bridge) platform
* device is not open */
if (!diag_hsic[index].hsic_device_opened) {
hsic_diag_bridge_ops[index].ctxt =
(void *)(int)(index);
err = diag_bridge_open(index,
&hsic_diag_bridge_ops[index]);
if (err) {
pr_err("diag: HSIC channel open error: %d\n",
err);
} else {
pr_debug("diag: opened HSIC channel\n");
diag_hsic[index].hsic_device_opened =
1;
}
} else {
pr_debug("diag: HSIC channel already open\n");
}
/*
* Turn on communication over usb mdm and HSIC,
* if the HSIC device driver is enabled
* and opened
*/
if (diag_hsic[index].hsic_device_opened) {
diag_hsic[index].hsic_ch = 1;
/* Poll USB mdm channel to check for data */
if (driver->logging_mode == USB_MODE)
queue_work(diag_bridge[index].wq,
&diag_bridge[index].
diag_read_work);
/* Poll HSIC channel to check for data */
queue_work(diag_bridge[index].wq,
&diag_hsic[index].
diag_read_hsic_work);
}
}
}
mutex_unlock(&diag_bridge[index].bridge_mutex);
}
/*
* diagfwd_disconnect_bridge is called when the USB mdm channel
* is disconnected. So disconnect should happen for all bridges
*/
int diagfwd_disconnect_bridge(int process_cable)
{
int i;
pr_debug("diag: In %s, process_cable: %d\n", __func__, process_cable);
for (i = 0; i < MAX_BRIDGES; i++) {
if (diag_bridge[i].enabled) {
mutex_lock(&diag_bridge[i].bridge_mutex);
/* If the usb cable is being disconnected */
if (process_cable) {
diag_bridge[i].usb_connected = 0;
usb_diag_free_req(diag_bridge[i].ch);
}
if (i == SMUX) {
if (driver->diag_smux_enabled &&
driver->logging_mode == USB_MODE) {
driver->in_busy_smux = 1;
driver->lcid = LCID_INVALID;
driver->smux_connected = 0;
/*
* Turn off communication over usb
* and smux
*/
msm_smux_close(LCID_VALID);
}
} else {
if (diag_hsic[i].hsic_device_enabled &&
(driver->logging_mode != MEMORY_DEVICE_MODE
|| !diag_hsic[i].hsic_data_requested)) {
diag_hsic[i].
in_busy_hsic_read_on_device = 1;
diag_hsic[i].in_busy_hsic_write = 1;
/* Turn off communication over usb
* and HSIC */
diag_hsic_close(i);
}
}
mutex_unlock(&diag_bridge[i].bridge_mutex);
}
}
return 0;
}
/* Called after the asychronous usb_diag_read() on mdm channel is complete */
int diagfwd_read_complete_bridge(struct diag_request *diag_read_ptr)
{
int index = (int)(diag_read_ptr->context);
/* The read of the usb on the mdm (not HSIC/SMUX) has completed */
diag_bridge[index].read_len = diag_read_ptr->actual;
if (index == SMUX) {
if (driver->diag_smux_enabled) {
diagfwd_read_complete_smux();
return 0;
} else {
pr_warning("diag: incorrect callback for smux\n");
}
}
/* If SMUX not enabled, check for HSIC */
diag_hsic[index].in_busy_hsic_read_on_device = 0;
if (!diag_hsic[index].hsic_ch) {
pr_err("DIAG in %s: hsic_ch == 0, ch %d\n", __func__, index);
return 0;
}
/*
* The read of the usb driver on the mdm channel has completed.
* If there is no write on the HSIC in progress, check if the
* read has data to pass on to the HSIC. If so, pass the usb
* mdm data on to the HSIC.
*/
if (!diag_hsic[index].in_busy_hsic_write &&
diag_bridge[index].usb_buf_out &&
(diag_bridge[index].read_len > 0)) {
/*
* Initiate the HSIC write. The HSIC write is
* asynchronous. When complete the write
* complete callback function will be called
*/
int err;
diag_hsic[index].in_busy_hsic_write = 1;
err = diag_bridge_write(index, diag_bridge[index].usb_buf_out,
diag_bridge[index].read_len);
if (err) {
pr_err_ratelimited("diag: mdm data on HSIC write err: %d\n",
err);
/*
* If the error is recoverable, then clear
* the write flag, so we will resubmit a
* write on the next frame. Otherwise, don't
* resubmit a write on the next frame.
*/
if ((-ENODEV) != err)
diag_hsic[index].in_busy_hsic_write = 0;
}
}
/*
* If there is no write of the usb mdm data on the
* HSIC channel
*/
if (!diag_hsic[index].in_busy_hsic_write)
queue_work(diag_bridge[index].wq,
&diag_bridge[index].diag_read_work);
return 0;
}
static void diagfwd_bridge_notifier(void *priv, unsigned event,
struct diag_request *d_req)
{
int index;
switch (event) {
case USB_DIAG_CONNECT:
queue_work(driver->diag_wq,
&driver->diag_connect_work);
break;
case USB_DIAG_DISCONNECT:
queue_work(driver->diag_wq,
&driver->diag_disconnect_work);
break;
case USB_DIAG_READ_DONE:
index = (int)(d_req->context);
queue_work(diag_bridge[index].wq,
&diag_bridge[index].usb_read_complete_work);
break;
case USB_DIAG_WRITE_DONE:
index = (int)(d_req->context);
if (index == SMUX && driver->diag_smux_enabled)
diagfwd_write_complete_smux();
else if (diag_hsic[index].hsic_device_enabled)
diagfwd_write_complete_hsic(d_req, index);
break;
default:
pr_err("diag: in %s: Unknown event from USB diag:%u\n",
__func__, event);
break;
}
}
void diagfwd_bridge_init(int index)
{
int ret;
unsigned char name[20];
if (index == HSIC) {
strlcpy(name, "hsic", sizeof(name));
} else if (index == HSIC_2) {
strlcpy(name, "hsic_2", sizeof(name));
} else if (index == SMUX) {
strlcpy(name, "smux", sizeof(name));
} else {
pr_err("diag: incorrect bridge init, instance: %d\n", index);
return;
}
strlcpy(diag_bridge[index].name, name,
sizeof(diag_bridge[index].name));
strlcat(name, "_diag_wq", sizeof(diag_bridge[index].name));
diag_bridge[index].id = index;
diag_bridge[index].wq = create_singlethread_workqueue(name);
diag_bridge[index].read_len = 0;
diag_bridge[index].write_len = 0;
if (diag_bridge[index].usb_buf_out == NULL)
diag_bridge[index].usb_buf_out =
kzalloc(USB_MAX_OUT_BUF, GFP_KERNEL);
if (diag_bridge[index].usb_buf_out == NULL)
goto err;
if (diag_bridge[index].usb_read_ptr == NULL)
diag_bridge[index].usb_read_ptr =
kzalloc(sizeof(struct diag_request), GFP_KERNEL);
if (diag_bridge[index].usb_read_ptr == NULL)
goto err;
if (diag_bridge[index].usb_read_ptr->context == NULL)
diag_bridge[index].usb_read_ptr->context =
kzalloc(sizeof(int), GFP_KERNEL);
if (diag_bridge[index].usb_read_ptr->context == NULL)
goto err;
mutex_init(&diag_bridge[index].bridge_mutex);
if (index == HSIC || index == HSIC_2) {
INIT_WORK(&(diag_bridge[index].usb_read_complete_work),
diag_usb_read_complete_hsic_fn);
#ifdef CONFIG_DIAG_OVER_USB
INIT_WORK(&(diag_bridge[index].diag_read_work),
diag_read_usb_hsic_work_fn);
if (index == HSIC)
diag_bridge[index].ch = usb_diag_open(DIAG_MDM,
(void *)index, diagfwd_bridge_notifier);
else if (index == HSIC_2)
diag_bridge[index].ch = usb_diag_open(DIAG_MDM2,
(void *)index, diagfwd_bridge_notifier);
if (IS_ERR(diag_bridge[index].ch)) {
pr_err("diag: Unable to open USB MDM ch = %d\n", index);
goto err;
} else
diag_bridge[index].enabled = 1;
#endif
} else if (index == SMUX) {
INIT_WORK(&(diag_bridge[index].usb_read_complete_work),
diag_usb_read_complete_smux_fn);
#ifdef CONFIG_DIAG_OVER_USB
INIT_WORK(&(diag_bridge[index].diag_read_work),
diag_read_usb_smux_work_fn);
diag_bridge[index].ch = usb_diag_open(DIAG_QSC, (void *)index,
diagfwd_bridge_notifier);
if (IS_ERR(diag_bridge[index].ch)) {
pr_err("diag: Unable to open USB diag QSC channel\n");
goto err;
} else
diag_bridge[index].enabled = 1;
#endif
ret = platform_driver_register(&msm_diagfwd_smux_driver);
if (ret)
pr_err("diag: could not register SMUX device, ret: %d\n",
ret);
}
return;
err:
pr_err("diag: Could not initialize for bridge forwarding\n");
kfree(diag_bridge[index].usb_buf_out);
kfree(diag_hsic[index].hsic_buf_tbl);
kfree(driver->write_ptr_mdm);
kfree(diag_bridge[index].usb_read_ptr);
if (diag_bridge[index].wq)
destroy_workqueue(diag_bridge[index].wq);
return;
}
void diagfwd_bridge_exit(void)
{
int i;
pr_debug("diag: in %s\n", __func__);
for (i = 0; i < MAX_HSIC_CH; i++) {
if (diag_hsic[i].hsic_device_enabled) {
diag_hsic_close(i);
diag_hsic[i].hsic_device_enabled = 0;
diag_bridge[i].enabled = 0;
}
diag_hsic[i].hsic_inited = 0;
kfree(diag_hsic[i].hsic_buf_tbl);
}
diagmem_exit(driver, POOL_TYPE_ALL);
if (driver->diag_smux_enabled) {
driver->lcid = LCID_INVALID;
kfree(driver->buf_in_smux);
driver->diag_smux_enabled = 0;
diag_bridge[SMUX].enabled = 0;
}
platform_driver_unregister(&msm_hsic_ch_driver);
platform_driver_unregister(&msm_diagfwd_smux_driver);
/* destroy USB MDM specific variables */
for (i = 0; i < MAX_BRIDGES; i++) {
if (diag_bridge[i].enabled) {
#ifdef CONFIG_DIAG_OVER_USB
if (diag_bridge[i].usb_connected)
usb_diag_free_req(diag_bridge[i].ch);
usb_diag_close(diag_bridge[i].ch);
#endif
kfree(diag_bridge[i].usb_buf_out);
kfree(diag_bridge[i].usb_read_ptr);
destroy_workqueue(diag_bridge[i].wq);
diag_bridge[i].enabled = 0;
}
}
kfree(driver->write_ptr_mdm);
}