root/samples/qmi/qmi_sample_client.c
// 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 const 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 const 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 const 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 const 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 const 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 const 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_unsized *)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 void 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);
}

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 const 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");