Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 36809d1c authored by Tingwei Zhang's avatar Tingwei Zhang
Browse files

soc: qcom: Add a snapshot of QDSS bridge driver



This is the snapshot of the QDSS bridge driver as of msm-4.14 commit
4f22a43 (soc: qcom: qdss_bridge: Add spin_lock for accessing lists).
Use DEVICE_ATTR_RW for mode.

Change-Id: I5fcd2ea772ff47e5fc9e4c4b35a5f954e593e066
Signed-off-by: default avatarTingwei Zhang <tingwei@codeaurora.org>
parent bdd918fd
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -531,6 +531,15 @@ config QCOM_GLINK_PKT
	  This enable the userspace clients to read and write to
	  some glink packets channel.

config QCOM_QDSS_BRIDGE
	bool "Configure bridge driver for QTI/Qualcomm Technologies, Inc. MDM"
	depends on MHI_BUS
	help
	  The driver will help route diag traffic from modem side over the QDSS
	  sub-system to USB on APSS side. The driver acts as a bridge between the
	  MHI and USB interface.
	  If unsure, say N.

config MSM_CDSP_LOADER
	tristate "CDSP loader support"
	help
+1 −0
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ obj-$(CONFIG_QCOM_BUS_SCALING) += msm_bus/
obj-$(CONFIG_QCOM_FSA4480_I2C) += fsa4480-i2c.o
obj-$(CONFIG_QCOM_GLINK) += glink_probe.o
obj-$(CONFIG_QCOM_GLINK_PKT) += glink_pkt.o
obj-$(CONFIG_QCOM_QDSS_BRIDGE) += qdss_bridge.o
obj-$(CONFIG_QSEE_IPC_IRQ) += qsee_ipc_irq.o
obj-$(CONFIG_QSEE_IPC_IRQ_BRIDGE) += qsee_ipc_irq_bridge.o
obj-$(CONFIG_QPNP_PBS) += qpnp-pbs.o
+932 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
 */

#define KMSG_COMPONENT "QDSS diag bridge"
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt

#include <linux/slab.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/poll.h>
#include <linux/ratelimit.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/dma-direction.h>
#include <linux/mhi.h>
#include <linux/usb/usb_qdss.h>
#include <linux/of.h>
#include "qdss_bridge.h"

#define MODULE_NAME "qdss_bridge"

#define QDSS_BUF_SIZE		(16*1024)
#define MHI_CLIENT_QDSS_IN	9

/* Max number of objects needed */
static int poolsize = 32;

static struct class *mhi_class;

static const char * const str_mhi_transfer_mode[] = {
		[MHI_TRANSFER_TYPE_USB]			= "usb",
		[MHI_TRANSFER_TYPE_UCI]			= "uci",
};

static int qdss_destroy_mhi_buf_tbl(struct qdss_bridge_drvdata *drvdata)
{
	struct list_head *start, *temp;
	struct qdss_mhi_buf_tbl_t *entry = NULL;

	spin_lock_bh(&drvdata->lock);
	list_for_each_safe(start, temp, &drvdata->mhi_buf_tbl) {
		entry = list_entry(start, struct qdss_mhi_buf_tbl_t, link);
		list_del(&entry->link);
		kfree(entry->buf);
		kfree(entry);
	}
	spin_unlock_bh(&drvdata->lock);

	return 0;
}

static int qdss_destroy_buf_tbl(struct qdss_bridge_drvdata *drvdata)
{
	struct list_head *start, *temp;
	struct qdss_buf_tbl_lst *entry = NULL;

	spin_lock_bh(&drvdata->lock);
	list_for_each_safe(start, temp, &drvdata->buf_tbl) {
		entry = list_entry(start, struct qdss_buf_tbl_lst, link);
		list_del(&entry->link);
		kfree(entry->buf);
		kfree(entry->usb_req);
		kfree(entry);
	}
	spin_unlock_bh(&drvdata->lock);

	return 0;
}

static int qdss_destroy_read_done_list(struct qdss_bridge_drvdata *drvdata)
{
	struct list_head *start, *temp;
	struct qdss_mhi_buf_tbl_t *entry = NULL;

	spin_lock_bh(&drvdata->lock);
	list_for_each_safe(start, temp, &drvdata->read_done_list) {
		entry = list_entry(start, struct qdss_mhi_buf_tbl_t, link);
		list_del(&entry->link);
		kfree(entry);
	}
	spin_unlock_bh(&drvdata->lock);

	return 0;
}

static int qdss_create_buf_tbl(struct qdss_bridge_drvdata *drvdata)
{
	struct qdss_buf_tbl_lst *entry;
	void *buf;
	struct qdss_request *usb_req;
	int i;

	for (i = 0; i < poolsize; i++) {
		entry = kzalloc(sizeof(*entry), GFP_KERNEL);
		if (!entry)
			goto err;

		buf = kzalloc(drvdata->mtu, GFP_KERNEL);
		usb_req = kzalloc(sizeof(*usb_req), GFP_KERNEL);

		entry->buf = buf;
		entry->usb_req = usb_req;
		atomic_set(&entry->available, 1);
		list_add_tail(&entry->link, &drvdata->buf_tbl);

		if (!buf || !usb_req)
			goto err;
	}

	return 0;
err:
	qdss_destroy_buf_tbl(drvdata);
	return -ENOMEM;
}

struct qdss_buf_tbl_lst *qdss_get_buf_tbl_entry(
					struct qdss_bridge_drvdata *drvdata,
					void *buf)
{
	struct qdss_buf_tbl_lst *entry;

	spin_lock_bh(&drvdata->lock);
	list_for_each_entry(entry, &drvdata->buf_tbl, link) {
		if (atomic_read(&entry->available))
			continue;
		if (entry->buf == buf) {
			spin_unlock_bh(&drvdata->lock);
			return entry;
		}
	}

	spin_unlock_bh(&drvdata->lock);
	return NULL;
}


static void qdss_del_buf_tbl_entry(struct qdss_bridge_drvdata *drvdata,
				void *buf)
{
	struct qdss_mhi_buf_tbl_t *entry, *tmp;

	spin_lock_bh(&drvdata->lock);
	list_for_each_entry_safe(entry, tmp, &drvdata->mhi_buf_tbl, link) {
		if (entry->buf == buf) {
			list_del(&entry->link);
			kfree(entry->buf);
			kfree(entry);
			spin_unlock_bh(&drvdata->lock);
			return;
		}
	}

	spin_unlock_bh(&drvdata->lock);
}

struct qdss_buf_tbl_lst *qdss_get_entry(struct qdss_bridge_drvdata *drvdata)
{
	struct qdss_buf_tbl_lst *item;

	list_for_each_entry(item, &drvdata->buf_tbl, link)
		if (atomic_cmpxchg(&item->available, 1, 0) == 1)
			return item;

	return NULL;
}

static void qdss_buf_tbl_remove(struct qdss_bridge_drvdata *drvdata,
				void *buf)
{
	struct qdss_buf_tbl_lst *entry = NULL;

	spin_lock_bh(&drvdata->lock);
	list_for_each_entry(entry, &drvdata->buf_tbl, link) {
		if (entry->buf != buf)
			continue;
		atomic_set(&entry->available, 1);
		spin_unlock_bh(&drvdata->lock);
		return;
	}
	spin_unlock_bh(&drvdata->lock);
	pr_err_ratelimited("Failed to find buffer for removal\n");
}

static void mhi_ch_close(struct qdss_bridge_drvdata *drvdata)
{
	if (drvdata->mode == MHI_TRANSFER_TYPE_USB) {
		flush_workqueue(drvdata->mhi_wq);
		qdss_destroy_buf_tbl(drvdata);
		qdss_destroy_read_done_list(drvdata);
	} else if (drvdata->mode == MHI_TRANSFER_TYPE_UCI) {
		qdss_destroy_mhi_buf_tbl(drvdata);
		drvdata->cur_buf = NULL;
		qdss_destroy_read_done_list(drvdata);
	}
}

static ssize_t mode_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct qdss_bridge_drvdata *drvdata = dev_get_drvdata(dev);

	return scnprintf(buf, PAGE_SIZE, "%s\n",
			str_mhi_transfer_mode[drvdata->mode]);
}

static ssize_t mode_store(struct device *dev,
					struct device_attribute *attr,
					const char *buf, size_t size)
{
	struct qdss_bridge_drvdata *drvdata = dev_get_drvdata(dev);
	char str[10] = "";
	int ret;

	if (strlen(buf) >= 10)
		return -EINVAL;
	if (sscanf(buf, "%3s", str) != 1)
		return -EINVAL;

	spin_lock_bh(&drvdata->lock);
	if (!strcmp(str, str_mhi_transfer_mode[MHI_TRANSFER_TYPE_UCI])) {
		if (drvdata->mode == MHI_TRANSFER_TYPE_USB) {
			if (drvdata->opened == ENABLE) {
				drvdata->opened = DISABLE;
				spin_unlock_bh(&drvdata->lock);
				usb_qdss_close(drvdata->usb_ch);
				mhi_unprepare_from_transfer(drvdata->mhi_dev);
				mhi_ch_close(drvdata);
				drvdata->mode = MHI_TRANSFER_TYPE_UCI;
			} else if (drvdata->opened == DISABLE) {
				drvdata->mode = MHI_TRANSFER_TYPE_UCI;
				spin_unlock_bh(&drvdata->lock);
			} else {
				ret = -ERESTARTSYS;
				goto out;
			}
		} else
			spin_unlock_bh(&drvdata->lock);

	} else if (!strcmp(str, str_mhi_transfer_mode[MHI_TRANSFER_TYPE_USB])) {
		if (drvdata->mode == MHI_TRANSFER_TYPE_UCI) {
			if (drvdata->opened == ENABLE) {
				drvdata->opened = DISABLE;
				spin_unlock_bh(&drvdata->lock);
				wake_up(&drvdata->uci_wq);
				mhi_unprepare_from_transfer(drvdata->mhi_dev);
				mhi_ch_close(drvdata);
				drvdata->mode = MHI_TRANSFER_TYPE_USB;
				queue_work(drvdata->mhi_wq,
						&drvdata->open_work);
			} else if (drvdata->opened == DISABLE) {
				drvdata->mode = MHI_TRANSFER_TYPE_USB;
				spin_unlock_bh(&drvdata->lock);
				queue_work(drvdata->mhi_wq,
						&drvdata->open_work);
			} else {
				ret = -ERESTARTSYS;
				goto out;
			}
		} else
			spin_unlock_bh(&drvdata->lock);

	} else {
		ret = -EINVAL;
		goto out;
	}

	ret = size;
	return ret;
out:
	spin_unlock_bh(&drvdata->lock);
	return ret;
}

static DEVICE_ATTR_RW(mode);

static void mhi_read_work_fn(struct work_struct *work)
{
	int err = 0;
	enum MHI_FLAGS mhi_flags = MHI_EOT;
	struct qdss_buf_tbl_lst *entry;

	struct qdss_bridge_drvdata *drvdata =
				container_of(work,
					     struct qdss_bridge_drvdata,
					     read_work);

	do {
		spin_lock_bh(&drvdata->lock);
		if (drvdata->opened != ENABLE) {
			spin_unlock_bh(&drvdata->lock);
			break;
		}
		entry = qdss_get_entry(drvdata);
		if (!entry) {
			spin_unlock_bh(&drvdata->lock);
			break;
		}

		err = mhi_queue_transfer(drvdata->mhi_dev, DMA_FROM_DEVICE,
					entry->buf, drvdata->mtu, mhi_flags);
		if (err) {
			pr_err_ratelimited("Unable to read from MHI buffer err:%d",
					   err);
			goto fail;
		}
		spin_unlock_bh(&drvdata->lock);

	} while (entry);

	return;
fail:
	spin_unlock_bh(&drvdata->lock);
	qdss_buf_tbl_remove(drvdata, entry->buf);
	queue_work(drvdata->mhi_wq, &drvdata->read_work);
}

static int mhi_queue_read(struct qdss_bridge_drvdata *drvdata)
{
	queue_work(drvdata->mhi_wq, &(drvdata->read_work));
	return 0;
}

static int usb_write(struct qdss_bridge_drvdata *drvdata,
			     unsigned char *buf, size_t len)
{
	int ret = 0;
	struct qdss_buf_tbl_lst *entry;

	entry = qdss_get_buf_tbl_entry(drvdata, buf);
	if (!entry)
		return -EINVAL;

	entry->usb_req->buf = buf;
	entry->usb_req->length = len;
	ret = usb_qdss_write(drvdata->usb_ch, entry->usb_req);

	return ret;
}

static void mhi_read_done_work_fn(struct work_struct *work)
{
	unsigned char *buf = NULL;
	int err = 0;
	size_t len = 0;
	struct qdss_mhi_buf_tbl_t *tp, *_tp;
	struct qdss_bridge_drvdata *drvdata =
				container_of(work,
					     struct qdss_bridge_drvdata,
					     read_done_work);
	LIST_HEAD(head);

	do {
		spin_lock_bh(&drvdata->lock);
		if (drvdata->opened != ENABLE
		    || drvdata->mode != MHI_TRANSFER_TYPE_USB) {
			spin_unlock_bh(&drvdata->lock);
			break;
		}

		if (list_empty(&drvdata->read_done_list)) {
			spin_unlock_bh(&drvdata->lock);
			break;
		}
		list_splice_tail_init(&drvdata->read_done_list, &head);
		spin_unlock_bh(&drvdata->lock);

		list_for_each_entry_safe(tp, _tp, &head, link) {
			list_del(&tp->link);
			buf = tp->buf;
			len = tp->len;
			kfree(tp);
			if (!buf)
				break;
			pr_debug("Read from mhi buf %pK len:%zd\n", buf, len);
			/*
			 * The read buffers can come after the MHI channels are
			 * closed. If the channels are closed at the time of
			 * read, discard the buffers here and do not forward
			 * them to the mux layer.
			 */
			spin_lock_bh(&drvdata->lock);
			if (drvdata->mode == MHI_TRANSFER_TYPE_USB) {
				if (drvdata->opened == ENABLE) {
					spin_unlock_bh(&drvdata->lock);
					err = usb_write(drvdata, buf, len);
					if (err)
						qdss_buf_tbl_remove(drvdata,
								    buf);
				} else if (drvdata->opened == DISABLE) {
					spin_unlock_bh(&drvdata->lock);
					qdss_buf_tbl_remove(drvdata, buf);
				} else {
					spin_unlock_bh(&drvdata->lock);
				}
			} else {
				spin_unlock_bh(&drvdata->lock);
			}
		}
		list_del_init(&head);
	} while (buf);
}

static void usb_write_done(struct qdss_bridge_drvdata *drvdata,
				   struct qdss_request *d_req)
{
	if (d_req->status) {
		pr_err_ratelimited("USB write failed err:%d\n", d_req->status);
		mhi_queue_read(drvdata);
		return;
	}
	qdss_buf_tbl_remove(drvdata, d_req->buf);
	mhi_queue_read(drvdata);
}

static void usb_notifier(void *priv, unsigned int event,
			struct qdss_request *d_req, struct usb_qdss_ch *ch)
{
	struct qdss_bridge_drvdata *drvdata = priv;

	if (!drvdata)
		return;

	switch (event) {
	case USB_QDSS_CONNECT:
		usb_qdss_alloc_req(ch, poolsize, 0);
		mhi_queue_read(drvdata);
		break;

	case USB_QDSS_DISCONNECT:
		/* Leave MHI/USB open.Only close on MHI disconnect */
		break;

	case USB_QDSS_DATA_WRITE_DONE:
		usb_write_done(drvdata, d_req);
		break;

	default:
		break;
	}
}

static int mhi_ch_open(struct qdss_bridge_drvdata *drvdata)
{
	int ret;

	spin_lock_bh(&drvdata->lock);
	if (drvdata->opened == ENABLE) {
		spin_unlock_bh(&drvdata->lock);
		return 0;
	}
	if (drvdata->opened == SSR) {
		spin_unlock_bh(&drvdata->lock);
		return -ERESTARTSYS;
	}
	drvdata->opened = ENABLE;
	spin_unlock_bh(&drvdata->lock);

	ret = mhi_prepare_for_transfer(drvdata->mhi_dev);
	if (ret) {
		pr_err("Unable to open MHI channel\n");
		goto err;
	}

	return 0;
err:
	spin_lock_bh(&drvdata->lock);
	drvdata->opened = DISABLE;
	spin_unlock_bh(&drvdata->lock);
	return ret;
}

static void qdss_bridge_open_work_fn(struct work_struct *work)
{
	struct qdss_bridge_drvdata *drvdata =
				container_of(work,
					     struct qdss_bridge_drvdata,
					     open_work);
	int ret;

	ret = mhi_ch_open(drvdata);
	if (ret)
		goto err_open;

	ret = qdss_create_buf_tbl(drvdata);
	if (ret)
		goto err;

	drvdata->usb_ch = usb_qdss_open("qdss_mdm", drvdata, usb_notifier);
	if (IS_ERR_OR_NULL(drvdata->usb_ch)) {
		ret = PTR_ERR(drvdata->usb_ch);
		goto err;
	}

	return;
err:
	mhi_unprepare_from_transfer(drvdata->mhi_dev);
	mhi_ch_close(drvdata);
err_open:
	pr_err("Open work failed with err:%d\n", ret);
}

static void qdss_mhi_write_cb(struct mhi_device *mhi_dev,
				struct mhi_result *result)
{
}

static void qdss_mhi_read_cb(struct mhi_device *mhi_dev,
				struct mhi_result *result)
{
	struct qdss_bridge_drvdata *drvdata = NULL;
	struct qdss_mhi_buf_tbl_t *tp;
	void *buf = NULL;

	drvdata = mhi_dev->priv_data;
	if (!drvdata)
		return;
	buf = result->buf_addr;

	spin_lock_bh(&drvdata->lock);
	if (drvdata->opened == ENABLE &&
	    result->transaction_status != -ENOTCONN) {
		spin_unlock_bh(&drvdata->lock);
		tp = kmalloc(sizeof(*tp), GFP_ATOMIC);
		if (!tp)
			return;
		tp->buf = buf;
		tp->len = result->bytes_xferd;
		spin_lock_bh(&drvdata->lock);
		list_add_tail(&tp->link, &drvdata->read_done_list);
		spin_unlock_bh(&drvdata->lock);
		if (drvdata->mode == MHI_TRANSFER_TYPE_USB)
			queue_work(drvdata->mhi_wq, &drvdata->read_done_work);
		else
			wake_up(&drvdata->uci_wq);
	} else {
		if (drvdata->mode == MHI_TRANSFER_TYPE_USB) {
			spin_unlock_bh(&drvdata->lock);
			qdss_buf_tbl_remove(drvdata, buf);
		} else {
			spin_unlock_bh(&drvdata->lock);
			return;
		}
	}

}

static int mhi_uci_release(struct inode *inode, struct file *file)
{
	struct qdss_bridge_drvdata *drvdata = file->private_data;

	spin_lock_bh(&drvdata->lock);
	if (drvdata->mode == MHI_TRANSFER_TYPE_UCI) {
		if (drvdata->opened == ENABLE) {
			drvdata->opened = DISABLE;
			spin_unlock_bh(&drvdata->lock);
			wake_up(&drvdata->uci_wq);
			mhi_unprepare_from_transfer(drvdata->mhi_dev);
			mhi_ch_close(drvdata);
		} else if (drvdata->opened == SSR) {
			spin_unlock_bh(&drvdata->lock);
			complete(&drvdata->completion);
		} else
			spin_unlock_bh(&drvdata->lock);
	} else
		spin_unlock_bh(&drvdata->lock);

	return 0;
}

static ssize_t mhi_uci_read(struct file *file,
			char __user *buf,
			size_t count,
			loff_t *ppos)
{
	struct qdss_bridge_drvdata *drvdata = file->private_data;
	struct mhi_device *mhi_dev = drvdata->mhi_dev;
	struct qdss_mhi_buf_tbl_t *uci_buf;
	char *ptr;
	size_t to_copy;
	int ret = 0;

	if (!buf)
		return -EINVAL;

	pr_debug("Client provided buf len:%lu\n", count);

	/* confirm channel is active */
	spin_lock_bh(&drvdata->lock);
	if (drvdata->opened != ENABLE ||
	    drvdata->mode != MHI_TRANSFER_TYPE_UCI) {
		spin_unlock_bh(&drvdata->lock);
		return -ERESTARTSYS;
	}

	/* No data available to read, wait */
	if (!drvdata->cur_buf && list_empty(&drvdata->read_done_list)) {
		spin_unlock_bh(&drvdata->lock);

		pr_debug("No data available to read waiting\n");
		ret = wait_event_interruptible(drvdata->uci_wq,
				((drvdata->opened != ENABLE
				 || !list_empty(&drvdata->read_done_list))));
		if (ret == -ERESTARTSYS) {
			pr_debug("Exit signal caught for node\n");
			return -ERESTARTSYS;
		}

		spin_lock_bh(&drvdata->lock);
		if (drvdata->opened != ENABLE) {
			spin_unlock_bh(&drvdata->lock);
			pr_debug("node was disabled or SSR occurred.\n");
			ret = -ERESTARTSYS;
			return ret;
		}
	}

	/* new read, get the next descriptor from the list */
	if (!drvdata->cur_buf) {
		uci_buf = list_first_entry_or_null(&drvdata->read_done_list,
					struct qdss_mhi_buf_tbl_t, link);
		if (unlikely(!uci_buf)) {
			ret = -EIO;
			goto read_error;
		}

		list_del(&uci_buf->link);
		drvdata->cur_buf = uci_buf;
		drvdata->rx_size = uci_buf->len;
		pr_debug("Got pkt of size:%zu\n", drvdata->rx_size);
	}

	uci_buf = drvdata->cur_buf;
	spin_unlock_bh(&drvdata->lock);

	/* Copy the buffer to user space */
	to_copy = min_t(size_t, count, drvdata->rx_size);
	ptr = uci_buf->buf + (uci_buf->len - drvdata->rx_size);
	ret = copy_to_user(buf, ptr, to_copy);
	if (ret)
		return ret;

	pr_debug("Copied %lu of %lu bytes\n", to_copy, drvdata->rx_size);
	drvdata->rx_size -= to_copy;

	/* we finished with this buffer, queue it back to hardware */
	if (!drvdata->rx_size) {
		spin_lock_bh(&drvdata->lock);
		drvdata->cur_buf = NULL;

		if (drvdata->opened == ENABLE)
			ret = mhi_queue_transfer(mhi_dev, DMA_FROM_DEVICE,
						 uci_buf->buf, drvdata->mtu,
						 MHI_EOT);
		else
			ret = -ERESTARTSYS;

		spin_unlock_bh(&drvdata->lock);

		if (ret) {
			pr_err("Failed to recycle element, ret: %d\n", ret);
			qdss_del_buf_tbl_entry(drvdata, uci_buf->buf);
			uci_buf->buf = NULL;
			uci_buf = NULL;
			return ret;
		}
	}

	pr_debug("Returning %lu bytes\n", to_copy);
	return to_copy;

read_error:
	spin_unlock_bh(&drvdata->lock);
	return ret;
}

static int mhi_queue_inbound(struct qdss_bridge_drvdata *drvdata)
{
	struct mhi_device *mhi_dev = drvdata->mhi_dev;
	int nr_trbs = mhi_get_no_free_descriptors(mhi_dev, DMA_FROM_DEVICE);
	void *buf;
	struct qdss_mhi_buf_tbl_t *entry;
	int ret = -EIO, i;

	for (i = 0; i < nr_trbs; i++) {
		entry = kzalloc(sizeof(*entry), GFP_KERNEL);
		if (!entry)
			goto err;

		buf = kzalloc(drvdata->mtu, GFP_KERNEL);
		if (!buf) {
			kfree(entry);
			goto err;
		}

		entry->buf = buf;

		ret = mhi_queue_transfer(mhi_dev, DMA_FROM_DEVICE, buf,
					drvdata->mtu,
					MHI_EOT);
		if (ret) {
			kfree(buf);
			kfree(entry);
			pr_err("Failed to queue buffer %d\n", i);
			return ret;
		}
		list_add_tail(&entry->link, &drvdata->mhi_buf_tbl);
	}

	return ret;
err:
	return -ENOMEM;

}

static int mhi_uci_open(struct inode *inode, struct file *filp)
{
	int ret = -EIO;
	struct qdss_mhi_buf_tbl_t *buf_itr, *tmp;
	struct qdss_bridge_drvdata *drvdata = container_of(inode->i_cdev,
					struct qdss_bridge_drvdata,
					cdev);

	spin_lock_bh(&drvdata->lock);
	if (drvdata->opened) {
		pr_err("Node was opened or SSR occurred\n");
		spin_unlock_bh(&drvdata->lock);
		return ret;
	}
	drvdata->opened = ENABLE;
	spin_unlock_bh(&drvdata->lock);

	ret = mhi_prepare_for_transfer(drvdata->mhi_dev);
	if (ret) {
		pr_err("Error starting transfer channels\n");
		goto error_open_chan;
	}

	ret = mhi_queue_inbound(drvdata);
	if (ret)
		goto error_rx_queue;

	filp->private_data = drvdata;
	return ret;

error_rx_queue:
	mhi_unprepare_from_transfer(drvdata->mhi_dev);
	list_for_each_entry_safe(buf_itr, tmp, &drvdata->read_done_list, link) {
		list_del(&buf_itr->link);
		kfree(buf_itr->buf);
	}

error_open_chan:
	spin_lock_bh(&drvdata->lock);
	drvdata->opened = DISABLE;
	spin_unlock_bh(&drvdata->lock);
	return ret;
}



static const struct file_operations mhidev_fops = {
	.open = mhi_uci_open,
	.release = mhi_uci_release,
	.read = mhi_uci_read,
};

static void qdss_mhi_remove(struct mhi_device *mhi_dev)
{
	struct qdss_bridge_drvdata *drvdata = NULL;

	if (!mhi_dev)
		return;
	drvdata = mhi_dev->priv_data;
	if (!drvdata)
		return;
	spin_lock_bh(&drvdata->lock);
	if (drvdata->opened == ENABLE) {
		drvdata->opened = SSR;
		if (drvdata->mode == MHI_TRANSFER_TYPE_UCI) {
			spin_unlock_bh(&drvdata->lock);
			wake_up(&drvdata->uci_wq);
			wait_for_completion(&drvdata->completion);
		} else {
			spin_unlock_bh(&drvdata->lock);
			if (drvdata->usb_ch && drvdata->usb_ch->priv_usb)
				usb_qdss_close(drvdata->usb_ch);
		}
		mhi_ch_close(drvdata);

	} else
		spin_unlock_bh(&drvdata->lock);

	device_remove_file(drvdata->dev, &dev_attr_mode);
	device_destroy(mhi_class, drvdata->cdev.dev);
	cdev_del(&drvdata->cdev);
	unregister_chrdev_region(drvdata->cdev.dev, 1);
}

int qdss_mhi_init(struct qdss_bridge_drvdata *drvdata)
{
	drvdata->mhi_wq = create_singlethread_workqueue(MODULE_NAME);
	if (!drvdata->mhi_wq)
		return -ENOMEM;

	spin_lock_init(&drvdata->lock);
	INIT_WORK(&(drvdata->read_work), mhi_read_work_fn);
	INIT_WORK(&(drvdata->read_done_work), mhi_read_done_work_fn);
	INIT_WORK(&(drvdata->open_work), qdss_bridge_open_work_fn);
	INIT_LIST_HEAD(&drvdata->buf_tbl);
	INIT_LIST_HEAD(&drvdata->mhi_buf_tbl);
	init_waitqueue_head(&drvdata->uci_wq);
	init_completion(&drvdata->completion);
	INIT_LIST_HEAD(&drvdata->read_done_list);
	drvdata->opened = DISABLE;

	return 0;
}

static int qdss_mhi_probe(struct mhi_device *mhi_dev,
				const struct mhi_device_id *id)
{
	int ret;
	unsigned int baseminor = 0;
	unsigned int count = 1;
	struct qdss_bridge_drvdata *drvdata;
	dev_t dev;

	drvdata = devm_kzalloc(&mhi_dev->dev, sizeof(*drvdata), GFP_KERNEL);
	if (!drvdata) {
		ret = -ENOMEM;
		return ret;
	}

	ret = alloc_chrdev_region(&dev, baseminor, count, "mhi_qdss");
	if (ret < 0) {
		pr_err("alloc_chrdev_region failed %d\n", ret);
		return ret;
	}
	cdev_init(&drvdata->cdev, &mhidev_fops);

	drvdata->cdev.owner = THIS_MODULE;
	drvdata->cdev.ops = &mhidev_fops;

	ret = cdev_add(&drvdata->cdev, dev, 1);
	if (ret)
		goto exit_unreg_chrdev_region;

	drvdata->dev = device_create(mhi_class, NULL,
			       drvdata->cdev.dev, drvdata,
			       "mhi_qdss");
	if (IS_ERR(drvdata->dev)) {
		pr_err("class_device_create failed %d\n", ret);
		ret = -ENOMEM;
		goto exit_cdev_add;
	}

	drvdata->mode = MHI_TRANSFER_TYPE_USB;
	drvdata->mtu = min_t(size_t, id->driver_data, mhi_dev->mtu);
	drvdata->mhi_dev = mhi_dev;
	mhi_device_set_devdata(mhi_dev, drvdata);
	dev_set_drvdata(drvdata->dev, drvdata);

	ret = device_create_file(drvdata->dev, &dev_attr_mode);
	if (ret) {
		pr_err("sysfs node create failed error:%d\n", ret);
		goto exit_destroy_device;
	}

	ret = qdss_mhi_init(drvdata);
	if (ret) {
		pr_err("Device probe failed err:%d\n", ret);
		goto remove_sysfs_exit;
	}
	queue_work(drvdata->mhi_wq, &drvdata->open_work);
	return 0;

remove_sysfs_exit:
	device_remove_file(drvdata->dev, &dev_attr_mode);
exit_destroy_device:
	device_destroy(mhi_class, drvdata->cdev.dev);
exit_cdev_add:
	cdev_del(&drvdata->cdev);
exit_unreg_chrdev_region:
	unregister_chrdev_region(drvdata->cdev.dev, 1);
	return ret;

}

static const struct mhi_device_id qdss_mhi_match_table[] = {
	{ .chan = "QDSS", .driver_data = 0x4000 },
	{},
};

static struct mhi_driver qdss_mhi_driver = {
	.id_table = qdss_mhi_match_table,
	.probe = qdss_mhi_probe,
	.remove = qdss_mhi_remove,
	.dl_xfer_cb = qdss_mhi_read_cb,
	.ul_xfer_cb = qdss_mhi_write_cb,
	.driver = {
		.name = MODULE_NAME,
		.owner = THIS_MODULE,
	}
};

static int __init qdss_bridge_init(void)
{
	int ret;

	mhi_class = class_create(THIS_MODULE, MODULE_NAME);
	if (IS_ERR(mhi_class))
		return -ENODEV;

	ret = mhi_driver_register(&qdss_mhi_driver);
	if (ret)
		class_destroy(mhi_class);

	return ret;
}

static void __exit qdss_bridge_exit(void)
{
	mhi_driver_unregister(&qdss_mhi_driver);
}

module_init(qdss_bridge_init);
module_exit(qdss_bridge_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("QDSS Bridge driver");
+59 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved.
 */

#ifndef _QDSS_BRIDGE_H
#define _QDSS_BRIDGE_H

struct qdss_buf_tbl_lst {
	struct list_head link;
	unsigned char *buf;
	struct qdss_request *usb_req;
	atomic_t available;
};

struct qdss_mhi_buf_tbl_t {
	struct list_head link;
	unsigned char *buf;
	size_t len;
};

enum mhi_transfer_mode {
	MHI_TRANSFER_TYPE_USB,
	MHI_TRANSFER_TYPE_UCI,
};

enum open_status {
	DISABLE,
	ENABLE,
	SSR,
};

struct qdss_bridge_drvdata {
	int alias;
	enum open_status opened;
	struct completion completion;
	size_t mtu;
	enum mhi_transfer_mode mode;
	spinlock_t lock;
	struct device *dev;
	struct cdev cdev;
	struct mhi_device *mhi_dev;
	struct work_struct read_work;
	struct work_struct read_done_work;
	struct work_struct open_work;
	struct work_struct close_work;
	struct workqueue_struct *mhi_wq;
	struct mhi_client_handle *hdl;
	struct mhi_client_info_t *client_info;
	struct list_head buf_tbl;
	struct list_head mhi_buf_tbl;
	struct list_head read_done_list;
	struct usb_qdss_ch *usb_ch;
	struct qdss_mhi_buf_tbl_t *cur_buf;
	wait_queue_head_t uci_wq;
	size_t rx_size;
};

#endif