| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. |
| */ |
| |
| #include <linux/rtnetlink.h> |
| #include <linux/soc/qcom/qmi.h> |
| #include <soc/qcom/rmnet_qmi.h> |
| #define CREATE_TRACE_POINTS |
| #include <trace/events/wda.h> |
| #include "qmi_rmnet_i.h" |
| |
| struct wda_qmi_data { |
| void *rmnet_port; |
| struct workqueue_struct *wda_wq; |
| struct work_struct svc_arrive; |
| struct qmi_handle handle; |
| struct sockaddr_qrtr ssctl; |
| struct svc_info svc; |
| int restart_state; |
| }; |
| |
| static void wda_svc_config(struct work_struct *work); |
| /* **************************************************** */ |
| #define WDA_SERVICE_ID_V01 0x1A |
| #define WDA_SERVICE_VERS_V01 0x01 |
| #define WDA_TIMEOUT_JF msecs_to_jiffies(1000) |
| |
| #define QMI_WDA_SET_POWERSAVE_CONFIG_REQ_V01 0x002D |
| #define QMI_WDA_SET_POWERSAVE_CONFIG_RESP_V01 0x002D |
| #define QMI_WDA_SET_POWERSAVE_CONFIG_REQ_V01_MAX_MSG_LEN 18 |
| #define QMI_WDA_SET_POWERSAVE_CONFIG_RESP_V01_MAX_MSG_LEN 14 |
| |
| #define QMI_WDA_SET_POWERSAVE_MODE_REQ_V01 0x002E |
| #define QMI_WDA_SET_POWERSAVE_MODE_RESP_V01 0x002E |
| #define QMI_WDA_SET_POWERSAVE_MODE_REQ_V01_MAX_MSG_LEN 4 |
| #define QMI_WDA_SET_POWERSAVE_MODE_RESP_V01_MAX_MSG_LEN 7 |
| |
| enum wda_powersave_config_mask_enum_v01 { |
| WDA_DATA_POWERSAVE_CONFIG_MASK_ENUM_MIN_ENUM_VAL_V01 = -2147483647, |
| WDA_DATA_POWERSAVE_CONFIG_NOT_SUPPORTED = 0x00, |
| WDA_DATA_POWERSAVE_CONFIG_DL_MARKER_V01 = 0x01, |
| WDA_DATA_POWERSAVE_CONFIG_FLOW_CTL_V01 = 0x02, |
| WDA_DATA_POWERSAVE_CONFIG_ALL_MASK_V01 = 0x7FFFFFFF, |
| WDA_DATA_POWERSAVE_CONFIG_MASK_ENUM_MAX_ENUM_VAL_V01 = 2147483647 |
| }; |
| |
| struct wda_set_powersave_config_req_msg_v01 { |
| /* Mandatory */ |
| struct data_ep_id_type_v01 ep_id; |
| /* Optional */ |
| uint8_t req_data_cfg_valid; |
| enum wda_powersave_config_mask_enum_v01 req_data_cfg; |
| }; |
| |
| struct wda_set_powersave_config_resp_msg_v01 { |
| /* Mandatory */ |
| struct qmi_response_type_v01 resp; |
| /* Optional */ |
| uint8_t data_cfg_valid; |
| enum wda_powersave_config_mask_enum_v01 data_cfg; |
| }; |
| |
| struct wda_set_powersave_mode_req_msg_v01 { |
| /* Mandatory */ |
| uint8_t powersave_control_flag; |
| }; |
| |
| struct wda_set_powersave_mode_resp_msg_v01 { |
| /* Mandatory */ |
| struct qmi_response_type_v01 resp; |
| }; |
| |
| static struct qmi_elem_info wda_set_powersave_config_req_msg_v01_ei[] = { |
| { |
| .data_type = QMI_STRUCT, |
| .elem_len = 1, |
| .elem_size = sizeof(struct data_ep_id_type_v01), |
| .array_type = NO_ARRAY, |
| .tlv_type = 0x01, |
| .offset = offsetof(struct |
| wda_set_powersave_config_req_msg_v01, |
| ep_id), |
| .ei_array = data_ep_id_type_v01_ei, |
| }, |
| { |
| .data_type = QMI_OPT_FLAG, |
| .elem_len = 1, |
| .elem_size = sizeof(uint8_t), |
| .array_type = NO_ARRAY, |
| .tlv_type = 0x10, |
| .offset = offsetof(struct |
| wda_set_powersave_config_req_msg_v01, |
| req_data_cfg_valid), |
| .ei_array = NULL, |
| }, |
| { |
| .data_type = QMI_SIGNED_4_BYTE_ENUM, |
| .elem_len = 1, |
| .elem_size = sizeof(enum |
| wda_powersave_config_mask_enum_v01), |
| .array_type = NO_ARRAY, |
| .tlv_type = 0x10, |
| .offset = offsetof(struct |
| wda_set_powersave_config_req_msg_v01, |
| req_data_cfg), |
| .ei_array = NULL, |
| }, |
| { |
| .data_type = QMI_EOTI, |
| .array_type = NO_ARRAY, |
| .tlv_type = QMI_COMMON_TLV_TYPE, |
| }, |
| }; |
| |
| static struct qmi_elem_info wda_set_powersave_config_resp_msg_v01_ei[] = { |
| { |
| .data_type = QMI_STRUCT, |
| .elem_len = 1, |
| .elem_size = sizeof(struct qmi_response_type_v01), |
| .array_type = NO_ARRAY, |
| .tlv_type = 0x02, |
| .offset = offsetof(struct |
| wda_set_powersave_config_resp_msg_v01, |
| resp), |
| .ei_array = qmi_response_type_v01_ei, |
| }, |
| { |
| .data_type = QMI_OPT_FLAG, |
| .elem_len = 1, |
| .elem_size = sizeof(uint8_t), |
| .array_type = NO_ARRAY, |
| .tlv_type = 0x10, |
| .offset = offsetof(struct |
| wda_set_powersave_config_resp_msg_v01, |
| data_cfg_valid), |
| .ei_array = NULL, |
| }, |
| { |
| .data_type = QMI_SIGNED_4_BYTE_ENUM, |
| .elem_len = 1, |
| .elem_size = sizeof(enum |
| wda_powersave_config_mask_enum_v01), |
| .array_type = NO_ARRAY, |
| .tlv_type = 0x10, |
| .offset = offsetof(struct |
| wda_set_powersave_config_resp_msg_v01, |
| data_cfg), |
| .ei_array = NULL, |
| }, |
| { |
| .data_type = QMI_EOTI, |
| .array_type = NO_ARRAY, |
| .tlv_type = QMI_COMMON_TLV_TYPE, |
| }, |
| }; |
| |
| static struct qmi_elem_info wda_set_powersave_mode_req_msg_v01_ei[] = { |
| { |
| .data_type = QMI_UNSIGNED_1_BYTE, |
| .elem_len = 1, |
| .elem_size = sizeof(uint8_t), |
| .array_type = NO_ARRAY, |
| .tlv_type = 0x01, |
| .offset = offsetof(struct |
| wda_set_powersave_mode_req_msg_v01, |
| powersave_control_flag), |
| .ei_array = NULL, |
| }, |
| { |
| .data_type = QMI_EOTI, |
| .array_type = NO_ARRAY, |
| .tlv_type = QMI_COMMON_TLV_TYPE, |
| }, |
| }; |
| |
| static struct qmi_elem_info wda_set_powersave_mode_resp_msg_v01_ei[] = { |
| { |
| .data_type = QMI_STRUCT, |
| .elem_len = 1, |
| .elem_size = sizeof(struct qmi_response_type_v01), |
| .array_type = NO_ARRAY, |
| .tlv_type = 0x02, |
| .offset = offsetof(struct |
| wda_set_powersave_mode_resp_msg_v01, |
| resp), |
| .ei_array = qmi_response_type_v01_ei, |
| }, |
| { |
| .data_type = QMI_EOTI, |
| .array_type = NO_ARRAY, |
| .tlv_type = QMI_COMMON_TLV_TYPE, |
| }, |
| }; |
| |
| static int wda_set_powersave_mode_req(void *wda_data, uint8_t enable) |
| { |
| struct wda_qmi_data *data = (struct wda_qmi_data *)wda_data; |
| struct wda_set_powersave_mode_resp_msg_v01 *resp; |
| struct wda_set_powersave_mode_req_msg_v01 *req; |
| struct qmi_txn txn; |
| int ret; |
| |
| if (!data || !data->rmnet_port) |
| return -EINVAL; |
| |
| req = kzalloc(sizeof(*req), GFP_ATOMIC); |
| if (!req) |
| return -ENOMEM; |
| |
| resp = kzalloc(sizeof(*resp), GFP_ATOMIC); |
| if (!resp) { |
| kfree(req); |
| return -ENOMEM; |
| } |
| |
| ret = qmi_txn_init(&data->handle, &txn, |
| wda_set_powersave_mode_resp_msg_v01_ei, resp); |
| if (ret < 0) { |
| pr_err("%s() Failed init for response, err: %d\n", |
| __func__, ret); |
| goto out; |
| } |
| |
| req->powersave_control_flag = enable; |
| ret = qmi_send_request(&data->handle, &data->ssctl, &txn, |
| QMI_WDA_SET_POWERSAVE_MODE_REQ_V01, |
| QMI_WDA_SET_POWERSAVE_MODE_REQ_V01_MAX_MSG_LEN, |
| wda_set_powersave_mode_req_msg_v01_ei, req); |
| if (ret < 0) { |
| qmi_txn_cancel(&txn); |
| pr_err("%s() Failed sending request, err: %d\n", |
| __func__, ret); |
| goto out; |
| } |
| |
| ret = qmi_txn_wait(&txn, WDA_TIMEOUT_JF); |
| if (ret < 0) { |
| pr_err("%s() Response waiting failed, err: %d\n", |
| __func__, ret); |
| } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { |
| pr_err("%s() Request rejected, result: %d, err: %d\n", |
| __func__, resp->resp.result, resp->resp.error); |
| ret = -resp->resp.result; |
| } |
| |
| out: |
| kfree(resp); |
| kfree(req); |
| return ret; |
| } |
| |
| static int wda_set_powersave_config_req(struct qmi_handle *wda_handle, |
| int dl_marker) |
| { |
| struct wda_qmi_data *data = container_of(wda_handle, |
| struct wda_qmi_data, handle); |
| struct wda_set_powersave_config_resp_msg_v01 *resp; |
| struct wda_set_powersave_config_req_msg_v01 *req; |
| struct qmi_txn txn; |
| int ret; |
| |
| req = kzalloc(sizeof(*req), GFP_ATOMIC); |
| if (!req) |
| return -ENOMEM; |
| |
| resp = kzalloc(sizeof(*resp), GFP_ATOMIC); |
| if (!resp) { |
| kfree(req); |
| return -ENOMEM; |
| } |
| |
| ret = qmi_txn_init(wda_handle, &txn, |
| wda_set_powersave_config_resp_msg_v01_ei, resp); |
| if (ret < 0) { |
| pr_err("%s() Failed init for response, err: %d\n", |
| __func__, ret); |
| goto out; |
| } |
| |
| req->ep_id.ep_type = data->svc.ep_type; |
| req->ep_id.iface_id = data->svc.iface_id; |
| req->req_data_cfg_valid = 1; |
| req->req_data_cfg = dl_marker ? WDA_DATA_POWERSAVE_CONFIG_ALL_MASK_V01 : |
| WDA_DATA_POWERSAVE_CONFIG_FLOW_CTL_V01; |
| ret = qmi_send_request(wda_handle, &data->ssctl, &txn, |
| QMI_WDA_SET_POWERSAVE_CONFIG_REQ_V01, |
| QMI_WDA_SET_POWERSAVE_CONFIG_REQ_V01_MAX_MSG_LEN, |
| wda_set_powersave_config_req_msg_v01_ei, req); |
| if (ret < 0) { |
| qmi_txn_cancel(&txn); |
| pr_err("%s() Failed sending request, err: %d\n", __func__, ret); |
| goto out; |
| } |
| |
| ret = qmi_txn_wait(&txn, WDA_TIMEOUT_JF); |
| if (ret < 0) { |
| pr_err("%s() Response waiting failed, err: %d\n", |
| __func__, ret); |
| } else if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { |
| pr_err("%s() Request rejected, result: %d, error: %d\n", |
| __func__, resp->resp.result, resp->resp.error); |
| ret = -resp->resp.result; |
| } |
| |
| out: |
| kfree(resp); |
| kfree(req); |
| return ret; |
| } |
| |
| static void wda_svc_config(struct work_struct *work) |
| { |
| struct wda_qmi_data *data = container_of(work, struct wda_qmi_data, |
| svc_arrive); |
| struct qmi_info *qmi; |
| int rc, dl_marker = 0; |
| |
| while (!rtnl_trylock()) { |
| if (!data->restart_state) |
| cond_resched(); |
| else |
| return; |
| } |
| |
| dl_marker = rmnet_get_dlmarker_info(data->rmnet_port); |
| rtnl_unlock(); |
| |
| if (data->restart_state == 1) |
| return; |
| |
| rc = wda_set_powersave_config_req(&data->handle, dl_marker); |
| if (rc < 0) { |
| pr_err("%s Failed to init service, err[%d]\n", __func__, rc); |
| return; |
| } |
| |
| if (data->restart_state == 1) |
| return; |
| while (!rtnl_trylock()) { |
| if (!data->restart_state) |
| cond_resched(); |
| else |
| return; |
| } |
| |
| qmi = (struct qmi_info *)rmnet_get_qmi_pt(data->rmnet_port); |
| if (!qmi) { |
| rtnl_unlock(); |
| return; |
| } |
| |
| qmi->wda_pending = NULL; |
| qmi->wda_client = (void *)data; |
| trace_wda_client_state_up(data->svc.instance, |
| data->svc.ep_type, |
| data->svc.iface_id); |
| |
| rtnl_unlock(); |
| |
| pr_info("Connection established with the WDA Service, DL Marker %s\n", |
| dl_marker ? "enabled" : "disabled"); |
| } |
| |
| static int wda_svc_arrive(struct qmi_handle *qmi, struct qmi_service *svc) |
| { |
| struct wda_qmi_data *data = container_of(qmi, struct wda_qmi_data, |
| handle); |
| |
| data->ssctl.sq_family = AF_QIPCRTR; |
| data->ssctl.sq_node = svc->node; |
| data->ssctl.sq_port = svc->port; |
| |
| queue_work(data->wda_wq, &data->svc_arrive); |
| |
| return 0; |
| } |
| |
| static void wda_svc_exit(struct qmi_handle *qmi, struct qmi_service *svc) |
| { |
| struct wda_qmi_data *data = container_of(qmi, struct wda_qmi_data, |
| handle); |
| |
| if (!data) |
| pr_info("%s() data is null\n", __func__); |
| } |
| |
| static struct qmi_ops server_ops = { |
| .new_server = wda_svc_arrive, |
| .del_server = wda_svc_exit, |
| }; |
| |
| int |
| wda_qmi_client_init(void *port, struct svc_info *psvc, struct qmi_info *qmi) |
| { |
| struct wda_qmi_data *data; |
| int rc = -ENOMEM; |
| |
| if (!port || !qmi) |
| return -EINVAL; |
| |
| data = kzalloc(sizeof(*data), GFP_KERNEL); |
| if (!data) |
| return -ENOMEM; |
| |
| data->wda_wq = create_singlethread_workqueue("wda_wq"); |
| if (!data->wda_wq) { |
| pr_err("%s Could not create workqueue\n", __func__); |
| goto err0; |
| } |
| |
| data->rmnet_port = port; |
| data->restart_state = 0; |
| memcpy(&data->svc, psvc, sizeof(data->svc)); |
| INIT_WORK(&data->svc_arrive, wda_svc_config); |
| |
| rc = qmi_handle_init(&data->handle, |
| QMI_WDA_SET_POWERSAVE_CONFIG_RESP_V01_MAX_MSG_LEN, |
| &server_ops, NULL); |
| if (rc < 0) { |
| pr_err("%s: Failed qmi_handle_init, err: %d\n", __func__, rc); |
| goto err1; |
| } |
| |
| rc = qmi_add_lookup(&data->handle, WDA_SERVICE_ID_V01, |
| WDA_SERVICE_VERS_V01, psvc->instance); |
| if (rc < 0) { |
| pr_err("%s(): Failed qmi_add_lookup, err: %d\n", __func__, rc); |
| goto err2; |
| } |
| |
| qmi->wda_pending = (void *)data; |
| return 0; |
| |
| err2: |
| qmi_handle_release(&data->handle); |
| err1: |
| destroy_workqueue(data->wda_wq); |
| err0: |
| kfree(data); |
| return rc; |
| } |
| |
| void wda_qmi_client_exit(void *wda_data) |
| { |
| struct wda_qmi_data *data = (struct wda_qmi_data *)wda_data; |
| |
| if (!data) { |
| pr_info("%s() data is null\n", __func__); |
| return; |
| } |
| |
| data->restart_state = 1; |
| trace_wda_client_state_down(0); |
| qmi_handle_release(&data->handle); |
| destroy_workqueue(data->wda_wq); |
| kfree(data); |
| } |
| |
| int wda_set_powersave_mode(void *wda_data, uint8_t enable) |
| { |
| trace_wda_set_powersave_mode(enable); |
| return wda_set_powersave_mode_req(wda_data, enable); |
| } |