| /* Copyright (c) 2012, 2014, 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/termios.h> |
| #include <linux/slab.h> |
| #include <linux/diagchar.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/usb/usbdiag.h> |
| |
| #include "diagchar.h" |
| #include "diagfwd_bridge.h" |
| #include "diagfwd_smux.h" |
| |
| struct diag_smux_info diag_smux[NUM_SMUX_DEV] = { |
| { |
| .id = SMUX_1, |
| .lcid = SMUX_USB_DIAG_0, |
| .dev_id = DIAGFWD_SMUX, |
| .name = "SMUX_1", |
| .read_buf = NULL, |
| .read_len = 0, |
| .in_busy = 0, |
| .enabled = 0, |
| .opened = 0, |
| }, |
| }; |
| |
| static void diag_smux_event(void *priv, int event_type, const void *metadata) |
| { |
| int len = 0; |
| int id = (int)priv; |
| unsigned char *rx_buf = NULL; |
| struct diag_smux_info *ch = NULL; |
| |
| if (id < 0 || id >= NUM_SMUX_DEV) |
| return; |
| |
| ch = &diag_smux[id]; |
| if (metadata) { |
| len = ((struct smux_meta_read *)metadata)->len; |
| rx_buf = ((struct smux_meta_read *)metadata)->buffer; |
| } |
| |
| switch (event_type) { |
| case SMUX_CONNECTED: |
| pr_info("diag: SMUX_CONNECTED received, ch: %d\n", ch->id); |
| ch->opened = 1; |
| ch->in_busy = 0; |
| break; |
| case SMUX_DISCONNECTED: |
| ch->opened = 0; |
| msm_smux_close(ch->lcid); |
| pr_info("diag: SMUX_DISCONNECTED received, ch: %d\n", ch->id); |
| break; |
| case SMUX_WRITE_DONE: |
| pr_debug("diag: SMUX Write done, ch: %d\n", ch->id); |
| diag_remote_dev_write_done(ch->dev_id, rx_buf, len, ch->id); |
| break; |
| case SMUX_WRITE_FAIL: |
| pr_info("diag: SMUX Write Failed, ch: %d\n", ch->id); |
| break; |
| case SMUX_READ_FAIL: |
| pr_info("diag: SMUX Read Failed, ch: %d\n", ch->id); |
| break; |
| case SMUX_READ_DONE: |
| ch->read_buf = rx_buf; |
| ch->read_len = len; |
| ch->in_busy = 1; |
| diag_remote_dev_read_done(ch->dev_id, ch->read_buf, |
| ch->read_len); |
| break; |
| }; |
| } |
| |
| static int diag_smux_init_ch(struct diag_smux_info *ch) |
| { |
| if (!ch) |
| return -EINVAL; |
| |
| if (!ch->enabled) { |
| pr_debug("diag: SMUX channel is not enabled id: %d\n", ch->id); |
| return -ENODEV; |
| } |
| |
| if (ch->inited) { |
| pr_debug("diag: SMUX channel %d is already initialize\n", |
| ch->id); |
| return 0; |
| } |
| |
| ch->read_buf = kzalloc(IN_BUF_SIZE, GFP_KERNEL); |
| if (!ch->read_buf) |
| return -ENOMEM; |
| |
| ch->inited = 1; |
| |
| return 0; |
| } |
| |
| static int smux_get_rx_buffer(void *priv, void **pkt_priv, void **buf, |
| int size) |
| { |
| int id = (int)priv; |
| struct diag_smux_info *ch = NULL; |
| |
| if (id < 0 || id >= NUM_SMUX_DEV) |
| return -EINVAL; |
| |
| ch = &diag_smux[id]; |
| |
| if (ch->in_busy) { |
| pr_debug("diag: read buffer for SMUX is BUSY\n"); |
| return -EAGAIN; |
| } |
| |
| *pkt_priv = (void *)0x1234; |
| *buf = ch->read_buf; |
| ch->in_busy = 1; |
| return 0; |
| } |
| |
| static int smux_open(int id) |
| { |
| int err = 0; |
| struct diag_smux_info *ch = NULL; |
| |
| if (id < 0 || id >= NUM_SMUX_DEV) |
| return -EINVAL; |
| |
| ch = &diag_smux[id]; |
| if (ch->opened) { |
| pr_debug("diag: SMUX channel %d is already connected\n", |
| ch->id); |
| return 0; |
| } |
| |
| err = diag_smux_init_ch(ch); |
| if (err) { |
| pr_err("diag: Unable to initialize SMUX channel %d, err: %d\n", |
| ch->id, err); |
| return err; |
| } |
| |
| err = msm_smux_open(ch->lcid, (void *)ch->id, diag_smux_event, |
| smux_get_rx_buffer); |
| if (err) { |
| pr_err("diag: failed to open SMUX ch %d, err: %d\n", |
| ch->id, err); |
| return err; |
| } |
| msm_smux_tiocm_set(ch->lcid, TIOCM_DTR, 0); |
| ch->opened = 1; |
| pr_info("diag: SMUX ch %d is connected\n", ch->id); |
| return 0; |
| } |
| |
| static int smux_close(int id) |
| { |
| struct diag_smux_info *ch = NULL; |
| |
| if (id < 0 || id >= NUM_SMUX_DEV) |
| return -EINVAL; |
| |
| ch = &diag_smux[id]; |
| if (!ch->enabled) { |
| pr_debug("diag: SMUX channel is not enabled id: %d\n", ch->id); |
| return -ENODEV; |
| } |
| |
| msm_smux_close(ch->lcid); |
| ch->opened = 0; |
| ch->in_busy = 1; |
| kfree(ch->read_buf); |
| ch->read_buf = NULL; |
| return 0; |
| } |
| |
| static int smux_queue_read(int id) |
| { |
| return 0; |
| } |
| |
| static int smux_write(int id, unsigned char *buf, int len, int ctxt) |
| { |
| struct diag_smux_info *ch = NULL; |
| |
| if (id < 0 || id >= NUM_SMUX_DEV) |
| return -EINVAL; |
| |
| ch = &diag_smux[id]; |
| return msm_smux_write(ch->lcid, NULL, buf, len); |
| } |
| |
| static int smux_fwd_complete(int id, unsigned char *buf, int len, int ctxt) |
| { |
| if (id < 0 || id >= NUM_SMUX_DEV) |
| return -EINVAL; |
| |
| diag_smux[id].in_busy = 0; |
| return 0; |
| } |
| |
| static int diagfwd_smux_runtime_suspend(struct device *dev) |
| { |
| dev_dbg(dev, "pm_runtime: suspending...\n"); |
| return 0; |
| } |
| |
| static int diagfwd_smux_runtime_resume(struct device *dev) |
| { |
| dev_dbg(dev, "pm_runtime: resuming...\n"); |
| return 0; |
| } |
| |
| static const struct dev_pm_ops diagfwd_smux_dev_pm_ops = { |
| .runtime_suspend = diagfwd_smux_runtime_suspend, |
| .runtime_resume = diagfwd_smux_runtime_resume, |
| }; |
| |
| static int diagfwd_smux_probe(struct platform_device *pdev) |
| { |
| if (!pdev) |
| return -EINVAL; |
| |
| pr_debug("diag: SMUX probe called, pdev->id: %d\n", pdev->id); |
| if (pdev->id < 0 || pdev->id >= NUM_SMUX_DEV) { |
| pr_err("diag: No support for SMUX device %d\n", pdev->id); |
| return -EINVAL; |
| } |
| |
| diag_smux[pdev->id].enabled = 1; |
| return smux_open(pdev->id); |
| } |
| |
| static int diagfwd_smux_remove(struct platform_device *pdev) |
| { |
| if (!pdev) |
| return -EINVAL; |
| |
| pr_debug("diag: SMUX probe called, pdev->id: %d\n", pdev->id); |
| if (pdev->id < 0 || pdev->id >= NUM_SMUX_DEV) { |
| pr_err("diag: No support for SMUX device %d\n", pdev->id); |
| return -EINVAL; |
| } |
| if (!diag_smux[pdev->id].enabled) { |
| pr_err("diag: SMUX channel %d is not enabled\n", |
| diag_smux[pdev->id].id); |
| return -ENODEV; |
| } |
| return smux_close(pdev->id); |
| } |
| |
| static struct platform_driver msm_diagfwd_smux_driver = { |
| .probe = diagfwd_smux_probe, |
| .remove = diagfwd_smux_remove, |
| .driver = { |
| .name = "SMUX_DIAG", |
| .owner = THIS_MODULE, |
| .pm = &diagfwd_smux_dev_pm_ops, |
| }, |
| }; |
| |
| static struct diag_remote_dev_ops diag_smux_fwd_ops = { |
| .open = smux_open, |
| .close = smux_close, |
| .queue_read = smux_queue_read, |
| .write = smux_write, |
| .fwd_complete = smux_fwd_complete, |
| }; |
| |
| int diag_smux_init(void) |
| { |
| int i; |
| int err = 0; |
| struct diag_smux_info *ch = NULL; |
| char wq_name[DIAG_SMUX_NAME_SZ + 11]; |
| |
| for (i = 0; i < NUM_SMUX_DEV; i++) { |
| ch = &diag_smux[i]; |
| strlcpy(wq_name, "DIAG_SMUX_", 11); |
| strlcat(wq_name, ch->name, sizeof(ch->name)); |
| ch->smux_wq = create_singlethread_workqueue(wq_name); |
| if (!ch->smux_wq) { |
| err = -ENOMEM; |
| goto fail; |
| } |
| err = diagfwd_bridge_register(ch->dev_id, ch->id, |
| &diag_smux_fwd_ops); |
| if (err) { |
| pr_err("diag: Unable to register SMUX ch %d with bridge\n", |
| ch->id); |
| goto fail; |
| } |
| } |
| |
| err = platform_driver_register(&msm_diagfwd_smux_driver); |
| if (err) { |
| pr_err("diag: Unable to register SMUX device, err: %d\n", err); |
| goto fail; |
| } |
| |
| return 0; |
| fail: |
| diag_smux_exit(); |
| return err; |
| } |
| |
| void diag_smux_exit(void) |
| { |
| int i; |
| struct diag_smux_info *ch = NULL; |
| |
| for (i = 0; i < NUM_SMUX_DEV; i++) { |
| ch = &diag_smux[i]; |
| kfree(ch->read_buf); |
| ch->read_buf = NULL; |
| ch->enabled = 0; |
| ch->opened = 0; |
| ch->read_len = 0; |
| } |
| platform_driver_unregister(&msm_diagfwd_smux_driver); |
| } |