blob: 4c4635aabce69a5af3242190012dea6c677a102b [file] [log] [blame]
/* Copyright (c) 2012, 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/uaccess.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/list.h>
#include <linux/socket.h>
#include <linux/gfp.h>
#include <linux/qmi_encdec.h>
#include <mach/msm_qmi_interface.h>
#include <mach/msm_ipc_router.h>
#include "msm_qmi_interface_priv.h"
static LIST_HEAD(svc_event_nb_list);
static DEFINE_MUTEX(svc_event_nb_list_lock);
struct elem_info qmi_response_type_v01_ei[] = {
{
.data_type = QMI_SIGNED_2_BYTE_ENUM,
.elem_len = 1,
.elem_size = sizeof(uint16_t),
.is_array = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
.offset = offsetof(struct qmi_response_type_v01,
result),
.ei_array = NULL,
},
{
.data_type = QMI_SIGNED_2_BYTE_ENUM,
.elem_len = 1,
.elem_size = sizeof(uint16_t),
.is_array = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
.offset = offsetof(struct qmi_response_type_v01,
error),
.ei_array = NULL,
},
{
.data_type = QMI_EOTI,
.elem_len = 0,
.elem_size = 0,
.is_array = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
.offset = 0,
.ei_array = NULL,
},
};
static void qmi_event_notify(unsigned event, void *priv)
{
struct qmi_handle *handle = (struct qmi_handle *)priv;
unsigned long flags;
if (!handle)
return;
mutex_lock(&handle->handle_lock);
if (handle->handle_reset) {
mutex_unlock(&handle->handle_lock);
return;
}
switch (event) {
case MSM_IPC_ROUTER_READ_CB:
spin_lock_irqsave(&handle->notify_lock, flags);
handle->notify(handle, QMI_RECV_MSG, handle->notify_priv);
spin_unlock_irqrestore(&handle->notify_lock, flags);
break;
default:
break;
}
mutex_unlock(&handle->handle_lock);
}
struct qmi_handle *qmi_handle_create(
void (*notify)(struct qmi_handle *handle,
enum qmi_event_type event, void *notify_priv),
void *notify_priv)
{
struct qmi_handle *temp_handle;
struct msm_ipc_port *port_ptr;
temp_handle = kzalloc(sizeof(struct qmi_handle), GFP_KERNEL);
if (!temp_handle) {
pr_err("%s: Failure allocating client handle\n", __func__);
return NULL;
}
port_ptr = msm_ipc_router_create_port(qmi_event_notify,
(void *)temp_handle);
if (!port_ptr) {
pr_err("%s: IPC router port creation failed\n", __func__);
kfree(temp_handle);
return NULL;
}
temp_handle->src_port = port_ptr;
temp_handle->next_txn_id = 1;
INIT_LIST_HEAD(&temp_handle->txn_list);
mutex_init(&temp_handle->handle_lock);
spin_lock_init(&temp_handle->notify_lock);
temp_handle->notify = notify;
temp_handle->notify_priv = notify_priv;
temp_handle->handle_reset = 0;
init_waitqueue_head(&temp_handle->reset_waitq);
return temp_handle;
}
EXPORT_SYMBOL(qmi_handle_create);
static void clean_txn_info(struct qmi_handle *handle)
{
struct qmi_txn *txn_handle, *temp_txn_handle;
list_for_each_entry_safe(txn_handle, temp_txn_handle,
&handle->txn_list, list) {
if (txn_handle->type == QMI_ASYNC_TXN) {
list_del(&txn_handle->list);
kfree(txn_handle);
} else if (txn_handle->type == QMI_SYNC_TXN) {
wake_up(&txn_handle->wait_q);
}
}
}
int qmi_handle_destroy(struct qmi_handle *handle)
{
int rc;
if (!handle)
return -EINVAL;
mutex_lock(&handle->handle_lock);
handle->handle_reset = 1;
clean_txn_info(handle);
mutex_unlock(&handle->handle_lock);
rc = wait_event_interruptible(handle->reset_waitq,
list_empty(&handle->txn_list));
/* TODO: Destroy client owned transaction */
msm_ipc_router_close_port((struct msm_ipc_port *)(handle->src_port));
kfree(handle->dest_info);
kfree(handle);
return 0;
}
EXPORT_SYMBOL(qmi_handle_destroy);
int qmi_register_ind_cb(struct qmi_handle *handle,
void (*ind_cb)(struct qmi_handle *handle,
unsigned int msg_id, void *msg,
unsigned int msg_len, void *ind_cb_priv),
void *ind_cb_priv)
{
if (!handle)
return -EINVAL;
mutex_lock(&handle->handle_lock);
if (handle->handle_reset) {
mutex_unlock(&handle->handle_lock);
return -ENETRESET;
}
handle->ind_cb = ind_cb;
handle->ind_cb_priv = ind_cb_priv;
mutex_unlock(&handle->handle_lock);
return 0;
}
EXPORT_SYMBOL(qmi_register_ind_cb);
static int qmi_encode_and_send_req(struct qmi_txn **ret_txn_handle,
struct qmi_handle *handle, enum txn_type type,
struct msg_desc *req_desc, void *req, unsigned int req_len,
struct msg_desc *resp_desc, void *resp, unsigned int resp_len,
void (*resp_cb)(struct qmi_handle *handle,
unsigned int msg_id, void *msg,
void *resp_cb_data),
void *resp_cb_data)
{
struct qmi_txn *txn_handle;
int rc, encoded_req_len;
void *encoded_req;
if (!handle || !handle->dest_info ||
!req_desc || !req || !resp_desc || !resp)
return -EINVAL;
mutex_lock(&handle->handle_lock);
if (handle->handle_reset) {
mutex_unlock(&handle->handle_lock);
return -ENETRESET;
}
/* Allocate Transaction Info */
txn_handle = kzalloc(sizeof(struct qmi_txn), GFP_KERNEL);
if (!txn_handle) {
pr_err("%s: Failed to allocate txn handle\n", __func__);
mutex_unlock(&handle->handle_lock);
return -ENOMEM;
}
txn_handle->type = type;
INIT_LIST_HEAD(&txn_handle->list);
init_waitqueue_head(&txn_handle->wait_q);
/* Cache the parameters passed & mark it as sync*/
txn_handle->handle = handle;
txn_handle->resp_desc = resp_desc;
txn_handle->resp = resp;
txn_handle->resp_len = resp_len;
txn_handle->resp_received = 0;
txn_handle->resp_cb = resp_cb;
txn_handle->resp_cb_data = resp_cb_data;
/* Encode the request msg */
encoded_req_len = req_desc->max_msg_len + QMI_HEADER_SIZE;
encoded_req = kmalloc(encoded_req_len, GFP_KERNEL);
if (!encoded_req) {
pr_err("%s: Failed to allocate req_msg_buf\n", __func__);
rc = -ENOMEM;
goto encode_and_send_req_err1;
}
rc = qmi_kernel_encode(req_desc,
(void *)(encoded_req + QMI_HEADER_SIZE),
req_desc->max_msg_len, req);
if (rc < 0) {
pr_err("%s: Encode Failure %d\n", __func__, rc);
goto encode_and_send_req_err2;
}
encoded_req_len = rc;
/* Encode the header & Add to the txn_list */
if (!handle->next_txn_id)
handle->next_txn_id++;
txn_handle->txn_id = handle->next_txn_id++;
encode_qmi_header(encoded_req, QMI_REQUEST_CONTROL_FLAG,
txn_handle->txn_id, req_desc->msg_id,
encoded_req_len);
encoded_req_len += QMI_HEADER_SIZE;
list_add_tail(&txn_handle->list, &handle->txn_list);
/* Send the request */
rc = msm_ipc_router_send_msg((struct msm_ipc_port *)(handle->src_port),
(struct msm_ipc_addr *)handle->dest_info,
encoded_req, encoded_req_len);
if (rc < 0) {
pr_err("%s: send_msg failed %d\n", __func__, rc);
goto encode_and_send_req_err3;
}
mutex_unlock(&handle->handle_lock);
kfree(encoded_req);
if (ret_txn_handle)
*ret_txn_handle = txn_handle;
return 0;
encode_and_send_req_err3:
list_del(&txn_handle->list);
encode_and_send_req_err2:
kfree(encoded_req);
encode_and_send_req_err1:
kfree(txn_handle);
mutex_unlock(&handle->handle_lock);
return rc;
}
int qmi_send_req_wait(struct qmi_handle *handle,
struct msg_desc *req_desc,
void *req, unsigned int req_len,
struct msg_desc *resp_desc,
void *resp, unsigned int resp_len,
unsigned long timeout_ms)
{
struct qmi_txn *txn_handle = NULL;
int rc;
/* Encode and send the request */
rc = qmi_encode_and_send_req(&txn_handle, handle, QMI_SYNC_TXN,
req_desc, req, req_len,
resp_desc, resp, resp_len,
NULL, NULL);
if (rc < 0) {
pr_err("%s: Error encode & send req: %d\n", __func__, rc);
return rc;
}
/* Wait for the response */
if (!timeout_ms) {
rc = wait_event_interruptible(txn_handle->wait_q,
(txn_handle->resp_received ||
handle->handle_reset));
} else {
rc = wait_event_interruptible_timeout(txn_handle->wait_q,
(txn_handle->resp_received ||
handle->handle_reset),
msecs_to_jiffies(timeout_ms));
if (rc == 0)
rc = -ETIMEDOUT;
}
mutex_lock(&handle->handle_lock);
if (!txn_handle->resp_received) {
pr_err("%s: Response Wait Error %d\n", __func__, rc);
if (handle->handle_reset)
rc = -ENETRESET;
if (rc >= 0)
rc = -EFAULT;
goto send_req_wait_err;
}
rc = 0;
send_req_wait_err:
list_del(&txn_handle->list);
kfree(txn_handle);
mutex_unlock(&handle->handle_lock);
wake_up(&handle->reset_waitq);
return rc;
}
EXPORT_SYMBOL(qmi_send_req_wait);
int qmi_send_req_nowait(struct qmi_handle *handle,
struct msg_desc *req_desc,
void *req, unsigned int req_len,
struct msg_desc *resp_desc,
void *resp, unsigned int resp_len,
void (*resp_cb)(struct qmi_handle *handle,
unsigned int msg_id, void *msg,
void *resp_cb_data),
void *resp_cb_data)
{
return qmi_encode_and_send_req(NULL, handle, QMI_ASYNC_TXN,
req_desc, req, req_len,
resp_desc, resp, resp_len,
resp_cb, resp_cb_data);
}
EXPORT_SYMBOL(qmi_send_req_nowait);
static struct qmi_txn *find_txn_handle(struct qmi_handle *handle,
uint16_t txn_id)
{
struct qmi_txn *txn_handle;
list_for_each_entry(txn_handle, &handle->txn_list, list) {
if (txn_handle->txn_id == txn_id)
return txn_handle;
}
return NULL;
}
static int handle_qmi_response(struct qmi_handle *handle,
unsigned char *resp_msg, uint16_t txn_id,
uint16_t msg_id, uint16_t msg_len)
{
struct qmi_txn *txn_handle;
int rc;
/* Find the transaction handle */
txn_handle = find_txn_handle(handle, txn_id);
if (!txn_handle) {
pr_err("%s Response received for non-existent txn_id %d\n",
__func__, txn_id);
return -EINVAL;
}
/* Decode the message */
rc = qmi_kernel_decode(txn_handle->resp_desc, txn_handle->resp,
(void *)(resp_msg + QMI_HEADER_SIZE), msg_len);
if (rc < 0) {
pr_err("%s: Response Decode Failure <%d: %d: %d> rc: %d\n",
__func__, txn_id, msg_id, msg_len, rc);
wake_up(&txn_handle->wait_q);
if (txn_handle->type == QMI_ASYNC_TXN) {
list_del(&txn_handle->list);
kfree(txn_handle);
}
return rc;
}
/* Handle async or sync resp */
switch (txn_handle->type) {
case QMI_SYNC_TXN:
txn_handle->resp_received = 1;
wake_up(&txn_handle->wait_q);
rc = 0;
break;
case QMI_ASYNC_TXN:
if (txn_handle->resp_cb)
txn_handle->resp_cb(txn_handle->handle, msg_id,
txn_handle->resp,
txn_handle->resp_cb_data);
list_del(&txn_handle->list);
kfree(txn_handle);
rc = 0;
break;
default:
pr_err("%s: Unrecognized transaction type\n", __func__);
return -EFAULT;
}
return rc;
}
static int handle_qmi_indication(struct qmi_handle *handle, void *msg,
unsigned int msg_id, unsigned int msg_len)
{
if (handle->ind_cb)
handle->ind_cb(handle, msg_id, msg,
msg_len, handle->ind_cb_priv);
return 0;
}
int qmi_recv_msg(struct qmi_handle *handle)
{
unsigned int recv_msg_len;
unsigned char *recv_msg = NULL;
struct msm_ipc_addr src_addr;
unsigned char cntl_flag;
uint16_t txn_id, msg_id, msg_len;
int rc;
if (!handle)
return -EINVAL;
mutex_lock(&handle->handle_lock);
if (handle->handle_reset) {
mutex_unlock(&handle->handle_lock);
return -ENETRESET;
}
/* Read the messages */
rc = msm_ipc_router_read_msg((struct msm_ipc_port *)(handle->src_port),
&src_addr, &recv_msg, &recv_msg_len);
if (rc < 0) {
pr_err("%s: Read failed %d\n", __func__, rc);
mutex_unlock(&handle->handle_lock);
return rc;
}
/* Decode the header & Handle the req, resp, indication message */
decode_qmi_header(recv_msg, &cntl_flag, &txn_id, &msg_id, &msg_len);
switch (cntl_flag) {
case QMI_RESPONSE_CONTROL_FLAG:
rc = handle_qmi_response(handle, recv_msg,
txn_id, msg_id, msg_len);
break;
case QMI_INDICATION_CONTROL_FLAG:
rc = handle_qmi_indication(handle, recv_msg, msg_id, msg_len);
break;
default:
rc = -EFAULT;
pr_err("%s: Unsupported message type %d\n",
__func__, cntl_flag);
break;
}
kfree(recv_msg);
mutex_unlock(&handle->handle_lock);
return rc;
}
EXPORT_SYMBOL(qmi_recv_msg);
int qmi_connect_to_service(struct qmi_handle *handle,
uint32_t service_id, uint32_t instance_id)
{
struct msm_ipc_port_name svc_name;
struct msm_ipc_server_info svc_info;
struct msm_ipc_addr *svc_dest_addr;
int rc;
if (!handle)
return -EINVAL;
svc_dest_addr = kzalloc(sizeof(struct msm_ipc_addr),
GFP_KERNEL);
if (!svc_dest_addr) {
pr_err("%s: Failure allocating memory\n", __func__);
return -ENOMEM;
}
svc_name.service = service_id;
svc_name.instance = instance_id;
rc = msm_ipc_router_lookup_server_name(&svc_name, &svc_info, 1, 0xFF);
if (rc <= 0) {
pr_err("%s: Server not found\n", __func__);
return -ENODEV;
}
svc_dest_addr->addrtype = MSM_IPC_ADDR_ID;
svc_dest_addr->addr.port_addr.node_id = svc_info.node_id;
svc_dest_addr->addr.port_addr.port_id = svc_info.port_id;
mutex_lock(&handle->handle_lock);
if (handle->handle_reset) {
mutex_unlock(&handle->handle_lock);
return -ENETRESET;
}
handle->dest_info = svc_dest_addr;
mutex_unlock(&handle->handle_lock);
return 0;
}
EXPORT_SYMBOL(qmi_connect_to_service);
static struct svc_event_nb *find_svc_event_nb_by_name(const char *name)
{
struct svc_event_nb *temp;
list_for_each_entry(temp, &svc_event_nb_list, list) {
if (!strncmp(name, temp->pdriver_name,
sizeof(temp->pdriver_name)))
return temp;
}
return NULL;
}
static int qmi_svc_event_probe(struct platform_device *pdev)
{
struct svc_event_nb *temp;
unsigned long flags;
mutex_lock(&svc_event_nb_list_lock);
temp = find_svc_event_nb_by_name(pdev->name);
if (!temp) {
mutex_unlock(&svc_event_nb_list_lock);
return -EINVAL;
}
spin_lock_irqsave(&temp->nb_lock, flags);
temp->svc_avail = 1;
raw_notifier_call_chain(&temp->svc_event_rcvr_list,
QMI_SERVER_ARRIVE, NULL);
spin_unlock_irqrestore(&temp->nb_lock, flags);
mutex_unlock(&svc_event_nb_list_lock);
return 0;
}
static int qmi_svc_event_remove(struct platform_device *pdev)
{
struct svc_event_nb *temp;
unsigned long flags;
mutex_lock(&svc_event_nb_list_lock);
temp = find_svc_event_nb_by_name(pdev->name);
if (!temp) {
mutex_unlock(&svc_event_nb_list_lock);
return -EINVAL;
}
spin_lock_irqsave(&temp->nb_lock, flags);
temp->svc_avail = 0;
raw_notifier_call_chain(&temp->svc_event_rcvr_list,
QMI_SERVER_EXIT, NULL);
spin_unlock_irqrestore(&temp->nb_lock, flags);
mutex_unlock(&svc_event_nb_list_lock);
return 0;
}
static struct svc_event_nb *find_svc_event_nb(uint32_t service_id,
uint32_t instance_id)
{
struct svc_event_nb *temp;
list_for_each_entry(temp, &svc_event_nb_list, list) {
if (temp->service_id == service_id &&
temp->instance_id == instance_id)
return temp;
}
return NULL;
}
static struct svc_event_nb *find_and_add_svc_event_nb(uint32_t service_id,
uint32_t instance_id)
{
struct svc_event_nb *temp;
int ret;
mutex_lock(&svc_event_nb_list_lock);
temp = find_svc_event_nb(service_id, instance_id);
if (temp) {
mutex_unlock(&svc_event_nb_list_lock);
return temp;
}
temp = kzalloc(sizeof(struct svc_event_nb), GFP_KERNEL);
if (!temp) {
mutex_unlock(&svc_event_nb_list_lock);
pr_err("%s: Failed to alloc notifier block\n", __func__);
return temp;
}
spin_lock_init(&temp->nb_lock);
temp->service_id = service_id;
temp->instance_id = instance_id;
INIT_LIST_HEAD(&temp->list);
temp->svc_driver.probe = qmi_svc_event_probe;
temp->svc_driver.remove = qmi_svc_event_remove;
scnprintf(temp->pdriver_name, sizeof(temp->pdriver_name),
"QMI%08x:%08x", service_id, instance_id);
temp->svc_driver.driver.name = temp->pdriver_name;
RAW_INIT_NOTIFIER_HEAD(&temp->svc_event_rcvr_list);
list_add_tail(&temp->list, &svc_event_nb_list);
mutex_unlock(&svc_event_nb_list_lock);
ret = platform_driver_register(&temp->svc_driver);
if (ret < 0) {
pr_err("%s: Failed pdriver register\n", __func__);
mutex_lock(&svc_event_nb_list_lock);
list_del(&temp->list);
mutex_unlock(&svc_event_nb_list_lock);
kfree(temp);
temp = NULL;
}
return temp;
}
int qmi_svc_event_notifier_register(uint32_t service_id,
uint32_t instance_id,
struct notifier_block *nb)
{
struct svc_event_nb *temp;
unsigned long flags;
int ret;
temp = find_and_add_svc_event_nb(service_id, instance_id);
if (!temp)
return -EFAULT;
mutex_lock(&svc_event_nb_list_lock);
temp = find_svc_event_nb(service_id, instance_id);
if (!temp) {
mutex_unlock(&svc_event_nb_list_lock);
return -EFAULT;
}
spin_lock_irqsave(&temp->nb_lock, flags);
if (temp->svc_avail)
nb->notifier_call(nb, QMI_SERVER_ARRIVE, NULL);
ret = raw_notifier_chain_register(&temp->svc_event_rcvr_list, nb);
spin_unlock_irqrestore(&temp->nb_lock, flags);
mutex_unlock(&svc_event_nb_list_lock);
return ret;
}
EXPORT_SYMBOL(qmi_svc_event_notifier_register);
int qmi_svc_event_notifier_unregister(uint32_t service_id,
uint32_t instance_id,
struct notifier_block *nb)
{
int ret;
struct svc_event_nb *temp;
unsigned long flags;
mutex_lock(&svc_event_nb_list_lock);
temp = find_svc_event_nb(service_id, instance_id);
if (!temp) {
mutex_unlock(&svc_event_nb_list_lock);
return -EINVAL;
}
spin_lock_irqsave(&temp->nb_lock, flags);
ret = raw_notifier_chain_unregister(&temp->svc_event_rcvr_list, nb);
spin_unlock_irqrestore(&temp->nb_lock, flags);
mutex_unlock(&svc_event_nb_list_lock);
return ret;
}
EXPORT_SYMBOL(qmi_svc_event_notifier_unregister);
MODULE_DESCRIPTION("MSM QMI Interface");
MODULE_LICENSE("GPL v2");