/* 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 #include #include #include #include #include #include #include #include #ifdef CONFIG_DIAG_OVER_USB #include #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) { int 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, int 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 && driver->diag_smux_enabled) { driver->in_busy_smux = 0; diagfwd_connect_smux(); } else { 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 *)(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); }