/* 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 #include #include #include #include #include #include #include #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 #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_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_BRIDGE, "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) { diag_notify_md_client(BRIDGE_TO_MUX(id), 0, DIAG_STATUS_OPEN); 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)); if (bridge_info[id].type == DIAG_DATA_TYPE) diag_notify_md_client(BRIDGE_TO_MUX(id), 0, DIAG_STATUS_CLOSED); } 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())) { 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(); }