| /* Copyright (c) 2013, 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/module.h> |
| #include <linux/slab.h> |
| #include <linux/errno.h> |
| #include <linux/delay.h> |
| #include <linux/debugfs.h> |
| #include <linux/qmi_encdec.h> |
| |
| #include <asm/uaccess.h> |
| |
| #include <mach/msm_qmi_interface.h> |
| |
| #include "kernel_test_service_v01.h" |
| |
| #define TEST_SERVICE_SVC_ID 0x0000000f |
| #define TEST_SERVICE_INS_ID 1 |
| |
| static int test_rep_cnt = 10; |
| module_param_named(rep_cnt, test_rep_cnt, int, S_IRUGO | S_IWUSR | S_IWGRP); |
| |
| static int test_data_sz = 50; |
| module_param_named(data_sz, test_data_sz, int, S_IRUGO | S_IWUSR | S_IWGRP); |
| |
| static int test_clnt_debug_mask; |
| module_param_named(debug_mask, test_clnt_debug_mask, |
| int, S_IRUGO | S_IWUSR | S_IWGRP); |
| |
| #define D(x...) do { \ |
| if (test_clnt_debug_mask) \ |
| pr_debug(x); \ |
| } while (0) |
| |
| /* Variable to initiate the test through debugfs interface */ |
| static struct dentry *test_dent; |
| |
| /* Test client port for IPC Router */ |
| static struct qmi_handle *test_clnt; |
| static int test_clnt_reset; |
| |
| /* Reader thread to receive responses & indications */ |
| static void test_clnt_recv_msg(struct work_struct *work); |
| static DECLARE_DELAYED_WORK(work_recv_msg, test_clnt_recv_msg); |
| static void test_clnt_svc_arrive(struct work_struct *work); |
| static DECLARE_DELAYED_WORK(work_svc_arrive, test_clnt_svc_arrive); |
| static void test_clnt_svc_exit(struct work_struct *work); |
| static DECLARE_DELAYED_WORK(work_svc_exit, test_clnt_svc_exit); |
| static struct workqueue_struct *test_clnt_workqueue; |
| |
| /* Variable to hold the test result */ |
| static int test_res; |
| |
| static unsigned int callback_count; |
| static void test_async_resp_cb(struct qmi_handle *handle, |
| unsigned int msg_id, void *msg, |
| void *resp_cb_data, int stat) |
| { |
| callback_count++; |
| if (stat == 0) |
| D("%s invoked %d time(s): [RESP_LEN] = %d, [RESP_VALID] = %d", |
| __func__, callback_count, |
| ((struct test_data_resp_msg_v01 *)msg)->data_len, |
| ((struct test_data_resp_msg_v01 *)msg)->data_valid); |
| else if (stat < 0) |
| pr_err("%s: Request Failed [MSG_ID]: %d, [ERR_ID]: %d, [Callback_count]: %d", |
| __func__, msg_id, stat, callback_count); |
| |
| kfree(msg); |
| kfree(resp_cb_data); |
| } |
| |
| static int test_qmi_ping_pong_send_sync_msg(void) |
| { |
| struct test_ping_req_msg_v01 req; |
| struct test_ping_resp_msg_v01 resp; |
| struct msg_desc req_desc, resp_desc; |
| int rc; |
| |
| memcpy(req.ping, "ping", sizeof(req.ping)); |
| req.client_name_valid = 0; |
| |
| req_desc.max_msg_len = TEST_PING_REQ_MAX_MSG_LEN_V01; |
| req_desc.msg_id = TEST_PING_REQ_MSG_ID_V01; |
| req_desc.ei_array = test_ping_req_msg_v01_ei; |
| |
| resp_desc.max_msg_len = TEST_PING_REQ_MAX_MSG_LEN_V01; |
| resp_desc.msg_id = TEST_PING_REQ_MSG_ID_V01; |
| resp_desc.ei_array = test_ping_resp_msg_v01_ei; |
| |
| rc = qmi_send_req_wait(test_clnt, &req_desc, &req, sizeof(req), |
| &resp_desc, &resp, sizeof(resp), 0); |
| if (rc < 0) { |
| pr_err("%s: send req failed %d\n", __func__, rc); |
| return rc; |
| } |
| |
| D("%s: Received %s response\n", __func__, resp.pong); |
| return rc; |
| } |
| |
| static int test_qmi_data_send_sync_msg(unsigned int data_len) |
| { |
| struct test_data_req_msg_v01 *req; |
| struct test_data_resp_msg_v01 *resp; |
| struct msg_desc req_desc, resp_desc; |
| int rc, i; |
| |
| req = kzalloc(sizeof(struct test_data_req_msg_v01), GFP_KERNEL); |
| if (!req) { |
| pr_err("%s: Data req msg alloc failed\n", __func__); |
| return -ENOMEM; |
| } |
| |
| resp = kzalloc(sizeof(struct test_data_resp_msg_v01), GFP_KERNEL); |
| if (!resp) { |
| pr_err("%s: Data resp msg alloc failed\n", __func__); |
| kfree(req); |
| return -ENOMEM; |
| } |
| |
| req->data_len = data_len; |
| for (i = 0; i < data_len; i = i + sizeof(int)) |
| memcpy(req->data + i, (uint8_t *)&i, sizeof(int)); |
| req->client_name_valid = 0; |
| |
| req_desc.max_msg_len = TEST_DATA_REQ_MAX_MSG_LEN_V01; |
| req_desc.msg_id = TEST_DATA_REQ_MSG_ID_V01; |
| req_desc.ei_array = test_data_req_msg_v01_ei; |
| |
| resp_desc.max_msg_len = TEST_DATA_REQ_MAX_MSG_LEN_V01; |
| resp_desc.msg_id = TEST_DATA_REQ_MSG_ID_V01; |
| resp_desc.ei_array = test_data_resp_msg_v01_ei; |
| |
| rc = qmi_send_req_wait(test_clnt, &req_desc, req, sizeof(*req), |
| &resp_desc, resp, sizeof(*resp), 0); |
| if (rc < 0) { |
| pr_err("%s: send req failed\n", __func__); |
| goto data_send_err; |
| } |
| |
| D("%s: data_valid %d\n", __func__, resp->data_valid); |
| D("%s: data_len %d\n", __func__, resp->data_len); |
| data_send_err: |
| kfree(resp); |
| kfree(req); |
| return rc; |
| } |
| |
| static int test_qmi_data_send_async_msg(unsigned int data_len) |
| { |
| struct test_data_req_msg_v01 *req; |
| struct test_data_resp_msg_v01 *resp; |
| struct msg_desc req_desc, *resp_desc; |
| int rc, i; |
| |
| req = kzalloc(sizeof(struct test_data_req_msg_v01), GFP_KERNEL); |
| if (!req) { |
| pr_err("%s: Data req msg alloc failed\n", __func__); |
| return -ENOMEM; |
| } |
| |
| resp = kzalloc(sizeof(struct test_data_resp_msg_v01), GFP_KERNEL); |
| if (!resp) { |
| pr_err("%s: Data resp msg alloc failed\n", __func__); |
| kfree(req); |
| return -ENOMEM; |
| } |
| |
| resp_desc = kzalloc(sizeof(struct msg_desc), GFP_KERNEL); |
| if (!resp_desc) { |
| pr_err("%s: Resp_desc msg alloc failed\n", __func__); |
| kfree(req); |
| kfree(resp); |
| return -ENOMEM; |
| } |
| |
| req->data_len = data_len; |
| for (i = 0; i < data_len; i = i + sizeof(int)) |
| memcpy(req->data + i, (uint8_t *)&i, sizeof(int)); |
| req->client_name_valid = 0; |
| |
| req_desc.max_msg_len = TEST_DATA_REQ_MAX_MSG_LEN_V01; |
| req_desc.msg_id = TEST_DATA_REQ_MSG_ID_V01; |
| req_desc.ei_array = test_data_req_msg_v01_ei; |
| |
| resp_desc->max_msg_len = TEST_DATA_REQ_MAX_MSG_LEN_V01; |
| resp_desc->msg_id = TEST_DATA_REQ_MSG_ID_V01; |
| resp_desc->ei_array = test_data_resp_msg_v01_ei; |
| |
| rc = qmi_send_req_nowait(test_clnt, &req_desc, req, sizeof(*req), |
| resp_desc, resp, sizeof(*resp), |
| test_async_resp_cb, (void *)resp_desc); |
| if (rc < 0) { |
| pr_err("%s: send req failed\n", __func__); |
| kfree(resp); |
| kfree(resp_desc); |
| } |
| kfree(req); |
| return rc; |
| } |
| |
| static void test_clnt_recv_msg(struct work_struct *work) |
| { |
| int rc; |
| |
| do { |
| D("%s: Notified about a Receive Event", __func__); |
| } while ((rc = qmi_recv_msg(test_clnt)) == 0); |
| |
| if (rc != -ENOMSG) |
| pr_err("%s: Error receiving message\n", __func__); |
| } |
| |
| static void test_clnt_notify(struct qmi_handle *handle, |
| enum qmi_event_type event, void *notify_priv) |
| { |
| switch (event) { |
| case QMI_RECV_MSG: |
| queue_delayed_work(test_clnt_workqueue, |
| &work_recv_msg, 0); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void test_clnt_svc_arrive(struct work_struct *work) |
| { |
| int rc; |
| |
| D("%s begins\n", __func__); |
| |
| /* Create a Local client port for QMI communication */ |
| test_clnt = qmi_handle_create(test_clnt_notify, NULL); |
| if (!test_clnt) { |
| pr_err("%s: QMI client handle alloc failed\n", __func__); |
| return; |
| } |
| |
| D("%s: Lookup server name\n", __func__); |
| rc = qmi_connect_to_service(test_clnt, TEST_SERVICE_SVC_ID, |
| TEST_SERVICE_INS_ID); |
| if (rc < 0) { |
| pr_err("%s: Server not found\n", __func__); |
| qmi_handle_destroy(test_clnt); |
| test_clnt = NULL; |
| return; |
| } |
| test_clnt_reset = 0; |
| D("%s complete\n", __func__); |
| } |
| |
| static void test_clnt_svc_exit(struct work_struct *work) |
| { |
| D("%s begins\n", __func__); |
| |
| qmi_handle_destroy(test_clnt); |
| test_clnt_reset = 1; |
| test_clnt = NULL; |
| |
| D("%s complete\n", __func__); |
| } |
| |
| static int test_clnt_svc_event_notify(struct notifier_block *this, |
| unsigned long code, |
| void *_cmd) |
| { |
| D("%s: event %ld\n", __func__, code); |
| switch (code) { |
| case QMI_SERVER_ARRIVE: |
| queue_delayed_work(test_clnt_workqueue, |
| &work_svc_arrive, 0); |
| break; |
| case QMI_SERVER_EXIT: |
| queue_delayed_work(test_clnt_workqueue, |
| &work_svc_exit, 0); |
| break; |
| default: |
| break; |
| } |
| return 0; |
| } |
| |
| static int test_qmi_open(struct inode *ip, struct file *fp) |
| { |
| if (!test_clnt) { |
| pr_err("%s Test client is not initialized\n", __func__); |
| return -ENODEV; |
| } |
| return 0; |
| } |
| |
| static ssize_t test_qmi_read(struct file *fp, char __user *buf, |
| size_t count, loff_t *pos) |
| { |
| char _buf[16]; |
| snprintf(_buf, sizeof(_buf), "%d\n", test_res); |
| test_res = 0; |
| return simple_read_from_buffer(buf, count, pos, |
| _buf, strnlen(_buf, 16)); |
| } |
| |
| static int test_qmi_release(struct inode *ip, struct file *fp) |
| { |
| return 0; |
| } |
| |
| static ssize_t test_qmi_write(struct file *fp, const char __user *buf, |
| size_t count, loff_t *pos) |
| { |
| unsigned char cmd[64]; |
| int len; |
| int i; |
| |
| if (count < 1) |
| return 0; |
| |
| len = min(count, (sizeof(cmd) - 1)); |
| |
| if (copy_from_user(cmd, buf, len)) |
| return -EFAULT; |
| |
| cmd[len] = 0; |
| if (cmd[len-1] == '\n') { |
| cmd[len-1] = 0; |
| len--; |
| } |
| |
| if (!strncmp(cmd, "ping_pong", sizeof(cmd))) { |
| for (i = 0; i < test_rep_cnt; i++) { |
| test_res = test_qmi_ping_pong_send_sync_msg(); |
| if (test_res == -ENETRESET || test_clnt_reset) { |
| do { |
| msleep(50); |
| } while (test_clnt_reset); |
| } |
| } |
| } else if (!strncmp(cmd, "data", sizeof(cmd))) { |
| for (i = 0; i < test_rep_cnt; i++) { |
| test_res = test_qmi_data_send_sync_msg(test_data_sz); |
| if (test_res == -ENETRESET || test_clnt_reset) { |
| do { |
| msleep(50); |
| } while (test_clnt_reset); |
| } |
| } |
| } else if (!strncmp(cmd, "data_async", sizeof(cmd))) { |
| int i; |
| callback_count = 0; |
| for (i = 0; i < test_rep_cnt; i++) { |
| test_res = test_qmi_data_send_async_msg(test_data_sz); |
| if (test_res == -ENETRESET || test_clnt_reset) { |
| --i; |
| do { |
| msleep(50); |
| } while (test_clnt_reset); |
| } else if (test_res < 0) { |
| --i; |
| pr_err("%s: Error sending txn, aborting now", |
| __func__); |
| break; |
| } |
| } |
| while (callback_count < i) { |
| if (test_clnt_reset) { |
| pr_err("%s: Service Exited", __func__); |
| break; |
| } |
| msleep(50); |
| } |
| D("%s complete\n", __func__); |
| callback_count = 0; |
| } else { |
| test_res = -EINVAL; |
| } |
| return count; |
| } |
| |
| static struct notifier_block test_clnt_nb = { |
| .notifier_call = test_clnt_svc_event_notify, |
| }; |
| |
| static const struct file_operations debug_ops = { |
| .owner = THIS_MODULE, |
| .open = test_qmi_open, |
| .read = test_qmi_read, |
| .write = test_qmi_write, |
| .release = test_qmi_release, |
| }; |
| |
| static int __init test_qmi_init(void) |
| { |
| int rc; |
| |
| test_clnt_workqueue = create_singlethread_workqueue("test_clnt"); |
| if (!test_clnt_workqueue) |
| return -EFAULT; |
| |
| rc = qmi_svc_event_notifier_register(TEST_SERVICE_SVC_ID, |
| TEST_SERVICE_INS_ID, &test_clnt_nb); |
| if (rc < 0) { |
| pr_err("%s: notifier register failed\n", __func__); |
| destroy_workqueue(test_clnt_workqueue); |
| return rc; |
| } |
| |
| test_dent = debugfs_create_file("test_qmi_client", 0444, 0, |
| NULL, &debug_ops); |
| if (IS_ERR(test_dent)) { |
| pr_err("%s: unable to create debugfs %ld\n", |
| __func__, IS_ERR(test_dent)); |
| test_dent = NULL; |
| qmi_svc_event_notifier_unregister(TEST_SERVICE_SVC_ID, |
| TEST_SERVICE_INS_ID, &test_clnt_nb); |
| destroy_workqueue(test_clnt_workqueue); |
| return -EFAULT; |
| } |
| |
| return 0; |
| } |
| |
| static void __exit test_qmi_exit(void) |
| { |
| qmi_svc_event_notifier_unregister(TEST_SERVICE_SVC_ID, |
| TEST_SERVICE_INS_ID, &test_clnt_nb); |
| destroy_workqueue(test_clnt_workqueue); |
| debugfs_remove(test_dent); |
| } |
| |
| module_init(test_qmi_init); |
| module_exit(test_qmi_exit); |
| |
| MODULE_DESCRIPTION("TEST QMI Client Driver"); |
| MODULE_LICENSE("GPL v2"); |