Qualcomm ARM Based Driver Updates for v4.16 - Redo
* Fix error handling code in SMP2P probe * Update SMP2P to use ACPS as mailbox client * Add QMI support * Fixups for Qualcomm SCM * Fix licensing on rmtfs_mem * Correct SMSM child node lookup * Populate firmware nodes during platform init -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABAgAGBQJaToihAAoJEFKiBbHx2RXVvj8P/1ZfQNQpN0b2mDJFZxGIislM JEZPOrdlIi3n9JMCNbp3BHc9kI8YM5XQPW08WfsbRZ+o+2vtGnHs4hF/a4Gw102f XHTAVjnT9HmoCrjFMCHF/1qZdFFIzCJgQnb/+cJ3hK2rnoJO2CCHMA8K1KhDlQ69 NFNwjWhx3lF5KrOm4xdStN+rjITpNZbSiMt6jMSHK1Up/RLz4SK3U2eY1AeqsP1p drMZVEg2N90C3C+fvBE80Iaq0Ncu3wg5MLoK3kCBGB/XidVZ+sgNJh4rfnWUfdnq JkT9kqiipYmBm2ONc2NiGqzrIwZCj0rx2Z4m7huSZlwRp7w92n9N0SYxUht2Ejpb AjenPJyqrRcuR7pF9mOpDPapZER7+11gXSGBVT8hPDPWkQvrjpHRArvTDJ0dckTQ iPEfAva7z/Z6Agxjzbus0N/TL3Hl59WkN8+2QZVT+ZBz4+5FVhxS3FQ7b71OCc4q 11QAbJk8bklpwQCiBFgz922vFa5XdnWq/1aWXFeIrkNhuhfAmM+pB99PrIib+GZM ZpU/1l9oynjlmgbuMuQDnoTjEMvtcM4so1Z/qXc56HIhTQfRo8gWIyCw50YGTO33 oFS1b7O9ilgTGcMpLlC9Vv7WHo0sXHpZ1wG6pLNQ7c1RSsBHHmx4gYYezf08cOeN 1Axa8G4s3dCx5kFyke5a =vdvo -----END PGP SIGNATURE----- Merge tag 'qcom-drivers-for-4.16' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/agross/linux into next/drivers Pull "Qualcomm ARM Based Driver Updates for v4.16 - Redo" from Andy Gross: * Fix error handling code in SMP2P probe * Update SMP2P to use ACPS as mailbox client * Add QMI support * Fixups for Qualcomm SCM * Fix licensing on rmtfs_mem * Correct SMSM child node lookup * Populate firmware nodes during platform init * tag 'qcom-drivers-for-4.16' of ssh://gitolite.kernel.org/pub/scm/linux/kernel/git/agross/linux: of: platform: populate /firmware/ node from of_platform_default_populate_init() soc: qcom: smp2p: Use common error handling code in qcom_smp2p_probe() soc: qcom: Introduce QMI helpers soc: qcom: Introduce QMI encoder/decoder firmware: qcom_scm: Add dependent headers to qcom_scm.h soc: qcom: smp2p: Access APCS as mailbox client soc: qcom: rmtfs_mem: add missing MODULE_DESCRIPTION/AUTHOR/LICENSE soc: qcom: smsm: fix child-node lookup firmware: qcom_scm: drop redandant of_platform_populate
This commit is contained in:
Коммит
830ebd37c5
|
@ -17,9 +17,15 @@ processor ID) and a string identifier.
|
|||
Value type: <prop-encoded-array>
|
||||
Definition: one entry specifying the smp2p notification interrupt
|
||||
|
||||
- qcom,ipc:
|
||||
- mboxes:
|
||||
Usage: required
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: reference to the associated doorbell in APCS, as described
|
||||
in mailbox/mailbox.txt
|
||||
|
||||
- qcom,ipc:
|
||||
Usage: required, unless mboxes is specified
|
||||
Value type: <prop-encoded-array>
|
||||
Definition: three entries specifying the outgoing ipc bit used for
|
||||
signaling the remote end of the smp2p edge:
|
||||
- phandle to a syscon node representing the apcs registers
|
||||
|
|
|
@ -622,30 +622,6 @@ static struct platform_driver qcom_scm_driver = {
|
|||
|
||||
static int __init qcom_scm_init(void)
|
||||
{
|
||||
struct device_node *np, *fw_np;
|
||||
int ret;
|
||||
|
||||
fw_np = of_find_node_by_name(NULL, "firmware");
|
||||
|
||||
if (!fw_np)
|
||||
return -ENODEV;
|
||||
|
||||
np = of_find_matching_node(fw_np, qcom_scm_dt_match);
|
||||
|
||||
if (!np) {
|
||||
of_node_put(fw_np);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
of_node_put(np);
|
||||
|
||||
ret = of_platform_populate(fw_np, qcom_scm_dt_match, NULL, NULL);
|
||||
|
||||
of_node_put(fw_np);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return platform_driver_register(&qcom_scm_driver);
|
||||
}
|
||||
subsys_initcall(qcom_scm_init);
|
||||
|
|
|
@ -518,6 +518,10 @@ static int __init of_platform_default_populate_init(void)
|
|||
for_each_matching_node(node, reserved_mem_matches)
|
||||
of_platform_device_create(node, NULL, NULL);
|
||||
|
||||
node = of_find_node_by_path("/firmware");
|
||||
if (node)
|
||||
of_platform_populate(node, NULL, NULL, NULL);
|
||||
|
||||
/* Populate everything else. */
|
||||
of_platform_default_populate(NULL, NULL, NULL);
|
||||
|
||||
|
|
|
@ -35,6 +35,15 @@ config QCOM_PM
|
|||
modes. It interface with various system drivers to put the cores in
|
||||
low power modes.
|
||||
|
||||
config QCOM_QMI_HELPERS
|
||||
tristate
|
||||
depends on ARCH_QCOM
|
||||
help
|
||||
Helper library for handling QMI encoded messages. QMI encoded
|
||||
messages are used in communication between the majority of QRTR
|
||||
clients and this helpers provide the common functionality needed for
|
||||
doing this from a kernel driver.
|
||||
|
||||
config QCOM_RMTFS_MEM
|
||||
tristate "Qualcomm Remote Filesystem memory driver"
|
||||
depends on ARCH_QCOM
|
||||
|
@ -75,6 +84,7 @@ config QCOM_SMEM_STATE
|
|||
|
||||
config QCOM_SMP2P
|
||||
tristate "Qualcomm Shared Memory Point to Point support"
|
||||
depends on MAILBOX
|
||||
depends on QCOM_SMEM
|
||||
select QCOM_SMEM_STATE
|
||||
help
|
||||
|
|
|
@ -3,6 +3,8 @@ obj-$(CONFIG_QCOM_GLINK_SSR) += glink_ssr.o
|
|||
obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
|
||||
obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o
|
||||
obj-$(CONFIG_QCOM_PM) += spm.o
|
||||
obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o
|
||||
qmi_helpers-y += qmi_encdec.o qmi_interface.o
|
||||
obj-$(CONFIG_QCOM_RMTFS_MEM) += rmtfs_mem.o
|
||||
obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o
|
||||
obj-$(CONFIG_QCOM_SMEM) += smem.o
|
||||
|
|
|
@ -0,0 +1,816 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
|
||||
* Copyright (C) 2017 Linaro Ltd.
|
||||
*/
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/soc/qcom/qmi.h>
|
||||
|
||||
#define QMI_ENCDEC_ENCODE_TLV(type, length, p_dst) do { \
|
||||
*p_dst++ = type; \
|
||||
*p_dst++ = ((u8)((length) & 0xFF)); \
|
||||
*p_dst++ = ((u8)(((length) >> 8) & 0xFF)); \
|
||||
} while (0)
|
||||
|
||||
#define QMI_ENCDEC_DECODE_TLV(p_type, p_length, p_src) do { \
|
||||
*p_type = (u8)*p_src++; \
|
||||
*p_length = (u8)*p_src++; \
|
||||
*p_length |= ((u8)*p_src) << 8; \
|
||||
} while (0)
|
||||
|
||||
#define QMI_ENCDEC_ENCODE_N_BYTES(p_dst, p_src, size) \
|
||||
do { \
|
||||
memcpy(p_dst, p_src, size); \
|
||||
p_dst = (u8 *)p_dst + size; \
|
||||
p_src = (u8 *)p_src + size; \
|
||||
} while (0)
|
||||
|
||||
#define QMI_ENCDEC_DECODE_N_BYTES(p_dst, p_src, size) \
|
||||
do { \
|
||||
memcpy(p_dst, p_src, size); \
|
||||
p_dst = (u8 *)p_dst + size; \
|
||||
p_src = (u8 *)p_src + size; \
|
||||
} while (0)
|
||||
|
||||
#define UPDATE_ENCODE_VARIABLES(temp_si, buf_dst, \
|
||||
encoded_bytes, tlv_len, encode_tlv, rc) \
|
||||
do { \
|
||||
buf_dst = (u8 *)buf_dst + rc; \
|
||||
encoded_bytes += rc; \
|
||||
tlv_len += rc; \
|
||||
temp_si = temp_si + 1; \
|
||||
encode_tlv = 1; \
|
||||
} while (0)
|
||||
|
||||
#define UPDATE_DECODE_VARIABLES(buf_src, decoded_bytes, rc) \
|
||||
do { \
|
||||
buf_src = (u8 *)buf_src + rc; \
|
||||
decoded_bytes += rc; \
|
||||
} while (0)
|
||||
|
||||
#define TLV_LEN_SIZE sizeof(u16)
|
||||
#define TLV_TYPE_SIZE sizeof(u8)
|
||||
#define OPTIONAL_TLV_TYPE_START 0x10
|
||||
|
||||
static int qmi_encode(struct qmi_elem_info *ei_array, void *out_buf,
|
||||
const void *in_c_struct, u32 out_buf_len,
|
||||
int enc_level);
|
||||
|
||||
static int qmi_decode(struct qmi_elem_info *ei_array, void *out_c_struct,
|
||||
const void *in_buf, u32 in_buf_len, int dec_level);
|
||||
|
||||
/**
|
||||
* skip_to_next_elem() - Skip to next element in the structure to be encoded
|
||||
* @ei_array: Struct info describing the element to be skipped.
|
||||
* @level: Depth level of encoding/decoding to identify nested structures.
|
||||
*
|
||||
* This function is used while encoding optional elements. If the flag
|
||||
* corresponding to an optional element is not set, then encoding the
|
||||
* optional element can be skipped. This function can be used to perform
|
||||
* that operation.
|
||||
*
|
||||
* Return: struct info of the next element that can be encoded.
|
||||
*/
|
||||
static struct qmi_elem_info *skip_to_next_elem(struct qmi_elem_info *ei_array,
|
||||
int level)
|
||||
{
|
||||
struct qmi_elem_info *temp_ei = ei_array;
|
||||
u8 tlv_type;
|
||||
|
||||
if (level > 1) {
|
||||
temp_ei = temp_ei + 1;
|
||||
} else {
|
||||
do {
|
||||
tlv_type = temp_ei->tlv_type;
|
||||
temp_ei = temp_ei + 1;
|
||||
} while (tlv_type == temp_ei->tlv_type);
|
||||
}
|
||||
|
||||
return temp_ei;
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_calc_min_msg_len() - Calculate the minimum length of a QMI message
|
||||
* @ei_array: Struct info array describing the structure.
|
||||
* @level: Level to identify the depth of the nested structures.
|
||||
*
|
||||
* Return: Expected minimum length of the QMI message or 0 on error.
|
||||
*/
|
||||
static int qmi_calc_min_msg_len(struct qmi_elem_info *ei_array,
|
||||
int level)
|
||||
{
|
||||
int min_msg_len = 0;
|
||||
struct qmi_elem_info *temp_ei = ei_array;
|
||||
|
||||
if (!ei_array)
|
||||
return min_msg_len;
|
||||
|
||||
while (temp_ei->data_type != QMI_EOTI) {
|
||||
/* Optional elements do not count in minimum length */
|
||||
if (temp_ei->data_type == QMI_OPT_FLAG) {
|
||||
temp_ei = skip_to_next_elem(temp_ei, level);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (temp_ei->data_type == QMI_DATA_LEN) {
|
||||
min_msg_len += (temp_ei->elem_size == sizeof(u8) ?
|
||||
sizeof(u8) : sizeof(u16));
|
||||
temp_ei++;
|
||||
continue;
|
||||
} else if (temp_ei->data_type == QMI_STRUCT) {
|
||||
min_msg_len += qmi_calc_min_msg_len(temp_ei->ei_array,
|
||||
(level + 1));
|
||||
temp_ei++;
|
||||
} else if (temp_ei->data_type == QMI_STRING) {
|
||||
if (level > 1)
|
||||
min_msg_len += temp_ei->elem_len <= U8_MAX ?
|
||||
sizeof(u8) : sizeof(u16);
|
||||
min_msg_len += temp_ei->elem_len * temp_ei->elem_size;
|
||||
temp_ei++;
|
||||
} else {
|
||||
min_msg_len += (temp_ei->elem_len * temp_ei->elem_size);
|
||||
temp_ei++;
|
||||
}
|
||||
|
||||
/*
|
||||
* Type & Length info. not prepended for elements in the
|
||||
* nested structure.
|
||||
*/
|
||||
if (level == 1)
|
||||
min_msg_len += (TLV_TYPE_SIZE + TLV_LEN_SIZE);
|
||||
}
|
||||
|
||||
return min_msg_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_encode_basic_elem() - Encodes elements of basic/primary data type
|
||||
* @buf_dst: Buffer to store the encoded information.
|
||||
* @buf_src: Buffer containing the elements to be encoded.
|
||||
* @elem_len: Number of elements, in the buf_src, to be encoded.
|
||||
* @elem_size: Size of a single instance of the element to be encoded.
|
||||
*
|
||||
* This function encodes the "elem_len" number of data elements, each of
|
||||
* size "elem_size" bytes from the source buffer "buf_src" and stores the
|
||||
* encoded information in the destination buffer "buf_dst". The elements are
|
||||
* of primary data type which include u8 - u64 or similar. This
|
||||
* function returns the number of bytes of encoded information.
|
||||
*
|
||||
* Return: The number of bytes of encoded information.
|
||||
*/
|
||||
static int qmi_encode_basic_elem(void *buf_dst, const void *buf_src,
|
||||
u32 elem_len, u32 elem_size)
|
||||
{
|
||||
u32 i, rc = 0;
|
||||
|
||||
for (i = 0; i < elem_len; i++) {
|
||||
QMI_ENCDEC_ENCODE_N_BYTES(buf_dst, buf_src, elem_size);
|
||||
rc += elem_size;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_encode_struct_elem() - Encodes elements of struct data type
|
||||
* @ei_array: Struct info array descibing the struct element.
|
||||
* @buf_dst: Buffer to store the encoded information.
|
||||
* @buf_src: Buffer containing the elements to be encoded.
|
||||
* @elem_len: Number of elements, in the buf_src, to be encoded.
|
||||
* @out_buf_len: Available space in the encode buffer.
|
||||
* @enc_level: Depth of the nested structure from the main structure.
|
||||
*
|
||||
* This function encodes the "elem_len" number of struct elements, each of
|
||||
* size "ei_array->elem_size" bytes from the source buffer "buf_src" and
|
||||
* stores the encoded information in the destination buffer "buf_dst". The
|
||||
* elements are of struct data type which includes any C structure. This
|
||||
* function returns the number of bytes of encoded information.
|
||||
*
|
||||
* Return: The number of bytes of encoded information on success or negative
|
||||
* errno on error.
|
||||
*/
|
||||
static int qmi_encode_struct_elem(struct qmi_elem_info *ei_array,
|
||||
void *buf_dst, const void *buf_src,
|
||||
u32 elem_len, u32 out_buf_len,
|
||||
int enc_level)
|
||||
{
|
||||
int i, rc, encoded_bytes = 0;
|
||||
struct qmi_elem_info *temp_ei = ei_array;
|
||||
|
||||
for (i = 0; i < elem_len; i++) {
|
||||
rc = qmi_encode(temp_ei->ei_array, buf_dst, buf_src,
|
||||
out_buf_len - encoded_bytes, enc_level);
|
||||
if (rc < 0) {
|
||||
pr_err("%s: STRUCT Encode failure\n", __func__);
|
||||
return rc;
|
||||
}
|
||||
buf_dst = buf_dst + rc;
|
||||
buf_src = buf_src + temp_ei->elem_size;
|
||||
encoded_bytes += rc;
|
||||
}
|
||||
|
||||
return encoded_bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_encode_string_elem() - Encodes elements of string data type
|
||||
* @ei_array: Struct info array descibing the string element.
|
||||
* @buf_dst: Buffer to store the encoded information.
|
||||
* @buf_src: Buffer containing the elements to be encoded.
|
||||
* @out_buf_len: Available space in the encode buffer.
|
||||
* @enc_level: Depth of the string element from the main structure.
|
||||
*
|
||||
* This function encodes a string element of maximum length "ei_array->elem_len"
|
||||
* bytes from the source buffer "buf_src" and stores the encoded information in
|
||||
* the destination buffer "buf_dst". This function returns the number of bytes
|
||||
* of encoded information.
|
||||
*
|
||||
* Return: The number of bytes of encoded information on success or negative
|
||||
* errno on error.
|
||||
*/
|
||||
static int qmi_encode_string_elem(struct qmi_elem_info *ei_array,
|
||||
void *buf_dst, const void *buf_src,
|
||||
u32 out_buf_len, int enc_level)
|
||||
{
|
||||
int rc;
|
||||
int encoded_bytes = 0;
|
||||
struct qmi_elem_info *temp_ei = ei_array;
|
||||
u32 string_len = 0;
|
||||
u32 string_len_sz = 0;
|
||||
|
||||
string_len = strlen(buf_src);
|
||||
string_len_sz = temp_ei->elem_len <= U8_MAX ?
|
||||
sizeof(u8) : sizeof(u16);
|
||||
if (string_len > temp_ei->elem_len) {
|
||||
pr_err("%s: String to be encoded is longer - %d > %d\n",
|
||||
__func__, string_len, temp_ei->elem_len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (enc_level == 1) {
|
||||
if (string_len + TLV_LEN_SIZE + TLV_TYPE_SIZE >
|
||||
out_buf_len) {
|
||||
pr_err("%s: Output len %d > Out Buf len %d\n",
|
||||
__func__, string_len, out_buf_len);
|
||||
return -ETOOSMALL;
|
||||
}
|
||||
} else {
|
||||
if (string_len + string_len_sz > out_buf_len) {
|
||||
pr_err("%s: Output len %d > Out Buf len %d\n",
|
||||
__func__, string_len, out_buf_len);
|
||||
return -ETOOSMALL;
|
||||
}
|
||||
rc = qmi_encode_basic_elem(buf_dst, &string_len,
|
||||
1, string_len_sz);
|
||||
encoded_bytes += rc;
|
||||
}
|
||||
|
||||
rc = qmi_encode_basic_elem(buf_dst + encoded_bytes, buf_src,
|
||||
string_len, temp_ei->elem_size);
|
||||
encoded_bytes += rc;
|
||||
|
||||
return encoded_bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_encode() - Core Encode Function
|
||||
* @ei_array: Struct info array describing the structure to be encoded.
|
||||
* @out_buf: Buffer to hold the encoded QMI message.
|
||||
* @in_c_struct: Pointer to the C structure to be encoded.
|
||||
* @out_buf_len: Available space in the encode buffer.
|
||||
* @enc_level: Encode level to indicate the depth of the nested structure,
|
||||
* within the main structure, being encoded.
|
||||
*
|
||||
* Return: The number of bytes of encoded information on success or negative
|
||||
* errno on error.
|
||||
*/
|
||||
static int qmi_encode(struct qmi_elem_info *ei_array, void *out_buf,
|
||||
const void *in_c_struct, u32 out_buf_len,
|
||||
int enc_level)
|
||||
{
|
||||
struct qmi_elem_info *temp_ei = ei_array;
|
||||
u8 opt_flag_value = 0;
|
||||
u32 data_len_value = 0, data_len_sz;
|
||||
u8 *buf_dst = (u8 *)out_buf;
|
||||
u8 *tlv_pointer;
|
||||
u32 tlv_len;
|
||||
u8 tlv_type;
|
||||
u32 encoded_bytes = 0;
|
||||
const void *buf_src;
|
||||
int encode_tlv = 0;
|
||||
int rc;
|
||||
|
||||
if (!ei_array)
|
||||
return 0;
|
||||
|
||||
tlv_pointer = buf_dst;
|
||||
tlv_len = 0;
|
||||
if (enc_level == 1)
|
||||
buf_dst = buf_dst + (TLV_LEN_SIZE + TLV_TYPE_SIZE);
|
||||
|
||||
while (temp_ei->data_type != QMI_EOTI) {
|
||||
buf_src = in_c_struct + temp_ei->offset;
|
||||
tlv_type = temp_ei->tlv_type;
|
||||
|
||||
if (temp_ei->array_type == NO_ARRAY) {
|
||||
data_len_value = 1;
|
||||
} else if (temp_ei->array_type == STATIC_ARRAY) {
|
||||
data_len_value = temp_ei->elem_len;
|
||||
} else if (data_len_value <= 0 ||
|
||||
temp_ei->elem_len < data_len_value) {
|
||||
pr_err("%s: Invalid data length\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (temp_ei->data_type) {
|
||||
case QMI_OPT_FLAG:
|
||||
rc = qmi_encode_basic_elem(&opt_flag_value, buf_src,
|
||||
1, sizeof(u8));
|
||||
if (opt_flag_value)
|
||||
temp_ei = temp_ei + 1;
|
||||
else
|
||||
temp_ei = skip_to_next_elem(temp_ei, enc_level);
|
||||
break;
|
||||
|
||||
case QMI_DATA_LEN:
|
||||
memcpy(&data_len_value, buf_src, temp_ei->elem_size);
|
||||
data_len_sz = temp_ei->elem_size == sizeof(u8) ?
|
||||
sizeof(u8) : sizeof(u16);
|
||||
/* Check to avoid out of range buffer access */
|
||||
if ((data_len_sz + encoded_bytes + TLV_LEN_SIZE +
|
||||
TLV_TYPE_SIZE) > out_buf_len) {
|
||||
pr_err("%s: Too Small Buffer @DATA_LEN\n",
|
||||
__func__);
|
||||
return -ETOOSMALL;
|
||||
}
|
||||
rc = qmi_encode_basic_elem(buf_dst, &data_len_value,
|
||||
1, data_len_sz);
|
||||
UPDATE_ENCODE_VARIABLES(temp_ei, buf_dst,
|
||||
encoded_bytes, tlv_len,
|
||||
encode_tlv, rc);
|
||||
if (!data_len_value)
|
||||
temp_ei = skip_to_next_elem(temp_ei, enc_level);
|
||||
else
|
||||
encode_tlv = 0;
|
||||
break;
|
||||
|
||||
case QMI_UNSIGNED_1_BYTE:
|
||||
case QMI_UNSIGNED_2_BYTE:
|
||||
case QMI_UNSIGNED_4_BYTE:
|
||||
case QMI_UNSIGNED_8_BYTE:
|
||||
case QMI_SIGNED_2_BYTE_ENUM:
|
||||
case QMI_SIGNED_4_BYTE_ENUM:
|
||||
/* Check to avoid out of range buffer access */
|
||||
if (((data_len_value * temp_ei->elem_size) +
|
||||
encoded_bytes + TLV_LEN_SIZE + TLV_TYPE_SIZE) >
|
||||
out_buf_len) {
|
||||
pr_err("%s: Too Small Buffer @data_type:%d\n",
|
||||
__func__, temp_ei->data_type);
|
||||
return -ETOOSMALL;
|
||||
}
|
||||
rc = qmi_encode_basic_elem(buf_dst, buf_src,
|
||||
data_len_value,
|
||||
temp_ei->elem_size);
|
||||
UPDATE_ENCODE_VARIABLES(temp_ei, buf_dst,
|
||||
encoded_bytes, tlv_len,
|
||||
encode_tlv, rc);
|
||||
break;
|
||||
|
||||
case QMI_STRUCT:
|
||||
rc = qmi_encode_struct_elem(temp_ei, buf_dst, buf_src,
|
||||
data_len_value,
|
||||
out_buf_len - encoded_bytes,
|
||||
enc_level + 1);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
UPDATE_ENCODE_VARIABLES(temp_ei, buf_dst,
|
||||
encoded_bytes, tlv_len,
|
||||
encode_tlv, rc);
|
||||
break;
|
||||
|
||||
case QMI_STRING:
|
||||
rc = qmi_encode_string_elem(temp_ei, buf_dst, buf_src,
|
||||
out_buf_len - encoded_bytes,
|
||||
enc_level);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
UPDATE_ENCODE_VARIABLES(temp_ei, buf_dst,
|
||||
encoded_bytes, tlv_len,
|
||||
encode_tlv, rc);
|
||||
break;
|
||||
default:
|
||||
pr_err("%s: Unrecognized data type\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (encode_tlv && enc_level == 1) {
|
||||
QMI_ENCDEC_ENCODE_TLV(tlv_type, tlv_len, tlv_pointer);
|
||||
encoded_bytes += (TLV_TYPE_SIZE + TLV_LEN_SIZE);
|
||||
tlv_pointer = buf_dst;
|
||||
tlv_len = 0;
|
||||
buf_dst = buf_dst + TLV_LEN_SIZE + TLV_TYPE_SIZE;
|
||||
encode_tlv = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return encoded_bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_decode_basic_elem() - Decodes elements of basic/primary data type
|
||||
* @buf_dst: Buffer to store the decoded element.
|
||||
* @buf_src: Buffer containing the elements in QMI wire format.
|
||||
* @elem_len: Number of elements to be decoded.
|
||||
* @elem_size: Size of a single instance of the element to be decoded.
|
||||
*
|
||||
* This function decodes the "elem_len" number of elements in QMI wire format,
|
||||
* each of size "elem_size" bytes from the source buffer "buf_src" and stores
|
||||
* the decoded elements in the destination buffer "buf_dst". The elements are
|
||||
* of primary data type which include u8 - u64 or similar. This
|
||||
* function returns the number of bytes of decoded information.
|
||||
*
|
||||
* Return: The total size of the decoded data elements, in bytes.
|
||||
*/
|
||||
static int qmi_decode_basic_elem(void *buf_dst, const void *buf_src,
|
||||
u32 elem_len, u32 elem_size)
|
||||
{
|
||||
u32 i, rc = 0;
|
||||
|
||||
for (i = 0; i < elem_len; i++) {
|
||||
QMI_ENCDEC_DECODE_N_BYTES(buf_dst, buf_src, elem_size);
|
||||
rc += elem_size;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_decode_struct_elem() - Decodes elements of struct data type
|
||||
* @ei_array: Struct info array descibing the struct element.
|
||||
* @buf_dst: Buffer to store the decoded element.
|
||||
* @buf_src: Buffer containing the elements in QMI wire format.
|
||||
* @elem_len: Number of elements to be decoded.
|
||||
* @tlv_len: Total size of the encoded inforation corresponding to
|
||||
* this struct element.
|
||||
* @dec_level: Depth of the nested structure from the main structure.
|
||||
*
|
||||
* This function decodes the "elem_len" number of elements in QMI wire format,
|
||||
* each of size "(tlv_len/elem_len)" bytes from the source buffer "buf_src"
|
||||
* and stores the decoded elements in the destination buffer "buf_dst". The
|
||||
* elements are of struct data type which includes any C structure. This
|
||||
* function returns the number of bytes of decoded information.
|
||||
*
|
||||
* Return: The total size of the decoded data elements on success, negative
|
||||
* errno on error.
|
||||
*/
|
||||
static int qmi_decode_struct_elem(struct qmi_elem_info *ei_array,
|
||||
void *buf_dst, const void *buf_src,
|
||||
u32 elem_len, u32 tlv_len,
|
||||
int dec_level)
|
||||
{
|
||||
int i, rc, decoded_bytes = 0;
|
||||
struct qmi_elem_info *temp_ei = ei_array;
|
||||
|
||||
for (i = 0; i < elem_len && decoded_bytes < tlv_len; i++) {
|
||||
rc = qmi_decode(temp_ei->ei_array, buf_dst, buf_src,
|
||||
tlv_len - decoded_bytes, dec_level);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
buf_src = buf_src + rc;
|
||||
buf_dst = buf_dst + temp_ei->elem_size;
|
||||
decoded_bytes += rc;
|
||||
}
|
||||
|
||||
if ((dec_level <= 2 && decoded_bytes != tlv_len) ||
|
||||
(dec_level > 2 && (i < elem_len || decoded_bytes > tlv_len))) {
|
||||
pr_err("%s: Fault in decoding: dl(%d), db(%d), tl(%d), i(%d), el(%d)\n",
|
||||
__func__, dec_level, decoded_bytes, tlv_len,
|
||||
i, elem_len);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
return decoded_bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_decode_string_elem() - Decodes elements of string data type
|
||||
* @ei_array: Struct info array descibing the string element.
|
||||
* @buf_dst: Buffer to store the decoded element.
|
||||
* @buf_src: Buffer containing the elements in QMI wire format.
|
||||
* @tlv_len: Total size of the encoded inforation corresponding to
|
||||
* this string element.
|
||||
* @dec_level: Depth of the string element from the main structure.
|
||||
*
|
||||
* This function decodes the string element of maximum length
|
||||
* "ei_array->elem_len" from the source buffer "buf_src" and puts it into
|
||||
* the destination buffer "buf_dst". This function returns number of bytes
|
||||
* decoded from the input buffer.
|
||||
*
|
||||
* Return: The total size of the decoded data elements on success, negative
|
||||
* errno on error.
|
||||
*/
|
||||
static int qmi_decode_string_elem(struct qmi_elem_info *ei_array,
|
||||
void *buf_dst, const void *buf_src,
|
||||
u32 tlv_len, int dec_level)
|
||||
{
|
||||
int rc;
|
||||
int decoded_bytes = 0;
|
||||
u32 string_len = 0;
|
||||
u32 string_len_sz = 0;
|
||||
struct qmi_elem_info *temp_ei = ei_array;
|
||||
|
||||
if (dec_level == 1) {
|
||||
string_len = tlv_len;
|
||||
} else {
|
||||
string_len_sz = temp_ei->elem_len <= U8_MAX ?
|
||||
sizeof(u8) : sizeof(u16);
|
||||
rc = qmi_decode_basic_elem(&string_len, buf_src,
|
||||
1, string_len_sz);
|
||||
decoded_bytes += rc;
|
||||
}
|
||||
|
||||
if (string_len > temp_ei->elem_len) {
|
||||
pr_err("%s: String len %d > Max Len %d\n",
|
||||
__func__, string_len, temp_ei->elem_len);
|
||||
return -ETOOSMALL;
|
||||
} else if (string_len > tlv_len) {
|
||||
pr_err("%s: String len %d > Input Buffer Len %d\n",
|
||||
__func__, string_len, tlv_len);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
rc = qmi_decode_basic_elem(buf_dst, buf_src + decoded_bytes,
|
||||
string_len, temp_ei->elem_size);
|
||||
*((char *)buf_dst + string_len) = '\0';
|
||||
decoded_bytes += rc;
|
||||
|
||||
return decoded_bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* find_ei() - Find element info corresponding to TLV Type
|
||||
* @ei_array: Struct info array of the message being decoded.
|
||||
* @type: TLV Type of the element being searched.
|
||||
*
|
||||
* Every element that got encoded in the QMI message will have a type
|
||||
* information associated with it. While decoding the QMI message,
|
||||
* this function is used to find the struct info regarding the element
|
||||
* that corresponds to the type being decoded.
|
||||
*
|
||||
* Return: Pointer to struct info, if found
|
||||
*/
|
||||
static struct qmi_elem_info *find_ei(struct qmi_elem_info *ei_array,
|
||||
u32 type)
|
||||
{
|
||||
struct qmi_elem_info *temp_ei = ei_array;
|
||||
|
||||
while (temp_ei->data_type != QMI_EOTI) {
|
||||
if (temp_ei->tlv_type == (u8)type)
|
||||
return temp_ei;
|
||||
temp_ei = temp_ei + 1;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_decode() - Core Decode Function
|
||||
* @ei_array: Struct info array describing the structure to be decoded.
|
||||
* @out_c_struct: Buffer to hold the decoded C struct
|
||||
* @in_buf: Buffer containing the QMI message to be decoded
|
||||
* @in_buf_len: Length of the QMI message to be decoded
|
||||
* @dec_level: Decode level to indicate the depth of the nested structure,
|
||||
* within the main structure, being decoded
|
||||
*
|
||||
* Return: The number of bytes of decoded information on success, negative
|
||||
* errno on error.
|
||||
*/
|
||||
static int qmi_decode(struct qmi_elem_info *ei_array, void *out_c_struct,
|
||||
const void *in_buf, u32 in_buf_len,
|
||||
int dec_level)
|
||||
{
|
||||
struct qmi_elem_info *temp_ei = ei_array;
|
||||
u8 opt_flag_value = 1;
|
||||
u32 data_len_value = 0, data_len_sz = 0;
|
||||
u8 *buf_dst = out_c_struct;
|
||||
const u8 *tlv_pointer;
|
||||
u32 tlv_len = 0;
|
||||
u32 tlv_type;
|
||||
u32 decoded_bytes = 0;
|
||||
const void *buf_src = in_buf;
|
||||
int rc;
|
||||
|
||||
while (decoded_bytes < in_buf_len) {
|
||||
if (dec_level >= 2 && temp_ei->data_type == QMI_EOTI)
|
||||
return decoded_bytes;
|
||||
|
||||
if (dec_level == 1) {
|
||||
tlv_pointer = buf_src;
|
||||
QMI_ENCDEC_DECODE_TLV(&tlv_type,
|
||||
&tlv_len, tlv_pointer);
|
||||
buf_src += (TLV_TYPE_SIZE + TLV_LEN_SIZE);
|
||||
decoded_bytes += (TLV_TYPE_SIZE + TLV_LEN_SIZE);
|
||||
temp_ei = find_ei(ei_array, tlv_type);
|
||||
if (!temp_ei && tlv_type < OPTIONAL_TLV_TYPE_START) {
|
||||
pr_err("%s: Inval element info\n", __func__);
|
||||
return -EINVAL;
|
||||
} else if (!temp_ei) {
|
||||
UPDATE_DECODE_VARIABLES(buf_src,
|
||||
decoded_bytes, tlv_len);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* No length information for elements in nested
|
||||
* structures. So use remaining decodable buffer space.
|
||||
*/
|
||||
tlv_len = in_buf_len - decoded_bytes;
|
||||
}
|
||||
|
||||
buf_dst = out_c_struct + temp_ei->offset;
|
||||
if (temp_ei->data_type == QMI_OPT_FLAG) {
|
||||
memcpy(buf_dst, &opt_flag_value, sizeof(u8));
|
||||
temp_ei = temp_ei + 1;
|
||||
buf_dst = out_c_struct + temp_ei->offset;
|
||||
}
|
||||
|
||||
if (temp_ei->data_type == QMI_DATA_LEN) {
|
||||
data_len_sz = temp_ei->elem_size == sizeof(u8) ?
|
||||
sizeof(u8) : sizeof(u16);
|
||||
rc = qmi_decode_basic_elem(&data_len_value, buf_src,
|
||||
1, data_len_sz);
|
||||
memcpy(buf_dst, &data_len_value, sizeof(u32));
|
||||
temp_ei = temp_ei + 1;
|
||||
buf_dst = out_c_struct + temp_ei->offset;
|
||||
tlv_len -= data_len_sz;
|
||||
UPDATE_DECODE_VARIABLES(buf_src, decoded_bytes, rc);
|
||||
}
|
||||
|
||||
if (temp_ei->array_type == NO_ARRAY) {
|
||||
data_len_value = 1;
|
||||
} else if (temp_ei->array_type == STATIC_ARRAY) {
|
||||
data_len_value = temp_ei->elem_len;
|
||||
} else if (data_len_value > temp_ei->elem_len) {
|
||||
pr_err("%s: Data len %d > max spec %d\n",
|
||||
__func__, data_len_value, temp_ei->elem_len);
|
||||
return -ETOOSMALL;
|
||||
}
|
||||
|
||||
switch (temp_ei->data_type) {
|
||||
case QMI_UNSIGNED_1_BYTE:
|
||||
case QMI_UNSIGNED_2_BYTE:
|
||||
case QMI_UNSIGNED_4_BYTE:
|
||||
case QMI_UNSIGNED_8_BYTE:
|
||||
case QMI_SIGNED_2_BYTE_ENUM:
|
||||
case QMI_SIGNED_4_BYTE_ENUM:
|
||||
rc = qmi_decode_basic_elem(buf_dst, buf_src,
|
||||
data_len_value,
|
||||
temp_ei->elem_size);
|
||||
UPDATE_DECODE_VARIABLES(buf_src, decoded_bytes, rc);
|
||||
break;
|
||||
|
||||
case QMI_STRUCT:
|
||||
rc = qmi_decode_struct_elem(temp_ei, buf_dst, buf_src,
|
||||
data_len_value, tlv_len,
|
||||
dec_level + 1);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
UPDATE_DECODE_VARIABLES(buf_src, decoded_bytes, rc);
|
||||
break;
|
||||
|
||||
case QMI_STRING:
|
||||
rc = qmi_decode_string_elem(temp_ei, buf_dst, buf_src,
|
||||
tlv_len, dec_level);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
UPDATE_DECODE_VARIABLES(buf_src, decoded_bytes, rc);
|
||||
break;
|
||||
|
||||
default:
|
||||
pr_err("%s: Unrecognized data type\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
temp_ei = temp_ei + 1;
|
||||
}
|
||||
|
||||
return decoded_bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_encode_message() - Encode C structure as QMI encoded message
|
||||
* @type: Type of QMI message
|
||||
* @msg_id: Message ID of the message
|
||||
* @len: Passed as max length of the message, updated to actual size
|
||||
* @txn_id: Transaction ID
|
||||
* @ei: QMI message descriptor
|
||||
* @c_struct: Reference to structure to encode
|
||||
*
|
||||
* Return: Buffer with encoded message, or negative ERR_PTR() on error
|
||||
*/
|
||||
void *qmi_encode_message(int type, unsigned int msg_id, size_t *len,
|
||||
unsigned int txn_id, struct qmi_elem_info *ei,
|
||||
const void *c_struct)
|
||||
{
|
||||
struct qmi_header *hdr;
|
||||
ssize_t msglen = 0;
|
||||
void *msg;
|
||||
int ret;
|
||||
|
||||
/* Check the possibility of a zero length QMI message */
|
||||
if (!c_struct) {
|
||||
ret = qmi_calc_min_msg_len(ei, 1);
|
||||
if (ret) {
|
||||
pr_err("%s: Calc. len %d != 0, but NULL c_struct\n",
|
||||
__func__, ret);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
msg = kzalloc(sizeof(*hdr) + *len, GFP_KERNEL);
|
||||
if (!msg)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
/* Encode message, if we have a message */
|
||||
if (c_struct) {
|
||||
msglen = qmi_encode(ei, msg + sizeof(*hdr), c_struct, *len, 1);
|
||||
if (msglen < 0) {
|
||||
kfree(msg);
|
||||
return ERR_PTR(msglen);
|
||||
}
|
||||
}
|
||||
|
||||
hdr = msg;
|
||||
hdr->type = type;
|
||||
hdr->txn_id = txn_id;
|
||||
hdr->msg_id = msg_id;
|
||||
hdr->msg_len = msglen;
|
||||
|
||||
*len = sizeof(*hdr) + msglen;
|
||||
|
||||
return msg;
|
||||
}
|
||||
EXPORT_SYMBOL(qmi_encode_message);
|
||||
|
||||
/**
|
||||
* qmi_decode_message() - Decode QMI encoded message to C structure
|
||||
* @buf: Buffer with encoded message
|
||||
* @len: Amount of data in @buf
|
||||
* @ei: QMI message descriptor
|
||||
* @c_struct: Reference to structure to decode into
|
||||
*
|
||||
* Return: The number of bytes of decoded information on success, negative
|
||||
* errno on error.
|
||||
*/
|
||||
int qmi_decode_message(const void *buf, size_t len,
|
||||
struct qmi_elem_info *ei, void *c_struct)
|
||||
{
|
||||
if (!ei)
|
||||
return -EINVAL;
|
||||
|
||||
if (!c_struct || !buf || !len)
|
||||
return -EINVAL;
|
||||
|
||||
return qmi_decode(ei, c_struct, buf + sizeof(struct qmi_header),
|
||||
len - sizeof(struct qmi_header), 1);
|
||||
}
|
||||
EXPORT_SYMBOL(qmi_decode_message);
|
||||
|
||||
/* Common header in all QMI responses */
|
||||
struct qmi_elem_info qmi_response_type_v01_ei[] = {
|
||||
{
|
||||
.data_type = QMI_SIGNED_2_BYTE_ENUM,
|
||||
.elem_len = 1,
|
||||
.elem_size = sizeof(u16),
|
||||
.array_type = NO_ARRAY,
|
||||
.tlv_type = QMI_COMMON_TLV_TYPE,
|
||||
.offset = offsetof(struct qmi_response_type_v01, result),
|
||||
.ei_array = NULL,
|
||||
},
|
||||
{
|
||||
.data_type = QMI_SIGNED_2_BYTE_ENUM,
|
||||
.elem_len = 1,
|
||||
.elem_size = sizeof(u16),
|
||||
.array_type = NO_ARRAY,
|
||||
.tlv_type = QMI_COMMON_TLV_TYPE,
|
||||
.offset = offsetof(struct qmi_response_type_v01, error),
|
||||
.ei_array = NULL,
|
||||
},
|
||||
{
|
||||
.data_type = QMI_EOTI,
|
||||
.elem_len = 0,
|
||||
.elem_size = 0,
|
||||
.array_type = NO_ARRAY,
|
||||
.tlv_type = QMI_COMMON_TLV_TYPE,
|
||||
.offset = 0,
|
||||
.ei_array = NULL,
|
||||
},
|
||||
};
|
||||
EXPORT_SYMBOL(qmi_response_type_v01_ei);
|
||||
|
||||
MODULE_DESCRIPTION("QMI encoder/decoder helper");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,848 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2017 Linaro Ltd.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/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/workqueue.h>
|
||||
#include <linux/soc/qcom/qmi.h>
|
||||
|
||||
static struct socket *qmi_sock_create(struct qmi_handle *qmi,
|
||||
struct sockaddr_qrtr *sq);
|
||||
|
||||
/**
|
||||
* qmi_recv_new_server() - handler of NEW_SERVER control message
|
||||
* @qmi: qmi handle
|
||||
* @service: service id of the new server
|
||||
* @instance: instance id of the new server
|
||||
* @node: node of the new server
|
||||
* @port: port of the new server
|
||||
*
|
||||
* Calls the new_server callback to inform the client about a newly registered
|
||||
* server matching the currently registered service lookup.
|
||||
*/
|
||||
static void qmi_recv_new_server(struct qmi_handle *qmi,
|
||||
unsigned int service, unsigned int instance,
|
||||
unsigned int node, unsigned int port)
|
||||
{
|
||||
struct qmi_ops *ops = &qmi->ops;
|
||||
struct qmi_service *svc;
|
||||
int ret;
|
||||
|
||||
if (!ops->new_server)
|
||||
return;
|
||||
|
||||
/* Ignore EOF marker */
|
||||
if (!node && !port)
|
||||
return;
|
||||
|
||||
svc = kzalloc(sizeof(*svc), GFP_KERNEL);
|
||||
if (!svc)
|
||||
return;
|
||||
|
||||
svc->service = service;
|
||||
svc->version = instance & 0xff;
|
||||
svc->instance = instance >> 8;
|
||||
svc->node = node;
|
||||
svc->port = port;
|
||||
|
||||
ret = ops->new_server(qmi, svc);
|
||||
if (ret < 0)
|
||||
kfree(svc);
|
||||
else
|
||||
list_add(&svc->list_node, &qmi->lookup_results);
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_recv_del_server() - handler of DEL_SERVER control message
|
||||
* @qmi: qmi handle
|
||||
* @node: node of the dying server, a value of -1 matches all nodes
|
||||
* @port: port of the dying server, a value of -1 matches all ports
|
||||
*
|
||||
* Calls the del_server callback for each previously seen server, allowing the
|
||||
* client to react to the disappearing server.
|
||||
*/
|
||||
static void qmi_recv_del_server(struct qmi_handle *qmi,
|
||||
unsigned int node, unsigned int port)
|
||||
{
|
||||
struct qmi_ops *ops = &qmi->ops;
|
||||
struct qmi_service *svc;
|
||||
struct qmi_service *tmp;
|
||||
|
||||
list_for_each_entry_safe(svc, tmp, &qmi->lookup_results, list_node) {
|
||||
if (node != -1 && svc->node != node)
|
||||
continue;
|
||||
if (port != -1 && svc->port != port)
|
||||
continue;
|
||||
|
||||
if (ops->del_server)
|
||||
ops->del_server(qmi, svc);
|
||||
|
||||
list_del(&svc->list_node);
|
||||
kfree(svc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_recv_bye() - handler of BYE control message
|
||||
* @qmi: qmi handle
|
||||
* @node: id of the dying node
|
||||
*
|
||||
* Signals the client that all previously registered services on this node are
|
||||
* now gone and then calls the bye callback to allow the client client further
|
||||
* cleaning up resources associated with this remote.
|
||||
*/
|
||||
static void qmi_recv_bye(struct qmi_handle *qmi,
|
||||
unsigned int node)
|
||||
{
|
||||
struct qmi_ops *ops = &qmi->ops;
|
||||
|
||||
qmi_recv_del_server(qmi, node, -1);
|
||||
|
||||
if (ops->bye)
|
||||
ops->bye(qmi, node);
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_recv_del_client() - handler of DEL_CLIENT control message
|
||||
* @qmi: qmi handle
|
||||
* @node: node of the dying client
|
||||
* @port: port of the dying client
|
||||
*
|
||||
* Signals the client about a dying client, by calling the del_client callback.
|
||||
*/
|
||||
static void qmi_recv_del_client(struct qmi_handle *qmi,
|
||||
unsigned int node, unsigned int port)
|
||||
{
|
||||
struct qmi_ops *ops = &qmi->ops;
|
||||
|
||||
if (ops->del_client)
|
||||
ops->del_client(qmi, node, port);
|
||||
}
|
||||
|
||||
static void qmi_recv_ctrl_pkt(struct qmi_handle *qmi,
|
||||
const void *buf, size_t len)
|
||||
{
|
||||
const struct qrtr_ctrl_pkt *pkt = buf;
|
||||
|
||||
if (len < sizeof(struct qrtr_ctrl_pkt)) {
|
||||
pr_debug("ignoring short control packet\n");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (le32_to_cpu(pkt->cmd)) {
|
||||
case QRTR_TYPE_BYE:
|
||||
qmi_recv_bye(qmi, le32_to_cpu(pkt->client.node));
|
||||
break;
|
||||
case QRTR_TYPE_NEW_SERVER:
|
||||
qmi_recv_new_server(qmi,
|
||||
le32_to_cpu(pkt->server.service),
|
||||
le32_to_cpu(pkt->server.instance),
|
||||
le32_to_cpu(pkt->server.node),
|
||||
le32_to_cpu(pkt->server.port));
|
||||
break;
|
||||
case QRTR_TYPE_DEL_SERVER:
|
||||
qmi_recv_del_server(qmi,
|
||||
le32_to_cpu(pkt->server.node),
|
||||
le32_to_cpu(pkt->server.port));
|
||||
break;
|
||||
case QRTR_TYPE_DEL_CLIENT:
|
||||
qmi_recv_del_client(qmi,
|
||||
le32_to_cpu(pkt->client.node),
|
||||
le32_to_cpu(pkt->client.port));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void qmi_send_new_lookup(struct qmi_handle *qmi, struct qmi_service *svc)
|
||||
{
|
||||
struct qrtr_ctrl_pkt pkt;
|
||||
struct sockaddr_qrtr sq;
|
||||
struct msghdr msg = { };
|
||||
struct kvec iv = { &pkt, sizeof(pkt) };
|
||||
int ret;
|
||||
|
||||
memset(&pkt, 0, sizeof(pkt));
|
||||
pkt.cmd = cpu_to_le32(QRTR_TYPE_NEW_LOOKUP);
|
||||
pkt.server.service = cpu_to_le32(svc->service);
|
||||
pkt.server.instance = cpu_to_le32(svc->version | svc->instance << 8);
|
||||
|
||||
sq.sq_family = qmi->sq.sq_family;
|
||||
sq.sq_node = qmi->sq.sq_node;
|
||||
sq.sq_port = QRTR_PORT_CTRL;
|
||||
|
||||
msg.msg_name = &sq;
|
||||
msg.msg_namelen = sizeof(sq);
|
||||
|
||||
mutex_lock(&qmi->sock_lock);
|
||||
if (qmi->sock) {
|
||||
ret = kernel_sendmsg(qmi->sock, &msg, &iv, 1, sizeof(pkt));
|
||||
if (ret < 0)
|
||||
pr_err("failed to send lookup registration: %d\n", ret);
|
||||
}
|
||||
mutex_unlock(&qmi->sock_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_add_lookup() - register a new lookup with the name service
|
||||
* @qmi: qmi handle
|
||||
* @service: service id of the request
|
||||
* @instance: instance id of the request
|
||||
* @version: version number of the request
|
||||
*
|
||||
* Registering a lookup query with the name server will cause the name server
|
||||
* to send NEW_SERVER and DEL_SERVER control messages to this socket as
|
||||
* matching services are registered.
|
||||
*
|
||||
* Return: 0 on success, negative errno on failure.
|
||||
*/
|
||||
int qmi_add_lookup(struct qmi_handle *qmi, unsigned int service,
|
||||
unsigned int version, unsigned int instance)
|
||||
{
|
||||
struct qmi_service *svc;
|
||||
|
||||
svc = kzalloc(sizeof(*svc), GFP_KERNEL);
|
||||
if (!svc)
|
||||
return -ENOMEM;
|
||||
|
||||
svc->service = service;
|
||||
svc->version = version;
|
||||
svc->instance = instance;
|
||||
|
||||
list_add(&svc->list_node, &qmi->lookups);
|
||||
|
||||
qmi_send_new_lookup(qmi, svc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(qmi_add_lookup);
|
||||
|
||||
static void qmi_send_new_server(struct qmi_handle *qmi, struct qmi_service *svc)
|
||||
{
|
||||
struct qrtr_ctrl_pkt pkt;
|
||||
struct sockaddr_qrtr sq;
|
||||
struct msghdr msg = { };
|
||||
struct kvec iv = { &pkt, sizeof(pkt) };
|
||||
int ret;
|
||||
|
||||
memset(&pkt, 0, sizeof(pkt));
|
||||
pkt.cmd = cpu_to_le32(QRTR_TYPE_NEW_SERVER);
|
||||
pkt.server.service = cpu_to_le32(svc->service);
|
||||
pkt.server.instance = cpu_to_le32(svc->version | svc->instance << 8);
|
||||
pkt.server.node = cpu_to_le32(qmi->sq.sq_node);
|
||||
pkt.server.port = cpu_to_le32(qmi->sq.sq_port);
|
||||
|
||||
sq.sq_family = qmi->sq.sq_family;
|
||||
sq.sq_node = qmi->sq.sq_node;
|
||||
sq.sq_port = QRTR_PORT_CTRL;
|
||||
|
||||
msg.msg_name = &sq;
|
||||
msg.msg_namelen = sizeof(sq);
|
||||
|
||||
mutex_lock(&qmi->sock_lock);
|
||||
if (qmi->sock) {
|
||||
ret = kernel_sendmsg(qmi->sock, &msg, &iv, 1, sizeof(pkt));
|
||||
if (ret < 0)
|
||||
pr_err("send service registration failed: %d\n", ret);
|
||||
}
|
||||
mutex_unlock(&qmi->sock_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_add_server() - register a service with the name service
|
||||
* @qmi: qmi handle
|
||||
* @service: type of the service
|
||||
* @instance: instance of the service
|
||||
* @version: version of the service
|
||||
*
|
||||
* Register a new service with the name service. This allows clients to find
|
||||
* and start sending messages to the client associated with @qmi.
|
||||
*
|
||||
* Return: 0 on success, negative errno on failure.
|
||||
*/
|
||||
int qmi_add_server(struct qmi_handle *qmi, unsigned int service,
|
||||
unsigned int version, unsigned int instance)
|
||||
{
|
||||
struct qmi_service *svc;
|
||||
|
||||
svc = kzalloc(sizeof(*svc), GFP_KERNEL);
|
||||
if (!svc)
|
||||
return -ENOMEM;
|
||||
|
||||
svc->service = service;
|
||||
svc->version = version;
|
||||
svc->instance = instance;
|
||||
|
||||
list_add(&svc->list_node, &qmi->services);
|
||||
|
||||
qmi_send_new_server(qmi, svc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(qmi_add_server);
|
||||
|
||||
/**
|
||||
* qmi_txn_init() - allocate transaction id within the given QMI handle
|
||||
* @qmi: QMI handle
|
||||
* @txn: transaction context
|
||||
* @ei: description of how to decode a matching response (optional)
|
||||
* @c_struct: pointer to the object to decode the response into (optional)
|
||||
*
|
||||
* This allocates a transaction id within the QMI handle. If @ei and @c_struct
|
||||
* are specified any responses to this transaction will be decoded as described
|
||||
* by @ei into @c_struct.
|
||||
*
|
||||
* A client calling qmi_txn_init() must call either qmi_txn_wait() or
|
||||
* qmi_txn_cancel() to free up the allocated resources.
|
||||
*
|
||||
* Return: Transaction id on success, negative errno on failure.
|
||||
*/
|
||||
int qmi_txn_init(struct qmi_handle *qmi, struct qmi_txn *txn,
|
||||
struct qmi_elem_info *ei, void *c_struct)
|
||||
{
|
||||
int ret;
|
||||
|
||||
memset(txn, 0, sizeof(*txn));
|
||||
|
||||
mutex_init(&txn->lock);
|
||||
init_completion(&txn->completion);
|
||||
txn->qmi = qmi;
|
||||
txn->ei = ei;
|
||||
txn->dest = c_struct;
|
||||
|
||||
mutex_lock(&qmi->txn_lock);
|
||||
ret = idr_alloc_cyclic(&qmi->txns, txn, 0, INT_MAX, GFP_KERNEL);
|
||||
if (ret < 0)
|
||||
pr_err("failed to allocate transaction id\n");
|
||||
|
||||
txn->id = ret;
|
||||
mutex_unlock(&qmi->txn_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(qmi_txn_init);
|
||||
|
||||
/**
|
||||
* qmi_txn_wait() - wait for a response on a transaction
|
||||
* @txn: transaction handle
|
||||
* @timeout: timeout, in jiffies
|
||||
*
|
||||
* If the transaction is decoded by the means of @ei and @c_struct the return
|
||||
* value will be the returned value of qmi_decode_message(), otherwise it's up
|
||||
* to the specified message handler to fill out the result.
|
||||
*
|
||||
* Return: the transaction response on success, negative errno on failure.
|
||||
*/
|
||||
int qmi_txn_wait(struct qmi_txn *txn, unsigned long timeout)
|
||||
{
|
||||
struct qmi_handle *qmi = txn->qmi;
|
||||
int ret;
|
||||
|
||||
ret = wait_for_completion_interruptible_timeout(&txn->completion,
|
||||
timeout);
|
||||
|
||||
mutex_lock(&qmi->txn_lock);
|
||||
mutex_lock(&txn->lock);
|
||||
idr_remove(&qmi->txns, txn->id);
|
||||
mutex_unlock(&txn->lock);
|
||||
mutex_unlock(&qmi->txn_lock);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
else if (ret == 0)
|
||||
return -ETIMEDOUT;
|
||||
else
|
||||
return txn->result;
|
||||
}
|
||||
EXPORT_SYMBOL(qmi_txn_wait);
|
||||
|
||||
/**
|
||||
* qmi_txn_cancel() - cancel an ongoing transaction
|
||||
* @txn: transaction id
|
||||
*/
|
||||
void qmi_txn_cancel(struct qmi_txn *txn)
|
||||
{
|
||||
struct qmi_handle *qmi = txn->qmi;
|
||||
|
||||
mutex_lock(&qmi->txn_lock);
|
||||
mutex_lock(&txn->lock);
|
||||
idr_remove(&qmi->txns, txn->id);
|
||||
mutex_unlock(&txn->lock);
|
||||
mutex_unlock(&qmi->txn_lock);
|
||||
}
|
||||
EXPORT_SYMBOL(qmi_txn_cancel);
|
||||
|
||||
/**
|
||||
* qmi_invoke_handler() - find and invoke a handler for a message
|
||||
* @qmi: qmi handle
|
||||
* @sq: sockaddr of the sender
|
||||
* @txn: transaction object for the message
|
||||
* @buf: buffer containing the message
|
||||
* @len: length of @buf
|
||||
*
|
||||
* Find handler and invoke handler for the incoming message.
|
||||
*/
|
||||
static void qmi_invoke_handler(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
|
||||
struct qmi_txn *txn, const void *buf, size_t len)
|
||||
{
|
||||
const struct qmi_msg_handler *handler;
|
||||
const struct qmi_header *hdr = buf;
|
||||
void *dest;
|
||||
int ret;
|
||||
|
||||
if (!qmi->handlers)
|
||||
return;
|
||||
|
||||
for (handler = qmi->handlers; handler->fn; handler++) {
|
||||
if (handler->type == hdr->type &&
|
||||
handler->msg_id == hdr->msg_id)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handler->fn)
|
||||
return;
|
||||
|
||||
dest = kzalloc(handler->decoded_size, GFP_KERNEL);
|
||||
if (!dest)
|
||||
return;
|
||||
|
||||
ret = qmi_decode_message(buf, len, handler->ei, dest);
|
||||
if (ret < 0)
|
||||
pr_err("failed to decode incoming message\n");
|
||||
else
|
||||
handler->fn(qmi, sq, txn, dest);
|
||||
|
||||
kfree(dest);
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_handle_net_reset() - invoked to handle ENETRESET on a QMI handle
|
||||
* @qmi: the QMI context
|
||||
*
|
||||
* As a result of registering a name service with the QRTR all open sockets are
|
||||
* flagged with ENETRESET and this function will be called. The typical case is
|
||||
* the initial boot, where this signals that the local node id has been
|
||||
* configured and as such any bound sockets needs to be rebound. So close the
|
||||
* socket, inform the client and re-initialize the socket.
|
||||
*
|
||||
* For clients it's generally sufficient to react to the del_server callbacks,
|
||||
* but server code is expected to treat the net_reset callback as a "bye" from
|
||||
* all nodes.
|
||||
*
|
||||
* Finally the QMI handle will send out registration requests for any lookups
|
||||
* and services.
|
||||
*/
|
||||
static void qmi_handle_net_reset(struct qmi_handle *qmi)
|
||||
{
|
||||
struct sockaddr_qrtr sq;
|
||||
struct qmi_service *svc;
|
||||
struct socket *sock;
|
||||
|
||||
sock = qmi_sock_create(qmi, &sq);
|
||||
if (IS_ERR(sock))
|
||||
return;
|
||||
|
||||
mutex_lock(&qmi->sock_lock);
|
||||
sock_release(qmi->sock);
|
||||
qmi->sock = NULL;
|
||||
mutex_unlock(&qmi->sock_lock);
|
||||
|
||||
qmi_recv_del_server(qmi, -1, -1);
|
||||
|
||||
if (qmi->ops.net_reset)
|
||||
qmi->ops.net_reset(qmi);
|
||||
|
||||
mutex_lock(&qmi->sock_lock);
|
||||
qmi->sock = sock;
|
||||
qmi->sq = sq;
|
||||
mutex_unlock(&qmi->sock_lock);
|
||||
|
||||
list_for_each_entry(svc, &qmi->lookups, list_node)
|
||||
qmi_send_new_lookup(qmi, svc);
|
||||
|
||||
list_for_each_entry(svc, &qmi->services, list_node)
|
||||
qmi_send_new_server(qmi, svc);
|
||||
}
|
||||
|
||||
static void qmi_handle_message(struct qmi_handle *qmi,
|
||||
struct sockaddr_qrtr *sq,
|
||||
const void *buf, size_t len)
|
||||
{
|
||||
const struct qmi_header *hdr;
|
||||
struct qmi_txn tmp_txn;
|
||||
struct qmi_txn *txn = NULL;
|
||||
int ret;
|
||||
|
||||
if (len < sizeof(*hdr)) {
|
||||
pr_err("ignoring short QMI packet\n");
|
||||
return;
|
||||
}
|
||||
|
||||
hdr = buf;
|
||||
|
||||
/* If this is a response, find the matching transaction handle */
|
||||
if (hdr->type == QMI_RESPONSE) {
|
||||
mutex_lock(&qmi->txn_lock);
|
||||
txn = idr_find(&qmi->txns, hdr->txn_id);
|
||||
|
||||
/* Ignore unexpected responses */
|
||||
if (!txn) {
|
||||
mutex_unlock(&qmi->txn_lock);
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock(&txn->lock);
|
||||
mutex_unlock(&qmi->txn_lock);
|
||||
|
||||
if (txn->dest && txn->ei) {
|
||||
ret = qmi_decode_message(buf, len, txn->ei, txn->dest);
|
||||
if (ret < 0)
|
||||
pr_err("failed to decode incoming message\n");
|
||||
|
||||
txn->result = ret;
|
||||
complete(&txn->completion);
|
||||
} else {
|
||||
qmi_invoke_handler(qmi, sq, txn, buf, len);
|
||||
}
|
||||
|
||||
mutex_unlock(&txn->lock);
|
||||
} else {
|
||||
/* Create a txn based on the txn_id of the incoming message */
|
||||
memset(&tmp_txn, 0, sizeof(tmp_txn));
|
||||
tmp_txn.id = hdr->txn_id;
|
||||
|
||||
qmi_invoke_handler(qmi, sq, &tmp_txn, buf, len);
|
||||
}
|
||||
}
|
||||
|
||||
static void qmi_data_ready_work(struct work_struct *work)
|
||||
{
|
||||
struct qmi_handle *qmi = container_of(work, struct qmi_handle, work);
|
||||
struct qmi_ops *ops = &qmi->ops;
|
||||
struct sockaddr_qrtr sq;
|
||||
struct msghdr msg = { .msg_name = &sq, .msg_namelen = sizeof(sq) };
|
||||
struct kvec iv;
|
||||
ssize_t msglen;
|
||||
|
||||
for (;;) {
|
||||
iv.iov_base = qmi->recv_buf;
|
||||
iv.iov_len = qmi->recv_buf_size;
|
||||
|
||||
mutex_lock(&qmi->sock_lock);
|
||||
if (qmi->sock)
|
||||
msglen = kernel_recvmsg(qmi->sock, &msg, &iv, 1,
|
||||
iv.iov_len, MSG_DONTWAIT);
|
||||
else
|
||||
msglen = -EPIPE;
|
||||
mutex_unlock(&qmi->sock_lock);
|
||||
if (msglen == -EAGAIN)
|
||||
break;
|
||||
|
||||
if (msglen == -ENETRESET) {
|
||||
qmi_handle_net_reset(qmi);
|
||||
|
||||
/* The old qmi->sock is gone, our work is done */
|
||||
break;
|
||||
}
|
||||
|
||||
if (msglen < 0) {
|
||||
pr_err("qmi recvmsg failed: %zd\n", msglen);
|
||||
break;
|
||||
}
|
||||
|
||||
if (sq.sq_node == qmi->sq.sq_node &&
|
||||
sq.sq_port == QRTR_PORT_CTRL) {
|
||||
qmi_recv_ctrl_pkt(qmi, qmi->recv_buf, msglen);
|
||||
} else if (ops->msg_handler) {
|
||||
ops->msg_handler(qmi, &sq, qmi->recv_buf, msglen);
|
||||
} else {
|
||||
qmi_handle_message(qmi, &sq, qmi->recv_buf, msglen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void qmi_data_ready(struct sock *sk)
|
||||
{
|
||||
struct qmi_handle *qmi = sk->sk_user_data;
|
||||
|
||||
/*
|
||||
* This will be NULL if we receive data while being in
|
||||
* qmi_handle_release()
|
||||
*/
|
||||
if (!qmi)
|
||||
return;
|
||||
|
||||
queue_work(qmi->wq, &qmi->work);
|
||||
}
|
||||
|
||||
static struct socket *qmi_sock_create(struct qmi_handle *qmi,
|
||||
struct sockaddr_qrtr *sq)
|
||||
{
|
||||
struct socket *sock;
|
||||
int sl = sizeof(*sq);
|
||||
int ret;
|
||||
|
||||
ret = sock_create_kern(&init_net, AF_QIPCRTR, SOCK_DGRAM,
|
||||
PF_QIPCRTR, &sock);
|
||||
if (ret < 0)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
ret = kernel_getsockname(sock, (struct sockaddr *)sq, &sl);
|
||||
if (ret < 0) {
|
||||
sock_release(sock);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
sock->sk->sk_user_data = qmi;
|
||||
sock->sk->sk_data_ready = qmi_data_ready;
|
||||
sock->sk->sk_error_report = qmi_data_ready;
|
||||
|
||||
return sock;
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_handle_init() - initialize a QMI client handle
|
||||
* @qmi: QMI handle to initialize
|
||||
* @recv_buf_size: maximum size of incoming message
|
||||
* @ops: reference to callbacks for QRTR notifications
|
||||
* @handlers: NULL-terminated list of QMI message handlers
|
||||
*
|
||||
* This initializes the QMI client handle to allow sending and receiving QMI
|
||||
* messages. As messages are received the appropriate handler will be invoked.
|
||||
*
|
||||
* Return: 0 on success, negative errno on failure.
|
||||
*/
|
||||
int qmi_handle_init(struct qmi_handle *qmi, size_t recv_buf_size,
|
||||
const struct qmi_ops *ops,
|
||||
const struct qmi_msg_handler *handlers)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_init(&qmi->txn_lock);
|
||||
mutex_init(&qmi->sock_lock);
|
||||
|
||||
idr_init(&qmi->txns);
|
||||
|
||||
INIT_LIST_HEAD(&qmi->lookups);
|
||||
INIT_LIST_HEAD(&qmi->lookup_results);
|
||||
INIT_LIST_HEAD(&qmi->services);
|
||||
|
||||
INIT_WORK(&qmi->work, qmi_data_ready_work);
|
||||
|
||||
qmi->handlers = handlers;
|
||||
if (ops)
|
||||
qmi->ops = *ops;
|
||||
|
||||
if (recv_buf_size < sizeof(struct qrtr_ctrl_pkt))
|
||||
recv_buf_size = sizeof(struct qrtr_ctrl_pkt);
|
||||
else
|
||||
recv_buf_size += sizeof(struct qmi_header);
|
||||
|
||||
qmi->recv_buf_size = recv_buf_size;
|
||||
qmi->recv_buf = kzalloc(recv_buf_size, GFP_KERNEL);
|
||||
if (!qmi->recv_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
qmi->wq = alloc_workqueue("qmi_msg_handler", WQ_UNBOUND, 1);
|
||||
if (!qmi->wq) {
|
||||
ret = -ENOMEM;
|
||||
goto err_free_recv_buf;
|
||||
}
|
||||
|
||||
qmi->sock = qmi_sock_create(qmi, &qmi->sq);
|
||||
if (IS_ERR(qmi->sock)) {
|
||||
pr_err("failed to create QMI socket\n");
|
||||
ret = PTR_ERR(qmi->sock);
|
||||
goto err_destroy_wq;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_destroy_wq:
|
||||
destroy_workqueue(qmi->wq);
|
||||
err_free_recv_buf:
|
||||
kfree(qmi->recv_buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(qmi_handle_init);
|
||||
|
||||
/**
|
||||
* qmi_handle_release() - release the QMI client handle
|
||||
* @qmi: QMI client handle
|
||||
*
|
||||
* This closes the underlying socket and stops any handling of QMI messages.
|
||||
*/
|
||||
void qmi_handle_release(struct qmi_handle *qmi)
|
||||
{
|
||||
struct socket *sock = qmi->sock;
|
||||
struct qmi_service *svc, *tmp;
|
||||
|
||||
sock->sk->sk_user_data = NULL;
|
||||
cancel_work_sync(&qmi->work);
|
||||
|
||||
qmi_recv_del_server(qmi, -1, -1);
|
||||
|
||||
mutex_lock(&qmi->sock_lock);
|
||||
sock_release(sock);
|
||||
qmi->sock = NULL;
|
||||
mutex_unlock(&qmi->sock_lock);
|
||||
|
||||
destroy_workqueue(qmi->wq);
|
||||
|
||||
idr_destroy(&qmi->txns);
|
||||
|
||||
kfree(qmi->recv_buf);
|
||||
|
||||
/* Free registered lookup requests */
|
||||
list_for_each_entry_safe(svc, tmp, &qmi->lookups, list_node) {
|
||||
list_del(&svc->list_node);
|
||||
kfree(svc);
|
||||
}
|
||||
|
||||
/* Free registered service information */
|
||||
list_for_each_entry_safe(svc, tmp, &qmi->services, list_node) {
|
||||
list_del(&svc->list_node);
|
||||
kfree(svc);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(qmi_handle_release);
|
||||
|
||||
/**
|
||||
* qmi_send_message() - send a QMI message
|
||||
* @qmi: QMI client handle
|
||||
* @sq: destination sockaddr
|
||||
* @txn: transaction object to use for the message
|
||||
* @type: type of message to send
|
||||
* @msg_id: message id
|
||||
* @len: max length of the QMI message
|
||||
* @ei: QMI message description
|
||||
* @c_struct: object to be encoded
|
||||
*
|
||||
* This function encodes @c_struct using @ei into a message of type @type,
|
||||
* with @msg_id and @txn into a buffer of maximum size @len, and sends this to
|
||||
* @sq.
|
||||
*
|
||||
* Return: 0 on success, negative errno on failure.
|
||||
*/
|
||||
static ssize_t qmi_send_message(struct qmi_handle *qmi,
|
||||
struct sockaddr_qrtr *sq, struct qmi_txn *txn,
|
||||
int type, int msg_id, size_t len,
|
||||
struct qmi_elem_info *ei, const void *c_struct)
|
||||
{
|
||||
struct msghdr msghdr = {};
|
||||
struct kvec iv;
|
||||
void *msg;
|
||||
int ret;
|
||||
|
||||
msg = qmi_encode_message(type,
|
||||
msg_id, &len,
|
||||
txn->id, ei,
|
||||
c_struct);
|
||||
if (IS_ERR(msg))
|
||||
return PTR_ERR(msg);
|
||||
|
||||
iv.iov_base = msg;
|
||||
iv.iov_len = len;
|
||||
|
||||
if (sq) {
|
||||
msghdr.msg_name = sq;
|
||||
msghdr.msg_namelen = sizeof(*sq);
|
||||
}
|
||||
|
||||
mutex_lock(&qmi->sock_lock);
|
||||
if (qmi->sock) {
|
||||
ret = kernel_sendmsg(qmi->sock, &msghdr, &iv, 1, len);
|
||||
if (ret < 0)
|
||||
pr_err("failed to send QMI message\n");
|
||||
} else {
|
||||
ret = -EPIPE;
|
||||
}
|
||||
mutex_unlock(&qmi->sock_lock);
|
||||
|
||||
kfree(msg);
|
||||
|
||||
return ret < 0 ? ret : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* qmi_send_request() - send a request QMI message
|
||||
* @qmi: QMI client handle
|
||||
* @sq: destination sockaddr
|
||||
* @txn: transaction object to use for the message
|
||||
* @msg_id: message id
|
||||
* @len: max length of the QMI message
|
||||
* @ei: QMI message description
|
||||
* @c_struct: object to be encoded
|
||||
*
|
||||
* Return: 0 on success, negative errno on failure.
|
||||
*/
|
||||
ssize_t qmi_send_request(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
|
||||
struct qmi_txn *txn, int msg_id, size_t len,
|
||||
struct qmi_elem_info *ei, const void *c_struct)
|
||||
{
|
||||
return qmi_send_message(qmi, sq, txn, QMI_REQUEST, msg_id, len, ei,
|
||||
c_struct);
|
||||
}
|
||||
EXPORT_SYMBOL(qmi_send_request);
|
||||
|
||||
/**
|
||||
* qmi_send_response() - send a response QMI message
|
||||
* @qmi: QMI client handle
|
||||
* @sq: destination sockaddr
|
||||
* @txn: transaction object to use for the message
|
||||
* @msg_id: message id
|
||||
* @len: max length of the QMI message
|
||||
* @ei: QMI message description
|
||||
* @c_struct: object to be encoded
|
||||
*
|
||||
* Return: 0 on success, negative errno on failure.
|
||||
*/
|
||||
ssize_t qmi_send_response(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
|
||||
struct qmi_txn *txn, int msg_id, size_t len,
|
||||
struct qmi_elem_info *ei, const void *c_struct)
|
||||
{
|
||||
return qmi_send_message(qmi, sq, txn, QMI_RESPONSE, msg_id, len, ei,
|
||||
c_struct);
|
||||
}
|
||||
EXPORT_SYMBOL(qmi_send_response);
|
||||
|
||||
/**
|
||||
* qmi_send_indication() - send an indication QMI message
|
||||
* @qmi: QMI client handle
|
||||
* @sq: destination sockaddr
|
||||
* @msg_id: message id
|
||||
* @len: max length of the QMI message
|
||||
* @ei: QMI message description
|
||||
* @c_struct: object to be encoded
|
||||
*
|
||||
* Return: 0 on success, negative errno on failure.
|
||||
*/
|
||||
ssize_t qmi_send_indication(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
|
||||
int msg_id, size_t len, struct qmi_elem_info *ei,
|
||||
const void *c_struct)
|
||||
{
|
||||
struct qmi_txn txn;
|
||||
ssize_t rval;
|
||||
int ret;
|
||||
|
||||
ret = qmi_txn_init(qmi, &txn, NULL, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
rval = qmi_send_message(qmi, sq, &txn, QMI_INDICATION, msg_id, len, ei,
|
||||
c_struct);
|
||||
|
||||
/* We don't care about future messages on this txn */
|
||||
qmi_txn_cancel(&txn);
|
||||
|
||||
return rval;
|
||||
}
|
||||
EXPORT_SYMBOL(qmi_send_indication);
|
|
@ -267,3 +267,7 @@ static void qcom_rmtfs_mem_exit(void)
|
|||
unregister_chrdev_region(qcom_rmtfs_mem_major, QCOM_RMTFS_MEM_DEV_MAX);
|
||||
}
|
||||
module_exit(qcom_rmtfs_mem_exit);
|
||||
|
||||
MODULE_AUTHOR("Linaro Ltd");
|
||||
MODULE_DESCRIPTION("Qualcomm Remote Filesystem memory driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <linux/of.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/mailbox_client.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
@ -126,6 +127,8 @@ struct smp2p_entry {
|
|||
* @ipc_regmap: regmap for the outbound ipc
|
||||
* @ipc_offset: offset within the regmap
|
||||
* @ipc_bit: bit in regmap@offset to kick to signal remote processor
|
||||
* @mbox_client: mailbox client handle
|
||||
* @mbox_chan: apcs ipc mailbox channel handle
|
||||
* @inbound: list of inbound entries
|
||||
* @outbound: list of outbound entries
|
||||
*/
|
||||
|
@ -146,6 +149,9 @@ struct qcom_smp2p {
|
|||
int ipc_offset;
|
||||
int ipc_bit;
|
||||
|
||||
struct mbox_client mbox_client;
|
||||
struct mbox_chan *mbox_chan;
|
||||
|
||||
struct list_head inbound;
|
||||
struct list_head outbound;
|
||||
};
|
||||
|
@ -154,7 +160,13 @@ static void qcom_smp2p_kick(struct qcom_smp2p *smp2p)
|
|||
{
|
||||
/* Make sure any updated data is written before the kick */
|
||||
wmb();
|
||||
|
||||
if (smp2p->mbox_chan) {
|
||||
mbox_send_message(smp2p->mbox_chan, NULL);
|
||||
mbox_client_txdone(smp2p->mbox_chan, 0);
|
||||
} else {
|
||||
regmap_write(smp2p->ipc_regmap, smp2p->ipc_offset, BIT(smp2p->ipc_bit));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -453,10 +465,6 @@ static int qcom_smp2p_probe(struct platform_device *pdev)
|
|||
|
||||
platform_set_drvdata(pdev, smp2p);
|
||||
|
||||
ret = smp2p_parse_ipc(smp2p);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
key = "qcom,smem";
|
||||
ret = of_property_read_u32_array(pdev->dev.of_node, key,
|
||||
smp2p->smem_items, 2);
|
||||
|
@ -465,17 +473,13 @@ static int qcom_smp2p_probe(struct platform_device *pdev)
|
|||
|
||||
key = "qcom,local-pid";
|
||||
ret = of_property_read_u32(pdev->dev.of_node, key, &smp2p->local_pid);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to read %s\n", key);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (ret)
|
||||
goto report_read_failure;
|
||||
|
||||
key = "qcom,remote-pid";
|
||||
ret = of_property_read_u32(pdev->dev.of_node, key, &smp2p->remote_pid);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to read %s\n", key);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (ret)
|
||||
goto report_read_failure;
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
|
@ -483,9 +487,23 @@ static int qcom_smp2p_probe(struct platform_device *pdev)
|
|||
return irq;
|
||||
}
|
||||
|
||||
smp2p->mbox_client.dev = &pdev->dev;
|
||||
smp2p->mbox_client.knows_txdone = true;
|
||||
smp2p->mbox_chan = mbox_request_channel(&smp2p->mbox_client, 0);
|
||||
if (IS_ERR(smp2p->mbox_chan)) {
|
||||
if (PTR_ERR(smp2p->mbox_chan) != -ENODEV)
|
||||
return PTR_ERR(smp2p->mbox_chan);
|
||||
|
||||
smp2p->mbox_chan = NULL;
|
||||
|
||||
ret = smp2p_parse_ipc(smp2p);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = qcom_smp2p_alloc_outbound_item(smp2p);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
goto release_mbox;
|
||||
|
||||
for_each_available_child_of_node(pdev->dev.of_node, node) {
|
||||
entry = devm_kzalloc(&pdev->dev, sizeof(*entry), GFP_KERNEL);
|
||||
|
@ -540,7 +558,14 @@ unwind_interfaces:
|
|||
|
||||
smp2p->out->valid_entries = 0;
|
||||
|
||||
release_mbox:
|
||||
mbox_free_channel(smp2p->mbox_chan);
|
||||
|
||||
return ret;
|
||||
|
||||
report_read_failure:
|
||||
dev_err(&pdev->dev, "failed to read %s\n", key);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int qcom_smp2p_remove(struct platform_device *pdev)
|
||||
|
@ -554,6 +579,8 @@ static int qcom_smp2p_remove(struct platform_device *pdev)
|
|||
list_for_each_entry(entry, &smp2p->outbound, node)
|
||||
qcom_smem_state_unregister(entry->state);
|
||||
|
||||
mbox_free_channel(smp2p->mbox_chan);
|
||||
|
||||
smp2p->out->valid_entries = 0;
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -496,8 +496,10 @@ static int qcom_smsm_probe(struct platform_device *pdev)
|
|||
if (!smsm->hosts)
|
||||
return -ENOMEM;
|
||||
|
||||
local_node = of_find_node_with_property(of_node_get(pdev->dev.of_node),
|
||||
"#qcom,smem-state-cells");
|
||||
for_each_child_of_node(pdev->dev.of_node, local_node) {
|
||||
if (of_find_property(local_node, "#qcom,smem-state-cells", NULL))
|
||||
break;
|
||||
}
|
||||
if (!local_node) {
|
||||
dev_err(&pdev->dev, "no state entry\n");
|
||||
return -EINVAL;
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
#ifndef __QCOM_SCM_H
|
||||
#define __QCOM_SCM_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/cpumask.h>
|
||||
|
||||
#define QCOM_SCM_VERSION(major, minor) (((major) << 16) | ((minor) & 0xFF))
|
||||
#define QCOM_SCM_CPU_PWR_DOWN_L2_ON 0x0
|
||||
#define QCOM_SCM_CPU_PWR_DOWN_L2_OFF 0x1
|
||||
|
|
|
@ -0,0 +1,271 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2012-2014, The Linux Foundation. All rights reserved.
|
||||
* Copyright (c) 2017, Linaro Ltd.
|
||||
*/
|
||||
#ifndef __QMI_HELPERS_H__
|
||||
#define __QMI_HELPERS_H__
|
||||
|
||||
#include <linux/completion.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/qrtr.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
struct socket;
|
||||
|
||||
/**
|
||||
* qmi_header - wireformat header of QMI messages
|
||||
* @type: type of message
|
||||
* @txn_id: transaction id
|
||||
* @msg_id: message id
|
||||
* @msg_len: length of message payload following header
|
||||
*/
|
||||
struct qmi_header {
|
||||
u8 type;
|
||||
u16 txn_id;
|
||||
u16 msg_id;
|
||||
u16 msg_len;
|
||||
} __packed;
|
||||
|
||||
#define QMI_REQUEST 0
|
||||
#define QMI_RESPONSE 2
|
||||
#define QMI_INDICATION 4
|
||||
|
||||
#define QMI_COMMON_TLV_TYPE 0
|
||||
|
||||
enum qmi_elem_type {
|
||||
QMI_EOTI,
|
||||
QMI_OPT_FLAG,
|
||||
QMI_DATA_LEN,
|
||||
QMI_UNSIGNED_1_BYTE,
|
||||
QMI_UNSIGNED_2_BYTE,
|
||||
QMI_UNSIGNED_4_BYTE,
|
||||
QMI_UNSIGNED_8_BYTE,
|
||||
QMI_SIGNED_2_BYTE_ENUM,
|
||||
QMI_SIGNED_4_BYTE_ENUM,
|
||||
QMI_STRUCT,
|
||||
QMI_STRING,
|
||||
};
|
||||
|
||||
enum qmi_array_type {
|
||||
NO_ARRAY,
|
||||
STATIC_ARRAY,
|
||||
VAR_LEN_ARRAY,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct qmi_elem_info - describes how to encode a single QMI element
|
||||
* @data_type: Data type of this element.
|
||||
* @elem_len: Array length of this element, if an array.
|
||||
* @elem_size: Size of a single instance of this data type.
|
||||
* @array_type: Array type of this element.
|
||||
* @tlv_type: QMI message specific type to identify which element
|
||||
* is present in an incoming message.
|
||||
* @offset: Specifies the offset of the first instance of this
|
||||
* element in the data structure.
|
||||
* @ei_array: Null-terminated array of @qmi_elem_info to describe nested
|
||||
* structures.
|
||||
*/
|
||||
struct qmi_elem_info {
|
||||
enum qmi_elem_type data_type;
|
||||
u32 elem_len;
|
||||
u32 elem_size;
|
||||
enum qmi_array_type array_type;
|
||||
u8 tlv_type;
|
||||
u32 offset;
|
||||
struct qmi_elem_info *ei_array;
|
||||
};
|
||||
|
||||
#define QMI_RESULT_SUCCESS_V01 0
|
||||
#define QMI_RESULT_FAILURE_V01 1
|
||||
|
||||
#define QMI_ERR_NONE_V01 0
|
||||
#define QMI_ERR_MALFORMED_MSG_V01 1
|
||||
#define QMI_ERR_NO_MEMORY_V01 2
|
||||
#define QMI_ERR_INTERNAL_V01 3
|
||||
#define QMI_ERR_CLIENT_IDS_EXHAUSTED_V01 5
|
||||
#define QMI_ERR_INVALID_ID_V01 41
|
||||
#define QMI_ERR_ENCODING_V01 58
|
||||
#define QMI_ERR_INCOMPATIBLE_STATE_V01 90
|
||||
#define QMI_ERR_NOT_SUPPORTED_V01 94
|
||||
|
||||
/**
|
||||
* qmi_response_type_v01 - common response header (decoded)
|
||||
* @result: result of the transaction
|
||||
* @error: error value, when @result is QMI_RESULT_FAILURE_V01
|
||||
*/
|
||||
struct qmi_response_type_v01 {
|
||||
u16 result;
|
||||
u16 error;
|
||||
};
|
||||
|
||||
extern struct qmi_elem_info qmi_response_type_v01_ei[];
|
||||
|
||||
/**
|
||||
* struct qmi_service - context to track lookup-results
|
||||
* @service: service type
|
||||
* @version: version of the @service
|
||||
* @instance: instance id of the @service
|
||||
* @node: node of the service
|
||||
* @port: port of the service
|
||||
* @priv: handle for client's use
|
||||
* @list_node: list_head for house keeping
|
||||
*/
|
||||
struct qmi_service {
|
||||
unsigned int service;
|
||||
unsigned int version;
|
||||
unsigned int instance;
|
||||
|
||||
unsigned int node;
|
||||
unsigned int port;
|
||||
|
||||
void *priv;
|
||||
struct list_head list_node;
|
||||
};
|
||||
|
||||
struct qmi_handle;
|
||||
|
||||
/**
|
||||
* struct qmi_ops - callbacks for qmi_handle
|
||||
* @new_server: inform client of a new_server lookup-result, returning
|
||||
* successfully from this call causes the library to call
|
||||
* @del_server as the service is removed from the
|
||||
* lookup-result. @priv of the qmi_service can be used by
|
||||
* the client
|
||||
* @del_server: inform client of a del_server lookup-result
|
||||
* @net_reset: inform client that the name service was restarted and
|
||||
* that and any state needs to be released
|
||||
* @msg_handler: invoked for incoming messages, allows a client to
|
||||
* override the usual QMI message handler
|
||||
* @bye: inform a client that all clients from a node are gone
|
||||
* @del_client: inform a client that a particular client is gone
|
||||
*/
|
||||
struct qmi_ops {
|
||||
int (*new_server)(struct qmi_handle *qmi, struct qmi_service *svc);
|
||||
void (*del_server)(struct qmi_handle *qmi, struct qmi_service *svc);
|
||||
void (*net_reset)(struct qmi_handle *qmi);
|
||||
void (*msg_handler)(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
|
||||
const void *data, size_t count);
|
||||
void (*bye)(struct qmi_handle *qmi, unsigned int node);
|
||||
void (*del_client)(struct qmi_handle *qmi,
|
||||
unsigned int node, unsigned int port);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct qmi_txn - transaction context
|
||||
* @qmi: QMI handle this transaction is associated with
|
||||
* @id: transaction id
|
||||
* @lock: for synchronization between handler and waiter of messages
|
||||
* @completion: completion object as the transaction receives a response
|
||||
* @result: result code for the completed transaction
|
||||
* @ei: description of the QMI encoded response (optional)
|
||||
* @dest: destination buffer to decode message into (optional)
|
||||
*/
|
||||
struct qmi_txn {
|
||||
struct qmi_handle *qmi;
|
||||
|
||||
int id;
|
||||
|
||||
struct mutex lock;
|
||||
struct completion completion;
|
||||
int result;
|
||||
|
||||
struct qmi_elem_info *ei;
|
||||
void *dest;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct qmi_msg_handler - description of QMI message handler
|
||||
* @type: type of message
|
||||
* @msg_id: message id
|
||||
* @ei: description of the QMI encoded message
|
||||
* @decoded_size: size of the decoded object
|
||||
* @fn: function to invoke as the message is decoded
|
||||
*/
|
||||
struct qmi_msg_handler {
|
||||
unsigned int type;
|
||||
unsigned int msg_id;
|
||||
|
||||
struct qmi_elem_info *ei;
|
||||
|
||||
size_t decoded_size;
|
||||
void (*fn)(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
|
||||
struct qmi_txn *txn, const void *decoded);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct qmi_handle - QMI context
|
||||
* @sock: socket handle
|
||||
* @sock_lock: synchronization of @sock modifications
|
||||
* @sq: sockaddr of @sock
|
||||
* @work: work for handling incoming messages
|
||||
* @wq: workqueue to post @work on
|
||||
* @recv_buf: scratch buffer for handling incoming messages
|
||||
* @recv_buf_size: size of @recv_buf
|
||||
* @lookups: list of registered lookup requests
|
||||
* @lookup_results: list of lookup-results advertised to the client
|
||||
* @services: list of registered services (by this client)
|
||||
* @ops: reference to callbacks
|
||||
* @txns: outstanding transactions
|
||||
* @txn_lock: lock for modifications of @txns
|
||||
* @handlers: list of handlers for incoming messages
|
||||
*/
|
||||
struct qmi_handle {
|
||||
struct socket *sock;
|
||||
struct mutex sock_lock;
|
||||
|
||||
struct sockaddr_qrtr sq;
|
||||
|
||||
struct work_struct work;
|
||||
struct workqueue_struct *wq;
|
||||
|
||||
void *recv_buf;
|
||||
size_t recv_buf_size;
|
||||
|
||||
struct list_head lookups;
|
||||
struct list_head lookup_results;
|
||||
struct list_head services;
|
||||
|
||||
struct qmi_ops ops;
|
||||
|
||||
struct idr txns;
|
||||
struct mutex txn_lock;
|
||||
|
||||
const struct qmi_msg_handler *handlers;
|
||||
};
|
||||
|
||||
int qmi_add_lookup(struct qmi_handle *qmi, unsigned int service,
|
||||
unsigned int version, unsigned int instance);
|
||||
int qmi_add_server(struct qmi_handle *qmi, unsigned int service,
|
||||
unsigned int version, unsigned int instance);
|
||||
|
||||
int qmi_handle_init(struct qmi_handle *qmi, size_t max_msg_len,
|
||||
const struct qmi_ops *ops,
|
||||
const struct qmi_msg_handler *handlers);
|
||||
void qmi_handle_release(struct qmi_handle *qmi);
|
||||
|
||||
ssize_t qmi_send_request(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
|
||||
struct qmi_txn *txn, int msg_id, size_t len,
|
||||
struct qmi_elem_info *ei, const void *c_struct);
|
||||
ssize_t qmi_send_response(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
|
||||
struct qmi_txn *txn, int msg_id, size_t len,
|
||||
struct qmi_elem_info *ei, const void *c_struct);
|
||||
ssize_t qmi_send_indication(struct qmi_handle *qmi, struct sockaddr_qrtr *sq,
|
||||
int msg_id, size_t len, struct qmi_elem_info *ei,
|
||||
const void *c_struct);
|
||||
|
||||
void *qmi_encode_message(int type, unsigned int msg_id, size_t *len,
|
||||
unsigned int txn_id, struct qmi_elem_info *ei,
|
||||
const void *c_struct);
|
||||
|
||||
int qmi_decode_message(const void *buf, size_t len,
|
||||
struct qmi_elem_info *ei, void *c_struct);
|
||||
|
||||
int qmi_txn_init(struct qmi_handle *qmi, struct qmi_txn *txn,
|
||||
struct qmi_elem_info *ei, void *c_struct);
|
||||
int qmi_txn_wait(struct qmi_txn *txn, unsigned long timeout);
|
||||
void qmi_txn_cancel(struct qmi_txn *txn);
|
||||
|
||||
#endif
|
Загрузка…
Ссылка в новой задаче