blob: 79d2f1fe36394a91f43c3e92e41f9808a865d2e2 [file] [log] [blame]
/* Copyright (c) 2014-2019, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/diagchar.h>
#include <linux/sched.h>
#include <linux/err.h>
#include <linux/kmemleak.h>
#include <linux/ratelimit.h>
#include <linux/workqueue.h>
#include <linux/pm_runtime.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/dma-direction.h>
#include <linux/mhi.h>
#include <linux/delay.h>
#include <linux/vmalloc.h>
#include <asm/current.h>
#include <linux/atomic.h>
#include "diagmem.h"
#include "diagfwd_bridge.h"
#include "diagfwd_mhi.h"
#include "diag_ipc_logging.h"
#define SET_CH_CTXT(index, type) (((index & 0xFF) << 8) | (type & 0xFF))
#define GET_INFO_INDEX(val) ((val & 0xFF00) >> 8)
#define GET_CH_TYPE(val) ((val & 0x00FF))
#define CHANNELS_OPENED 0
#define OPEN_CHANNELS 1
#define CHANNELS_CLOSED 0
#define CLOSE_CHANNELS 1
#define DIAG_MHI_STRING_SZ 11
struct diag_mhi_info diag_mhi[NUM_MHI_DEV] = {
{
.id = MHI_1,
.dev_id = DIAGFWD_MDM,
.name = "MDM",
.enabled = 0,
.num_read = 0,
.mempool = POOL_TYPE_MDM,
.mempool_init = 0,
.mhi_wq = NULL,
.mhi_dev = NULL,
.read_ch = {
.type = TYPE_MHI_READ_CH,
},
.write_ch = {
.type = TYPE_MHI_WRITE_CH,
}
},
{
.id = MHI_2,
.dev_id = DIAGFWD_MDM_2,
.name = "MDM_2",
.enabled = 0,
.num_read = 0,
.mempool = POOL_TYPE_MDM2,
.mempool_init = 0,
.mhi_wq = NULL,
.mhi_dev = NULL,
.read_ch = {
.type = TYPE_MHI_READ_CH,
},
.write_ch = {
.type = TYPE_MHI_WRITE_CH,
}
},
#ifdef CONFIG_MHI_DCI
{
.dev_id = DIAGFWD_MDM_DCI,
.name = "MDM_DCI",
.enabled = 0,
.num_read = 0,
.mempool = POOL_TYPE_MDM_DCI,
.mempool_init = 0,
.mhi_wq = NULL,
.mhi_dev = NULL,
.read_ch = {
.type = TYPE_MHI_READ_CH,
},
.write_ch = {
.type = TYPE_MHI_WRITE_CH,
}
},
#endif
};
static int mhi_buf_tbl_add(struct diag_mhi_info *mhi_info, int type,
void *buf, int len)
{
struct diag_mhi_buf_tbl_t *item;
struct diag_mhi_ch_t *ch = NULL;
if (!mhi_info || !buf || len < 0)
return -EINVAL;
switch (type) {
case TYPE_MHI_READ_CH:
ch = &mhi_info->read_ch;
break;
case TYPE_MHI_WRITE_CH:
ch = &mhi_info->write_ch;
break;
default:
pr_err_ratelimited("diag: In %s, invalid type: %d\n",
__func__, type);
return -EINVAL;
}
item = kzalloc(sizeof(*item), GFP_ATOMIC);
if (!item)
return -ENOMEM;
kmemleak_not_leak(item);
item->buf = buf;
DIAG_LOG(DIAG_DEBUG_MHI,
"buffer %pK added to table of ch: %s\n", buf, mhi_info->name);
item->len = len;
list_add_tail(&item->link, &ch->buf_tbl);
return 0;
}
static void mhi_buf_tbl_remove(struct diag_mhi_info *mhi_info, int type,
void *buf, int len)
{
int found = 0;
unsigned long flags;
struct list_head *start, *temp;
struct diag_mhi_buf_tbl_t *item = NULL;
struct diag_mhi_ch_t *ch = NULL;
if (!mhi_info || !buf || len < 0)
return;
switch (type) {
case TYPE_MHI_READ_CH:
ch = &mhi_info->read_ch;
break;
case TYPE_MHI_WRITE_CH:
ch = &mhi_info->write_ch;
break;
default:
pr_err_ratelimited("diag: In %s, invalid type: %d\n",
__func__, type);
return;
}
spin_lock_irqsave(&ch->lock, flags);
list_for_each_safe(start, temp, &ch->buf_tbl) {
item = list_entry(start, struct diag_mhi_buf_tbl_t, link);
if (item->buf != buf)
continue;
list_del(&item->link);
if (type == TYPE_MHI_READ_CH) {
DIAG_LOG(DIAG_DEBUG_MHI,
"Callback received on buffer:%pK from mhi\n", buf);
diagmem_free(driver, item->buf, mhi_info->mempool);
}
kfree(item);
found = 1;
}
spin_unlock_irqrestore(&ch->lock, flags);
if (!found) {
pr_err_ratelimited("diag: In %s, unable to find buffer, ch: %pK, type: %d, buf: %pK\n",
__func__, ch, ch->type, buf);
}
}
static void mhi_buf_tbl_clear(struct diag_mhi_info *mhi_info)
{
unsigned long flags;
struct list_head *start, *temp;
struct diag_mhi_buf_tbl_t *item = NULL;
struct diag_mhi_buf_tbl_t *tp = NULL, *tp_temp = NULL;
struct diag_mhi_ch_t *ch = NULL;
unsigned char *buf;
if (!mhi_info || !mhi_info->enabled)
return;
/* Clear all the pending reads */
ch = &mhi_info->read_ch;
/* At this point, the channel should already by closed */
if (!(atomic_read(&ch->opened))) {
spin_lock_irqsave(&ch->lock, flags);
list_for_each_safe(start, temp, &ch->buf_tbl) {
item = list_entry(start, struct diag_mhi_buf_tbl_t,
link);
list_del(&item->link);
buf = item->buf;
list_for_each_entry_safe(tp, tp_temp,
&mhi_info->read_done_list, link) {
if (tp->buf == buf) {
DIAG_LOG(DIAG_DEBUG_MHI,
"buffer:%pK removed from table for ch:%s\n",
buf, mhi_info->name);
list_del(&tp->link);
kfree(tp);
tp = NULL;
}
}
diagmem_free(driver, item->buf, mhi_info->mempool);
kfree(item);
}
spin_unlock_irqrestore(&ch->lock, flags);
}
/* Clear all the pending writes */
ch = &mhi_info->write_ch;
/* At this point, the channel should already by closed */
if (!(atomic_read(&ch->opened))) {
spin_lock_irqsave(&ch->lock, flags);
list_for_each_safe(start, temp, &ch->buf_tbl) {
item = list_entry(start, struct diag_mhi_buf_tbl_t,
link);
list_del(&item->link);
diag_remote_dev_write_done(mhi_info->dev_id, item->buf,
item->len, mhi_info->id);
kfree(item);
}
spin_unlock_irqrestore(&ch->lock, flags);
}
}
static int __mhi_close(struct diag_mhi_info *mhi_info, int close_flag)
{
if (!mhi_info)
return -EIO;
if (!mhi_info->enabled)
return -ENODEV;
atomic_set(&(mhi_info->read_ch.opened), 0);
atomic_set(&(mhi_info->write_ch.opened), 0);
flush_workqueue(mhi_info->mhi_wq);
if (close_flag == CLOSE_CHANNELS) {
mutex_lock(&mhi_info->ch_mutex);
mhi_unprepare_from_transfer(mhi_info->mhi_dev);
mutex_unlock(&mhi_info->ch_mutex);
}
mhi_buf_tbl_clear(mhi_info);
diag_remote_dev_close(mhi_info->dev_id);
return 0;
}
static int mhi_close(int id)
{
if (id < 0 || id >= NUM_MHI_DEV) {
pr_err("diag: In %s, invalid index %d\n", __func__, id);
return -EINVAL;
}
if (!diag_mhi[id].enabled)
return -ENODEV;
/*
* This function is called whenever the channel needs to be closed
* explicitly by Diag. Close both the read and write channels (denoted
* by CLOSE_CHANNELS flag)
*/
return __mhi_close(&diag_mhi[id], CLOSE_CHANNELS);
}
static void mhi_close_work_fn(struct work_struct *work)
{
struct diag_mhi_info *mhi_info = container_of(work,
struct diag_mhi_info,
close_work);
/*
* This is a part of work function which is queued after the channels
* are explicitly closed. Do not close channels again (denoted by
* CHANNELS_CLOSED flag)
*/
if (mhi_info)
__mhi_close(mhi_info, CHANNELS_CLOSED);
}
static int __mhi_open(struct diag_mhi_info *mhi_info, int open_flag)
{
int err = 0;
if (!mhi_info)
return -EIO;
if (!mhi_info->enabled)
return -ENODEV;
if (open_flag == OPEN_CHANNELS) {
if ((atomic_read(&(mhi_info->read_ch.opened))) &&
(atomic_read(&(mhi_info->write_ch.opened))))
return 0;
mutex_lock(&mhi_info->ch_mutex);
err = mhi_prepare_for_transfer(mhi_info->mhi_dev);
mutex_unlock(&mhi_info->ch_mutex);
if (err) {
pr_err("diag: In %s, unable to open ch, err: %d\n",
__func__, err);
goto fail;
}
atomic_set(&mhi_info->read_ch.opened, 1);
atomic_set(&mhi_info->write_ch.opened, 1);
DIAG_LOG(DIAG_DEBUG_BRIDGE,
"opened mhi read/write channel, port: %d\n",
mhi_info->id);
} else if (open_flag == CHANNELS_OPENED) {
if (!atomic_read(&(mhi_info->read_ch.opened)) ||
!atomic_read(&(mhi_info->write_ch.opened))) {
return -ENODEV;
}
}
diag_remote_dev_open(mhi_info->dev_id);
queue_work(mhi_info->mhi_wq, &(mhi_info->read_work));
return 0;
fail:
pr_err("diag: Failed to open mhi channlels, err: %d\n", err);
mhi_close(mhi_info->id);
return err;
}
static int mhi_open(int id)
{
int err = 0;
if (id < 0 || id >= NUM_MHI_DEV) {
pr_err("diag: In %s, invalid index %d\n", __func__, id);
return -EINVAL;
}
/*
* This function is called whenever the channel needs to be opened
* explicitly by Diag. Open both the read and write channels (denoted by
* OPEN_CHANNELS flag)
*/
err = __mhi_open(&diag_mhi[id], OPEN_CHANNELS);
if (err)
return err;
diag_remote_dev_open(diag_mhi[id].dev_id);
queue_work(diag_mhi[id].mhi_wq, &(diag_mhi[id].read_work));
return 0;
}
static void mhi_open_work_fn(struct work_struct *work)
{
struct diag_mhi_info *mhi_info = container_of(work,
struct diag_mhi_info,
open_work);
/*
* This is a part of work function which is queued after the channels
* are explicitly opened. Do not open channels again (denoted by
* CHANNELS_OPENED flag)
*/
if (mhi_info) {
diag_remote_dev_open(mhi_info->dev_id);
queue_work(mhi_info->mhi_wq, &(mhi_info->read_work));
}
}
static void mhi_read_done_work_fn(struct work_struct *work)
{
unsigned char *buf = NULL;
int err = 0;
int len;
unsigned long flags;
struct diag_mhi_buf_tbl_t *tp;
struct diag_mhi_info *mhi_info = container_of(work,
struct diag_mhi_info,
read_done_work);
if (!mhi_info)
return;
do {
if (!(atomic_read(&(mhi_info->read_ch.opened))))
break;
spin_lock_irqsave(&mhi_info->read_ch.lock, flags);
if (list_empty(&mhi_info->read_done_list)) {
spin_unlock_irqrestore(&mhi_info->read_ch.lock, flags);
break;
}
tp = list_first_entry(&mhi_info->read_done_list,
struct diag_mhi_buf_tbl_t, link);
list_del(&tp->link);
buf = tp->buf;
len = tp->len;
kfree(tp);
spin_unlock_irqrestore(&mhi_info->read_ch.lock, flags);
if (!buf)
break;
DIAG_LOG(DIAG_DEBUG_BRIDGE,
"read from mhi port %d buf %pK len:%d\n",
mhi_info->id, buf, len);
/*
* The read buffers can come after the MHI channels are closed.
* If the channels are closed at the time of read, discard the
* buffers here and do not forward them to the mux layer.
*/
if ((atomic_read(&(mhi_info->read_ch.opened)))) {
err = diag_remote_dev_read_done(mhi_info->dev_id, buf,
len);
if (err) {
DIAG_LOG(DIAG_DEBUG_MHI,
"diag: remove buf entry %pK for failing flush to sink\n",
buf);
mhi_buf_tbl_remove(mhi_info, TYPE_MHI_READ_CH,
buf, len);
break;
}
} else {
DIAG_LOG(DIAG_DEBUG_MHI,
"diag: remove buf entry %pK if channel is closed\n",
buf);
mhi_buf_tbl_remove(mhi_info, TYPE_MHI_READ_CH, buf,
len);
break;
}
} while (buf);
}
static void mhi_read_work_fn(struct work_struct *work)
{
int err = 0;
unsigned char *buf = NULL;
enum mhi_flags mhi_flags = MHI_EOT;
struct diag_mhi_ch_t *read_ch = NULL;
unsigned long flags;
struct diag_mhi_info *mhi_info = container_of(work,
struct diag_mhi_info,
read_work);
if (!mhi_info)
return;
read_ch = &mhi_info->read_ch;
do {
if (!(atomic_read(&(read_ch->opened))))
break;
spin_lock_irqsave(&read_ch->lock, flags);
buf = diagmem_alloc(driver, DIAG_MDM_BUF_SIZE,
mhi_info->mempool);
if (!buf) {
spin_unlock_irqrestore(&read_ch->lock, flags);
break;
}
DIAG_LOG(DIAG_DEBUG_MHI,
"Allocated buffer %pK, ch: %s\n", buf, mhi_info->name);
err = mhi_buf_tbl_add(mhi_info, TYPE_MHI_READ_CH, buf,
DIAG_MDM_BUF_SIZE);
if (err) {
diagmem_free(driver, buf, mhi_info->mempool);
spin_unlock_irqrestore(&read_ch->lock, flags);
goto fail;
}
DIAG_LOG(DIAG_DEBUG_BRIDGE,
"queueing a read buf %pK, ch: %s\n",
buf, mhi_info->name);
err = mhi_queue_buf(mhi_info->mhi_dev, DMA_FROM_DEVICE,
buf, DIAG_MDM_BUF_SIZE, mhi_flags);
spin_unlock_irqrestore(&read_ch->lock, flags);
if (err) {
pr_err_ratelimited("diag: Unable to read from MHI channel %s, err: %d\n",
mhi_info->name, err);
goto fail;
}
} while (buf);
return;
fail:
DIAG_LOG(DIAG_DEBUG_MHI,
"diag: remove buf entry %pK for error\n", buf);
mhi_buf_tbl_remove(mhi_info, TYPE_MHI_READ_CH, buf, DIAG_MDM_BUF_SIZE);
queue_work(mhi_info->mhi_wq, &mhi_info->read_work);
}
static int mhi_queue_read(int id)
{
if (id < 0 || id >= NUM_MHI_DEV) {
pr_err_ratelimited("diag: In %s, invalid index %d\n", __func__,
id);
return -EINVAL;
}
queue_work(diag_mhi[id].mhi_wq, &(diag_mhi[id].read_work));
return 0;
}
static int mhi_write(int id, unsigned char *buf, int len, int ctxt)
{
int err = 0;
enum mhi_flags mhi_flags = MHI_EOT;
unsigned long flags;
struct diag_mhi_ch_t *ch = NULL;
if (id < 0 || id >= NUM_MHI_DEV) {
pr_err_ratelimited("diag: In %s, invalid index %d\n", __func__,
id);
return -EINVAL;
}
if (!buf || len <= 0) {
pr_err("diag: In %s, ch %d, invalid buf %pK len %d\n",
__func__, id, buf, len);
return -EINVAL;
}
if (!diag_mhi[id].enabled) {
pr_err_ratelimited("diag: In %s, MHI channel %s is not enabled\n",
__func__, diag_mhi[id].name);
return -EIO;
}
ch = &diag_mhi[id].write_ch;
if (!(atomic_read(&(ch->opened)))) {
pr_err_ratelimited("diag: In %s, MHI write channel %s is not open\n",
__func__, diag_mhi[id].name);
return -EIO;
}
spin_lock_irqsave(&ch->lock, flags);
err = mhi_buf_tbl_add(&diag_mhi[id], TYPE_MHI_WRITE_CH, buf,
len);
if (err) {
spin_unlock_irqrestore(&ch->lock, flags);
goto fail;
}
err = mhi_queue_buf(diag_mhi[id].mhi_dev, DMA_TO_DEVICE, buf,
len, mhi_flags);
spin_unlock_irqrestore(&ch->lock, flags);
if (err) {
DIAG_LOG(DIAG_DEBUG_MHI,
"Cannot write to MHI channel %s, len %d, err: %d\n",
diag_mhi[id].name, len, err);
mhi_buf_tbl_remove(&diag_mhi[id], TYPE_MHI_WRITE_CH, buf, len);
goto fail;
}
return 0;
fail:
return err;
}
static int mhi_fwd_complete(int id, unsigned char *buf, int len, int ctxt)
{
if (id < 0 || id >= NUM_MHI_DEV) {
pr_err_ratelimited("diag: In %s, invalid index %d\n", __func__,
id);
return -EINVAL;
}
if (!buf)
return -EINVAL;
DIAG_LOG(DIAG_DEBUG_MHI,
"Remove buffer from mhi read table after write completion %pK len:%d\n",
buf, len);
mhi_buf_tbl_remove(&diag_mhi[id], TYPE_MHI_READ_CH, buf, len);
queue_work(diag_mhi[id].mhi_wq, &(diag_mhi[id].read_work));
return 0;
}
static int mhi_remote_proc_check(int index)
{
return diag_mhi[index].enabled;
}
static struct diag_mhi_info *diag_get_mhi_info(struct mhi_device *mhi_dev)
{
struct diag_mhi_info *mhi_info = NULL;
int i;
for (i = 0; i < NUM_MHI_DEV; i++) {
mhi_info = &diag_mhi[i];
if (mhi_info->mhi_dev == mhi_dev)
return mhi_info;
}
return NULL;
}
static void diag_mhi_read_cb(struct mhi_device *mhi_dev,
struct mhi_result *result)
{
struct diag_mhi_info *mhi_info = NULL;
struct diag_mhi_buf_tbl_t *item = NULL;
struct diag_mhi_buf_tbl_t *tp = NULL, *temp = NULL;
unsigned long flags;
void *buf = NULL;
uint8_t queue_read = 0;
if (!mhi_dev)
return;
mhi_info = diag_get_mhi_info(mhi_dev);
if (!mhi_info)
return;
buf = result->buf_addr;
if (!buf)
return;
if (atomic_read(&mhi_info->read_ch.opened) &&
result->transaction_status != -ENOTCONN) {
spin_lock_irqsave(&mhi_info->read_ch.lock, flags);
tp = kmalloc(sizeof(*tp), GFP_ATOMIC);
if (!tp) {
DIAG_LOG(DIAG_DEBUG_BRIDGE,
"no mem for list\n");
spin_unlock_irqrestore(&mhi_info->read_ch.lock, flags);
return;
}
list_for_each_entry_safe(item, temp,
&mhi_info->read_ch.buf_tbl, link) {
if (item->buf == buf) {
DIAG_LOG(DIAG_DEBUG_MHI,
"Callback received on buffer:%pK from mhi\n",
buf);
tp->buf = buf;
tp->len = result->bytes_xferd;
list_add_tail(&tp->link,
&mhi_info->read_done_list);
queue_read = 1;
break;
}
}
spin_unlock_irqrestore(&mhi_info->read_ch.lock, flags);
if (queue_read)
queue_work(mhi_info->mhi_wq,
&(mhi_info->read_done_work));
} else {
DIAG_LOG(DIAG_DEBUG_MHI,
"Removing buf entry from read table if ch is not open %pK\n",
buf);
mhi_buf_tbl_remove(mhi_info, TYPE_MHI_READ_CH, buf,
result->bytes_xferd);
}
}
static void diag_mhi_write_cb(struct mhi_device *mhi_dev,
struct mhi_result *result)
{
struct diag_mhi_info *mhi_info = NULL;
void *buf = NULL;
if (!mhi_dev)
return;
mhi_info = diag_get_mhi_info(mhi_dev);
if (!mhi_info)
return;
buf = result->buf_addr;
if (!buf) {
pr_err_ratelimited("diag: In %s, unable to de-serialize the data\n",
__func__);
return;
}
mhi_buf_tbl_remove(mhi_info, TYPE_MHI_WRITE_CH, buf,
result->bytes_xferd);
diag_remote_dev_write_done(mhi_info->dev_id, buf,
result->bytes_xferd,
mhi_info->id);
}
static void diag_mhi_remove(struct mhi_device *mhi_dev)
{
unsigned long flags;
struct diag_mhi_info *mhi_info = NULL;
if (!mhi_dev)
return;
mhi_info = diag_get_mhi_info(mhi_dev);
if (!mhi_info)
return;
if (!mhi_info->enabled)
return;
__mhi_close(mhi_info, CHANNELS_CLOSED);
spin_lock_irqsave(&mhi_info->lock, flags);
mhi_info->enabled = 0;
spin_unlock_irqrestore(&mhi_info->lock, flags);
}
static int diag_mhi_probe(struct mhi_device *mhi_dev,
const struct mhi_device_id *id)
{
int index;
int ret = 0;
unsigned long flags;
struct diag_mhi_info *mhi_info;
DIAG_LOG(DIAG_DEBUG_BRIDGE, "received probe\n");
for (index = 0; index < NUM_MHI_DEV; index++) {
if (!diag_mhi[index].enabled)
break;
}
if (index == NUM_MHI_DEV)
return -ENODEV;
mhi_info = &diag_mhi[index];
diag_mhi[index].mhi_dev = mhi_dev;
DIAG_LOG(DIAG_DEBUG_BRIDGE,
"diag: mhi device is ready to open\n");
spin_lock_irqsave(&mhi_info->lock, flags);
mhi_info->enabled = 1;
spin_unlock_irqrestore(&mhi_info->lock, flags);
__mhi_open(&diag_mhi[index], OPEN_CHANNELS);
queue_work(diag_mhi[index].mhi_wq,
&(diag_mhi[index].open_work));
return ret;
}
static struct diag_remote_dev_ops diag_mhi_fwd_ops = {
.open = mhi_open,
.close = mhi_close,
.queue_read = mhi_queue_read,
.write = mhi_write,
.fwd_complete = mhi_fwd_complete,
.remote_proc_check = mhi_remote_proc_check,
};
static void diag_mhi_dev_exit(int dev)
{
struct diag_mhi_info *mhi_info = NULL;
mhi_info = &diag_mhi[dev];
if (!mhi_info)
return;
if (mhi_info->mhi_wq)
destroy_workqueue(mhi_info->mhi_wq);
mhi_close(mhi_info->id);
if (mhi_info->mempool_init)
diagmem_exit(driver, mhi_info->mempool);
}
int diag_mhi_init(void)
{
int i;
int err = 0;
struct diag_mhi_info *mhi_info = NULL;
char wq_name[DIAG_MHI_NAME_SZ + DIAG_MHI_STRING_SZ];
for (i = 0; i < NUM_MHI_DEV; i++) {
mhi_info = &diag_mhi[i];
spin_lock_init(&mhi_info->lock);
spin_lock_init(&mhi_info->read_ch.lock);
spin_lock_init(&mhi_info->write_ch.lock);
mutex_init(&mhi_info->ch_mutex);
INIT_LIST_HEAD(&mhi_info->read_ch.buf_tbl);
INIT_LIST_HEAD(&mhi_info->write_ch.buf_tbl);
atomic_set(&(mhi_info->read_ch.opened), 0);
atomic_set(&(mhi_info->write_ch.opened), 0);
INIT_WORK(&(mhi_info->read_work), mhi_read_work_fn);
INIT_LIST_HEAD(&mhi_info->read_done_list);
INIT_WORK(&(mhi_info->read_done_work), mhi_read_done_work_fn);
INIT_WORK(&(mhi_info->open_work), mhi_open_work_fn);
INIT_WORK(&(mhi_info->close_work), mhi_close_work_fn);
strlcpy(wq_name, "diag_mhi_", sizeof(wq_name));
strlcat(wq_name, mhi_info->name, sizeof(wq_name));
diagmem_init(driver, mhi_info->mempool);
mhi_info->mempool_init = 1;
mhi_info->mhi_wq = create_singlethread_workqueue(wq_name);
if (!mhi_info->mhi_wq)
goto fail;
err = diagfwd_bridge_register(mhi_info->dev_id, mhi_info->id,
&diag_mhi_fwd_ops);
if (err) {
pr_err("diag: Unable to register MHI channel %d with bridge, err: %d\n",
i, err);
goto fail;
}
DIAG_LOG(DIAG_DEBUG_BRIDGE, "mhi port %d is initailzed\n", i);
}
return 0;
fail:
diag_mhi_dev_exit(i);
return -ENOMEM;
}
void diag_mhi_exit(void)
{
int i;
for (i = 0; i < NUM_MHI_DEV; i++) {
diag_mhi_dev_exit(i);
}
}
static const struct mhi_device_id diag_mhi_match_table[] = {
{ .chan = "DIAG", .driver_data = MHI_1 },
#ifdef CONFIG_MHI_DCI
{ .chan = "DCI", .driver_data = MHI_DCI_1 },
#endif
{},
};
static struct mhi_driver diag_mhi_driver = {
.id_table = diag_mhi_match_table,
.remove = diag_mhi_remove,
.probe = diag_mhi_probe,
.ul_xfer_cb = diag_mhi_write_cb,
.dl_xfer_cb = diag_mhi_read_cb,
.driver = {
.name = "diag_mhi_driver",
.owner = THIS_MODULE,
},
};
void diag_register_with_mhi(void)
{
int ret = 0;
ret = diag_remote_init();
if (ret)
return;
ret = diag_mhi_init();
if (ret) {
diag_remote_exit();
return;
}
mhi_driver_register(&diag_mhi_driver);
}
void diag_unregister_mhi(void)
{
mhi_driver_unregister(&diag_mhi_driver);
diag_mhi_exit();
diag_remote_exit();
}