diff options
Diffstat (limited to 'drivers/soc/qcom')
| -rw-r--r-- | drivers/soc/qcom/Kconfig | 12 | ||||
| -rw-r--r-- | drivers/soc/qcom/glink.c | 6 | ||||
| -rw-r--r-- | drivers/soc/qcom/icnss.c | 6 | ||||
| -rw-r--r-- | drivers/soc/qcom/ipc_router_glink_xprt.c | 11 | ||||
| -rw-r--r-- | drivers/soc/qcom/qdsp6v2/Makefile | 2 | ||||
| -rw-r--r-- | drivers/soc/qcom/qdsp6v2/apr_vm.c | 1270 | ||||
| -rw-r--r-- | drivers/soc/qcom/qdsp6v2/msm_audio_ion_vm.c | 838 |
7 files changed, 2140 insertions, 5 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 829876226689..a2136c6863d3 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -616,6 +616,15 @@ config MSM_QDSP6_APRV2 used by audio driver to configure QDSP6's ASM, ADM and AFE. +config MSM_QDSP6_APRV2_VM + bool "Audio QDSP6 APRv2 virtualization support" + depends on MSM_HAB + help + Enable APRv2 IPC protocol support over + HAB between application processor and + QDSP6. APR is used by audio driver to + configure QDSP6's ASM, ADM and AFE. + config MSM_QDSP6_APRV3 bool "Audio QDSP6 APRv3 support" depends on MSM_SMD @@ -800,7 +809,8 @@ config MSM_EVENT_TIMER config MSM_AVTIMER tristate "Avtimer Driver" - depends on MSM_QDSP6_APRV2 || MSM_QDSP6_APRV3 || MSM_QDSP6_APRV2_GLINK + depends on MSM_QDSP6_APRV2 || MSM_QDSP6_APRV3 || MSM_QDSP6_APRV2_GLINK || \ + MSM_QDSP6_APRV2_VM help This driver gets the Q6 out of power collapsed state and exposes ioctl control to read avtimer tick. diff --git a/drivers/soc/qcom/glink.c b/drivers/soc/qcom/glink.c index ad9bf3a2232d..b8464fdfd310 100644 --- a/drivers/soc/qcom/glink.c +++ b/drivers/soc/qcom/glink.c @@ -4294,6 +4294,12 @@ static void glink_core_channel_cleanup(struct glink_core_xprt_ctx *xprt_ptr) rwref_read_get(&xprt_ptr->xprt_state_lhb0); ctx = get_first_ch_ctx(xprt_ptr); while (ctx) { + spin_lock_irqsave(&xprt_ptr->tx_ready_lock_lhb3, flags); + spin_lock(&ctx->tx_lists_lock_lhc3); + if (!list_empty(&ctx->tx_active)) + glink_qos_done_ch_tx(ctx); + spin_unlock(&ctx->tx_lists_lock_lhc3); + spin_unlock_irqrestore(&xprt_ptr->tx_ready_lock_lhb3, flags); rwref_write_get_atomic(&ctx->ch_state_lhb2, true); if (ctx->local_open_state == GLINK_CHANNEL_OPENED || ctx->local_open_state == GLINK_CHANNEL_OPENING) { diff --git a/drivers/soc/qcom/icnss.c b/drivers/soc/qcom/icnss.c index e9a097151141..83efbbe25e6b 100644 --- a/drivers/soc/qcom/icnss.c +++ b/drivers/soc/qcom/icnss.c @@ -2347,7 +2347,7 @@ static int icnss_driver_event_pd_service_down(struct icnss_priv *priv, if (!test_bit(ICNSS_WLFW_EXISTS, &priv->state)) goto out; - if (test_bit(ICNSS_PD_RESTART, &priv->state)) { + if (test_bit(ICNSS_PD_RESTART, &priv->state) && event_data->crashed) { icnss_pr_err("PD Down while recovery inprogress, crashed: %d, state: 0x%lx\n", event_data->crashed, priv->state); ICNSS_ASSERT(0); @@ -2651,7 +2651,9 @@ event_post: clear_bit(ICNSS_HOST_TRIGGERED_PDR, &priv->state); fw_down_data.crashed = event_data->crashed; - icnss_call_driver_uevent(priv, ICNSS_UEVENT_FW_DOWN, &fw_down_data); + if (test_bit(ICNSS_FW_READY, &priv->state)) + icnss_call_driver_uevent(priv, ICNSS_UEVENT_FW_DOWN, + &fw_down_data); icnss_driver_event_post(ICNSS_DRIVER_EVENT_PD_SERVICE_DOWN, ICNSS_EVENT_SYNC, event_data); done: diff --git a/drivers/soc/qcom/ipc_router_glink_xprt.c b/drivers/soc/qcom/ipc_router_glink_xprt.c index 0c588c586306..d12031901b34 100644 --- a/drivers/soc/qcom/ipc_router_glink_xprt.c +++ b/drivers/soc/qcom/ipc_router_glink_xprt.c @@ -31,6 +31,7 @@ static int ipc_router_glink_xprt_debug_mask; module_param_named(debug_mask, ipc_router_glink_xprt_debug_mask, int, S_IRUGO | S_IWUSR | S_IWGRP); +#define IPCRTR_INTENT_REQ_TIMEOUT_MS 5000 #if defined(DEBUG) #define D(x...) do { \ if (ipc_router_glink_xprt_debug_mask) \ @@ -43,6 +44,7 @@ if (ipc_router_glink_xprt_debug_mask) \ #define MIN_FRAG_SZ (IPC_ROUTER_HDR_SIZE + sizeof(union rr_control_msg)) #define IPC_RTR_XPRT_NAME_LEN (2 * GLINK_NAME_SIZE) #define PIL_SUBSYSTEM_NAME_LEN 32 +#define IPC_RTR_WS_NAME_LEN ((2 * GLINK_NAME_SIZE) + 4) #define MAX_NUM_LO_INTENTS 5 #define MAX_NUM_MD_INTENTS 3 @@ -59,6 +61,7 @@ if (ipc_router_glink_xprt_debug_mask) \ * @transport: Physical Transport Name as identified by Glink. * @pil_edge: Edge name understood by PIL. * @ipc_rtr_xprt_name: XPRT Name to be registered with IPC Router. + * @notify_rx_ws_name: Name of wakesource used in notify rx path. * @xprt: IPC Router XPRT structure to contain XPRT specific info. * @ch_hndl: Opaque Channel handle returned by GLink. * @xprt_wq: Workqueue to queue read & other XPRT related works. @@ -79,6 +82,7 @@ struct ipc_router_glink_xprt { char transport[GLINK_NAME_SIZE]; char pil_edge[PIL_SUBSYSTEM_NAME_LEN]; char ipc_rtr_xprt_name[IPC_RTR_XPRT_NAME_LEN]; + char notify_rx_ws_name[IPC_RTR_WS_NAME_LEN]; struct msm_ipc_router_xprt xprt; void *ch_hndl; struct workqueue_struct *xprt_wq; @@ -604,6 +608,7 @@ static void glink_xprt_ch_open(struct ipc_router_glink_xprt *glink_xprtp) open_cfg.notify_state = glink_xprt_notify_state; open_cfg.notify_rx_intent_req = glink_xprt_notify_rx_intent_req; open_cfg.priv = glink_xprtp; + open_cfg.rx_intent_req_timeout_ms = IPCRTR_INTENT_REQ_TIMEOUT_MS; glink_xprtp->pil = msm_ipc_load_subsystem(glink_xprtp); glink_xprtp->ch_hndl = glink_open(&open_cfg); @@ -763,8 +768,10 @@ static int ipc_router_glink_config_init( kfree(glink_xprtp); return -EFAULT; } - - wakeup_source_init(&glink_xprtp->notify_rxv_ws, xprt_wq_name); + scnprintf(glink_xprtp->notify_rx_ws_name, IPC_RTR_WS_NAME_LEN, + "%s_%s_rx", glink_xprtp->ch_name, glink_xprtp->edge); + wakeup_source_init(&glink_xprtp->notify_rxv_ws, + glink_xprtp->notify_rx_ws_name); mutex_lock(&glink_xprt_list_lock_lha1); list_add(&glink_xprtp->list, &glink_xprt_list); mutex_unlock(&glink_xprt_list_lock_lha1); diff --git a/drivers/soc/qcom/qdsp6v2/Makefile b/drivers/soc/qcom/qdsp6v2/Makefile index 8c5b0d0e81c8..90feb8b659d1 100644 --- a/drivers/soc/qcom/qdsp6v2/Makefile +++ b/drivers/soc/qcom/qdsp6v2/Makefile @@ -2,7 +2,9 @@ obj-$(CONFIG_MSM_QDSP6_APRV2) += apr.o apr_v2.o apr_tal.o voice_svc.o obj-$(CONFIG_MSM_QDSP6_APRV3) += apr.o apr_v3.o apr_tal.o voice_svc.o obj-$(CONFIG_MSM_QDSP6_APRV2_GLINK) += apr.o apr_v2.o apr_tal_glink.o voice_svc.o obj-$(CONFIG_MSM_QDSP6_APRV3_GLINK) += apr.o apr_v3.o apr_tal_glink.o voice_svc.o +obj-$(CONFIG_MSM_QDSP6_APRV2_VM) += apr_vm.o apr_v2.o voice_svc.o obj-$(CONFIG_SND_SOC_MSM_QDSP6V2_INTF) += msm_audio_ion.o +obj-$(CONFIG_SND_SOC_QDSP6V2_VM) += msm_audio_ion_vm.o obj-$(CONFIG_MSM_ADSP_LOADER) += adsp-loader.o obj-$(CONFIG_MSM_QDSP6_SSR) += audio_ssr.o obj-$(CONFIG_MSM_QDSP6_PDR) += audio_pdr.o diff --git a/drivers/soc/qcom/qdsp6v2/apr_vm.c b/drivers/soc/qcom/qdsp6v2/apr_vm.c new file mode 100644 index 000000000000..d0ea7b22717a --- /dev/null +++ b/drivers/soc/qcom/qdsp6v2/apr_vm.c @@ -0,0 +1,1270 @@ +/* Copyright (c) 2016-2017 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/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/uaccess.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/debugfs.h> +#include <linux/platform_device.h> +#include <linux/sysfs.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <soc/qcom/subsystem_restart.h> +#include <soc/qcom/scm.h> +#include <sound/apr_audio-v2.h> +#include <linux/qdsp6v2/apr.h> +#include <linux/qdsp6v2/apr_tal.h> +#include <linux/qdsp6v2/aprv2_vm.h> +#include <linux/qdsp6v2/dsp_debug.h> +#include <linux/qdsp6v2/audio_notifier.h> +#include <linux/ipc_logging.h> +#include <linux/habmm.h> + +#define APR_PKT_IPC_LOG_PAGE_CNT 2 +#define APR_VM_CB_THREAD_NAME "apr_vm_cb_thread" +#define APR_TX_BUF_SIZE 4096 +#define APR_RX_BUF_SIZE 4096 + +static struct apr_q6 q6; +static struct apr_client client[APR_DEST_MAX][APR_CLIENT_MAX]; +static void *apr_pkt_ctx; +static wait_queue_head_t dsp_wait; +static wait_queue_head_t modem_wait; +static bool is_modem_up; +static bool is_initial_boot; +/* Subsystem restart: QDSP6 data, functions */ +static struct workqueue_struct *apr_reset_workqueue; +static void apr_reset_deregister(struct work_struct *work); +static void dispatch_event(unsigned long code, uint16_t proc); +struct apr_reset_work { + void *handle; + struct work_struct work; +}; + +static bool apr_cf_debug; + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_apr_debug; +static ssize_t apr_debug_write(struct file *filp, const char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + char cmd; + + if (copy_from_user(&cmd, ubuf, 1)) + return -EFAULT; + + apr_cf_debug = (cmd == '1') ? true : false; + + return cnt; +} + +static const struct file_operations apr_debug_ops = { + .write = apr_debug_write, +}; +#endif + +#define APR_PKT_INFO(x...) \ +do { \ + if (apr_pkt_ctx) \ + ipc_log_string(apr_pkt_ctx, "<APR>: "x); \ +} while (0) + +/* hab handle */ +static uint32_t hab_handle_tx; +static uint32_t hab_handle_rx; +static char apr_tx_buf[APR_TX_BUF_SIZE]; +static char apr_rx_buf[APR_RX_BUF_SIZE]; + +/* apr callback thread task */ +static struct task_struct *apr_vm_cb_thread_task; +static int pid; + + +struct apr_svc_table { + char name[64]; + int idx; + int id; + int dest_svc; + int client_id; + int handle; +}; + +/* + * src svc should be assigned dynamically through apr registration: + * 1. replace with a proper string name for registration. + * e.g. "qcom.apps.lnx." + name + * 2. register apr BE, retrieve dynamic src svc address, + * apr handle and store in svc tbl. + */ + +static struct mutex m_lock_tbl_qdsp6; + +static struct apr_svc_table svc_tbl_qdsp6[] = { + { + .name = "AFE", + .idx = 0, + .id = 0, + .dest_svc = APR_SVC_AFE, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "ASM", + .idx = 1, + .id = 0, + .dest_svc = APR_SVC_ASM, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "ADM", + .idx = 2, + .id = 0, + .dest_svc = APR_SVC_ADM, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "CORE", + .idx = 3, + .id = 0, + .dest_svc = APR_SVC_ADSP_CORE, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "TEST", + .idx = 4, + .id = 0, + .dest_svc = APR_SVC_TEST_CLIENT, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "MVM", + .idx = 5, + .id = 0, + .dest_svc = APR_SVC_ADSP_MVM, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "CVS", + .idx = 6, + .id = 0, + .dest_svc = APR_SVC_ADSP_CVS, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "CVP", + .idx = 7, + .id = 0, + .dest_svc = APR_SVC_ADSP_CVP, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "USM", + .idx = 8, + .id = 0, + .dest_svc = APR_SVC_USM, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "VIDC", + .idx = 9, + .id = 0, + .dest_svc = APR_SVC_VIDC, + .handle = 0, + }, + { + .name = "LSM", + .idx = 10, + .id = 0, + .dest_svc = APR_SVC_LSM, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, +}; + +static struct mutex m_lock_tbl_voice; + +static struct apr_svc_table svc_tbl_voice[] = { + { + .name = "VSM", + .idx = 0, + .id = 0, + .dest_svc = APR_SVC_VSM, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, + { + .name = "VPM", + .idx = 1, + .id = 0, + .dest_svc = APR_SVC_VPM, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, + { + .name = "MVS", + .idx = 2, + .id = 0, + .dest_svc = APR_SVC_MVS, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, + { + .name = "MVM", + .idx = 3, + .id = 0, + .dest_svc = APR_SVC_MVM, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, + { + .name = "CVS", + .idx = 4, + .id = 0, + .dest_svc = APR_SVC_CVS, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, + { + .name = "CVP", + .idx = 5, + .id = 0, + .dest_svc = APR_SVC_CVP, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, + { + .name = "SRD", + .idx = 6, + .id = 0, + .dest_svc = APR_SVC_SRD, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, + { + .name = "TEST", + .idx = 7, + .id = 0, + .dest_svc = APR_SVC_TEST_CLIENT, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, +}; + +enum apr_subsys_state apr_get_modem_state(void) +{ + return atomic_read(&q6.modem_state); +} + +void apr_set_modem_state(enum apr_subsys_state state) +{ + atomic_set(&q6.modem_state, state); +} + +enum apr_subsys_state apr_cmpxchg_modem_state(enum apr_subsys_state prev, + enum apr_subsys_state new) +{ + return atomic_cmpxchg(&q6.modem_state, prev, new); +} + +static void apr_modem_down(unsigned long opcode) +{ + apr_set_modem_state(APR_SUBSYS_DOWN); + dispatch_event(opcode, APR_DEST_MODEM); +} + +static void apr_modem_up(void) +{ + if (apr_cmpxchg_modem_state(APR_SUBSYS_DOWN, APR_SUBSYS_UP) == + APR_SUBSYS_DOWN) + wake_up(&modem_wait); + is_modem_up = 1; +} + +enum apr_subsys_state apr_get_q6_state(void) +{ + return atomic_read(&q6.q6_state); +} +EXPORT_SYMBOL(apr_get_q6_state); + +int apr_set_q6_state(enum apr_subsys_state state) +{ + pr_debug("%s: setting adsp state %d\n", __func__, state); + if (state < APR_SUBSYS_DOWN || state > APR_SUBSYS_LOADED) + return -EINVAL; + atomic_set(&q6.q6_state, state); + return 0; +} +EXPORT_SYMBOL(apr_set_q6_state); + +enum apr_subsys_state apr_cmpxchg_q6_state(enum apr_subsys_state prev, + enum apr_subsys_state new) +{ + return atomic_cmpxchg(&q6.q6_state, prev, new); +} + +static void apr_adsp_down(unsigned long opcode) +{ + apr_set_q6_state(APR_SUBSYS_DOWN); + dispatch_event(opcode, APR_DEST_QDSP6); +} + +static void apr_adsp_up(void) +{ + if (apr_cmpxchg_q6_state(APR_SUBSYS_DOWN, APR_SUBSYS_LOADED) == + APR_SUBSYS_DOWN) + wake_up(&dsp_wait); +} + +int apr_wait_for_device_up(int dest_id) +{ + int rc = -1; + + if (dest_id == APR_DEST_MODEM) + rc = wait_event_interruptible_timeout(modem_wait, + (apr_get_modem_state() == APR_SUBSYS_UP), + (1 * HZ)); + else if (dest_id == APR_DEST_QDSP6) + rc = wait_event_interruptible_timeout(dsp_wait, + (apr_get_q6_state() == APR_SUBSYS_UP), + (1 * HZ)); + else + pr_err("%s: unknown dest_id %d\n", __func__, dest_id); + /* returns left time */ + return rc; +} + +static int apr_vm_nb_receive(int32_t handle, void *dest_buff, + uint32_t *size_bytes, uint32_t timeout) +{ + int rc; + uint32_t dest_buff_bytes = *size_bytes; + unsigned long delay = jiffies + (HZ / 2); + + do { + *size_bytes = dest_buff_bytes; + rc = habmm_socket_recv(handle, + dest_buff, + size_bytes, + timeout, + HABMM_SOCKET_RECV_FLAGS_NON_BLOCKING); + } while (time_before(jiffies, delay) && (rc == -EAGAIN) && + (*size_bytes == 0)); + + return rc; +} + +static int apr_vm_cb_process_evt(char *buf, int len) +{ + struct apr_client_data data; + struct apr_client *apr_client; + struct apr_svc *c_svc; + struct apr_hdr *hdr; + uint16_t hdr_size; + uint16_t msg_type; + uint16_t ver; + uint16_t src; + uint16_t svc; + uint16_t clnt; + int i; + int temp_port = 0; + uint32_t *ptr; + uint32_t evt_id; + + pr_debug("APR: len = %d\n", len); + ptr = (uint32_t *)buf; + pr_debug("\n*****************\n"); + for (i = 0; i < len/4; i++) + pr_debug("%x ", ptr[i]); + pr_debug("\n"); + pr_debug("\n*****************\n"); + + if (!buf || len <= APR_HDR_SIZE + sizeof(uint32_t)) { + pr_err("APR: Improper apr pkt received: %p %d\n", buf, len); + return -EINVAL; + } + + evt_id = *((int32_t *)buf); + if (evt_id != APRV2_VM_EVT_RX_PKT_AVAILABLE) { + pr_err("APR: Wrong evt id: %d\n", evt_id); + return -EINVAL; + } + hdr = (struct apr_hdr *)(buf + sizeof(uint32_t)); + + ver = hdr->hdr_field; + ver = (ver & 0x000F); + if (ver > APR_PKT_VER + 1) { + pr_err("APR: Wrong version: %d\n", ver); + return -EINVAL; + } + + hdr_size = hdr->hdr_field; + hdr_size = ((hdr_size & 0x00F0) >> 0x4) * 4; + if (hdr_size < APR_HDR_SIZE) { + pr_err("APR: Wrong hdr size:%d\n", hdr_size); + return -EINVAL; + } + + if (hdr->pkt_size < APR_HDR_SIZE) { + pr_err("APR: Wrong paket size\n"); + return -EINVAL; + } + + msg_type = hdr->hdr_field; + msg_type = (msg_type >> 0x08) & 0x0003; + if (msg_type >= APR_MSG_TYPE_MAX && msg_type != APR_BASIC_RSP_RESULT) { + pr_err("APR: Wrong message type: %d\n", msg_type); + return -EINVAL; + } + + /* + * dest_svc is dynamic created by apr service + * no need to check the range of dest_svc + */ + if (hdr->src_domain >= APR_DOMAIN_MAX || + hdr->dest_domain >= APR_DOMAIN_MAX || + hdr->src_svc >= APR_SVC_MAX) { + pr_err("APR: Wrong APR header\n"); + return -EINVAL; + } + + svc = hdr->dest_svc; + if (hdr->src_domain == APR_DOMAIN_MODEM) + clnt = APR_CLIENT_VOICE; + else if (hdr->src_domain == APR_DOMAIN_ADSP) + clnt = APR_CLIENT_AUDIO; + else { + pr_err("APR: Pkt from wrong source: %d\n", hdr->src_domain); + return -EINVAL; + } + + src = apr_get_data_src(hdr); + if (src == APR_DEST_MAX) + return -EINVAL; + + pr_debug("src =%d clnt = %d\n", src, clnt); + apr_client = &client[src][clnt]; + for (i = 0; i < APR_SVC_MAX; i++) + if (apr_client->svc[i].id == svc) { + pr_debug("svc_id = %d\n", apr_client->svc[i].id); + c_svc = &apr_client->svc[i]; + break; + } + + if (i == APR_SVC_MAX) { + pr_err("APR: service is not registered\n"); + return -ENXIO; + } + + pr_debug("svc_idx = %d\n", i); + pr_debug("%x %x %x %p %p\n", c_svc->id, c_svc->dest_id, + c_svc->client_id, c_svc->fn, c_svc->priv); + + data.payload_size = hdr->pkt_size - hdr_size; + data.opcode = hdr->opcode; + data.src = src; + data.src_port = hdr->src_port; + data.dest_port = hdr->dest_port; + data.token = hdr->token; + data.msg_type = msg_type; + if (data.payload_size > 0) + data.payload = (char *)hdr + hdr_size; + + if (unlikely(apr_cf_debug)) { + if (hdr->opcode == APR_BASIC_RSP_RESULT && data.payload) { + uint32_t *ptr = data.payload; + + APR_PKT_INFO( + "Rx: src_addr[0x%X] dest_addr[0x%X] opcode[0x%X] token[0x%X] rc[0x%X]", + (hdr->src_domain << 8) | hdr->src_svc, + (hdr->dest_domain << 8) | hdr->dest_svc, + hdr->opcode, hdr->token, ptr[1]); + } else { + APR_PKT_INFO( + "Rx: src_addr[0x%X] dest_addr[0x%X] opcode[0x%X] token[0x%X]", + (hdr->src_domain << 8) | hdr->src_svc, + (hdr->dest_domain << 8) | hdr->dest_svc, hdr->opcode, + hdr->token); + } + } + + temp_port = ((data.dest_port >> 8) * 8) + (data.dest_port & 0xFF); + pr_debug("port = %d t_port = %d\n", data.src_port, temp_port); + if (c_svc->port_cnt && c_svc->port_fn[temp_port]) + c_svc->port_fn[temp_port](&data, c_svc->port_priv[temp_port]); + else if (c_svc->fn) + c_svc->fn(&data, c_svc->priv); + else + pr_err("APR: Rxed a packet for NULL callback\n"); + + return 0; +} + +static int apr_vm_cb_thread(void *data) +{ + uint32_t apr_rx_buf_len; + struct aprv2_vm_ack_rx_pkt_available_t apr_ack; + int status = 0; + int ret = 0; + + while (1) { + apr_rx_buf_len = sizeof(apr_rx_buf); + ret = habmm_socket_recv(hab_handle_rx, + (void *)&apr_rx_buf, + &apr_rx_buf_len, + 0xFFFFFFFF, + 0); + if (ret) { + pr_err("%s: habmm_socket_recv failed %d\n", + __func__, ret); + /* + * TODO: depends on the HAB error code, + * may need to implement + * a retry mechanism. + * break if recv failed ? + */ + break; + } + + status = apr_vm_cb_process_evt(apr_rx_buf, apr_rx_buf_len); + + apr_ack.status = status; + ret = habmm_socket_send(hab_handle_rx, + (void *)&apr_ack, + sizeof(apr_ack), + 0); + if (ret) { + pr_err("%s: habmm_socket_send failed %d\n", + __func__, ret); + /* TODO: break if send failed ? */ + break; + } + } + + return ret; +} + +static int apr_vm_get_svc(const char *svc_name, int domain_id, int *client_id, + int *svc_idx, int *svc_id, int *dest_svc, int *handle) +{ + int i; + int size; + struct apr_svc_table *tbl; + struct mutex *lock; + struct aprv2_vm_cmd_register_rsp_t apr_rsp; + uint32_t apr_len; + int ret = 0; + struct { + uint32_t cmd_id; + struct aprv2_vm_cmd_register_t reg_cmd; + } tx_data; + + if (domain_id == APR_DOMAIN_ADSP) { + tbl = svc_tbl_qdsp6; + size = ARRAY_SIZE(svc_tbl_qdsp6); + lock = &m_lock_tbl_qdsp6; + } else { + tbl = svc_tbl_voice; + size = ARRAY_SIZE(svc_tbl_voice); + lock = &m_lock_tbl_voice; + } + + mutex_lock(lock); + for (i = 0; i < size; i++) { + if (!strcmp(svc_name, tbl[i].name)) { + *client_id = tbl[i].client_id; + *svc_idx = tbl[i].idx; + if (!tbl[i].id && !tbl[i].handle) { + /* need to register a new service */ + memset((void *) &tx_data, 0, sizeof(tx_data)); + + apr_len = sizeof(tx_data); + tx_data.cmd_id = APRV2_VM_CMDID_REGISTER; + tx_data.reg_cmd.name_size = snprintf( + tx_data.reg_cmd.name, + APRV2_VM_MAX_DNS_SIZE, + "qcom.apps.lnx.%s", + svc_name); + tx_data.reg_cmd.addr = 0; + ret = habmm_socket_send(hab_handle_tx, + (void *) &tx_data, + apr_len, + 0); + if (ret) { + pr_err("%s: habmm_socket_send failed %d\n", + __func__, ret); + mutex_unlock(lock); + return ret; + } + /* wait for response */ + apr_len = sizeof(apr_rsp); + ret = apr_vm_nb_receive(hab_handle_tx, + (void *)&apr_rsp, + &apr_len, + 0xFFFFFFFF); + if (ret) { + pr_err("%s: apr_vm_nb_receive failed %d\n", + __func__, ret); + mutex_unlock(lock); + return ret; + } + if (apr_rsp.status) { + pr_err("%s: apr_vm_nb_receive status %d\n", + __func__, apr_rsp.status); + ret = apr_rsp.status; + mutex_unlock(lock); + return ret; + } + /* update svc table */ + tbl[i].handle = apr_rsp.handle; + tbl[i].id = apr_rsp.addr & + APRV2_VM_PKT_SERVICE_ID_MASK; + } + *svc_id = tbl[i].id; + *dest_svc = tbl[i].dest_svc; + *handle = tbl[i].handle; + break; + } + } + mutex_unlock(lock); + + pr_debug("%s: svc_name = %s client_id = %d domain_id = %d\n", + __func__, svc_name, *client_id, domain_id); + pr_debug("%s: src_svc = %d dest_svc = %d handle = %d\n", + __func__, *svc_id, *dest_svc, *handle); + + if (i == size) { + pr_err("%s: APR: Wrong svc name %s\n", __func__, svc_name); + ret = -EINVAL; + } + + return ret; +} + +static int apr_vm_rel_svc(int domain_id, int svc_id, int handle) +{ + int i; + int size; + struct apr_svc_table *tbl; + struct mutex *lock; + struct aprv2_vm_cmd_deregister_rsp_t apr_rsp; + uint32_t apr_len; + int ret = 0; + struct { + uint32_t cmd_id; + struct aprv2_vm_cmd_deregister_t dereg_cmd; + } tx_data; + + if (domain_id == APR_DOMAIN_ADSP) { + tbl = svc_tbl_qdsp6; + size = ARRAY_SIZE(svc_tbl_qdsp6); + lock = &m_lock_tbl_qdsp6; + } else { + tbl = svc_tbl_voice; + size = ARRAY_SIZE(svc_tbl_voice); + lock = &m_lock_tbl_voice; + } + + mutex_lock(lock); + for (i = 0; i < size; i++) { + if (tbl[i].id == svc_id && tbl[i].handle == handle) { + /* need to deregister a service */ + memset((void *) &tx_data, 0, sizeof(tx_data)); + + apr_len = sizeof(tx_data); + tx_data.cmd_id = APRV2_VM_CMDID_DEREGISTER; + tx_data.dereg_cmd.handle = handle; + ret = habmm_socket_send(hab_handle_tx, + (void *) &tx_data, + apr_len, + 0); + if (ret) + pr_err("%s: habmm_socket_send failed %d\n", + __func__, ret); + /* + * TODO: if send failed, should not wait for recv. + * should clear regardless? + */ + /* wait for response */ + apr_len = sizeof(apr_rsp); + ret = apr_vm_nb_receive(hab_handle_tx, + (void *)&apr_rsp, + &apr_len, + 0xFFFFFFFF); + if (ret) + pr_err("%s: apr_vm_nb_receive failed %d\n", + __func__, ret); + if (apr_rsp.status) { + pr_err("%s: apr_vm_nb_receive status %d\n", + __func__, apr_rsp.status); + ret = apr_rsp.status; + } + /* clear svc table */ + tbl[i].handle = 0; + tbl[i].id = 0; + break; + } + } + mutex_unlock(lock); + + if (i == size) { + pr_err("%s: APR: Wrong svc id %d handle %d\n", + __func__, svc_id, handle); + ret = -EINVAL; + } + + return ret; +} + +int apr_send_pkt(void *handle, uint32_t *buf) +{ + struct apr_svc *svc = handle; + struct apr_hdr *hdr; + unsigned long flags; + uint32_t *cmd_id = (uint32_t *)apr_tx_buf; + struct aprv2_vm_cmd_async_send_t *apr_send = + (struct aprv2_vm_cmd_async_send_t *)(apr_tx_buf + + sizeof(uint32_t)); + uint32_t apr_send_len; + struct aprv2_vm_cmd_async_send_rsp_t apr_rsp; + uint32_t apr_rsp_len; + int ret = 0; + + if (!handle || !buf) { + pr_err("APR: Wrong parameters\n"); + return -EINVAL; + } + if (svc->need_reset) { + pr_err("APR: send_pkt service need reset\n"); + return -ENETRESET; + } + + if ((svc->dest_id == APR_DEST_QDSP6) && + (apr_get_q6_state() != APR_SUBSYS_LOADED)) { + pr_err("%s: Still dsp is not Up\n", __func__); + return -ENETRESET; + } else if ((svc->dest_id == APR_DEST_MODEM) && + (apr_get_modem_state() == APR_SUBSYS_DOWN)) { + pr_err("%s: Still Modem is not Up\n", __func__); + return -ENETRESET; + } + + spin_lock_irqsave(&svc->w_lock, flags); + if (!svc->id || !svc->vm_handle) { + pr_err("APR: Still service is not yet opened\n"); + ret = -EINVAL; + goto done; + } + hdr = (struct apr_hdr *)buf; + + hdr->src_domain = APR_DOMAIN_APPS; + hdr->src_svc = svc->id; + hdr->dest_domain = svc->dest_domain; + hdr->dest_svc = svc->vm_dest_svc; + + if (unlikely(apr_cf_debug)) { + APR_PKT_INFO( + "Tx: src_addr[0x%X] dest_addr[0x%X] opcode[0x%X] token[0x%X]", + (hdr->src_domain << 8) | hdr->src_svc, + (hdr->dest_domain << 8) | hdr->dest_svc, hdr->opcode, + hdr->token); + } + + memset((void *)&apr_tx_buf, 0, sizeof(apr_tx_buf)); + /* pkt_size + cmd_id + handle */ + apr_send_len = hdr->pkt_size + sizeof(uint32_t) * 2; + *cmd_id = APRV2_VM_CMDID_ASYNC_SEND; + apr_send->handle = svc->vm_handle; + + /* safe check */ + if (hdr->pkt_size > APR_TX_BUF_SIZE - (sizeof(uint32_t) * 2)) { + pr_err("APR: Wrong pkt size %d\n", hdr->pkt_size); + ret = -ENOMEM; + goto done; + } + memcpy(&apr_send->pkt_header, buf, hdr->pkt_size); + + ret = habmm_socket_send(hab_handle_tx, + (void *)&apr_tx_buf, + apr_send_len, + 0); + if (ret) { + pr_err("%s: habmm_socket_send failed %d\n", + __func__, ret); + goto done; + } + /* wait for response */ + apr_rsp_len = sizeof(apr_rsp); + ret = apr_vm_nb_receive(hab_handle_tx, + (void *)&apr_rsp, + &apr_rsp_len, + 0xFFFFFFFF); + if (ret) { + pr_err("%s: apr_vm_nb_receive failed %d\n", + __func__, ret); + goto done; + } + if (apr_rsp.status) { + pr_err("%s: apr_vm_nb_receive status %d\n", + __func__, apr_rsp.status); + /* should translate status properly */ + ret = -ECOMM; + goto done; + } + + /* upon successful send, return packet size */ + ret = hdr->pkt_size; + +done: + spin_unlock_irqrestore(&svc->w_lock, flags); + return ret; +} + +struct apr_svc *apr_register(char *dest, char *svc_name, apr_fn svc_fn, + uint32_t src_port, void *priv) +{ + struct apr_client *clnt; + int client_id = 0; + int svc_idx = 0; + int svc_id = 0; + int dest_id = 0; + int domain_id = 0; + int temp_port = 0; + struct apr_svc *svc = NULL; + int rc = 0; + bool can_open_channel = true; + int dest_svc = 0; + int handle = 0; + + if (!dest || !svc_name || !svc_fn) + return NULL; + + if (!strcmp(dest, "ADSP")) + domain_id = APR_DOMAIN_ADSP; + else if (!strcmp(dest, "MODEM")) { + /* Don't request for SMD channels if destination is MODEM, + * as these channels are no longer used and these clients + * are to listen only for MODEM SSR events + */ + can_open_channel = false; + domain_id = APR_DOMAIN_MODEM; + } else { + pr_err("APR: wrong destination\n"); + goto done; + } + + dest_id = apr_get_dest_id(dest); + + if (dest_id == APR_DEST_QDSP6) { + if (apr_get_q6_state() != APR_SUBSYS_LOADED) { + pr_err("%s: adsp not up\n", __func__); + return NULL; + } + pr_debug("%s: adsp Up\n", __func__); + } else if (dest_id == APR_DEST_MODEM) { + if (apr_get_modem_state() == APR_SUBSYS_DOWN) { + if (is_modem_up) { + pr_err("%s: modem shutdown due to SSR, ret", + __func__); + return NULL; + } + pr_debug("%s: Wait for modem to bootup\n", __func__); + rc = apr_wait_for_device_up(APR_DEST_MODEM); + if (rc == 0) { + pr_err("%s: Modem is not Up\n", __func__); + return NULL; + } + } + pr_debug("%s: modem Up\n", __func__); + } + + if (apr_vm_get_svc(svc_name, domain_id, &client_id, &svc_idx, &svc_id, + &dest_svc, &handle)) { + pr_err("%s: apr_vm_get_svc failed\n", __func__); + goto done; + } + + clnt = &client[dest_id][client_id]; + svc = &clnt->svc[svc_idx]; + mutex_lock(&svc->m_lock); + clnt->id = client_id; + if (svc->need_reset) { + mutex_unlock(&svc->m_lock); + pr_err("APR: Service needs reset\n"); + goto done; + } + svc->id = svc_id; + svc->vm_dest_svc = dest_svc; + svc->dest_id = dest_id; + svc->client_id = client_id; + svc->dest_domain = domain_id; + svc->pkt_owner = APR_PKT_OWNER_DRIVER; + svc->vm_handle = handle; + + if (src_port != 0xFFFFFFFF) { + temp_port = ((src_port >> 8) * 8) + (src_port & 0xFF); + pr_debug("port = %d t_port = %d\n", src_port, temp_port); + if (temp_port >= APR_MAX_PORTS || temp_port < 0) { + pr_err("APR: temp_port out of bounds\n"); + mutex_unlock(&svc->m_lock); + return NULL; + } + if (!svc->port_cnt && !svc->svc_cnt) + clnt->svc_cnt++; + svc->port_cnt++; + svc->port_fn[temp_port] = svc_fn; + svc->port_priv[temp_port] = priv; + } else { + if (!svc->fn) { + if (!svc->port_cnt && !svc->svc_cnt) + clnt->svc_cnt++; + svc->fn = svc_fn; + if (svc->port_cnt) + svc->svc_cnt++; + svc->priv = priv; + } + } + + mutex_unlock(&svc->m_lock); +done: + return svc; +} + +static void apr_reset_deregister(struct work_struct *work) +{ + struct apr_svc *handle = NULL; + struct apr_reset_work *apr_reset = + container_of(work, struct apr_reset_work, work); + + handle = apr_reset->handle; + pr_debug("%s:handle[%pK]\n", __func__, handle); + apr_deregister(handle); + kfree(apr_reset); +} + +int apr_deregister(void *handle) +{ + struct apr_svc *svc = handle; + struct apr_client *clnt; + uint16_t dest_id; + uint16_t client_id; + + if (!handle) + return -EINVAL; + + mutex_lock(&svc->m_lock); + dest_id = svc->dest_id; + client_id = svc->client_id; + clnt = &client[dest_id][client_id]; + + if (svc->port_cnt > 0 || svc->svc_cnt > 0) { + if (svc->port_cnt) + svc->port_cnt--; + else if (svc->svc_cnt) + svc->svc_cnt--; + if (!svc->port_cnt && !svc->svc_cnt) { + client[dest_id][client_id].svc_cnt--; + svc->need_reset = 0x0; + } + } else if (client[dest_id][client_id].svc_cnt > 0) { + client[dest_id][client_id].svc_cnt--; + if (!client[dest_id][client_id].svc_cnt) { + svc->need_reset = 0x0; + pr_debug("%s: service is reset %p\n", __func__, svc); + } + } + + if (!svc->port_cnt && !svc->svc_cnt) { + if (apr_vm_rel_svc(svc->dest_domain, svc->id, svc->vm_handle)) + pr_err("%s: apr_vm_rel_svc failed\n", __func__); + svc->priv = NULL; + svc->id = 0; + svc->vm_dest_svc = 0; + svc->fn = NULL; + svc->dest_id = 0; + svc->client_id = 0; + svc->need_reset = 0x0; + svc->vm_handle = 0; + } + mutex_unlock(&svc->m_lock); + + return 0; +} + +void apr_reset(void *handle) +{ + struct apr_reset_work *apr_reset_worker = NULL; + + if (!handle) + return; + pr_debug("%s: handle[%pK]\n", __func__, handle); + + if (apr_reset_workqueue == NULL) { + pr_err("%s: apr_reset_workqueue is NULL\n", __func__); + return; + } + + apr_reset_worker = kzalloc(sizeof(struct apr_reset_work), + GFP_ATOMIC); + + if (apr_reset_worker == NULL) { + pr_err("%s: mem failure\n", __func__); + return; + } + + apr_reset_worker->handle = handle; + INIT_WORK(&apr_reset_worker->work, apr_reset_deregister); + queue_work(apr_reset_workqueue, &apr_reset_worker->work); +} + +/* Dispatch the Reset events to Modem and audio clients */ +static void dispatch_event(unsigned long code, uint16_t proc) +{ + struct apr_client *apr_client; + struct apr_client_data data; + struct apr_svc *svc; + uint16_t clnt; + int i, j; + + memset(&data, 0, sizeof(data)); + data.opcode = RESET_EVENTS; + data.reset_event = code; + + /* Service domain can be different from the processor */ + data.reset_proc = apr_get_reset_domain(proc); + + clnt = APR_CLIENT_AUDIO; + apr_client = &client[proc][clnt]; + for (i = 0; i < APR_SVC_MAX; i++) { + mutex_lock(&apr_client->svc[i].m_lock); + if (apr_client->svc[i].fn) { + apr_client->svc[i].need_reset = 0x1; + apr_client->svc[i].fn(&data, apr_client->svc[i].priv); + } + if (apr_client->svc[i].port_cnt) { + svc = &(apr_client->svc[i]); + svc->need_reset = 0x1; + for (j = 0; j < APR_MAX_PORTS; j++) + if (svc->port_fn[j]) + svc->port_fn[j](&data, + svc->port_priv[j]); + } + mutex_unlock(&apr_client->svc[i].m_lock); + } + + clnt = APR_CLIENT_VOICE; + apr_client = &client[proc][clnt]; + for (i = 0; i < APR_SVC_MAX; i++) { + mutex_lock(&apr_client->svc[i].m_lock); + if (apr_client->svc[i].fn) { + apr_client->svc[i].need_reset = 0x1; + apr_client->svc[i].fn(&data, apr_client->svc[i].priv); + } + if (apr_client->svc[i].port_cnt) { + svc = &(apr_client->svc[i]); + svc->need_reset = 0x1; + for (j = 0; j < APR_MAX_PORTS; j++) + if (svc->port_fn[j]) + svc->port_fn[j](&data, + svc->port_priv[j]); + } + mutex_unlock(&apr_client->svc[i].m_lock); + } +} + +static int apr_notifier_service_cb(struct notifier_block *this, + unsigned long opcode, void *data) +{ + struct audio_notifier_cb_data *cb_data = data; + + if (cb_data == NULL) { + pr_err("%s: Callback data is NULL!\n", __func__); + goto done; + } + + pr_debug("%s: Service opcode 0x%lx, domain %d\n", + __func__, opcode, cb_data->domain); + + switch (opcode) { + case AUDIO_NOTIFIER_SERVICE_DOWN: + /* + * Use flag to ignore down notifications during + * initial boot. There is no benefit from error + * recovery notifications during initial boot + * up since everything is expected to be down. + */ + if (is_initial_boot) { + is_initial_boot = false; + break; + } + if (cb_data->domain == AUDIO_NOTIFIER_MODEM_DOMAIN) + apr_modem_down(opcode); + else + apr_adsp_down(opcode); + break; + case AUDIO_NOTIFIER_SERVICE_UP: + is_initial_boot = false; + if (cb_data->domain == AUDIO_NOTIFIER_MODEM_DOMAIN) + apr_modem_up(); + else + apr_adsp_up(); + break; + default: + break; + } +done: + return NOTIFY_OK; +} + +static struct notifier_block adsp_service_nb = { + .notifier_call = apr_notifier_service_cb, + .priority = 0, +}; + +static struct notifier_block modem_service_nb = { + .notifier_call = apr_notifier_service_cb, + .priority = 0, +}; + +static void apr_vm_set_subsys_state(void) +{ + /* set default subsys state in vm env. + * Both q6 and modem should be in LOADED state, + * since vm boots up at late stage after pm. + */ + apr_set_q6_state(APR_SUBSYS_LOADED); + apr_set_modem_state(APR_SUBSYS_LOADED); +} + +static int __init apr_init(void) +{ + int i, j, k; + int ret; + + /* open apr channel tx and rx, store as global */ + ret = habmm_socket_open(&hab_handle_tx, + MM_AUD_1, + 0xFFFFFFFF, + HABMM_SOCKET_OPEN_FLAGS_SINGLE_BE_SINGLE_FE); + if (ret) { + pr_err("%s: habmm_socket_open tx failed %d\n", __func__, ret); + return ret; + } + + ret = habmm_socket_open(&hab_handle_rx, + MM_AUD_2, + 0xFFFFFFFF, + HABMM_SOCKET_OPEN_FLAGS_SINGLE_BE_SINGLE_FE); + if (ret) { + pr_err("%s: habmm_socket_open rx failed %d\n", __func__, ret); + habmm_socket_close(hab_handle_tx); + return ret; + } + pr_info("%s: hab_handle_tx %x hab_handle_rx %x\n", + __func__, hab_handle_tx, hab_handle_rx); + + /* create apr ch rx cb thread */ + apr_vm_cb_thread_task = kthread_run(apr_vm_cb_thread, + NULL, + APR_VM_CB_THREAD_NAME); + if (IS_ERR(apr_vm_cb_thread_task)) { + ret = PTR_ERR(apr_vm_cb_thread_task); + pr_err("%s: kthread_run failed %d\n", __func__, ret); + habmm_socket_close(hab_handle_tx); + habmm_socket_close(hab_handle_rx); + return ret; + } + pid = apr_vm_cb_thread_task->pid; + pr_info("%s: apr_vm_cb_thread started pid %d\n", + __func__, pid); + + mutex_init(&m_lock_tbl_qdsp6); + mutex_init(&m_lock_tbl_voice); + + for (i = 0; i < APR_DEST_MAX; i++) + for (j = 0; j < APR_CLIENT_MAX; j++) { + mutex_init(&client[i][j].m_lock); + for (k = 0; k < APR_SVC_MAX; k++) { + mutex_init(&client[i][j].svc[k].m_lock); + spin_lock_init(&client[i][j].svc[k].w_lock); + } + } + + apr_vm_set_subsys_state(); + mutex_init(&q6.lock); + apr_reset_workqueue = create_singlethread_workqueue("apr_driver"); + if (!apr_reset_workqueue) { + habmm_socket_close(hab_handle_tx); + habmm_socket_close(hab_handle_rx); + kthread_stop(apr_vm_cb_thread_task); + return -ENOMEM; + } + + apr_pkt_ctx = ipc_log_context_create(APR_PKT_IPC_LOG_PAGE_CNT, + "apr", 0); + if (!apr_pkt_ctx) + pr_err("%s: Unable to create ipc log context\n", __func__); + + is_initial_boot = true; + subsys_notif_register("apr_adsp", AUDIO_NOTIFIER_ADSP_DOMAIN, + &adsp_service_nb); + subsys_notif_register("apr_modem", AUDIO_NOTIFIER_MODEM_DOMAIN, + &modem_service_nb); + + return 0; +} +device_initcall(apr_init); + +static int __init apr_late_init(void) +{ + int ret = 0; + + init_waitqueue_head(&dsp_wait); + init_waitqueue_head(&modem_wait); + + return ret; +} +late_initcall(apr_late_init); + +static void __exit apr_exit(void) +{ + habmm_socket_close(hab_handle_tx); + habmm_socket_close(hab_handle_rx); + kthread_stop(apr_vm_cb_thread_task); +} +__exitcall(apr_exit); + +#ifdef CONFIG_DEBUG_FS +static int __init apr_debug_init(void) +{ + debugfs_apr_debug = debugfs_create_file("msm_apr_debug", + S_IFREG | S_IRUGO, NULL, NULL, + &apr_debug_ops); + return 0; +} +device_initcall(apr_debug_init); +#endif diff --git a/drivers/soc/qcom/qdsp6v2/msm_audio_ion_vm.c b/drivers/soc/qcom/qdsp6v2/msm_audio_ion_vm.c new file mode 100644 index 000000000000..a3aa8823d8ce --- /dev/null +++ b/drivers/soc/qcom/qdsp6v2/msm_audio_ion_vm.c @@ -0,0 +1,838 @@ +/* + * Copyright (c) 2016-2017 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/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/of_device.h> +#include <linux/msm_audio_ion.h> +#include <linux/habmm.h> +#include "../../../staging/android/ion/ion_priv.h" +#include "../../../staging/android/ion/ion_hvenv_driver.h" + +#define MSM_AUDIO_ION_PROBED (1 << 0) + +#define MSM_AUDIO_SMMU_VM_CMD_MAP 0x00000001 +#define MSM_AUDIO_SMMU_VM_CMD_UNMAP 0x00000002 +#define MSM_AUDIO_SMMU_VM_HAB_MINOR_ID 1 + +struct msm_audio_ion_private { + bool smmu_enabled; + bool audioheap_enabled; + u8 device_status; + struct list_head smmu_map_list; + struct mutex smmu_map_mutex; +}; + +struct msm_audio_smmu_map_data { + struct ion_client *client; + struct ion_handle *handle; + u32 export_id; + struct list_head list; +}; + +struct msm_audio_smmu_vm_map_cmd { + int cmd_id; + u32 export_id; + u32 buf_size; +}; + +struct msm_audio_smmu_vm_map_cmd_rsp { + int status; + u64 addr; +}; + +struct msm_audio_smmu_vm_unmap_cmd { + int cmd_id; + u32 export_id; +}; + +struct msm_audio_smmu_vm_unmap_cmd_rsp { + int status; +}; + +static struct msm_audio_ion_private msm_audio_ion_data = {0,}; +static u32 msm_audio_ion_hab_handle; + +static int msm_audio_ion_get_phys(struct ion_client *client, + struct ion_handle *handle, + ion_phys_addr_t *addr, size_t *len, + void *vaddr); + +static int msm_audio_ion_smmu_map(struct ion_client *client, + struct ion_handle *handle, + ion_phys_addr_t *addr, size_t *len, void *vaddr) +{ + int rc; + u32 export_id; + u32 cmd_rsp_size; + bool exported = false; + struct msm_audio_smmu_vm_map_cmd_rsp cmd_rsp; + struct msm_audio_smmu_map_data *map_data = NULL; + struct msm_audio_smmu_vm_map_cmd smmu_map_cmd; + + rc = ion_handle_get_size(client, handle, len); + if (rc) { + pr_err("%s: ion_handle_get_size failed, client = %pK, handle = %pK, rc = %d\n", + __func__, client, handle, rc); + goto err; + } + + /* Data required to track per buffer mapping */ + map_data = kzalloc(sizeof(*map_data), GFP_KERNEL); + if (!map_data) { + rc = -ENOMEM; + goto err; + } + + /* Export the buffer to physical VM */ + rc = habmm_export(msm_audio_ion_hab_handle, vaddr, *len, + &export_id, 0); + if (rc) { + pr_err("%s: habmm_export failed vaddr = %pK, len = %zd, rc = %d\n", + __func__, vaddr, *len, rc); + goto err; + } + + exported = true; + smmu_map_cmd.cmd_id = MSM_AUDIO_SMMU_VM_CMD_MAP; + smmu_map_cmd.export_id = export_id; + smmu_map_cmd.buf_size = *len; + + mutex_lock(&(msm_audio_ion_data.smmu_map_mutex)); + rc = habmm_socket_send(msm_audio_ion_hab_handle, + (void *)&smmu_map_cmd, sizeof(smmu_map_cmd), 0); + if (rc) { + pr_err("%s: habmm_socket_send failed %d\n", + __func__, rc); + mutex_unlock(&(msm_audio_ion_data.smmu_map_mutex)); + goto err; + } + + cmd_rsp_size = sizeof(cmd_rsp); + rc = habmm_socket_recv(msm_audio_ion_hab_handle, + (void *)&cmd_rsp, + &cmd_rsp_size, + 0xFFFFFFFF, + 0); + if (rc) { + pr_err("%s: habmm_socket_recv failed %d\n", + __func__, rc); + mutex_unlock(&(msm_audio_ion_data.smmu_map_mutex)); + goto err; + } + mutex_unlock(&(msm_audio_ion_data.smmu_map_mutex)); + + if (cmd_rsp_size != sizeof(cmd_rsp)) { + pr_err("%s: invalid size for cmd rsp %lu, expected %lu\n", + __func__, cmd_rsp_size, sizeof(cmd_rsp)); + rc = -EIO; + goto err; + } + + if (cmd_rsp.status) { + pr_err("%s: SMMU map command failed %d\n", + __func__, cmd_rsp.status); + rc = cmd_rsp.status; + goto err; + } + + *addr = (ion_phys_addr_t)cmd_rsp.addr; + + map_data->client = client; + map_data->handle = handle; + map_data->export_id = export_id; + + mutex_lock(&(msm_audio_ion_data.smmu_map_mutex)); + list_add_tail(&(map_data->list), + &(msm_audio_ion_data.smmu_map_list)); + mutex_unlock(&(msm_audio_ion_data.smmu_map_mutex)); + + return 0; + +err: + if (exported) + (void)habmm_unexport(msm_audio_ion_hab_handle, export_id, 0); + + kfree(map_data); + + return rc; +} + +static int msm_audio_ion_smmu_unmap(struct ion_client *client, + struct ion_handle *handle) +{ + int rc; + bool found = false; + u32 cmd_rsp_size; + struct msm_audio_smmu_vm_unmap_cmd_rsp cmd_rsp; + struct msm_audio_smmu_map_data *map_data, *next; + struct msm_audio_smmu_vm_unmap_cmd smmu_unmap_cmd; + + /* + * Though list_for_each_entry_safe is delete safe, lock + * should be explicitly acquired to avoid race condition + * on adding elements to the list. + */ + mutex_lock(&(msm_audio_ion_data.smmu_map_mutex)); + list_for_each_entry_safe(map_data, next, + &(msm_audio_ion_data.smmu_map_list), list) { + + if (map_data->handle == handle && map_data->client == client) { + found = true; + smmu_unmap_cmd.cmd_id = MSM_AUDIO_SMMU_VM_CMD_UNMAP; + smmu_unmap_cmd.export_id = map_data->export_id; + + rc = habmm_socket_send(msm_audio_ion_hab_handle, + (void *)&smmu_unmap_cmd, + sizeof(smmu_unmap_cmd), 0); + if (rc) { + pr_err("%s: habmm_socket_send failed %d\n", + __func__, rc); + goto err; + } + + cmd_rsp_size = sizeof(cmd_rsp); + rc = habmm_socket_recv(msm_audio_ion_hab_handle, + (void *)&cmd_rsp, + &cmd_rsp_size, + 0xFFFFFFFF, + 0); + if (rc) { + pr_err("%s: habmm_socket_recv failed %d\n", + __func__, rc); + goto err; + } + + if (cmd_rsp_size != sizeof(cmd_rsp)) { + pr_err("%s: invalid size for cmd rsp %lu\n", + __func__, cmd_rsp_size); + rc = -EIO; + goto err; + } + + if (cmd_rsp.status) { + pr_err("%s: SMMU unmap command failed %d\n", + __func__, cmd_rsp.status); + rc = cmd_rsp.status; + goto err; + } + + rc = habmm_unexport(msm_audio_ion_hab_handle, + map_data->export_id, 0xFFFFFFFF); + if (rc) { + pr_err("%s: habmm_unexport failed export_id = %d, rc = %d\n", + __func__, map_data->export_id, rc); + } + + list_del(&(map_data->list)); + kfree(map_data); + break; + } + } + mutex_unlock(&(msm_audio_ion_data.smmu_map_mutex)); + + if (!found) { + pr_err("%s: cannot find map_data ion_handle %pK, ion_client %pK\n", + __func__, handle, client); + rc = -EINVAL; + } + + return rc; + +err: + if (found) { + (void)habmm_unexport(msm_audio_ion_hab_handle, + map_data->export_id, 0xFFFFFFFF); + list_del(&(map_data->list)); + kfree(map_data); + } + + mutex_unlock(&(msm_audio_ion_data.smmu_map_mutex)); + return rc; +} + +int msm_audio_ion_alloc(const char *name, struct ion_client **client, + struct ion_handle **handle, size_t bufsz, + ion_phys_addr_t *paddr, size_t *pa_len, void **vaddr) +{ + int rc = -EINVAL; + unsigned long err_ion_ptr = 0; + + if ((msm_audio_ion_data.smmu_enabled == true) && + !(msm_audio_ion_data.device_status & MSM_AUDIO_ION_PROBED)) { + pr_debug("%s:probe is not done, deferred\n", __func__); + return -EPROBE_DEFER; + } + if (!name || !client || !handle || !paddr || !vaddr + || !bufsz || !pa_len) { + pr_err("%s: Invalid params\n", __func__); + return -EINVAL; + } + *client = msm_audio_ion_client_create(name); + if (IS_ERR_OR_NULL((void *)(*client))) { + pr_err("%s: ION create client for AUDIO failed\n", __func__); + goto err; + } + + *handle = ion_alloc(*client, bufsz, SZ_4K, + ION_HEAP(ION_AUDIO_HEAP_ID), 0); + if (IS_ERR_OR_NULL((void *) (*handle))) { + if (msm_audio_ion_data.smmu_enabled == true) { + pr_debug("system heap is used"); + msm_audio_ion_data.audioheap_enabled = 0; + *handle = ion_alloc(*client, bufsz, SZ_4K, + ION_HEAP(ION_SYSTEM_HEAP_ID), 0); + } + if (IS_ERR_OR_NULL((void *) (*handle))) { + if (IS_ERR((void *)(*handle))) + err_ion_ptr = PTR_ERR((int *)(*handle)); + pr_err("%s:ION alloc fail err ptr=%ld, smmu_enabled=%d\n", + __func__, err_ion_ptr, msm_audio_ion_data.smmu_enabled); + rc = -ENOMEM; + goto err_ion_client; + } + } else { + pr_debug("audio heap is used"); + msm_audio_ion_data.audioheap_enabled = 1; + } + + *vaddr = ion_map_kernel(*client, *handle); + if (IS_ERR_OR_NULL((void *)*vaddr)) { + pr_err("%s: ION memory mapping for AUDIO failed\n", __func__); + goto err_ion_handle; + } + pr_debug("%s: mapped address = %pK, size=%zd\n", __func__, + *vaddr, bufsz); + + if (bufsz != 0) { + pr_debug("%s: memset to 0 %pK %zd\n", __func__, *vaddr, bufsz); + memset((void *)*vaddr, 0, bufsz); + } + + rc = msm_audio_ion_get_phys(*client, *handle, paddr, pa_len, *vaddr); + if (rc) { + pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n", + __func__, rc); + goto err_get_phys; + } + + return rc; + +err_get_phys: + ion_unmap_kernel(*client, *handle); +err_ion_handle: + ion_free(*client, *handle); +err_ion_client: + msm_audio_ion_client_destroy(*client); + *handle = NULL; + *client = NULL; +err: + return rc; +} +EXPORT_SYMBOL(msm_audio_ion_alloc); + +int msm_audio_ion_phys_free(struct ion_client *client, + struct ion_handle *handle, + ion_phys_addr_t *paddr, + size_t *pa_len, u8 assign_type) +{ + if (!(msm_audio_ion_data.device_status & MSM_AUDIO_ION_PROBED)) { + pr_debug("%s:probe is not done, deferred\n", __func__); + return -EPROBE_DEFER; + } + + if (!client || !handle || !paddr || !pa_len) { + pr_err("%s: Invalid params\n", __func__); + return -EINVAL; + } + + /* hyp assign is not supported in VM */ + + ion_free(client, handle); + ion_client_destroy(client); + + return 0; +} + +int msm_audio_ion_phys_assign(const char *name, struct ion_client **client, + struct ion_handle **handle, int fd, + ion_phys_addr_t *paddr, + size_t *pa_len, u8 assign_type) +{ + int ret; + + if (!(msm_audio_ion_data.device_status & MSM_AUDIO_ION_PROBED)) { + pr_debug("%s:probe is not done, deferred\n", __func__); + return -EPROBE_DEFER; + } + + if (!name || !client || !handle || !paddr || !pa_len) { + pr_err("%s: Invalid params\n", __func__); + return -EINVAL; + } + + *client = msm_audio_ion_client_create(name); + if (IS_ERR_OR_NULL((void *)(*client))) { + pr_err("%s: ION create client failed\n", __func__); + return -EINVAL; + } + + *handle = ion_import_dma_buf(*client, fd); + if (IS_ERR_OR_NULL((void *) (*handle))) { + pr_err("%s: ion import dma buffer failed\n", + __func__); + ret = -EINVAL; + goto err_destroy_client; + } + + ret = ion_phys(*client, *handle, paddr, pa_len); + if (ret) { + pr_err("%s: could not get physical address for handle, ret = %d\n", + __func__, ret); + goto err_ion_handle; + } + + /* hyp assign is not supported in VM */ + + return ret; + +err_ion_handle: + ion_free(*client, *handle); + +err_destroy_client: + ion_client_destroy(*client); + *client = NULL; + *handle = NULL; + + return ret; +} + +int msm_audio_ion_import(const char *name, struct ion_client **client, + struct ion_handle **handle, int fd, + unsigned long *ionflag, size_t bufsz, + ion_phys_addr_t *paddr, size_t *pa_len, void **vaddr) +{ + int rc = 0; + + if ((msm_audio_ion_data.smmu_enabled == true) && + !(msm_audio_ion_data.device_status & MSM_AUDIO_ION_PROBED)) { + pr_debug("%s:probe is not done, deferred\n", __func__); + return -EPROBE_DEFER; + } + + if (!name || !client || !handle || !paddr || !vaddr || !pa_len) { + pr_err("%s: Invalid params\n", __func__); + rc = -EINVAL; + goto err; + } + + *client = msm_audio_ion_client_create(name); + if (IS_ERR_OR_NULL((void *)(*client))) { + pr_err("%s: ION create client for AUDIO failed\n", __func__); + rc = -EINVAL; + goto err; + } + + /* name should be audio_acdb_client or Audio_Dec_Client, + * bufsz should be 0 and fd shouldn't be 0 as of now + */ + *handle = ion_import_dma_buf(*client, fd); + pr_debug("%s: DMA Buf name=%s, fd=%d handle=%pK\n", __func__, + name, fd, *handle); + if (IS_ERR_OR_NULL((void *) (*handle))) { + pr_err("%s: ion import dma buffer failed\n", + __func__); + rc = -EINVAL; + goto err_destroy_client; + } + + if (ionflag != NULL) { + rc = ion_handle_get_flags(*client, *handle, ionflag); + if (rc) { + pr_err("%s: could not get flags for the handle\n", + __func__); + goto err_ion_handle; + } + } + + *vaddr = ion_map_kernel(*client, *handle); + if (IS_ERR_OR_NULL((void *)*vaddr)) { + pr_err("%s: ION memory mapping for AUDIO failed\n", __func__); + rc = -ENOMEM; + goto err_ion_handle; + } + pr_debug("%s: mapped address = %pK, size=%zd\n", __func__, + *vaddr, bufsz); + + rc = msm_audio_ion_get_phys(*client, *handle, paddr, pa_len, *vaddr); + if (rc) { + pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n", + __func__, rc); + goto err_get_phys; + } + + return 0; + +err_get_phys: + ion_unmap_kernel(*client, *handle); +err_ion_handle: + ion_free(*client, *handle); +err_destroy_client: + msm_audio_ion_client_destroy(*client); + *client = NULL; + *handle = NULL; +err: + return rc; +} + +int msm_audio_ion_free(struct ion_client *client, struct ion_handle *handle) +{ + int ret = 0; + + if (!client || !handle) { + pr_err("%s Invalid params\n", __func__); + return -EINVAL; + } + + if (msm_audio_ion_data.smmu_enabled) { + ret = msm_audio_ion_smmu_unmap(client, handle); + if (ret) + pr_err("%s: smmu unmap failed with ret %d\n", + __func__, ret); + } + + ion_unmap_kernel(client, handle); + + ion_free(client, handle); + msm_audio_ion_client_destroy(client); + return ret; +} +EXPORT_SYMBOL(msm_audio_ion_free); + +int msm_audio_ion_mmap(struct audio_buffer *ab, + struct vm_area_struct *vma) +{ + struct sg_table *table; + unsigned long addr = vma->vm_start; + unsigned long offset = vma->vm_pgoff * PAGE_SIZE; + struct scatterlist *sg; + unsigned int i; + struct page *page; + int ret; + + pr_debug("%s\n", __func__); + + table = ion_sg_table(ab->client, ab->handle); + + if (IS_ERR(table)) { + pr_err("%s: Unable to get sg_table from ion: %ld\n", + __func__, PTR_ERR(table)); + return PTR_ERR(table); + } else if (!table) { + pr_err("%s: sg_list is NULL\n", __func__); + return -EINVAL; + } + + /* uncached */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + /* We need to check if a page is associated with this sg list because: + * If the allocation came from a carveout we currently don't have + * pages associated with carved out memory. This might change in the + * future and we can remove this check and the else statement. + */ + page = sg_page(table->sgl); + if (page) { + pr_debug("%s: page is NOT null\n", __func__); + for_each_sg(table->sgl, sg, table->nents, i) { + unsigned long remainder = vma->vm_end - addr; + unsigned long len = sg->length; + + page = sg_page(sg); + + if (offset >= len) { + offset -= len; + continue; + } else if (offset) { + page += offset / PAGE_SIZE; + len -= offset; + offset = 0; + } + len = min(len, remainder); + pr_debug("vma=%pK, addr=%x len=%ld vm_start=%x vm_end=%x vm_page_prot=%ld\n", + vma, (unsigned int)addr, len, + (unsigned int)vma->vm_start, + (unsigned int)vma->vm_end, + (unsigned long int)vma->vm_page_prot); + remap_pfn_range(vma, addr, page_to_pfn(page), len, + vma->vm_page_prot); + addr += len; + if (addr >= vma->vm_end) + return 0; + } + } else { + ion_phys_addr_t phys_addr; + size_t phys_len; + size_t va_len = 0; + + pr_debug("%s: page is NULL\n", __func__); + + ret = ion_phys(ab->client, ab->handle, &phys_addr, &phys_len); + if (ret) { + pr_err("%s: Unable to get phys address from ION buffer: %d\n", + __func__, ret); + return ret; + } + pr_debug("phys=%pK len=%zd\n", &phys_addr, phys_len); + pr_debug("vma=%pK, vm_start=%x vm_end=%x vm_pgoff=%ld vm_page_prot=%ld\n", + vma, (unsigned int)vma->vm_start, + (unsigned int)vma->vm_end, vma->vm_pgoff, + (unsigned long int)vma->vm_page_prot); + va_len = vma->vm_end - vma->vm_start; + if ((offset > phys_len) || (va_len > phys_len-offset)) { + pr_err("wrong offset size %ld, lens= %zd, va_len=%zd\n", + offset, phys_len, va_len); + return -EINVAL; + } + ret = remap_pfn_range(vma, vma->vm_start, + __phys_to_pfn(phys_addr) + vma->vm_pgoff, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); + } + return 0; +} + + +bool msm_audio_ion_is_smmu_available(void) +{ + return msm_audio_ion_data.smmu_enabled; +} + +/* move to static section again */ +struct ion_client *msm_audio_ion_client_create(const char *name) +{ + struct ion_client *pclient = NULL; + + pclient = hvenv_ion_client_create(name); + return pclient; +} + + +void msm_audio_ion_client_destroy(struct ion_client *client) +{ + pr_debug("%s: client = %pK smmu_enabled = %d\n", __func__, + client, msm_audio_ion_data.smmu_enabled); + + ion_client_destroy(client); +} + +int msm_audio_ion_import_legacy(const char *name, struct ion_client *client, + struct ion_handle **handle, int fd, + unsigned long *ionflag, size_t bufsz, + ion_phys_addr_t *paddr, size_t *pa_len, void **vaddr) +{ + int rc = 0; + + if (!name || !client || !handle || !paddr || !vaddr || !pa_len) { + pr_err("%s: Invalid params\n", __func__); + rc = -EINVAL; + goto err; + } + /* client is already created for legacy and given*/ + /* name should be audio_acdb_client or Audio_Dec_Client, + * bufsz should be 0 and fd shouldn't be 0 as of now + */ + *handle = ion_import_dma_buf(client, fd); + pr_debug("%s: DMA Buf name=%s, fd=%d handle=%pK\n", __func__, + name, fd, *handle); + if (IS_ERR_OR_NULL((void *)(*handle))) { + pr_err("%s: ion import dma buffer failed\n", + __func__); + rc = -EINVAL; + goto err; + } + + if (ionflag != NULL) { + rc = ion_handle_get_flags(client, *handle, ionflag); + if (rc) { + pr_err("%s: could not get flags for the handle\n", + __func__); + rc = -EINVAL; + goto err_ion_handle; + } + } + + /*Need to add condition SMMU enable or not */ + *vaddr = ion_map_kernel(client, *handle); + if (IS_ERR_OR_NULL((void *)*vaddr)) { + pr_err("%s: ION memory mapping for AUDIO failed\n", __func__); + rc = -EINVAL; + goto err_ion_handle; + } + + if (bufsz != 0) + memset((void *)*vaddr, 0, bufsz); + + rc = msm_audio_ion_get_phys(client, *handle, paddr, pa_len, *vaddr); + if (rc) { + pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n", + __func__, rc); + goto err_get_phys; + } + + return 0; + +err_get_phys: + ion_unmap_kernel(client, *handle); +err_ion_handle: + ion_free(client, *handle); +err: + return rc; +} + +int msm_audio_ion_free_legacy(struct ion_client *client, + struct ion_handle *handle) +{ + ion_unmap_kernel(client, handle); + + ion_free(client, handle); + /* no client_destrody in legacy*/ + return 0; +} + +static int msm_audio_ion_get_phys(struct ion_client *client, + struct ion_handle *handle, + ion_phys_addr_t *addr, size_t *len, void *vaddr) +{ + int rc = 0; + + pr_debug("%s: smmu_enabled = %d\n", __func__, + msm_audio_ion_data.smmu_enabled); + + if (msm_audio_ion_data.smmu_enabled) { + rc = msm_audio_ion_smmu_map(client, handle, addr, len, vaddr); + if (rc) { + pr_err("%s: failed to do smmu map, err = %d\n", + __func__, rc); + goto err; + } + } else { + rc = ion_phys(client, handle, addr, len); + } + + pr_debug("%s: phys=%pK, len=%zd, rc=%d\n", + __func__, &(*addr), *len, rc); +err: + return rc; +} + +static const struct of_device_id msm_audio_ion_dt_match[] = { + { .compatible = "qcom,msm-audio-ion-vm" }, + { } +}; +MODULE_DEVICE_TABLE(of, msm_audio_ion_dt_match); + +u32 msm_audio_populate_upper_32_bits(ion_phys_addr_t pa) +{ + return upper_32_bits(pa); +} + +static int msm_audio_ion_probe(struct platform_device *pdev) +{ + int rc = 0; + const char *msm_audio_ion_dt = "qcom,smmu-enabled"; + bool smmu_enabled; + struct device *dev = &pdev->dev; + + if (dev->of_node == NULL) { + pr_err("%s: device tree is not found\n", + __func__); + msm_audio_ion_data.smmu_enabled = 0; + return 0; + } + + smmu_enabled = of_property_read_bool(dev->of_node, + msm_audio_ion_dt); + msm_audio_ion_data.smmu_enabled = smmu_enabled; + + pr_info("%s: SMMU is %s\n", __func__, + (smmu_enabled) ? "Enabled" : "Disabled"); + + if (smmu_enabled) { + rc = habmm_socket_open(&msm_audio_ion_hab_handle, + HAB_MMID_CREATE(MM_AUD_3, + MSM_AUDIO_SMMU_VM_HAB_MINOR_ID), + 0xFFFFFFFF, + HABMM_SOCKET_OPEN_FLAGS_SINGLE_BE_SINGLE_FE); + if (rc) { + pr_err("%s: habmm_socket_open failed %d\n", + __func__, rc); + return rc; + } + + pr_info("%s: msm_audio_ion_hab_handle %x\n", + __func__, msm_audio_ion_hab_handle); + + INIT_LIST_HEAD(&msm_audio_ion_data.smmu_map_list); + mutex_init(&(msm_audio_ion_data.smmu_map_mutex)); + } + + if (!rc) + msm_audio_ion_data.device_status |= MSM_AUDIO_ION_PROBED; + + return rc; +} + +static int msm_audio_ion_remove(struct platform_device *pdev) +{ + if (msm_audio_ion_data.smmu_enabled) { + if (msm_audio_ion_hab_handle) + habmm_socket_close(msm_audio_ion_hab_handle); + + mutex_destroy(&(msm_audio_ion_data.smmu_map_mutex)); + } + msm_audio_ion_data.smmu_enabled = 0; + msm_audio_ion_data.device_status = 0; + + return 0; +} + +static struct platform_driver msm_audio_ion_driver = { + .driver = { + .name = "msm-audio-ion-vm", + .owner = THIS_MODULE, + .of_match_table = msm_audio_ion_dt_match, + }, + .probe = msm_audio_ion_probe, + .remove = msm_audio_ion_remove, +}; + +static int __init msm_audio_ion_init(void) +{ + return platform_driver_register(&msm_audio_ion_driver); +} +module_init(msm_audio_ion_init); + +static void __exit msm_audio_ion_exit(void) +{ + platform_driver_unregister(&msm_audio_ion_driver); +} +module_exit(msm_audio_ion_exit); + +MODULE_DESCRIPTION("MSM Audio ION VM module"); +MODULE_LICENSE("GPL v2"); |
