Karthikeyan Ramasubramanian | a9859e8 | 2012-07-12 13:10:42 -0600 | [diff] [blame] | 1 | /* Copyright (c) 2013, The Linux Foundation. All rights reserved. |
| 2 | * |
| 3 | * This program is free software; you can redistribute it and/or modify |
| 4 | * it under the terms of the GNU General Public License version 2 and |
| 5 | * only version 2 as published by the Free Software Foundation. |
| 6 | * |
| 7 | * This program is distributed in the hope that it will be useful, |
| 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 10 | * GNU General Public License for more details. |
| 11 | * |
| 12 | */ |
| 13 | |
| 14 | #include <linux/module.h> |
| 15 | #include <linux/slab.h> |
| 16 | #include <linux/errno.h> |
| 17 | #include <linux/delay.h> |
| 18 | #include <linux/debugfs.h> |
| 19 | #include <linux/qmi_encdec.h> |
| 20 | |
| 21 | #include <asm/uaccess.h> |
| 22 | |
| 23 | #include <mach/msm_qmi_interface.h> |
| 24 | |
| 25 | #include "kernel_test_service_v01.h" |
| 26 | |
| 27 | #define TEST_SERVICE_SVC_ID 0x0000000f |
| 28 | #define TEST_SERVICE_INS_ID 1 |
| 29 | |
| 30 | static int test_rep_cnt = 10; |
| 31 | module_param_named(rep_cnt, test_rep_cnt, int, S_IRUGO | S_IWUSR | S_IWGRP); |
| 32 | |
| 33 | static int test_data_sz = 50; |
| 34 | module_param_named(data_sz, test_data_sz, int, S_IRUGO | S_IWUSR | S_IWGRP); |
| 35 | |
| 36 | static int test_clnt_debug_mask; |
| 37 | module_param_named(debug_mask, test_clnt_debug_mask, |
| 38 | int, S_IRUGO | S_IWUSR | S_IWGRP); |
| 39 | |
| 40 | #define D(x...) do { \ |
| 41 | if (test_clnt_debug_mask) \ |
| 42 | pr_debug(x); \ |
| 43 | } while (0) |
| 44 | |
| 45 | /* Variable to initiate the test through debugfs interface */ |
| 46 | static struct dentry *test_dent; |
| 47 | |
| 48 | /* Test client port for IPC Router */ |
| 49 | static struct qmi_handle *test_clnt; |
| 50 | static int test_clnt_reset; |
| 51 | |
| 52 | /* Reader thread to receive responses & indications */ |
| 53 | static void test_clnt_recv_msg(struct work_struct *work); |
| 54 | static DECLARE_DELAYED_WORK(work_recv_msg, test_clnt_recv_msg); |
| 55 | static void test_clnt_svc_arrive(struct work_struct *work); |
| 56 | static DECLARE_DELAYED_WORK(work_svc_arrive, test_clnt_svc_arrive); |
| 57 | static void test_clnt_svc_exit(struct work_struct *work); |
| 58 | static DECLARE_DELAYED_WORK(work_svc_exit, test_clnt_svc_exit); |
| 59 | static struct workqueue_struct *test_clnt_workqueue; |
| 60 | |
| 61 | /* Variable to hold the test result */ |
| 62 | static int test_res; |
| 63 | |
Zaheerulla Meer | aef869c | 2013-05-27 20:03:30 +0530 | [diff] [blame] | 64 | static unsigned int callback_count; |
| 65 | static void test_async_resp_cb(struct qmi_handle *handle, |
| 66 | unsigned int msg_id, void *msg, |
| 67 | void *resp_cb_data, int stat) |
| 68 | { |
| 69 | callback_count++; |
| 70 | if (stat == 0) |
| 71 | D("%s invoked %d time(s): [RESP_LEN] = %d, [RESP_VALID] = %d", |
| 72 | __func__, callback_count, |
| 73 | ((struct test_data_resp_msg_v01 *)msg)->data_len, |
| 74 | ((struct test_data_resp_msg_v01 *)msg)->data_valid); |
| 75 | else if (stat < 0) |
| 76 | pr_err("%s: Request Failed [MSG_ID]: %d, [ERR_ID]: %d, [Callback_count]: %d", |
| 77 | __func__, msg_id, stat, callback_count); |
| 78 | |
| 79 | kfree(msg); |
| 80 | kfree(resp_cb_data); |
| 81 | } |
| 82 | |
Karthikeyan Ramasubramanian | a9859e8 | 2012-07-12 13:10:42 -0600 | [diff] [blame] | 83 | static int test_qmi_ping_pong_send_sync_msg(void) |
| 84 | { |
| 85 | struct test_ping_req_msg_v01 req; |
| 86 | struct test_ping_resp_msg_v01 resp; |
| 87 | struct msg_desc req_desc, resp_desc; |
| 88 | int rc; |
| 89 | |
| 90 | memcpy(req.ping, "ping", sizeof(req.ping)); |
| 91 | req.client_name_valid = 0; |
| 92 | |
| 93 | req_desc.max_msg_len = TEST_PING_REQ_MAX_MSG_LEN_V01; |
| 94 | req_desc.msg_id = TEST_PING_REQ_MSG_ID_V01; |
| 95 | req_desc.ei_array = test_ping_req_msg_v01_ei; |
| 96 | |
| 97 | resp_desc.max_msg_len = TEST_PING_REQ_MAX_MSG_LEN_V01; |
| 98 | resp_desc.msg_id = TEST_PING_REQ_MSG_ID_V01; |
| 99 | resp_desc.ei_array = test_ping_resp_msg_v01_ei; |
| 100 | |
| 101 | rc = qmi_send_req_wait(test_clnt, &req_desc, &req, sizeof(req), |
| 102 | &resp_desc, &resp, sizeof(resp), 0); |
| 103 | if (rc < 0) { |
| 104 | pr_err("%s: send req failed %d\n", __func__, rc); |
| 105 | return rc; |
| 106 | } |
| 107 | |
| 108 | D("%s: Received %s response\n", __func__, resp.pong); |
| 109 | return rc; |
| 110 | } |
| 111 | |
| 112 | static int test_qmi_data_send_sync_msg(unsigned int data_len) |
| 113 | { |
| 114 | struct test_data_req_msg_v01 *req; |
| 115 | struct test_data_resp_msg_v01 *resp; |
| 116 | struct msg_desc req_desc, resp_desc; |
| 117 | int rc, i; |
| 118 | |
| 119 | req = kzalloc(sizeof(struct test_data_req_msg_v01), GFP_KERNEL); |
| 120 | if (!req) { |
| 121 | pr_err("%s: Data req msg alloc failed\n", __func__); |
| 122 | return -ENOMEM; |
| 123 | } |
| 124 | |
| 125 | resp = kzalloc(sizeof(struct test_data_resp_msg_v01), GFP_KERNEL); |
| 126 | if (!resp) { |
| 127 | pr_err("%s: Data resp msg alloc failed\n", __func__); |
| 128 | kfree(req); |
| 129 | return -ENOMEM; |
| 130 | } |
| 131 | |
| 132 | req->data_len = data_len; |
| 133 | for (i = 0; i < data_len; i = i + sizeof(int)) |
| 134 | memcpy(req->data + i, (uint8_t *)&i, sizeof(int)); |
| 135 | req->client_name_valid = 0; |
| 136 | |
| 137 | req_desc.max_msg_len = TEST_DATA_REQ_MAX_MSG_LEN_V01; |
| 138 | req_desc.msg_id = TEST_DATA_REQ_MSG_ID_V01; |
| 139 | req_desc.ei_array = test_data_req_msg_v01_ei; |
| 140 | |
| 141 | resp_desc.max_msg_len = TEST_DATA_REQ_MAX_MSG_LEN_V01; |
| 142 | resp_desc.msg_id = TEST_DATA_REQ_MSG_ID_V01; |
| 143 | resp_desc.ei_array = test_data_resp_msg_v01_ei; |
| 144 | |
| 145 | rc = qmi_send_req_wait(test_clnt, &req_desc, req, sizeof(*req), |
| 146 | &resp_desc, resp, sizeof(*resp), 0); |
| 147 | if (rc < 0) { |
| 148 | pr_err("%s: send req failed\n", __func__); |
| 149 | goto data_send_err; |
| 150 | } |
| 151 | |
| 152 | D("%s: data_valid %d\n", __func__, resp->data_valid); |
| 153 | D("%s: data_len %d\n", __func__, resp->data_len); |
| 154 | data_send_err: |
| 155 | kfree(resp); |
| 156 | kfree(req); |
| 157 | return rc; |
| 158 | } |
| 159 | |
Zaheerulla Meer | aef869c | 2013-05-27 20:03:30 +0530 | [diff] [blame] | 160 | static int test_qmi_data_send_async_msg(unsigned int data_len) |
| 161 | { |
| 162 | struct test_data_req_msg_v01 *req; |
| 163 | struct test_data_resp_msg_v01 *resp; |
| 164 | struct msg_desc req_desc, *resp_desc; |
| 165 | int rc, i; |
| 166 | |
| 167 | req = kzalloc(sizeof(struct test_data_req_msg_v01), GFP_KERNEL); |
| 168 | if (!req) { |
| 169 | pr_err("%s: Data req msg alloc failed\n", __func__); |
| 170 | return -ENOMEM; |
| 171 | } |
| 172 | |
| 173 | resp = kzalloc(sizeof(struct test_data_resp_msg_v01), GFP_KERNEL); |
| 174 | if (!resp) { |
| 175 | pr_err("%s: Data resp msg alloc failed\n", __func__); |
| 176 | kfree(req); |
| 177 | return -ENOMEM; |
| 178 | } |
| 179 | |
| 180 | resp_desc = kzalloc(sizeof(struct msg_desc), GFP_KERNEL); |
| 181 | if (!resp_desc) { |
| 182 | pr_err("%s: Resp_desc msg alloc failed\n", __func__); |
| 183 | kfree(req); |
| 184 | kfree(resp); |
| 185 | return -ENOMEM; |
| 186 | } |
| 187 | |
| 188 | req->data_len = data_len; |
| 189 | for (i = 0; i < data_len; i = i + sizeof(int)) |
| 190 | memcpy(req->data + i, (uint8_t *)&i, sizeof(int)); |
| 191 | req->client_name_valid = 0; |
| 192 | |
| 193 | req_desc.max_msg_len = TEST_DATA_REQ_MAX_MSG_LEN_V01; |
| 194 | req_desc.msg_id = TEST_DATA_REQ_MSG_ID_V01; |
| 195 | req_desc.ei_array = test_data_req_msg_v01_ei; |
| 196 | |
| 197 | resp_desc->max_msg_len = TEST_DATA_REQ_MAX_MSG_LEN_V01; |
| 198 | resp_desc->msg_id = TEST_DATA_REQ_MSG_ID_V01; |
| 199 | resp_desc->ei_array = test_data_resp_msg_v01_ei; |
| 200 | |
| 201 | rc = qmi_send_req_nowait(test_clnt, &req_desc, req, sizeof(*req), |
| 202 | resp_desc, resp, sizeof(*resp), |
| 203 | test_async_resp_cb, (void *)resp_desc); |
| 204 | if (rc < 0) { |
| 205 | pr_err("%s: send req failed\n", __func__); |
| 206 | kfree(resp); |
| 207 | kfree(resp_desc); |
| 208 | } |
| 209 | kfree(req); |
| 210 | return rc; |
| 211 | } |
| 212 | |
Karthikeyan Ramasubramanian | a9859e8 | 2012-07-12 13:10:42 -0600 | [diff] [blame] | 213 | static void test_clnt_recv_msg(struct work_struct *work) |
| 214 | { |
| 215 | int rc; |
| 216 | |
Zaheerulla Meer | aef869c | 2013-05-27 20:03:30 +0530 | [diff] [blame] | 217 | do { |
| 218 | D("%s: Notified about a Receive Event", __func__); |
| 219 | } while ((rc = qmi_recv_msg(test_clnt)) == 0); |
| 220 | |
| 221 | if (rc != -ENOMSG) |
Karthikeyan Ramasubramanian | a9859e8 | 2012-07-12 13:10:42 -0600 | [diff] [blame] | 222 | pr_err("%s: Error receiving message\n", __func__); |
| 223 | } |
| 224 | |
| 225 | static void test_clnt_notify(struct qmi_handle *handle, |
| 226 | enum qmi_event_type event, void *notify_priv) |
| 227 | { |
| 228 | switch (event) { |
| 229 | case QMI_RECV_MSG: |
| 230 | queue_delayed_work(test_clnt_workqueue, |
| 231 | &work_recv_msg, 0); |
| 232 | break; |
| 233 | default: |
| 234 | break; |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | static void test_clnt_svc_arrive(struct work_struct *work) |
| 239 | { |
| 240 | int rc; |
| 241 | |
| 242 | D("%s begins\n", __func__); |
| 243 | |
| 244 | /* Create a Local client port for QMI communication */ |
| 245 | test_clnt = qmi_handle_create(test_clnt_notify, NULL); |
| 246 | if (!test_clnt) { |
| 247 | pr_err("%s: QMI client handle alloc failed\n", __func__); |
| 248 | return; |
| 249 | } |
| 250 | |
| 251 | D("%s: Lookup server name\n", __func__); |
| 252 | rc = qmi_connect_to_service(test_clnt, TEST_SERVICE_SVC_ID, |
| 253 | TEST_SERVICE_INS_ID); |
| 254 | if (rc < 0) { |
| 255 | pr_err("%s: Server not found\n", __func__); |
| 256 | qmi_handle_destroy(test_clnt); |
| 257 | test_clnt = NULL; |
| 258 | return; |
| 259 | } |
| 260 | test_clnt_reset = 0; |
| 261 | D("%s complete\n", __func__); |
| 262 | } |
| 263 | |
| 264 | static void test_clnt_svc_exit(struct work_struct *work) |
| 265 | { |
| 266 | D("%s begins\n", __func__); |
| 267 | |
| 268 | qmi_handle_destroy(test_clnt); |
| 269 | test_clnt_reset = 1; |
| 270 | test_clnt = NULL; |
| 271 | |
| 272 | D("%s complete\n", __func__); |
| 273 | } |
| 274 | |
| 275 | static int test_clnt_svc_event_notify(struct notifier_block *this, |
| 276 | unsigned long code, |
| 277 | void *_cmd) |
| 278 | { |
| 279 | D("%s: event %ld\n", __func__, code); |
| 280 | switch (code) { |
| 281 | case QMI_SERVER_ARRIVE: |
| 282 | queue_delayed_work(test_clnt_workqueue, |
| 283 | &work_svc_arrive, 0); |
| 284 | break; |
| 285 | case QMI_SERVER_EXIT: |
| 286 | queue_delayed_work(test_clnt_workqueue, |
| 287 | &work_svc_exit, 0); |
| 288 | break; |
| 289 | default: |
| 290 | break; |
| 291 | } |
| 292 | return 0; |
| 293 | } |
| 294 | |
| 295 | static int test_qmi_open(struct inode *ip, struct file *fp) |
| 296 | { |
| 297 | if (!test_clnt) { |
| 298 | pr_err("%s Test client is not initialized\n", __func__); |
| 299 | return -ENODEV; |
| 300 | } |
| 301 | return 0; |
| 302 | } |
| 303 | |
| 304 | static ssize_t test_qmi_read(struct file *fp, char __user *buf, |
| 305 | size_t count, loff_t *pos) |
| 306 | { |
| 307 | char _buf[16]; |
| 308 | snprintf(_buf, sizeof(_buf), "%d\n", test_res); |
| 309 | test_res = 0; |
| 310 | return simple_read_from_buffer(buf, count, pos, |
| 311 | _buf, strnlen(_buf, 16)); |
| 312 | } |
| 313 | |
| 314 | static int test_qmi_release(struct inode *ip, struct file *fp) |
| 315 | { |
| 316 | return 0; |
| 317 | } |
| 318 | |
| 319 | static ssize_t test_qmi_write(struct file *fp, const char __user *buf, |
| 320 | size_t count, loff_t *pos) |
| 321 | { |
| 322 | unsigned char cmd[64]; |
| 323 | int len; |
| 324 | int i; |
| 325 | |
| 326 | if (count < 1) |
| 327 | return 0; |
| 328 | |
| 329 | len = min(count, (sizeof(cmd) - 1)); |
| 330 | |
| 331 | if (copy_from_user(cmd, buf, len)) |
| 332 | return -EFAULT; |
| 333 | |
| 334 | cmd[len] = 0; |
| 335 | if (cmd[len-1] == '\n') { |
| 336 | cmd[len-1] = 0; |
| 337 | len--; |
| 338 | } |
| 339 | |
| 340 | if (!strncmp(cmd, "ping_pong", sizeof(cmd))) { |
| 341 | for (i = 0; i < test_rep_cnt; i++) { |
| 342 | test_res = test_qmi_ping_pong_send_sync_msg(); |
| 343 | if (test_res == -ENETRESET || test_clnt_reset) { |
| 344 | do { |
| 345 | msleep(50); |
| 346 | } while (test_clnt_reset); |
| 347 | } |
| 348 | } |
| 349 | } else if (!strncmp(cmd, "data", sizeof(cmd))) { |
| 350 | for (i = 0; i < test_rep_cnt; i++) { |
| 351 | test_res = test_qmi_data_send_sync_msg(test_data_sz); |
| 352 | if (test_res == -ENETRESET || test_clnt_reset) { |
| 353 | do { |
| 354 | msleep(50); |
| 355 | } while (test_clnt_reset); |
| 356 | } |
| 357 | } |
Zaheerulla Meer | aef869c | 2013-05-27 20:03:30 +0530 | [diff] [blame] | 358 | } else if (!strncmp(cmd, "data_async", sizeof(cmd))) { |
| 359 | int i; |
| 360 | callback_count = 0; |
| 361 | for (i = 0; i < test_rep_cnt; i++) { |
| 362 | test_res = test_qmi_data_send_async_msg(test_data_sz); |
| 363 | if (test_res == -ENETRESET || test_clnt_reset) { |
| 364 | --i; |
| 365 | do { |
| 366 | msleep(50); |
| 367 | } while (test_clnt_reset); |
| 368 | } else if (test_res < 0) { |
| 369 | --i; |
| 370 | pr_err("%s: Error sending txn, aborting now", |
| 371 | __func__); |
| 372 | break; |
| 373 | } |
| 374 | } |
| 375 | while (callback_count < i) { |
| 376 | if (test_clnt_reset) { |
| 377 | pr_err("%s: Service Exited", __func__); |
| 378 | break; |
| 379 | } |
| 380 | msleep(50); |
| 381 | } |
| 382 | D("%s complete\n", __func__); |
| 383 | callback_count = 0; |
Karthikeyan Ramasubramanian | a9859e8 | 2012-07-12 13:10:42 -0600 | [diff] [blame] | 384 | } else { |
| 385 | test_res = -EINVAL; |
| 386 | } |
| 387 | return count; |
| 388 | } |
| 389 | |
| 390 | static struct notifier_block test_clnt_nb = { |
| 391 | .notifier_call = test_clnt_svc_event_notify, |
| 392 | }; |
| 393 | |
| 394 | static const struct file_operations debug_ops = { |
| 395 | .owner = THIS_MODULE, |
| 396 | .open = test_qmi_open, |
| 397 | .read = test_qmi_read, |
| 398 | .write = test_qmi_write, |
| 399 | .release = test_qmi_release, |
| 400 | }; |
| 401 | |
| 402 | static int __init test_qmi_init(void) |
| 403 | { |
| 404 | int rc; |
| 405 | |
| 406 | test_clnt_workqueue = create_singlethread_workqueue("test_clnt"); |
| 407 | if (!test_clnt_workqueue) |
| 408 | return -EFAULT; |
| 409 | |
| 410 | rc = qmi_svc_event_notifier_register(TEST_SERVICE_SVC_ID, |
| 411 | TEST_SERVICE_INS_ID, &test_clnt_nb); |
| 412 | if (rc < 0) { |
| 413 | pr_err("%s: notifier register failed\n", __func__); |
| 414 | destroy_workqueue(test_clnt_workqueue); |
| 415 | return rc; |
| 416 | } |
| 417 | |
| 418 | test_dent = debugfs_create_file("test_qmi_client", 0444, 0, |
| 419 | NULL, &debug_ops); |
| 420 | if (IS_ERR(test_dent)) { |
| 421 | pr_err("%s: unable to create debugfs %ld\n", |
| 422 | __func__, IS_ERR(test_dent)); |
| 423 | test_dent = NULL; |
| 424 | qmi_svc_event_notifier_unregister(TEST_SERVICE_SVC_ID, |
| 425 | TEST_SERVICE_INS_ID, &test_clnt_nb); |
| 426 | destroy_workqueue(test_clnt_workqueue); |
| 427 | return -EFAULT; |
| 428 | } |
| 429 | |
| 430 | return 0; |
| 431 | } |
| 432 | |
| 433 | static void __exit test_qmi_exit(void) |
| 434 | { |
| 435 | qmi_svc_event_notifier_unregister(TEST_SERVICE_SVC_ID, |
| 436 | TEST_SERVICE_INS_ID, &test_clnt_nb); |
| 437 | destroy_workqueue(test_clnt_workqueue); |
| 438 | debugfs_remove(test_dent); |
| 439 | } |
| 440 | |
| 441 | module_init(test_qmi_init); |
| 442 | module_exit(test_qmi_exit); |
| 443 | |
| 444 | MODULE_DESCRIPTION("TEST QMI Client Driver"); |
| 445 | MODULE_LICENSE("GPL v2"); |