diff options
| author | Karthikeyan Ramasubramanian <kramasub@codeaurora.org> | 2016-02-03 12:54:31 -0700 |
|---|---|---|
| committer | David Keitel <dkeitel@codeaurora.org> | 2016-03-22 11:08:05 -0700 |
| commit | 963de668849a8885ae9d39115beefe53dec21648 (patch) | |
| tree | 18af4bb21c317bc48c43af6152c0606f7d8e5c2c | |
| parent | 95c936f3341fdf90e96ed344eb51049c274a87f7 (diff) | |
soc: qcom: Add snapshot of ipc_router_mhi_xprt
This snapshot is taken as of msm-3.18 commit e70ad0cd (Promotion of
kernel.lnx.3.18-151201.)
Signed-off-by: Karthikeyan Ramasubramanian <kramasub@codeaurora.org>
| -rw-r--r-- | drivers/soc/qcom/Kconfig | 10 | ||||
| -rw-r--r-- | drivers/soc/qcom/Makefile | 1 | ||||
| -rw-r--r-- | drivers/soc/qcom/ipc_router_mhi_xprt.c | 1024 |
3 files changed, 1035 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 4c226ed3fa86..c97fafe7c5b2 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -181,6 +181,16 @@ config MSM_IPC_ROUTER_HSIC_XPRT registers the transport with IPC Router and enable message exchange. +config MSM_IPC_ROUTER_MHI_XPRT + depends on MSM_MHI + depends on IPC_ROUTER + bool "MSM MHI XPRT Layer" + help + MHI Transport Layer that enables off-chip communication of + IPC Router. When the MHI endpoint becomes available, this layer + registers the transport with IPC Router and enable message + exchange. + config QCOM_SMD tristate "Qualcomm Shared Memory Driver (SMD)" depends on QCOM_SMEM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 9a3236c54301..deed994a51df 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_MSM_RPM_SMD) += rpm-smd-debug.o endif obj-$(CONFIG_MSM_IPC_ROUTER_SMD_XPRT) += ipc_router_smd_xprt.o obj-$(CONFIG_MSM_IPC_ROUTER_HSIC_XPRT) += ipc_router_hsic_xprt.o +obj-$(CONFIG_MSM_IPC_ROUTER_MHI_XPRT) += ipc_router_mhi_xprt.o obj-$(CONFIG_MSM_PIL_SSR_GENERIC) += subsys-pil-tz.o obj-$(CONFIG_MSM_PIL_MSS_QDSP6V5) += pil-q6v5.o pil-msa.o pil-q6v5-mss.o diff --git a/drivers/soc/qcom/ipc_router_mhi_xprt.c b/drivers/soc/qcom/ipc_router_mhi_xprt.c new file mode 100644 index 000000000000..c8adeacffcf4 --- /dev/null +++ b/drivers/soc/qcom/ipc_router_mhi_xprt.c @@ -0,0 +1,1024 @@ +/* Copyright (c) 2014-2015, 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. + */ + +/* + * IPC ROUTER MHI XPRT module. + */ +#include <linux/delay.h> +#include <linux/ipc_router_xprt.h> +#include <linux/module.h> +#include <linux/msm_mhi.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/skbuff.h> +#include <linux/types.h> + + +static int ipc_router_mhi_xprt_debug_mask; +module_param_named(debug_mask, ipc_router_mhi_xprt_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); + +#define D(x...) do { \ +if (ipc_router_mhi_xprt_debug_mask) \ + pr_info(x); \ +} while (0) + +#define NUM_MHI_XPRTS 1 +#define XPRT_NAME_LEN 32 +#define IPC_ROUTER_MHI_XPRT_MAX_PKT_SIZE 0x1000 +#define IPC_ROUTER_MHI_XPRT_NUM_TRBS 10 + +/** + * ipc_router_mhi_addr_map - DMA to virtual address mapping for an IPC Router + * packet. + * @list_node: Address mapping list node used by mhi transport map list. + * @virt_addr: The virtual address in mapping. + * @dma_addr: The dma address in mapping. + * @pkt: The IPC Router packet for which the virtual address of skbs are mapped + * to DMA address during TX/RX operations. + */ +struct ipc_router_mhi_addr_map { + struct list_head list_node; + void *virt_addr; + dma_addr_t dma_addr; + struct rr_packet *pkt; +}; + +/** + * ipc_router_mhi_channel - MHI Channel related information + * @out_chan_id: Out channel ID for use by IPC ROUTER enumerated in MHI driver. + * @out_handle: MHI Output channel handle. + * @out_clnt_info: IPC Router callbacks/info to be passed to the MHI driver. + * @in_chan_id: In channel ID for use by IPC ROUTER enumerated in MHI driver. + * @in_handle: MHI Input channel handle. + * @in_clnt_info: IPC Router callbacks/info to be passed to the MHI driver. + * @state_lock: Lock to protect access to the state information. + * @out_chan_enabled: State of the outgoing channel. + * @in_chan_enabled: State of the incoming channel. + * @bytes_to_rx: Remaining bytes to be received in a packet. + * @in_skbq_lock: Lock to protect access to the input skbs queue. + * @in_skbq: Queue containing the input buffers. + * @max_packet_size: Possible maximum packet size. + * @num_trbs: Number of TRBs. + * @mhi_xprtp: Pointer to IPC Router MHI XPRT. + */ +struct ipc_router_mhi_channel { + enum MHI_CLIENT_CHANNEL out_chan_id; + struct mhi_client_handle *out_handle; + struct mhi_client_info_t out_clnt_info; + + enum MHI_CLIENT_CHANNEL in_chan_id; + struct mhi_client_handle *in_handle; + struct mhi_client_info_t in_clnt_info; + + struct mutex state_lock; + bool out_chan_enabled; + bool in_chan_enabled; + int bytes_to_rx; + + struct mutex in_skbq_lock; + struct sk_buff_head in_skbq; + size_t max_packet_size; + uint32_t num_trbs; + void *mhi_xprtp; +}; + +/** + * ipc_router_mhi_xprt - IPC Router's MHI XPRT structure + * @list: IPC router's MHI XPRTs list. + * @ch_hndl: Data Structure to hold MHI Channel information. + * @xprt_name: Name of the XPRT to be registered with IPC Router. + * @xprt: IPC Router XPRT structure to contain MHI XPRT specific info. + * @wq: Workqueue to queue read & other XPRT related works. + * @read_work: Read Work to perform read operation from MHI Driver. + * @in_pkt: Pointer to any partially read packet. + * @write_wait_q: Wait Queue to handle the write events. + * @sft_close_complete: Variable to indicate completion of SSR handling + * by IPC Router. + * @xprt_version: IPC Router header version supported by this XPRT. + * @xprt_option: XPRT specific options to be handled by IPC Router. + * @tx_addr_map_list_lock: The lock to protect the address mapping list for TX + * operations. + * @tx_addr_map_list: DMA to virtual address mapping list for TX operations. + * @rx_addr_map_list_lock: The lock to protect the address mapping list for RX + * operations. + * @rx_addr_map_list: DMA to virtual address mapping list for RX operations. + */ +struct ipc_router_mhi_xprt { + struct list_head list; + struct ipc_router_mhi_channel ch_hndl; + char xprt_name[XPRT_NAME_LEN]; + struct msm_ipc_router_xprt xprt; + struct workqueue_struct *wq; + struct work_struct read_work; + struct rr_packet *in_pkt; + wait_queue_head_t write_wait_q; + struct completion sft_close_complete; + unsigned xprt_version; + unsigned xprt_option; + struct mutex tx_addr_map_list_lock; + struct list_head tx_addr_map_list; + struct mutex rx_addr_map_list_lock; + struct list_head rx_addr_map_list; +}; + +struct ipc_router_mhi_xprt_work { + struct ipc_router_mhi_xprt *mhi_xprtp; + enum MHI_CLIENT_CHANNEL chan_id; + struct work_struct work; +}; + +static void mhi_xprt_read_data(struct work_struct *work); +static void mhi_xprt_enable_event(struct work_struct *work); +static void mhi_xprt_disable_event(struct work_struct *work); + +/** + * ipc_router_mhi_xprt_config - Config. Info. of each MHI XPRT + * @out_chan_id: Out channel ID for use by IPC ROUTER enumerated in MHI driver. + * @in_chan_id: In channel ID for use by IPC ROUTER enumerated in MHI driver. + * @xprt_name: Name of the XPRT to be registered with IPC Router. + * @link_id: Network Cluster ID to which this XPRT belongs to. + * @xprt_version: IPC Router header version supported by this XPRT. + */ +struct ipc_router_mhi_xprt_config { + enum MHI_CLIENT_CHANNEL out_chan_id; + enum MHI_CLIENT_CHANNEL in_chan_id; + char xprt_name[XPRT_NAME_LEN]; + uint32_t link_id; + uint32_t xprt_version; +}; + +#define MODULE_NAME "ipc_router_mhi_xprt" +static DEFINE_MUTEX(mhi_xprt_list_lock_lha1); +static LIST_HEAD(mhi_xprt_list); + +/* + * ipc_router_mhi_release_pkt() - Release a cloned IPC Router packet + * @ref: Reference to the kref object in the IPC Router packet. + */ +void ipc_router_mhi_release_pkt(struct kref *ref) +{ + struct rr_packet *pkt = container_of(ref, struct rr_packet, ref); + + release_pkt(pkt); +} + +/* + * ipc_router_mhi_xprt_find_addr_map() - Search the mapped virtual address + * @addr_map_list: The list of address mappings. + * @addr_map_list_lock: Reference to the lock that protects the @addr_map_list. + * @addr: The dma address for which mapped virtual address need to be found. + * + * Return: The mapped virtual Address if found, NULL otherwise. + */ +void *ipc_router_mhi_xprt_find_addr_map(struct list_head *addr_map_list, + struct mutex *addr_map_list_lock, + dma_addr_t addr) +{ + struct ipc_router_mhi_addr_map *addr_mapping; + struct ipc_router_mhi_addr_map *tmp_addr_mapping; + void *virt_addr; + + if (!addr_map_list || !addr_map_list_lock) + return NULL; + mutex_lock(addr_map_list_lock); + list_for_each_entry_safe(addr_mapping, tmp_addr_mapping, + addr_map_list, list_node) { + if (addr_mapping->dma_addr == addr) { + virt_addr = addr_mapping->virt_addr; + list_del(&addr_mapping->list_node); + if (addr_mapping->pkt) + kref_put(&addr_mapping->pkt->ref, + ipc_router_mhi_release_pkt); + kfree(addr_mapping); + mutex_unlock(addr_map_list_lock); + return virt_addr; + } + } + mutex_unlock(addr_map_list_lock); + IPC_RTR_ERR( + "%s: Virtual address mapping for DMA addr [%p] not found\n", + __func__, (void *)addr); + return NULL; +} + +/* + * ipc_router_mhi_xprt_add_addr_map() - Add a mapping of virtual address to dma + * address + * @addr_map_list: The list of address mappings. + * @addr_map_list_lock: Reference to the lock that protects the @addr_map_list. + * @pkt: The IPC Router packet that contains the virtual address in skbs. + * @addr: The virtual address which needs to be added. + * @dma_addr: The dma address which needs to be added. + * + * Return: 0 on success, standard Linux error code otherwise. + */ +int ipc_router_mhi_xprt_add_addr_map(struct list_head *addr_map_list, + struct mutex *addr_map_list_lock, + struct rr_packet *pkt, void *virt_addr, + dma_addr_t dma_addr) +{ + struct ipc_router_mhi_addr_map *addr_mapping; + + if (!addr_map_list || !addr_map_list_lock) + return -EINVAL; + addr_mapping = kmalloc(sizeof(*addr_mapping), GFP_KERNEL); + if (!addr_mapping) + return -ENOMEM; + addr_mapping->virt_addr = virt_addr; + addr_mapping->dma_addr = dma_addr; + addr_mapping->pkt = pkt; + mutex_lock(addr_map_list_lock); + if (addr_mapping->pkt) + kref_get(&addr_mapping->pkt->ref); + list_add_tail(&addr_mapping->list_node, addr_map_list); + mutex_unlock(addr_map_list_lock); + return 0; +} + +/* + * mhi_xprt_queue_in_buffers() - Queue input buffers + * @mhi_xprtp: MHI XPRT in which the input buffer has to be queued. + * @num_trbs: Number of buffers to be queued. + * + * @return: number of buffers queued. + */ +int mhi_xprt_queue_in_buffers(struct ipc_router_mhi_xprt *mhi_xprtp, + uint32_t num_trbs) +{ + int i; + struct sk_buff *skb; + dma_addr_t dma_addr; + uint32_t buf_size = mhi_xprtp->ch_hndl.max_packet_size; + enum MHI_STATUS rc_val = MHI_STATUS_SUCCESS; + + for (i = 0; i < num_trbs; i++) { + skb = alloc_skb(buf_size, GFP_KERNEL); + if (!skb) { + IPC_RTR_ERR("%s: Could not allocate %d SKB(s)\n", + __func__, (i + 1)); + break; + } + dma_addr = dma_map_single(NULL, skb->data, + buf_size, DMA_BIDIRECTIONAL); + if (dma_mapping_error(NULL, dma_addr)) { + IPC_RTR_ERR("%s: Failed to map DMA for SKB # %d\n", + __func__, (i + 1)); + kfree_skb(skb); + break; + } + if (ipc_router_mhi_xprt_add_addr_map( + &mhi_xprtp->rx_addr_map_list, + &mhi_xprtp->rx_addr_map_list_lock, NULL, + skb->data, dma_addr) < 0) { + IPC_RTR_ERR("%s: Could not map %d SKB->DMA address\n", + __func__, (i + 1)); + break; + } + mutex_lock(&mhi_xprtp->ch_hndl.in_skbq_lock); + rc_val = mhi_queue_xfer(mhi_xprtp->ch_hndl.in_handle, + dma_addr, buf_size, MHI_EOT); + if (rc_val != MHI_STATUS_SUCCESS) { + mutex_unlock(&mhi_xprtp->ch_hndl.in_skbq_lock); + IPC_RTR_ERR("%s: Failed to queue TRB # %d into MHI\n", + __func__, (i + 1)); + dma_unmap_single(NULL, dma_addr, + buf_size, DMA_TO_DEVICE); + kfree_skb(skb); + break; + } + skb_queue_tail(&mhi_xprtp->ch_hndl.in_skbq, skb); + mutex_unlock(&mhi_xprtp->ch_hndl.in_skbq_lock); + } + return i; +} + +/** + * ipc_router_mhi_get_xprt_version() - Get IPC Router header version + * supported by the XPRT + * @xprt: XPRT for which the version information is required. + * + * @return: IPC Router header version supported by the XPRT. + */ +static int ipc_router_mhi_get_xprt_version(struct msm_ipc_router_xprt *xprt) +{ + struct ipc_router_mhi_xprt *mhi_xprtp; + if (!xprt) + return -EINVAL; + mhi_xprtp = container_of(xprt, struct ipc_router_mhi_xprt, xprt); + + return (int)mhi_xprtp->xprt_version; +} + +/** + * ipc_router_mhi_get_xprt_option() - Get XPRT options + * @xprt: XPRT for which the option information is required. + * + * @return: Options supported by the XPRT. + */ +static int ipc_router_mhi_get_xprt_option(struct msm_ipc_router_xprt *xprt) +{ + struct ipc_router_mhi_xprt *mhi_xprtp; + if (!xprt) + return -EINVAL; + mhi_xprtp = container_of(xprt, struct ipc_router_mhi_xprt, xprt); + + return (int)mhi_xprtp->xprt_option; +} + +/** + * ipc_router_mhi_write_avail() - Get available write space + * @xprt: XPRT for which the available write space info. is required. + * + * @return: Write space in bytes on success, 0 on SSR. + */ +static int ipc_router_mhi_write_avail(struct msm_ipc_router_xprt *xprt) +{ + int write_avail; + struct ipc_router_mhi_xprt *mhi_xprtp = + container_of(xprt, struct ipc_router_mhi_xprt, xprt); + + mutex_lock(&mhi_xprtp->ch_hndl.state_lock); + if (!mhi_xprtp->ch_hndl.out_chan_enabled) + write_avail = 0; + else + write_avail = mhi_get_free_desc(mhi_xprtp->ch_hndl.out_handle) * + mhi_xprtp->ch_hndl.max_packet_size; + mutex_unlock(&mhi_xprtp->ch_hndl.state_lock); + return write_avail; +} + +/** + * ipc_router_mhi_write_skb() - Write a single SKB onto the XPRT + * @mhi_xprtp: XPRT in which the SKB has to be written. + * @skb: SKB to be written. + * + * @return: return number of bytes written on success, + * standard Linux error codes on failure. + */ +static int ipc_router_mhi_write_skb(struct ipc_router_mhi_xprt *mhi_xprtp, + struct sk_buff *skb, struct rr_packet *pkt) +{ + size_t sz_to_write = 0; + size_t offset = 0; + int rc; + dma_addr_t dma_addr; + + while (offset < skb->len) { + wait_event(mhi_xprtp->write_wait_q, + mhi_get_free_desc(mhi_xprtp->ch_hndl.out_handle) || + !mhi_xprtp->ch_hndl.out_chan_enabled); + mutex_lock(&mhi_xprtp->ch_hndl.state_lock); + if (!mhi_xprtp->ch_hndl.out_chan_enabled) { + mutex_unlock(&mhi_xprtp->ch_hndl.state_lock); + IPC_RTR_ERR("%s: %s chnl reset\n", + __func__, mhi_xprtp->xprt_name); + return -ENETRESET; + } + + sz_to_write = min((size_t)(skb->len - offset), + (size_t)IPC_ROUTER_MHI_XPRT_MAX_PKT_SIZE); + dma_addr = dma_map_single(NULL, skb->data + offset, + sz_to_write, DMA_TO_DEVICE); + if (dma_mapping_error(NULL, dma_addr)) { + mutex_unlock(&mhi_xprtp->ch_hndl.state_lock); + IPC_RTR_ERR("%s: Failed to map DMA 0x%zx\n", + __func__, sz_to_write); + return -ENOMEM; + } + if (ipc_router_mhi_xprt_add_addr_map( + &mhi_xprtp->tx_addr_map_list, + &mhi_xprtp->tx_addr_map_list_lock, pkt, + skb->data + offset, dma_addr) < 0) { + IPC_RTR_ERR("%s: Could not map SKB->DMA address\n", + __func__); + break; + } + + rc = mhi_queue_xfer(mhi_xprtp->ch_hndl.out_handle, + dma_addr, sz_to_write, MHI_EOT | MHI_EOB); + if (rc != 0) { + mutex_unlock(&mhi_xprtp->ch_hndl.state_lock); + dma_unmap_single(NULL, dma_addr, sz_to_write, + DMA_TO_DEVICE); + IPC_RTR_ERR("%s: Error queueing mhi_xfer 0x%zx\n", + __func__, sz_to_write); + return -EFAULT; + } else { + offset += sz_to_write; + } + mutex_unlock(&mhi_xprtp->ch_hndl.state_lock); + } + return skb->len; +} + +/** + * ipc_router_mhi_write() - Write to XPRT + * @data: Data to be written to the XPRT. + * @len: Length of the data to be written. + * @xprt: XPRT to which the data has to be written. + * + * @return: Data Length on success, standard Linux error codes on failure. + */ +static int ipc_router_mhi_write(void *data, + uint32_t len, struct msm_ipc_router_xprt *xprt) +{ + struct rr_packet *pkt = (struct rr_packet *)data; + struct sk_buff *ipc_rtr_pkt; + struct rr_packet *cloned_pkt; + int rc; + struct ipc_router_mhi_xprt *mhi_xprtp = + container_of(xprt, struct ipc_router_mhi_xprt, xprt); + + if (!pkt) + return -EINVAL; + + if (!len || pkt->length != len) + return -EINVAL; + + cloned_pkt = clone_pkt(pkt); + if (!cloned_pkt) { + pr_err("%s: Error in cloning packet while tx\n", __func__); + return -ENOMEM; + } + D("%s: Ready to write %d bytes\n", __func__, len); + skb_queue_walk(cloned_pkt->pkt_fragment_q, ipc_rtr_pkt) { + rc = ipc_router_mhi_write_skb(mhi_xprtp, ipc_rtr_pkt, + cloned_pkt); + if (rc < 0) { + IPC_RTR_ERR("%s: Error writing SKB %d\n", + __func__, rc); + break; + } + } + + kref_put(&cloned_pkt->ref, ipc_router_mhi_release_pkt); + if (rc < 0) + return rc; + else + return len; +} + +/** + * mhi_xprt_read_data() - Read work to read from the XPRT + * @work: Read work to be executed. + * + * This function is a read work item queued on a XPRT specific workqueue. + * The work parameter contains information regarding the XPRT on which this + * read work has to be performed. The work item keeps reading from the MHI + * endpoint, until the endpoint returns an error. + */ +static void mhi_xprt_read_data(struct work_struct *work) +{ + dma_addr_t data_addr; + ssize_t data_sz; + void *skb_data; + struct sk_buff *skb; + struct ipc_router_mhi_xprt *mhi_xprtp = + container_of(work, struct ipc_router_mhi_xprt, read_work); + struct mhi_result result; + int rc; + + mutex_lock(&mhi_xprtp->ch_hndl.state_lock); + if (!mhi_xprtp->ch_hndl.in_chan_enabled) { + mutex_unlock(&mhi_xprtp->ch_hndl.state_lock); + if (mhi_xprtp->in_pkt) + release_pkt(mhi_xprtp->in_pkt); + mhi_xprtp->in_pkt = NULL; + mhi_xprtp->ch_hndl.bytes_to_rx = 0; + IPC_RTR_ERR("%s: %s channel reset\n", + __func__, mhi_xprtp->xprt.name); + return; + } + mutex_unlock(&mhi_xprtp->ch_hndl.state_lock); + + while (1) { + rc = mhi_poll_inbound(mhi_xprtp->ch_hndl.in_handle, &result); + if (rc || !result.payload_buf || !result.bytes_xferd) { + if (rc != MHI_STATUS_RING_EMPTY) + IPC_RTR_ERR("%s: Poll failed %s:%d:%p:%u\n", + __func__, mhi_xprtp->xprt_name, rc, + (void *)result.payload_buf, + result.bytes_xferd); + break; + } + data_addr = result.payload_buf; + data_sz = result.bytes_xferd; + + /* Create a new rr_packet, if first fragment */ + if (!mhi_xprtp->ch_hndl.bytes_to_rx) { + mhi_xprtp->in_pkt = create_pkt(NULL); + if (!mhi_xprtp->in_pkt) { + IPC_RTR_ERR("%s: Couldn't alloc rr_packet\n", + __func__); + return; + } + D("%s: Allocated rr_packet\n", __func__); + } + + skb_data = ipc_router_mhi_xprt_find_addr_map( + &mhi_xprtp->rx_addr_map_list, + &mhi_xprtp->rx_addr_map_list_lock, + data_addr); + + dma_unmap_single(NULL, data_addr, data_sz, DMA_BIDIRECTIONAL); + if (!skb_data) + continue; + mutex_lock(&mhi_xprtp->ch_hndl.in_skbq_lock); + skb_queue_walk(&mhi_xprtp->ch_hndl.in_skbq, skb) { + if (skb->data == skb_data) { + skb_unlink(skb, &mhi_xprtp->ch_hndl.in_skbq); + break; + } + } + mutex_unlock(&mhi_xprtp->ch_hndl.in_skbq_lock); + skb_put(skb, data_sz); + skb_queue_tail(mhi_xprtp->in_pkt->pkt_fragment_q, skb); + mhi_xprtp->in_pkt->length += data_sz; + if (!mhi_xprtp->ch_hndl.bytes_to_rx) + mhi_xprtp->ch_hndl.bytes_to_rx = + ipc_router_peek_pkt_size(skb_data) - data_sz; + else + mhi_xprtp->ch_hndl.bytes_to_rx -= data_sz; + /* Packet is completely read, so notify to router */ + if (!mhi_xprtp->ch_hndl.bytes_to_rx) { + D("%s: Packet size read %d\n", + __func__, mhi_xprtp->in_pkt->length); + msm_ipc_router_xprt_notify(&mhi_xprtp->xprt, + IPC_ROUTER_XPRT_EVENT_DATA, + (void *)mhi_xprtp->in_pkt); + release_pkt(mhi_xprtp->in_pkt); + mhi_xprtp->in_pkt = NULL; + } + + while (mhi_xprt_queue_in_buffers(mhi_xprtp, 1) != 1 && + mhi_xprtp->ch_hndl.in_chan_enabled) + msleep(100); + } +} + +/** + * ipc_router_mhi_close() - Close the XPRT + * @xprt: XPRT which needs to be closed. + * + * @return: 0 on success, standard Linux error codes on failure. + */ +static int ipc_router_mhi_close(struct msm_ipc_router_xprt *xprt) +{ + struct ipc_router_mhi_xprt *mhi_xprtp; + + if (!xprt) + return -EINVAL; + mhi_xprtp = container_of(xprt, struct ipc_router_mhi_xprt, xprt); + + mutex_lock(&mhi_xprtp->ch_hndl.state_lock); + mhi_xprtp->ch_hndl.out_chan_enabled = false; + mhi_xprtp->ch_hndl.in_chan_enabled = false; + mutex_unlock(&mhi_xprtp->ch_hndl.state_lock); + flush_workqueue(mhi_xprtp->wq); + mhi_close_channel(mhi_xprtp->ch_hndl.in_handle); + mhi_close_channel(mhi_xprtp->ch_hndl.out_handle); + return 0; +} + +/** + * mhi_xprt_sft_close_done() - Completion of XPRT reset + * @xprt: XPRT on which the reset operation is complete. + * + * This function is used by IPC Router to signal this MHI XPRT Abstraction + * Layer(XAL) that the reset of XPRT is completely handled by IPC Router. + */ +static void mhi_xprt_sft_close_done(struct msm_ipc_router_xprt *xprt) +{ + struct ipc_router_mhi_xprt *mhi_xprtp = + container_of(xprt, struct ipc_router_mhi_xprt, xprt); + + complete_all(&mhi_xprtp->sft_close_complete); +} + +/** + * mhi_xprt_enable_event() - Enable the MHI link for communication + * @work: Work containing some reference to the link to be enabled. + * + * This work is scheduled when the MHI link to the peripheral is up. + */ +static void mhi_xprt_enable_event(struct work_struct *work) +{ + struct ipc_router_mhi_xprt_work *xprt_work = + container_of(work, struct ipc_router_mhi_xprt_work, work); + struct ipc_router_mhi_xprt *mhi_xprtp = xprt_work->mhi_xprtp; + int rc; + bool notify = false; + + if (xprt_work->chan_id == mhi_xprtp->ch_hndl.out_chan_id) { + rc = mhi_open_channel(mhi_xprtp->ch_hndl.out_handle); + if (rc != MHI_STATUS_SUCCESS) { + IPC_RTR_ERR("%s Failed to open chan 0x%x, rc %d\n", + __func__, mhi_xprtp->ch_hndl.out_chan_id, rc); + goto out_enable_event; + } + mutex_lock(&mhi_xprtp->ch_hndl.state_lock); + mhi_xprtp->ch_hndl.out_chan_enabled = true; + notify = mhi_xprtp->ch_hndl.out_chan_enabled && + mhi_xprtp->ch_hndl.in_chan_enabled; + mutex_unlock(&mhi_xprtp->ch_hndl.state_lock); + } else if (xprt_work->chan_id == mhi_xprtp->ch_hndl.in_chan_id) { + rc = mhi_open_channel(mhi_xprtp->ch_hndl.in_handle); + if (rc != MHI_STATUS_SUCCESS) { + IPC_RTR_ERR("%s Failed to open chan 0x%x, rc %d\n", + __func__, mhi_xprtp->ch_hndl.in_chan_id, rc); + goto out_enable_event; + } + mutex_lock(&mhi_xprtp->ch_hndl.state_lock); + mhi_xprtp->ch_hndl.in_chan_enabled = true; + notify = mhi_xprtp->ch_hndl.out_chan_enabled && + mhi_xprtp->ch_hndl.in_chan_enabled; + mutex_unlock(&mhi_xprtp->ch_hndl.state_lock); + } + + /* Register the XPRT before receiving any data */ + if (notify) { + msm_ipc_router_xprt_notify(&mhi_xprtp->xprt, + IPC_ROUTER_XPRT_EVENT_OPEN, NULL); + D("%s: Notified IPC Router of %s OPEN\n", + __func__, mhi_xprtp->xprt.name); + } + + if (xprt_work->chan_id != mhi_xprtp->ch_hndl.in_chan_id) + goto out_enable_event; + + rc = mhi_xprt_queue_in_buffers(mhi_xprtp, mhi_xprtp->ch_hndl.num_trbs); + if (rc > 0) + goto out_enable_event; + + IPC_RTR_ERR("%s: Could not queue one TRB atleast\n", __func__); + mutex_lock(&mhi_xprtp->ch_hndl.state_lock); + mhi_xprtp->ch_hndl.in_chan_enabled = false; + mutex_unlock(&mhi_xprtp->ch_hndl.state_lock); + if (notify) + msm_ipc_router_xprt_notify(&mhi_xprtp->xprt, + IPC_ROUTER_XPRT_EVENT_CLOSE, NULL); + mhi_close_channel(mhi_xprtp->ch_hndl.in_handle); +out_enable_event: + kfree(xprt_work); +} + +/** + * mhi_xprt_disable_event() - Disable the MHI link for communication + * @work: Work containing some reference to the link to be disabled. + * + * This work is scheduled when the MHI link to the peripheral is down. + */ +static void mhi_xprt_disable_event(struct work_struct *work) +{ + struct ipc_router_mhi_xprt_work *xprt_work = + container_of(work, struct ipc_router_mhi_xprt_work, work); + struct ipc_router_mhi_xprt *mhi_xprtp = xprt_work->mhi_xprtp; + bool notify = false; + + if (xprt_work->chan_id == mhi_xprtp->ch_hndl.out_chan_id) { + mutex_lock(&mhi_xprtp->ch_hndl.state_lock); + notify = mhi_xprtp->ch_hndl.out_chan_enabled && + mhi_xprtp->ch_hndl.in_chan_enabled; + mhi_xprtp->ch_hndl.out_chan_enabled = false; + mutex_unlock(&mhi_xprtp->ch_hndl.state_lock); + wake_up(&mhi_xprtp->write_wait_q); + mhi_close_channel(mhi_xprtp->ch_hndl.out_handle); + } else if (xprt_work->chan_id == mhi_xprtp->ch_hndl.in_chan_id) { + mutex_lock(&mhi_xprtp->ch_hndl.state_lock); + notify = mhi_xprtp->ch_hndl.out_chan_enabled && + mhi_xprtp->ch_hndl.in_chan_enabled; + mhi_xprtp->ch_hndl.in_chan_enabled = false; + mutex_unlock(&mhi_xprtp->ch_hndl.state_lock); + /* Queue a read work to remove any partially read packets */ + queue_work(mhi_xprtp->wq, &mhi_xprtp->read_work); + flush_workqueue(mhi_xprtp->wq); + mhi_close_channel(mhi_xprtp->ch_hndl.in_handle); + } + + if (notify) { + init_completion(&mhi_xprtp->sft_close_complete); + msm_ipc_router_xprt_notify(&mhi_xprtp->xprt, + IPC_ROUTER_XPRT_EVENT_CLOSE, NULL); + D("%s: Notified IPC Router of %s CLOSE\n", + __func__, mhi_xprtp->xprt.name); + wait_for_completion(&mhi_xprtp->sft_close_complete); + } + kfree(xprt_work); +} + +/** + * mhi_xprt_xfer_event() - Function to handle MHI XFER Callbacks + * @cb_info: Information containing xfer callback details. + * + * This function is called when the MHI generates a XFER event to the + * IPC Router. This function is used to handle events like tx/rx. + */ +static void mhi_xprt_xfer_event(struct mhi_cb_info *cb_info) +{ + struct ipc_router_mhi_xprt *mhi_xprtp; + dma_addr_t out_dma_addr; + + mhi_xprtp = (struct ipc_router_mhi_xprt *)(cb_info->result->user_data); + if (cb_info->chan == mhi_xprtp->ch_hndl.out_chan_id) { + out_dma_addr = (dma_addr_t)cb_info->result->payload_buf; + dma_unmap_single(NULL, out_dma_addr, + cb_info->result->bytes_xferd, DMA_TO_DEVICE); + mutex_lock(&mhi_xprtp->ch_hndl.state_lock); + ipc_router_mhi_xprt_find_addr_map(&mhi_xprtp->tx_addr_map_list, + &mhi_xprtp->tx_addr_map_list_lock, + out_dma_addr); + wake_up(&mhi_xprtp->write_wait_q); + mutex_unlock(&mhi_xprtp->ch_hndl.state_lock); + } else if (cb_info->chan == mhi_xprtp->ch_hndl.in_chan_id) { + queue_work(mhi_xprtp->wq, &mhi_xprtp->read_work); + } else { + IPC_RTR_ERR("%s: chan_id %d not part of %s\n", + __func__, cb_info->chan, mhi_xprtp->xprt_name); + } +} + +/** + * ipc_router_mhi_xprt_cb() - Callback to notify events on a channel + * @cb_info: Information containing the details of callback. + * + * This function is called by the MHI driver to notify different events + * like successful tx/rx, SSR events etc. + */ +static void ipc_router_mhi_xprt_cb(struct mhi_cb_info *cb_info) +{ + struct ipc_router_mhi_xprt *mhi_xprtp; + struct ipc_router_mhi_xprt_work *xprt_work; + + if (cb_info->result == NULL) { + IPC_RTR_ERR("%s: Result not available in cb_info\n", __func__); + return; + } + + mhi_xprtp = (struct ipc_router_mhi_xprt *)(cb_info->result->user_data); + switch (cb_info->cb_reason) { + case MHI_CB_MHI_ENABLED: + case MHI_CB_MHI_DISABLED: + xprt_work = kmalloc(sizeof(*xprt_work), GFP_KERNEL); + if (!xprt_work) { + IPC_RTR_ERR("%s: Couldn't handle %d event on %s\n", + __func__, cb_info->cb_reason, + mhi_xprtp->xprt_name); + return; + } + xprt_work->mhi_xprtp = mhi_xprtp; + xprt_work->chan_id = cb_info->chan; + if (cb_info->cb_reason == MHI_CB_MHI_ENABLED) + INIT_WORK(&xprt_work->work, mhi_xprt_enable_event); + else + INIT_WORK(&xprt_work->work, mhi_xprt_disable_event); + queue_work(mhi_xprtp->wq, &xprt_work->work); + break; + case MHI_CB_XFER: + mhi_xprt_xfer_event(cb_info); + break; + default: + IPC_RTR_ERR("%s: Invalid cb reason %x\n", + __func__, cb_info->cb_reason); + } +} + +/** + * ipc_router_mhi_driver_register() - register for MHI channels + * + * @mhi_xprtp: pointer to IPC router mhi xprt structure. + * + * @return: 0 on success, standard Linux error codes on error. + * + * This function is called when a new XPRT is added. + */ +static int ipc_router_mhi_driver_register( + struct ipc_router_mhi_xprt *mhi_xprtp) +{ + enum MHI_STATUS rc_status; + + rc_status = mhi_register_channel(&mhi_xprtp->ch_hndl.out_handle, + mhi_xprtp->ch_hndl.out_chan_id, 0, + &mhi_xprtp->ch_hndl.out_clnt_info, + (void *)mhi_xprtp); + if (rc_status != MHI_STATUS_SUCCESS) { + IPC_RTR_ERR("%s: Error %d registering out_chan for %s\n", + __func__, rc_status, mhi_xprtp->xprt_name); + return -EFAULT; + } + + rc_status = mhi_register_channel(&mhi_xprtp->ch_hndl.in_handle, + mhi_xprtp->ch_hndl.in_chan_id, 0, + &mhi_xprtp->ch_hndl.in_clnt_info, + (void *)mhi_xprtp); + if (rc_status != MHI_STATUS_SUCCESS) { + mhi_deregister_channel(mhi_xprtp->ch_hndl.out_handle); + IPC_RTR_ERR("%s: Error %d registering in_chan for %s\n", + __func__, rc_status, mhi_xprtp->xprt_name); + return -EFAULT; + } + return 0; +} + +/** + * ipc_router_mhi_config_init() - init MHI xprt configs + * + * @mhi_xprt_config: pointer to MHI xprt configurations. + * + * @return: 0 on success, standard Linux error codes on error. + * + * This function is called to initialize the MHI XPRT pointer with + * the MHI XPRT configurations from device tree. + */ +static int ipc_router_mhi_config_init( + struct ipc_router_mhi_xprt_config *mhi_xprt_config) +{ + struct ipc_router_mhi_xprt *mhi_xprtp; + char wq_name[XPRT_NAME_LEN]; + int rc; + + mhi_xprtp = kzalloc(sizeof(struct ipc_router_mhi_xprt), GFP_KERNEL); + if (IS_ERR_OR_NULL(mhi_xprtp)) { + IPC_RTR_ERR("%s: kzalloc() failed for mhi_xprtp:%s\n", + __func__, mhi_xprt_config->xprt_name); + return -ENOMEM; + } + + scnprintf(wq_name, XPRT_NAME_LEN, "MHI_XPRT%x:%x", + mhi_xprt_config->out_chan_id, mhi_xprt_config->in_chan_id); + mhi_xprtp->wq = create_singlethread_workqueue(wq_name); + if (!mhi_xprtp->wq) { + IPC_RTR_ERR("%s: %s create WQ failed\n", + __func__, mhi_xprt_config->xprt_name); + kfree(mhi_xprtp); + return -EFAULT; + } + + INIT_WORK(&mhi_xprtp->read_work, mhi_xprt_read_data); + init_waitqueue_head(&mhi_xprtp->write_wait_q); + mhi_xprtp->xprt_version = mhi_xprt_config->xprt_version; + strlcpy(mhi_xprtp->xprt_name, mhi_xprt_config->xprt_name, + XPRT_NAME_LEN); + + /* Initialize XPRT operations and parameters registered with IPC RTR */ + mhi_xprtp->xprt.link_id = mhi_xprt_config->link_id; + mhi_xprtp->xprt.name = mhi_xprtp->xprt_name; + mhi_xprtp->xprt.get_version = ipc_router_mhi_get_xprt_version; + mhi_xprtp->xprt.get_option = ipc_router_mhi_get_xprt_option; + mhi_xprtp->xprt.read_avail = NULL; + mhi_xprtp->xprt.read = NULL; + mhi_xprtp->xprt.write_avail = ipc_router_mhi_write_avail; + mhi_xprtp->xprt.write = ipc_router_mhi_write; + mhi_xprtp->xprt.close = ipc_router_mhi_close; + mhi_xprtp->xprt.sft_close_done = mhi_xprt_sft_close_done; + mhi_xprtp->xprt.priv = NULL; + + /* Initialize channel handle parameters */ + mhi_xprtp->ch_hndl.out_chan_id = mhi_xprt_config->out_chan_id; + mhi_xprtp->ch_hndl.in_chan_id = mhi_xprt_config->in_chan_id; + mhi_xprtp->ch_hndl.out_clnt_info.mhi_client_cb = ipc_router_mhi_xprt_cb; + mhi_xprtp->ch_hndl.in_clnt_info.mhi_client_cb = ipc_router_mhi_xprt_cb; + mutex_init(&mhi_xprtp->ch_hndl.state_lock); + mutex_init(&mhi_xprtp->ch_hndl.in_skbq_lock); + skb_queue_head_init(&mhi_xprtp->ch_hndl.in_skbq); + mhi_xprtp->ch_hndl.max_packet_size = IPC_ROUTER_MHI_XPRT_MAX_PKT_SIZE; + mhi_xprtp->ch_hndl.num_trbs = IPC_ROUTER_MHI_XPRT_NUM_TRBS; + mhi_xprtp->ch_hndl.mhi_xprtp = mhi_xprtp; + INIT_LIST_HEAD(&mhi_xprtp->tx_addr_map_list); + mutex_init(&mhi_xprtp->tx_addr_map_list_lock); + INIT_LIST_HEAD(&mhi_xprtp->rx_addr_map_list); + mutex_init(&mhi_xprtp->rx_addr_map_list_lock); + + rc = ipc_router_mhi_driver_register(mhi_xprtp); + return rc; +} + +/** + * parse_devicetree() - parse device tree binding + * + * @node: pointer to device tree node + * @mhi_xprt_config: pointer to MHI XPRT configurations + * + * @return: 0 on success, -ENODEV on failure. + */ +static int parse_devicetree(struct device_node *node, + struct ipc_router_mhi_xprt_config *mhi_xprt_config) +{ + int rc; + uint32_t out_chan_id; + uint32_t in_chan_id; + const char *remote_ss; + uint32_t link_id; + uint32_t version; + char *key; + + key = "qcom,out-chan-id"; + rc = of_property_read_u32(node, key, &out_chan_id); + if (rc) + goto error; + mhi_xprt_config->out_chan_id = out_chan_id; + + key = "qcom,in-chan-id"; + rc = of_property_read_u32(node, key, &in_chan_id); + if (rc) + goto error; + mhi_xprt_config->in_chan_id = in_chan_id; + + key = "qcom,xprt-remote"; + remote_ss = of_get_property(node, key, NULL); + if (!remote_ss) + goto error; + + key = "qcom,xprt-linkid"; + rc = of_property_read_u32(node, key, &link_id); + if (rc) + goto error; + mhi_xprt_config->link_id = link_id; + + key = "qcom,xprt-version"; + rc = of_property_read_u32(node, key, &version); + if (rc) + goto error; + mhi_xprt_config->xprt_version = version; + + scnprintf(mhi_xprt_config->xprt_name, XPRT_NAME_LEN, + "IPCRTR_MHI%x:%x_%s", + out_chan_id, in_chan_id, remote_ss); + + return 0; +error: + IPC_RTR_ERR("%s: missing key: %s\n", __func__, key); + return -ENODEV; +} + +/** + * ipc_router_mhi_xprt_probe() - Probe an MHI xprt + * @pdev: Platform device corresponding to MHI xprt. + * + * @return: 0 on success, standard Linux error codes on error. + * + * This function is called when the underlying device tree driver registers + * a platform device, mapped to an MHI transport. + */ +static int ipc_router_mhi_xprt_probe(struct platform_device *pdev) +{ + int rc; + struct ipc_router_mhi_xprt_config mhi_xprt_config; + + if (pdev && pdev->dev.of_node) { + rc = parse_devicetree(pdev->dev.of_node, &mhi_xprt_config); + if (rc) { + IPC_RTR_ERR("%s: failed to parse device tree\n", + __func__); + return rc; + } + + rc = ipc_router_mhi_config_init(&mhi_xprt_config); + if (rc) { + IPC_RTR_ERR("%s: init failed\n", __func__); + return rc; + } + } + return rc; +} + +static struct of_device_id ipc_router_mhi_xprt_match_table[] = { + { .compatible = "qcom,ipc_router_mhi_xprt" }, + {}, +}; + +static struct platform_driver ipc_router_mhi_xprt_driver = { + .probe = ipc_router_mhi_xprt_probe, + .driver = { + .name = MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = ipc_router_mhi_xprt_match_table, + }, +}; + +static int __init ipc_router_mhi_xprt_init(void) +{ + int rc; + + rc = platform_driver_register(&ipc_router_mhi_xprt_driver); + if (rc) { + IPC_RTR_ERR("%s: ipc_router_mhi_xprt_driver reg. failed %d\n", + __func__, rc); + return rc; + } + return 0; +} + +module_init(ipc_router_mhi_xprt_init); +MODULE_DESCRIPTION("IPC Router MHI XPRT"); +MODULE_LICENSE("GPL v2"); |
