/* Copyright (c) 2013, 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 HSIC XPRT module. */ #define DEBUG #include #include #include #include #include #include "ipc_router.h" static int msm_ipc_router_hsic_xprt_debug_mask; module_param_named(debug_mask, msm_ipc_router_hsic_xprt_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); #if defined(DEBUG) #define D(x...) do { \ if (msm_ipc_router_hsic_xprt_debug_mask) \ pr_info(x); \ } while (0) #else #define D(x...) do { } while (0) #endif #define NUM_HSIC_XPRTS 1 #define XPRT_NAME_LEN 32 /** * msm_ipc_router_hsic_xprt - IPC Router's HSIC XPRT strucutre * @xprt: IPC Router XPRT structure to contain HSIC XPRT specific info. * @pdev: Platform device registered by IPC Bridge function driver. * @hsic_xprt_wq: Workqueue to queue read & other XPRT related works. * @read_work: Read Work to perform read operation from HSIC's ipc_bridge. * @in_pkt: Pointer to any partially read packet. * @ss_reset_lock: Lock to protect access to the ss_reset flag. * @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. */ struct msm_ipc_router_hsic_xprt { struct msm_ipc_router_xprt xprt; struct platform_device *pdev; struct workqueue_struct *hsic_xprt_wq; struct delayed_work read_work; struct rr_packet *in_pkt; struct mutex ss_reset_lock; int ss_reset; struct completion sft_close_complete; unsigned xprt_version; unsigned xprt_option; }; struct msm_ipc_router_hsic_xprt_work { struct msm_ipc_router_xprt *xprt; struct work_struct work; }; static void hsic_xprt_read_data(struct work_struct *work); /** * msm_ipc_router_hsic_xprt_config - Config. Info. of each HSIC XPRT * @ch_name: Name of the HSIC endpoint exported by ipc_bridge driver. * @xprt_name: Name of the XPRT to be registered with IPC Router. * @hsic_pdev_id: ID to differentiate among multiple ipc_bridge endpoints. * @link_id: Network Cluster ID to which this XPRT belongs to. * @xprt_version: IPC Router header version supported by this XPRT. */ struct msm_ipc_router_hsic_xprt_config { char ch_name[XPRT_NAME_LEN]; char xprt_name[XPRT_NAME_LEN]; int hsic_pdev_id; uint32_t link_id; unsigned xprt_version; }; struct msm_ipc_router_hsic_xprt_config hsic_xprt_cfg[] = { {"ipc_bridge", "ipc_rtr_ipc_bridge1", 1, 1, 3}, }; static struct msm_ipc_router_hsic_xprt hsic_remote_xprt[NUM_HSIC_XPRTS]; /** * find_hsic_xprt_cfg() - Find the config info specific to an HSIC endpoint * @pdev: Platform device registered by HSIC's ipc_bridge driver * * @return: Index to the entry in the hsic_remote_xprt table if matching * endpoint is found, < 0 on error. * * This function is used to find the configuration information specific to * an HSIC endpoint from the hsic_remote_xprt table. */ static int find_hsic_xprt_cfg(struct platform_device *pdev) { int i; for (i = 0; i < NUM_HSIC_XPRTS; i++) { /* TODO: Update the condition for multiple hsic links */ if (!strncmp(pdev->name, hsic_xprt_cfg[i].ch_name, 32)) return i; } return -ENODEV; } /** * msm_ipc_router_hsic_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 msm_ipc_router_hsic_get_xprt_version( struct msm_ipc_router_xprt *xprt) { struct msm_ipc_router_hsic_xprt *hsic_xprtp; if (!xprt) return -EINVAL; hsic_xprtp = container_of(xprt, struct msm_ipc_router_hsic_xprt, xprt); return (int)hsic_xprtp->xprt_version; } /** * msm_ipc_router_hsic_get_xprt_option() - Get XPRT options * @xprt: XPRT for which the option information is required. * * @return: Options supported by the XPRT. */ static int msm_ipc_router_hsic_get_xprt_option( struct msm_ipc_router_xprt *xprt) { struct msm_ipc_router_hsic_xprt *hsic_xprtp; if (!xprt) return -EINVAL; hsic_xprtp = container_of(xprt, struct msm_ipc_router_hsic_xprt, xprt); return (int)hsic_xprtp->xprt_option; } /** * msm_ipc_router_hsic_remote_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 msm_ipc_router_hsic_remote_write_avail( struct msm_ipc_router_xprt *xprt) { struct ipc_bridge_platform_data *pdata; int write_avail; struct msm_ipc_router_hsic_xprt *hsic_xprtp = container_of(xprt, struct msm_ipc_router_hsic_xprt, xprt); mutex_lock(&hsic_xprtp->ss_reset_lock); if (hsic_xprtp->ss_reset || !hsic_xprtp->pdev) { write_avail = 0; } else { pdata = hsic_xprtp->pdev->dev.platform_data; write_avail = pdata->max_write_size; } mutex_unlock(&hsic_xprtp->ss_reset_lock); return write_avail; } /** * msm_ipc_router_hsic_remote_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 msm_ipc_router_hsic_remote_write(void *data, uint32_t len, struct msm_ipc_router_xprt *xprt) { struct rr_packet *pkt = (struct rr_packet *)data; struct sk_buff *skb; struct ipc_bridge_platform_data *pdata; struct msm_ipc_router_hsic_xprt *hsic_xprtp; int ret; if (!pkt || pkt->length != len || !xprt) { pr_err("%s: Invalid input parameters\n", __func__); return -EINVAL; } hsic_xprtp = container_of(xprt, struct msm_ipc_router_hsic_xprt, xprt); mutex_lock(&hsic_xprtp->ss_reset_lock); if (hsic_xprtp->ss_reset) { pr_err("%s: Trying to write on a reset link\n", __func__); mutex_unlock(&hsic_xprtp->ss_reset_lock); return -ENETRESET; } if (!hsic_xprtp->pdev) { pr_err("%s: Trying to write on a closed link\n", __func__); mutex_unlock(&hsic_xprtp->ss_reset_lock); return -ENODEV; } pdata = hsic_xprtp->pdev->dev.platform_data; if (!pdata || !pdata->write) { pr_err("%s on a uninitialized link\n", __func__); mutex_unlock(&hsic_xprtp->ss_reset_lock); return -EFAULT; } skb = skb_peek(pkt->pkt_fragment_q); if (!skb) { pr_err("%s SKB is NULL\n", __func__); mutex_unlock(&hsic_xprtp->ss_reset_lock); return -EINVAL; } D("%s: About to write %d bytes\n", __func__, len); ret = pdata->write(hsic_xprtp->pdev, skb->data, skb->len); if (ret == skb->len) ret = len; D("%s: Finished writing %d bytes\n", __func__, len); mutex_unlock(&hsic_xprtp->ss_reset_lock); return ret; } /** * msm_ipc_router_hsic_remote_close() - Close the XPRT * @xprt: XPRT which needs to be closed. * * @return: 0 on success, standard Linux error codes on failure. */ static int msm_ipc_router_hsic_remote_close( struct msm_ipc_router_xprt *xprt) { struct msm_ipc_router_hsic_xprt *hsic_xprtp; struct ipc_bridge_platform_data *pdata; if (!xprt) return -EINVAL; hsic_xprtp = container_of(xprt, struct msm_ipc_router_hsic_xprt, xprt); mutex_lock(&hsic_xprtp->ss_reset_lock); hsic_xprtp->ss_reset = 1; mutex_unlock(&hsic_xprtp->ss_reset_lock); flush_workqueue(hsic_xprtp->hsic_xprt_wq); destroy_workqueue(hsic_xprtp->hsic_xprt_wq); pdata = hsic_xprtp->pdev->dev.platform_data; if (pdata && pdata->close) pdata->close(hsic_xprtp->pdev); hsic_xprtp->pdev = NULL; return 0; } /** * hsic_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 HSIC * endpoint, until the endpoint returns an error. */ static void hsic_xprt_read_data(struct work_struct *work) { int pkt_size; struct sk_buff *skb = NULL; void *data; struct ipc_bridge_platform_data *pdata; struct delayed_work *rwork = to_delayed_work(work); struct msm_ipc_router_hsic_xprt *hsic_xprtp = container_of(rwork, struct msm_ipc_router_hsic_xprt, read_work); while (1) { mutex_lock(&hsic_xprtp->ss_reset_lock); if (hsic_xprtp->ss_reset) { mutex_unlock(&hsic_xprtp->ss_reset_lock); break; } pdata = hsic_xprtp->pdev->dev.platform_data; mutex_unlock(&hsic_xprtp->ss_reset_lock); while (!hsic_xprtp->in_pkt) { hsic_xprtp->in_pkt = kzalloc(sizeof(struct rr_packet), GFP_KERNEL); if (hsic_xprtp->in_pkt) break; pr_err("%s: packet allocation failure\n", __func__); msleep(100); } while (!hsic_xprtp->in_pkt->pkt_fragment_q) { hsic_xprtp->in_pkt->pkt_fragment_q = kmalloc(sizeof(struct sk_buff_head), GFP_KERNEL); if (hsic_xprtp->in_pkt->pkt_fragment_q) break; pr_err("%s: Couldn't alloc pkt_fragment_q\n", __func__); msleep(100); } skb_queue_head_init(hsic_xprtp->in_pkt->pkt_fragment_q); D("%s: Allocated rr_packet\n", __func__); while (!skb) { skb = alloc_skb(pdata->max_read_size, GFP_KERNEL); if (skb) break; pr_err("%s: Couldn't alloc SKB\n", __func__); msleep(100); } data = skb_put(skb, pdata->max_read_size); pkt_size = pdata->read(hsic_xprtp->pdev, data, pdata->max_read_size); if (pkt_size < 0) { pr_err("%s: Error %d @ read operation\n", __func__, pkt_size); kfree_skb(skb); kfree(hsic_xprtp->in_pkt->pkt_fragment_q); kfree(hsic_xprtp->in_pkt); break; } skb_queue_tail(hsic_xprtp->in_pkt->pkt_fragment_q, skb); hsic_xprtp->in_pkt->length = pkt_size; D("%s: Packet size read %d\n", __func__, pkt_size); msm_ipc_router_xprt_notify(&hsic_xprtp->xprt, IPC_ROUTER_XPRT_EVENT_DATA, (void *)hsic_xprtp->in_pkt); release_pkt(hsic_xprtp->in_pkt); hsic_xprtp->in_pkt = NULL; skb = NULL; } } /** * hsic_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 HSIC XPRT Abstraction * Layer(XAL) that the reset of XPRT is completely handled by IPC Router. */ static void hsic_xprt_sft_close_done(struct msm_ipc_router_xprt *xprt) { struct msm_ipc_router_hsic_xprt *hsic_xprtp = container_of(xprt, struct msm_ipc_router_hsic_xprt, xprt); complete_all(&hsic_xprtp->sft_close_complete); } /** * msm_ipc_router_hsic_remote_remove() - Remove an HSIC endpoint * @pdev: Platform device corresponding to HSIC endpoint. * * @return: 0 on success, standard Linux error codes on error. * * This function is called when the underlying ipc_bridge driver unregisters * a platform device, mapped to an HSIC endpoint, during SSR. */ static int msm_ipc_router_hsic_remote_remove(struct platform_device *pdev) { int id; struct ipc_bridge_platform_data *pdata; id = find_hsic_xprt_cfg(pdev); if (id < 0) { pr_err("%s: called for unknown ch %s\n", __func__, pdev->name); return id; } mutex_lock(&hsic_remote_xprt[id].ss_reset_lock); hsic_remote_xprt[id].ss_reset = 1; mutex_unlock(&hsic_remote_xprt[id].ss_reset_lock); flush_workqueue(hsic_remote_xprt[id].hsic_xprt_wq); destroy_workqueue(hsic_remote_xprt[id].hsic_xprt_wq); init_completion(&hsic_remote_xprt[id].sft_close_complete); msm_ipc_router_xprt_notify(&hsic_remote_xprt[id].xprt, IPC_ROUTER_XPRT_EVENT_CLOSE, NULL); D("%s: Notified IPC Router of %s CLOSE\n", __func__, hsic_remote_xprt[id].xprt.name); wait_for_completion(&hsic_remote_xprt[id].sft_close_complete); hsic_remote_xprt[id].pdev = NULL; pdata = pdev->dev.platform_data; if (pdata && pdata->close) pdata->close(pdev); return 0; } /** * msm_ipc_router_hsic_remote_probe() - Probe an HSIC endpoint * @pdev: Platform device corresponding to HSIC endpoint. * * @return: 0 on success, standard Linux error codes on error. * * This function is called when the underlying ipc_bridge driver registers * a platform device, mapped to an HSIC endpoint. */ static int msm_ipc_router_hsic_remote_probe(struct platform_device *pdev) { int rc; int id; /*Index into the hsic_xprt_cfg table*/ struct ipc_bridge_platform_data *pdata; pdata = pdev->dev.platform_data; if (!pdata || !pdata->open || !pdata->read || !pdata->write || !pdata->close) { pr_err("%s: pdata or pdata->operations is NULL\n", __func__); return -EINVAL; } id = find_hsic_xprt_cfg(pdev); if (id < 0) { pr_err("%s: called for unknown ch %s\n", __func__, pdev->name); return id; } hsic_remote_xprt[id].hsic_xprt_wq = create_singlethread_workqueue(pdev->name); if (!hsic_remote_xprt[id].hsic_xprt_wq) { pr_err("%s: WQ creation failed for %s\n", __func__, pdev->name); return -EFAULT; } hsic_remote_xprt[id].xprt.name = hsic_xprt_cfg[id].xprt_name; hsic_remote_xprt[id].xprt.link_id = hsic_xprt_cfg[id].link_id; hsic_remote_xprt[id].xprt.get_version = msm_ipc_router_hsic_get_xprt_version; hsic_remote_xprt[id].xprt.get_option = msm_ipc_router_hsic_get_xprt_option; hsic_remote_xprt[id].xprt.read_avail = NULL; hsic_remote_xprt[id].xprt.read = NULL; hsic_remote_xprt[id].xprt.write_avail = msm_ipc_router_hsic_remote_write_avail; hsic_remote_xprt[id].xprt.write = msm_ipc_router_hsic_remote_write; hsic_remote_xprt[id].xprt.close = msm_ipc_router_hsic_remote_close; hsic_remote_xprt[id].xprt.sft_close_done = hsic_xprt_sft_close_done; hsic_remote_xprt[id].xprt.priv = NULL; hsic_remote_xprt[id].in_pkt = NULL; INIT_DELAYED_WORK(&hsic_remote_xprt[id].read_work, hsic_xprt_read_data); mutex_init(&hsic_remote_xprt[id].ss_reset_lock); hsic_remote_xprt[id].ss_reset = 0; hsic_remote_xprt[id].xprt_version = hsic_xprt_cfg[id].xprt_version; hsic_remote_xprt[id].xprt_option = 0; rc = pdata->open(pdev); if (rc < 0) { pr_err("%s: Channel open failed for %s.%d\n", __func__, pdev->name, pdev->id); destroy_workqueue(hsic_remote_xprt[id].hsic_xprt_wq); return rc; } hsic_remote_xprt[id].pdev = pdev; msm_ipc_router_xprt_notify(&hsic_remote_xprt[id].xprt, IPC_ROUTER_XPRT_EVENT_OPEN, NULL); D("%s: Notified IPC Router of %s OPEN\n", __func__, hsic_remote_xprt[id].xprt.name); queue_delayed_work(hsic_remote_xprt[id].hsic_xprt_wq, &hsic_remote_xprt[id].read_work, 0); return 0; } static struct platform_driver msm_ipc_router_hsic_remote_driver[] = { { .probe = msm_ipc_router_hsic_remote_probe, .remove = msm_ipc_router_hsic_remote_remove, .driver = { .name = "ipc_bridge", .owner = THIS_MODULE, }, }, }; static int __init msm_ipc_router_hsic_init(void) { int i, ret, rc = 0; BUG_ON(ARRAY_SIZE(hsic_xprt_cfg) != NUM_HSIC_XPRTS); for (i = 0; i < ARRAY_SIZE(msm_ipc_router_hsic_remote_driver); i++) { ret = platform_driver_register( &msm_ipc_router_hsic_remote_driver[i]); if (ret) { pr_err("%s: Failed to register platform driver for xprt%d. Continuing...\n", __func__, i); rc = ret; } } return rc; } module_init(msm_ipc_router_hsic_init); MODULE_DESCRIPTION("IPC Router HSIC XPRT"); MODULE_LICENSE("GPL v2");