/* Copyright (c) 2019-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 "qrtr.h" #define QRTR_MAX_PKT_SIZE SZ_32K /* MHI DEV Enums are defined from Host perspective */ #define QRTR_MHI_DEV_OUT MHI_CLIENT_IPCR_IN #define QRTR_MHI_DEV_IN MHI_CLIENT_IPCR_OUT /** * struct qrtr_mhi_dev_ep - qrtr mhi device endpoint * @ep: endpoint * @dev: device from platform bus * @out: channel handle from mhi dev * @out_tre: complete when channel is ready to send * @out_lock: hold when resetting completion variable * @in: channel handle from mhi dev * @buf_in: buffer to hold incoming data * @net_id: subnet id used by qrtr core * @rt: realtime option used by qrtr core */ struct qrtr_mhi_dev_ep { struct qrtr_endpoint ep; struct device *dev; struct mhi_dev_client *out; struct completion out_tre; struct mutex out_lock; /* for out critical sections */ struct mhi_dev_client *in; void *buf_in; u32 net_id; bool rt; }; static struct qrtr_mhi_dev_ep *qrtr_mhi_device_endpoint; static int qrtr_mhi_dev_send(struct qrtr_endpoint *ep, struct sk_buff *skb) { struct qrtr_mhi_dev_ep *qep; struct mhi_req req = { 0 }; int rc; qep = container_of(ep, struct qrtr_mhi_dev_ep, ep); rc = skb_linearize(skb); if (rc) { kfree_skb(skb); return rc; } req.chan = QRTR_MHI_DEV_OUT; req.client = qep->out; req.mode = DMA_SYNC; req.buf = skb->data; req.len = skb->len; do { wait_for_completion(&qep->out_tre); mutex_lock(&qep->out_lock); rc = mhi_dev_write_channel(&req); if (rc == 0) reinit_completion(&qep->out_tre); mutex_unlock(&qep->out_lock); } while (!rc); if (rc != skb->len) { dev_err(qep->dev, "send failed rc:%d len:%d\n", rc, skb->len); kfree_skb(skb); return rc; } consume_skb(skb); return 0; } static void qrtr_mhi_dev_read(struct qrtr_mhi_dev_ep *qep) { struct mhi_req req = { 0 }; int rc; int bytes_read; req.chan = QRTR_MHI_DEV_IN; req.client = qep->in; req.mode = DMA_SYNC; req.buf = qep->buf_in; req.len = QRTR_MAX_PKT_SIZE; do { bytes_read = mhi_dev_read_channel(&req); if (bytes_read < 0) { dev_err(qep->dev, "failed to read channel %d\n", bytes_read); return; } if (bytes_read == 0) return; rc = qrtr_endpoint_post(&qep->ep, req.buf, req.transfer_len); if (rc == -EINVAL) dev_err(qep->dev, "invalid ipcrouter packet\n"); } while (bytes_read > 0); } static void qrtr_mhi_dev_event_cb(struct mhi_dev_client_cb_reason *reason) { struct qrtr_mhi_dev_ep *qep; qep = qrtr_mhi_device_endpoint; if (!qep) return; if (reason->reason == MHI_DEV_TRE_AVAILABLE) { pr_debug("TRE available event for chan %d\n", reason->ch_id); if (reason->ch_id == QRTR_MHI_DEV_IN) { qrtr_mhi_dev_read(qep); } else { mutex_lock(&qep->out_lock); complete_all(&qep->out_tre); mutex_unlock(&qep->out_lock); } } } static int qrtr_mhi_dev_open_channels(struct qrtr_mhi_dev_ep *qep) { int rc; /* write channel */ rc = mhi_dev_open_channel(QRTR_MHI_DEV_IN, &qep->in, qrtr_mhi_dev_event_cb); if (rc < 0) return rc; /* read channel */ rc = mhi_dev_open_channel(QRTR_MHI_DEV_OUT, &qep->out, qrtr_mhi_dev_event_cb); if (rc < 0) { mhi_dev_close_channel(qep->in); return rc; } return 0; } static void qrtr_mhi_dev_close_channels(struct qrtr_mhi_dev_ep *qep) { mhi_dev_close_channel(qep->in); mhi_dev_close_channel(qep->out); } static void qrtr_mhi_dev_state_cb(struct mhi_dev_client_cb_data *cb_data) { struct qrtr_mhi_dev_ep *qep; int rc; if (!cb_data || !cb_data->user_data) return; qep = cb_data->user_data; switch (cb_data->ctrl_info) { case MHI_STATE_CONNECTED: rc = qrtr_mhi_dev_open_channels(qep); if (rc) { dev_err(qep->dev, "open failed %d", rc); return; } rc = qrtr_endpoint_register(&qep->ep, qep->net_id, qep->rt); if (rc) { dev_err(qep->dev, "register failed %d", rc); qrtr_mhi_dev_close_channels(qep); } break; case MHI_STATE_DISCONNECTED: qrtr_endpoint_unregister(&qep->ep); qrtr_mhi_dev_close_channels(qep); break; default: break; } } static int qrtr_mhi_dev_probe(struct platform_device *pdev) { struct qrtr_mhi_dev_ep *qep; struct device_node *node; int rc; struct mhi_dev_client_cb_data cb_data; qep = devm_kzalloc(&pdev->dev, sizeof(*qep), GFP_KERNEL); if (!qep) return -ENOMEM; qep->dev = &pdev->dev; node = pdev->dev.of_node; rc = of_property_read_u32(node, "qcom,net-id", &qep->net_id); if (rc < 0) qep->net_id = QRTR_EP_NET_ID_AUTO; qep->rt = of_property_read_bool(node, "qcom,low-latency"); qep->buf_in = devm_kzalloc(&pdev->dev, QRTR_MAX_PKT_SIZE, GFP_KERNEL); if (!qep->buf_in) return -ENOMEM; qrtr_mhi_device_endpoint = qep; mutex_init(&qep->out_lock); init_completion(&qep->out_tre); qep->ep.xmit = qrtr_mhi_dev_send; /* HOST init TX first followed by RX, so register for endpoint TX * which makes both channel ready by checking one channel state. */ rc = mhi_register_state_cb(qrtr_mhi_dev_state_cb, qep, QRTR_MHI_DEV_OUT); if (rc == -EEXIST) { /** * MHI stack will return -EEXIST if mhi channel is already * opend by the host and will not invoke reqistered callback. * But future state change notification will inform through * registered callback. */ complete_all(&qep->out_tre); cb_data.user_data = (void *)qep; cb_data.channel = QRTR_MHI_DEV_OUT; cb_data.ctrl_info = MHI_STATE_CONNECTED; qrtr_mhi_dev_state_cb(&cb_data); } else if (rc) { return rc; } return 0; } static const struct of_device_id qrtr_mhi_dev_match_table[] = { { .compatible = "qcom,qrtr-mhi-dev"}, {}, }; static struct platform_driver qrtr_mhi_dev_driver = { .probe = qrtr_mhi_dev_probe, .driver = { .name = "qrtr_mhi_dev", .of_match_table = qrtr_mhi_dev_match_table, }, }; module_platform_driver(qrtr_mhi_dev_driver); MODULE_DESCRIPTION("QTI IPC-Router MHI device interface driver"); MODULE_LICENSE("GPL v2");