| /* Copyright (c) 2015-2018 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/mutex.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/debugfs.h> |
| #include <linux/ipa.h> |
| #include <linux/ipa_usb.h> |
| #include <linux/rndis_ipa.h> |
| #include <linux/ecm_ipa.h> |
| #include "../ipa_v3/ipa_i.h" |
| #include "../ipa_rm_i.h" |
| |
| #define IPA_USB_RM_TIMEOUT_MSEC 10000 |
| #define IPA_USB_DEV_READY_TIMEOUT_MSEC 10000 |
| |
| #define IPA_HOLB_TMR_EN 0x1 |
| |
| /* GSI channels weights */ |
| #define IPA_USB_DL_CHAN_LOW_WEIGHT 0x5 |
| #define IPA_USB_UL_CHAN_LOW_WEIGHT 0x4 |
| |
| #define IPA_USB_MAX_MSG_LEN 4096 |
| |
| #define IPA_USB_DRV_NAME "ipa_usb" |
| |
| #define IPA_USB_DBG(fmt, args...) \ |
| do { \ |
| pr_debug(IPA_USB_DRV_NAME " %s:%d " fmt, \ |
| __func__, __LINE__, ## args); \ |
| IPA_IPC_LOGGING(ipa_get_ipc_logbuf(), \ |
| IPA_USB_DRV_NAME " %s:%d " fmt, ## args); \ |
| IPA_IPC_LOGGING(ipa_get_ipc_logbuf_low(), \ |
| IPA_USB_DRV_NAME " %s:%d " fmt, ## args); \ |
| } while (0) |
| |
| #define IPA_USB_DBG_LOW(fmt, args...) \ |
| do { \ |
| pr_debug(IPA_USB_DRV_NAME " %s:%d " fmt, \ |
| __func__, __LINE__, ## args); \ |
| IPA_IPC_LOGGING(ipa_get_ipc_logbuf_low(), \ |
| IPA_USB_DRV_NAME " %s:%d " fmt, ## args); \ |
| } while (0) |
| |
| #define IPA_USB_ERR(fmt, args...) \ |
| do { \ |
| pr_err(IPA_USB_DRV_NAME " %s:%d " fmt, \ |
| __func__, __LINE__, ## args); \ |
| IPA_IPC_LOGGING(ipa_get_ipc_logbuf(), \ |
| IPA_USB_DRV_NAME " %s:%d " fmt, ## args); \ |
| IPA_IPC_LOGGING(ipa_get_ipc_logbuf_low(), \ |
| IPA_USB_DRV_NAME " %s:%d " fmt, ## args); \ |
| } while (0) |
| |
| #define IPA_USB_INFO(fmt, args...) \ |
| do { \ |
| pr_info(IPA_USB_DRV_NAME " %s:%d " fmt, \ |
| __func__, __LINE__, ## args); \ |
| IPA_IPC_LOGGING(ipa_get_ipc_logbuf(), \ |
| IPA_USB_DRV_NAME " %s:%d " fmt, ## args); \ |
| IPA_IPC_LOGGING(ipa_get_ipc_logbuf_low(), \ |
| IPA_USB_DRV_NAME " %s:%d " fmt, ## args); \ |
| } while (0) |
| |
| enum ipa_usb_direction { |
| IPA_USB_DIR_UL, |
| IPA_USB_DIR_DL, |
| }; |
| |
| struct ipa_usb_xdci_connect_params_internal { |
| enum ipa_usb_max_usb_packet_size max_pkt_size; |
| u32 ipa_to_usb_clnt_hdl; |
| u8 ipa_to_usb_xferrscidx; |
| bool ipa_to_usb_xferrscidx_valid; |
| u32 usb_to_ipa_clnt_hdl; |
| u8 usb_to_ipa_xferrscidx; |
| bool usb_to_ipa_xferrscidx_valid; |
| enum ipa_usb_teth_prot teth_prot; |
| struct ipa_usb_teth_prot_params teth_prot_params; |
| u32 max_supported_bandwidth_mbps; |
| }; |
| |
| enum ipa3_usb_teth_prot_state { |
| IPA_USB_TETH_PROT_INITIALIZED, |
| IPA_USB_TETH_PROT_CONNECTED, |
| IPA_USB_TETH_PROT_INVALID |
| }; |
| |
| struct ipa3_usb_teth_prot_context { |
| union { |
| struct ipa_usb_init_params rndis; |
| struct ecm_ipa_params ecm; |
| struct teth_bridge_init_params teth_bridge; |
| } teth_prot_params; |
| enum ipa3_usb_teth_prot_state state; |
| void *user_data; |
| }; |
| |
| enum ipa3_usb_cons_state { |
| IPA_USB_CONS_GRANTED, |
| IPA_USB_CONS_RELEASED |
| }; |
| |
| struct ipa3_usb_rm_context { |
| struct ipa_rm_create_params prod_params; |
| struct ipa_rm_create_params cons_params; |
| bool prod_valid; |
| bool cons_valid; |
| struct completion prod_comp; |
| enum ipa3_usb_cons_state cons_state; |
| /* consumer was requested*/ |
| bool cons_requested; |
| /* consumer was requested and released before it was granted*/ |
| bool cons_requested_released; |
| }; |
| |
| struct ipa3_usb_pm_context { |
| struct ipa_pm_register_params reg_params; |
| struct work_struct *remote_wakeup_work; |
| u32 hdl; |
| }; |
| |
| enum ipa3_usb_state { |
| IPA_USB_INVALID, |
| IPA_USB_INITIALIZED, |
| IPA_USB_CONNECTED, |
| IPA_USB_STOPPED, |
| IPA_USB_SUSPEND_REQUESTED, |
| IPA_USB_SUSPENDED, |
| IPA_USB_SUSPENDED_NO_RWAKEUP, |
| IPA_USB_RESUME_IN_PROGRESS |
| }; |
| |
| enum ipa3_usb_transport_type { |
| IPA_USB_TRANSPORT_TETH, |
| IPA_USB_TRANSPORT_DPL, |
| IPA_USB_TRANSPORT_MAX |
| }; |
| |
| /* Get transport type from tethering protocol */ |
| #define IPA3_USB_GET_TTYPE(__teth_prot) \ |
| (((__teth_prot) == IPA_USB_DIAG) ? \ |
| IPA_USB_TRANSPORT_DPL : IPA_USB_TRANSPORT_TETH) |
| |
| /* Does the given transport type is DPL? */ |
| #define IPA3_USB_IS_TTYPE_DPL(__ttype) \ |
| ((__ttype) == IPA_USB_TRANSPORT_DPL) |
| |
| struct ipa3_usb_teth_prot_conn_params { |
| u32 usb_to_ipa_clnt_hdl; |
| u32 ipa_to_usb_clnt_hdl; |
| struct ipa_usb_teth_prot_params params; |
| }; |
| |
| /** |
| * Transport type - could be either data tethering or DPL |
| * Each transport has it's own RM resources and statuses |
| */ |
| struct ipa3_usb_transport_type_ctx { |
| struct ipa3_usb_rm_context rm_ctx; |
| struct ipa3_usb_pm_context pm_ctx; |
| int (*ipa_usb_notify_cb)(enum ipa_usb_notify_event, void *user_data); |
| void *user_data; |
| enum ipa3_usb_state state; |
| struct ipa_usb_xdci_chan_params ul_ch_params; |
| struct ipa_usb_xdci_chan_params dl_ch_params; |
| struct ipa3_usb_teth_prot_conn_params teth_conn_params; |
| }; |
| |
| struct ipa3_usb_smmu_reg_map { |
| int cnt; |
| phys_addr_t addr; |
| }; |
| |
| struct ipa3_usb_context { |
| struct ipa3_usb_teth_prot_context |
| teth_prot_ctx[IPA_USB_MAX_TETH_PROT_SIZE]; |
| int num_init_prot; /* without dpl */ |
| struct teth_bridge_init_params teth_bridge_params; |
| struct completion dev_ready_comp; |
| u32 qmi_req_id; |
| spinlock_t state_lock; |
| bool dl_data_pending; |
| struct workqueue_struct *wq; |
| struct mutex general_mutex; |
| struct ipa3_usb_transport_type_ctx |
| ttype_ctx[IPA_USB_TRANSPORT_MAX]; |
| struct dentry *dfile_state_info; |
| struct dentry *dent; |
| struct ipa3_usb_smmu_reg_map smmu_reg_map; |
| }; |
| |
| enum ipa3_usb_op { |
| IPA_USB_OP_INIT_TETH_PROT, |
| IPA_USB_OP_REQUEST_CHANNEL, |
| IPA_USB_OP_CONNECT, |
| IPA_USB_OP_DISCONNECT, |
| IPA_USB_OP_RELEASE_CHANNEL, |
| IPA_USB_OP_DEINIT_TETH_PROT, |
| IPA_USB_OP_SUSPEND, |
| IPA_USB_OP_SUSPEND_NO_RWAKEUP, |
| IPA_USB_OP_RESUME |
| }; |
| |
| struct ipa3_usb_status_dbg_info { |
| const char *teth_state; |
| const char *dpl_state; |
| int num_init_prot; |
| const char *inited_prots[IPA_USB_MAX_TETH_PROT_SIZE]; |
| const char *teth_connected_prot; |
| const char *dpl_connected_prot; |
| const char *teth_cons_state; |
| const char *dpl_cons_state; |
| }; |
| |
| static void ipa3_usb_wq_notify_remote_wakeup(struct work_struct *work); |
| static void ipa3_usb_wq_dpl_notify_remote_wakeup(struct work_struct *work); |
| static DECLARE_WORK(ipa3_usb_notify_remote_wakeup_work, |
| ipa3_usb_wq_notify_remote_wakeup); |
| static DECLARE_WORK(ipa3_usb_dpl_notify_remote_wakeup_work, |
| ipa3_usb_wq_dpl_notify_remote_wakeup); |
| |
| struct ipa3_usb_context *ipa3_usb_ctx; |
| |
| static char *ipa3_usb_op_to_string(enum ipa3_usb_op op) |
| { |
| switch (op) { |
| case IPA_USB_OP_INIT_TETH_PROT: |
| return "IPA_USB_OP_INIT_TETH_PROT"; |
| case IPA_USB_OP_REQUEST_CHANNEL: |
| return "IPA_USB_OP_REQUEST_CHANNEL"; |
| case IPA_USB_OP_CONNECT: |
| return "IPA_USB_OP_CONNECT"; |
| case IPA_USB_OP_DISCONNECT: |
| return "IPA_USB_OP_DISCONNECT"; |
| case IPA_USB_OP_RELEASE_CHANNEL: |
| return "IPA_USB_OP_RELEASE_CHANNEL"; |
| case IPA_USB_OP_DEINIT_TETH_PROT: |
| return "IPA_USB_OP_DEINIT_TETH_PROT"; |
| case IPA_USB_OP_SUSPEND: |
| return "IPA_USB_OP_SUSPEND"; |
| case IPA_USB_OP_SUSPEND_NO_RWAKEUP: |
| return "IPA_USB_OP_SUSPEND_NO_RWAKEUP"; |
| case IPA_USB_OP_RESUME: |
| return "IPA_USB_OP_RESUME"; |
| } |
| |
| return "UNSUPPORTED"; |
| } |
| |
| static char *ipa3_usb_state_to_string(enum ipa3_usb_state state) |
| { |
| switch (state) { |
| case IPA_USB_INVALID: |
| return "IPA_USB_INVALID"; |
| case IPA_USB_INITIALIZED: |
| return "IPA_USB_INITIALIZED"; |
| case IPA_USB_CONNECTED: |
| return "IPA_USB_CONNECTED"; |
| case IPA_USB_STOPPED: |
| return "IPA_USB_STOPPED"; |
| case IPA_USB_SUSPEND_REQUESTED: |
| return "IPA_USB_SUSPEND_REQUESTED"; |
| case IPA_USB_SUSPENDED: |
| return "IPA_USB_SUSPENDED"; |
| case IPA_USB_SUSPENDED_NO_RWAKEUP: |
| return "IPA_USB_SUSPENDED_NO_RWAKEUP"; |
| case IPA_USB_RESUME_IN_PROGRESS: |
| return "IPA_USB_RESUME_IN_PROGRESS"; |
| } |
| |
| return "UNSUPPORTED"; |
| } |
| |
| static char *ipa3_usb_notify_event_to_string(enum ipa_usb_notify_event event) |
| { |
| switch (event) { |
| case IPA_USB_DEVICE_READY: |
| return "IPA_USB_DEVICE_READY"; |
| case IPA_USB_REMOTE_WAKEUP: |
| return "IPA_USB_REMOTE_WAKEUP"; |
| case IPA_USB_SUSPEND_COMPLETED: |
| return "IPA_USB_SUSPEND_COMPLETED"; |
| } |
| |
| return "UNSUPPORTED"; |
| } |
| |
| static bool ipa3_usb_set_state(enum ipa3_usb_state new_state, bool err_permit, |
| enum ipa3_usb_transport_type ttype) |
| { |
| unsigned long flags; |
| int state_legal = false; |
| enum ipa3_usb_state state; |
| struct ipa3_usb_rm_context *rm_ctx; |
| |
| spin_lock_irqsave(&ipa3_usb_ctx->state_lock, flags); |
| state = ipa3_usb_ctx->ttype_ctx[ttype].state; |
| switch (new_state) { |
| case IPA_USB_INVALID: |
| if (state == IPA_USB_INITIALIZED) |
| state_legal = true; |
| break; |
| case IPA_USB_INITIALIZED: |
| if (state == IPA_USB_STOPPED || state == IPA_USB_INVALID || |
| ((!IPA3_USB_IS_TTYPE_DPL(ttype)) && |
| (state == IPA_USB_INITIALIZED))) |
| state_legal = true; |
| break; |
| case IPA_USB_CONNECTED: |
| if (state == IPA_USB_INITIALIZED || |
| state == IPA_USB_STOPPED || |
| state == IPA_USB_RESUME_IN_PROGRESS || |
| state == IPA_USB_SUSPENDED_NO_RWAKEUP || |
| /* |
| * In case of failure during suspend request |
| * handling, state is reverted to connected. |
| */ |
| (err_permit && state == IPA_USB_SUSPEND_REQUESTED)) |
| state_legal = true; |
| break; |
| case IPA_USB_STOPPED: |
| if (state == IPA_USB_CONNECTED || |
| state == IPA_USB_SUSPENDED || |
| state == IPA_USB_SUSPENDED_NO_RWAKEUP) |
| state_legal = true; |
| break; |
| case IPA_USB_SUSPEND_REQUESTED: |
| if (state == IPA_USB_CONNECTED) |
| state_legal = true; |
| break; |
| case IPA_USB_SUSPENDED: |
| if (state == IPA_USB_SUSPEND_REQUESTED || |
| /* |
| * In case of failure during resume, state is reverted |
| * to original, which could be suspended. Allow it |
| */ |
| (err_permit && state == IPA_USB_RESUME_IN_PROGRESS)) |
| state_legal = true; |
| break; |
| case IPA_USB_SUSPENDED_NO_RWAKEUP: |
| if (state == IPA_USB_CONNECTED) |
| state_legal = true; |
| break; |
| case IPA_USB_RESUME_IN_PROGRESS: |
| if (state == IPA_USB_SUSPENDED) |
| state_legal = true; |
| break; |
| default: |
| state_legal = false; |
| break; |
| |
| } |
| if (state_legal) { |
| if (state != new_state) { |
| IPA_USB_DBG("ipa_usb %s state changed %s -> %s\n", |
| IPA3_USB_IS_TTYPE_DPL(ttype) ? "DPL" : "", |
| ipa3_usb_state_to_string(state), |
| ipa3_usb_state_to_string(new_state)); |
| ipa3_usb_ctx->ttype_ctx[ttype].state = new_state; |
| } |
| } else { |
| IPA_USB_ERR("invalid state change %s -> %s\n", |
| ipa3_usb_state_to_string(state), |
| ipa3_usb_state_to_string(new_state)); |
| } |
| |
| if (!ipa_pm_is_used() && |
| state_legal && (new_state == IPA_USB_CONNECTED)) { |
| rm_ctx = &ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx; |
| if ((rm_ctx->cons_state == IPA_USB_CONS_GRANTED) || |
| rm_ctx->cons_requested_released) { |
| rm_ctx->cons_requested = false; |
| rm_ctx->cons_requested_released = |
| false; |
| } |
| /* Notify RM that consumer is granted */ |
| if (rm_ctx->cons_requested) { |
| ipa_rm_notify_completion( |
| IPA_RM_RESOURCE_GRANTED, |
| rm_ctx->cons_params.name); |
| rm_ctx->cons_state = IPA_USB_CONS_GRANTED; |
| rm_ctx->cons_requested = false; |
| } |
| } |
| |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, flags); |
| return state_legal; |
| } |
| |
| static bool ipa3_usb_check_legal_op(enum ipa3_usb_op op, |
| enum ipa3_usb_transport_type ttype) |
| { |
| unsigned long flags; |
| bool is_legal = false; |
| enum ipa3_usb_state state; |
| bool is_dpl; |
| |
| if (ipa3_usb_ctx == NULL) { |
| IPA_USB_ERR("ipa_usb_ctx is not initialized!\n"); |
| return false; |
| } |
| |
| is_dpl = IPA3_USB_IS_TTYPE_DPL(ttype); |
| |
| spin_lock_irqsave(&ipa3_usb_ctx->state_lock, flags); |
| state = ipa3_usb_ctx->ttype_ctx[ttype].state; |
| switch (op) { |
| case IPA_USB_OP_INIT_TETH_PROT: |
| if (state == IPA_USB_INVALID || |
| (!is_dpl && state == IPA_USB_INITIALIZED)) |
| is_legal = true; |
| break; |
| case IPA_USB_OP_REQUEST_CHANNEL: |
| if (state == IPA_USB_INITIALIZED) |
| is_legal = true; |
| break; |
| case IPA_USB_OP_CONNECT: |
| if (state == IPA_USB_INITIALIZED || state == IPA_USB_STOPPED) |
| is_legal = true; |
| break; |
| case IPA_USB_OP_DISCONNECT: |
| if (state == IPA_USB_CONNECTED || |
| state == IPA_USB_SUSPENDED || |
| state == IPA_USB_SUSPENDED_NO_RWAKEUP) |
| is_legal = true; |
| break; |
| case IPA_USB_OP_RELEASE_CHANNEL: |
| /* when releasing 1st channel state will be changed already */ |
| if (state == IPA_USB_STOPPED || |
| (!is_dpl && state == IPA_USB_INITIALIZED)) |
| is_legal = true; |
| break; |
| case IPA_USB_OP_DEINIT_TETH_PROT: |
| /* |
| * For data tethering we should allow deinit an inited protocol |
| * always. E.g. rmnet is inited and rndis is connected. |
| * USB can deinit rmnet first and then disconnect rndis |
| * on cable disconnect. |
| */ |
| if (!is_dpl || state == IPA_USB_INITIALIZED) |
| is_legal = true; |
| break; |
| case IPA_USB_OP_SUSPEND: |
| if (state == IPA_USB_CONNECTED) |
| is_legal = true; |
| break; |
| case IPA_USB_OP_SUSPEND_NO_RWAKEUP: |
| if (state == IPA_USB_CONNECTED) |
| is_legal = true; |
| break; |
| case IPA_USB_OP_RESUME: |
| if (state == IPA_USB_SUSPENDED || |
| state == IPA_USB_SUSPENDED_NO_RWAKEUP) |
| is_legal = true; |
| break; |
| default: |
| is_legal = false; |
| break; |
| } |
| |
| if (!is_legal) { |
| IPA_USB_ERR("Illegal %s operation: state=%s operation=%s\n", |
| is_dpl ? "DPL" : "", |
| ipa3_usb_state_to_string(state), |
| ipa3_usb_op_to_string(op)); |
| } |
| |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, flags); |
| return is_legal; |
| } |
| |
| static void ipa3_usb_notify_do(enum ipa3_usb_transport_type ttype, |
| enum ipa_usb_notify_event event) |
| { |
| int (*cb)(enum ipa_usb_notify_event, void *user_data); |
| void *user_data; |
| int res; |
| |
| IPA_USB_DBG("Trying to notify USB with %s\n", |
| ipa3_usb_notify_event_to_string(event)); |
| |
| cb = ipa3_usb_ctx->ttype_ctx[ttype].ipa_usb_notify_cb; |
| user_data = ipa3_usb_ctx->ttype_ctx[ttype].user_data; |
| |
| if (cb) { |
| res = cb(event, user_data); |
| IPA_USB_DBG("Notified USB with %s. is_dpl=%d result=%d\n", |
| ipa3_usb_notify_event_to_string(event), |
| IPA3_USB_IS_TTYPE_DPL(ttype), res); |
| } |
| } |
| |
| /* |
| * This call-back is called from ECM or RNDIS drivers. |
| * Both drivers are data tethering drivers and not DPL |
| */ |
| void ipa3_usb_device_ready_notify_cb(void) |
| { |
| IPA_USB_DBG_LOW("entry\n"); |
| ipa3_usb_notify_do(IPA_USB_TRANSPORT_TETH, |
| IPA_USB_DEVICE_READY); |
| IPA_USB_DBG_LOW("exit\n"); |
| } |
| |
| static void ipa3_usb_prod_notify_cb_do(enum ipa_rm_event event, |
| enum ipa3_usb_transport_type ttype) |
| { |
| struct ipa3_usb_rm_context *rm_ctx; |
| |
| IPA_USB_DBG_LOW("entry\n"); |
| |
| rm_ctx = &ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx; |
| |
| switch (event) { |
| case IPA_RM_RESOURCE_GRANTED: |
| IPA_USB_DBG(":%s granted\n", |
| ipa_rm_resource_str(rm_ctx->prod_params.name)); |
| complete_all(&rm_ctx->prod_comp); |
| break; |
| case IPA_RM_RESOURCE_RELEASED: |
| IPA_USB_DBG(":%s released\n", |
| ipa_rm_resource_str(rm_ctx->prod_params.name)); |
| complete_all(&rm_ctx->prod_comp); |
| break; |
| } |
| IPA_USB_DBG_LOW("exit\n"); |
| } |
| |
| static void ipa3_usb_prod_notify_cb(void *user_data, enum ipa_rm_event event, |
| unsigned long data) |
| { |
| ipa3_usb_prod_notify_cb_do(event, IPA_USB_TRANSPORT_TETH); |
| } |
| |
| static void ipa3_usb_dpl_dummy_prod_notify_cb(void *user_data, |
| enum ipa_rm_event event, unsigned long data) |
| { |
| ipa3_usb_prod_notify_cb_do(event, IPA_USB_TRANSPORT_TETH); |
| } |
| |
| static void ipa3_usb_wq_notify_remote_wakeup(struct work_struct *work) |
| { |
| ipa3_usb_notify_do(IPA_USB_TRANSPORT_TETH, IPA_USB_REMOTE_WAKEUP); |
| } |
| |
| static void ipa3_usb_wq_dpl_notify_remote_wakeup(struct work_struct *work) |
| { |
| ipa3_usb_notify_do(IPA_USB_TRANSPORT_DPL, IPA_USB_REMOTE_WAKEUP); |
| } |
| |
| static int ipa3_usb_cons_request_resource_cb_do( |
| enum ipa3_usb_transport_type ttype, |
| struct work_struct *remote_wakeup_work) |
| { |
| struct ipa3_usb_rm_context *rm_ctx; |
| unsigned long flags; |
| int result; |
| |
| IPA_USB_DBG_LOW("entry\n"); |
| rm_ctx = &ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx; |
| spin_lock_irqsave(&ipa3_usb_ctx->state_lock, flags); |
| IPA_USB_DBG("state is %s\n", |
| ipa3_usb_state_to_string( |
| ipa3_usb_ctx->ttype_ctx[ttype].state)); |
| switch (ipa3_usb_ctx->ttype_ctx[ttype].state) { |
| case IPA_USB_CONNECTED: |
| case IPA_USB_SUSPENDED_NO_RWAKEUP: |
| rm_ctx->cons_state = IPA_USB_CONS_GRANTED; |
| result = 0; |
| break; |
| case IPA_USB_SUSPEND_REQUESTED: |
| rm_ctx->cons_requested = true; |
| if (rm_ctx->cons_state == IPA_USB_CONS_GRANTED) |
| result = 0; |
| else |
| result = -EINPROGRESS; |
| break; |
| case IPA_USB_SUSPENDED: |
| if (!rm_ctx->cons_requested) { |
| rm_ctx->cons_requested = true; |
| queue_work(ipa3_usb_ctx->wq, remote_wakeup_work); |
| } |
| result = -EINPROGRESS; |
| break; |
| default: |
| rm_ctx->cons_requested = true; |
| result = -EINPROGRESS; |
| break; |
| } |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, flags); |
| IPA_USB_DBG_LOW("exit with %d\n", result); |
| return result; |
| } |
| |
| static int ipa3_usb_cons_request_resource_cb(void) |
| { |
| return ipa3_usb_cons_request_resource_cb_do(IPA_USB_TRANSPORT_TETH, |
| &ipa3_usb_notify_remote_wakeup_work); |
| } |
| |
| static int ipa3_usb_dpl_cons_request_resource_cb(void) |
| { |
| return ipa3_usb_cons_request_resource_cb_do(IPA_USB_TRANSPORT_DPL, |
| &ipa3_usb_dpl_notify_remote_wakeup_work); |
| } |
| |
| static int ipa3_usb_cons_release_resource_cb_do( |
| enum ipa3_usb_transport_type ttype) |
| { |
| unsigned long flags; |
| struct ipa3_usb_rm_context *rm_ctx; |
| |
| IPA_USB_DBG_LOW("entry\n"); |
| rm_ctx = &ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx; |
| spin_lock_irqsave(&ipa3_usb_ctx->state_lock, flags); |
| IPA_USB_DBG("state is %s\n", |
| ipa3_usb_state_to_string( |
| ipa3_usb_ctx->ttype_ctx[ttype].state)); |
| switch (ipa3_usb_ctx->ttype_ctx[ttype].state) { |
| case IPA_USB_SUSPENDED: |
| /* Proceed with the suspend if no DL/DPL data */ |
| if (rm_ctx->cons_requested) |
| rm_ctx->cons_requested_released = true; |
| break; |
| case IPA_USB_SUSPEND_REQUESTED: |
| if (rm_ctx->cons_requested) |
| rm_ctx->cons_requested_released = true; |
| break; |
| case IPA_USB_STOPPED: |
| case IPA_USB_RESUME_IN_PROGRESS: |
| case IPA_USB_SUSPENDED_NO_RWAKEUP: |
| if (rm_ctx->cons_requested) |
| rm_ctx->cons_requested = false; |
| break; |
| case IPA_USB_CONNECTED: |
| case IPA_USB_INITIALIZED: |
| break; |
| default: |
| IPA_USB_ERR("received cons_release_cb in bad state: %s!\n", |
| ipa3_usb_state_to_string( |
| ipa3_usb_ctx->ttype_ctx[ttype].state)); |
| WARN_ON(1); |
| break; |
| } |
| |
| rm_ctx->cons_state = IPA_USB_CONS_RELEASED; |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, flags); |
| IPA_USB_DBG_LOW("exit\n"); |
| return 0; |
| } |
| |
| static int ipa3_usb_cons_release_resource_cb(void) |
| { |
| return ipa3_usb_cons_release_resource_cb_do(IPA_USB_TRANSPORT_TETH); |
| } |
| |
| static int ipa3_usb_dpl_cons_release_resource_cb(void) |
| { |
| return ipa3_usb_cons_release_resource_cb_do(IPA_USB_TRANSPORT_DPL); |
| } |
| |
| static void ipa3_usb_pm_cb(void *p, enum ipa_pm_cb_event event) |
| { |
| struct ipa3_usb_transport_type_ctx *ttype_ctx = |
| (struct ipa3_usb_transport_type_ctx *)p; |
| unsigned long flags; |
| |
| IPA_USB_DBG_LOW("entry\n"); |
| |
| if (event != IPA_PM_REQUEST_WAKEUP) { |
| IPA_USB_ERR("Unexpected event %d\n", event); |
| WARN_ON(1); |
| return; |
| } |
| |
| spin_lock_irqsave(&ipa3_usb_ctx->state_lock, flags); |
| IPA_USB_DBG("state is %s\n", |
| ipa3_usb_state_to_string(ttype_ctx->state)); |
| if (ttype_ctx->state == IPA_USB_SUSPENDED) |
| queue_work(ipa3_usb_ctx->wq, |
| ttype_ctx->pm_ctx.remote_wakeup_work); |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, flags); |
| IPA_USB_DBG_LOW("exit\n"); |
| } |
| |
| static char *ipa3_usb_teth_prot_to_string(enum ipa_usb_teth_prot teth_prot) |
| { |
| switch (teth_prot) { |
| case IPA_USB_RNDIS: |
| return "rndis_ipa"; |
| case IPA_USB_ECM: |
| return "ecm_ipa"; |
| case IPA_USB_RMNET: |
| case IPA_USB_MBIM: |
| return "teth_bridge"; |
| case IPA_USB_DIAG: |
| return "dpl"; |
| default: |
| break; |
| } |
| |
| return "unsupported"; |
| } |
| |
| static char *ipa3_usb_teth_bridge_prot_to_string( |
| enum ipa_usb_teth_prot teth_prot) |
| { |
| switch (teth_prot) { |
| case IPA_USB_RMNET: |
| return "rmnet"; |
| case IPA_USB_MBIM: |
| return "mbim"; |
| default: |
| break; |
| } |
| |
| return "unsupported"; |
| } |
| |
| static int ipa3_usb_init_teth_bridge(void) |
| { |
| int result; |
| |
| result = teth_bridge_init(&ipa3_usb_ctx->teth_bridge_params); |
| if (result) { |
| IPA_USB_ERR("Failed to initialize teth_bridge.\n"); |
| return result; |
| } |
| |
| return 0; |
| } |
| |
| static int ipa3_usb_register_pm(enum ipa3_usb_transport_type ttype) |
| { |
| struct ipa3_usb_transport_type_ctx *ttype_ctx = |
| &ipa3_usb_ctx->ttype_ctx[ttype]; |
| int result; |
| |
| /* there is one PM resource for teth and one for DPL */ |
| if (!IPA3_USB_IS_TTYPE_DPL(ttype) && ipa3_usb_ctx->num_init_prot > 0) |
| return 0; |
| |
| memset(&ttype_ctx->pm_ctx.reg_params, 0, |
| sizeof(ttype_ctx->pm_ctx.reg_params)); |
| ttype_ctx->pm_ctx.reg_params.name = (ttype == IPA_USB_TRANSPORT_DPL) ? |
| "USB DPL" : "USB"; |
| ttype_ctx->pm_ctx.reg_params.callback = ipa3_usb_pm_cb; |
| ttype_ctx->pm_ctx.reg_params.user_data = ttype_ctx; |
| ttype_ctx->pm_ctx.reg_params.group = IPA_PM_GROUP_DEFAULT; |
| |
| result = ipa_pm_register(&ttype_ctx->pm_ctx.reg_params, |
| &ttype_ctx->pm_ctx.hdl); |
| if (result) { |
| IPA_USB_ERR("fail to register with PM %d\n", result); |
| goto fail_pm_reg; |
| } |
| |
| result = ipa_pm_associate_ipa_cons_to_client(ttype_ctx->pm_ctx.hdl, |
| (ttype == IPA_USB_TRANSPORT_DPL) ? |
| IPA_CLIENT_USB_DPL_CONS : IPA_CLIENT_USB_CONS); |
| if (result) { |
| IPA_USB_ERR("fail to associate cons with PM %d\n", result); |
| goto fail_pm_cons; |
| } |
| |
| return 0; |
| |
| fail_pm_cons: |
| ipa_pm_deregister(ttype_ctx->pm_ctx.hdl); |
| fail_pm_reg: |
| memset(&ttype_ctx->pm_ctx.reg_params, 0, |
| sizeof(ttype_ctx->pm_ctx.reg_params)); |
| return result; |
| } |
| |
| static int ipa3_usb_deregister_pm(enum ipa3_usb_transport_type ttype) |
| { |
| struct ipa3_usb_pm_context *pm_ctx = |
| &ipa3_usb_ctx->ttype_ctx[ttype].pm_ctx; |
| int result; |
| |
| result = ipa_pm_deregister(pm_ctx->hdl); |
| if (result) |
| return result; |
| |
| memset(&pm_ctx->reg_params, 0, sizeof(pm_ctx->reg_params)); |
| return 0; |
| } |
| |
| static int ipa3_usb_create_rm_resources(enum ipa3_usb_transport_type ttype) |
| { |
| struct ipa3_usb_rm_context *rm_ctx; |
| int result = -EFAULT; |
| bool created = false; |
| |
| rm_ctx = &ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx; |
| |
| /* create PROD */ |
| if (!rm_ctx->prod_valid) { |
| rm_ctx->prod_params.name = IPA3_USB_IS_TTYPE_DPL(ttype) ? |
| IPA_RM_RESOURCE_USB_DPL_DUMMY_PROD : |
| IPA_RM_RESOURCE_USB_PROD; |
| rm_ctx->prod_params.floor_voltage = IPA_VOLTAGE_SVS2; |
| rm_ctx->prod_params.reg_params.user_data = NULL; |
| rm_ctx->prod_params.reg_params.notify_cb = |
| IPA3_USB_IS_TTYPE_DPL(ttype) ? |
| ipa3_usb_dpl_dummy_prod_notify_cb : |
| ipa3_usb_prod_notify_cb; |
| rm_ctx->prod_params.request_resource = NULL; |
| rm_ctx->prod_params.release_resource = NULL; |
| result = ipa_rm_create_resource(&rm_ctx->prod_params); |
| if (result) { |
| IPA_USB_ERR("Failed to create %s RM resource\n", |
| ipa_rm_resource_str(rm_ctx->prod_params.name)); |
| return result; |
| } |
| rm_ctx->prod_valid = true; |
| created = true; |
| IPA_USB_DBG("Created %s RM resource\n", |
| ipa_rm_resource_str(rm_ctx->prod_params.name)); |
| } |
| |
| /* Create CONS */ |
| if (!rm_ctx->cons_valid) { |
| rm_ctx->cons_params.name = IPA3_USB_IS_TTYPE_DPL(ttype) ? |
| IPA_RM_RESOURCE_USB_DPL_CONS : |
| IPA_RM_RESOURCE_USB_CONS; |
| rm_ctx->cons_params.floor_voltage = IPA_VOLTAGE_SVS2; |
| rm_ctx->cons_params.reg_params.user_data = NULL; |
| rm_ctx->cons_params.reg_params.notify_cb = NULL; |
| rm_ctx->cons_params.request_resource = |
| IPA3_USB_IS_TTYPE_DPL(ttype) ? |
| ipa3_usb_dpl_cons_request_resource_cb : |
| ipa3_usb_cons_request_resource_cb; |
| rm_ctx->cons_params.release_resource = |
| IPA3_USB_IS_TTYPE_DPL(ttype) ? |
| ipa3_usb_dpl_cons_release_resource_cb : |
| ipa3_usb_cons_release_resource_cb; |
| result = ipa_rm_create_resource(&rm_ctx->cons_params); |
| if (result) { |
| IPA_USB_ERR("Failed to create %s RM resource\n", |
| ipa_rm_resource_str(rm_ctx->cons_params.name)); |
| goto create_cons_rsc_fail; |
| } |
| rm_ctx->cons_valid = true; |
| IPA_USB_DBG("Created %s RM resource\n", |
| ipa_rm_resource_str(rm_ctx->cons_params.name)); |
| } |
| |
| return 0; |
| |
| create_cons_rsc_fail: |
| if (created) { |
| rm_ctx->prod_valid = false; |
| ipa_rm_delete_resource(rm_ctx->prod_params.name); |
| } |
| return result; |
| } |
| |
| int ipa_usb_init_teth_prot(enum ipa_usb_teth_prot teth_prot, |
| struct ipa_usb_teth_params *teth_params, |
| int (*ipa_usb_notify_cb)(enum ipa_usb_notify_event, |
| void *), |
| void *user_data) |
| { |
| int result = -EFAULT; |
| enum ipa3_usb_transport_type ttype; |
| |
| mutex_lock(&ipa3_usb_ctx->general_mutex); |
| IPA_USB_DBG_LOW("entry\n"); |
| if (teth_prot < 0 || teth_prot >= IPA_USB_MAX_TETH_PROT_SIZE || |
| ((teth_prot == IPA_USB_RNDIS || teth_prot == IPA_USB_ECM) && |
| teth_params == NULL) || ipa_usb_notify_cb == NULL || |
| user_data == NULL) { |
| IPA_USB_ERR("bad parameters.\n"); |
| result = -EINVAL; |
| goto bad_params; |
| } |
| |
| ttype = IPA3_USB_GET_TTYPE(teth_prot); |
| |
| if (!ipa3_usb_check_legal_op(IPA_USB_OP_INIT_TETH_PROT, ttype)) { |
| IPA_USB_ERR("Illegal operation.\n"); |
| result = -EPERM; |
| goto bad_params; |
| } |
| |
| /* Create IPA RM USB resources */ |
| if (ipa_pm_is_used()) |
| result = ipa3_usb_register_pm(ttype); |
| else |
| result = ipa3_usb_create_rm_resources(ttype); |
| if (result) { |
| IPA_USB_ERR("Failed creating IPA RM USB resources\n"); |
| goto bad_params; |
| } |
| |
| if (!ipa3_usb_ctx->ttype_ctx[ttype].ipa_usb_notify_cb) { |
| ipa3_usb_ctx->ttype_ctx[ttype].ipa_usb_notify_cb = |
| ipa_usb_notify_cb; |
| } else if (!IPA3_USB_IS_TTYPE_DPL(ttype)) { |
| if (ipa3_usb_ctx->ttype_ctx[ttype].ipa_usb_notify_cb != |
| ipa_usb_notify_cb) { |
| IPA_USB_ERR("Got different notify_cb\n"); |
| result = -EINVAL; |
| goto bad_params; |
| } |
| } else { |
| IPA_USB_ERR("Already has dpl_notify_cb\n"); |
| result = -EINVAL; |
| goto bad_params; |
| } |
| |
| /* Initialize tethering protocol */ |
| switch (teth_prot) { |
| case IPA_USB_RNDIS: |
| case IPA_USB_ECM: |
| if (ipa3_usb_ctx->teth_prot_ctx[teth_prot].state != |
| IPA_USB_TETH_PROT_INVALID) { |
| IPA_USB_DBG("%s already initialized\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| result = -EPERM; |
| goto bad_params; |
| } |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].user_data = user_data; |
| if (teth_prot == IPA_USB_RNDIS) { |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot]. |
| teth_prot_params.rndis.device_ready_notify = |
| ipa3_usb_device_ready_notify_cb; |
| memcpy(ipa3_usb_ctx->teth_prot_ctx[teth_prot]. |
| teth_prot_params.rndis.host_ethaddr, |
| teth_params->host_ethaddr, |
| sizeof(teth_params->host_ethaddr)); |
| memcpy(ipa3_usb_ctx->teth_prot_ctx[teth_prot]. |
| teth_prot_params.rndis.device_ethaddr, |
| teth_params->device_ethaddr, |
| sizeof(teth_params->device_ethaddr)); |
| |
| result = rndis_ipa_init(&ipa3_usb_ctx-> |
| teth_prot_ctx[teth_prot]. |
| teth_prot_params.rndis); |
| if (result) { |
| IPA_USB_ERR("Failed to initialize %s\n", |
| ipa3_usb_teth_prot_to_string( |
| teth_prot)); |
| goto teth_prot_init_fail; |
| } |
| } else { |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot]. |
| teth_prot_params.ecm.device_ready_notify = |
| ipa3_usb_device_ready_notify_cb; |
| memcpy(ipa3_usb_ctx->teth_prot_ctx[teth_prot]. |
| teth_prot_params.ecm.host_ethaddr, |
| teth_params->host_ethaddr, |
| sizeof(teth_params->host_ethaddr)); |
| memcpy(ipa3_usb_ctx->teth_prot_ctx[teth_prot]. |
| teth_prot_params.ecm.device_ethaddr, |
| teth_params->device_ethaddr, |
| sizeof(teth_params->device_ethaddr)); |
| |
| result = ecm_ipa_init(&ipa3_usb_ctx-> |
| teth_prot_ctx[teth_prot].teth_prot_params.ecm); |
| if (result) { |
| IPA_USB_ERR("Failed to initialize %s\n", |
| ipa3_usb_teth_prot_to_string( |
| teth_prot)); |
| goto teth_prot_init_fail; |
| } |
| } |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].state = |
| IPA_USB_TETH_PROT_INITIALIZED; |
| ipa3_usb_ctx->num_init_prot++; |
| IPA_USB_DBG("initialized %s\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| break; |
| case IPA_USB_RMNET: |
| case IPA_USB_MBIM: |
| if (ipa3_usb_ctx->teth_prot_ctx[teth_prot].state != |
| IPA_USB_TETH_PROT_INVALID) { |
| IPA_USB_DBG("%s already initialized\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| result = -EPERM; |
| goto bad_params; |
| } |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].user_data = user_data; |
| result = ipa3_usb_init_teth_bridge(); |
| if (result) |
| goto teth_prot_init_fail; |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].state = |
| IPA_USB_TETH_PROT_INITIALIZED; |
| ipa3_usb_ctx->num_init_prot++; |
| IPA_USB_DBG("initialized %s %s\n", |
| ipa3_usb_teth_prot_to_string(teth_prot), |
| ipa3_usb_teth_bridge_prot_to_string(teth_prot)); |
| break; |
| case IPA_USB_DIAG: |
| if (ipa3_usb_ctx->teth_prot_ctx[teth_prot].state != |
| IPA_USB_TETH_PROT_INVALID) { |
| IPA_USB_DBG("DPL already initialized\n"); |
| result = -EPERM; |
| goto bad_params; |
| } |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].user_data = user_data; |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].state = |
| IPA_USB_TETH_PROT_INITIALIZED; |
| IPA_USB_DBG("initialized DPL\n"); |
| break; |
| default: |
| IPA_USB_ERR("unexpected tethering protocol\n"); |
| result = -EINVAL; |
| goto bad_params; |
| } |
| |
| if (!ipa3_usb_set_state(IPA_USB_INITIALIZED, false, ttype)) |
| IPA_USB_ERR("failed to change state to initialized\n"); |
| |
| IPA_USB_DBG_LOW("exit\n"); |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return 0; |
| |
| teth_prot_init_fail: |
| if ((IPA3_USB_IS_TTYPE_DPL(ttype)) |
| || (ipa3_usb_ctx->num_init_prot == 0)) { |
| if (ipa_pm_is_used()) { |
| ipa3_usb_deregister_pm(ttype); |
| } else { |
| ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx.prod_valid = |
| false; |
| ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx.cons_valid = |
| false; |
| ipa_rm_delete_resource( |
| ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx.prod_params.name); |
| ipa_rm_delete_resource( |
| ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx.cons_params.name); |
| } |
| } |
| bad_params: |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return result; |
| } |
| EXPORT_SYMBOL(ipa_usb_init_teth_prot); |
| |
| void ipa3_usb_gsi_evt_err_cb(struct gsi_evt_err_notify *notify) |
| { |
| IPA_USB_DBG_LOW("entry\n"); |
| if (!notify) |
| return; |
| IPA_USB_ERR("Received event error %d, description: %d\n", |
| notify->evt_id, notify->err_desc); |
| IPA_USB_DBG_LOW("exit\n"); |
| } |
| |
| void ipa3_usb_gsi_chan_err_cb(struct gsi_chan_err_notify *notify) |
| { |
| IPA_USB_DBG_LOW("entry\n"); |
| if (!notify) |
| return; |
| IPA_USB_ERR("Received channel error %d, description: %d\n", |
| notify->evt_id, notify->err_desc); |
| IPA_USB_DBG_LOW("exit\n"); |
| } |
| |
| static bool ipa3_usb_check_chan_params(struct ipa_usb_xdci_chan_params *params) |
| { |
| IPA_USB_DBG_LOW("gevntcount_low_addr = %x\n", |
| params->gevntcount_low_addr); |
| IPA_USB_DBG_LOW("gevntcount_hi_addr = %x\n", |
| params->gevntcount_hi_addr); |
| IPA_USB_DBG_LOW("dir = %d\n", params->dir); |
| IPA_USB_DBG_LOW("xfer_ring_len = %d\n", params->xfer_ring_len); |
| IPA_USB_DBG_LOW("last_trb_addr_iova = %x\n", |
| params->xfer_scratch.last_trb_addr_iova); |
| IPA_USB_DBG_LOW("const_buffer_size = %d\n", |
| params->xfer_scratch.const_buffer_size); |
| IPA_USB_DBG_LOW("depcmd_low_addr = %x\n", |
| params->xfer_scratch.depcmd_low_addr); |
| IPA_USB_DBG_LOW("depcmd_hi_addr = %x\n", |
| params->xfer_scratch.depcmd_hi_addr); |
| |
| if (params->client >= IPA_CLIENT_MAX || |
| params->teth_prot < 0 || |
| params->teth_prot >= IPA_USB_MAX_TETH_PROT_SIZE || |
| params->xfer_ring_len % GSI_CHAN_RE_SIZE_16B || |
| params->xfer_scratch.const_buffer_size < 1 || |
| params->xfer_scratch.const_buffer_size > 31) { |
| IPA_USB_ERR("Invalid params\n"); |
| return false; |
| } |
| switch (params->teth_prot) { |
| case IPA_USB_DIAG: |
| if (!IPA_CLIENT_IS_CONS(params->client)) { |
| IPA_USB_ERR("DPL supports only DL channel\n"); |
| return false; |
| } |
| case IPA_USB_RNDIS: |
| case IPA_USB_ECM: |
| if (ipa3_usb_ctx->teth_prot_ctx[params->teth_prot].state == |
| IPA_USB_TETH_PROT_INVALID) { |
| IPA_USB_ERR("%s is not initialized\n", |
| ipa3_usb_teth_prot_to_string( |
| params->teth_prot)); |
| return false; |
| } |
| break; |
| case IPA_USB_RMNET: |
| case IPA_USB_MBIM: |
| if (ipa3_usb_ctx->teth_prot_ctx[params->teth_prot].state == |
| IPA_USB_TETH_PROT_INVALID) { |
| IPA_USB_ERR("%s is not initialized\n", |
| ipa3_usb_teth_bridge_prot_to_string( |
| params->teth_prot)); |
| return false; |
| } |
| break; |
| default: |
| IPA_USB_ERR("Unknown tethering protocol (%d)\n", |
| params->teth_prot); |
| return false; |
| } |
| return true; |
| } |
| |
| static int ipa3_usb_smmu_map_xdci_channel( |
| struct ipa_usb_xdci_chan_params *params, bool map) |
| { |
| int result; |
| u32 gevntcount_r = rounddown(params->gevntcount_low_addr, PAGE_SIZE); |
| u32 xfer_scratch_r = |
| rounddown(params->xfer_scratch.depcmd_low_addr, PAGE_SIZE); |
| |
| if (gevntcount_r != xfer_scratch_r) { |
| IPA_USB_ERR("No support more than 1 page map for USB regs\n"); |
| WARN_ON(1); |
| return -EINVAL; |
| } |
| |
| if (map) { |
| if (ipa3_usb_ctx->smmu_reg_map.cnt == 0) { |
| ipa3_usb_ctx->smmu_reg_map.addr = gevntcount_r; |
| result = ipa3_smmu_map_peer_reg( |
| ipa3_usb_ctx->smmu_reg_map.addr, true, |
| IPA_SMMU_CB_AP); |
| if (result) { |
| IPA_USB_ERR("failed to map USB regs %d\n", |
| result); |
| return result; |
| } |
| } else { |
| if (gevntcount_r != ipa3_usb_ctx->smmu_reg_map.addr) { |
| IPA_USB_ERR( |
| "No support for map different reg\n"); |
| return -EINVAL; |
| } |
| } |
| ipa3_usb_ctx->smmu_reg_map.cnt++; |
| } else { |
| if (gevntcount_r != ipa3_usb_ctx->smmu_reg_map.addr) { |
| IPA_USB_ERR( |
| "No support for map different reg\n"); |
| return -EINVAL; |
| } |
| |
| if (ipa3_usb_ctx->smmu_reg_map.cnt == 1) { |
| result = ipa3_smmu_map_peer_reg( |
| ipa3_usb_ctx->smmu_reg_map.addr, false, |
| IPA_SMMU_CB_AP); |
| if (result) { |
| IPA_USB_ERR("failed to unmap USB regs %d\n", |
| result); |
| return result; |
| } |
| } |
| ipa3_usb_ctx->smmu_reg_map.cnt--; |
| } |
| |
| |
| result = ipa3_smmu_map_peer_buff(params->xfer_ring_base_addr_iova, |
| params->xfer_ring_len, map, params->sgt_xfer_rings, |
| IPA_SMMU_CB_AP); |
| if (result) { |
| IPA_USB_ERR("failed to map Xfer ring %d\n", result); |
| return result; |
| } |
| |
| result = ipa3_smmu_map_peer_buff(params->data_buff_base_addr_iova, |
| params->data_buff_base_len, map, params->sgt_data_buff, |
| IPA_SMMU_CB_AP); |
| if (result) { |
| IPA_USB_ERR("failed to map TRBs buff %d\n", result); |
| return result; |
| } |
| |
| return 0; |
| } |
| |
| static int ipa3_usb_request_xdci_channel( |
| struct ipa_usb_xdci_chan_params *params, |
| enum ipa_usb_direction dir, |
| struct ipa_req_chan_out_params *out_params) |
| { |
| int result = -EFAULT; |
| struct ipa_request_gsi_channel_params chan_params; |
| enum ipa3_usb_transport_type ttype; |
| struct ipa_usb_xdci_chan_params *xdci_ch_params; |
| |
| IPA_USB_DBG_LOW("entry\n"); |
| if (params == NULL || out_params == NULL || |
| !ipa3_usb_check_chan_params(params)) { |
| IPA_USB_ERR("bad parameters\n"); |
| return -EINVAL; |
| } |
| |
| ttype = IPA3_USB_GET_TTYPE(params->teth_prot); |
| |
| if (!ipa3_usb_check_legal_op(IPA_USB_OP_REQUEST_CHANNEL, ttype)) { |
| IPA_USB_ERR("Illegal operation\n"); |
| return -EPERM; |
| } |
| |
| memset(&chan_params, 0, sizeof(struct ipa_request_gsi_channel_params)); |
| memcpy(&chan_params.ipa_ep_cfg, ¶ms->ipa_ep_cfg, |
| sizeof(struct ipa_ep_cfg)); |
| chan_params.client = params->client; |
| switch (params->teth_prot) { |
| case IPA_USB_RNDIS: |
| chan_params.priv = ipa3_usb_ctx->teth_prot_ctx[IPA_USB_RNDIS]. |
| teth_prot_params.rndis.private; |
| if (params->dir == GSI_CHAN_DIR_FROM_GSI) |
| chan_params.notify = |
| ipa3_usb_ctx->teth_prot_ctx[IPA_USB_RNDIS]. |
| teth_prot_params.rndis.ipa_tx_notify; |
| else |
| chan_params.notify = |
| ipa3_usb_ctx->teth_prot_ctx[IPA_USB_RNDIS]. |
| teth_prot_params.rndis.ipa_rx_notify; |
| chan_params.skip_ep_cfg = |
| ipa3_usb_ctx->teth_prot_ctx[IPA_USB_RNDIS]. |
| teth_prot_params.rndis.skip_ep_cfg; |
| break; |
| case IPA_USB_ECM: |
| chan_params.priv = ipa3_usb_ctx->teth_prot_ctx[IPA_USB_ECM]. |
| teth_prot_params.ecm.private; |
| if (params->dir == GSI_CHAN_DIR_FROM_GSI) |
| chan_params.notify = |
| ipa3_usb_ctx->teth_prot_ctx[IPA_USB_ECM]. |
| teth_prot_params.ecm.ecm_ipa_tx_dp_notify; |
| else |
| chan_params.notify = |
| ipa3_usb_ctx->teth_prot_ctx[IPA_USB_ECM]. |
| teth_prot_params.ecm.ecm_ipa_rx_dp_notify; |
| chan_params.skip_ep_cfg = |
| ipa3_usb_ctx->teth_prot_ctx[IPA_USB_ECM]. |
| teth_prot_params.ecm.skip_ep_cfg; |
| break; |
| case IPA_USB_RMNET: |
| case IPA_USB_MBIM: |
| chan_params.priv = |
| ipa3_usb_ctx->teth_bridge_params.private_data; |
| chan_params.notify = |
| ipa3_usb_ctx->teth_bridge_params.usb_notify_cb; |
| chan_params.skip_ep_cfg = |
| ipa3_usb_ctx->teth_bridge_params.skip_ep_cfg; |
| break; |
| case IPA_USB_DIAG: |
| chan_params.priv = NULL; |
| chan_params.notify = NULL; |
| chan_params.skip_ep_cfg = true; |
| break; |
| default: |
| break; |
| } |
| |
| result = ipa3_usb_smmu_map_xdci_channel(params, true); |
| if (result) { |
| IPA_USB_ERR("failed to smmu map %d\n", result); |
| return result; |
| } |
| |
| /* store channel params for SMMU unmap */ |
| if (dir == IPA_USB_DIR_UL) |
| xdci_ch_params = &ipa3_usb_ctx->ttype_ctx[ttype].ul_ch_params; |
| else |
| xdci_ch_params = &ipa3_usb_ctx->ttype_ctx[ttype].dl_ch_params; |
| |
| *xdci_ch_params = *params; |
| result = ipa_smmu_store_sgt( |
| &xdci_ch_params->sgt_xfer_rings, |
| params->sgt_xfer_rings); |
| if (result) |
| return result; |
| |
| result = ipa_smmu_store_sgt( |
| &xdci_ch_params->sgt_data_buff, |
| params->sgt_data_buff); |
| if (result) { |
| ipa_smmu_free_sgt(&xdci_ch_params->sgt_xfer_rings); |
| return result; |
| } |
| chan_params.keep_ipa_awake = params->keep_ipa_awake; |
| chan_params.evt_ring_params.intf = GSI_EVT_CHTYPE_XDCI_EV; |
| chan_params.evt_ring_params.intr = GSI_INTR_IRQ; |
| chan_params.evt_ring_params.re_size = GSI_EVT_RING_RE_SIZE_16B; |
| chan_params.evt_ring_params.ring_len = params->xfer_ring_len - |
| chan_params.evt_ring_params.re_size; |
| chan_params.evt_ring_params.ring_base_addr = |
| params->xfer_ring_base_addr_iova; |
| chan_params.evt_ring_params.ring_base_vaddr = NULL; |
| chan_params.evt_ring_params.int_modt = 0; |
| chan_params.evt_ring_params.int_modt = 0; |
| chan_params.evt_ring_params.intvec = 0; |
| chan_params.evt_ring_params.msi_addr = 0; |
| chan_params.evt_ring_params.rp_update_addr = 0; |
| chan_params.evt_ring_params.exclusive = true; |
| chan_params.evt_ring_params.err_cb = ipa3_usb_gsi_evt_err_cb; |
| chan_params.evt_ring_params.user_data = NULL; |
| chan_params.evt_scratch.xdci.gevntcount_low_addr = |
| params->gevntcount_low_addr; |
| chan_params.evt_scratch.xdci.gevntcount_hi_addr = |
| params->gevntcount_hi_addr; |
| chan_params.chan_params.prot = GSI_CHAN_PROT_XDCI; |
| chan_params.chan_params.dir = params->dir; |
| /* chan_id is set in ipa3_request_gsi_channel() */ |
| chan_params.chan_params.re_size = GSI_CHAN_RE_SIZE_16B; |
| chan_params.chan_params.ring_len = params->xfer_ring_len; |
| chan_params.chan_params.ring_base_addr = |
| params->xfer_ring_base_addr_iova; |
| chan_params.chan_params.ring_base_vaddr = NULL; |
| if (ipa3_ctx->ipa_hw_type >= IPA_HW_v4_0) |
| chan_params.chan_params.use_db_eng = GSI_CHAN_DIRECT_MODE; |
| else |
| chan_params.chan_params.use_db_eng = GSI_CHAN_DB_MODE; |
| chan_params.chan_params.prefetch_mode = |
| ipa_get_ep_prefetch_mode(chan_params.client); |
| chan_params.chan_params.max_prefetch = GSI_ONE_PREFETCH_SEG; |
| if (params->dir == GSI_CHAN_DIR_FROM_GSI) |
| chan_params.chan_params.low_weight = |
| IPA_USB_DL_CHAN_LOW_WEIGHT; |
| else |
| chan_params.chan_params.low_weight = |
| IPA_USB_UL_CHAN_LOW_WEIGHT; |
| chan_params.chan_params.xfer_cb = NULL; |
| chan_params.chan_params.err_cb = ipa3_usb_gsi_chan_err_cb; |
| chan_params.chan_params.chan_user_data = NULL; |
| chan_params.chan_scratch.xdci.last_trb_addr = |
| params->xfer_scratch.last_trb_addr_iova; |
| /* xferrscidx will be updated later */ |
| chan_params.chan_scratch.xdci.xferrscidx = 0; |
| chan_params.chan_scratch.xdci.const_buffer_size = |
| params->xfer_scratch.const_buffer_size; |
| chan_params.chan_scratch.xdci.depcmd_low_addr = |
| params->xfer_scratch.depcmd_low_addr; |
| chan_params.chan_scratch.xdci.depcmd_hi_addr = |
| params->xfer_scratch.depcmd_hi_addr; |
| chan_params.chan_scratch.xdci.outstanding_threshold = |
| ((params->teth_prot == IPA_USB_MBIM) ? 1 : 2) * |
| chan_params.chan_params.re_size; |
| |
| if (ipa3_ctx->ipa_hw_type >= IPA_HW_v4_0) |
| chan_params.chan_scratch.xdci.outstanding_threshold = 0; |
| |
| /* max_outstanding_tre is set in ipa3_request_gsi_channel() */ |
| result = ipa3_request_gsi_channel(&chan_params, out_params); |
| if (result) { |
| IPA_USB_ERR("failed to allocate GSI channel\n"); |
| ipa3_usb_smmu_map_xdci_channel(params, false); |
| return result; |
| } |
| |
| IPA_USB_DBG_LOW("exit\n"); |
| return 0; |
| } |
| |
| static int ipa3_usb_release_xdci_channel(u32 clnt_hdl, |
| enum ipa_usb_direction dir, |
| enum ipa3_usb_transport_type ttype) |
| { |
| int result = 0; |
| struct ipa_usb_xdci_chan_params *xdci_ch_params; |
| |
| IPA_USB_DBG_LOW("entry\n"); |
| if (ttype < 0 || ttype >= IPA_USB_TRANSPORT_MAX) { |
| IPA_USB_ERR("bad parameter.\n"); |
| return -EINVAL; |
| } |
| |
| if (!ipa3_usb_check_legal_op(IPA_USB_OP_RELEASE_CHANNEL, ttype)) { |
| IPA_USB_ERR("Illegal operation.\n"); |
| return -EPERM; |
| } |
| |
| /* Release channel */ |
| result = ipa3_release_gsi_channel(clnt_hdl); |
| if (result) { |
| IPA_USB_ERR("failed to deallocate channel.\n"); |
| return result; |
| } |
| |
| if (dir == IPA_USB_DIR_UL) |
| xdci_ch_params = &ipa3_usb_ctx->ttype_ctx[ttype].ul_ch_params; |
| else |
| xdci_ch_params = &ipa3_usb_ctx->ttype_ctx[ttype].dl_ch_params; |
| |
| result = ipa3_usb_smmu_map_xdci_channel(xdci_ch_params, false); |
| |
| if (xdci_ch_params->sgt_xfer_rings != NULL) |
| ipa_smmu_free_sgt(&xdci_ch_params->sgt_xfer_rings); |
| if (xdci_ch_params->sgt_data_buff != NULL) |
| ipa_smmu_free_sgt(&xdci_ch_params->sgt_data_buff); |
| |
| /* Change ipa_usb state to INITIALIZED */ |
| if (!ipa3_usb_set_state(IPA_USB_INITIALIZED, false, ttype)) |
| IPA_USB_ERR("failed to change state to initialized\n"); |
| |
| IPA_USB_DBG_LOW("exit\n"); |
| return 0; |
| } |
| |
| static int ipa3_usb_request_prod(enum ipa3_usb_transport_type ttype) |
| { |
| int result; |
| struct ipa3_usb_rm_context *rm_ctx; |
| const char *rsrc_str; |
| |
| rm_ctx = &ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx; |
| rsrc_str = ipa_rm_resource_str(rm_ctx->prod_params.name); |
| |
| IPA_USB_DBG_LOW("requesting %s\n", rsrc_str); |
| init_completion(&rm_ctx->prod_comp); |
| result = ipa_rm_request_resource(rm_ctx->prod_params.name); |
| if (result) { |
| if (result != -EINPROGRESS) { |
| IPA_USB_ERR("failed to request %s: %d\n", |
| rsrc_str, result); |
| return result; |
| } |
| result = wait_for_completion_timeout(&rm_ctx->prod_comp, |
| msecs_to_jiffies(IPA_USB_RM_TIMEOUT_MSEC)); |
| if (result == 0) { |
| IPA_USB_ERR("timeout request %s\n", rsrc_str); |
| return -ETIME; |
| } |
| } |
| |
| IPA_USB_DBG_LOW("%s granted\n", rsrc_str); |
| return 0; |
| } |
| |
| static int ipa3_usb_release_prod(enum ipa3_usb_transport_type ttype) |
| { |
| int result; |
| struct ipa3_usb_rm_context *rm_ctx; |
| const char *rsrc_str; |
| |
| rm_ctx = &ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx; |
| rsrc_str = ipa_rm_resource_str(rm_ctx->prod_params.name); |
| |
| IPA_USB_DBG_LOW("releasing %s\n", rsrc_str); |
| |
| init_completion(&rm_ctx->prod_comp); |
| result = ipa_rm_release_resource(rm_ctx->prod_params.name); |
| if (result) { |
| if (result != -EINPROGRESS) { |
| IPA_USB_ERR("failed to release %s: %d\n", |
| rsrc_str, result); |
| return result; |
| } |
| result = wait_for_completion_timeout(&rm_ctx->prod_comp, |
| msecs_to_jiffies(IPA_USB_RM_TIMEOUT_MSEC)); |
| if (result == 0) { |
| IPA_USB_ERR("timeout release %s\n", rsrc_str); |
| return -ETIME; |
| } |
| } |
| |
| IPA_USB_DBG_LOW("%s released\n", rsrc_str); |
| return 0; |
| } |
| |
| static bool ipa3_usb_check_connect_params( |
| struct ipa_usb_xdci_connect_params_internal *params) |
| { |
| IPA_USB_DBG_LOW("ul xferrscidx = %d\n", params->usb_to_ipa_xferrscidx); |
| IPA_USB_DBG_LOW("dl xferrscidx = %d\n", params->ipa_to_usb_xferrscidx); |
| IPA_USB_DBG_LOW("max_supported_bandwidth_mbps = %d\n", |
| params->max_supported_bandwidth_mbps); |
| |
| if (params->max_pkt_size < IPA_USB_HIGH_SPEED_512B || |
| params->max_pkt_size > IPA_USB_SUPER_SPEED_1024B || |
| params->ipa_to_usb_xferrscidx < 0 || |
| params->ipa_to_usb_xferrscidx > 127 || |
| (params->teth_prot != IPA_USB_DIAG && |
| (params->usb_to_ipa_xferrscidx < 0 || |
| params->usb_to_ipa_xferrscidx > 127)) || |
| params->teth_prot < 0 || |
| params->teth_prot >= IPA_USB_MAX_TETH_PROT_SIZE) { |
| IPA_USB_ERR("Invalid params\n"); |
| return false; |
| } |
| |
| if (ipa3_usb_ctx->teth_prot_ctx[params->teth_prot].state == |
| IPA_USB_TETH_PROT_INVALID) { |
| IPA_USB_ERR("%s is not initialized\n", |
| ipa3_usb_teth_prot_to_string( |
| params->teth_prot)); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static int ipa3_usb_connect_teth_bridge( |
| struct teth_bridge_connect_params *params) |
| { |
| int result; |
| |
| result = teth_bridge_connect(params); |
| if (result) { |
| IPA_USB_ERR("failed to connect teth_bridge (%s)\n", |
| params->tethering_mode == TETH_TETHERING_MODE_RMNET ? |
| "rmnet" : "mbim"); |
| return result; |
| } |
| |
| return 0; |
| } |
| |
| static int ipa3_usb_connect_dpl(void) |
| { |
| int res = 0; |
| |
| if (ipa_pm_is_used()) |
| return 0; |
| |
| /* |
| * Add DPL dependency to RM dependency graph, first add_dependency call |
| * is sync in order to make sure the IPA clocks are up before we |
| * continue and notify the USB driver it may continue. |
| */ |
| res = ipa_rm_add_dependency_sync(IPA_RM_RESOURCE_USB_DPL_DUMMY_PROD, |
| IPA_RM_RESOURCE_Q6_CONS); |
| if (res < 0) { |
| IPA_USB_ERR("ipa_rm_add_dependency_sync() failed.\n"); |
| return res; |
| } |
| |
| /* |
| * this add_dependency call can't be sync since it will block until DPL |
| * status is connected (which can happen only later in the flow), |
| * the clocks are already up so the call doesn't need to block. |
| */ |
| res = ipa_rm_add_dependency(IPA_RM_RESOURCE_Q6_PROD, |
| IPA_RM_RESOURCE_USB_DPL_CONS); |
| if (res < 0 && res != -EINPROGRESS) { |
| IPA_USB_ERR("ipa_rm_add_dependency() failed.\n"); |
| ipa_rm_delete_dependency(IPA_RM_RESOURCE_USB_DPL_DUMMY_PROD, |
| IPA_RM_RESOURCE_Q6_CONS); |
| return res; |
| } |
| |
| return 0; |
| } |
| |
| static int ipa3_usb_connect_teth_prot(enum ipa_usb_teth_prot teth_prot) |
| { |
| int result; |
| struct teth_bridge_connect_params teth_bridge_params; |
| struct ipa3_usb_teth_prot_conn_params *teth_conn_params; |
| enum ipa3_usb_transport_type ttype; |
| |
| IPA_USB_DBG("connecting protocol = %s\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| |
| ttype = IPA3_USB_GET_TTYPE(teth_prot); |
| |
| teth_conn_params = &(ipa3_usb_ctx->ttype_ctx[ttype].teth_conn_params); |
| |
| switch (teth_prot) { |
| case IPA_USB_RNDIS: |
| if (ipa3_usb_ctx->teth_prot_ctx[IPA_USB_RNDIS].state == |
| IPA_USB_TETH_PROT_CONNECTED) { |
| IPA_USB_DBG("%s is already connected.\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| break; |
| } |
| ipa3_usb_ctx->ttype_ctx[ttype].user_data = |
| ipa3_usb_ctx->teth_prot_ctx[IPA_USB_RNDIS].user_data; |
| result = rndis_ipa_pipe_connect_notify( |
| teth_conn_params->usb_to_ipa_clnt_hdl, |
| teth_conn_params->ipa_to_usb_clnt_hdl, |
| teth_conn_params->params.max_xfer_size_bytes_to_dev, |
| teth_conn_params->params.max_packet_number_to_dev, |
| teth_conn_params->params.max_xfer_size_bytes_to_host, |
| ipa3_usb_ctx->teth_prot_ctx[IPA_USB_RNDIS]. |
| teth_prot_params.rndis.private); |
| if (result) { |
| IPA_USB_ERR("failed to connect %s.\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| ipa3_usb_ctx->ttype_ctx[ttype].user_data = NULL; |
| return result; |
| } |
| ipa3_usb_ctx->teth_prot_ctx[IPA_USB_RNDIS].state = |
| IPA_USB_TETH_PROT_CONNECTED; |
| IPA_USB_DBG("%s is connected.\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| break; |
| case IPA_USB_ECM: |
| if (ipa3_usb_ctx->teth_prot_ctx[IPA_USB_ECM].state == |
| IPA_USB_TETH_PROT_CONNECTED) { |
| IPA_USB_DBG("%s is already connected.\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| break; |
| } |
| ipa3_usb_ctx->ttype_ctx[ttype].user_data = |
| ipa3_usb_ctx->teth_prot_ctx[IPA_USB_ECM].user_data; |
| result = ecm_ipa_connect(teth_conn_params->usb_to_ipa_clnt_hdl, |
| teth_conn_params->ipa_to_usb_clnt_hdl, |
| ipa3_usb_ctx->teth_prot_ctx[IPA_USB_ECM]. |
| teth_prot_params.ecm.private); |
| if (result) { |
| IPA_USB_ERR("failed to connect %s.\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| ipa3_usb_ctx->ttype_ctx[ttype].user_data = NULL; |
| return result; |
| } |
| ipa3_usb_ctx->teth_prot_ctx[IPA_USB_ECM].state = |
| IPA_USB_TETH_PROT_CONNECTED; |
| IPA_USB_DBG("%s is connected.\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| break; |
| case IPA_USB_RMNET: |
| case IPA_USB_MBIM: |
| if (ipa3_usb_ctx->teth_prot_ctx[teth_prot].state == |
| IPA_USB_TETH_PROT_CONNECTED) { |
| IPA_USB_DBG("%s is already connected.\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| break; |
| } |
| result = ipa3_usb_init_teth_bridge(); |
| if (result) |
| return result; |
| |
| ipa3_usb_ctx->ttype_ctx[ttype].user_data = |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot]. |
| user_data; |
| teth_bridge_params.ipa_usb_pipe_hdl = |
| teth_conn_params->ipa_to_usb_clnt_hdl; |
| teth_bridge_params.usb_ipa_pipe_hdl = |
| teth_conn_params->usb_to_ipa_clnt_hdl; |
| teth_bridge_params.tethering_mode = |
| (teth_prot == IPA_USB_RMNET) ? |
| (TETH_TETHERING_MODE_RMNET):(TETH_TETHERING_MODE_MBIM); |
| teth_bridge_params.client_type = IPA_CLIENT_USB_PROD; |
| result = ipa3_usb_connect_teth_bridge(&teth_bridge_params); |
| if (result) { |
| ipa3_usb_ctx->ttype_ctx[ttype].user_data = NULL; |
| return result; |
| } |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].state = |
| IPA_USB_TETH_PROT_CONNECTED; |
| ipa3_usb_notify_do(ttype, IPA_USB_DEVICE_READY); |
| IPA_USB_DBG("%s (%s) is connected.\n", |
| ipa3_usb_teth_prot_to_string(teth_prot), |
| ipa3_usb_teth_bridge_prot_to_string(teth_prot)); |
| break; |
| case IPA_USB_DIAG: |
| if (ipa3_usb_ctx->teth_prot_ctx[IPA_USB_DIAG].state == |
| IPA_USB_TETH_PROT_CONNECTED) { |
| IPA_USB_DBG("%s is already connected.\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| break; |
| } |
| |
| ipa3_usb_ctx->ttype_ctx[ttype].user_data = |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].user_data; |
| result = ipa3_usb_connect_dpl(); |
| if (result) { |
| IPA_USB_ERR("Failed connecting DPL result=%d\n", |
| result); |
| ipa3_usb_ctx->ttype_ctx[ttype].user_data = NULL; |
| return result; |
| } |
| ipa3_usb_ctx->teth_prot_ctx[IPA_USB_DIAG].state = |
| IPA_USB_TETH_PROT_CONNECTED; |
| ipa3_usb_notify_do(ttype, IPA_USB_DEVICE_READY); |
| IPA_USB_DBG("%s is connected.\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| break; |
| default: |
| IPA_USB_ERR("Invalid tethering protocol\n"); |
| return -EFAULT; |
| } |
| |
| return 0; |
| } |
| |
| static int ipa3_usb_disconnect_teth_bridge(void) |
| { |
| int result; |
| |
| result = teth_bridge_disconnect(IPA_CLIENT_USB_PROD); |
| if (result) { |
| IPA_USB_ERR("failed to disconnect teth_bridge.\n"); |
| return result; |
| } |
| |
| return 0; |
| } |
| |
| static int ipa3_usb_disconnect_dpl(void) |
| { |
| int res; |
| |
| if (ipa_pm_is_used()) |
| return 0; |
| |
| /* Remove DPL RM dependency */ |
| res = ipa_rm_delete_dependency(IPA_RM_RESOURCE_USB_DPL_DUMMY_PROD, |
| IPA_RM_RESOURCE_Q6_CONS); |
| if (res) |
| IPA_USB_ERR("deleting DPL_DUMMY_PROD rsrc dependency fail\n"); |
| |
| res = ipa_rm_delete_dependency(IPA_RM_RESOURCE_Q6_PROD, |
| IPA_RM_RESOURCE_USB_DPL_CONS); |
| if (res) |
| IPA_USB_ERR("deleting DPL_CONS rsrc dependencty fail\n"); |
| |
| return 0; |
| } |
| |
| static int ipa3_usb_disconnect_teth_prot(enum ipa_usb_teth_prot teth_prot) |
| { |
| int result = 0; |
| enum ipa3_usb_transport_type ttype; |
| |
| ttype = IPA3_USB_GET_TTYPE(teth_prot); |
| |
| switch (teth_prot) { |
| case IPA_USB_RNDIS: |
| case IPA_USB_ECM: |
| if (ipa3_usb_ctx->teth_prot_ctx[teth_prot].state != |
| IPA_USB_TETH_PROT_CONNECTED) { |
| IPA_USB_DBG("%s is not connected.\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| return -EPERM; |
| } |
| if (teth_prot == IPA_USB_RNDIS) { |
| result = rndis_ipa_pipe_disconnect_notify( |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot]. |
| teth_prot_params.rndis.private); |
| } else { |
| result = ecm_ipa_disconnect( |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot]. |
| teth_prot_params.ecm.private); |
| } |
| if (result) { |
| IPA_USB_ERR("failed to disconnect %s.\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| break; |
| } |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].state = |
| IPA_USB_TETH_PROT_INITIALIZED; |
| IPA_USB_DBG("disconnected %s\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| break; |
| case IPA_USB_RMNET: |
| case IPA_USB_MBIM: |
| if (ipa3_usb_ctx->teth_prot_ctx[teth_prot].state != |
| IPA_USB_TETH_PROT_CONNECTED) { |
| IPA_USB_DBG("%s (%s) is not connected.\n", |
| ipa3_usb_teth_prot_to_string(teth_prot), |
| ipa3_usb_teth_bridge_prot_to_string(teth_prot)); |
| return -EPERM; |
| } |
| result = ipa3_usb_disconnect_teth_bridge(); |
| if (result) |
| break; |
| |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].state = |
| IPA_USB_TETH_PROT_INITIALIZED; |
| IPA_USB_DBG("disconnected %s (%s)\n", |
| ipa3_usb_teth_prot_to_string(teth_prot), |
| ipa3_usb_teth_bridge_prot_to_string(teth_prot)); |
| break; |
| case IPA_USB_DIAG: |
| if (ipa3_usb_ctx->teth_prot_ctx[teth_prot].state != |
| IPA_USB_TETH_PROT_CONNECTED) { |
| IPA_USB_DBG("%s is not connected.\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| return -EPERM; |
| } |
| result = ipa3_usb_disconnect_dpl(); |
| if (result) |
| break; |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].state = |
| IPA_USB_TETH_PROT_INITIALIZED; |
| IPA_USB_DBG("disconnected %s\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| break; |
| default: |
| break; |
| } |
| |
| ipa3_usb_ctx->ttype_ctx[ttype].user_data = NULL; |
| return result; |
| } |
| |
| static int ipa3_usb_xdci_connect_internal( |
| struct ipa_usb_xdci_connect_params_internal *params) |
| { |
| int result = -EFAULT; |
| struct ipa_rm_perf_profile profile; |
| enum ipa3_usb_transport_type ttype; |
| |
| IPA_USB_DBG_LOW("entry\n"); |
| if (params == NULL || !ipa3_usb_check_connect_params(params)) { |
| IPA_USB_ERR("bad parameters.\n"); |
| return -EINVAL; |
| } |
| |
| ttype = (params->teth_prot == IPA_USB_DIAG) ? IPA_USB_TRANSPORT_DPL : |
| IPA_USB_TRANSPORT_TETH; |
| |
| if (!ipa3_usb_check_legal_op(IPA_USB_OP_CONNECT, ttype)) { |
| IPA_USB_ERR("Illegal operation.\n"); |
| return -EPERM; |
| } |
| |
| ipa3_usb_ctx->ttype_ctx[ttype].teth_conn_params.ipa_to_usb_clnt_hdl |
| = params->ipa_to_usb_clnt_hdl; |
| if (!IPA3_USB_IS_TTYPE_DPL(ttype)) |
| ipa3_usb_ctx->ttype_ctx[ttype].teth_conn_params. |
| usb_to_ipa_clnt_hdl = params->usb_to_ipa_clnt_hdl; |
| ipa3_usb_ctx->ttype_ctx[ttype].teth_conn_params.params |
| = params->teth_prot_params; |
| |
| /* Set EE xDCI specific scratch */ |
| result = ipa3_set_usb_max_packet_size(params->max_pkt_size); |
| if (result) { |
| IPA_USB_ERR("failed setting xDCI EE scratch field\n"); |
| return result; |
| } |
| |
| if (ipa_pm_is_used()) { |
| /* perf profile is not set on USB DPL pipe */ |
| if (ttype != IPA_USB_TRANSPORT_DPL) { |
| result = ipa_pm_set_perf_profile( |
| ipa3_usb_ctx->ttype_ctx[ttype].pm_ctx.hdl, |
| params->max_supported_bandwidth_mbps); |
| if (result) { |
| IPA_USB_ERR("failed to set perf profile\n"); |
| return result; |
| } |
| } |
| |
| result = ipa_pm_activate_sync( |
| ipa3_usb_ctx->ttype_ctx[ttype].pm_ctx.hdl); |
| if (result) { |
| IPA_USB_ERR("failed to activate pm\n"); |
| return result; |
| } |
| } else { |
| /* Set RM PROD & CONS perf profile */ |
| profile.max_supported_bandwidth_mbps = |
| params->max_supported_bandwidth_mbps; |
| result = ipa_rm_set_perf_profile( |
| ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx.prod_params.name, |
| &profile); |
| if (result) { |
| IPA_USB_ERR("failed to set %s perf profile\n", |
| ipa_rm_resource_str(ipa3_usb_ctx-> |
| ttype_ctx[ttype]. |
| rm_ctx.prod_params.name)); |
| return result; |
| } |
| result = ipa_rm_set_perf_profile( |
| ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx.cons_params.name, |
| &profile); |
| if (result) { |
| IPA_USB_ERR("failed to set %s perf profile\n", |
| ipa_rm_resource_str(ipa3_usb_ctx-> |
| ttype_ctx[ttype]. |
| rm_ctx.cons_params.name)); |
| return result; |
| } |
| |
| /* Request PROD */ |
| result = ipa3_usb_request_prod(ttype); |
| if (result) |
| return result; |
| } |
| |
| if (params->teth_prot != IPA_USB_DIAG) { |
| /* Start UL channel */ |
| result = ipa3_xdci_start(params->usb_to_ipa_clnt_hdl, |
| params->usb_to_ipa_xferrscidx, |
| params->usb_to_ipa_xferrscidx_valid); |
| if (result) { |
| IPA_USB_ERR("failed to connect UL channel.\n"); |
| goto connect_ul_fail; |
| } |
| } |
| |
| /* Start DL/DPL channel */ |
| result = ipa3_xdci_start(params->ipa_to_usb_clnt_hdl, |
| params->ipa_to_usb_xferrscidx, |
| params->ipa_to_usb_xferrscidx_valid); |
| if (result) { |
| IPA_USB_ERR("failed to connect DL/DPL channel.\n"); |
| goto connect_dl_fail; |
| } |
| |
| /* Connect tethering protocol */ |
| result = ipa3_usb_connect_teth_prot(params->teth_prot); |
| if (result) { |
| IPA_USB_ERR("failed to connect teth protocol\n"); |
| goto connect_teth_prot_fail; |
| } |
| |
| if (!ipa3_usb_set_state(IPA_USB_CONNECTED, false, ttype)) { |
| IPA_USB_ERR( |
| "failed to change state to connected\n"); |
| goto state_change_connected_fail; |
| } |
| |
| IPA_USB_DBG_LOW("exit\n"); |
| return 0; |
| |
| state_change_connected_fail: |
| ipa3_usb_disconnect_teth_prot(params->teth_prot); |
| connect_teth_prot_fail: |
| ipa3_xdci_disconnect(params->ipa_to_usb_clnt_hdl, false, -1); |
| ipa3_reset_gsi_channel(params->ipa_to_usb_clnt_hdl); |
| ipa3_reset_gsi_event_ring(params->ipa_to_usb_clnt_hdl); |
| connect_dl_fail: |
| if (params->teth_prot != IPA_USB_DIAG) { |
| ipa3_xdci_disconnect(params->usb_to_ipa_clnt_hdl, false, -1); |
| ipa3_reset_gsi_channel(params->usb_to_ipa_clnt_hdl); |
| ipa3_reset_gsi_event_ring(params->usb_to_ipa_clnt_hdl); |
| } |
| connect_ul_fail: |
| if (ipa_pm_is_used()) |
| ipa_pm_deactivate_sync( |
| ipa3_usb_ctx->ttype_ctx[ttype].pm_ctx.hdl); |
| else |
| ipa3_usb_release_prod(ttype); |
| return result; |
| } |
| |
| #ifdef CONFIG_DEBUG_FS |
| static char dbg_buff[IPA_USB_MAX_MSG_LEN]; |
| |
| static char *ipa3_usb_cons_state_to_string(enum ipa3_usb_cons_state state) |
| { |
| switch (state) { |
| case IPA_USB_CONS_GRANTED: |
| return "CONS_GRANTED"; |
| case IPA_USB_CONS_RELEASED: |
| return "CONS_RELEASED"; |
| } |
| |
| return "UNSUPPORTED"; |
| } |
| |
| static int ipa3_usb_get_status_dbg_info(struct ipa3_usb_status_dbg_info *status) |
| { |
| int res; |
| int i; |
| unsigned long flags; |
| |
| IPA_USB_DBG_LOW("entry\n"); |
| |
| if (ipa3_usb_ctx == NULL) { |
| IPA_USB_ERR("IPA USB was not inited yet\n"); |
| return -EFAULT; |
| } |
| |
| mutex_lock(&ipa3_usb_ctx->general_mutex); |
| |
| if (!status) { |
| IPA_USB_ERR("Invalid input\n"); |
| res = -EINVAL; |
| goto bail; |
| } |
| |
| memset(status, 0, sizeof(struct ipa3_usb_status_dbg_info)); |
| |
| spin_lock_irqsave(&ipa3_usb_ctx->state_lock, flags); |
| status->teth_state = ipa3_usb_state_to_string( |
| ipa3_usb_ctx->ttype_ctx[IPA_USB_TRANSPORT_TETH].state); |
| status->dpl_state = ipa3_usb_state_to_string( |
| ipa3_usb_ctx->ttype_ctx[IPA_USB_TRANSPORT_DPL].state); |
| if (ipa3_usb_ctx->ttype_ctx[IPA_USB_TRANSPORT_TETH].rm_ctx.cons_valid) |
| status->teth_cons_state = ipa3_usb_cons_state_to_string( |
| ipa3_usb_ctx->ttype_ctx[IPA_USB_TRANSPORT_TETH]. |
| rm_ctx.cons_state); |
| if (ipa3_usb_ctx->ttype_ctx[IPA_USB_TRANSPORT_DPL].rm_ctx.cons_valid) |
| status->dpl_cons_state = ipa3_usb_cons_state_to_string( |
| ipa3_usb_ctx->ttype_ctx[IPA_USB_TRANSPORT_DPL]. |
| rm_ctx.cons_state); |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, flags); |
| |
| for (i = 0 ; i < IPA_USB_MAX_TETH_PROT_SIZE ; i++) { |
| if (ipa3_usb_ctx->teth_prot_ctx[i].state == |
| IPA_USB_TETH_PROT_INITIALIZED) { |
| if ((i == IPA_USB_RMNET) || (i == IPA_USB_MBIM)) |
| status->inited_prots[status->num_init_prot++] = |
| ipa3_usb_teth_bridge_prot_to_string(i); |
| else |
| status->inited_prots[status->num_init_prot++] = |
| ipa3_usb_teth_prot_to_string(i); |
| } else if (ipa3_usb_ctx->teth_prot_ctx[i].state == |
| IPA_USB_TETH_PROT_CONNECTED) { |
| switch (i) { |
| case IPA_USB_RMNET: |
| case IPA_USB_MBIM: |
| status->teth_connected_prot = |
| ipa3_usb_teth_bridge_prot_to_string(i); |
| break; |
| case IPA_USB_DIAG: |
| status->dpl_connected_prot = |
| ipa3_usb_teth_prot_to_string(i); |
| break; |
| default: |
| status->teth_connected_prot = |
| ipa3_usb_teth_prot_to_string(i); |
| } |
| } |
| } |
| |
| res = 0; |
| IPA_USB_DBG_LOW("exit\n"); |
| bail: |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return res; |
| } |
| |
| static ssize_t ipa3_read_usb_state_info(struct file *file, char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| struct ipa3_usb_status_dbg_info status; |
| int result; |
| int nbytes; |
| int cnt = 0; |
| int i; |
| |
| result = ipa3_usb_get_status_dbg_info(&status); |
| if (result) { |
| nbytes = scnprintf(dbg_buff, IPA_USB_MAX_MSG_LEN, |
| "Fail to read IPA USB status\n"); |
| cnt += nbytes; |
| } else { |
| nbytes = scnprintf(dbg_buff, IPA_USB_MAX_MSG_LEN, |
| "Tethering Data State: %s\n" |
| "DPL State: %s\n" |
| "Protocols in Initialized State: ", |
| status.teth_state, |
| status.dpl_state); |
| cnt += nbytes; |
| |
| for (i = 0 ; i < status.num_init_prot ; i++) { |
| nbytes = scnprintf(dbg_buff + cnt, |
| IPA_USB_MAX_MSG_LEN - cnt, |
| "%s ", status.inited_prots[i]); |
| cnt += nbytes; |
| } |
| nbytes = scnprintf(dbg_buff + cnt, IPA_USB_MAX_MSG_LEN - cnt, |
| status.num_init_prot ? "\n" : "None\n"); |
| cnt += nbytes; |
| |
| nbytes = scnprintf(dbg_buff + cnt, IPA_USB_MAX_MSG_LEN - cnt, |
| "Protocols in Connected State: "); |
| cnt += nbytes; |
| if (status.teth_connected_prot) { |
| nbytes = scnprintf(dbg_buff + cnt, |
| IPA_USB_MAX_MSG_LEN - cnt, |
| "%s ", status.teth_connected_prot); |
| cnt += nbytes; |
| } |
| if (status.dpl_connected_prot) { |
| nbytes = scnprintf(dbg_buff + cnt, |
| IPA_USB_MAX_MSG_LEN - cnt, |
| "%s ", status.dpl_connected_prot); |
| cnt += nbytes; |
| } |
| nbytes = scnprintf(dbg_buff + cnt, IPA_USB_MAX_MSG_LEN - cnt, |
| (status.teth_connected_prot || |
| status.dpl_connected_prot) ? "\n" : "None\n"); |
| cnt += nbytes; |
| |
| nbytes = scnprintf(dbg_buff + cnt, IPA_USB_MAX_MSG_LEN - cnt, |
| "USB Tethering Consumer State: %s\n", |
| status.teth_cons_state ? |
| status.teth_cons_state : "Invalid"); |
| cnt += nbytes; |
| |
| nbytes = scnprintf(dbg_buff + cnt, IPA_USB_MAX_MSG_LEN - cnt, |
| "DPL Consumer State: %s\n", |
| status.dpl_cons_state ? status.dpl_cons_state : |
| "Invalid"); |
| cnt += nbytes; |
| } |
| |
| return simple_read_from_buffer(ubuf, count, ppos, dbg_buff, cnt); |
| } |
| |
| const struct file_operations ipa3_ipa_usb_ops = { |
| .read = ipa3_read_usb_state_info, |
| }; |
| |
| static void ipa_usb_debugfs_init(void) |
| { |
| const mode_t read_only_mode = S_IRUSR | S_IRGRP | S_IROTH; |
| |
| ipa3_usb_ctx->dent = debugfs_create_dir("ipa_usb", 0); |
| if (IS_ERR(ipa3_usb_ctx->dent)) { |
| pr_err("fail to create folder in debug_fs.\n"); |
| return; |
| } |
| |
| ipa3_usb_ctx->dfile_state_info = debugfs_create_file("state_info", |
| read_only_mode, ipa3_usb_ctx->dent, 0, |
| &ipa3_ipa_usb_ops); |
| if (!ipa3_usb_ctx->dfile_state_info || |
| IS_ERR(ipa3_usb_ctx->dfile_state_info)) { |
| pr_err("failed to create file for state_info\n"); |
| goto fail; |
| } |
| |
| return; |
| |
| fail: |
| debugfs_remove_recursive(ipa3_usb_ctx->dent); |
| ipa3_usb_ctx->dent = NULL; |
| } |
| |
| static void ipa_usb_debugfs_remove(void) |
| { |
| if (IS_ERR(ipa3_usb_ctx->dent)) { |
| IPA_USB_ERR("ipa_usb debugfs folder was not created.\n"); |
| return; |
| } |
| |
| debugfs_remove_recursive(ipa3_usb_ctx->dent); |
| } |
| #else /* CONFIG_DEBUG_FS */ |
| static void ipa_usb_debugfs_init(void){} |
| static void ipa_usb_debugfs_remove(void){} |
| #endif /* CONFIG_DEBUG_FS */ |
| |
| static int ipa_usb_set_lock_unlock(bool is_lock) |
| { |
| IPA_USB_DBG("entry\n"); |
| if (is_lock) |
| mutex_lock(&ipa3_usb_ctx->general_mutex); |
| else |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| IPA_USB_DBG("exit\n"); |
| |
| return 0; |
| } |
| |
| |
| |
| int ipa_usb_xdci_connect(struct ipa_usb_xdci_chan_params *ul_chan_params, |
| struct ipa_usb_xdci_chan_params *dl_chan_params, |
| struct ipa_req_chan_out_params *ul_out_params, |
| struct ipa_req_chan_out_params *dl_out_params, |
| struct ipa_usb_xdci_connect_params *connect_params) |
| { |
| int result = -EFAULT; |
| struct ipa_usb_xdci_connect_params_internal conn_params; |
| |
| mutex_lock(&ipa3_usb_ctx->general_mutex); |
| IPA_USB_DBG_LOW("entry\n"); |
| if (connect_params == NULL || dl_chan_params == NULL || |
| dl_out_params == NULL || |
| (connect_params->teth_prot != IPA_USB_DIAG && |
| (ul_chan_params == NULL || ul_out_params == NULL))) { |
| IPA_USB_ERR("bad parameters.\n"); |
| result = -EINVAL; |
| goto bad_params; |
| } |
| |
| if (connect_params->teth_prot != IPA_USB_DIAG) { |
| result = ipa3_usb_request_xdci_channel(ul_chan_params, |
| IPA_USB_DIR_UL, ul_out_params); |
| if (result) { |
| IPA_USB_ERR("failed to allocate UL channel.\n"); |
| goto bad_params; |
| } |
| } |
| |
| result = ipa3_usb_request_xdci_channel(dl_chan_params, IPA_USB_DIR_DL, |
| dl_out_params); |
| if (result) { |
| IPA_USB_ERR("failed to allocate DL/DPL channel.\n"); |
| goto alloc_dl_chan_fail; |
| } |
| |
| memset(&conn_params, 0, |
| sizeof(struct ipa_usb_xdci_connect_params_internal)); |
| conn_params.max_pkt_size = connect_params->max_pkt_size; |
| conn_params.ipa_to_usb_clnt_hdl = dl_out_params->clnt_hdl; |
| conn_params.ipa_to_usb_xferrscidx = |
| connect_params->ipa_to_usb_xferrscidx; |
| conn_params.ipa_to_usb_xferrscidx_valid = |
| connect_params->ipa_to_usb_xferrscidx_valid; |
| if (connect_params->teth_prot != IPA_USB_DIAG) { |
| conn_params.usb_to_ipa_clnt_hdl = ul_out_params->clnt_hdl; |
| conn_params.usb_to_ipa_xferrscidx = |
| connect_params->usb_to_ipa_xferrscidx; |
| conn_params.usb_to_ipa_xferrscidx_valid = |
| connect_params->usb_to_ipa_xferrscidx_valid; |
| } |
| conn_params.teth_prot = connect_params->teth_prot; |
| conn_params.teth_prot_params = connect_params->teth_prot_params; |
| conn_params.max_supported_bandwidth_mbps = |
| connect_params->max_supported_bandwidth_mbps; |
| result = ipa3_usb_xdci_connect_internal(&conn_params); |
| if (result) { |
| IPA_USB_ERR("failed to connect.\n"); |
| goto connect_fail; |
| } |
| |
| /* |
| * Register for xdci lock/unlock callback with ipa core driver. |
| * As per use case, only register for IPA_CONS end point for now. |
| * If needed we can include the same for IPA_PROD ep. |
| * For IPA_USB_DIAG/DPL config there will not be any UL ep. |
| */ |
| if (connect_params->teth_prot != IPA_USB_DIAG) |
| ipa3_register_lock_unlock_callback(&ipa_usb_set_lock_unlock, |
| ul_out_params->clnt_hdl); |
| |
| IPA_USB_DBG_LOW("exit\n"); |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return 0; |
| |
| connect_fail: |
| ipa3_usb_release_xdci_channel(dl_out_params->clnt_hdl, IPA_USB_DIR_DL, |
| IPA3_USB_GET_TTYPE(dl_chan_params->teth_prot)); |
| alloc_dl_chan_fail: |
| if (connect_params->teth_prot != IPA_USB_DIAG) |
| ipa3_usb_release_xdci_channel(ul_out_params->clnt_hdl, |
| IPA_USB_DIR_UL, |
| IPA3_USB_GET_TTYPE(ul_chan_params->teth_prot)); |
| bad_params: |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return result; |
| } |
| EXPORT_SYMBOL(ipa_usb_xdci_connect); |
| |
| static int ipa3_usb_check_disconnect_prot(enum ipa_usb_teth_prot teth_prot) |
| { |
| if (teth_prot < 0 || teth_prot >= IPA_USB_MAX_TETH_PROT_SIZE) { |
| IPA_USB_ERR("bad parameter.\n"); |
| return -EFAULT; |
| } |
| |
| if (ipa3_usb_ctx->teth_prot_ctx[teth_prot].state != |
| IPA_USB_TETH_PROT_CONNECTED) { |
| IPA_USB_ERR("%s is not connected.\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| return -EFAULT; |
| } |
| |
| return 0; |
| } |
| |
| /* Assumes lock already acquired */ |
| static int ipa_usb_xdci_dismiss_channels(u32 ul_clnt_hdl, u32 dl_clnt_hdl, |
| enum ipa_usb_teth_prot teth_prot) |
| { |
| int result = 0; |
| enum ipa3_usb_transport_type ttype; |
| |
| ttype = IPA3_USB_GET_TTYPE(teth_prot); |
| |
| IPA_USB_DBG_LOW("entry\n"); |
| |
| /* Reset DL channel */ |
| result = ipa3_reset_gsi_channel(dl_clnt_hdl); |
| if (result) { |
| IPA_USB_ERR("failed to reset DL channel.\n"); |
| return result; |
| } |
| |
| /* Reset DL event ring */ |
| result = ipa3_reset_gsi_event_ring(dl_clnt_hdl); |
| if (result) { |
| IPA_USB_ERR("failed to reset DL event ring.\n"); |
| return result; |
| } |
| |
| if (!IPA3_USB_IS_TTYPE_DPL(ttype)) { |
| ipa3_xdci_ep_delay_rm(ul_clnt_hdl); /* Remove ep_delay if set */ |
| /* Reset UL channel */ |
| result = ipa3_reset_gsi_channel(ul_clnt_hdl); |
| if (result) { |
| IPA_USB_ERR("failed to reset UL channel.\n"); |
| return result; |
| } |
| |
| /* Reset UL event ring */ |
| result = ipa3_reset_gsi_event_ring(ul_clnt_hdl); |
| if (result) { |
| IPA_USB_ERR("failed to reset UL event ring.\n"); |
| return result; |
| } |
| } |
| |
| /* |
| * Deregister for xdci lock/unlock callback from ipa core driver. |
| * As per use case, only deregister for IPA_CONS end point for now. |
| * If needed we can include the same for IPA_PROD ep. |
| * For IPA_USB_DIAG/DPL config there will not be any UL config. |
| */ |
| if (!IPA3_USB_IS_TTYPE_DPL(ttype)) |
| ipa3_deregister_lock_unlock_callback(ul_clnt_hdl); |
| |
| /* Change state to STOPPED */ |
| if (!ipa3_usb_set_state(IPA_USB_STOPPED, false, ttype)) |
| IPA_USB_ERR("failed to change state to stopped\n"); |
| |
| if (!IPA3_USB_IS_TTYPE_DPL(ttype)) { |
| result = ipa3_usb_release_xdci_channel(ul_clnt_hdl, |
| IPA_USB_DIR_UL, ttype); |
| if (result) { |
| IPA_USB_ERR("failed to release UL channel.\n"); |
| return result; |
| } |
| } |
| |
| result = ipa3_usb_release_xdci_channel(dl_clnt_hdl, |
| IPA_USB_DIR_DL, ttype); |
| if (result) { |
| IPA_USB_ERR("failed to release DL channel.\n"); |
| return result; |
| } |
| |
| IPA_USB_DBG_LOW("exit\n"); |
| |
| return 0; |
| } |
| |
| int ipa_usb_xdci_disconnect(u32 ul_clnt_hdl, u32 dl_clnt_hdl, |
| enum ipa_usb_teth_prot teth_prot) |
| { |
| int result = 0; |
| struct ipa_ep_cfg_holb holb_cfg; |
| unsigned long flags; |
| enum ipa3_usb_state orig_state; |
| enum ipa3_usb_transport_type ttype; |
| |
| mutex_lock(&ipa3_usb_ctx->general_mutex); |
| IPA_USB_DBG_LOW("entry\n"); |
| |
| ttype = IPA3_USB_GET_TTYPE(teth_prot); |
| |
| if (!ipa3_usb_check_legal_op(IPA_USB_OP_DISCONNECT, ttype)) { |
| IPA_USB_ERR("Illegal operation.\n"); |
| result = -EPERM; |
| goto bad_params; |
| } |
| |
| spin_lock_irqsave(&ipa3_usb_ctx->state_lock, flags); |
| if (ipa3_usb_ctx->ttype_ctx[ttype].state == |
| IPA_USB_SUSPENDED_NO_RWAKEUP) { |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, flags); |
| result = ipa_usb_xdci_dismiss_channels(ul_clnt_hdl, dl_clnt_hdl, |
| teth_prot); |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return result; |
| } |
| |
| if (ipa3_usb_check_disconnect_prot(teth_prot)) { |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, flags); |
| result = -EINVAL; |
| goto bad_params; |
| } |
| |
| if (ipa3_usb_ctx->ttype_ctx[ttype].state != IPA_USB_SUSPENDED) { |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, flags); |
| /* Stop DL/DPL channel */ |
| result = ipa3_xdci_disconnect(dl_clnt_hdl, false, -1); |
| if (result) { |
| IPA_USB_ERR("failed to disconnect DL/DPL channel.\n"); |
| goto bad_params; |
| } |
| } else { |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, flags); |
| memset(&holb_cfg, 0, sizeof(holb_cfg)); |
| holb_cfg.en = IPA_HOLB_TMR_EN; |
| holb_cfg.tmr_val = 0; |
| ipa3_cfg_ep_holb(dl_clnt_hdl, &holb_cfg); |
| } |
| |
| spin_lock_irqsave(&ipa3_usb_ctx->state_lock, flags); |
| orig_state = ipa3_usb_ctx->ttype_ctx[ttype].state; |
| if (!IPA3_USB_IS_TTYPE_DPL(ttype)) { |
| if (orig_state != IPA_USB_SUSPENDED) { |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, |
| flags); |
| /* Stop UL channel */ |
| result = ipa3_xdci_disconnect(ul_clnt_hdl, |
| true, |
| ipa3_usb_ctx->qmi_req_id); |
| if (result) { |
| IPA_USB_ERR("failed disconnect UL channel\n"); |
| goto bad_params; |
| } |
| ipa3_usb_ctx->qmi_req_id++; |
| } else |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, |
| flags); |
| } else |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, flags); |
| |
| result = ipa_usb_xdci_dismiss_channels(ul_clnt_hdl, dl_clnt_hdl, |
| teth_prot); |
| if (result) |
| goto bad_params; |
| |
| /* Disconnect tethering protocol */ |
| result = ipa3_usb_disconnect_teth_prot(teth_prot); |
| if (result) |
| goto bad_params; |
| |
| if (orig_state != IPA_USB_SUSPENDED) { |
| if (ipa_pm_is_used()) |
| result = ipa_pm_deactivate_sync( |
| ipa3_usb_ctx->ttype_ctx[ttype].pm_ctx.hdl); |
| else |
| result = ipa3_usb_release_prod(ttype); |
| if (result) { |
| IPA_USB_ERR("failed to release PROD.\n"); |
| goto bad_params; |
| } |
| } |
| |
| IPA_USB_DBG_LOW("exit\n"); |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return 0; |
| |
| bad_params: |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return result; |
| |
| } |
| EXPORT_SYMBOL(ipa_usb_xdci_disconnect); |
| |
| int ipa_usb_deinit_teth_prot(enum ipa_usb_teth_prot teth_prot) |
| { |
| int result = -EFAULT; |
| enum ipa3_usb_transport_type ttype; |
| |
| mutex_lock(&ipa3_usb_ctx->general_mutex); |
| IPA_USB_DBG_LOW("entry\n"); |
| if (teth_prot < 0 || teth_prot >= IPA_USB_MAX_TETH_PROT_SIZE) { |
| IPA_USB_ERR("bad parameters.\n"); |
| result = -EINVAL; |
| goto bad_params; |
| } |
| |
| ttype = IPA3_USB_GET_TTYPE(teth_prot); |
| |
| if (!ipa3_usb_check_legal_op(IPA_USB_OP_DEINIT_TETH_PROT, ttype)) { |
| IPA_USB_ERR("Illegal operation.\n"); |
| result = -EPERM; |
| goto bad_params; |
| } |
| |
| /* Clean-up tethering protocol */ |
| switch (teth_prot) { |
| case IPA_USB_RNDIS: |
| case IPA_USB_ECM: |
| if (ipa3_usb_ctx->teth_prot_ctx[teth_prot].state != |
| IPA_USB_TETH_PROT_INITIALIZED) { |
| IPA_USB_ERR("%s is not initialized\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| result = -EINVAL; |
| goto bad_params; |
| } |
| if (teth_prot == IPA_USB_RNDIS) |
| rndis_ipa_cleanup( |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot]. |
| teth_prot_params.rndis.private); |
| else |
| ecm_ipa_cleanup( |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot]. |
| teth_prot_params.ecm.private); |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].user_data = NULL; |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].state = |
| IPA_USB_TETH_PROT_INVALID; |
| ipa3_usb_ctx->num_init_prot--; |
| IPA_USB_DBG("deinitialized %s\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| break; |
| case IPA_USB_RMNET: |
| case IPA_USB_MBIM: |
| if (ipa3_usb_ctx->teth_prot_ctx[teth_prot].state != |
| IPA_USB_TETH_PROT_INITIALIZED) { |
| IPA_USB_ERR("%s (%s) is not initialized\n", |
| ipa3_usb_teth_prot_to_string(teth_prot), |
| ipa3_usb_teth_bridge_prot_to_string(teth_prot)); |
| result = -EINVAL; |
| goto bad_params; |
| } |
| |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].user_data = |
| NULL; |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].state = |
| IPA_USB_TETH_PROT_INVALID; |
| ipa3_usb_ctx->num_init_prot--; |
| IPA_USB_DBG("deinitialized %s (%s)\n", |
| ipa3_usb_teth_prot_to_string(teth_prot), |
| ipa3_usb_teth_bridge_prot_to_string(teth_prot)); |
| break; |
| case IPA_USB_DIAG: |
| if (ipa3_usb_ctx->teth_prot_ctx[teth_prot].state != |
| IPA_USB_TETH_PROT_INITIALIZED) { |
| IPA_USB_ERR("%s is not initialized\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| result = -EINVAL; |
| goto bad_params; |
| } |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].user_data = |
| NULL; |
| ipa3_usb_ctx->teth_prot_ctx[teth_prot].state = |
| IPA_USB_TETH_PROT_INVALID; |
| IPA_USB_DBG("deinitialized %s\n", |
| ipa3_usb_teth_prot_to_string(teth_prot)); |
| break; |
| default: |
| IPA_USB_ERR("unexpected tethering protocol\n"); |
| result = -EINVAL; |
| goto bad_params; |
| } |
| |
| if (IPA3_USB_IS_TTYPE_DPL(ttype) || |
| (ipa3_usb_ctx->num_init_prot == 0)) { |
| if (!ipa3_usb_set_state(IPA_USB_INVALID, false, ttype)) |
| IPA_USB_ERR( |
| "failed to change state to invalid\n"); |
| if (ipa_pm_is_used()) { |
| ipa3_usb_deregister_pm(ttype); |
| ipa3_usb_ctx->ttype_ctx[ttype].ipa_usb_notify_cb = NULL; |
| } else { |
| ipa_rm_delete_resource( |
| ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx.prod_params.name); |
| ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx.prod_valid = |
| false; |
| ipa_rm_delete_resource( |
| ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx.cons_params.name); |
| ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx.cons_valid = |
| false; |
| ipa3_usb_ctx->ttype_ctx[ttype].ipa_usb_notify_cb = NULL; |
| } |
| } |
| |
| IPA_USB_DBG_LOW("exit\n"); |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return 0; |
| |
| bad_params: |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return result; |
| } |
| EXPORT_SYMBOL(ipa_usb_deinit_teth_prot); |
| |
| /* Assumes lock already acquired */ |
| static int ipa3_usb_suspend_no_remote_wakeup(u32 ul_clnt_hdl, u32 dl_clnt_hdl, |
| enum ipa_usb_teth_prot teth_prot) |
| { |
| int result = 0; |
| enum ipa3_usb_transport_type ttype; |
| |
| ttype = IPA3_USB_GET_TTYPE(teth_prot); |
| |
| if (!ipa3_usb_check_legal_op(IPA_USB_OP_SUSPEND_NO_RWAKEUP, ttype)) { |
| IPA_USB_ERR("Illegal operation.\n"); |
| result = -EPERM; |
| goto fail_exit; |
| } |
| |
| IPA_USB_DBG("Start suspend with no remote wakeup sequence: %s\n", |
| IPA3_USB_IS_TTYPE_DPL(ttype) ? |
| "DPL channel":"Data Tethering channels"); |
| |
| if (ipa3_usb_check_disconnect_prot(teth_prot)) { |
| result = -EINVAL; |
| goto fail_exit; |
| } |
| |
| /* Stop DL/DPL channel */ |
| result = ipa3_xdci_disconnect(dl_clnt_hdl, false, -1); |
| if (result) { |
| IPA_USB_ERR("failed to disconnect DL/DPL channel.\n"); |
| goto fail_exit; |
| } |
| |
| if (!IPA3_USB_IS_TTYPE_DPL(ttype)) { |
| /* Stop UL channel */ |
| result = ipa3_xdci_disconnect(ul_clnt_hdl, true, |
| ipa3_usb_ctx->qmi_req_id); |
| if (result) { |
| IPA_USB_ERR("failed disconnect UL channel\n"); |
| goto start_dl; |
| } |
| ipa3_usb_ctx->qmi_req_id++; |
| } |
| |
| /* Disconnect tethering protocol */ |
| result = ipa3_usb_disconnect_teth_prot(teth_prot); |
| if (result) |
| goto start_ul; |
| |
| if (ipa_pm_is_used()) |
| result = ipa_pm_deactivate_sync( |
| ipa3_usb_ctx->ttype_ctx[ttype].pm_ctx.hdl); |
| else |
| result = ipa3_usb_release_prod(ttype); |
| if (result) { |
| IPA_USB_ERR("failed to release PROD.\n"); |
| goto connect_teth; |
| } |
| |
| /* Change ipa_usb state to SUSPENDED_NO_RWAKEUP */ |
| if (!ipa3_usb_set_state(IPA_USB_SUSPENDED_NO_RWAKEUP, false, ttype)) |
| IPA_USB_ERR("failed to change state to suspend no rwakeup\n"); |
| |
| IPA_USB_DBG_LOW("exit\n"); |
| return 0; |
| |
| connect_teth: |
| (void)ipa3_usb_connect_teth_prot(teth_prot); |
| start_ul: |
| if (!IPA3_USB_IS_TTYPE_DPL(ttype)) |
| (void)ipa3_xdci_connect(ul_clnt_hdl); |
| start_dl: |
| (void)ipa3_xdci_connect(dl_clnt_hdl); |
| fail_exit: |
| return result; |
| } |
| |
| int ipa_usb_xdci_suspend(u32 ul_clnt_hdl, u32 dl_clnt_hdl, |
| enum ipa_usb_teth_prot teth_prot, bool with_remote_wakeup) |
| { |
| int result = 0; |
| unsigned long flags; |
| enum ipa3_usb_transport_type ttype; |
| |
| mutex_lock(&ipa3_usb_ctx->general_mutex); |
| IPA_USB_DBG_LOW("entry\n"); |
| |
| if (teth_prot < 0 || teth_prot >= IPA_USB_MAX_TETH_PROT_SIZE) { |
| IPA_USB_ERR("bad parameters.\n"); |
| result = -EINVAL; |
| goto bad_params; |
| } |
| |
| if (!with_remote_wakeup) { |
| result = ipa3_usb_suspend_no_remote_wakeup(ul_clnt_hdl, |
| dl_clnt_hdl, teth_prot); |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return result; |
| } |
| |
| ttype = IPA3_USB_GET_TTYPE(teth_prot); |
| |
| if (!ipa3_usb_check_legal_op(IPA_USB_OP_SUSPEND, ttype)) { |
| IPA_USB_ERR("Illegal operation.\n"); |
| result = -EPERM; |
| goto bad_params; |
| } |
| |
| IPA_USB_DBG("Start suspend sequence: %s\n", |
| IPA3_USB_IS_TTYPE_DPL(ttype) ? |
| "DPL channel":"Data Tethering channels"); |
| |
| /* Change state to SUSPEND_REQUESTED */ |
| if (!ipa3_usb_set_state(IPA_USB_SUSPEND_REQUESTED, false, ttype)) { |
| IPA_USB_ERR( |
| "fail changing state to suspend_req.\n"); |
| result = -EFAULT; |
| goto bad_params; |
| } |
| |
| /* Stop UL channel & suspend DL/DPL EP */ |
| result = ipa3_xdci_suspend(ul_clnt_hdl, dl_clnt_hdl, |
| true, |
| ipa3_usb_ctx->qmi_req_id, IPA3_USB_IS_TTYPE_DPL(ttype)); |
| if (result) { |
| IPA_USB_ERR("failed to suspend\n"); |
| goto suspend_fail; |
| } |
| ipa3_usb_ctx->qmi_req_id++; |
| |
| if (ipa_pm_is_used()) |
| result = ipa_pm_deactivate_sync( |
| ipa3_usb_ctx->ttype_ctx[ttype].pm_ctx.hdl); |
| else |
| result = ipa3_usb_release_prod(ttype); |
| if (result) { |
| IPA_USB_ERR("failed to release PROD\n"); |
| goto release_prod_fail; |
| } |
| |
| /* Check if DL/DPL data pending */ |
| spin_lock_irqsave(&ipa3_usb_ctx->state_lock, flags); |
| if (ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx.cons_state == |
| IPA_USB_CONS_GRANTED && |
| ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx.cons_requested) { |
| |
| IPA_USB_DBG("DL/DPL data pending, invoke remote wakeup\n"); |
| queue_work(ipa3_usb_ctx->wq, |
| IPA3_USB_IS_TTYPE_DPL(ttype) ? |
| &ipa3_usb_dpl_notify_remote_wakeup_work : |
| &ipa3_usb_notify_remote_wakeup_work); |
| } |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, flags); |
| |
| /* Change state to SUSPENDED */ |
| if (!ipa3_usb_set_state(IPA_USB_SUSPENDED, false, ttype)) |
| IPA_USB_ERR("failed to change state to suspended\n"); |
| |
| /* Check if DL/DPL data pending */ |
| spin_lock_irqsave(&ipa3_usb_ctx->state_lock, flags); |
| if (ipa3_usb_ctx->ttype_ctx[ttype].rm_ctx.cons_requested) { |
| IPA_USB_DBG_LOW( |
| "DL/DPL data is pending, invoking remote wakeup\n"); |
| queue_work(ipa3_usb_ctx->wq, IPA3_USB_IS_TTYPE_DPL(ttype) ? |
| &ipa3_usb_dpl_notify_remote_wakeup_work : |
| &ipa3_usb_notify_remote_wakeup_work); |
| } |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, flags); |
| |
| IPA_USB_DBG_LOW("exit\n"); |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return 0; |
| |
| release_prod_fail: |
| ipa3_xdci_resume(ul_clnt_hdl, dl_clnt_hdl, |
| IPA3_USB_IS_TTYPE_DPL(ttype)); |
| suspend_fail: |
| /* Change state back to CONNECTED */ |
| if (!ipa3_usb_set_state(IPA_USB_CONNECTED, true, ttype)) |
| IPA_USB_ERR("failed to change state back to connected\n"); |
| bad_params: |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return result; |
| } |
| EXPORT_SYMBOL(ipa_usb_xdci_suspend); |
| |
| /* Assumes lock already acquired */ |
| static int ipa3_usb_resume_no_remote_wakeup(u32 ul_clnt_hdl, u32 dl_clnt_hdl, |
| enum ipa_usb_teth_prot teth_prot) |
| { |
| int result = -EFAULT; |
| enum ipa3_usb_transport_type ttype; |
| |
| ttype = IPA3_USB_GET_TTYPE(teth_prot); |
| |
| IPA_USB_DBG("Start resume with no remote wakeup sequence: %s\n", |
| IPA3_USB_IS_TTYPE_DPL(ttype) ? |
| "DPL channel":"Data Tethering channels"); |
| |
| /* Request USB_PROD */ |
| if (ipa_pm_is_used()) |
| result = ipa_pm_activate_sync( |
| ipa3_usb_ctx->ttype_ctx[ttype].pm_ctx.hdl); |
| else |
| result = ipa3_usb_request_prod(ttype); |
| if (result) |
| goto fail_exit; |
| |
| /* Connect tethering protocol */ |
| result = ipa3_usb_connect_teth_prot(teth_prot); |
| if (result) { |
| IPA_USB_ERR("failed to connect teth protocol\n"); |
| goto release_prod; |
| } |
| |
| if (!IPA3_USB_IS_TTYPE_DPL(ttype)) { |
| /* Start UL channel */ |
| result = ipa3_xdci_connect(ul_clnt_hdl); |
| if (result) { |
| IPA_USB_ERR("failed to start UL channel.\n"); |
| goto disconn_teth; |
| } |
| } |
| |
| /* Start DL/DPL channel */ |
| result = ipa3_xdci_connect(dl_clnt_hdl); |
| if (result) { |
| IPA_USB_ERR("failed to start DL/DPL channel.\n"); |
| goto stop_ul; |
| } |
| |
| /* Change state to CONNECTED */ |
| if (!ipa3_usb_set_state(IPA_USB_CONNECTED, false, ttype)) { |
| IPA_USB_ERR("failed to change state to connected\n"); |
| result = -EFAULT; |
| goto stop_dl; |
| } |
| |
| return 0; |
| |
| stop_dl: |
| (void)ipa3_xdci_disconnect(dl_clnt_hdl, false, -1); |
| stop_ul: |
| if (!IPA3_USB_IS_TTYPE_DPL(ttype)) { |
| (void)ipa3_xdci_disconnect(ul_clnt_hdl, true, |
| ipa3_usb_ctx->qmi_req_id); |
| ipa3_usb_ctx->qmi_req_id++; |
| } |
| disconn_teth: |
| (void)ipa3_usb_disconnect_teth_prot(teth_prot); |
| release_prod: |
| if (ipa_pm_is_used()) |
| (void)ipa_pm_deactivate_sync( |
| ipa3_usb_ctx->ttype_ctx[ttype].pm_ctx.hdl); |
| else |
| (void)ipa3_usb_release_prod(ttype); |
| fail_exit: |
| return result; |
| } |
| |
| int ipa_usb_xdci_resume(u32 ul_clnt_hdl, u32 dl_clnt_hdl, |
| enum ipa_usb_teth_prot teth_prot) |
| { |
| int result = -EFAULT; |
| enum ipa3_usb_state prev_state; |
| unsigned long flags; |
| enum ipa3_usb_transport_type ttype; |
| |
| mutex_lock(&ipa3_usb_ctx->general_mutex); |
| IPA_USB_DBG_LOW("entry\n"); |
| |
| if (teth_prot < 0 || teth_prot >= IPA_USB_MAX_TETH_PROT_SIZE) { |
| IPA_USB_ERR("bad parameters.\n"); |
| result = -EINVAL; |
| goto bad_params; |
| } |
| |
| ttype = IPA3_USB_GET_TTYPE(teth_prot); |
| |
| if (!ipa3_usb_check_legal_op(IPA_USB_OP_RESUME, ttype)) { |
| IPA_USB_ERR("Illegal operation.\n"); |
| result = -EPERM; |
| goto bad_params; |
| } |
| |
| spin_lock_irqsave(&ipa3_usb_ctx->state_lock, flags); |
| prev_state = ipa3_usb_ctx->ttype_ctx[ttype].state; |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, flags); |
| if (prev_state == IPA_USB_SUSPENDED_NO_RWAKEUP) { |
| result = ipa3_usb_resume_no_remote_wakeup(ul_clnt_hdl, |
| dl_clnt_hdl, teth_prot); |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return result; |
| } |
| |
| IPA_USB_DBG("Start resume sequence: %s\n", |
| IPA3_USB_IS_TTYPE_DPL(ttype) ? |
| "DPL channel" : "Data Tethering channels"); |
| |
| /* Change state to RESUME_IN_PROGRESS */ |
| if (!ipa3_usb_set_state(IPA_USB_RESUME_IN_PROGRESS, false, ttype)) { |
| IPA_USB_ERR("failed to change state to resume_in_progress\n"); |
| result = -EFAULT; |
| goto bad_params; |
| } |
| |
| /* Request USB_PROD */ |
| if (ipa_pm_is_used()) |
| result = ipa_pm_activate_sync( |
| ipa3_usb_ctx->ttype_ctx[ttype].pm_ctx.hdl); |
| else |
| result = ipa3_usb_request_prod(ttype); |
| if (result) |
| goto prod_req_fail; |
| |
| if (!IPA3_USB_IS_TTYPE_DPL(ttype)) { |
| /* Start UL channel */ |
| result = ipa3_start_gsi_channel(ul_clnt_hdl); |
| if (result) { |
| IPA_USB_ERR("failed to start UL channel.\n"); |
| goto start_ul_fail; |
| } |
| } |
| |
| /* Start DL/DPL channel */ |
| result = ipa3_start_gsi_channel(dl_clnt_hdl); |
| if (result) { |
| IPA_USB_ERR("failed to start DL/DPL channel.\n"); |
| goto start_dl_fail; |
| } |
| |
| /* Change state to CONNECTED */ |
| if (!ipa3_usb_set_state(IPA_USB_CONNECTED, false, ttype)) { |
| IPA_USB_ERR("failed to change state to connected\n"); |
| result = -EFAULT; |
| goto state_change_connected_fail; |
| } |
| |
| IPA_USB_DBG_LOW("exit\n"); |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return 0; |
| |
| state_change_connected_fail: |
| result = ipa3_stop_gsi_channel(dl_clnt_hdl); |
| if (result) |
| IPA_USB_ERR("Error stopping DL/DPL channel: %d\n", |
| result); |
| start_dl_fail: |
| if (!IPA3_USB_IS_TTYPE_DPL(ttype)) { |
| result = ipa3_stop_gsi_channel(ul_clnt_hdl); |
| if (result) |
| IPA_USB_ERR("Error stopping UL channel: %d\n", result); |
| } |
| start_ul_fail: |
| if (ipa_pm_is_used()) |
| ipa_pm_deactivate_sync( |
| ipa3_usb_ctx->ttype_ctx[ttype].pm_ctx.hdl); |
| else |
| ipa3_usb_release_prod(ttype); |
| prod_req_fail: |
| /* Change state back to prev_state */ |
| if (!ipa3_usb_set_state(prev_state, true, ttype)) |
| IPA_USB_ERR("failed to change state back to %s\n", |
| ipa3_usb_state_to_string(prev_state)); |
| bad_params: |
| mutex_unlock(&ipa3_usb_ctx->general_mutex); |
| return result; |
| } |
| EXPORT_SYMBOL(ipa_usb_xdci_resume); |
| |
| static int __init ipa3_usb_init(void) |
| { |
| int i; |
| unsigned long flags; |
| int res; |
| struct ipa3_usb_pm_context *pm_ctx; |
| |
| pr_debug("entry\n"); |
| ipa3_usb_ctx = kzalloc(sizeof(struct ipa3_usb_context), GFP_KERNEL); |
| if (ipa3_usb_ctx == NULL) { |
| pr_err(":ipa_usb init failed\n"); |
| return -ENOMEM; |
| } |
| memset(ipa3_usb_ctx, 0, sizeof(struct ipa3_usb_context)); |
| |
| for (i = 0; i < IPA_USB_MAX_TETH_PROT_SIZE; i++) |
| ipa3_usb_ctx->teth_prot_ctx[i].state = |
| IPA_USB_TETH_PROT_INVALID; |
| ipa3_usb_ctx->num_init_prot = 0; |
| init_completion(&ipa3_usb_ctx->dev_ready_comp); |
| ipa3_usb_ctx->qmi_req_id = 0; |
| spin_lock_init(&ipa3_usb_ctx->state_lock); |
| ipa3_usb_ctx->dl_data_pending = false; |
| mutex_init(&ipa3_usb_ctx->general_mutex); |
| |
| /* init PM related members */ |
| pm_ctx = &ipa3_usb_ctx->ttype_ctx[IPA_USB_TRANSPORT_TETH].pm_ctx; |
| pm_ctx->hdl = ~0; |
| pm_ctx->remote_wakeup_work = &ipa3_usb_notify_remote_wakeup_work; |
| pm_ctx = &ipa3_usb_ctx->ttype_ctx[IPA_USB_TRANSPORT_DPL].pm_ctx; |
| pm_ctx->hdl = ~0; |
| pm_ctx->remote_wakeup_work = &ipa3_usb_dpl_notify_remote_wakeup_work; |
| |
| for (i = 0; i < IPA_USB_TRANSPORT_MAX; i++) { |
| ipa3_usb_ctx->ttype_ctx[i].rm_ctx.prod_valid = false; |
| ipa3_usb_ctx->ttype_ctx[i].rm_ctx.cons_valid = false; |
| init_completion(&ipa3_usb_ctx->ttype_ctx[i].rm_ctx.prod_comp); |
| ipa3_usb_ctx->ttype_ctx[i].user_data = NULL; |
| } |
| |
| spin_lock_irqsave(&ipa3_usb_ctx->state_lock, flags); |
| for (i = 0; i < IPA_USB_TRANSPORT_MAX; i++) { |
| ipa3_usb_ctx->ttype_ctx[i].state = IPA_USB_INVALID; |
| ipa3_usb_ctx->ttype_ctx[i].rm_ctx.cons_state = |
| IPA_USB_CONS_RELEASED; |
| } |
| spin_unlock_irqrestore(&ipa3_usb_ctx->state_lock, flags); |
| |
| ipa3_usb_ctx->wq = create_singlethread_workqueue("ipa_usb_wq"); |
| if (!ipa3_usb_ctx->wq) { |
| pr_err("failed to create workqueue\n"); |
| res = -EFAULT; |
| goto ipa_usb_workqueue_fail; |
| } |
| |
| ipa_usb_debugfs_init(); |
| |
| pr_info("exit: IPA_USB init success!\n"); |
| |
| return 0; |
| |
| ipa_usb_workqueue_fail: |
| pr_err(":init failed (%d)\n", -res); |
| kfree(ipa3_usb_ctx); |
| return res; |
| } |
| |
| static void ipa3_usb_exit(void) |
| { |
| IPA_USB_DBG_LOW("IPA_USB exit\n"); |
| ipa_usb_debugfs_remove(); |
| kfree(ipa3_usb_ctx); |
| } |
| |
| arch_initcall(ipa3_usb_init); |
| module_exit(ipa3_usb_exit); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("IPA USB client driver"); |