2017-06-26 19:02:28 +03:00
// 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 ,
} ;
2020-11-25 00:29:54 +03:00
static const struct qmi_msg_handler qmi_sample_handlers [ ] = {
2017-06-26 19:02:28 +03:00
{
. 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 ;
2020-11-25 00:29:54 +03:00
static const struct qmi_ops lookup_ops = {
2017-06-26 19:02:28 +03:00
. 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 " ) ;