From 0ff9eadd9cb27e123880d584b4348efece5f9c49 Mon Sep 17 00:00:00 2001 From: Lazarus Motha Date: Thu, 8 Sep 2022 12:45:49 -0700 Subject: [PATCH] bus: mhi: devices: Add snapshot of MHI devices driver This is a snapshot of MHI devices driver from msm-5.15 commit 08c9da520628 ("bus: mhi: Add snapshot of MHI devices"). Change-Id: I1f23afeedaab09f94a411f5b9eb02d2ff31a777a Signed-off-by: Lazarus Motha --- drivers/bus/mhi/devices/Kconfig | 47 + drivers/bus/mhi/devices/Makefile | 13 + drivers/bus/mhi/devices/mhi_dtr.c | 277 +++++ drivers/bus/mhi/devices/mhi_netdev.c | 1181 ++++++++++++++++++++++ drivers/bus/mhi/devices/mhi_satellite.c | 1232 +++++++++++++++++++++++ drivers/bus/mhi/devices/mhi_uci.c | 825 +++++++++++++++ 6 files changed, 3575 insertions(+) create mode 100644 drivers/bus/mhi/devices/Kconfig create mode 100644 drivers/bus/mhi/devices/Makefile create mode 100644 drivers/bus/mhi/devices/mhi_dtr.c create mode 100644 drivers/bus/mhi/devices/mhi_netdev.c create mode 100644 drivers/bus/mhi/devices/mhi_satellite.c create mode 100644 drivers/bus/mhi/devices/mhi_uci.c diff --git a/drivers/bus/mhi/devices/Kconfig b/drivers/bus/mhi/devices/Kconfig new file mode 100644 index 000000000000..3516e0b73020 --- /dev/null +++ b/drivers/bus/mhi/devices/Kconfig @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menu "MHI device support" + +config MHI_NETDEV + tristate "MHI NETDEV" + depends on MHI_BUS + help + MHI based net device driver for transferring IP traffic + between host and modem. By enabling this driver, clients + can transfer data using standard network interface. Over + the air traffic goes thru mhi netdev interface. + +config MHI_UCI + tristate "MHI UCI" + depends on MHI_BUS && MHI_BUS_MISC + help + MHI based uci driver is for transferring data between host and + modem using standard file operations from user space. Open, read, + write, ioctl, and close operations are supported by this driver. + Please check mhi_uci_match_table for all supported channels that + are exposed to userspace. + +config MHI_DTR + tristate "MHI DTR SIGNALING" + depends on MHI_BUS && MHI_BUS_MISC + help + MHI device driver for enabling DTR<>RTS UART signaling as IP_CTRL + client for userspace. Data Terminal Ready (DTR) is a control signal + in RS-232 serial communications, transmitted from data terminal + equipment (DTE), or host in this case, to data communications + equipment (DCE), or external modem in this case to indicate that the + terminal/host is ready for communications and the modem may initiate + a communications channel. + +config MHI_SATELLITE + tristate "MHI SATELLITE" + depends on MHI_BUS + help + MHI proxy satellite device driver enables NON-HLOS MHI satellite + drivers to communicate with device over PCIe link without host + involvement. Host facilitates propagation of events from device + to NON-HLOS MHI satellite drivers, channel states, and power + management over IPC communication. It helps in HLOS power + savings. + +endmenu diff --git a/drivers/bus/mhi/devices/Makefile b/drivers/bus/mhi/devices/Makefile new file mode 100644 index 000000000000..ee1beda53f28 --- /dev/null +++ b/drivers/bus/mhi/devices/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only + +mhi_dev_uci-y := mhi_uci.o +obj-$(CONFIG_MHI_UCI) += mhi_dev_uci.o + +mhi_dev_netdev-y := mhi_netdev.o +obj-$(CONFIG_MHI_NETDEV) += mhi_dev_netdev.o + +mhi_dev_dtr-y := mhi_dtr.o +obj-$(CONFIG_MHI_DTR) += mhi_dev_dtr.o + +mhi_dev_satellite-y := mhi_satellite.o +obj-$(CONFIG_MHI_SATELLITE) += mhi_dev_satellite.o diff --git a/drivers/bus/mhi/devices/mhi_dtr.c b/drivers/bus/mhi/devices/mhi_dtr.c new file mode 100644 index 000000000000..a1412ad5adaa --- /dev/null +++ b/drivers/bus/mhi/devices/mhi_dtr.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct dtr_ctrl_msg { + u32 preamble; + u32 msg_id; + u32 dest_id; + u32 size; + u32 msg; +} __packed; + +static struct dtr_info { + struct completion completion; + struct mhi_device *mhi_dev; + void *ipc_log; +} *dtr_info; + +static enum MHI_DEBUG_LEVEL dtr_log_level = MHI_MSG_LVL_INFO; + +#define MHI_DTR_IPC_LOG_PAGES (5) +#define DTR_LOG(fmt, ...) do { \ + dev_dbg(dev, "[I][%s] " fmt, __func__, ##__VA_ARGS__); \ + if (dtr_info->ipc_log && dtr_log_level <= MHI_MSG_LVL_INFO) \ + ipc_log_string(dtr_info->ipc_log, "[I][%s] " fmt, \ + __func__, ##__VA_ARGS__); \ + } while (0) + +#define DTR_ERR(fmt, ...) do { \ + dev_err(dev, "[E][%s] " fmt, __func__, ##__VA_ARGS__); \ + if (dtr_info->ipc_log && dtr_log_level <= MHI_MSG_LVL_ERROR) \ + ipc_log_string(dtr_info->ipc_log, "[E][%s] " fmt, \ + __func__, ##__VA_ARGS__); \ + } while (0) + +#define CTRL_MAGIC (0x4C525443) +#define CTRL_MSG_DTR BIT(0) +#define CTRL_MSG_RTS BIT(1) +#define CTRL_MSG_DCD BIT(0) +#define CTRL_MSG_DSR BIT(1) +#define CTRL_MSG_RI BIT(3) +#define CTRL_HOST_STATE (0x10) +#define CTRL_DEVICE_STATE (0x11) +#define CTRL_GET_CHID(dtr) ((dtr)->dest_id & 0xFF) + +static int mhi_dtr_tiocmset(struct mhi_controller *mhi_cntrl, + struct mhi_device *mhi_dev, + u32 tiocm) +{ + struct device *dev = &mhi_dev->dev; + struct dtr_ctrl_msg *dtr_msg = NULL; + /* protects state changes for MHI device termios states */ + spinlock_t *res_lock = &mhi_dev->dev.devres_lock; + u32 cur_tiocm; + int ret = 0; + + cur_tiocm = mhi_dev->tiocm & ~(TIOCM_CD | TIOCM_DSR | TIOCM_RI); + + tiocm &= (TIOCM_DTR | TIOCM_RTS); + + /* state did not change */ + if (cur_tiocm == tiocm) + return 0; + + dtr_msg = kzalloc(sizeof(*dtr_msg), GFP_KERNEL); + if (!dtr_msg) { + ret = -ENOMEM; + goto tiocm_exit; + } + + dtr_msg->preamble = CTRL_MAGIC; + dtr_msg->msg_id = CTRL_HOST_STATE; + dtr_msg->dest_id = mhi_dev->ul_chan_id; + dtr_msg->size = sizeof(u32); + if (tiocm & TIOCM_DTR) + dtr_msg->msg |= CTRL_MSG_DTR; + if (tiocm & TIOCM_RTS) + dtr_msg->msg |= CTRL_MSG_RTS; + + reinit_completion(&dtr_info->completion); + ret = mhi_queue_buf(dtr_info->mhi_dev, DMA_TO_DEVICE, dtr_msg, + sizeof(*dtr_msg), MHI_EOT); + if (ret) + goto tiocm_exit; + + ret = wait_for_completion_timeout(&dtr_info->completion, + msecs_to_jiffies(mhi_cntrl->timeout_ms)); + if (!ret) { + DTR_ERR("Failed to receive transfer callback\n"); + ret = -EIO; + goto tiocm_exit; + } + + DTR_LOG("DTR TIOCMSET update done for %s\n", mhi_dev->name); + ret = 0; + spin_lock_irq(res_lock); + mhi_dev->tiocm &= ~(TIOCM_DTR | TIOCM_RTS); + mhi_dev->tiocm |= tiocm; + spin_unlock_irq(res_lock); + +tiocm_exit: + kfree(dtr_msg); + + return ret; +} + +long mhi_device_ioctl(struct mhi_device *mhi_dev, unsigned int cmd, + unsigned long arg) +{ + struct device *dev = &mhi_dev->dev; + struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; + int ret; + + /* ioctl not supported by this controller */ + if (!dtr_info->mhi_dev) { + DTR_ERR("%s request denied. DTR channels not running\n", + mhi_dev->name); + return -EIO; + } + + switch (cmd) { + case TIOCMGET: + return mhi_dev->tiocm; + case TIOCMSET: + { + u32 tiocm; + + ret = get_user(tiocm, (u32 __user *)arg); + if (ret) + return ret; + + return mhi_dtr_tiocmset(mhi_cntrl, mhi_dev, tiocm); + } + default: + break; + } + + return -ENOIOCTLCMD; +} +EXPORT_SYMBOL(mhi_device_ioctl); + +static void mhi_dtr_dl_xfer_cb(struct mhi_device *mhi_dev, + struct mhi_result *mhi_result) +{ + struct device *dev = &mhi_dev->dev; + struct mhi_controller *mhi_cntrl = mhi_dev->mhi_cntrl; + struct dtr_ctrl_msg *dtr_msg = mhi_result->buf_addr; + /* protects state changes for MHI device termios states */ + spinlock_t *res_lock; + + if (mhi_result->bytes_xferd != sizeof(*dtr_msg)) { + DTR_ERR("Unexpected length %zu received\n", + mhi_result->bytes_xferd); + return; + } + + DTR_LOG("preamble: 0x%x msg_id: %u dest_id: %u msg: 0x%x\n", + dtr_msg->preamble, dtr_msg->msg_id, dtr_msg->dest_id, + dtr_msg->msg); + + mhi_dev = mhi_get_device_for_channel(mhi_cntrl, CTRL_GET_CHID(dtr_msg)); + if (!mhi_dev) + return; + + res_lock = &mhi_dev->dev.devres_lock; + spin_lock_irq(res_lock); + mhi_dev->tiocm &= ~(TIOCM_CD | TIOCM_DSR | TIOCM_RI); + + if (dtr_msg->msg & CTRL_MSG_DCD) + mhi_dev->tiocm |= TIOCM_CD; + + if (dtr_msg->msg & CTRL_MSG_DSR) + mhi_dev->tiocm |= TIOCM_DSR; + + if (dtr_msg->msg & CTRL_MSG_RI) + mhi_dev->tiocm |= TIOCM_RI; + spin_unlock_irq(res_lock); + + /* Notify the update */ + mhi_notify(mhi_dev, MHI_CB_DTR_SIGNAL); +} + +static void mhi_dtr_ul_xfer_cb(struct mhi_device *mhi_dev, + struct mhi_result *mhi_result) +{ + struct device *dev = &mhi_dev->dev; + + DTR_LOG("Received with status: %d\n", mhi_result->transaction_status); + if (!mhi_result->transaction_status) + complete(&dtr_info->completion); +} + +static void mhi_dtr_status_cb(struct mhi_device *mhi_dev, enum mhi_callback cb) +{ + struct device *dev = &mhi_dev->dev; + int ret; + + if (cb != MHI_CB_DTR_START_CHANNELS) + return; + + ret = mhi_prepare_for_transfer(mhi_dev, 0); + if (!ret) + dtr_info->mhi_dev = mhi_dev; + + DTR_LOG("DTR channels start attempt returns: %d\n", ret); +} + +static void mhi_dtr_remove(struct mhi_device *mhi_dev) +{ + dtr_info->mhi_dev = NULL; +} + +static int mhi_dtr_probe(struct mhi_device *mhi_dev, + const struct mhi_device_id *id) +{ + struct device *dev = &mhi_dev->dev; + + dtr_info->ipc_log = ipc_log_context_create(MHI_DTR_IPC_LOG_PAGES, + dev_name(&mhi_dev->dev), 0); + + DTR_LOG("Probe complete\n"); + + return 0; +} + +static const struct mhi_device_id mhi_dtr_table[] = { + { .chan = "IP_CTRL" }, + {}, +}; + +static struct mhi_driver mhi_dtr_driver = { + .id_table = mhi_dtr_table, + .remove = mhi_dtr_remove, + .probe = mhi_dtr_probe, + .ul_xfer_cb = mhi_dtr_ul_xfer_cb, + .dl_xfer_cb = mhi_dtr_dl_xfer_cb, + .status_cb = mhi_dtr_status_cb, + .driver = { + .name = "MHI_DTR", + .owner = THIS_MODULE, + } +}; + +static int __init mhi_dtr_init(void) +{ + dtr_info = kzalloc(sizeof(*dtr_info), GFP_KERNEL); + if (!dtr_info) + return -ENOMEM; + + init_completion(&dtr_info->completion); + + return mhi_driver_register(&mhi_dtr_driver); +} +module_init(mhi_dtr_init); + +static void __exit mhi_dtr_exit(void) +{ + mhi_driver_unregister(&mhi_dtr_driver); + kfree(dtr_info); +} +module_exit(mhi_dtr_exit); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("MHI_DTR"); +MODULE_DESCRIPTION("MHI DTR Driver"); diff --git a/drivers/bus/mhi/devices/mhi_netdev.c b/drivers/bus/mhi/devices/mhi_netdev.c new file mode 100644 index 000000000000..f4e075402fed --- /dev/null +++ b/drivers/bus/mhi/devices/mhi_netdev.c @@ -0,0 +1,1181 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MHI_NETDEV_DRIVER_NAME "mhi_netdev" +#define WATCHDOG_TIMEOUT (30 * HZ) +#define IPC_LOG_PAGES (100) +#define MAX_NETBUF_SIZE (128) +#define MHI_NETDEV_NAPI_POLL_WEIGHT (64) + +#ifdef CONFIG_MHI_BUS_DEBUG +#define MHI_NETDEV_LOG_LVL MHI_MSG_LVL_VERBOSE +#else +#define MHI_NETDEV_LOG_LVL MHI_MSG_LVL_ERROR +#endif + +#define MSG_VERB(fmt, ...) do { \ + if (mhi_netdev->ipc_log && mhi_netdev->msg_lvl <= MHI_MSG_LVL_VERBOSE) \ + ipc_log_string(mhi_netdev->ipc_log, "%s[D][%s] " fmt, \ + "", __func__, ##__VA_ARGS__); \ +} while (0) + +#define MSG_LOG(fmt, ...) do { \ + if (mhi_netdev->ipc_log && mhi_netdev->msg_lvl <= MHI_MSG_LVL_INFO) \ + ipc_log_string(mhi_netdev->ipc_log, "%s[I][%s] " fmt, \ + "", __func__, ##__VA_ARGS__); \ +} while (0) + +#define MSG_ERR(fmt, ...) do { \ + pr_err("[E][%s] " fmt, __func__, ##__VA_ARGS__);\ + if (mhi_netdev->ipc_log && mhi_netdev->msg_lvl <= MHI_MSG_LVL_ERROR) \ + ipc_log_string(mhi_netdev->ipc_log, "%s[E][%s] " fmt, \ + "", __func__, ##__VA_ARGS__); \ +} while (0) + +const char * const mhi_log_level_str[MHI_MSG_LVL_MAX] = { + [MHI_MSG_LVL_VERBOSE] = "Verbose", + [MHI_MSG_LVL_INFO] = "Info", + [MHI_MSG_LVL_ERROR] = "Error", + [MHI_MSG_LVL_CRITICAL] = "Critical", + [MHI_MSG_LVL_MASK_ALL] = "Mask all", +}; +#define MHI_NETDEV_LOG_LEVEL_STR(level) ((level >= MHI_MSG_LVL_MAX || \ + !mhi_log_level_str[level]) ? \ + "Mask all" : mhi_log_level_str[level]) + +struct mhi_net_chain { + struct sk_buff *head, *tail; /* chained skb */ +}; + +struct mhi_netdev { + struct mhi_device *mhi_dev; + struct mhi_netdev *rsc_dev; /* rsc linked node */ + struct mhi_netdev *rsc_parent; + bool is_rsc_dev; + int wake; + + u32 mru; + u32 order; + const char *interface_name; + struct napi_struct *napi; + struct net_device *ndev; + + struct list_head *recycle_pool; + int pool_size; + bool chain_skb; + struct mhi_net_chain *chain; + + struct task_struct *alloc_task; + wait_queue_head_t alloc_event; + int bg_pool_limit; /* minimum pool size */ + int bg_pool_size; /* current size of the pool */ + struct list_head *bg_pool; + spinlock_t bg_lock; /* lock to access list */ + + + struct dentry *dentry; + enum MHI_DEBUG_LEVEL msg_lvl; + void *ipc_log; + + /* debug stats */ + u32 abuffers, kbuffers, rbuffers; + bool napi_scheduled; +}; + +struct mhi_netdev_priv { + struct mhi_netdev *mhi_netdev; +}; + +/* Try not to make this structure bigger than 128 bytes, since this take space + * in payload packet. + * Example: If MRU = 16K, effective MRU = 16K - sizeof(mhi_netbuf) + */ +struct mhi_netbuf { + struct mhi_buf mhi_buf; /* this must be first element */ + bool recycle; + struct page *page; + struct list_head node; + void (*unmap)(struct device *dev, dma_addr_t addr, size_t size, + enum dma_data_direction dir); +}; + +struct mhi_netdev_driver_data { + u32 mru; + bool chain_skb; + bool is_rsc_chan; + bool has_rsc_child; + const char *interface_name; +}; + +static struct mhi_netdev *rsc_parent_netdev; +static struct mhi_driver mhi_netdev_driver; +static void mhi_netdev_create_debugfs(struct mhi_netdev *mhi_netdev); + +static __be16 mhi_netdev_ip_type_trans(u8 data) +{ + __be16 protocol = htons(ETH_P_MAP); + + /* determine L3 protocol */ + switch (data & 0xf0) { + case 0x40: + /* length must be 5 at a minimum to support 20 byte IP header */ + if ((data & 0x0f) > 4) + protocol = htons(ETH_P_IP); + break; + case 0x60: + protocol = htons(ETH_P_IPV6); + break; + default: + /* default is already QMAP */ + break; + } + return protocol; +} + +static struct mhi_netbuf *mhi_netdev_alloc(struct device *dev, + gfp_t gfp, + unsigned int order) +{ + struct page *page; + struct mhi_netbuf *netbuf; + struct mhi_buf *mhi_buf; + void *vaddr; + + page = __dev_alloc_pages(gfp | __GFP_NOMEMALLOC, order); + if (!page) + return NULL; + + vaddr = page_address(page); + + /* we going to use the end of page to store cached data */ + netbuf = vaddr + (PAGE_SIZE << order) - sizeof(*netbuf); + netbuf->recycle = false; + netbuf->page = page; + mhi_buf = (struct mhi_buf *)netbuf; + mhi_buf->buf = vaddr; + mhi_buf->len = (void *)netbuf - vaddr; + + if (!dev) + return netbuf; + + mhi_buf->dma_addr = dma_map_page(dev, page, 0, mhi_buf->len, + DMA_FROM_DEVICE); + if (dma_mapping_error(dev, mhi_buf->dma_addr)) { + __free_pages(netbuf->page, order); + return NULL; + } + + return netbuf; +} + +static void mhi_netdev_unmap_page(struct device *dev, + dma_addr_t dma_addr, + size_t len, + enum dma_data_direction dir) +{ + dma_unmap_page(dev, dma_addr, len, dir); +} + +static int mhi_netdev_tmp_alloc(struct mhi_netdev *mhi_netdev, + struct mhi_device *mhi_dev, + int nr_tre) +{ + struct device *dev = mhi_dev->dev.parent->parent; + const u32 order = mhi_netdev->order; + int i, ret; + + for (i = 0; i < nr_tre; i++) { + struct mhi_buf *mhi_buf; + struct mhi_netbuf *netbuf = mhi_netdev_alloc(dev, GFP_ATOMIC, + order); + if (!netbuf) + return -ENOMEM; + + mhi_buf = (struct mhi_buf *)netbuf; + netbuf->unmap = mhi_netdev_unmap_page; + + ret = mhi_queue_dma(mhi_dev, DMA_FROM_DEVICE, mhi_buf, + mhi_buf->len, MHI_EOT); + if (unlikely(ret)) { + MSG_ERR("Failed to queue transfer, ret:%d\n", ret); + mhi_netdev_unmap_page(dev, mhi_buf->dma_addr, + mhi_buf->len, DMA_FROM_DEVICE); + __free_pages(netbuf->page, order); + return ret; + } + mhi_netdev->abuffers++; + } + + return 0; +} + +static int mhi_netdev_queue_bg_pool(struct mhi_netdev *mhi_netdev, + struct mhi_device *mhi_dev, + int nr_tre) +{ + struct device *dev = mhi_dev->dev.parent->parent; + int i, ret; + LIST_HEAD(head); + + spin_lock_bh(&mhi_netdev->bg_lock); + list_splice_init(mhi_netdev->bg_pool, &head); + spin_unlock_bh(&mhi_netdev->bg_lock); + + for (i = 0; i < nr_tre; i++) { + struct mhi_netbuf *net_buf = + list_first_entry_or_null(&head, struct mhi_netbuf, node); + struct mhi_buf *mhi_buf = (struct mhi_buf *)net_buf; + + if (!mhi_buf) + break; + + mhi_buf->dma_addr = dma_map_page(dev, net_buf->page, 0, + mhi_buf->len, DMA_FROM_DEVICE); + if (dma_mapping_error(dev, mhi_buf->dma_addr)) + break; + + net_buf->unmap = mhi_netdev_unmap_page; + ret = mhi_queue_dma(mhi_dev, DMA_FROM_DEVICE, mhi_buf, + mhi_buf->len, MHI_EOT); + if (unlikely(ret)) { + MSG_ERR("Failed to queue transfer, ret: %d\n", ret); + mhi_netdev_unmap_page(dev, mhi_buf->dma_addr, + mhi_buf->len, DMA_FROM_DEVICE); + break; + } + list_del(&net_buf->node); + mhi_netdev->kbuffers++; + } + + /* add remaining buffers back to main pool */ + spin_lock_bh(&mhi_netdev->bg_lock); + list_splice(&head, mhi_netdev->bg_pool); + mhi_netdev->bg_pool_size -= i; + spin_unlock_bh(&mhi_netdev->bg_lock); + + + /* wake up the bg thread to allocate more buffers */ + wake_up_interruptible(&mhi_netdev->alloc_event); + + return i; +} + +static void mhi_netdev_queue(struct mhi_netdev *mhi_netdev, + struct mhi_device *mhi_dev) +{ + struct device *dev = mhi_dev->dev.parent->parent; + struct mhi_netbuf *netbuf, *temp_buf; + struct mhi_buf *mhi_buf; + struct list_head *pool = mhi_netdev->recycle_pool; + int nr_tre = mhi_get_free_desc_count(mhi_dev, DMA_FROM_DEVICE); + int i, ret; + const int max_peek = 4; + + MSG_VERB("Enter free descriptors: %d\n", nr_tre); + + if (!nr_tre) + return; + + /* try going thru reclaim pool first */ + for (i = 0; i < nr_tre; i++) { + /* peek for the next buffer, we going to peak several times, + * and we going to give up if buffers are not yet free + */ + int peek = 0; + + netbuf = NULL; + list_for_each_entry(temp_buf, pool, node) { + mhi_buf = (struct mhi_buf *)temp_buf; + /* page == 1 idle, buffer is free to reclaim */ + if (page_ref_count(temp_buf->page) == 1) { + netbuf = temp_buf; + break; + } + + if (peek++ >= max_peek) + break; + } + + /* could not find a free buffer */ + if (!netbuf) + break; + + /* increment reference count so when network stack is done + * with buffer, the buffer won't be freed + */ + page_ref_inc(temp_buf->page); + list_del(&temp_buf->node); + dma_sync_single_for_device(dev, mhi_buf->dma_addr, mhi_buf->len, + DMA_FROM_DEVICE); + ret = mhi_queue_dma(mhi_dev, DMA_FROM_DEVICE, mhi_buf, + mhi_buf->len, MHI_EOT); + if (unlikely(ret)) { + MSG_ERR("Failed to queue buffer, ret: %d\n", ret); + netbuf->unmap(dev, mhi_buf->dma_addr, mhi_buf->len, + DMA_FROM_DEVICE); + page_ref_dec(temp_buf->page); + list_add(&temp_buf->node, pool); + return; + } + mhi_netdev->rbuffers++; + } + + /* recycling did not work, buffers are still busy use bg pool */ + if (i < nr_tre) + i += mhi_netdev_queue_bg_pool(mhi_netdev, mhi_dev, nr_tre - i); + + /* recyling did not work, buffers are still busy allocate temp pkts */ + if (i < nr_tre) + mhi_netdev_tmp_alloc(mhi_netdev, mhi_dev, nr_tre - i); +} + +/* allocating pool of memory */ +static int mhi_netdev_alloc_pool(struct mhi_netdev *mhi_netdev) +{ + int i; + struct mhi_netbuf *netbuf, *tmp; + struct mhi_buf *mhi_buf; + const u32 order = mhi_netdev->order; + struct device *dev = mhi_netdev->mhi_dev->dev.parent->parent; + struct list_head *pool = kmalloc(sizeof(*pool), GFP_KERNEL); + + if (!pool) + return -ENOMEM; + + INIT_LIST_HEAD(pool); + + for (i = 0; i < mhi_netdev->pool_size; i++) { + /* allocate paged data */ + netbuf = mhi_netdev_alloc(dev, GFP_KERNEL, order); + if (!netbuf) + goto error_alloc_page; + + netbuf->unmap = dma_sync_single_for_cpu; + netbuf->recycle = true; + mhi_buf = (struct mhi_buf *)netbuf; + list_add(&netbuf->node, pool); + } + + mhi_netdev->recycle_pool = pool; + + return 0; + +error_alloc_page: + list_for_each_entry_safe(netbuf, tmp, pool, node) { + list_del(&netbuf->node); + mhi_buf = (struct mhi_buf *)netbuf; + dma_unmap_page(dev, mhi_buf->dma_addr, mhi_buf->len, + DMA_FROM_DEVICE); + __free_pages(netbuf->page, order); + } + + kfree(pool); + + return -ENOMEM; +} + +static void mhi_netdev_free_pool(struct mhi_netdev *mhi_netdev) +{ + struct device *dev = mhi_netdev->mhi_dev->dev.parent->parent; + struct mhi_netbuf *netbuf, *tmp; + struct mhi_buf *mhi_buf; + + list_for_each_entry_safe(netbuf, tmp, mhi_netdev->recycle_pool, node) { + list_del(&netbuf->node); + mhi_buf = (struct mhi_buf *)netbuf; + dma_unmap_page(dev, mhi_buf->dma_addr, mhi_buf->len, + DMA_FROM_DEVICE); + __free_pages(netbuf->page, mhi_netdev->order); + } + kfree(mhi_netdev->recycle_pool); + + /* free the bg pool */ + list_for_each_entry_safe(netbuf, tmp, mhi_netdev->bg_pool, node) { + list_del(&netbuf->node); + __free_pages(netbuf->page, mhi_netdev->order); + mhi_netdev->bg_pool_size--; + } + + kfree(mhi_netdev->bg_pool); +} + +static int mhi_netdev_alloc_thread(void *data) +{ + struct mhi_netdev *mhi_netdev = data; + struct mhi_netbuf *netbuf, *tmp_buf; + struct mhi_buf *mhi_buf; + const u32 order = mhi_netdev->order; + LIST_HEAD(head); + + while (!kthread_should_stop()) { + while (mhi_netdev->bg_pool_size <= mhi_netdev->bg_pool_limit) { + int buffers = 0, i; + + /* do a bulk allocation */ + for (i = 0; i < NAPI_POLL_WEIGHT; i++) { + if (kthread_should_stop()) + goto exit_alloc; + + netbuf = mhi_netdev_alloc(NULL, GFP_KERNEL, + order); + if (!netbuf) + continue; + + mhi_buf = (struct mhi_buf *)netbuf; + list_add(&netbuf->node, &head); + buffers++; + } + + /* add the list to main pool */ + spin_lock_bh(&mhi_netdev->bg_lock); + list_splice_init(&head, mhi_netdev->bg_pool); + mhi_netdev->bg_pool_size += buffers; + spin_unlock_bh(&mhi_netdev->bg_lock); + } + + /* replenish the ring */ + napi_schedule(mhi_netdev->napi); + mhi_netdev->napi_scheduled = true; + + /* wait for buffers to run low or thread to stop */ + wait_event_interruptible(mhi_netdev->alloc_event, + kthread_should_stop() || + mhi_netdev->bg_pool_size <= mhi_netdev->bg_pool_limit); + } + +exit_alloc: + list_for_each_entry_safe(netbuf, tmp_buf, &head, node) { + list_del(&netbuf->node); + __free_pages(netbuf->page, order); + } + + return 0; +} + +static int mhi_netdev_poll(struct napi_struct *napi, int budget) +{ + struct net_device *dev = napi->dev; + struct mhi_netdev_priv *mhi_netdev_priv = netdev_priv(dev); + struct mhi_netdev *mhi_netdev = mhi_netdev_priv->mhi_netdev; + struct mhi_device *mhi_dev = mhi_netdev->mhi_dev; + struct mhi_netdev *rsc_dev = mhi_netdev->rsc_dev; + struct mhi_net_chain *chain = mhi_netdev->chain; + int rx_work = 0; + + MSG_VERB("Enter: %d\n", budget); + + rx_work = mhi_poll(mhi_dev, budget); + + /* chained skb, push it to stack */ + if (chain && chain->head) { + netif_receive_skb(chain->head); + chain->head = NULL; + } + + if (rx_work < 0) { + MSG_ERR("Error polling ret: %d\n", rx_work); + napi_complete(napi); + mhi_netdev->napi_scheduled = false; + return 0; + } + + /* queue new buffers */ + mhi_netdev_queue(mhi_netdev, mhi_dev); + + if (rsc_dev) + mhi_netdev_queue(mhi_netdev, rsc_dev->mhi_dev); + + /* complete work if # of packet processed less than allocated budget */ + if (rx_work < budget) { + napi_complete(napi); + mhi_netdev->napi_scheduled = false; + } + + MSG_VERB("Polled: %d\n", rx_work); + + return rx_work; +} + +static int mhi_netdev_open(struct net_device *dev) +{ + struct mhi_netdev_priv *mhi_netdev_priv = netdev_priv(dev); + struct mhi_netdev *mhi_netdev = mhi_netdev_priv->mhi_netdev; + struct mhi_device *mhi_dev = mhi_netdev->mhi_dev; + + MSG_LOG("Opened netdev interface\n"); + + /* tx queue may not necessarily be stopped already + * so stop the queue if tx path is not enabled + */ + if (!mhi_dev->ul_chan) + netif_stop_queue(dev); + else + netif_start_queue(dev); + + return 0; + +} + +static int mhi_netdev_change_mtu(struct net_device *dev, int new_mtu) +{ + + if (new_mtu < 0 || MHI_MAX_MTU < new_mtu) + return -EINVAL; + + dev->mtu = new_mtu; + return 0; +} + +static netdev_tx_t mhi_netdev_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct mhi_netdev_priv *mhi_netdev_priv = netdev_priv(dev); + struct mhi_netdev *mhi_netdev = mhi_netdev_priv->mhi_netdev; + struct mhi_device *mhi_dev = mhi_netdev->mhi_dev; + netdev_tx_t res = NETDEV_TX_OK; + int ret; + + MSG_VERB("Entered\n"); + + ret = mhi_queue_skb(mhi_dev, DMA_TO_DEVICE, skb, skb->len, + MHI_EOT); + if (ret) { + MSG_VERB("Failed to queue with reason: %d\n", res); + netif_stop_queue(dev); + res = NETDEV_TX_BUSY; + } + + MSG_VERB("Exited\n"); + + return res; +} + +static int mhi_netdev_ioctl_extended(struct net_device *dev, struct ifreq *ifr) +{ + struct rmnet_ioctl_extended_s ext_cmd; + int rc = 0; + struct mhi_netdev_priv *mhi_netdev_priv = netdev_priv(dev); + struct mhi_netdev *mhi_netdev = mhi_netdev_priv->mhi_netdev; + struct mhi_device *mhi_dev = mhi_netdev->mhi_dev; + + rc = copy_from_user(&ext_cmd, ifr->ifr_ifru.ifru_data, + sizeof(struct rmnet_ioctl_extended_s)); + if (rc) + return rc; + + switch (ext_cmd.extended_ioctl) { + case RMNET_IOCTL_GET_SUPPORTED_FEATURES: + ext_cmd.u.data = 0; + break; + case RMNET_IOCTL_GET_DRIVER_NAME: + strscpy(ext_cmd.u.if_name, mhi_netdev->interface_name, + sizeof(ext_cmd.u.if_name)); + break; + case RMNET_IOCTL_SET_SLEEP_STATE: + if (ext_cmd.u.data && mhi_netdev->wake) { + /* Request to enable LPM */ + MSG_VERB("Enable MHI LPM\n"); + mhi_netdev->wake--; + mhi_device_put(mhi_dev); + } else if (!ext_cmd.u.data && !mhi_netdev->wake) { + /* Request to disable LPM */ + MSG_VERB("Disable MHI LPM\n"); + mhi_netdev->wake++; + mhi_device_get(mhi_dev); + } + break; + default: + rc = -EINVAL; + break; + } + + rc = copy_to_user(ifr->ifr_ifru.ifru_data, &ext_cmd, + sizeof(struct rmnet_ioctl_extended_s)); + return rc; +} + +static int mhi_netdev_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + int rc = 0; + struct rmnet_ioctl_data_s ioctl_data; + + switch (cmd) { + case RMNET_IOCTL_SET_LLP_IP: /* set RAWIP protocol */ + break; + case RMNET_IOCTL_GET_LLP: /* get link protocol state */ + ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP; + if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data, + sizeof(struct rmnet_ioctl_data_s))) + rc = -EFAULT; + break; + case RMNET_IOCTL_GET_OPMODE: /* get operation mode */ + ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP; + if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data, + sizeof(struct rmnet_ioctl_data_s))) + rc = -EFAULT; + break; + case RMNET_IOCTL_SET_QOS_ENABLE: + rc = -EINVAL; + break; + case RMNET_IOCTL_SET_QOS_DISABLE: + rc = 0; + break; + case RMNET_IOCTL_OPEN: + case RMNET_IOCTL_CLOSE: + /* we just ignore them and return success */ + rc = 0; + break; + case RMNET_IOCTL_EXTENDED: + rc = mhi_netdev_ioctl_extended(dev, ifr); + break; + default: + /* don't fail any IOCTL right now */ + rc = 0; + break; + } + + return rc; +} + +static const struct net_device_ops mhi_netdev_ops_ip = { + .ndo_open = mhi_netdev_open, + .ndo_start_xmit = mhi_netdev_xmit, + .ndo_do_ioctl = mhi_netdev_ioctl, + .ndo_change_mtu = mhi_netdev_change_mtu, + .ndo_set_mac_address = 0, + .ndo_validate_addr = 0, +}; + +static void mhi_netdev_setup(struct net_device *dev) +{ + dev->netdev_ops = &mhi_netdev_ops_ip; + ether_setup(dev); + + /* set this after calling ether_setup */ + dev->header_ops = 0; /* No header */ + dev->type = ARPHRD_RAWIP; + dev->hard_header_len = 0; + dev->addr_len = 0; + dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST); + dev->watchdog_timeo = WATCHDOG_TIMEOUT; +} + +/* enable mhi_netdev netdev, call only after grabbing mhi_netdev.mutex */ +static int mhi_netdev_enable_iface(struct mhi_netdev *mhi_netdev) +{ + int ret = 0; + char ifname[IFNAMSIZ]; + struct mhi_device *mhi_dev = mhi_netdev->mhi_dev; + struct mhi_netdev_priv *mhi_netdev_priv; + + snprintf(ifname, sizeof(ifname), "%s%%d", mhi_netdev->interface_name); + + rtnl_lock(); + mhi_netdev->ndev = alloc_netdev(sizeof(*mhi_netdev_priv), + ifname, NET_NAME_PREDICTABLE, + mhi_netdev_setup); + if (!mhi_netdev->ndev) { + rtnl_unlock(); + return -ENOMEM; + } + + mhi_netdev->ndev->mtu = mhi_dev->mhi_cntrl->buffer_len; + + SET_NETDEV_DEV(mhi_netdev->ndev, &mhi_dev->dev); + mhi_netdev_priv = netdev_priv(mhi_netdev->ndev); + mhi_netdev_priv->mhi_netdev = mhi_netdev; + rtnl_unlock(); + + mhi_netdev->napi = devm_kzalloc(&mhi_dev->dev, + sizeof(*mhi_netdev->napi), GFP_KERNEL); + if (!mhi_netdev->napi) { + ret = -ENOMEM; + goto napi_alloc_fail; + } + + netif_napi_add(mhi_netdev->ndev, mhi_netdev->napi, + mhi_netdev_poll, MHI_NETDEV_NAPI_POLL_WEIGHT); + + ret = register_netdev(mhi_netdev->ndev); + if (ret) { + MSG_ERR("Network device registration failed\n"); + goto net_dev_reg_fail; + } + + napi_enable(mhi_netdev->napi); + + MSG_LOG("Exited\n"); + + return 0; + +net_dev_reg_fail: + netif_napi_del(mhi_netdev->napi); + +napi_alloc_fail: + free_netdev(mhi_netdev->ndev); + mhi_netdev->ndev = NULL; + + return ret; +} + +static void mhi_netdev_xfer_ul_cb(struct mhi_device *mhi_dev, + struct mhi_result *mhi_result) +{ + struct mhi_netdev *mhi_netdev = dev_get_drvdata(&mhi_dev->dev); + struct sk_buff *skb = mhi_result->buf_addr; + struct net_device *ndev = mhi_netdev->ndev; + + ndev->stats.tx_packets++; + ndev->stats.tx_bytes += skb->len; + dev_kfree_skb(skb); + + if (netif_queue_stopped(ndev)) + netif_wake_queue(ndev); +} + +static void mhi_netdev_push_skb(struct mhi_netdev *mhi_netdev, + struct mhi_buf *mhi_buf, + struct mhi_result *mhi_result) +{ + struct sk_buff *skb; + struct mhi_netbuf *netbuf; + + netbuf = (struct mhi_netbuf *)mhi_buf; + + skb = alloc_skb(0, GFP_ATOMIC); + if (!skb) { + __free_pages(netbuf->page, mhi_netdev->order); + return; + } + + skb_add_rx_frag(skb, 0, netbuf->page, 0, + mhi_result->bytes_xferd, mhi_netdev->mru); + skb->dev = mhi_netdev->ndev; + skb->protocol = mhi_netdev_ip_type_trans(*(u8 *)mhi_buf->buf); + netif_receive_skb(skb); +} + +static void mhi_netdev_xfer_dl_cb(struct mhi_device *mhi_dev, + struct mhi_result *mhi_result) +{ + struct mhi_netdev *mhi_netdev = dev_get_drvdata(&mhi_dev->dev); + struct mhi_netbuf *netbuf = mhi_result->buf_addr; + struct mhi_buf *mhi_buf = &netbuf->mhi_buf; + struct sk_buff *skb; + struct net_device *ndev = mhi_netdev->ndev; + struct device *dev = mhi_dev->dev.parent->parent; + struct mhi_net_chain *chain = mhi_netdev->chain; + + netbuf->unmap(dev, mhi_buf->dma_addr, mhi_buf->len, DMA_FROM_DEVICE); + if (likely(netbuf->recycle)) + list_add_tail(&netbuf->node, mhi_netdev->recycle_pool); + + /* modem is down, drop the buffer */ + if (mhi_result->transaction_status == -ENOTCONN) { + __free_pages(netbuf->page, mhi_netdev->order); + return; + } + + ndev->stats.rx_packets++; + ndev->stats.rx_bytes += mhi_result->bytes_xferd; + + if (unlikely(!chain)) { + mhi_netdev_push_skb(mhi_netdev, mhi_buf, mhi_result); + return; + } + + /* we support chaining */ + skb = alloc_skb(0, GFP_ATOMIC); + if (likely(skb)) { + skb_add_rx_frag(skb, 0, netbuf->page, 0, + mhi_result->bytes_xferd, mhi_netdev->mru); + /* this is first on list */ + if (!chain->head) { + skb->dev = ndev; + skb->protocol = + mhi_netdev_ip_type_trans(*(u8 *)mhi_buf->buf); + chain->head = skb; + } else { + skb_shinfo(chain->tail)->frag_list = skb; + } + + chain->tail = skb; + } else { + __free_pages(netbuf->page, mhi_netdev->order); + } +} + +static void mhi_netdev_status_cb(struct mhi_device *mhi_dev, + enum mhi_callback mhi_cb) +{ + struct mhi_netdev *mhi_netdev = dev_get_drvdata(&mhi_dev->dev); + + if (mhi_cb != MHI_CB_PENDING_DATA) + return; + + napi_schedule(mhi_netdev->napi); + mhi_netdev->napi_scheduled = true; +} + +#ifdef CONFIG_DEBUG_FS + +struct dentry *dentry; + +static int mhi_netdev_debugfs_stats_show(struct seq_file *m, void *d) +{ + struct mhi_netdev *mhi_netdev = m->private; + + seq_printf(m, + "mru:%u order:%u pool_size:%d, bg_pool_size:%d bg_pool_limit:%d abuf:%u kbuf:%u rbuf:%u\n", + mhi_netdev->mru, mhi_netdev->order, mhi_netdev->pool_size, + mhi_netdev->bg_pool_size, mhi_netdev->bg_pool_limit, + mhi_netdev->abuffers, mhi_netdev->kbuffers, + mhi_netdev->rbuffers); + + seq_printf(m, "chaining SKBs:%s\n", (mhi_netdev->chain) ? + "enabled" : "disabled"); + + return 0; +} + +static int mhi_netdev_debugfs_stats_open(struct inode *inode, struct file *fp) +{ + return single_open(fp, mhi_netdev_debugfs_stats_show, inode->i_private); +} + +static const struct file_operations debugfs_stats = { + .open = mhi_netdev_debugfs_stats_open, + .release = single_release, + .read = seq_read, +}; + +static int mhi_netdev_debugfs_chain(void *data, u64 val) +{ + struct mhi_netdev *mhi_netdev = data; + struct mhi_netdev *rsc_dev = mhi_netdev->rsc_dev; + + mhi_netdev->chain = NULL; + + if (rsc_dev) + rsc_dev->chain = NULL; + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(debugfs_chain, NULL, + mhi_netdev_debugfs_chain, "%llu\n"); + +static void mhi_netdev_create_debugfs(struct mhi_netdev *mhi_netdev) +{ + char node_name[40]; + struct mhi_device *mhi_dev = mhi_netdev->mhi_dev; + + /* Both tx & rx client handle contain same device info */ + snprintf(node_name, sizeof(node_name), "%s_%s", dev_name(&mhi_dev->dev), + mhi_netdev->interface_name); + + if (IS_ERR_OR_NULL(dentry)) + return; + + mhi_netdev->dentry = debugfs_create_dir(node_name, dentry); + if (IS_ERR_OR_NULL(mhi_netdev->dentry)) + return; + + debugfs_create_file_unsafe("stats", 0444, mhi_netdev->dentry, + mhi_netdev, &debugfs_stats); + debugfs_create_file_unsafe("chain", 0444, mhi_netdev->dentry, + mhi_netdev, &debugfs_chain); +} + +static void mhi_netdev_create_debugfs_dir(void) +{ + dentry = debugfs_create_dir(MHI_NETDEV_DRIVER_NAME, 0); +} + +#else + +static void mhi_netdev_create_debugfs(struct mhi_netdev *mhi_netdev) +{ +} + +static void mhi_netdev_create_debugfs_dir(void) +{ +} + +#endif + +static ssize_t log_level_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct mhi_device *mhi_dev = to_mhi_device(dev); + struct mhi_netdev *mhi_netdev = dev_get_drvdata(&mhi_dev->dev); + + if (!mhi_netdev) + return -EIO; + + return scnprintf(buf, PAGE_SIZE, + "MHI network device IPC log level begins from: %s\n", + MHI_NETDEV_LOG_LEVEL_STR(mhi_netdev->msg_lvl)); +} + +static ssize_t log_level_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct mhi_device *mhi_dev = to_mhi_device(dev); + struct mhi_netdev *mhi_netdev = dev_get_drvdata(&mhi_dev->dev); + enum MHI_DEBUG_LEVEL log_level; + + if (kstrtou32(buf, 0, &log_level) < 0) + return -EINVAL; + + if (!mhi_netdev) + return -EIO; + + mhi_netdev->msg_lvl = log_level; + + /* set level for parent if RSC child netdev and vice versa */ + if (mhi_netdev->is_rsc_dev) + mhi_netdev->rsc_parent->msg_lvl = log_level; + else if (mhi_netdev->rsc_dev) + mhi_netdev->rsc_dev->msg_lvl = log_level; + + MSG_LOG("MHI Network device IPC log level changed to: %s\n", + MHI_NETDEV_LOG_LEVEL_STR(log_level)); + + return count; +} +static DEVICE_ATTR_RW(log_level); + +static struct attribute *mhi_netdev_attrs[] = { + &dev_attr_log_level.attr, + NULL, +}; + +static const struct attribute_group mhi_netdev_group = { + .attrs = mhi_netdev_attrs, +}; + +static void mhi_netdev_remove(struct mhi_device *mhi_dev) +{ + struct mhi_netdev *mhi_netdev = dev_get_drvdata(&mhi_dev->dev); + + MSG_LOG("Remove notification received\n"); + + /* rsc parent takes cares of the cleanup except buffer pool */ + if (mhi_netdev->is_rsc_dev) { + mhi_netdev_free_pool(mhi_netdev); + return; + } + + sysfs_remove_group(&mhi_dev->dev.kobj, &mhi_netdev_group); + kthread_stop(mhi_netdev->alloc_task); + netif_stop_queue(mhi_netdev->ndev); + napi_disable(mhi_netdev->napi); + unregister_netdev(mhi_netdev->ndev); + netif_napi_del(mhi_netdev->napi); + free_netdev(mhi_netdev->ndev); + mhi_netdev->ndev = NULL; + + if (!IS_ERR_OR_NULL(mhi_netdev->dentry)) + debugfs_remove_recursive(mhi_netdev->dentry); + + if (!mhi_netdev->rsc_parent) + mhi_netdev_free_pool(mhi_netdev); +} + +static void mhi_netdev_clone_dev(struct mhi_netdev *mhi_netdev, + struct mhi_netdev *parent) +{ + mhi_netdev->ndev = parent->ndev; + mhi_netdev->napi = parent->napi; + mhi_netdev->ipc_log = parent->ipc_log; + mhi_netdev->msg_lvl = parent->msg_lvl; + mhi_netdev->is_rsc_dev = true; + mhi_netdev->chain = parent->chain; + mhi_netdev->rsc_parent = parent; + mhi_netdev->recycle_pool = parent->recycle_pool; + mhi_netdev->bg_pool = parent->bg_pool; +} + +static int mhi_netdev_probe(struct mhi_device *mhi_dev, + const struct mhi_device_id *id) +{ + struct mhi_netdev *mhi_netdev; + struct mhi_netdev_driver_data *data; + char node_name[40]; + int nr_tre, ret; + + data = (struct mhi_netdev_driver_data *)id->driver_data; + + mhi_netdev = devm_kzalloc(&mhi_dev->dev, sizeof(*mhi_netdev), + GFP_KERNEL); + if (!mhi_netdev) + return -ENOMEM; + + /* move mhi channels to start state */ + ret = mhi_prepare_for_transfer(mhi_dev, 0); + if (ret) { + MSG_ERR("Failed to start channels, ret: %d\n", ret); + return ret; + } + + mhi_netdev->mhi_dev = mhi_dev; + dev_set_drvdata(&mhi_dev->dev, mhi_netdev); + + mhi_netdev->mru = data->mru; + mhi_netdev->rsc_parent = data->has_rsc_child ? mhi_netdev : NULL; + mhi_netdev->rsc_dev = data->is_rsc_chan ? mhi_netdev : NULL; + + /* MRU must be multiplication of page size */ + mhi_netdev->order = __ilog2_u32(mhi_netdev->mru / PAGE_SIZE); + if ((PAGE_SIZE << mhi_netdev->order) < mhi_netdev->mru) + return -EINVAL; + + if (data->is_rsc_chan) { + if (!rsc_parent_netdev || !rsc_parent_netdev->ndev) + return -ENODEV; + + /* this device is shared with parent device. so we won't be + * creating a new network interface. Clone parent + * information to child node + */ + mhi_netdev_clone_dev(mhi_netdev, rsc_parent_netdev); + } else { + mhi_netdev->msg_lvl = MHI_NETDEV_LOG_LVL; + + ret = sysfs_create_group(&mhi_dev->dev.kobj, &mhi_netdev_group); + if (ret) + MSG_ERR("Failed to create MHI netdev sysfs group\n"); + + if (data->chain_skb) { + mhi_netdev->chain = devm_kzalloc(&mhi_dev->dev, + sizeof(*mhi_netdev->chain), + GFP_KERNEL); + if (!mhi_netdev->chain) + return -ENOMEM; + } + + mhi_netdev->interface_name = data->interface_name; + + ret = mhi_netdev_enable_iface(mhi_netdev); + if (ret) + return ret; + + /* setup pool size ~2x ring length*/ + nr_tre = mhi_get_free_desc_count(mhi_dev, DMA_FROM_DEVICE); + mhi_netdev->pool_size = 1 << __ilog2_u32(nr_tre); + if (nr_tre > mhi_netdev->pool_size) + mhi_netdev->pool_size <<= 1; + mhi_netdev->pool_size <<= 1; + + /* if we expect child device to share then double the pool */ + if (data->has_rsc_child) + mhi_netdev->pool_size <<= 1; + + /* allocate memory pool */ + ret = mhi_netdev_alloc_pool(mhi_netdev); + if (ret) + return -ENOMEM; + + /* create a background task to allocate memory */ + mhi_netdev->bg_pool = kmalloc(sizeof(*mhi_netdev->bg_pool), + GFP_KERNEL); + if (!mhi_netdev->bg_pool) + return -ENOMEM; + + init_waitqueue_head(&mhi_netdev->alloc_event); + INIT_LIST_HEAD(mhi_netdev->bg_pool); + spin_lock_init(&mhi_netdev->bg_lock); + mhi_netdev->bg_pool_limit = mhi_netdev->pool_size / 4; + mhi_netdev->alloc_task = kthread_run(mhi_netdev_alloc_thread, + mhi_netdev, + mhi_netdev->ndev->name); + if (IS_ERR(mhi_netdev->alloc_task)) + return PTR_ERR(mhi_netdev->alloc_task); + + rsc_parent_netdev = mhi_netdev; + + /* create ipc log buffer */ + snprintf(node_name, sizeof(node_name), + "%s_%s", dev_name(&mhi_dev->dev), + mhi_netdev->interface_name); + + mhi_netdev->ipc_log = ipc_log_context_create(IPC_LOG_PAGES, + node_name, 0); + + mhi_netdev_create_debugfs(mhi_netdev); + } + + /* now we have a pool of buffers allocated, queue to hardware + * by triggering a napi_poll + */ + napi_schedule(mhi_netdev->napi); + mhi_netdev->napi_scheduled = true; + + return 0; +} + +static const struct mhi_netdev_driver_data hw0_308_data = { + .mru = 0x8000, + .chain_skb = true, + .is_rsc_chan = false, + .has_rsc_child = false, + .interface_name = "rmnet_mhi", +}; + +static const struct mhi_device_id mhi_netdev_match_table[] = { + { .chan = "IP_HW0", .driver_data = (kernel_ulong_t)&hw0_308_data }, + {}, +}; + +static struct mhi_driver mhi_netdev_driver = { + .id_table = mhi_netdev_match_table, + .probe = mhi_netdev_probe, + .remove = mhi_netdev_remove, + .ul_xfer_cb = mhi_netdev_xfer_ul_cb, + .dl_xfer_cb = mhi_netdev_xfer_dl_cb, + .status_cb = mhi_netdev_status_cb, + .driver = { + .name = "mhi_netdev", + .owner = THIS_MODULE, + } +}; + +static int __init mhi_netdev_init(void) +{ + BUILD_BUG_ON(sizeof(struct mhi_netbuf) > MAX_NETBUF_SIZE); + mhi_netdev_create_debugfs_dir(); + + return mhi_driver_register(&mhi_netdev_driver); +} +module_init(mhi_netdev_init); + +static void __exit mhi_netdev_exit(void) +{ + debugfs_remove_recursive(dentry); + + mhi_driver_unregister(&mhi_netdev_driver); +} +module_exit(mhi_netdev_exit); + +MODULE_DESCRIPTION("MHI NETDEV Network Interface"); +MODULE_LICENSE("GPL"); diff --git a/drivers/bus/mhi/devices/mhi_satellite.c b/drivers/bus/mhi/devices/mhi_satellite.c new file mode 100644 index 000000000000..1ce6a6a865e8 --- /dev/null +++ b/drivers/bus/mhi/devices/mhi_satellite.c @@ -0,0 +1,1232 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MHI_SAT_DRIVER_NAME "mhi_satellite" + +/* logging macros */ +#define IPC_LOG_PAGES (10) + +#ifdef CONFIG_MHI_BUS_DEBUG +#define MHI_SAT_LOG_LVL MHI_MSG_LVL_VERBOSE +#else +#define MHI_SAT_LOG_LVL MHI_MSG_LVL_ERROR +#endif + +#define MSG_SUBSYS_LOG(fmt, ...) do { \ + if (!subsys) \ + break; \ + if (mhi_sat_driver.ipc_log_lvl <= MHI_MSG_LVL_INFO) \ + ipc_log_string(subsys->ipc_log, "%s[I][%s] " fmt, \ + "", __func__, ##__VA_ARGS__); \ +} while (0) + +#define MSG_LOG(fmt, ...) do { \ + if (!subsys || !sat_cntrl) \ + break; \ + if (mhi_sat_driver.ipc_log_lvl <= MHI_MSG_LVL_INFO) \ + ipc_log_string(subsys->ipc_log, "%s[I][%s][%x] " fmt, \ + "", __func__, sat_cntrl->dev_id, \ + ##__VA_ARGS__); \ +} while (0) + +#define MSG_ERR(fmt, ...) do { \ + if (!subsys || !sat_cntrl) \ + break; \ + pr_err("[E][%s][%s][%x] " fmt, __func__, subsys->name, \ + sat_cntrl->dev_id, ##__VA_ARGS__);\ + if (mhi_sat_driver.ipc_log_lvl <= MHI_MSG_LVL_ERROR) \ + ipc_log_string(subsys->ipc_log, "%s[E][%s][%x] " fmt, \ + "", __func__, sat_cntrl->dev_id, \ + ##__VA_ARGS__); \ +} while (0) + +const char * const mhi_log_level_str[MHI_MSG_LVL_MAX] = { + [MHI_MSG_LVL_VERBOSE] = "Verbose", + [MHI_MSG_LVL_INFO] = "Info", + [MHI_MSG_LVL_ERROR] = "Error", + [MHI_MSG_LVL_CRITICAL] = "Critical", + [MHI_MSG_LVL_MASK_ALL] = "Mask all", +}; +#define MSG_LOG_LEVEL_STR(level) ((level >= MHI_MSG_LVL_MAX || \ + !mhi_log_level_str[level]) ? \ + "Mask all" : mhi_log_level_str[level]) + +/* mhi sys error command */ +#define MHI_TRE_CMD_SYS_ERR_PTR (0) +#define MHI_TRE_CMD_SYS_ERR_D0 (0) +#define MHI_TRE_CMD_SYS_ERR_D1 (MHI_PKT_TYPE_SYS_ERR_CMD << 16) + +/* mhi state change event */ +#define MHI_TRE_EVT_MHI_STATE_PTR (0) +#define MHI_TRE_EVT_MHI_STATE_D0(state) (state << 24) +#define MHI_TRE_EVT_MHI_STATE_D1 (MHI_PKT_TYPE_STATE_CHANGE_EVENT << 16) + +/* mhi exec env change event */ +#define MHI_TRE_EVT_EE_PTR (0) +#define MHI_TRE_EVT_EE_D0(ee) (ee << 24) +#define MHI_TRE_EVT_EE_D1 (MHI_PKT_TYPE_EE_EVENT << 16) + +/* mhi config event */ +#define MHI_TRE_EVT_CFG_PTR(base_addr) (base_addr) +#define MHI_TRE_EVT_CFG_D0(er_base, num) ((er_base << 16) | (num & 0xFFFF)) +#define MHI_TRE_EVT_CFG_D1 (MHI_PKT_TYPE_CFG_EVENT << 16) + +/* command completion event */ +#define MHI_TRE_EVT_CMD_COMPLETION_PTR(ptr) (ptr) +#define MHI_TRE_EVT_CMD_COMPLETION_D0(code) (code << 24) +#define MHI_TRE_EVT_CMD_COMPLETION_D1 (MHI_PKT_TYPE_CMD_COMPLETION_EVENT << 16) + +/* packet parser macros */ +#define MHI_TRE_GET_PTR(tre) ((tre)->ptr) +#define MHI_TRE_GET_SIZE(tre) ((tre)->dword[0]) +#define MHI_TRE_GET_CCS(tre) (((tre)->dword[0] >> 24) & 0xFF) +#define MHI_TRE_GET_ID(tre) (((tre)->dword[1] >> 24) & 0xFF) +#define MHI_TRE_GET_TYPE(tre) (((tre)->dword[1] >> 16) & 0xFF) +#define MHI_TRE_IS_ER_CTXT_TYPE(tre) (((tre)->dword[1]) & 0x1) +#define MHI_TRE_IS_IO_ADDR_TYPE(tre) (((tre)->dword[1]) & 0x1) + +/* mhi core definitions */ +#define MHI_CTXT_TYPE_GENERIC (0xA) + +struct __packed mhi_generic_ctxt { + u32 reserved0; + u32 type; + u32 reserved1; + u64 ctxt_base; + u64 ctxt_size; + u64 reserved[2]; +}; + +enum mhi_pkt_type { + MHI_PKT_TYPE_INVALID = 0x0, + MHI_PKT_TYPE_RESET_CHAN_CMD = 0x10, + MHI_PKT_TYPE_STOP_CHAN_CMD = 0x11, + MHI_PKT_TYPE_START_CHAN_CMD = 0x12, + MHI_PKT_TYPE_STATE_CHANGE_EVENT = 0x20, + MHI_PKT_TYPE_CMD_COMPLETION_EVENT = 0x21, + MHI_PKT_TYPE_EE_EVENT = 0x40, + MHI_PKT_TYPE_CTXT_UPDATE_CMD = 0x64, + MHI_PKT_TYPE_IOMMU_MAP_CMD = 0x65, + MHI_PKT_TYPE_CFG_EVENT = 0x6E, + MHI_PKT_TYPE_SYS_ERR_CMD = 0xFF, +}; + +enum mhi_cmd_type { + MHI_CMD_TYPE_RESET = 0x10, + MHI_CMD_TYPE_STOP = 0x11, + MHI_CMD_TYPE_START = 0x12, +}; + +/* mhi event completion codes */ +enum mhi_ev_ccs { + MHI_EV_CC_INVALID = 0x0, + MHI_EV_CC_SUCCESS = 0x1, + MHI_EV_CC_BAD_TRE = 0x11, +}; + +/* satellite subsystem definitions */ +enum subsys_id { + SUBSYS_ADSP, + SUBSYS_CDSP, + SUBSYS_SLPI, + SUBSYS_MAX, +}; + +static const char * const subsys_names[SUBSYS_MAX] = { + [SUBSYS_ADSP] = "adsp", + [SUBSYS_CDSP] = "cdsp", + [SUBSYS_SLPI] = "slpi", +}; + +struct mhi_sat_subsys { + const char *name; + + struct rpmsg_device *rpdev; /* rpmsg device */ + + /* + * acquire either mutex or spinlock to walk controller list + * acquire both when modifying list + */ + struct list_head cntrl_list; /* controllers list */ + struct mutex cntrl_mutex; /* mutex to walk/modify controllers list */ + spinlock_t cntrl_lock; /* lock to walk/modify controllers list */ + + void *ipc_log; +}; + +/* satellite IPC definitions */ +#define SAT_MAJOR_VERSION (1) +#define SAT_MINOR_VERSION (0) +#define SAT_RESERVED_SEQ_NUM (0xFFFF) +#define SAT_MSG_SIZE(n) (sizeof(struct sat_header) + \ + (n * sizeof(struct sat_tre))) +#define SAT_TRE_SIZE(msg_size) (msg_size - sizeof(struct sat_header)) +#define SAT_TRE_OFFSET(msg) (msg + sizeof(struct sat_header)) +#define SAT_TRE_NUM_PKTS(payload_size) ((payload_size) / sizeof(struct sat_tre)) + +/* satellite IPC msg type */ +enum sat_msg_id { + SAT_MSG_ID_ACK = 0xA, + SAT_MSG_ID_CMD = 0xC, + SAT_MSG_ID_EVT = 0xE, +}; + +/* satellite IPC context type */ +enum sat_ctxt_type { + SAT_CTXT_TYPE_CHAN = 0x0, + SAT_CTXT_TYPE_EVENT = 0x1, + SAT_CTXT_TYPE_MAX, +}; + +/* satellite IPC context string */ +#define TO_SAT_CTXT_TYPE_STR(type) (type >= SAT_CTXT_TYPE_MAX ? "INVALID" : \ + sat_ctxt_str[type]) + +const char * const sat_ctxt_str[SAT_CTXT_TYPE_MAX] = { + [SAT_CTXT_TYPE_CHAN] = "CCA", + [SAT_CTXT_TYPE_EVENT] = "ECA", +}; + +/* satellite IPC transfer ring element */ +struct __packed sat_tre { + u64 ptr; + u32 dword[2]; +}; + +/* satellite IPC header */ +struct __packed sat_header { + u16 major_ver; + u16 minor_ver; + u16 msg_id; + u16 seq; + u16 reply_seq; + u16 payload_size; + u32 dev_id; + u8 reserved[8]; +}; + +/* satellite driver definitions */ +struct mhi_sat_packet { + struct list_head node; + + struct mhi_sat_cntrl *cntrl; /* satellite controller reference */ + void *msg; /* incoming message */ +}; + +enum mhi_sat_state { + SAT_READY, /* initial state when device is presented to driver */ + SAT_RUNNING, /* subsystem can communicate with the device */ + SAT_DISCONNECTED, /* rpmsg link is down */ + SAT_FATAL_DETECT, /* device is down as fatal error was detected early */ + SAT_ERROR, /* device is down after error or graceful shutdown */ + SAT_DISABLED, /* no further processing: wait for device removal */ +}; + +#define MHI_SAT_MAX_DEVICES 4 +#define MHI_SAT_ACTIVE(cntrl) (cntrl->state == SAT_RUNNING) +#define MHI_SAT_IN_ERROR_STATE(cntrl) (cntrl->state >= SAT_FATAL_DETECT) +#define MHI_SAT_ALLOW_CONNECTION(cntrl) (cntrl->state == SAT_READY || \ + cntrl->state == SAT_DISCONNECTED) +#define MHI_SAT_ALLOW_SYS_ERR(cntrl) (cntrl->state == SAT_RUNNING || \ + cntrl->state == SAT_FATAL_DETECT) + +struct mhi_sat_cntrl { + struct list_head node; + + struct mhi_controller *mhi_cntrl; /* device MHI controller reference */ + struct mhi_sat_subsys *subsys; + + struct list_head dev_list; + struct list_head addr_map_list; /* IOMMU mapped addresses list */ + struct mutex list_mutex; /* mutex for devices and address map lists */ + + struct list_head packet_list; + spinlock_t pkt_lock; /* lock to walk/modify received packets list */ + + struct work_struct connect_work; /* subsystem connection worker */ + struct work_struct process_work; /* incoming packets processor */ + struct work_struct error_work; /* error handling processor */ + + /* mhi core/controller configurations */ + u32 dev_id; /* unique device ID with BDF as per connection topology */ + int er_base; /* event rings base index */ + int er_max; /* event rings max index */ + int num_er; /* total number of event rings */ + + /* satellite controller function counts */ + int num_devices; /* mhi devices current count */ + int max_devices; /* count of maximum devices for subsys/controller */ + u16 seq; /* internal sequence number for all outgoing packets */ + enum mhi_sat_state state; /* controller state manager */ + spinlock_t state_lock; /* lock to change controller state */ + + /* command completion variables */ + u16 last_cmd_seq; /* sequence number of last sent command packet */ + enum mhi_ev_ccs last_cmd_ccs; /* last command completion event code */ + struct completion completion; /* command completion event wait */ + struct mutex cmd_wait_mutex; /* command completion wait mutex */ +}; + +struct mhi_sat_device { + struct list_head node; + + struct mhi_device *mhi_dev; /* mhi device pointer */ + struct mhi_sat_cntrl *cntrl; /* parent controller */ + + bool chan_started; +}; + +struct mhi_sat_driver { + enum MHI_DEBUG_LEVEL ipc_log_lvl; /* IPC log level */ + + struct mhi_sat_subsys *subsys; /* pointer to subsystem array */ + unsigned int num_subsys; +}; + +static struct mhi_sat_driver mhi_sat_driver; + +static struct mhi_sat_subsys *find_subsys_by_name(const char *name) +{ + int i; + struct mhi_sat_subsys *subsys = mhi_sat_driver.subsys; + + for (i = 0; i < mhi_sat_driver.num_subsys; i++, subsys++) { + if (!strcmp(name, subsys->name)) + return subsys; + } + + return NULL; +} + +static struct mhi_sat_cntrl *find_sat_cntrl_by_id(struct mhi_sat_subsys *subsys, + u32 dev_id) +{ + struct mhi_sat_cntrl *sat_cntrl; + unsigned long flags; + + spin_lock_irqsave(&subsys->cntrl_lock, flags); + list_for_each_entry(sat_cntrl, &subsys->cntrl_list, node) { + if (sat_cntrl->dev_id == dev_id) { + spin_unlock_irqrestore(&subsys->cntrl_lock, flags); + return sat_cntrl; + } + } + spin_unlock_irqrestore(&subsys->cntrl_lock, flags); + + return NULL; +} + +static struct mhi_sat_device *find_sat_dev_by_id( + struct mhi_sat_cntrl *sat_cntrl, int id, + enum sat_ctxt_type evt) +{ + struct mhi_sat_device *sat_dev; + int compare_id; + + mutex_lock(&sat_cntrl->list_mutex); + list_for_each_entry(sat_dev, &sat_cntrl->dev_list, node) { + compare_id = (evt == SAT_CTXT_TYPE_EVENT) ? + sat_dev->mhi_dev->dl_event_id : + sat_dev->mhi_dev->dl_chan_id; + + if (compare_id == id) { + mutex_unlock(&sat_cntrl->list_mutex); + return sat_dev; + } + } + mutex_unlock(&sat_cntrl->list_mutex); + + return NULL; +} + +static bool mhi_sat_isvalid_header(struct sat_header *hdr, int len) +{ + /* validate payload size */ + if (len >= sizeof(*hdr) && (len != hdr->payload_size + sizeof(*hdr))) + return false; + + /* validate SAT IPC version */ + if (hdr->major_ver != SAT_MAJOR_VERSION || + hdr->minor_ver != SAT_MINOR_VERSION) + return false; + + /* validate msg ID */ + if (hdr->msg_id != SAT_MSG_ID_CMD && hdr->msg_id != SAT_MSG_ID_EVT) + return false; + + return true; +} + +static int mhi_sat_wait_cmd_completion(struct mhi_sat_cntrl *sat_cntrl) +{ + struct mhi_sat_subsys *subsys = sat_cntrl->subsys; + int ret; + + reinit_completion(&sat_cntrl->completion); + + MSG_LOG("Wait for command completion\n"); + ret = wait_for_completion_timeout(&sat_cntrl->completion, + msecs_to_jiffies(sat_cntrl->mhi_cntrl->timeout_ms)); + if (!ret || sat_cntrl->last_cmd_ccs != MHI_EV_CC_SUCCESS) { + MSG_ERR("Command completion failure:seq:%u:ret:%d:ccs:%d\n", + sat_cntrl->last_cmd_seq, ret, sat_cntrl->last_cmd_ccs); + return -EIO; + } + + MSG_LOG("Command completion successful for seq:%u\n", + sat_cntrl->last_cmd_seq); + + return 0; +} + +static int mhi_sat_send_msg(struct mhi_sat_cntrl *sat_cntrl, + enum sat_msg_id type, u16 reply_seq, + void *msg, u16 msg_size) +{ + struct mhi_sat_subsys *subsys = sat_cntrl->subsys; + struct sat_header *hdr = msg; + + /* create sequence number for controller */ + sat_cntrl->seq++; + if (sat_cntrl->seq == SAT_RESERVED_SEQ_NUM) + sat_cntrl->seq = 0; + + /* populate header */ + hdr->major_ver = SAT_MAJOR_VERSION; + hdr->minor_ver = SAT_MINOR_VERSION; + hdr->msg_id = type; + hdr->seq = sat_cntrl->seq; + hdr->reply_seq = reply_seq; + hdr->payload_size = SAT_TRE_SIZE(msg_size); + hdr->dev_id = sat_cntrl->dev_id; + + /* save last sent command sequence number for completion event */ + if (type == SAT_MSG_ID_CMD) + sat_cntrl->last_cmd_seq = sat_cntrl->seq; + + return rpmsg_send(subsys->rpdev->ept, msg, msg_size); +} + +static void mhi_sat_process_cmds(struct mhi_sat_cntrl *sat_cntrl, + struct sat_header *hdr, struct sat_tre *pkt) +{ + struct mhi_sat_subsys *subsys = sat_cntrl->subsys; + int num_pkts = SAT_TRE_NUM_PKTS(hdr->payload_size), i; + + for (i = 0; i < num_pkts; i++, pkt++) { + enum mhi_ev_ccs code = MHI_EV_CC_INVALID; + + switch (MHI_TRE_GET_TYPE(pkt)) { + case MHI_PKT_TYPE_IOMMU_MAP_CMD: + { + struct mhi_buf_extended *buf; + struct mhi_controller *mhi_cntrl = sat_cntrl->mhi_cntrl; + dma_addr_t iova = DMA_MAPPING_ERROR; + struct device *parent_dev = + mhi_cntrl->mhi_dev->dev.parent; + + buf = kmalloc(sizeof(*buf), GFP_ATOMIC); + if (!buf) + goto iommu_map_cmd_completion; + + buf->phys_addr = MHI_TRE_GET_PTR(pkt); + buf->len = MHI_TRE_GET_SIZE(pkt); + buf->is_io = MHI_TRE_IS_IO_ADDR_TYPE(pkt); + + if (buf->is_io) { + iova = dma_map_resource(parent_dev, + buf->phys_addr, buf->len, + DMA_BIDIRECTIONAL, 0); + } else { + /* + * DMA_ATTR_SKIP_CPU_SYNC used due to assumption + * CPU does not read/write this memory, and addr + * & size may not be aligned per CMO requirement + */ + iova = dma_map_single_attrs(parent_dev, + phys_to_virt(buf->phys_addr), + buf->len, DMA_BIDIRECTIONAL, + DMA_ATTR_SKIP_CPU_SYNC); + } + + if (dma_mapping_error(parent_dev, iova)) { + kfree(buf); + goto iommu_map_cmd_completion; + } + + buf->dma_addr = iova; + + mutex_lock(&sat_cntrl->list_mutex); + list_add_tail(&buf->node, + &sat_cntrl->addr_map_list); + mutex_unlock(&sat_cntrl->list_mutex); + + code = MHI_EV_CC_SUCCESS; + +iommu_map_cmd_completion: + MSG_LOG("IOMMU MAP 0x%llx len:%d CMD %s:%llx\n", + MHI_TRE_GET_PTR(pkt), MHI_TRE_GET_SIZE(pkt), + (code == MHI_EV_CC_SUCCESS) ? "successful" : + "failed", iova); + + pkt->ptr = MHI_TRE_EVT_CMD_COMPLETION_PTR(iova); + pkt->dword[0] = MHI_TRE_EVT_CMD_COMPLETION_D0(code); + pkt->dword[1] = MHI_TRE_EVT_CMD_COMPLETION_D1; + break; + } + case MHI_PKT_TYPE_CTXT_UPDATE_CMD: + { + u64 ctxt_ptr = MHI_TRE_GET_PTR(pkt); + u64 ctxt_size = MHI_TRE_GET_SIZE(pkt); + int id = MHI_TRE_GET_ID(pkt); + enum sat_ctxt_type evt = MHI_TRE_IS_ER_CTXT_TYPE(pkt); + struct mhi_generic_ctxt gen_ctxt; + struct mhi_buf buf; + struct mhi_sat_device *sat_dev = find_sat_dev_by_id( + sat_cntrl, id, evt); + int ret; + + WARN_ON(!sat_dev); + + memset(&gen_ctxt, 0, sizeof(gen_ctxt)); + memset(&buf, 0, sizeof(buf)); + + gen_ctxt.type = MHI_CTXT_TYPE_GENERIC; + gen_ctxt.ctxt_base = ctxt_ptr; + gen_ctxt.ctxt_size = ctxt_size; + + buf.buf = &gen_ctxt; + buf.len = sizeof(gen_ctxt); + buf.name = TO_SAT_CTXT_TYPE_STR(evt); + + ret = mhi_device_configure(sat_dev->mhi_dev, + DMA_BIDIRECTIONAL, &buf, 1); + if (!ret) + code = MHI_EV_CC_SUCCESS; + + MSG_LOG("CTXT UPDATE CMD %s:%d %s\n", buf.name, id, + (code == MHI_EV_CC_SUCCESS) ? "successful" : + "failed"); + + pkt->ptr = MHI_TRE_EVT_CMD_COMPLETION_PTR(0); + pkt->dword[0] = MHI_TRE_EVT_CMD_COMPLETION_D0(code); + pkt->dword[1] = MHI_TRE_EVT_CMD_COMPLETION_D1; + break; + } + case MHI_PKT_TYPE_START_CHAN_CMD: + { + int id = MHI_TRE_GET_ID(pkt); + struct mhi_sat_device *sat_dev = find_sat_dev_by_id( + sat_cntrl, id, + SAT_CTXT_TYPE_CHAN); + int ret; + + WARN_ON(!sat_dev); + WARN_ON(sat_dev->chan_started); + + ret = mhi_prepare_for_transfer(sat_dev->mhi_dev, 0); + if (!ret) { + sat_dev->chan_started = true; + code = MHI_EV_CC_SUCCESS; + } + + MSG_LOG("START CHANNEL %d CMD %s\n", id, + (code == MHI_EV_CC_SUCCESS) ? "successful" : + "failure"); + + pkt->ptr = MHI_TRE_EVT_CMD_COMPLETION_PTR(0); + pkt->dword[0] = MHI_TRE_EVT_CMD_COMPLETION_D0(code); + pkt->dword[1] = MHI_TRE_EVT_CMD_COMPLETION_D1; + break; + } + case MHI_PKT_TYPE_RESET_CHAN_CMD: + { + int id = MHI_TRE_GET_ID(pkt); + struct mhi_sat_device *sat_dev = + find_sat_dev_by_id(sat_cntrl, id, + SAT_CTXT_TYPE_CHAN); + + WARN_ON(!sat_dev); + WARN_ON(!sat_dev->chan_started); + + mhi_unprepare_from_transfer(sat_dev->mhi_dev); + sat_dev->chan_started = false; + + MSG_LOG("RESET CHANNEL %d CMD successful\n", id); + + pkt->ptr = MHI_TRE_EVT_CMD_COMPLETION_PTR(0); + pkt->dword[0] = MHI_TRE_EVT_CMD_COMPLETION_D0( + MHI_EV_CC_SUCCESS); + pkt->dword[1] = MHI_TRE_EVT_CMD_COMPLETION_D1; + break; + } + default: + MSG_ERR("Unhandled MHI satellite command!"); + break; + } + } +} + +/* send sys_err command to subsystem if device asserts or is powered off */ +static void mhi_sat_send_sys_err(struct mhi_sat_cntrl *sat_cntrl) +{ + struct mhi_sat_subsys *subsys = sat_cntrl->subsys; + struct sat_tre *pkt; + void *msg; + int ret; + + /* flush all pending work */ + flush_work(&sat_cntrl->connect_work); + flush_work(&sat_cntrl->process_work); + + msg = kmalloc(SAT_MSG_SIZE(1), GFP_KERNEL); + if (!msg) { + MSG_ERR("Unable to malloc for SYS_ERR message!\n"); + return; + } + + pkt = SAT_TRE_OFFSET(msg); + pkt->ptr = MHI_TRE_CMD_SYS_ERR_PTR; + pkt->dword[0] = MHI_TRE_CMD_SYS_ERR_D0; + pkt->dword[1] = MHI_TRE_CMD_SYS_ERR_D1; + + mutex_lock(&sat_cntrl->cmd_wait_mutex); + + ret = mhi_sat_send_msg(sat_cntrl, SAT_MSG_ID_CMD, + SAT_RESERVED_SEQ_NUM, msg, + SAT_MSG_SIZE(1)); + kfree(msg); + if (ret) { + MSG_ERR("Failed to notify SYS_ERR cmd\n"); + mutex_unlock(&sat_cntrl->cmd_wait_mutex); + return; + } + + MSG_LOG("SYS_ERR command sent\n"); + + /* blocking call to wait for command completion event */ + mhi_sat_wait_cmd_completion(sat_cntrl); + + mutex_unlock(&sat_cntrl->cmd_wait_mutex); +} + +static void mhi_sat_error_worker(struct work_struct *work) +{ + struct mhi_sat_cntrl *sat_cntrl = container_of(work, + struct mhi_sat_cntrl, error_work); + struct mhi_sat_subsys *subsys = sat_cntrl->subsys; + struct sat_tre *pkt; + void *msg; + int ret; + + MSG_LOG("Entered\n"); + + /* flush all pending work */ + flush_work(&sat_cntrl->connect_work); + flush_work(&sat_cntrl->process_work); + + msg = kmalloc(SAT_MSG_SIZE(1), GFP_KERNEL); + if (!msg) { + MSG_ERR("Unable to malloc for SYS_ERR message!\n"); + return; + } + + pkt = SAT_TRE_OFFSET(msg); + pkt->ptr = MHI_TRE_EVT_MHI_STATE_PTR; + pkt->dword[0] = MHI_TRE_EVT_MHI_STATE_D0(MHI_STATE_SYS_ERR); + pkt->dword[1] = MHI_TRE_EVT_MHI_STATE_D1; + + ret = mhi_sat_send_msg(sat_cntrl, SAT_MSG_ID_EVT, + SAT_RESERVED_SEQ_NUM, msg, + SAT_MSG_SIZE(1)); + kfree(msg); + + MSG_LOG("SYS_ERROR state change event send %s!\n", ret ? "failure" : + "success"); +} + +static void mhi_sat_process_worker(struct work_struct *work) +{ + struct mhi_sat_cntrl *sat_cntrl = container_of(work, + struct mhi_sat_cntrl, process_work); + struct mhi_sat_subsys *subsys = sat_cntrl->subsys; + struct mhi_sat_packet *packet, *tmp; + struct sat_header *hdr; + struct sat_tre *pkt; + LIST_HEAD(head); + + MSG_LOG("Entered\n"); + + spin_lock_irq(&sat_cntrl->pkt_lock); + list_splice_tail_init(&sat_cntrl->packet_list, &head); + spin_unlock_irq(&sat_cntrl->pkt_lock); + + list_for_each_entry_safe(packet, tmp, &head, node) { + hdr = packet->msg; + pkt = SAT_TRE_OFFSET(packet->msg); + + list_del(&packet->node); + + if (!MHI_SAT_ACTIVE(sat_cntrl)) + goto process_next; + + mhi_sat_process_cmds(sat_cntrl, hdr, pkt); + + /* send response event(s) */ + mhi_sat_send_msg(sat_cntrl, SAT_MSG_ID_EVT, hdr->seq, + packet->msg, + SAT_MSG_SIZE(SAT_TRE_NUM_PKTS( + hdr->payload_size))); + +process_next: + kfree(packet); + } + + MSG_LOG("Exited\n"); +} + +static void mhi_sat_connect_worker(struct work_struct *work) +{ + struct mhi_sat_cntrl *sat_cntrl = container_of(work, + struct mhi_sat_cntrl, connect_work); + struct mhi_sat_subsys *subsys = sat_cntrl->subsys; + phys_addr_t base_addr; + enum mhi_sat_state prev_state; + struct sat_tre *pkt; + void *msg; + int ret; + + spin_lock_irq(&sat_cntrl->state_lock); + if (!subsys->rpdev || sat_cntrl->max_devices != sat_cntrl->num_devices + || !(MHI_SAT_ALLOW_CONNECTION(sat_cntrl))) { + spin_unlock_irq(&sat_cntrl->state_lock); + return; + } + prev_state = sat_cntrl->state; + sat_cntrl->state = SAT_RUNNING; + spin_unlock_irq(&sat_cntrl->state_lock); + + MSG_LOG("Entered\n"); + + ret = mhi_controller_get_base(sat_cntrl->mhi_cntrl, &base_addr); + if (ret) { + MSG_ERR("Could not get controller base address\n"); + goto error_connect_work; + } + + msg = kmalloc(SAT_MSG_SIZE(3), GFP_ATOMIC); + if (!msg) + goto error_connect_work; + + pkt = SAT_TRE_OFFSET(msg); + + /* prepare #1 MHI_CFG HELLO event */ + pkt->ptr = MHI_TRE_EVT_CFG_PTR(base_addr); + pkt->dword[0] = MHI_TRE_EVT_CFG_D0(sat_cntrl->er_base, + sat_cntrl->num_er); + pkt->dword[1] = MHI_TRE_EVT_CFG_D1; + pkt++; + + /* prepare M0 event */ + pkt->ptr = MHI_TRE_EVT_MHI_STATE_PTR; + pkt->dword[0] = MHI_TRE_EVT_MHI_STATE_D0(MHI_STATE_M0); + pkt->dword[1] = MHI_TRE_EVT_MHI_STATE_D1; + pkt++; + + /* prepare AMSS event */ + pkt->ptr = MHI_TRE_EVT_EE_PTR; + pkt->dword[0] = MHI_TRE_EVT_EE_D0(MHI_EE_AMSS); + pkt->dword[1] = MHI_TRE_EVT_EE_D1; + + ret = mhi_sat_send_msg(sat_cntrl, SAT_MSG_ID_EVT, SAT_RESERVED_SEQ_NUM, + msg, SAT_MSG_SIZE(3)); + kfree(msg); + if (ret) { + MSG_ERR("Failed to send hello packet:%d\n", ret); + goto error_connect_work; + } + + MSG_LOG("Device 0x%x sent hello packet\n", sat_cntrl->dev_id); + + return; + +error_connect_work: + spin_lock_irq(&sat_cntrl->state_lock); + if (MHI_SAT_ACTIVE(sat_cntrl)) + sat_cntrl->state = prev_state; + spin_unlock_irq(&sat_cntrl->state_lock); +} + +static void mhi_sat_process_events(struct mhi_sat_cntrl *sat_cntrl, + struct sat_header *hdr, struct sat_tre *pkt) +{ + int num_pkts = SAT_TRE_NUM_PKTS(hdr->payload_size); + int i; + + for (i = 0; i < num_pkts; i++, pkt++) { + if (MHI_TRE_GET_TYPE(pkt) == + MHI_PKT_TYPE_CMD_COMPLETION_EVENT) { + if (hdr->reply_seq != sat_cntrl->last_cmd_seq) + continue; + + sat_cntrl->last_cmd_ccs = MHI_TRE_GET_CCS(pkt); + complete(&sat_cntrl->completion); + } + } +} + +static int mhi_sat_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, + void *priv, u32 src) +{ + struct mhi_sat_subsys *subsys = dev_get_drvdata(&rpdev->dev); + struct sat_header *hdr = data; + struct sat_tre *pkt = SAT_TRE_OFFSET(data); + struct mhi_sat_cntrl *sat_cntrl; + struct mhi_sat_packet *packet; + unsigned long flags; + + WARN_ON(!mhi_sat_isvalid_header(hdr, len)); + + /* find controller packet was sent for */ + sat_cntrl = find_sat_cntrl_by_id(subsys, hdr->dev_id); + if (!sat_cntrl) { + MSG_ERR("Message for unknown device!\n"); + return 0; + } + + /* handle events directly regardless of controller active state */ + if (hdr->msg_id == SAT_MSG_ID_EVT) { + mhi_sat_process_events(sat_cntrl, hdr, pkt); + return 0; + } + + /* Inactive controller cannot process incoming commands */ + if (unlikely(!MHI_SAT_ACTIVE(sat_cntrl))) { + MSG_ERR("Message for inactive controller!\n"); + return 0; + } + + /* offload commands to process worker */ + packet = kmalloc(sizeof(*packet) + len, GFP_ATOMIC); + if (!packet) + return 0; + + packet->cntrl = sat_cntrl; + packet->msg = packet + 1; + memcpy(packet->msg, data, len); + + spin_lock_irqsave(&sat_cntrl->pkt_lock, flags); + list_add_tail(&packet->node, &sat_cntrl->packet_list); + spin_unlock_irqrestore(&sat_cntrl->pkt_lock, flags); + + schedule_work(&sat_cntrl->process_work); + + return 0; +} + +static void mhi_sat_rpmsg_remove(struct rpmsg_device *rpdev) +{ + struct mhi_sat_subsys *subsys = dev_get_drvdata(&rpdev->dev); + struct mhi_sat_cntrl *sat_cntrl; + struct mhi_sat_device *sat_dev; + struct mhi_buf_extended *buf, *tmp; + + MSG_SUBSYS_LOG("Enter\n"); + + /* unprepare each controller/device from transfer */ + mutex_lock(&subsys->cntrl_mutex); + list_for_each_entry(sat_cntrl, &subsys->cntrl_list, node) { + flush_work(&sat_cntrl->error_work); + + spin_lock_irq(&sat_cntrl->state_lock); + /* + * move to disabled state if early error fatal is detected + * and rpmsg link goes down before device remove call from + * mhi is received + */ + if (MHI_SAT_IN_ERROR_STATE(sat_cntrl)) { + sat_cntrl->state = SAT_DISABLED; + spin_unlock_irq(&sat_cntrl->state_lock); + continue; + } + sat_cntrl->state = SAT_DISCONNECTED; + spin_unlock_irq(&sat_cntrl->state_lock); + + flush_work(&sat_cntrl->connect_work); + flush_work(&sat_cntrl->process_work); + + mutex_lock(&sat_cntrl->list_mutex); + list_for_each_entry(sat_dev, &sat_cntrl->dev_list, node) { + if (sat_dev->chan_started) { + mhi_unprepare_from_transfer(sat_dev->mhi_dev); + sat_dev->chan_started = false; + } + } + + list_for_each_entry_safe(buf, tmp, &sat_cntrl->addr_map_list, + node) { + struct device *parent_dev = + sat_cntrl->mhi_cntrl->mhi_dev->dev.parent; + if (buf->is_io) { + dma_unmap_resource(parent_dev, buf->dma_addr, + buf->len, + DMA_BIDIRECTIONAL, 0); + } else { + dma_unmap_single_attrs(parent_dev, + buf->dma_addr, buf->len, + DMA_BIDIRECTIONAL, + DMA_ATTR_SKIP_CPU_SYNC); + } + list_del(&buf->node); + kfree(buf); + } + mutex_unlock(&sat_cntrl->list_mutex); + + MSG_LOG("Removed RPMSG link\n"); + } + subsys->rpdev = NULL; + mutex_unlock(&subsys->cntrl_mutex); +} + +static int mhi_sat_rpmsg_probe(struct rpmsg_device *rpdev) +{ + struct mhi_sat_subsys *subsys; + struct mhi_sat_cntrl *sat_cntrl; + const char *subsys_name; + int ret; + + ret = of_property_read_string(rpdev->dev.parent->of_node, "label", + &subsys_name); + if (ret) + return ret; + + /* find which subsystem has probed */ + subsys = find_subsys_by_name(subsys_name); + if (!subsys) + return -EINVAL; + + mutex_lock(&subsys->cntrl_mutex); + + MSG_SUBSYS_LOG("Received RPMSG probe\n"); + + dev_set_drvdata(&rpdev->dev, subsys); + + subsys->rpdev = rpdev; + + /* schedule work for each controller as GLINK has connected */ + spin_lock_irq(&subsys->cntrl_lock); + list_for_each_entry(sat_cntrl, &subsys->cntrl_list, node) + schedule_work(&sat_cntrl->connect_work); + spin_unlock_irq(&subsys->cntrl_lock); + + mutex_unlock(&subsys->cntrl_mutex); + + return 0; +} + +static struct rpmsg_device_id mhi_sat_rpmsg_match_table[] = { + { .name = "mhi_sat" }, + { }, +}; + +static struct rpmsg_driver mhi_sat_rpmsg_driver = { + .id_table = mhi_sat_rpmsg_match_table, + .probe = mhi_sat_rpmsg_probe, + .remove = mhi_sat_rpmsg_remove, + .callback = mhi_sat_rpmsg_cb, + .drv = { + .name = "mhi,sat_rpmsg", + }, +}; + +static void mhi_sat_dev_status_cb(struct mhi_device *mhi_dev, + enum mhi_callback mhi_cb) +{ + struct mhi_sat_device *sat_dev = dev_get_drvdata(&mhi_dev->dev); + struct mhi_sat_cntrl *sat_cntrl = sat_dev->cntrl; + struct mhi_sat_subsys *subsys = sat_cntrl->subsys; + unsigned long flags; + + if (mhi_cb != MHI_CB_FATAL_ERROR) + return; + + MSG_LOG("Device fatal error detected\n"); + spin_lock_irqsave(&sat_cntrl->state_lock, flags); + if (MHI_SAT_ACTIVE(sat_cntrl)) { + schedule_work(&sat_cntrl->error_work); + sat_cntrl->state = SAT_FATAL_DETECT; + } + + spin_unlock_irqrestore(&sat_cntrl->state_lock, flags); +} + +static void mhi_sat_dev_remove(struct mhi_device *mhi_dev) +{ + struct mhi_sat_device *sat_dev = dev_get_drvdata(&mhi_dev->dev); + struct mhi_sat_cntrl *sat_cntrl = sat_dev->cntrl; + struct mhi_sat_subsys *subsys = sat_cntrl->subsys; + struct mhi_buf_extended *buf, *tmp; + bool send_sys_err = false; + + /* remove device node from probed list */ + mutex_lock(&sat_cntrl->list_mutex); + list_del(&sat_dev->node); + mutex_unlock(&sat_cntrl->list_mutex); + + sat_cntrl->num_devices--; + + mutex_lock(&subsys->cntrl_mutex); + + cancel_work_sync(&sat_cntrl->error_work); + + /* send sys_err if first device is removed */ + spin_lock_irq(&sat_cntrl->state_lock); + if (MHI_SAT_ALLOW_SYS_ERR(sat_cntrl)) + send_sys_err = true; + sat_cntrl->state = SAT_ERROR; + spin_unlock_irq(&sat_cntrl->state_lock); + + if (send_sys_err) + mhi_sat_send_sys_err(sat_cntrl); + + /* exit if some devices are still present */ + if (sat_cntrl->num_devices) { + mutex_unlock(&subsys->cntrl_mutex); + return; + } + + /* + * cancel any pending work as it is possible that work gets queued + * when rpmsg probe comes in before controller is removed + */ + cancel_work_sync(&sat_cntrl->connect_work); + cancel_work_sync(&sat_cntrl->process_work); + + /* remove address mappings */ + mutex_lock(&sat_cntrl->list_mutex); + list_for_each_entry_safe(buf, tmp, &sat_cntrl->addr_map_list, node) { + struct device *parent_dev = + sat_cntrl->mhi_cntrl->mhi_dev->dev.parent; + if (buf->is_io) { + dma_unmap_resource(parent_dev, buf->dma_addr, buf->len, + DMA_BIDIRECTIONAL, 0); + } else { + dma_unmap_single_attrs(parent_dev, buf->dma_addr, + buf->len, DMA_BIDIRECTIONAL, + DMA_ATTR_SKIP_CPU_SYNC); + } + + dma_unmap_single_attrs(parent_dev, buf->dma_addr, + buf->len, DMA_BIDIRECTIONAL, + DMA_ATTR_SKIP_CPU_SYNC); + list_del(&buf->node); + kfree(buf); + } + mutex_unlock(&sat_cntrl->list_mutex); + + /* remove controller */ + spin_lock_irq(&subsys->cntrl_lock); + list_del(&sat_cntrl->node); + spin_unlock_irq(&subsys->cntrl_lock); + + mutex_destroy(&sat_cntrl->cmd_wait_mutex); + mutex_destroy(&sat_cntrl->list_mutex); + MSG_LOG("Satellite controller node removed\n"); + kfree(sat_cntrl); + + mutex_unlock(&subsys->cntrl_mutex); +} + +static int mhi_sat_dev_probe(struct mhi_device *mhi_dev, + const struct mhi_device_id *id) +{ + struct mhi_sat_device *sat_dev; + struct mhi_sat_cntrl *sat_cntrl; + struct mhi_sat_subsys *subsys = &mhi_sat_driver.subsys[id->driver_data]; + u32 dev_id = mhi_controller_get_numeric_id(mhi_dev->mhi_cntrl); + + if (!dev_id) { + pr_err("Satellite numeric ID not set by controller\n"); + return -EINVAL; + } + + /* find controller with unique device ID based on topology */ + sat_cntrl = find_sat_cntrl_by_id(subsys, dev_id); + if (!sat_cntrl) { + sat_cntrl = kzalloc(sizeof(*sat_cntrl), GFP_KERNEL); + if (!sat_cntrl) + return -ENOMEM; + + /* + * max_devices will be set once per device. Set it to + * -1 before it is populated to avoid false positive when + * RPMSG probe schedules connect worker but no device has + * probed in which case num_devices and max_devices are both + * zero. + */ + sat_cntrl->max_devices = -1; + sat_cntrl->dev_id = dev_id; + sat_cntrl->er_base = mhi_dev->dl_event_id; + sat_cntrl->mhi_cntrl = mhi_dev->mhi_cntrl; + sat_cntrl->last_cmd_seq = SAT_RESERVED_SEQ_NUM; + sat_cntrl->subsys = subsys; + init_completion(&sat_cntrl->completion); + mutex_init(&sat_cntrl->list_mutex); + mutex_init(&sat_cntrl->cmd_wait_mutex); + spin_lock_init(&sat_cntrl->pkt_lock); + spin_lock_init(&sat_cntrl->state_lock); + INIT_WORK(&sat_cntrl->connect_work, mhi_sat_connect_worker); + INIT_WORK(&sat_cntrl->process_work, mhi_sat_process_worker); + INIT_WORK(&sat_cntrl->error_work, mhi_sat_error_worker); + INIT_LIST_HEAD(&sat_cntrl->dev_list); + INIT_LIST_HEAD(&sat_cntrl->addr_map_list); + INIT_LIST_HEAD(&sat_cntrl->packet_list); + + mutex_lock(&subsys->cntrl_mutex); + spin_lock_irq(&subsys->cntrl_lock); + list_add(&sat_cntrl->node, &subsys->cntrl_list); + spin_unlock_irq(&subsys->cntrl_lock); + mutex_unlock(&subsys->cntrl_mutex); + + MSG_LOG("Controller allocated for 0x%x\n", dev_id); + } + + /* set maximum devices for subsystem from device tree */ + sat_cntrl->max_devices = MHI_SAT_MAX_DEVICES; + + /* get event ring base and max indexes */ + sat_cntrl->er_base = min(sat_cntrl->er_base, mhi_dev->dl_event_id); + sat_cntrl->er_max = max(sat_cntrl->er_base, mhi_dev->dl_event_id); + + sat_dev = devm_kzalloc(&mhi_dev->dev, sizeof(*sat_dev), GFP_KERNEL); + if (!sat_dev) + return -ENOMEM; + + sat_dev->mhi_dev = mhi_dev; + sat_dev->cntrl = sat_cntrl; + + mutex_lock(&sat_cntrl->list_mutex); + list_add(&sat_dev->node, &sat_cntrl->dev_list); + mutex_unlock(&sat_cntrl->list_mutex); + + dev_set_drvdata(&mhi_dev->dev, sat_dev); + + sat_cntrl->num_devices++; + + /* schedule connect worker if all devices for controller have probed */ + if (sat_cntrl->num_devices == sat_cntrl->max_devices) { + /* number of event rings is 1 more than difference in IDs */ + sat_cntrl->num_er = (sat_cntrl->er_max - sat_cntrl->er_base) + + 1; + MSG_LOG("All satellite channels probed!\n"); + schedule_work(&sat_cntrl->connect_work); + } + + return 0; +} + +/* .driver_data stores subsys id */ +static const struct mhi_device_id mhi_sat_dev_match_table[] = { + { .chan = "ADSP_0", .driver_data = SUBSYS_ADSP }, + { .chan = "ADSP_1", .driver_data = SUBSYS_ADSP }, + { .chan = "ADSP_2", .driver_data = SUBSYS_ADSP }, + { .chan = "ADSP_3", .driver_data = SUBSYS_ADSP }, + {}, +}; + +static struct mhi_driver mhi_sat_dev_driver = { + .id_table = mhi_sat_dev_match_table, + .probe = mhi_sat_dev_probe, + .remove = mhi_sat_dev_remove, + .status_cb = mhi_sat_dev_status_cb, + .driver = { + .name = MHI_SAT_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int mhi_sat_init(void) +{ + struct mhi_sat_subsys *subsys; + int i, ret; + + subsys = kcalloc(SUBSYS_MAX, sizeof(*subsys), GFP_KERNEL); + if (!subsys) + return -ENOMEM; + + mhi_sat_driver.subsys = subsys; + mhi_sat_driver.num_subsys = SUBSYS_MAX; + + for (i = 0; i < mhi_sat_driver.num_subsys; i++, subsys++) { + char log[32]; + + subsys->name = subsys_names[i]; + mutex_init(&subsys->cntrl_mutex); + spin_lock_init(&subsys->cntrl_lock); + INIT_LIST_HEAD(&subsys->cntrl_list); + scnprintf(log, sizeof(log), "mhi_sat_%s", subsys->name); + subsys->ipc_log = ipc_log_context_create(IPC_LOG_PAGES, log, 0); + } + + ret = register_rpmsg_driver(&mhi_sat_rpmsg_driver); + if (ret) + goto error_sat_init; + + ret = mhi_driver_register(&mhi_sat_dev_driver); + if (ret) + goto error_sat_register; + + return 0; + +error_sat_register: + unregister_rpmsg_driver(&mhi_sat_rpmsg_driver); + +error_sat_init: + subsys = mhi_sat_driver.subsys; + for (i = 0; i < mhi_sat_driver.num_subsys; i++, subsys++) { + ipc_log_context_destroy(subsys->ipc_log); + mutex_destroy(&subsys->cntrl_mutex); + } + kfree(mhi_sat_driver.subsys); + mhi_sat_driver.subsys = NULL; + + return ret; +} +module_init(mhi_sat_init); + +static void __exit mhi_sat_exit(void) +{ + struct mhi_sat_subsys *subsys; + int i; + + unregister_rpmsg_driver(&mhi_sat_rpmsg_driver); + + subsys = mhi_sat_driver.subsys; + for (i = 0; i < mhi_sat_driver.num_subsys; i++, subsys++) { + ipc_log_context_destroy(subsys->ipc_log); + mutex_destroy(&subsys->cntrl_mutex); + } + kfree(mhi_sat_driver.subsys); + mhi_sat_driver.subsys = NULL; +} +module_exit(mhi_sat_exit); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("MHI_SATELLITE"); +MODULE_DESCRIPTION("MHI SATELLITE DRIVER"); diff --git a/drivers/bus/mhi/devices/mhi_uci.c b/drivers/bus/mhi/devices/mhi_uci.c new file mode 100644 index 000000000000..f41f711f5329 --- /dev/null +++ b/drivers/bus/mhi/devices/mhi_uci.c @@ -0,0 +1,825 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MHI_UCI_DRIVER_NAME "mhi_uci" + +struct uci_chan { + wait_queue_head_t wq; + spinlock_t lock; + struct list_head pending; /* user space waiting to read */ + struct uci_buf *cur_buf; /* current buffer user space reading */ + size_t rx_size; + struct mutex chan_lock; +}; + +struct uci_buf { + void *data; + size_t len; + struct list_head node; +}; + +struct uci_dev { + struct list_head node; + dev_t devt; + struct device *dev; + struct mhi_device *mhi_dev; + const char *chan; + struct mutex mutex; /* sync open and close */ + struct uci_chan ul_chan; + struct uci_chan dl_chan; + size_t mtu; + size_t actual_mtu; /* maximum size of incoming buffer */ + int ref_count; + bool enabled; + u32 tiocm; + void *ipc_log; + enum MHI_DEBUG_LEVEL msg_lvl; +}; + +struct mhi_uci_drv { + struct list_head head; + struct mutex lock; + struct class *class; + int major; + dev_t dev_t; +}; + +#ifdef CONFIG_MHI_BUS_DEBUG +#define MHI_UCI_LOG_LVL MHI_MSG_LVL_VERBOSE +#else +#define MHI_UCI_LOG_LVL MHI_MSG_LVL_ERROR +#endif + +#define MHI_UCI_IPC_LOG_PAGES (50) +#define MSG_VERB(fmt, ...) do { \ + if (uci_dev->ipc_log && uci_dev->msg_lvl <= MHI_MSG_LVL_VERBOSE) \ + ipc_log_string(uci_dev->ipc_log, "%s[D][%s] " fmt, \ + "", __func__, ##__VA_ARGS__); \ + } while (0) + +#define MSG_LOG(fmt, ...) do { \ + if (uci_dev->ipc_log && uci_dev->msg_lvl <= MHI_MSG_LVL_INFO) \ + ipc_log_string(uci_dev->ipc_log, "%s[I][%s] " fmt, \ + "", __func__, ##__VA_ARGS__); \ + } while (0) + +#define MSG_ERR(fmt, ...) do { \ + pr_err("[E][%s] " fmt, __func__, ##__VA_ARGS__); \ + if (uci_dev->ipc_log && uci_dev->msg_lvl <= MHI_MSG_LVL_ERROR) \ + ipc_log_string(uci_dev->ipc_log, "%s[E][%s] " fmt, \ + "", __func__, ##__VA_ARGS__); \ + } while (0) + +#define MAX_UCI_DEVICES (64) + +const char * const mhi_uci_log_level_str[MHI_MSG_LVL_MAX] = { + [MHI_MSG_LVL_VERBOSE] = "Verbose", + [MHI_MSG_LVL_INFO] = "Info", + [MHI_MSG_LVL_ERROR] = "Error", + [MHI_MSG_LVL_CRITICAL] = "Critical", + [MHI_MSG_LVL_MASK_ALL] = "Mask all", +}; +#define MHI_UCI_LOG_LEVEL_STR(level) ((level >= MHI_MSG_LVL_MAX || \ + !mhi_uci_log_level_str[level]) ? \ + "Mask all" : mhi_uci_log_level_str[level]) + +static DECLARE_BITMAP(uci_minors, MAX_UCI_DEVICES); +static struct mhi_uci_drv mhi_uci_drv; + +static int mhi_queue_inbound(struct uci_dev *uci_dev) +{ + struct mhi_device *mhi_dev = uci_dev->mhi_dev; + int nr_trbs = mhi_get_free_desc_count(mhi_dev, DMA_FROM_DEVICE); + size_t mtu = uci_dev->mtu; + size_t actual_mtu = uci_dev->actual_mtu; + void *buf; + struct uci_buf *uci_buf; + int ret = -EIO, i; + + for (i = 0; i < nr_trbs; i++) { + buf = kmalloc(mtu, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + uci_buf = buf + actual_mtu; + uci_buf->data = buf; + + MSG_VERB("Allocated buf %d of %d size %ld\n", i, nr_trbs, + actual_mtu); + + ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, buf, + actual_mtu, MHI_EOT); + if (ret) { + kfree(buf); + MSG_ERR("Failed to queue buffer %d\n", i); + return ret; + } + } + + return ret; +} + +static long mhi_uci_ioctl(struct file *file, + unsigned int cmd, + unsigned long arg) +{ + struct uci_dev *uci_dev = file->private_data; + struct mhi_device *mhi_dev = uci_dev->mhi_dev; + struct uci_chan *uci_chan = &uci_dev->dl_chan; + long ret = -ENOIOCTLCMD; + + mutex_lock(&uci_dev->mutex); + + if (cmd == TIOCMGET) { + spin_lock_bh(&uci_chan->lock); + ret = uci_dev->tiocm; + spin_unlock_bh(&uci_chan->lock); + } else if (uci_dev->enabled) { + ret = mhi_device_ioctl(mhi_dev, cmd, arg); + if (!ret) { + spin_lock_bh(&uci_chan->lock); + uci_dev->tiocm = mhi_dev->tiocm; + spin_unlock_bh(&uci_chan->lock); + } + } + + mutex_unlock(&uci_dev->mutex); + + return ret; +} + +static int mhi_uci_release(struct inode *inode, struct file *file) +{ + struct uci_dev *uci_dev = file->private_data; + + mutex_lock(&uci_dev->mutex); + uci_dev->ref_count--; + if (!uci_dev->ref_count) { + struct uci_buf *itr, *tmp; + struct uci_chan *uci_chan; + + MSG_LOG("Last client left, closing node\n"); + + if (uci_dev->enabled) + mhi_unprepare_from_transfer(uci_dev->mhi_dev); + + /* clean inbound channel */ + uci_chan = &uci_dev->dl_chan; + list_for_each_entry_safe(itr, tmp, &uci_chan->pending, node) { + list_del(&itr->node); + kfree(itr->data); + } + if (uci_chan->cur_buf) + kfree(uci_chan->cur_buf->data); + + uci_chan->cur_buf = NULL; + + if (!uci_dev->enabled) { + MSG_LOG("Node is deleted, freeing dev node\n"); + mutex_unlock(&uci_dev->mutex); + mutex_destroy(&uci_dev->mutex); + mutex_destroy(&uci_dev->dl_chan.chan_lock); + mutex_destroy(&uci_dev->ul_chan.chan_lock); + clear_bit(MINOR(uci_dev->devt), uci_minors); + kfree(uci_dev); + return 0; + } + } + + MSG_LOG("exit: ref_count:%d\n", uci_dev->ref_count); + + mutex_unlock(&uci_dev->mutex); + + return 0; +} + +static __poll_t mhi_uci_poll(struct file *file, poll_table *wait) +{ + struct uci_dev *uci_dev = file->private_data; + struct mhi_device *mhi_dev = uci_dev->mhi_dev; + struct uci_chan *uci_chan; + __poll_t mask = 0; + + poll_wait(file, &uci_dev->dl_chan.wq, wait); + poll_wait(file, &uci_dev->ul_chan.wq, wait); + + uci_chan = &uci_dev->dl_chan; + spin_lock_bh(&uci_chan->lock); + if (!uci_dev->enabled) { + mask = EPOLLERR; + } else { + if (!list_empty(&uci_chan->pending) || uci_chan->cur_buf) { + MSG_VERB("Client can read from node\n"); + mask |= EPOLLIN | EPOLLRDNORM; + } + + if (uci_dev->tiocm) { + MSG_VERB("Line status changed\n"); + mask |= EPOLLPRI; + } + } + spin_unlock_bh(&uci_chan->lock); + + uci_chan = &uci_dev->ul_chan; + spin_lock_bh(&uci_chan->lock); + if (!uci_dev->enabled) { + mask |= EPOLLERR; + } else if (mhi_get_free_desc_count(mhi_dev, DMA_TO_DEVICE) > 0) { + MSG_VERB("Client can write to node\n"); + mask |= EPOLLOUT | EPOLLWRNORM; + } + spin_unlock_bh(&uci_chan->lock); + + MSG_LOG("Client attempted to poll, returning mask 0x%x\n", mask); + + return mask; +} + +static ssize_t mhi_uci_write(struct file *file, + const char __user *buf, + size_t count, + loff_t *offp) +{ + struct uci_dev *uci_dev = file->private_data; + struct mhi_device *mhi_dev = uci_dev->mhi_dev; + struct uci_chan *uci_chan = &uci_dev->ul_chan; + size_t bytes_xfered = 0; + int ret, nr_avail; + + if (!buf || !count) + return -EINVAL; + + mutex_lock(&uci_chan->chan_lock); + + /* confirm channel is active */ + spin_lock_bh(&uci_chan->lock); + if (!uci_dev->enabled) { + spin_unlock_bh(&uci_chan->lock); + ret = -ERESTARTSYS; + goto err_mtx_unlock; + } + + MSG_VERB("Enter: to xfer:%lu bytes\n", count); + + while (count) { + size_t xfer_size; + void *kbuf; + enum mhi_flags flags; + + spin_unlock_bh(&uci_chan->lock); + + /* wait for free descriptors */ + ret = wait_event_interruptible(uci_chan->wq, + (!uci_dev->enabled) || + (nr_avail = mhi_get_free_desc_count(mhi_dev, + DMA_TO_DEVICE)) > 0); + + if (ret == -ERESTARTSYS || !uci_dev->enabled) { + MSG_LOG("Exit signal caught for node or not enabled\n"); + ret = -ERESTARTSYS; + goto err_mtx_unlock; + } + + xfer_size = min_t(size_t, count, uci_dev->mtu); + kbuf = kmalloc(xfer_size, GFP_KERNEL); + if (!kbuf) { + MSG_ERR("Failed to allocate memory %lu\n", xfer_size); + ret = -ENOMEM; + goto err_mtx_unlock; + } + + ret = copy_from_user(kbuf, buf, xfer_size); + if (unlikely(ret)) { + kfree(kbuf); + goto err_mtx_unlock; + } + + spin_lock_bh(&uci_chan->lock); + + /* if ring is full after this force EOT */ + if (nr_avail > 1 && (count - xfer_size)) + flags = MHI_CHAIN; + else + flags = MHI_EOT; + + if (uci_dev->enabled) + ret = mhi_queue_buf(mhi_dev, DMA_TO_DEVICE, kbuf, + xfer_size, flags); + else + ret = -ERESTARTSYS; + + if (ret) { + kfree(kbuf); + goto sys_interrupt; + } + + bytes_xfered += xfer_size; + count -= xfer_size; + buf += xfer_size; + } + + spin_unlock_bh(&uci_chan->lock); + mutex_unlock(&uci_chan->chan_lock); + MSG_VERB("Exit: Number of bytes xferred:%lu\n", bytes_xfered); + + return bytes_xfered; + +sys_interrupt: + spin_unlock_bh(&uci_chan->lock); +err_mtx_unlock: + mutex_unlock(&uci_chan->chan_lock); + + return ret; +} + +static ssize_t mhi_uci_read(struct file *file, + char __user *buf, + size_t count, + loff_t *ppos) +{ + struct uci_dev *uci_dev = file->private_data; + struct mhi_device *mhi_dev = uci_dev->mhi_dev; + struct uci_chan *uci_chan = &uci_dev->dl_chan; + struct uci_buf *uci_buf; + char *ptr; + size_t to_copy; + int ret = 0; + + if (!buf) + return -EINVAL; + + MSG_VERB("Client provided buf len:%lu\n", count); + + mutex_lock(&uci_chan->chan_lock); + + /* confirm channel is active */ + spin_lock_bh(&uci_chan->lock); + if (!uci_dev->enabled) { + spin_unlock_bh(&uci_chan->lock); + ret = -ERESTARTSYS; + goto err_mtx_unlock; + } + + /* No data available to read, wait */ + if (!uci_chan->cur_buf && list_empty(&uci_chan->pending)) { + MSG_VERB("No data available to read waiting\n"); + + spin_unlock_bh(&uci_chan->lock); + ret = wait_event_interruptible(uci_chan->wq, + (!uci_dev->enabled || + !list_empty(&uci_chan->pending))); + if (ret == -ERESTARTSYS) { + MSG_LOG("Exit signal caught for node\n"); + ret = -ERESTARTSYS; + goto err_mtx_unlock; + } + + spin_lock_bh(&uci_chan->lock); + if (!uci_dev->enabled) { + MSG_LOG("node is disabled\n"); + ret = -ERESTARTSYS; + goto read_error; + } + } + + /* new read, get the next descriptor from the list */ + if (!uci_chan->cur_buf) { + uci_buf = list_first_entry_or_null(&uci_chan->pending, + struct uci_buf, node); + if (unlikely(!uci_buf)) { + ret = -EIO; + goto read_error; + } + + list_del(&uci_buf->node); + uci_chan->cur_buf = uci_buf; + uci_chan->rx_size = uci_buf->len; + MSG_VERB("Got pkt of size:%zu\n", uci_chan->rx_size); + } + + uci_buf = uci_chan->cur_buf; + spin_unlock_bh(&uci_chan->lock); + + /* Copy the buffer to user space */ + to_copy = min_t(size_t, count, uci_chan->rx_size); + ptr = uci_buf->data + (uci_buf->len - uci_chan->rx_size); + ret = copy_to_user(buf, ptr, to_copy); + if (ret) + goto err_mtx_unlock; + + MSG_VERB("Copied %lu of %lu bytes\n", to_copy, uci_chan->rx_size); + uci_chan->rx_size -= to_copy; + + /* we finished with this buffer, queue it back to hardware */ + if (!uci_chan->rx_size) { + spin_lock_bh(&uci_chan->lock); + uci_chan->cur_buf = NULL; + + if (uci_dev->enabled) + ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, + uci_buf->data, + uci_dev->actual_mtu, MHI_EOT); + else + ret = -ERESTARTSYS; + + if (ret) { + MSG_ERR("Failed to recycle element\n"); + kfree(uci_buf->data); + goto read_error; + } + + spin_unlock_bh(&uci_chan->lock); + } + + MSG_VERB("Returning %lu bytes\n", to_copy); + mutex_unlock(&uci_chan->chan_lock); + + return to_copy; + +read_error: + spin_unlock_bh(&uci_chan->lock); +err_mtx_unlock: + mutex_unlock(&uci_chan->chan_lock); + return ret; +} + +static int mhi_uci_open(struct inode *inode, struct file *filp) +{ + struct uci_dev *uci_dev = NULL, *tmp_dev; + int ret = -EIO; + struct uci_buf *buf_itr, *tmp; + struct uci_chan *dl_chan; + + mutex_lock(&mhi_uci_drv.lock); + list_for_each_entry(tmp_dev, &mhi_uci_drv.head, node) { + if (tmp_dev->devt == inode->i_rdev) { + uci_dev = tmp_dev; + break; + } + } + + /* could not find a minor node */ + if (!uci_dev) + goto error_exit; + + mutex_lock(&uci_dev->mutex); + if (!uci_dev->enabled) { + MSG_ERR("Node exist, but not in active state!\n"); + goto error_open_chan; + } + + uci_dev->ref_count++; + + MSG_LOG("Node open, ref counts %u\n", uci_dev->ref_count); + + if (uci_dev->ref_count == 1) { + MSG_LOG("Starting channel\n"); + ret = mhi_prepare_for_transfer(uci_dev->mhi_dev, 0); + if (ret) { + MSG_ERR("Error starting transfer channels\n"); + uci_dev->ref_count--; + goto error_open_chan; + } + + ret = mhi_queue_inbound(uci_dev); + if (ret) + goto error_rx_queue; + } + + filp->private_data = uci_dev; + mutex_unlock(&uci_dev->mutex); + mutex_unlock(&mhi_uci_drv.lock); + + return 0; + + error_rx_queue: + dl_chan = &uci_dev->dl_chan; + mhi_unprepare_from_transfer(uci_dev->mhi_dev); + list_for_each_entry_safe(buf_itr, tmp, &dl_chan->pending, node) { + list_del(&buf_itr->node); + kfree(buf_itr->data); + } + + error_open_chan: + mutex_unlock(&uci_dev->mutex); + +error_exit: + mutex_unlock(&mhi_uci_drv.lock); + + return ret; +} + +static const struct file_operations mhidev_fops = { + .open = mhi_uci_open, + .release = mhi_uci_release, + .read = mhi_uci_read, + .write = mhi_uci_write, + .poll = mhi_uci_poll, + .unlocked_ioctl = mhi_uci_ioctl, +}; + +static ssize_t log_level_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct mhi_device *mhi_dev = to_mhi_device(dev); + struct uci_dev *uci_dev = dev_get_drvdata(&mhi_dev->dev); + + if (!uci_dev) + return -EIO; + + return scnprintf(buf, PAGE_SIZE, + "IPC log level begins from: %s\n", + MHI_UCI_LOG_LEVEL_STR(uci_dev->msg_lvl)); +} + +static ssize_t log_level_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct mhi_device *mhi_dev = to_mhi_device(dev); + struct uci_dev *uci_dev = dev_get_drvdata(&mhi_dev->dev); + enum MHI_DEBUG_LEVEL log_level; + + if (kstrtou32(buf, 0, &log_level) < 0) + return -EINVAL; + + if (!uci_dev) + return -EIO; + + uci_dev->msg_lvl = log_level; + + MSG_LOG("IPC log level changed to: %s\n", + MHI_UCI_LOG_LEVEL_STR(log_level)); + + return count; +} +static DEVICE_ATTR_RW(log_level); + +static struct attribute *mhi_uci_attrs[] = { + &dev_attr_log_level.attr, + NULL, +}; + +static const struct attribute_group mhi_uci_group = { + .attrs = mhi_uci_attrs, +}; + +static void mhi_uci_remove(struct mhi_device *mhi_dev) +{ + struct uci_dev *uci_dev = dev_get_drvdata(&mhi_dev->dev); + + MSG_LOG("Enter\n"); + + + mutex_lock(&mhi_uci_drv.lock); + mutex_lock(&uci_dev->mutex); + + /* disable the node */ + spin_lock_irq(&uci_dev->dl_chan.lock); + spin_lock_irq(&uci_dev->ul_chan.lock); + uci_dev->enabled = false; + spin_unlock_irq(&uci_dev->ul_chan.lock); + spin_unlock_irq(&uci_dev->dl_chan.lock); + wake_up(&uci_dev->dl_chan.wq); + wake_up(&uci_dev->ul_chan.wq); + + /* delete the node to prevent new opens */ + device_destroy(mhi_uci_drv.class, uci_dev->devt); + uci_dev->dev = NULL; + list_del(&uci_dev->node); + sysfs_remove_group(&mhi_dev->dev.kobj, &mhi_uci_group); + + /* safe to free memory only if all file nodes are closed */ + if (!uci_dev->ref_count) { + mutex_unlock(&uci_dev->mutex); + mutex_destroy(&uci_dev->mutex); + mutex_destroy(&uci_dev->dl_chan.chan_lock); + mutex_destroy(&uci_dev->ul_chan.chan_lock); + clear_bit(MINOR(uci_dev->devt), uci_minors); + kfree(uci_dev); + mutex_unlock(&mhi_uci_drv.lock); + return; + } + + MSG_LOG("Exit\n"); + mutex_unlock(&uci_dev->mutex); + mutex_unlock(&mhi_uci_drv.lock); + +} + +static int mhi_uci_probe(struct mhi_device *mhi_dev, + const struct mhi_device_id *id) +{ + struct uci_dev *uci_dev; + char node_name[32]; + int minor, dir, ret; + + uci_dev = kzalloc(sizeof(*uci_dev), GFP_KERNEL); + if (!uci_dev) + return -ENOMEM; + + mutex_init(&uci_dev->mutex); + uci_dev->mhi_dev = mhi_dev; + + minor = find_first_zero_bit(uci_minors, MAX_UCI_DEVICES); + if (minor >= MAX_UCI_DEVICES) { + kfree(uci_dev); + return -ENOSPC; + } + + snprintf(node_name, sizeof(node_name), "%s_pipe_%d", + dev_name(mhi_dev->dev.parent), mhi_dev->ul_chan_id); + + mutex_lock(&uci_dev->mutex); + mutex_lock(&mhi_uci_drv.lock); + + uci_dev->devt = MKDEV(mhi_uci_drv.major, minor); + uci_dev->dev = device_create(mhi_uci_drv.class, &mhi_dev->dev, + uci_dev->devt, uci_dev, "%s", node_name); + if (IS_ERR(uci_dev->dev)) { + mutex_unlock(&mhi_uci_drv.lock); + mutex_unlock(&uci_dev->mutex); + mutex_destroy(&uci_dev->mutex); + ret = PTR_ERR(uci_dev->dev); + kfree(uci_dev); + return ret; + } + + set_bit(minor, uci_minors); + + /* create debugging buffer */ + uci_dev->ipc_log = ipc_log_context_create(MHI_UCI_IPC_LOG_PAGES, + dev_name(&mhi_dev->dev), 0); + uci_dev->msg_lvl = MHI_UCI_LOG_LVL; + ret = sysfs_create_group(&mhi_dev->dev.kobj, &mhi_uci_group); + if (ret) + MSG_ERR("Failed to create MHI UCI sysfs group\n"); + + for (dir = 0; dir < 2; dir++) { + struct uci_chan *uci_chan = (dir) ? + &uci_dev->ul_chan : &uci_dev->dl_chan; + spin_lock_init(&uci_chan->lock); + mutex_init(&uci_chan->chan_lock); + init_waitqueue_head(&uci_chan->wq); + INIT_LIST_HEAD(&uci_chan->pending); + } + + uci_dev->mtu = min_t(size_t, id->driver_data, MHI_MAX_MTU); + uci_dev->actual_mtu = uci_dev->mtu - sizeof(struct uci_buf); + dev_set_drvdata(&mhi_dev->dev, uci_dev); + uci_dev->enabled = true; + + list_add(&uci_dev->node, &mhi_uci_drv.head); + mutex_unlock(&mhi_uci_drv.lock); + mutex_unlock(&uci_dev->mutex); + + MSG_LOG("channel:%s successfully probed\n", mhi_dev->name); + + return 0; +}; + +static void mhi_ul_xfer_cb(struct mhi_device *mhi_dev, + struct mhi_result *mhi_result) +{ + struct uci_dev *uci_dev = dev_get_drvdata(&mhi_dev->dev); + struct uci_chan *uci_chan = &uci_dev->ul_chan; + + MSG_VERB("status:%d xfer_len:%zu\n", mhi_result->transaction_status, + mhi_result->bytes_xferd); + + kfree(mhi_result->buf_addr); + if (!mhi_result->transaction_status) + wake_up(&uci_chan->wq); +} + +static void mhi_dl_xfer_cb(struct mhi_device *mhi_dev, + struct mhi_result *mhi_result) +{ + struct uci_dev *uci_dev = dev_get_drvdata(&mhi_dev->dev); + struct uci_chan *uci_chan = &uci_dev->dl_chan; + unsigned long flags; + struct uci_buf *buf; + + MSG_VERB("status:%d receive_len:%zu\n", mhi_result->transaction_status, + mhi_result->bytes_xferd); + + if (mhi_result->transaction_status == -ENOTCONN) { + kfree(mhi_result->buf_addr); + return; + } + + spin_lock_irqsave(&uci_chan->lock, flags); + buf = mhi_result->buf_addr + uci_dev->actual_mtu; + buf->data = mhi_result->buf_addr; + buf->len = mhi_result->bytes_xferd; + list_add_tail(&buf->node, &uci_chan->pending); + spin_unlock_irqrestore(&uci_chan->lock, flags); + + if (mhi_dev->dev.power.wakeup) + pm_wakeup_hard_event(&mhi_dev->dev); + + wake_up(&uci_chan->wq); +} + +static void mhi_status_cb(struct mhi_device *mhi_dev, enum mhi_callback reason) +{ + struct uci_dev *uci_dev = dev_get_drvdata(&mhi_dev->dev); + struct uci_chan *uci_chan = &uci_dev->dl_chan; + unsigned long flags; + + if (reason == MHI_CB_DTR_SIGNAL) { + spin_lock_irqsave(&uci_chan->lock, flags); + uci_dev->tiocm = mhi_dev->tiocm; + spin_unlock_irqrestore(&uci_chan->lock, flags); + wake_up(&uci_chan->wq); + } +} + +/* .driver_data stores max mtu */ +static const struct mhi_device_id mhi_uci_match_table[] = { + { .chan = "LOOPBACK", .driver_data = 0x1000 }, + { .chan = "SAHARA", .driver_data = 0x8000 }, + { .chan = "DIAG", .driver_data = 0x1000 }, + { .chan = "EFS", .driver_data = 0x1000 }, + { .chan = "QMI0", .driver_data = 0x1000 }, + { .chan = "QMI1", .driver_data = 0x1000 }, + { .chan = "TF", .driver_data = 0x1000 }, + { .chan = "DCI", .driver_data = 0x1000 }, + { .chan = "DUN", .driver_data = 0x1000 }, + {}, +}; + +static struct mhi_driver mhi_uci_driver = { + .id_table = mhi_uci_match_table, + .remove = mhi_uci_remove, + .probe = mhi_uci_probe, + .ul_xfer_cb = mhi_ul_xfer_cb, + .dl_xfer_cb = mhi_dl_xfer_cb, + .status_cb = mhi_status_cb, + .driver = { + .name = MHI_UCI_DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int mhi_uci_init(void) +{ + int ret; + + ret = register_chrdev(0, MHI_UCI_DRIVER_NAME, &mhidev_fops); + if (ret < 0) + return ret; + + mhi_uci_drv.major = ret; + mhi_uci_drv.class = class_create(THIS_MODULE, MHI_UCI_DRIVER_NAME); + if (IS_ERR(mhi_uci_drv.class)) + return -ENODEV; + + mutex_init(&mhi_uci_drv.lock); + INIT_LIST_HEAD(&mhi_uci_drv.head); + + ret = mhi_driver_register(&mhi_uci_driver); + if (ret) + class_destroy(mhi_uci_drv.class); + + return ret; +} +module_init(mhi_uci_init); + +static void __exit mhi_uci_exit(void) +{ + if (mhi_uci_drv.major) { + unregister_chrdev_region(MKDEV(mhi_uci_drv.major, 0), + mhi_uci_drv.major); + mhi_uci_drv.major = 0; + } + + class_destroy(mhi_uci_drv.class); + + mhi_driver_unregister(&mhi_uci_driver); +} +module_exit(mhi_uci_exit); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("MHI_UCI"); +MODULE_DESCRIPTION("MHI UCI Driver");