/* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qmi_interface_priv.h" #define BUILD_INSTANCE_ID(vers, ins) (((vers) & 0xFF) | (((ins) & 0xFF) << 8)) #define LOOKUP_MASK 0xFFFFFFFF #define MAX_WQ_NAME_LEN 20 #define QMI_REQ_RESP_LOG_PAGES 3 #define QMI_IND_LOG_PAGES 2 #define QMI_REQ_RESP_LOG(buf...) \ do { \ if (qmi_req_resp_log_ctx) { \ ipc_log_string(qmi_req_resp_log_ctx, buf); \ } \ } while (0) \ #define QMI_IND_LOG(buf...) \ do { \ if (qmi_ind_log_ctx) { \ ipc_log_string(qmi_ind_log_ctx, buf); \ } \ } while (0) \ static LIST_HEAD(svc_event_nb_list); static DEFINE_MUTEX(svc_event_nb_list_lock); struct qmi_notify_event_work { unsigned event; void *oob_data; size_t oob_data_len; void *priv; struct work_struct work; }; static void qmi_notify_event_worker(struct work_struct *work); #define HANDLE_HASH_TBL_SZ 1 static DEFINE_HASHTABLE(handle_hash_tbl, HANDLE_HASH_TBL_SZ); static DEFINE_MUTEX(handle_hash_tbl_lock); struct elem_info qcom_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, }, }; EXPORT_SYMBOL(qcom_qmi_response_type_v01_ei); struct elem_info qmi_error_resp_type_v01_ei[] = { { .data_type = QMI_STRUCT, .elem_len = 1, .elem_size = sizeof(struct qmi_response_type_v01), .is_array = NO_ARRAY, .tlv_type = 0x02, .offset = 0, .ei_array = qcom_qmi_response_type_v01_ei, }, { .data_type = QMI_EOTI, .elem_len = 0, .elem_size = 0, .is_array = NO_ARRAY, .tlv_type = 0x00, .offset = 0, .ei_array = NULL, }, }; struct msg_desc err_resp_desc = { .max_msg_len = 7, .msg_id = 0, .ei_array = qmi_error_resp_type_v01_ei, }; static DEFINE_MUTEX(qmi_svc_event_notifier_lock); static struct msm_ipc_port *qmi_svc_event_notifier_port; static struct workqueue_struct *qmi_svc_event_notifier_wq; static void qmi_svc_event_notifier_init(void); static void qmi_svc_event_worker(struct work_struct *work); static struct svc_event_nb *find_svc_event_nb(uint32_t service_id, uint32_t instance_id); DECLARE_WORK(qmi_svc_event_work, qmi_svc_event_worker); static void svc_resume_tx_worker(struct work_struct *work); static void clean_txn_info(struct qmi_handle *handle); static void *qmi_req_resp_log_ctx; static void *qmi_ind_log_ctx; /** * qmi_log() - Pass log data to IPC logging framework * @handle: The pointer to the qmi_handle * @cntl_flg: Indicates the type(request/response/indications) of the message * @txn_id: Transaction ID of the message. * @msg_id: Message ID of the incoming/outgoing message. * @msg_len: Total size of the message. * * This function builds the data the would be passed on to the IPC logging * framework. The data that would be passed corresponds to the information * that is exchanged between the IPC Router and kernel modules during * request/response/indication transactions. */ static void qmi_log(struct qmi_handle *handle, unsigned char cntl_flag, uint16_t txn_id, uint16_t msg_id, uint16_t msg_len) { uint32_t service_id = 0; const char *ops_type = NULL; if (handle->handle_type == QMI_CLIENT_HANDLE) { service_id = handle->dest_service_id; if (cntl_flag == QMI_REQUEST_CONTROL_FLAG) ops_type = "TX"; else if (cntl_flag == QMI_INDICATION_CONTROL_FLAG || cntl_flag == QMI_RESPONSE_CONTROL_FLAG) ops_type = "RX"; } else if (handle->handle_type == QMI_SERVICE_HANDLE) { service_id = handle->svc_ops_options->service_id; if (cntl_flag == QMI_REQUEST_CONTROL_FLAG) ops_type = "RX"; else if (cntl_flag == QMI_INDICATION_CONTROL_FLAG || cntl_flag == QMI_RESPONSE_CONTROL_FLAG) ops_type = "TX"; } /* * IPC Logging format is as below:- * (CLNT or SERV) : * (Transmit/ RECV) : * (Req/Resp/Ind) : * : * : * : * : */ if (qmi_req_resp_log_ctx && ((cntl_flag == QMI_REQUEST_CONTROL_FLAG) || (cntl_flag == QMI_RESPONSE_CONTROL_FLAG))) { QMI_REQ_RESP_LOG("%s %s CF:%x TI:%x MI:%x ML:%x SvcId: %x", (handle->handle_type == QMI_CLIENT_HANDLE ? "QCCI" : "QCSI"), ops_type, cntl_flag, txn_id, msg_id, msg_len, service_id); } else if (qmi_ind_log_ctx && (cntl_flag == QMI_INDICATION_CONTROL_FLAG)) { QMI_IND_LOG("%s %s CF:%x TI:%x MI:%x ML:%x SvcId: %x", (handle->handle_type == QMI_CLIENT_HANDLE ? "QCCI" : "QCSI"), ops_type, cntl_flag, txn_id, msg_id, msg_len, service_id); } } /** * add_req_handle() - Create and Add a request handle to the connection * @conn_h: Connection handle over which the request has arrived. * @msg_id: Message ID of the request. * @txn_id: Transaction ID of the request. * * @return: Pointer to request handle on success, NULL on error. * * This function creates a request handle to track the request that arrives * on a connection. This function then adds it to the connection's request * handle list. */ static struct req_handle *add_req_handle(struct qmi_svc_clnt_conn *conn_h, uint16_t msg_id, uint16_t txn_id) { struct req_handle *req_h; req_h = kmalloc(sizeof(struct req_handle), GFP_KERNEL); if (!req_h) { pr_err("%s: Error allocating req_h\n", __func__); return NULL; } req_h->conn_h = conn_h; req_h->msg_id = msg_id; req_h->txn_id = txn_id; list_add_tail(&req_h->list, &conn_h->req_handle_list); return req_h; } /** * verify_req_handle() - Verify the validity of a request handle * @conn_h: Connection handle over which the request has arrived. * @req_h: Request handle to be verified. * * @return: true on success, false on failure. * * This function is used to check if the request handle is present in * the connection handle. */ static bool verify_req_handle(struct qmi_svc_clnt_conn *conn_h, struct req_handle *req_h) { struct req_handle *temp_req_h; list_for_each_entry(temp_req_h, &conn_h->req_handle_list, list) { if (temp_req_h == req_h) return true; } return false; } /** * rmv_req_handle() - Remove and destroy the request handle * @req_h: Request handle to be removed and destroyed. * * @return: 0. */ static int rmv_req_handle(struct req_handle *req_h) { list_del(&req_h->list); kfree(req_h); return 0; } /** * add_svc_clnt_conn() - Create and add a connection handle to a service * @handle: QMI handle in which the service is hosted. * @clnt_addr: Address of the client connecting with the service. * @clnt_addr_len: Length of the client address. * * @return: Pointer to connection handle on success, NULL on error. * * This function is used to create a connection handle that binds the service * with a client. This function is called on a service's QMI handle when a * client sends its first message to the service. * * This function must be called with handle->handle_lock locked. */ static struct qmi_svc_clnt_conn *add_svc_clnt_conn( struct qmi_handle *handle, void *clnt_addr, size_t clnt_addr_len) { struct qmi_svc_clnt_conn *conn_h; conn_h = kmalloc(sizeof(struct qmi_svc_clnt_conn), GFP_KERNEL); if (!conn_h) { pr_err("%s: Error allocating conn_h\n", __func__); return NULL; } conn_h->clnt_addr = kmalloc(clnt_addr_len, GFP_KERNEL); if (!conn_h->clnt_addr) { pr_err("%s: Error allocating clnt_addr\n", __func__); return NULL; } INIT_LIST_HEAD(&conn_h->list); conn_h->svc_handle = handle; memcpy(conn_h->clnt_addr, clnt_addr, clnt_addr_len); conn_h->clnt_addr_len = clnt_addr_len; INIT_LIST_HEAD(&conn_h->req_handle_list); INIT_DELAYED_WORK(&conn_h->resume_tx_work, svc_resume_tx_worker); INIT_LIST_HEAD(&conn_h->pending_txn_list); mutex_init(&conn_h->pending_txn_lock); list_add_tail(&conn_h->list, &handle->conn_list); return conn_h; } /** * find_svc_clnt_conn() - Find the existence of a client<->service connection * @handle: Service's QMI handle. * @clnt_addr: Address of the client to be present in the connection. * @clnt_addr_len: Length of the client address. * * @return: Pointer to connection handle if the matching connection is found, * NULL if the connection is not found. * * This function is used to find the existence of a client<->service connection * handle in a service's QMI handle. This function tries to match the client * address in the existing connections. * * This function must be called with handle->handle_lock locked. */ static struct qmi_svc_clnt_conn *find_svc_clnt_conn( struct qmi_handle *handle, void *clnt_addr, size_t clnt_addr_len) { struct qmi_svc_clnt_conn *conn_h; list_for_each_entry(conn_h, &handle->conn_list, list) { if (!memcmp(conn_h->clnt_addr, clnt_addr, clnt_addr_len)) return conn_h; } return NULL; } /** * verify_svc_clnt_conn() - Verify the existence of a connection handle * @handle: Service's QMI handle. * @conn_h: Connection handle to be verified. * * @return: true on success, false on failure. * * This function is used to verify the existence of a connection in the * connection list maintained by the service. * * This function must be called with handle->handle_lock locked. */ static bool verify_svc_clnt_conn(struct qmi_handle *handle, struct qmi_svc_clnt_conn *conn_h) { struct qmi_svc_clnt_conn *temp_conn_h; list_for_each_entry(temp_conn_h, &handle->conn_list, list) { if (temp_conn_h == conn_h) return true; } return false; } /** * rmv_svc_clnt_conn() - Remove the connection handle info from the service * @conn_h: Connection handle to be removed. * * This function removes a connection handle from a service's QMI handle. * * This function must be called with handle->handle_lock locked. */ static void rmv_svc_clnt_conn(struct qmi_svc_clnt_conn *conn_h) { struct req_handle *req_h, *temp_req_h; struct qmi_txn *txn_h, *temp_txn_h; list_del(&conn_h->list); list_for_each_entry_safe(req_h, temp_req_h, &conn_h->req_handle_list, list) rmv_req_handle(req_h); mutex_lock(&conn_h->pending_txn_lock); list_for_each_entry_safe(txn_h, temp_txn_h, &conn_h->pending_txn_list, list) { list_del(&txn_h->list); kfree(txn_h->enc_data); kfree(txn_h); } mutex_unlock(&conn_h->pending_txn_lock); flush_delayed_work(&conn_h->resume_tx_work); kfree(conn_h->clnt_addr); kfree(conn_h); } /** * qmi_event_notify() - Notification function to QMI client/service interface * @event: Type of event that gets notified. * @oob_data: Any out-of-band data associated with event. * @oob_data_len: Length of the out-of-band data, if any. * @priv: Private data. * * This function is called by the underlying transport to notify the QMI * interface regarding any incoming event. This function is registered by * QMI interface when it opens a port/handle with the underlying transport. */ static void qmi_event_notify(unsigned event, void *oob_data, size_t oob_data_len, void *priv) { struct qmi_notify_event_work *notify_work; struct qmi_handle *handle; uint32_t key = 0; notify_work = kmalloc(sizeof(struct qmi_notify_event_work), GFP_KERNEL); if (!notify_work) { pr_err("%s: Couldn't notify %d event to %p\n", __func__, event, priv); return; } notify_work->event = event; if (oob_data) { notify_work->oob_data = kmalloc(oob_data_len, GFP_KERNEL); if (!notify_work->oob_data) { pr_err("%s: Couldn't allocate oob_data @ %d to %p\n", __func__, event, priv); kfree(notify_work); return; } memcpy(notify_work->oob_data, oob_data, oob_data_len); } else { notify_work->oob_data = NULL; } notify_work->oob_data_len = oob_data_len; notify_work->priv = priv; INIT_WORK(¬ify_work->work, qmi_notify_event_worker); mutex_lock(&handle_hash_tbl_lock); hash_for_each_possible(handle_hash_tbl, handle, handle_hash, key) { if (handle == (struct qmi_handle *)priv) { queue_work(handle->handle_wq, ¬ify_work->work); mutex_unlock(&handle_hash_tbl_lock); return; } } mutex_unlock(&handle_hash_tbl_lock); kfree(notify_work->oob_data); kfree(notify_work); } static void qmi_notify_event_worker(struct work_struct *work) { struct qmi_notify_event_work *notify_work = container_of(work, struct qmi_notify_event_work, work); struct qmi_handle *handle = (struct qmi_handle *)notify_work->priv; unsigned long flags; if (!handle) return; mutex_lock(&handle->handle_lock); if (handle->handle_reset) { mutex_unlock(&handle->handle_lock); kfree(notify_work->oob_data); kfree(notify_work); return; } switch (notify_work->event) { case IPC_ROUTER_CTRL_CMD_DATA: spin_lock_irqsave(&handle->notify_lock, flags); handle->notify(handle, QMI_RECV_MSG, handle->notify_priv); spin_unlock_irqrestore(&handle->notify_lock, flags); break; case IPC_ROUTER_CTRL_CMD_RESUME_TX: if (handle->handle_type == QMI_CLIENT_HANDLE) { queue_delayed_work(handle->handle_wq, &handle->resume_tx_work, msecs_to_jiffies(0)); } else if (handle->handle_type == QMI_SERVICE_HANDLE) { struct msm_ipc_addr rtx_addr = {0}; struct qmi_svc_clnt_conn *conn_h; union rr_control_msg *msg; msg = (union rr_control_msg *)notify_work->oob_data; rtx_addr.addrtype = MSM_IPC_ADDR_ID; rtx_addr.addr.port_addr.node_id = msg->cli.node_id; rtx_addr.addr.port_addr.port_id = msg->cli.port_id; conn_h = find_svc_clnt_conn(handle, &rtx_addr, sizeof(rtx_addr)); if (conn_h) queue_delayed_work(handle->handle_wq, &conn_h->resume_tx_work, msecs_to_jiffies(0)); } break; case IPC_ROUTER_CTRL_CMD_NEW_SERVER: case IPC_ROUTER_CTRL_CMD_REMOVE_SERVER: case IPC_ROUTER_CTRL_CMD_REMOVE_CLIENT: queue_delayed_work(handle->handle_wq, &handle->ctl_work, msecs_to_jiffies(0)); break; default: break; } mutex_unlock(&handle->handle_lock); kfree(notify_work->oob_data); kfree(notify_work); } /** * clnt_resume_tx_worker() - Handle the Resume_Tx event * @work : Pointer to the work strcuture. * * This function handles the resume_tx event for any QMI client that * exists in the kernel space. This function parses the pending_txn_list of * the handle and attempts a send for each transaction in that list. */ static void clnt_resume_tx_worker(struct work_struct *work) { struct delayed_work *rtx_work = to_delayed_work(work); struct qmi_handle *handle = container_of(rtx_work, struct qmi_handle, resume_tx_work); struct qmi_txn *pend_txn, *temp_txn; int ret; uint16_t msg_id; mutex_lock(&handle->handle_lock); if (handle->handle_reset) goto out_clnt_handle_rtx; list_for_each_entry_safe(pend_txn, temp_txn, &handle->pending_txn_list, list) { ret = msm_ipc_router_send_msg( (struct msm_ipc_port *)handle->src_port, (struct msm_ipc_addr *)handle->dest_info, pend_txn->enc_data, pend_txn->enc_data_len); if (ret == -EAGAIN) break; msg_id = ((struct qmi_header *)pend_txn->enc_data)->msg_id; kfree(pend_txn->enc_data); if (ret < 0) { pr_err("%s: Sending transaction %d from port %d failed", __func__, pend_txn->txn_id, ((struct msm_ipc_port *)handle->src_port)-> this_port.port_id); if (pend_txn->type == QMI_ASYNC_TXN) { pend_txn->resp_cb(pend_txn->handle, msg_id, pend_txn->resp, pend_txn->resp_cb_data, ret); list_del(&pend_txn->list); kfree(pend_txn); } else if (pend_txn->type == QMI_SYNC_TXN) { pend_txn->send_stat = ret; wake_up(&pend_txn->wait_q); } } else { list_del(&pend_txn->list); list_add_tail(&pend_txn->list, &handle->txn_list); } } out_clnt_handle_rtx: mutex_unlock(&handle->handle_lock); } /** * svc_resume_tx_worker() - Handle the Resume_Tx event * @work : Pointer to the work strcuture. * * This function handles the resume_tx event for any QMI service that * exists in the kernel space. This function parses the pending_txn_list of * the connection handle and attempts a send for each transaction in that list. */ static void svc_resume_tx_worker(struct work_struct *work) { struct delayed_work *rtx_work = to_delayed_work(work); struct qmi_svc_clnt_conn *conn_h = container_of(rtx_work, struct qmi_svc_clnt_conn, resume_tx_work); struct qmi_handle *handle = (struct qmi_handle *)conn_h->svc_handle; struct qmi_txn *pend_txn, *temp_txn; int ret; mutex_lock(&conn_h->pending_txn_lock); if (handle->handle_reset) goto out_svc_handle_rtx; list_for_each_entry_safe(pend_txn, temp_txn, &conn_h->pending_txn_list, list) { ret = msm_ipc_router_send_msg( (struct msm_ipc_port *)handle->src_port, (struct msm_ipc_addr *)conn_h->clnt_addr, pend_txn->enc_data, pend_txn->enc_data_len); if (ret == -EAGAIN) break; if (ret < 0) pr_err("%s: Sending transaction %d from port %d failed", __func__, pend_txn->txn_id, ((struct msm_ipc_port *)handle->src_port)-> this_port.port_id); list_del(&pend_txn->list); kfree(pend_txn->enc_data); kfree(pend_txn); } out_svc_handle_rtx: mutex_unlock(&conn_h->pending_txn_lock); } /** * handle_rmv_server() - Handle the server exit event * @handle: Client handle on which the server exit event is received. * @ctl_msg: Information about the server that is exiting. * * @return: 0 on success, standard Linux error codes on failure. * * This function must be called with handle->handle_lock locked. */ static int handle_rmv_server(struct qmi_handle *handle, union rr_control_msg *ctl_msg) { struct msm_ipc_addr *svc_addr; unsigned long flags; if (unlikely(!handle->dest_info)) return 0; svc_addr = (struct msm_ipc_addr *)(handle->dest_info); if (svc_addr->addr.port_addr.node_id == ctl_msg->srv.node_id && svc_addr->addr.port_addr.port_id == ctl_msg->srv.port_id) { /* Wakeup any threads waiting for the response */ handle->handle_reset = 1; clean_txn_info(handle); spin_lock_irqsave(&handle->notify_lock, flags); handle->notify(handle, QMI_SERVER_EXIT, handle->notify_priv); spin_unlock_irqrestore(&handle->notify_lock, flags); } return 0; } /** * handle_rmv_client() - Handle the client exit event * @handle: Service handle on which the client exit event is received. * @ctl_msg: Information about the client that is exiting. * * @return: 0 on success, standard Linux error codes on failure. * * This function must be called with handle->handle_lock locked. */ static int handle_rmv_client(struct qmi_handle *handle, union rr_control_msg *ctl_msg) { struct qmi_svc_clnt_conn *conn_h; struct msm_ipc_addr clnt_addr = {0}; unsigned long flags; clnt_addr.addrtype = MSM_IPC_ADDR_ID; clnt_addr.addr.port_addr.node_id = ctl_msg->cli.node_id; clnt_addr.addr.port_addr.port_id = ctl_msg->cli.port_id; conn_h = find_svc_clnt_conn(handle, &clnt_addr, sizeof(clnt_addr)); if (conn_h) { spin_lock_irqsave(&handle->notify_lock, flags); handle->svc_ops_options->disconnect_cb(handle, conn_h); spin_unlock_irqrestore(&handle->notify_lock, flags); rmv_svc_clnt_conn(conn_h); } return 0; } /** * handle_ctl_msg: Worker function to handle the control events * @work: Work item to map the QMI handle. * * This function is a worker function to handle the incoming control * events like REMOVE_SERVER/REMOVE_CLIENT. The work item is unique * to a handle and the workker function handles the control events on * a specific handle. */ static void handle_ctl_msg(struct work_struct *work) { struct delayed_work *ctl_work = to_delayed_work(work); struct qmi_handle *handle = container_of(ctl_work, struct qmi_handle, ctl_work); unsigned int ctl_msg_len; union rr_control_msg *ctl_msg = NULL; struct msm_ipc_addr src_addr; int rc; mutex_lock(&handle->handle_lock); while (1) { if (handle->handle_reset) break; /* Read the messages */ rc = msm_ipc_router_read_msg( (struct msm_ipc_port *)(handle->ctl_port), &src_addr, (unsigned char **)&ctl_msg, &ctl_msg_len); if (rc == -ENOMSG) break; if (rc < 0) { pr_err("%s: Read failed %d\n", __func__, rc); break; } if (ctl_msg->cmd == IPC_ROUTER_CTRL_CMD_REMOVE_SERVER && handle->handle_type == QMI_CLIENT_HANDLE) handle_rmv_server(handle, ctl_msg); else if (ctl_msg->cmd == IPC_ROUTER_CTRL_CMD_REMOVE_CLIENT && handle->handle_type == QMI_SERVICE_HANDLE) handle_rmv_client(handle, ctl_msg); kfree(ctl_msg); } mutex_unlock(&handle->handle_lock); return; } 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, *ctl_port_ptr; static uint32_t handle_count; char wq_name[MAX_WQ_NAME_LEN]; temp_handle = kzalloc(sizeof(struct qmi_handle), GFP_KERNEL); if (!temp_handle) { pr_err("%s: Failure allocating client handle\n", __func__); return NULL; } mutex_lock(&handle_hash_tbl_lock); handle_count++; scnprintf(wq_name, MAX_WQ_NAME_LEN, "qmi_hndl%08x", handle_count); hash_add(handle_hash_tbl, &temp_handle->handle_hash, 0); temp_handle->handle_wq = create_singlethread_workqueue(wq_name); mutex_unlock(&handle_hash_tbl_lock); if (!temp_handle->handle_wq) { pr_err("%s: Couldn't create workqueue for handle\n", __func__); goto handle_create_err1; } /* Initialize common elements */ temp_handle->handle_type = QMI_CLIENT_HANDLE; temp_handle->next_txn_id = 1; mutex_init(&temp_handle->handle_lock); spin_lock_init(&temp_handle->notify_lock); temp_handle->notify = notify; temp_handle->notify_priv = notify_priv; init_waitqueue_head(&temp_handle->reset_waitq); INIT_DELAYED_WORK(&temp_handle->resume_tx_work, clnt_resume_tx_worker); INIT_DELAYED_WORK(&temp_handle->ctl_work, handle_ctl_msg); /* Initialize client specific elements */ INIT_LIST_HEAD(&temp_handle->txn_list); INIT_LIST_HEAD(&temp_handle->pending_txn_list); /* Initialize service specific elements */ INIT_LIST_HEAD(&temp_handle->conn_list); 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__); goto handle_create_err2; } ctl_port_ptr = msm_ipc_router_create_port(qmi_event_notify, (void *)temp_handle); if (!ctl_port_ptr) { pr_err("%s: IPC router ctl port creation failed\n", __func__); goto handle_create_err3; } msm_ipc_router_bind_control_port(ctl_port_ptr); temp_handle->src_port = port_ptr; temp_handle->ctl_port = ctl_port_ptr; return temp_handle; handle_create_err3: msm_ipc_router_close_port(port_ptr); handle_create_err2: destroy_workqueue(temp_handle->handle_wq); handle_create_err1: mutex_lock(&handle_hash_tbl_lock); hash_del(&temp_handle->handle_hash); mutex_unlock(&handle_hash_tbl_lock); kfree(temp_handle); return NULL; } EXPORT_SYMBOL(qmi_handle_create); static void clean_txn_info(struct qmi_handle *handle) { struct qmi_txn *txn_handle, *temp_txn_handle, *pend_txn; list_for_each_entry_safe(pend_txn, temp_txn_handle, &handle->pending_txn_list, list) { if (pend_txn->type == QMI_ASYNC_TXN) { list_del(&pend_txn->list); pend_txn->resp_cb(pend_txn->handle, ((struct qmi_header *) pend_txn->enc_data)->msg_id, pend_txn->resp, pend_txn->resp_cb_data, -ENETRESET); kfree(pend_txn->enc_data); kfree(pend_txn); } else if (pend_txn->type == QMI_SYNC_TXN) { kfree(pend_txn->enc_data); wake_up(&pend_txn->wait_q); } } 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) { DEFINE_WAIT(wait); if (!handle) return -EINVAL; mutex_lock(&handle_hash_tbl_lock); hash_del(&handle->handle_hash); mutex_unlock(&handle_hash_tbl_lock); mutex_lock(&handle->handle_lock); handle->handle_reset = 1; clean_txn_info(handle); msm_ipc_router_close_port((struct msm_ipc_port *)(handle->ctl_port)); msm_ipc_router_close_port((struct msm_ipc_port *)(handle->src_port)); mutex_unlock(&handle->handle_lock); flush_workqueue(handle->handle_wq); destroy_workqueue(handle->handle_wq); mutex_lock(&handle->handle_lock); while (!list_empty(&handle->txn_list) || !list_empty(&handle->pending_txn_list)) { prepare_to_wait(&handle->reset_waitq, &wait, TASK_UNINTERRUPTIBLE); mutex_unlock(&handle->handle_lock); schedule(); mutex_lock(&handle->handle_lock); finish_wait(&handle->reset_waitq, &wait); } mutex_unlock(&handle->handle_lock); 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, int stat), void *resp_cb_data) { struct qmi_txn *txn_handle; int rc, encoded_req_len; void *encoded_req; if (!handle || !handle->dest_info || !req_desc || !resp_desc || !resp) return -EINVAL; if ((!req && req_len) || (!req_len && req)) 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; txn_handle->enc_data = NULL; txn_handle->enc_data_len = 0; /* 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; /* * Check if this port has transactions queued to its pending list * and if there are any pending transactions then add the current * transaction to the pending list rather than sending it. This avoids * out-of-order message transfers. */ if (!list_empty(&handle->pending_txn_list)) { rc = -EAGAIN; goto append_pend_txn; } list_add_tail(&txn_handle->list, &handle->txn_list); qmi_log(handle, QMI_REQUEST_CONTROL_FLAG, txn_handle->txn_id, req_desc->msg_id, encoded_req_len); /* 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); append_pend_txn: if (rc == -EAGAIN) { txn_handle->enc_data = encoded_req; txn_handle->enc_data_len = encoded_req_len; if (list_empty(&handle->pending_txn_list)) list_del(&txn_handle->list); list_add_tail(&txn_handle->list, &handle->pending_txn_list); if (ret_txn_handle) *ret_txn_handle = txn_handle; mutex_unlock(&handle->handle_lock); return 0; } 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) { wait_event(txn_handle->wait_q, (txn_handle->resp_received || handle->handle_reset || (txn_handle->send_stat < 0))); } else { rc = wait_event_timeout(txn_handle->wait_q, (txn_handle->resp_received || handle->handle_reset || (txn_handle->send_stat < 0)), 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; if (txn_handle->send_stat < 0) rc = txn_handle->send_stat; goto send_req_wait_err; } rc = 0; send_req_wait_err: list_del(&txn_handle->list); kfree(txn_handle); wake_up(&handle->reset_waitq); mutex_unlock(&handle->handle_lock); 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, int stat), 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); /** * qmi_encode_and_send_resp() - Encode and send QMI response * @handle: QMI service handle sending the response. * @conn_h: Connection handle to which the response is sent. * @req_h: Request handle for which the response is sent. * @resp_desc: Message Descriptor describing the response structure. * @resp: Response structure. * @resp_len: Length of the response structure. * * @return: 0 on success, standard Linux error codes on failure. * * This function encodes and sends a response message from a service to * a client identified from the connection handle. The request for which * the response is sent is identified from the connection handle. * * This function must be called with handle->handle_lock locked. */ static int qmi_encode_and_send_resp(struct qmi_handle *handle, struct qmi_svc_clnt_conn *conn_h, struct req_handle *req_h, struct msg_desc *resp_desc, void *resp, unsigned int resp_len) { struct qmi_txn *txn_handle; uint16_t cntl_flag; int rc; int encoded_resp_len; void *encoded_resp; if (handle->handle_reset) { rc = -ENETRESET; goto encode_and_send_resp_err0; } if (handle->handle_type != QMI_SERVICE_HANDLE || !verify_svc_clnt_conn(handle, conn_h) || (req_h && !verify_req_handle(conn_h, req_h))) { rc = -EINVAL; goto encode_and_send_resp_err0; } /* 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__); rc = -ENOMEM; goto encode_and_send_resp_err0; } INIT_LIST_HEAD(&txn_handle->list); init_waitqueue_head(&txn_handle->wait_q); txn_handle->handle = handle; txn_handle->enc_data = NULL; txn_handle->enc_data_len = 0; /* Encode the response msg */ encoded_resp_len = resp_desc->max_msg_len + QMI_HEADER_SIZE; encoded_resp = kmalloc(encoded_resp_len, GFP_KERNEL); if (!encoded_resp) { pr_err("%s: Failed to allocate resp_msg_buf\n", __func__); rc = -ENOMEM; goto encode_and_send_resp_err1; } rc = qmi_kernel_encode(resp_desc, (void *)(encoded_resp + QMI_HEADER_SIZE), resp_desc->max_msg_len, resp); if (rc < 0) { pr_err("%s: Encode Failure %d\n", __func__, rc); goto encode_and_send_resp_err2; } encoded_resp_len = rc; /* Encode the header & Add to the txn_list */ if (req_h) { txn_handle->txn_id = req_h->txn_id; cntl_flag = QMI_RESPONSE_CONTROL_FLAG; } else { if (!handle->next_txn_id) handle->next_txn_id++; txn_handle->txn_id = handle->next_txn_id++; cntl_flag = QMI_INDICATION_CONTROL_FLAG; } encode_qmi_header(encoded_resp, cntl_flag, txn_handle->txn_id, resp_desc->msg_id, encoded_resp_len); encoded_resp_len += QMI_HEADER_SIZE; qmi_log(handle, cntl_flag, txn_handle->txn_id, resp_desc->msg_id, encoded_resp_len); /* * Check if this svc_clnt has transactions queued to its pending list * and if there are any pending transactions then add the current * transaction to the pending list rather than sending it. This avoids * out-of-order message transfers. */ mutex_lock(&conn_h->pending_txn_lock); if (list_empty(&conn_h->pending_txn_list)) rc = msm_ipc_router_send_msg( (struct msm_ipc_port *)(handle->src_port), (struct msm_ipc_addr *)conn_h->clnt_addr, encoded_resp, encoded_resp_len); else rc = -EAGAIN; if (req_h) rmv_req_handle(req_h); if (rc == -EAGAIN) { txn_handle->enc_data = encoded_resp; txn_handle->enc_data_len = encoded_resp_len; list_add_tail(&txn_handle->list, &conn_h->pending_txn_list); mutex_unlock(&conn_h->pending_txn_lock); return 0; } mutex_unlock(&conn_h->pending_txn_lock); if (rc < 0) pr_err("%s: send_msg failed %d\n", __func__, rc); encode_and_send_resp_err2: kfree(encoded_resp); encode_and_send_resp_err1: kfree(txn_handle); encode_and_send_resp_err0: return rc; } /** * qmi_send_resp() - Send response to a request * @handle: QMI handle from which the response is sent. * @clnt: Client to which the response is sent. * @req_handle: Request for which the response is sent. * @resp_desc: Descriptor explaining the response structure. * @resp: Pointer to the response structure. * @resp_len: Length of the response structure. * * @return: 0 on success, < 0 on error. */ int qmi_send_resp(struct qmi_handle *handle, void *conn_handle, void *req_handle, struct msg_desc *resp_desc, void *resp, unsigned int resp_len) { int rc; struct qmi_svc_clnt_conn *conn_h; struct req_handle *req_h; if (!handle || !conn_handle || !req_handle || !resp_desc || !resp || !resp_len) return -EINVAL; conn_h = (struct qmi_svc_clnt_conn *)conn_handle; req_h = (struct req_handle *)req_handle; mutex_lock(&handle->handle_lock); rc = qmi_encode_and_send_resp(handle, conn_h, req_h, resp_desc, resp, resp_len); if (rc < 0) pr_err("%s: Error encoding and sending response\n", __func__); mutex_unlock(&handle->handle_lock); return rc; } EXPORT_SYMBOL(qmi_send_resp); /** * qmi_send_resp_from_cb() - Send response to a request from request_cb * @handle: QMI handle from which the response is sent. * @clnt: Client to which the response is sent. * @req_handle: Request for which the response is sent. * @resp_desc: Descriptor explaining the response structure. * @resp: Pointer to the response structure. * @resp_len: Length of the response structure. * * @return: 0 on success, < 0 on error. */ int qmi_send_resp_from_cb(struct qmi_handle *handle, void *conn_handle, void *req_handle, struct msg_desc *resp_desc, void *resp, unsigned int resp_len) { int rc; struct qmi_svc_clnt_conn *conn_h; struct req_handle *req_h; if (!handle || !conn_handle || !req_handle || !resp_desc || !resp || !resp_len) return -EINVAL; conn_h = (struct qmi_svc_clnt_conn *)conn_handle; req_h = (struct req_handle *)req_handle; rc = qmi_encode_and_send_resp(handle, conn_h, req_h, resp_desc, resp, resp_len); if (rc < 0) pr_err("%s: Error encoding and sending response\n", __func__); return rc; } EXPORT_SYMBOL(qmi_send_resp_from_cb); /** * qmi_send_ind() - Send unsolicited event/indication to a client * @handle: QMI handle from which the indication is sent. * @clnt: Client to which the indication is sent. * @ind_desc: Descriptor explaining the indication structure. * @ind: Pointer to the indication structure. * @ind_len: Length of the indication structure. * * @return: 0 on success, < 0 on error. */ int qmi_send_ind(struct qmi_handle *handle, void *conn_handle, struct msg_desc *ind_desc, void *ind, unsigned int ind_len) { int rc = 0; struct qmi_svc_clnt_conn *conn_h; if (!handle || !conn_handle || !ind_desc) return -EINVAL; if ((!ind && ind_len) || (ind && !ind_len)) return -EINVAL; conn_h = (struct qmi_svc_clnt_conn *)conn_handle; mutex_lock(&handle->handle_lock); rc = qmi_encode_and_send_resp(handle, conn_h, NULL, ind_desc, ind, ind_len); if (rc < 0) pr_err("%s: Error encoding and sending ind.\n", __func__); mutex_unlock(&handle->handle_lock); return rc; } EXPORT_SYMBOL(qmi_send_ind); /** * qmi_send_ind_from_cb() - Send indication to a client from registration_cb * @handle: QMI handle from which the indication is sent. * @clnt: Client to which the indication is sent. * @ind_desc: Descriptor explaining the indication structure. * @ind: Pointer to the indication structure. * @ind_len: Length of the indication structure. * * @return: 0 on success, < 0 on error. */ int qmi_send_ind_from_cb(struct qmi_handle *handle, void *conn_handle, struct msg_desc *ind_desc, void *ind, unsigned int ind_len) { int rc = 0; struct qmi_svc_clnt_conn *conn_h; if (!handle || !conn_handle || !ind_desc) return -EINVAL; if ((!ind && ind_len) || (ind && !ind_len)) return -EINVAL; conn_h = (struct qmi_svc_clnt_conn *)conn_handle; rc = qmi_encode_and_send_resp(handle, conn_h, NULL, ind_desc, ind, ind_len); if (rc < 0) pr_err("%s: Error encoding and sending ind.\n", __func__); return rc; } EXPORT_SYMBOL(qmi_send_ind_from_cb); /** * translate_err_code() - Translate Linux error codes into QMI error codes * @err: Standard Linux error codes to be translated. * * @return: Return QMI error code. */ static int translate_err_code(int err) { int rc; switch (err) { case -ECONNREFUSED: rc = QMI_ERR_CLIENT_IDS_EXHAUSTED_V01; break; case -EBADMSG: rc = QMI_ERR_ENCODING_V01; break; case -ENOMEM: rc = QMI_ERR_NO_MEMORY_V01; break; case -EOPNOTSUPP: rc = QMI_ERR_MALFORMED_MSG_V01; break; case -ENOTSUPP: rc = QMI_ERR_NOT_SUPPORTED_V01; break; default: rc = QMI_ERR_INTERNAL_V01; break; } return rc; } /** * send_err_resp() - Send the error response * @handle: Service handle from which the response is sent. * @conn_h: Client<->Service connection on which the response is sent. * @addr: Client address to which the error response is sent. * @msg_id: Request message id for which the error response is sent. * @txn_id: Request Transaction ID for which the error response is sent. * @err: Error code to be sent. * * @return: 0 on success, standard Linux error codes on failure. * * This function is used to send an error response from within the QMI * service interface. This function is called when the service returns * an error to the QMI interface while handling a request. */ static int send_err_resp(struct qmi_handle *handle, struct qmi_svc_clnt_conn *conn_h, void *addr, uint16_t msg_id, uint16_t txn_id, int err) { struct qmi_response_type_v01 err_resp; struct qmi_txn *txn_handle; struct msm_ipc_addr *dest_addr; int rc; int encoded_resp_len; void *encoded_resp; if (handle->handle_reset) return -ENETRESET; err_resp.result = QMI_RESULT_FAILURE_V01; err_resp.error = translate_err_code(err); /* 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__); return -ENOMEM; } INIT_LIST_HEAD(&txn_handle->list); init_waitqueue_head(&txn_handle->wait_q); txn_handle->handle = handle; txn_handle->enc_data = NULL; txn_handle->enc_data_len = 0; /* Encode the response msg */ encoded_resp_len = err_resp_desc.max_msg_len + QMI_HEADER_SIZE; encoded_resp = kmalloc(encoded_resp_len, GFP_KERNEL); if (!encoded_resp) { pr_err("%s: Failed to allocate resp_msg_buf\n", __func__); rc = -ENOMEM; goto encode_and_send_err_resp_err0; } rc = qmi_kernel_encode(&err_resp_desc, (void *)(encoded_resp + QMI_HEADER_SIZE), err_resp_desc.max_msg_len, &err_resp); if (rc < 0) { pr_err("%s: Encode Failure %d\n", __func__, rc); goto encode_and_send_err_resp_err1; } encoded_resp_len = rc; /* Encode the header & Add to the txn_list */ txn_handle->txn_id = txn_id; encode_qmi_header(encoded_resp, QMI_RESPONSE_CONTROL_FLAG, txn_handle->txn_id, msg_id, encoded_resp_len); encoded_resp_len += QMI_HEADER_SIZE; qmi_log(handle, QMI_RESPONSE_CONTROL_FLAG, txn_id, msg_id, encoded_resp_len); /* * Check if this svc_clnt has transactions queued to its pending list * and if there are any pending transactions then add the current * transaction to the pending list rather than sending it. This avoids * out-of-order message transfers. */ if (!conn_h) { dest_addr = (struct msm_ipc_addr *)addr; goto tx_err_resp; } mutex_lock(&conn_h->pending_txn_lock); dest_addr = (struct msm_ipc_addr *)conn_h->clnt_addr; if (!list_empty(&conn_h->pending_txn_list)) { rc = -EAGAIN; goto queue_err_resp; } tx_err_resp: rc = msm_ipc_router_send_msg( (struct msm_ipc_port *)(handle->src_port), dest_addr, encoded_resp, encoded_resp_len); queue_err_resp: if (rc == -EAGAIN && conn_h) { txn_handle->enc_data = encoded_resp; txn_handle->enc_data_len = encoded_resp_len; list_add_tail(&txn_handle->list, &conn_h->pending_txn_list); mutex_unlock(&conn_h->pending_txn_lock); return 0; } if (conn_h) mutex_unlock(&conn_h->pending_txn_lock); if (rc < 0) pr_err("%s: send_msg failed %d\n", __func__, rc); encode_and_send_err_resp_err1: kfree(encoded_resp); encode_and_send_err_resp_err0: kfree(txn_handle); return rc; } /** * handle_qmi_request() - Handle the QMI request * @handle: QMI service handle on which the request has arrived. * @req_msg: Request message to be handled. * @txn_id: Transaction ID of the request message. * @msg_id: Message ID of the request message. * @msg_len: Message Length of the request message. * @src_addr: Address of the source which sent the request. * @src_addr_len: Length of the source address. * * @return: 0 on success, standard Linux error codes on failure. */ static int handle_qmi_request(struct qmi_handle *handle, unsigned char *req_msg, uint16_t txn_id, uint16_t msg_id, uint16_t msg_len, void *src_addr, size_t src_addr_len) { struct qmi_svc_clnt_conn *conn_h; struct msg_desc *req_desc = NULL; void *req_struct = NULL; unsigned int req_struct_len = 0; struct req_handle *req_h = NULL; int rc = 0; if (handle->handle_type != QMI_SERVICE_HANDLE) return -EOPNOTSUPP; conn_h = find_svc_clnt_conn(handle, src_addr, src_addr_len); if (conn_h) goto decode_req; /* New client, establish a connection */ conn_h = add_svc_clnt_conn(handle, src_addr, src_addr_len); if (!conn_h) { pr_err("%s: Error adding a new conn_h\n", __func__); rc = -ENOMEM; goto out_handle_req; } rc = handle->svc_ops_options->connect_cb(handle, conn_h); if (rc < 0) { pr_err("%s: Error accepting new client\n", __func__); rmv_svc_clnt_conn(conn_h); conn_h = NULL; goto out_handle_req; } decode_req: if (!msg_len) goto process_req; req_struct_len = handle->svc_ops_options->req_desc_cb(msg_id, &req_desc); if (!req_desc || req_struct_len <= 0) { pr_err("%s: Error getting req_desc for msg_id %d\n", __func__, msg_id); rc = -ENOTSUPP; goto out_handle_req; } req_struct = kzalloc(req_struct_len, GFP_KERNEL); if (!req_struct) { pr_err("%s: Error allocating request struct\n", __func__); rc = -ENOMEM; goto out_handle_req; } rc = qmi_kernel_decode(req_desc, req_struct, (void *)(req_msg + QMI_HEADER_SIZE), msg_len); if (rc < 0) { pr_err("%s: Error decoding msg_id %d\n", __func__, msg_id); rc = -EBADMSG; goto out_handle_req; } process_req: req_h = add_req_handle(conn_h, msg_id, txn_id); if (!req_h) { pr_err("%s: Error adding new request handle\n", __func__); rc = -ENOMEM; goto out_handle_req; } rc = handle->svc_ops_options->req_cb(handle, conn_h, req_h, msg_id, req_struct); if (rc < 0) { pr_err("%s: Error while req_cb\n", __func__); /* Check if the error is before or after sending a response */ if (verify_req_handle(conn_h, req_h)) rmv_req_handle(req_h); else rc = 0; } out_handle_req: kfree(req_struct); if (rc < 0) send_err_resp(handle, conn_h, src_addr, msg_id, txn_id, rc); return rc; } 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 0; } /* 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, 0); 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 + QMI_HEADER_SIZE, 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 = {0}; 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 == -ENOMSG) { mutex_unlock(&handle->handle_lock); return rc; } 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); qmi_log(handle, cntl_flag, txn_id, msg_id, msg_len); switch (cntl_flag) { case QMI_REQUEST_CONTROL_FLAG: rc = handle_qmi_request(handle, recv_msg, txn_id, msg_id, msg_len, &src_addr, sizeof(src_addr)); break; 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 service_vers, uint32_t service_ins) { struct msm_ipc_port_name svc_name; struct msm_ipc_server_info svc_info; struct msm_ipc_addr *svc_dest_addr; int rc; uint32_t instance_id; 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; } instance_id = BUILD_INSTANCE_ID(service_vers, service_ins); svc_name.service = service_id; svc_name.instance = instance_id; rc = msm_ipc_router_lookup_server_name(&svc_name, &svc_info, 1, LOOKUP_MASK); if (rc <= 0) { pr_err("%s: Server %08x:%08x not found\n", __func__, service_id, instance_id); 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; handle->dest_service_id = service_id; mutex_unlock(&handle->handle_lock); return 0; } EXPORT_SYMBOL(qmi_connect_to_service); /** * svc_event_add_svc_addr() - Add a specific service address to the list * @event_nb: Reference to the service event structure. * @node_id: Node id of the service address. * @port_id: Port id of the service address. * * Return: 0 on success, standard error code otheriwse. * * This function should be called with svc_addr_list_lock locked. */ static int svc_event_add_svc_addr(struct svc_event_nb *event_nb, uint32_t node_id, uint32_t port_id) { struct svc_addr *addr; if (!event_nb) return -EINVAL; addr = kmalloc(sizeof(*addr), GFP_KERNEL); if (!addr) { pr_err("%s: Memory allocation failed for address list\n", __func__); return -ENOMEM; } addr->port_addr.node_id = node_id; addr->port_addr.port_id = port_id; list_add_tail(&addr->list_node, &event_nb->svc_addr_list); return 0; } /** * qmi_notify_svc_event_arrive() - Notify the clients about service arrival * @service: Service id for the specific service. * @instance: Instance id for the specific service. * @node_id: Node id of the processor where the service is hosted. * @port_id: Port id of the service port created by IPC Router. * * Return: 0 on Success or standard error code. */ static int qmi_notify_svc_event_arrive(uint32_t service, uint32_t instance, uint32_t node_id, uint32_t port_id) { struct svc_event_nb *temp; unsigned long flags; struct svc_addr *addr; bool already_notified = false; mutex_lock(&svc_event_nb_list_lock); temp = find_svc_event_nb(service, instance); if (!temp) { mutex_unlock(&svc_event_nb_list_lock); return -EINVAL; } mutex_unlock(&svc_event_nb_list_lock); mutex_lock(&temp->svc_addr_list_lock); list_for_each_entry(addr, &temp->svc_addr_list, list_node) if (addr->port_addr.node_id == node_id && addr->port_addr.port_id == port_id) already_notified = true; if (!already_notified) { /* * Notify only if the clients are not notified about the * service during registration. */ svc_event_add_svc_addr(temp, node_id, port_id); spin_lock_irqsave(&temp->nb_lock, flags); raw_notifier_call_chain(&temp->svc_event_rcvr_list, QMI_SERVER_ARRIVE, NULL); spin_unlock_irqrestore(&temp->nb_lock, flags); } mutex_unlock(&temp->svc_addr_list_lock); return 0; } /** * qmi_notify_svc_event_exit() - Notify the clients about service exit * @service: Service id for the specific service. * @instance: Instance id for the specific service. * @node_id: Node id of the processor where the service is hosted. * @port_id: Port id of the service port created by IPC Router. * * Return: 0 on Success or standard error code. */ static int qmi_notify_svc_event_exit(uint32_t service, uint32_t instance, uint32_t node_id, uint32_t port_id) { struct svc_event_nb *temp; unsigned long flags; struct svc_addr *addr; struct svc_addr *temp_addr; mutex_lock(&svc_event_nb_list_lock); temp = find_svc_event_nb(service, instance); if (!temp) { mutex_unlock(&svc_event_nb_list_lock); return -EINVAL; } mutex_unlock(&svc_event_nb_list_lock); mutex_lock(&temp->svc_addr_list_lock); list_for_each_entry_safe(addr, temp_addr, &temp->svc_addr_list, list_node) { if (addr->port_addr.node_id == node_id && addr->port_addr.port_id == port_id) { /* * Notify only if an already notified service has * gone down. */ spin_lock_irqsave(&temp->nb_lock, flags); raw_notifier_call_chain(&temp->svc_event_rcvr_list, QMI_SERVER_EXIT, NULL); spin_unlock_irqrestore(&temp->nb_lock, flags); list_del(&addr->list_node); kfree(addr); } } mutex_unlock(&temp->svc_addr_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; } /** * find_and_add_svc_event_nb() - Find/Add a notifier block for specific service * @service_id: Service Id of the service * @instance_id:Instance Id of the service * * Return: Pointer to svc_event_nb structure for the specified service * * This function should only be called after acquiring svc_event_nb_list_lock. */ static struct svc_event_nb *find_and_add_svc_event_nb(uint32_t service_id, uint32_t instance_id) { struct svc_event_nb *temp; temp = find_svc_event_nb(service_id, instance_id); if (temp) return temp; temp = kzalloc(sizeof(struct svc_event_nb), GFP_KERNEL); if (!temp) { 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); INIT_LIST_HEAD(&temp->svc_addr_list); RAW_INIT_NOTIFIER_HEAD(&temp->svc_event_rcvr_list); mutex_init(&temp->svc_addr_list_lock); list_add_tail(&temp->list, &svc_event_nb_list); return temp; } int qmi_svc_event_notifier_register(uint32_t service_id, uint32_t service_vers, uint32_t service_ins, struct notifier_block *nb) { struct svc_event_nb *temp; unsigned long flags; int ret; int i; int num_servers; uint32_t instance_id; struct msm_ipc_port_name svc_name; struct msm_ipc_server_info *svc_info_arr = NULL; mutex_lock(&qmi_svc_event_notifier_lock); if (!qmi_svc_event_notifier_port && !qmi_svc_event_notifier_wq) qmi_svc_event_notifier_init(); mutex_unlock(&qmi_svc_event_notifier_lock); instance_id = BUILD_INSTANCE_ID(service_vers, service_ins); mutex_lock(&svc_event_nb_list_lock); temp = find_and_add_svc_event_nb(service_id, instance_id); if (!temp) { mutex_unlock(&svc_event_nb_list_lock); return -EFAULT; } mutex_unlock(&svc_event_nb_list_lock); mutex_lock(&temp->svc_addr_list_lock); spin_lock_irqsave(&temp->nb_lock, flags); ret = raw_notifier_chain_register(&temp->svc_event_rcvr_list, nb); spin_unlock_irqrestore(&temp->nb_lock, flags); if (!list_empty(&temp->svc_addr_list)) { /* Notify this client only if Some services already exist. */ spin_lock_irqsave(&temp->nb_lock, flags); nb->notifier_call(nb, QMI_SERVER_ARRIVE, NULL); spin_unlock_irqrestore(&temp->nb_lock, flags); } else { /* * Check if we have missed a new server event that happened * earlier. */ svc_name.service = service_id; svc_name.instance = instance_id; num_servers = msm_ipc_router_lookup_server_name(&svc_name, NULL, 0, LOOKUP_MASK); if (num_servers > 0) { svc_info_arr = kmalloc_array(num_servers, sizeof(*svc_info_arr), GFP_KERNEL); if (!svc_info_arr) return -ENOMEM; num_servers = msm_ipc_router_lookup_server_name( &svc_name, svc_info_arr, num_servers, LOOKUP_MASK); for (i = 0; i < num_servers; i++) svc_event_add_svc_addr(temp, svc_info_arr[i].node_id, svc_info_arr[i].port_id); kfree(svc_info_arr); spin_lock_irqsave(&temp->nb_lock, flags); raw_notifier_call_chain(&temp->svc_event_rcvr_list, QMI_SERVER_ARRIVE, NULL); spin_unlock_irqrestore(&temp->nb_lock, flags); } } mutex_unlock(&temp->svc_addr_list_lock); return ret; } EXPORT_SYMBOL(qmi_svc_event_notifier_register); int qmi_svc_event_notifier_unregister(uint32_t service_id, uint32_t service_vers, uint32_t service_ins, struct notifier_block *nb) { int ret; struct svc_event_nb *temp; unsigned long flags; uint32_t instance_id; instance_id = BUILD_INSTANCE_ID(service_vers, service_ins); 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); /** * qmi_svc_event_worker() - Read control messages over service event port * @work: Reference to the work structure queued. * */ static void qmi_svc_event_worker(struct work_struct *work) { union rr_control_msg *ctl_msg = NULL; unsigned int ctl_msg_len; struct msm_ipc_addr src_addr; int ret; while (1) { ret = msm_ipc_router_read_msg(qmi_svc_event_notifier_port, &src_addr, (unsigned char **)&ctl_msg, &ctl_msg_len); if (ret == -ENOMSG) break; if (ret < 0) { pr_err("%s:Error receiving control message\n", __func__); break; } if (ctl_msg->cmd == IPC_ROUTER_CTRL_CMD_NEW_SERVER) qmi_notify_svc_event_arrive(ctl_msg->srv.service, ctl_msg->srv.instance, ctl_msg->srv.node_id, ctl_msg->srv.port_id); else if (ctl_msg->cmd == IPC_ROUTER_CTRL_CMD_REMOVE_SERVER) qmi_notify_svc_event_exit(ctl_msg->srv.service, ctl_msg->srv.instance, ctl_msg->srv.node_id, ctl_msg->srv.port_id); kfree(ctl_msg); } } /** * qmi_svc_event_notify() - Callback for any service event posted on the control port * @event: The event posted on the control port. * @data: Any out-of-band data associated with event. * @odata_len: Length of the out-of-band data, if any. * @priv: Private Data. * * This function is called by the underlying transport to notify the QMI * interface regarding any incoming service related events. It is registered * during service event control port creation. */ static void qmi_svc_event_notify(unsigned event, void *data, size_t odata_len, void *priv) { if (event == IPC_ROUTER_CTRL_CMD_NEW_SERVER || event == IPC_ROUTER_CTRL_CMD_REMOVE_CLIENT || event == IPC_ROUTER_CTRL_CMD_REMOVE_SERVER) queue_work(qmi_svc_event_notifier_wq, &qmi_svc_event_work); } /** * qmi_svc_event_notifier_init() - Create a control port to get service events * * This function is called during first service notifier registration. It * creates a control port to get notification about server events so that * respective clients can be notified about the events. */ static void qmi_svc_event_notifier_init(void) { qmi_svc_event_notifier_wq = create_singlethread_workqueue( "qmi_svc_event_wq"); if (!qmi_svc_event_notifier_wq) { pr_err("%s: ctrl workqueue allocation failed\n", __func__); return; } qmi_svc_event_notifier_port = msm_ipc_router_create_port( qmi_svc_event_notify, NULL); if (!qmi_svc_event_notifier_port) { destroy_workqueue(qmi_svc_event_notifier_wq); pr_err("%s: IPC Router Port creation failed\n", __func__); return; } msm_ipc_router_bind_control_port(qmi_svc_event_notifier_port); return; } /** * qmi_log_init() - Init function for IPC Logging * * Initialize log contexts for QMI request/response/indications. */ void qmi_log_init(void) { qmi_req_resp_log_ctx = ipc_log_context_create(QMI_REQ_RESP_LOG_PAGES, "kqmi_req_resp", 0); if (!qmi_req_resp_log_ctx) { #ifdef CONFIG_IPC_CONTEXT pr_err("%s: Unable to create QMI IPC logging for Req/Resp", __func__); #else pr_err("%s: IPC Logging disabled\n", __func__); #endif } qmi_ind_log_ctx = ipc_log_context_create(QMI_IND_LOG_PAGES, "kqmi_ind", 0); if (!qmi_ind_log_ctx) { #ifdef CONFIG_IPC_LOGGING pr_err("%s: Unable to create QMI IPC %s", "logging for Indications", __func__); #else pr_err("%s: IPC Logging disabled\n", __func__); #endif } } /** * qmi_svc_register() - Register a QMI service with a QMI handle * @handle: QMI handle on which the service has to be registered. * @ops_options: Service specific operations and options. * * @return: 0 if successfully registered, < 0 on error. */ int qmi_svc_register(struct qmi_handle *handle, void *ops_options) { struct qmi_svc_ops_options *svc_ops_options; struct msm_ipc_addr svc_name; int rc; uint32_t instance_id; svc_ops_options = (struct qmi_svc_ops_options *)ops_options; if (!handle || !svc_ops_options) return -EINVAL; /* Check if the required elements of opts_options are filled */ if (!svc_ops_options->service_id || !svc_ops_options->service_vers || !svc_ops_options->connect_cb || !svc_ops_options->disconnect_cb || !svc_ops_options->req_desc_cb || !svc_ops_options->req_cb) return -EINVAL; mutex_lock(&handle->handle_lock); /* Check if another service/client is registered in that handle */ if (handle->handle_type == QMI_SERVICE_HANDLE || handle->dest_info) { mutex_unlock(&handle->handle_lock); return -EBUSY; } INIT_LIST_HEAD(&handle->conn_list); mutex_unlock(&handle->handle_lock); /* * Unlocked the handle_lock, because NEW_SERVER message will end up * in this handle's control port, which requires holding the same * mutex. Also it is safe to call register_server unlocked. */ /* Register the service */ instance_id = ((svc_ops_options->service_vers & 0xFF) | ((svc_ops_options->service_ins & 0xFF) << 8)); svc_name.addrtype = MSM_IPC_ADDR_NAME; svc_name.addr.port_name.service = svc_ops_options->service_id; svc_name.addr.port_name.instance = instance_id; rc = msm_ipc_router_register_server( (struct msm_ipc_port *)handle->src_port, &svc_name); if (rc < 0) { pr_err("%s: Error %d registering QMI service %08x:%08x\n", __func__, rc, svc_ops_options->service_id, instance_id); return rc; } mutex_lock(&handle->handle_lock); handle->svc_ops_options = svc_ops_options; handle->handle_type = QMI_SERVICE_HANDLE; mutex_unlock(&handle->handle_lock); return rc; } EXPORT_SYMBOL(qmi_svc_register); /** * qmi_svc_unregister() - Unregister the service from a QMI handle * @handle: QMI handle from which the service has to be unregistered. * * return: 0 on success, < 0 on error. */ int qmi_svc_unregister(struct qmi_handle *handle) { struct qmi_svc_clnt_conn *conn_h, *temp_conn_h; if (!handle || handle->handle_type != QMI_SERVICE_HANDLE) return -EINVAL; mutex_lock(&handle->handle_lock); handle->handle_type = QMI_CLIENT_HANDLE; mutex_unlock(&handle->handle_lock); /* * Unlocked the handle_lock, because REMOVE_SERVER message will end up * in this handle's control port, which requires holding the same * mutex. Also it is safe to call register_server unlocked. */ msm_ipc_router_unregister_server( (struct msm_ipc_port *)handle->src_port); mutex_lock(&handle->handle_lock); list_for_each_entry_safe(conn_h, temp_conn_h, &handle->conn_list, list) rmv_svc_clnt_conn(conn_h); mutex_unlock(&handle->handle_lock); return 0; } EXPORT_SYMBOL(qmi_svc_unregister); static int __init qmi_interface_init(void) { qmi_log_init(); return 0; } module_init(qmi_interface_init); MODULE_DESCRIPTION("MSM QMI Interface"); MODULE_LICENSE("GPL v2");