samples: Introduce Qualcomm QMI sample client

Introduce a sample driver that register for server notifications and
spawn clients for each available test service (service 15). The spawned
clients implements the interface for encoding "ping" and "data" requests
and decode the responses from the remote.

Change-Id: I92659781b5fcfbfb51175fe742f5767459b43f9d
Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
Patch-mainline: linux-arm-msm @ 15/11/2017 12:10
[clew@codeaurora.org: Merge differences between v1 and v3 patchsets
 from "samples: Introduce Qualcomm QMI sample client"]
Signed-off-by: Chris Lew <clew@codeaurora.org>
tirimbino
Bjorn Andersson 7 years ago committed by Chris Lew
parent 8d4be3237f
commit 97ae7769ac
  1. 6
      samples/Kconfig
  2. 2
      samples/Makefile
  3. 1
      samples/qmi/Makefile
  4. 292
      samples/qmi/qmi_sample_client.c
  5. 1
      samples/qrtr/Makefile

@ -62,11 +62,11 @@ config SAMPLE_KDB
Build an example of how to dynamically add the hello
command to the kdb shell.
config SAMPLE_QRTR_CLIENT
tristate "Build qrtr client sample -- loadable modules only"
config SAMPLE_QMI_CLIENT
tristate "Build qmi client sample -- loadable modules only"
depends on QCOM_QMI_HELPERS && m
help
Build an qrtr client sample driver, which demonstrates how to
Build an QMI client sample driver, which demonstrates how to
communicate with a remote QRTR service, using QMI encoded messages.
config SAMPLE_RPMSG_CLIENT

@ -3,4 +3,4 @@
obj-$(CONFIG_SAMPLES) += kobject/ kprobes/ trace_events/ livepatch/ \
hw_breakpoint/ kfifo/ kdb/ hidraw/ rpmsg/ seccomp/ \
configfs/ connector/ v4l/ trace_printk/ blackfin/ \
vfio-mdev/ statx/ qrtr/
vfio-mdev/ statx/ qmi/

@ -0,0 +1 @@
obj-$(CONFIG_SAMPLE_QMI_CLIENT) += qmi_sample_client.o

@ -1,7 +1,7 @@
/*
* Sample QRTR client driver
* Sample in-kernel QMI client driver
*
* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
* Copyright (c) 2013-2014, 2017, The Linux Foundation. All rights reserved.
* Copyright (C) 2017 Linaro Ltd.
*
* This software is licensed under the terms of the GNU General Public
@ -16,6 +16,7 @@
#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>
@ -46,7 +47,7 @@
#define TEST_DATA_REQ_MAX_MSG_LEN_V01 8456
struct test_name_type_v01 {
uint32_t name_len;
u32 name_len;
char name[TEST_MAX_NAME_SIZE_V01];
};
@ -54,7 +55,7 @@ static struct qmi_elem_info test_name_type_v01_ei[] = {
{
.data_type = QMI_DATA_LEN,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.elem_size = sizeof(u8),
.is_array = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
.offset = offsetof(struct test_name_type_v01,
@ -75,7 +76,7 @@ static struct qmi_elem_info test_name_type_v01_ei[] = {
struct test_ping_req_msg_v01 {
char ping[4];
uint8_t client_name_valid;
u8 client_name_valid;
struct test_name_type_v01 client_name;
};
@ -92,7 +93,7 @@ struct qmi_elem_info test_ping_req_msg_v01_ei[] = {
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.elem_size = sizeof(u8),
.is_array = NO_ARRAY,
.tlv_type = PING_OPT1_TLV_TYPE,
.offset = offsetof(struct test_ping_req_msg_v01,
@ -114,10 +115,10 @@ struct qmi_elem_info test_ping_req_msg_v01_ei[] = {
struct test_ping_resp_msg_v01 {
struct qmi_response_type_v01 resp;
uint8_t pong_valid;
u8 pong_valid;
char pong[4];
uint8_t service_name_valid;
u8 service_name_valid;
struct test_name_type_v01 service_name;
};
@ -135,7 +136,7 @@ struct qmi_elem_info test_ping_resp_msg_v01_ei[] = {
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.elem_size = sizeof(u8),
.is_array = NO_ARRAY,
.tlv_type = PING_OPT1_TLV_TYPE,
.offset = offsetof(struct test_ping_resp_msg_v01,
@ -153,7 +154,7 @@ struct qmi_elem_info test_ping_resp_msg_v01_ei[] = {
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.elem_size = sizeof(u8),
.is_array = NO_ARRAY,
.tlv_type = PING_OPT2_TLV_TYPE,
.offset = offsetof(struct test_ping_resp_msg_v01,
@ -173,10 +174,10 @@ struct qmi_elem_info test_ping_resp_msg_v01_ei[] = {
};
struct test_data_req_msg_v01 {
uint32_t data_len;
uint8_t data[TEST_MED_DATA_SIZE_V01];
u32 data_len;
u8 data[TEST_MED_DATA_SIZE_V01];
uint8_t client_name_valid;
u8 client_name_valid;
struct test_name_type_v01 client_name;
};
@ -184,7 +185,7 @@ struct qmi_elem_info test_data_req_msg_v01_ei[] = {
{
.data_type = QMI_DATA_LEN,
.elem_len = 1,
.elem_size = sizeof(uint32_t),
.elem_size = sizeof(u32),
.is_array = NO_ARRAY,
.tlv_type = DATA_REQ1_TLV_TYPE,
.offset = offsetof(struct test_data_req_msg_v01,
@ -193,7 +194,7 @@ struct qmi_elem_info test_data_req_msg_v01_ei[] = {
{
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = TEST_MED_DATA_SIZE_V01,
.elem_size = sizeof(uint8_t),
.elem_size = sizeof(u8),
.is_array = VAR_LEN_ARRAY,
.tlv_type = DATA_REQ1_TLV_TYPE,
.offset = offsetof(struct test_data_req_msg_v01,
@ -202,7 +203,7 @@ struct qmi_elem_info test_data_req_msg_v01_ei[] = {
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.elem_size = sizeof(u8),
.is_array = NO_ARRAY,
.tlv_type = DATA_OPT1_TLV_TYPE,
.offset = offsetof(struct test_data_req_msg_v01,
@ -224,11 +225,11 @@ struct qmi_elem_info test_data_req_msg_v01_ei[] = {
struct test_data_resp_msg_v01 {
struct qmi_response_type_v01 resp;
uint8_t data_valid;
uint32_t data_len;
uint8_t data[TEST_MED_DATA_SIZE_V01];
u8 data_valid;
u32 data_len;
u8 data[TEST_MED_DATA_SIZE_V01];
uint8_t service_name_valid;
u8 service_name_valid;
struct test_name_type_v01 service_name;
};
@ -246,7 +247,7 @@ struct qmi_elem_info test_data_resp_msg_v01_ei[] = {
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.elem_size = sizeof(u8),
.is_array = NO_ARRAY,
.tlv_type = DATA_OPT1_TLV_TYPE,
.offset = offsetof(struct test_data_resp_msg_v01,
@ -255,7 +256,7 @@ struct qmi_elem_info test_data_resp_msg_v01_ei[] = {
{
.data_type = QMI_DATA_LEN,
.elem_len = 1,
.elem_size = sizeof(uint32_t),
.elem_size = sizeof(u32),
.is_array = NO_ARRAY,
.tlv_type = DATA_OPT1_TLV_TYPE,
.offset = offsetof(struct test_data_resp_msg_v01,
@ -264,7 +265,7 @@ struct qmi_elem_info test_data_resp_msg_v01_ei[] = {
{
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = TEST_MED_DATA_SIZE_V01,
.elem_size = sizeof(uint8_t),
.elem_size = sizeof(u8),
.is_array = VAR_LEN_ARRAY,
.tlv_type = DATA_OPT1_TLV_TYPE,
.offset = offsetof(struct test_data_resp_msg_v01,
@ -273,7 +274,7 @@ struct qmi_elem_info test_data_resp_msg_v01_ei[] = {
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(uint8_t),
.elem_size = sizeof(u8),
.is_array = NO_ARRAY,
.tlv_type = DATA_OPT2_TLV_TYPE,
.offset = offsetof(struct test_data_resp_msg_v01,
@ -293,11 +294,11 @@ struct qmi_elem_info test_data_resp_msg_v01_ei[] = {
};
/*
* ping_pong_store() - ping_pong attribute store handler
* @dev: sample device context
* @attr: the ping_pong attribute
* @buf: write buffer
* @count: length of @buf
* 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
*
* Returns @count, or negative errno on failure.
*
@ -306,11 +307,10 @@ struct qmi_elem_info test_data_resp_msg_v01_ei[] = {
* transaction. It serves as an example of how to provide a custom response
* handler.
*/
static ssize_t ping_pong_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
static ssize_t ping_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct qmi_handle *qmi = dev_get_drvdata(dev);
struct qmi_handle *qmi = file->private_data;
struct test_ping_req_msg_v01 req = {0};
struct qmi_txn txn;
int ret;
@ -321,8 +321,7 @@ static ssize_t ping_pong_store(struct device *dev,
if (ret < 0)
return ret;
ret = qmi_send_message(qmi, NULL, &txn,
QMI_REQUEST,
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);
@ -337,7 +336,11 @@ static ssize_t ping_pong_store(struct device *dev,
return count;
}
static DEVICE_ATTR_WO(ping_pong);
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)
@ -358,11 +361,11 @@ static void ping_pong_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
}
/*
* data_store() - data attribute store handler
* @dev: sample device context
* @attr: the data attribute
* @buf: buffer with message to encode
* @count: length of @buf
* 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
*
* Returns @count, or negative errno on failure.
*
@ -371,10 +374,11 @@ static void ping_pong_cb(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
* transaction. It serves as an example of how to have the QMI helpers decode a
* transaction response into a provided object automatically.
*/
static ssize_t data_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
static ssize_t data_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
struct qmi_handle *qmi = dev_get_drvdata(dev);
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;
@ -391,51 +395,50 @@ static ssize_t data_store(struct device *dev, struct device_attribute *attr,
}
req->data_len = min_t(size_t, sizeof(req->data), count);
memcpy(req->data, buf, req->data_len);
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) {
count = ret;
if (ret < 0)
goto out;
}
ret = qmi_send_message(qmi, NULL, &txn,
QMI_REQUEST,
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);
count = ret;
goto out;
}
ret = qmi_txn_wait(&txn, 5 * HZ);
if (ret < 0) {
count = ret;
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");
count = -EINVAL;
ret = -EINVAL;
goto out;
}
ret = count;
out:
kfree(resp);
kfree(req);
return count;
return ret;
}
static DEVICE_ATTR_WO(data);
static struct attribute *qrtr_dev_attrs[] = {
&dev_attr_ping_pong.attr,
&dev_attr_data.attr,
NULL
static const struct file_operations data_fops = {
.open = simple_open,
.write = data_write,
};
ATTRIBUTE_GROUPS(qrtr_dev);
static struct qmi_msg_handler qrtr_sample_handlers[] = {
static struct qmi_msg_handler qmi_sample_handlers[] = {
{
.type = QMI_RESPONSE,
.msg_id = TEST_PING_REQ_MSG_ID_V01,
@ -446,67 +449,106 @@ static struct qmi_msg_handler qrtr_sample_handlers[] = {
{}
};
static int qrtr_sample_probe(struct platform_device *pdev)
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 qrtr_handle *qrtr;
struct qmi_handle *qmi;
struct sockaddr_qrtr *sq;
struct qmi_sample *sample;
char path[20];
int ret;
qmi = devm_kzalloc(&pdev->dev, sizeof(*qmi), GFP_KERNEL);
if (!qmi)
sample = devm_kzalloc(&pdev->dev, sizeof(*sample), GFP_KERNEL);
if (!sample)
return -ENOMEM;
qrtr = &qmi->qrtr;
ret = qmi_client_init(qmi, TEST_DATA_REQ_MAX_MSG_LEN_V01,
qrtr_sample_handlers);
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(qrtr->sock, (struct sockaddr *)sq,
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");
qmi_client_release(qmi);
return ret;
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;
}
platform_set_drvdata(pdev, qmi);
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 qrtr_sample_remove(struct platform_device *pdev)
static int qmi_sample_remove(struct platform_device *pdev)
{
struct qmi_handle *qmi = platform_get_drvdata(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_client_release(qmi);
qmi_handle_release(&sample->qmi);
return 0;
}
static struct platform_driver qrtr_sample_driver = {
.probe = qrtr_sample_probe,
.remove = qrtr_sample_remove,
static struct platform_driver qmi_sample_driver = {
.probe = qmi_sample_probe,
.remove = qmi_sample_remove,
.driver = {
.name = "qrtr_sample_client",
.name = "qmi_sample_client",
},
};
static int qrtr_sample_new_server(struct qrtr_handle *qrtr,
struct qrtr_service *service)
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 };
char name[32];
int ret;
snprintf(name, sizeof(name), "qrtr_sample_client@%d:%d",
service->node, service->port);
pdev = platform_device_alloc(name, PLATFORM_DEVID_NONE);
pdev = platform_device_alloc("qmi_sample_client", PLATFORM_DEVID_AUTO);
if (!pdev)
return -ENOMEM;
@ -514,13 +556,11 @@ static int qrtr_sample_new_server(struct qrtr_handle *qrtr,
if (ret)
goto err_put_device;
pdev->dev.groups = qrtr_dev_groups;
pdev->driver_override = (char *)qrtr_sample_driver.driver.name;
ret = platform_device_add(pdev);
if (ret)
goto err_put_device;
service->cookie = pdev;
service->priv = pdev;
return 0;
@ -530,74 +570,62 @@ err_put_device:
return ret;
}
static void qrtr_sample_del_server(struct qrtr_handle *qrtr,
struct qrtr_service *service)
static void qmi_sample_del_server(struct qmi_handle *qmi,
struct qmi_service *service)
{
struct platform_device *pdev = service->cookie;
struct platform_device *pdev = service->priv;
platform_device_unregister(pdev);
}
static struct qrtr_handle lookup_client;
static struct qrtr_handle_ops lookup_ops;
static void qrtr_sample_net_reset_work(struct work_struct *work)
{
int ret;
qrtr_client_release(&lookup_client);
static struct qmi_handle lookup_client;
ret = qrtr_client_init(&lookup_client, 0, &lookup_ops);
if (ret < 0)
return;
qrtr_client_new_lookup(&lookup_client, 15, 0);
}
static DECLARE_WORK(net_reset_work, qrtr_sample_net_reset_work);
static void qrtr_sample_net_reset(struct qrtr_handle *qrtr)
{
schedule_work(&net_reset_work);
}
static struct qrtr_handle_ops lookup_ops = {
.new_server = qrtr_sample_new_server,
.del_server = qrtr_sample_del_server,
.net_reset = qrtr_sample_net_reset,
static struct qmi_ops lookup_ops = {
.new_server = qmi_sample_new_server,
.del_server = qmi_sample_del_server,
};
static int qrtr_sample_init(void)
static int qmi_sample_init(void)
{
int ret;
ret = platform_driver_register(&qrtr_sample_driver);
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)
return ret;
goto err_remove_debug_dir;
ret = qrtr_client_init(&lookup_client, 0, &lookup_ops);
ret = qmi_handle_init(&lookup_client, 0, &lookup_ops, NULL);
if (ret < 0)
goto err_unregister_driver;
qrtr_client_new_lookup(&lookup_client, 15, 0);
qmi_add_lookup(&lookup_client, 15, 0, 0);
return 0;
err_unregister_driver:
platform_driver_unregister(&qrtr_sample_driver);
platform_driver_unregister(&qmi_sample_driver);
err_remove_debug_dir:
debugfs_remove(qmi_debug_dir);
return ret;
}
static void qrtr_sample_exit(void)
static void qmi_sample_exit(void)
{
qrtr_client_release(&lookup_client);
qmi_handle_release(&lookup_client);
platform_driver_unregister(&qmi_sample_driver);
platform_driver_unregister(&qrtr_sample_driver);
debugfs_remove(qmi_debug_dir);
}
module_init(qrtr_sample_init);
module_exit(qrtr_sample_exit);
module_init(qmi_sample_init);
module_exit(qmi_sample_exit);
MODULE_DESCRIPTION("Sample QRTR client driver");
MODULE_DESCRIPTION("Sample QMI client driver");
MODULE_LICENSE("GPL v2");

@ -1 +0,0 @@
obj-$(CONFIG_SAMPLE_QRTR_CLIENT) += qrtr_sample_client.o
Loading…
Cancel
Save