123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Sample in-kernel QMI client driver
- *
- * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
- * Copyright (C) 2017 Linaro Ltd.
- */
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/debugfs.h>
- #include <linux/device.h>
- #include <linux/platform_device.h>
- #include <linux/qrtr.h>
- #include <linux/net.h>
- #include <linux/completion.h>
- #include <linux/idr.h>
- #include <linux/string.h>
- #include <net/sock.h>
- #include <linux/soc/qcom/qmi.h>
- #define PING_REQ1_TLV_TYPE 0x1
- #define PING_RESP1_TLV_TYPE 0x2
- #define PING_OPT1_TLV_TYPE 0x10
- #define PING_OPT2_TLV_TYPE 0x11
- #define DATA_REQ1_TLV_TYPE 0x1
- #define DATA_RESP1_TLV_TYPE 0x2
- #define DATA_OPT1_TLV_TYPE 0x10
- #define DATA_OPT2_TLV_TYPE 0x11
- #define TEST_MED_DATA_SIZE_V01 8192
- #define TEST_MAX_NAME_SIZE_V01 255
- #define TEST_PING_REQ_MSG_ID_V01 0x20
- #define TEST_DATA_REQ_MSG_ID_V01 0x21
- #define TEST_PING_REQ_MAX_MSG_LEN_V01 266
- #define TEST_DATA_REQ_MAX_MSG_LEN_V01 8456
- struct test_name_type_v01 {
- u32 name_len;
- char name[TEST_MAX_NAME_SIZE_V01];
- };
- static struct qmi_elem_info test_name_type_v01_ei[] = {
- {
- .data_type = QMI_DATA_LEN,
- .elem_len = 1,
- .elem_size = sizeof(u8),
- .array_type = NO_ARRAY,
- .tlv_type = QMI_COMMON_TLV_TYPE,
- .offset = offsetof(struct test_name_type_v01,
- name_len),
- },
- {
- .data_type = QMI_UNSIGNED_1_BYTE,
- .elem_len = TEST_MAX_NAME_SIZE_V01,
- .elem_size = sizeof(char),
- .array_type = VAR_LEN_ARRAY,
- .tlv_type = QMI_COMMON_TLV_TYPE,
- .offset = offsetof(struct test_name_type_v01,
- name),
- },
- {}
- };
- struct test_ping_req_msg_v01 {
- char ping[4];
- u8 client_name_valid;
- struct test_name_type_v01 client_name;
- };
- static struct qmi_elem_info test_ping_req_msg_v01_ei[] = {
- {
- .data_type = QMI_UNSIGNED_1_BYTE,
- .elem_len = 4,
- .elem_size = sizeof(char),
- .array_type = STATIC_ARRAY,
- .tlv_type = PING_REQ1_TLV_TYPE,
- .offset = offsetof(struct test_ping_req_msg_v01,
- ping),
- },
- {
- .data_type = QMI_OPT_FLAG,
- .elem_len = 1,
- .elem_size = sizeof(u8),
- .array_type = NO_ARRAY,
- .tlv_type = PING_OPT1_TLV_TYPE,
- .offset = offsetof(struct test_ping_req_msg_v01,
- client_name_valid),
- },
- {
- .data_type = QMI_STRUCT,
- .elem_len = 1,
- .elem_size = sizeof(struct test_name_type_v01),
- .array_type = NO_ARRAY,
- .tlv_type = PING_OPT1_TLV_TYPE,
- .offset = offsetof(struct test_ping_req_msg_v01,
- client_name),
- .ei_array = test_name_type_v01_ei,
- },
- {}
- };
- struct test_ping_resp_msg_v01 {
- struct qmi_response_type_v01 resp;
- u8 pong_valid;
- char pong[4];
- u8 service_name_valid;
- struct test_name_type_v01 service_name;
- };
- static struct qmi_elem_info test_ping_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 = PING_RESP1_TLV_TYPE,
- .offset = offsetof(struct test_ping_resp_msg_v01,
- resp),
- .ei_array = qmi_response_type_v01_ei,
- },
- {
- .data_type = QMI_OPT_FLAG,
- .elem_len = 1,
- .elem_size = sizeof(u8),
- .array_type = NO_ARRAY,
- .tlv_type = PING_OPT1_TLV_TYPE,
- .offset = offsetof(struct test_ping_resp_msg_v01,
- pong_valid),
- },
- {
- .data_type = QMI_UNSIGNED_1_BYTE,
- .elem_len = 4,
- .elem_size = sizeof(char),
- .array_type = STATIC_ARRAY,
- .tlv_type = PING_OPT1_TLV_TYPE,
- .offset = offsetof(struct test_ping_resp_msg_v01,
- pong),
- },
- {
- .data_type = QMI_OPT_FLAG,
- .elem_len = 1,
- .elem_size = sizeof(u8),
- .array_type = NO_ARRAY,
- .tlv_type = PING_OPT2_TLV_TYPE,
- .offset = offsetof(struct test_ping_resp_msg_v01,
- service_name_valid),
- },
- {
- .data_type = QMI_STRUCT,
- .elem_len = 1,
- .elem_size = sizeof(struct test_name_type_v01),
- .array_type = NO_ARRAY,
- .tlv_type = PING_OPT2_TLV_TYPE,
- .offset = offsetof(struct test_ping_resp_msg_v01,
- service_name),
- .ei_array = test_name_type_v01_ei,
- },
- {}
- };
- struct test_data_req_msg_v01 {
- u32 data_len;
- u8 data[TEST_MED_DATA_SIZE_V01];
- u8 client_name_valid;
- struct test_name_type_v01 client_name;
- };
- static struct qmi_elem_info test_data_req_msg_v01_ei[] = {
- {
- .data_type = QMI_DATA_LEN,
- .elem_len = 1,
- .elem_size = sizeof(u32),
- .array_type = NO_ARRAY,
- .tlv_type = DATA_REQ1_TLV_TYPE,
- .offset = offsetof(struct test_data_req_msg_v01,
- data_len),
- },
- {
- .data_type = QMI_UNSIGNED_1_BYTE,
- .elem_len = TEST_MED_DATA_SIZE_V01,
- .elem_size = sizeof(u8),
- .array_type = VAR_LEN_ARRAY,
- .tlv_type = DATA_REQ1_TLV_TYPE,
- .offset = offsetof(struct test_data_req_msg_v01,
- data),
- },
- {
- .data_type = QMI_OPT_FLAG,
- .elem_len = 1,
- .elem_size = sizeof(u8),
- .array_type = NO_ARRAY,
- .tlv_type = DATA_OPT1_TLV_TYPE,
- .offset = offsetof(struct test_data_req_msg_v01,
- client_name_valid),
- },
- {
- .data_type = QMI_STRUCT,
- .elem_len = 1,
- .elem_size = sizeof(struct test_name_type_v01),
- .array_type = NO_ARRAY,
- .tlv_type = DATA_OPT1_TLV_TYPE,
- .offset = offsetof(struct test_data_req_msg_v01,
- client_name),
- .ei_array = test_name_type_v01_ei,
- },
- {}
- };
- struct test_data_resp_msg_v01 {
- struct qmi_response_type_v01 resp;
- u8 data_valid;
- u32 data_len;
- u8 data[TEST_MED_DATA_SIZE_V01];
- u8 service_name_valid;
- struct test_name_type_v01 service_name;
- };
- static struct qmi_elem_info test_data_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 = DATA_RESP1_TLV_TYPE,
- .offset = offsetof(struct test_data_resp_msg_v01,
- resp),
- .ei_array = qmi_response_type_v01_ei,
- },
- {
- .data_type = QMI_OPT_FLAG,
- .elem_len = 1,
- .elem_size = sizeof(u8),
- .array_type = NO_ARRAY,
- .tlv_type = DATA_OPT1_TLV_TYPE,
- .offset = offsetof(struct test_data_resp_msg_v01,
- data_valid),
- },
- {
- .data_type = QMI_DATA_LEN,
- .elem_len = 1,
- .elem_size = sizeof(u32),
- .array_type = NO_ARRAY,
- .tlv_type = DATA_OPT1_TLV_TYPE,
- .offset = offsetof(struct test_data_resp_msg_v01,
- data_len),
- },
- {
- .data_type = QMI_UNSIGNED_1_BYTE,
- .elem_len = TEST_MED_DATA_SIZE_V01,
- .elem_size = sizeof(u8),
- .array_type = VAR_LEN_ARRAY,
- .tlv_type = DATA_OPT1_TLV_TYPE,
- .offset = offsetof(struct test_data_resp_msg_v01,
- data),
- },
- {
- .data_type = QMI_OPT_FLAG,
- .elem_len = 1,
- .elem_size = sizeof(u8),
- .array_type = NO_ARRAY,
- .tlv_type = DATA_OPT2_TLV_TYPE,
- .offset = offsetof(struct test_data_resp_msg_v01,
- service_name_valid),
- },
- {
- .data_type = QMI_STRUCT,
- .elem_len = 1,
- .elem_size = sizeof(struct test_name_type_v01),
- .array_type = NO_ARRAY,
- .tlv_type = DATA_OPT2_TLV_TYPE,
- .offset = offsetof(struct test_data_resp_msg_v01,
- service_name),
- .ei_array = test_name_type_v01_ei,
- },
- {}
- };
- /*
- * ping_write() - ping_pong debugfs file write handler
- * @file: debugfs file context
- * @user_buf: reference to the user data (ignored)
- * @count: number of bytes in @user_buf
- * @ppos: offset in @file to write
- *
- * This function allows user space to send out a ping_pong QMI encoded message
- * to the associated remote test service and will return with the result of the
- * transaction. It serves as an example of how to provide a custom response
- * handler.
- *
- * Return: @count, or negative errno on failure.
- */
- static ssize_t ping_write(struct file *file, const char __user *user_buf,
- size_t count, loff_t *ppos)
- {
- struct qmi_handle *qmi = file->private_data;
- struct test_ping_req_msg_v01 req = {};
- struct qmi_txn txn;
- int ret;
- memcpy(req.ping, "ping", sizeof(req.ping));
- ret = qmi_txn_init(qmi, &txn, NULL, NULL);
- if (ret < 0)
- return ret;
- ret = qmi_send_request(qmi, NULL, &txn,
- TEST_PING_REQ_MSG_ID_V01,
- TEST_PING_REQ_MAX_MSG_LEN_V01,
- test_ping_req_msg_v01_ei, &req);
- if (ret < 0) {
- qmi_txn_cancel(&txn);
- return ret;
- }
- ret = qmi_txn_wait(&txn, 5 * HZ);
- if (ret < 0)
- count = ret;
- return count;
- }
- static const struct file_operations ping_fops = {
- .open = simple_open,
- .write = ping_write,
- };
- static void ping_pong_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
- struct qmi_txn *txn, const void *data)
- {
- const struct test_ping_resp_msg_v01 *resp = data;
- if (!txn) {
- pr_err("spurious ping response\n");
- return;
- }
- if (resp->resp.result == QMI_RESULT_FAILURE_V01)
- txn->result = -ENXIO;
- else if (!resp->pong_valid || memcmp(resp->pong, "pong", 4))
- txn->result = -EINVAL;
- complete(&txn->completion);
- }
- /*
- * data_write() - data debugfs file write handler
- * @file: debugfs file context
- * @user_buf: reference to the user data
- * @count: number of bytes in @user_buf
- * @ppos: offset in @file to write
- *
- * This function allows user space to send out a data QMI encoded message to
- * the associated remote test service and will return with the result of the
- * transaction. It serves as an example of how to have the QMI helpers decode a
- * transaction response into a provided object automatically.
- *
- * Return: @count, or negative errno on failure.
- */
- static ssize_t data_write(struct file *file, const char __user *user_buf,
- size_t count, loff_t *ppos)
- {
- struct qmi_handle *qmi = file->private_data;
- struct test_data_resp_msg_v01 *resp;
- struct test_data_req_msg_v01 *req;
- struct qmi_txn txn;
- int ret;
- req = kzalloc(sizeof(*req), GFP_KERNEL);
- if (!req)
- return -ENOMEM;
- resp = kzalloc(sizeof(*resp), GFP_KERNEL);
- if (!resp) {
- kfree(req);
- return -ENOMEM;
- }
- req->data_len = min_t(size_t, sizeof(req->data), count);
- if (copy_from_user(req->data, user_buf, req->data_len)) {
- ret = -EFAULT;
- goto out;
- }
- ret = qmi_txn_init(qmi, &txn, test_data_resp_msg_v01_ei, resp);
- if (ret < 0)
- goto out;
- ret = qmi_send_request(qmi, NULL, &txn,
- TEST_DATA_REQ_MSG_ID_V01,
- TEST_DATA_REQ_MAX_MSG_LEN_V01,
- test_data_req_msg_v01_ei, req);
- if (ret < 0) {
- qmi_txn_cancel(&txn);
- goto out;
- }
- ret = qmi_txn_wait(&txn, 5 * HZ);
- if (ret < 0) {
- goto out;
- } else if (!resp->data_valid ||
- resp->data_len != req->data_len ||
- memcmp(resp->data, req->data, req->data_len)) {
- pr_err("response data doesn't match expectation\n");
- ret = -EINVAL;
- goto out;
- }
- ret = count;
- out:
- kfree(resp);
- kfree(req);
- return ret;
- }
- static const struct file_operations data_fops = {
- .open = simple_open,
- .write = data_write,
- };
- static struct qmi_msg_handler qmi_sample_handlers[] = {
- {
- .type = QMI_RESPONSE,
- .msg_id = TEST_PING_REQ_MSG_ID_V01,
- .ei = test_ping_resp_msg_v01_ei,
- .decoded_size = sizeof(struct test_ping_req_msg_v01),
- .fn = ping_pong_cb
- },
- {}
- };
- struct qmi_sample {
- struct qmi_handle qmi;
- struct dentry *de_dir;
- struct dentry *de_data;
- struct dentry *de_ping;
- };
- static struct dentry *qmi_debug_dir;
- static int qmi_sample_probe(struct platform_device *pdev)
- {
- struct sockaddr_qrtr *sq;
- struct qmi_sample *sample;
- char path[20];
- int ret;
- sample = devm_kzalloc(&pdev->dev, sizeof(*sample), GFP_KERNEL);
- if (!sample)
- return -ENOMEM;
- ret = qmi_handle_init(&sample->qmi, TEST_DATA_REQ_MAX_MSG_LEN_V01,
- NULL,
- qmi_sample_handlers);
- if (ret < 0)
- return ret;
- sq = dev_get_platdata(&pdev->dev);
- ret = kernel_connect(sample->qmi.sock, (struct sockaddr *)sq,
- sizeof(*sq), 0);
- if (ret < 0) {
- pr_err("failed to connect to remote service port\n");
- goto err_release_qmi_handle;
- }
- snprintf(path, sizeof(path), "%d:%d", sq->sq_node, sq->sq_port);
- sample->de_dir = debugfs_create_dir(path, qmi_debug_dir);
- if (IS_ERR(sample->de_dir)) {
- ret = PTR_ERR(sample->de_dir);
- goto err_release_qmi_handle;
- }
- sample->de_data = debugfs_create_file("data", 0600, sample->de_dir,
- sample, &data_fops);
- if (IS_ERR(sample->de_data)) {
- ret = PTR_ERR(sample->de_data);
- goto err_remove_de_dir;
- }
- sample->de_ping = debugfs_create_file("ping", 0600, sample->de_dir,
- sample, &ping_fops);
- if (IS_ERR(sample->de_ping)) {
- ret = PTR_ERR(sample->de_ping);
- goto err_remove_de_data;
- }
- platform_set_drvdata(pdev, sample);
- return 0;
- err_remove_de_data:
- debugfs_remove(sample->de_data);
- err_remove_de_dir:
- debugfs_remove(sample->de_dir);
- err_release_qmi_handle:
- qmi_handle_release(&sample->qmi);
- return ret;
- }
- static int qmi_sample_remove(struct platform_device *pdev)
- {
- struct qmi_sample *sample = platform_get_drvdata(pdev);
- debugfs_remove(sample->de_ping);
- debugfs_remove(sample->de_data);
- debugfs_remove(sample->de_dir);
- qmi_handle_release(&sample->qmi);
- return 0;
- }
- static struct platform_driver qmi_sample_driver = {
- .probe = qmi_sample_probe,
- .remove = qmi_sample_remove,
- .driver = {
- .name = "qmi_sample_client",
- },
- };
- static int qmi_sample_new_server(struct qmi_handle *qmi,
- struct qmi_service *service)
- {
- struct platform_device *pdev;
- struct sockaddr_qrtr sq = { AF_QIPCRTR, service->node, service->port };
- int ret;
- pdev = platform_device_alloc("qmi_sample_client", PLATFORM_DEVID_AUTO);
- if (!pdev)
- return -ENOMEM;
- ret = platform_device_add_data(pdev, &sq, sizeof(sq));
- if (ret)
- goto err_put_device;
- ret = platform_device_add(pdev);
- if (ret)
- goto err_put_device;
- service->priv = pdev;
- return 0;
- err_put_device:
- platform_device_put(pdev);
- return ret;
- }
- static void qmi_sample_del_server(struct qmi_handle *qmi,
- struct qmi_service *service)
- {
- struct platform_device *pdev = service->priv;
- platform_device_unregister(pdev);
- }
- static struct qmi_handle lookup_client;
- static struct qmi_ops lookup_ops = {
- .new_server = qmi_sample_new_server,
- .del_server = qmi_sample_del_server,
- };
- static int qmi_sample_init(void)
- {
- int ret;
- qmi_debug_dir = debugfs_create_dir("qmi_sample", NULL);
- if (IS_ERR(qmi_debug_dir)) {
- pr_err("failed to create qmi_sample dir\n");
- return PTR_ERR(qmi_debug_dir);
- }
- ret = platform_driver_register(&qmi_sample_driver);
- if (ret)
- goto err_remove_debug_dir;
- ret = qmi_handle_init(&lookup_client, 0, &lookup_ops, NULL);
- if (ret < 0)
- goto err_unregister_driver;
- qmi_add_lookup(&lookup_client, 15, 0, 0);
- return 0;
- err_unregister_driver:
- platform_driver_unregister(&qmi_sample_driver);
- err_remove_debug_dir:
- debugfs_remove(qmi_debug_dir);
- return ret;
- }
- static void qmi_sample_exit(void)
- {
- qmi_handle_release(&lookup_client);
- platform_driver_unregister(&qmi_sample_driver);
- debugfs_remove(qmi_debug_dir);
- }
- module_init(qmi_sample_init);
- module_exit(qmi_sample_exit);
- MODULE_DESCRIPTION("Sample QMI client driver");
- MODULE_LICENSE("GPL v2");
|