Files
android_kernel_samsung_sm86…/wmi_tlv_helper.c
Sravan Kumar Kairam f2a6579d4d qcacmn: Validate buffer length in wmi tlv check and pad tlvs
Currently there is no validation of the buffer length passed by the
caller in the function to validate the TLV's coming for an event/
command. This may cause possible null pointer reference. In this
change validate the buffer length passed by the caller.

Change-Id: Ia365cf78acce3a235b9e15e6ed95a1aaa06b4c6f
CRs-Fixed: 2105739
2017-10-12 14:21:40 -07:00

1331 lines
42 KiB
C

/*
* Copyright (c) 2013-2017 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/*
* This file was originally distributed by Qualcomm Atheros, Inc.
* under proprietary terms before Copyright ownership was assigned
* to the Linux Foundation.
*/
#include "wmi_tlv_platform.c"
#include "wmi_tlv_defs.h"
#include "wmi_version.h"
#define WMITLV_GET_ATTRIB_NUM_TLVS 0xFFFFFFFF
#define WMITLV_GET_CMDID(val) (val & 0x00FFFFFF)
#define WMITLV_GET_NUM_TLVS(val) ((val >> 24) & 0xFF)
#define WMITLV_GET_TAGID(val) (val & 0x00000FFF)
#define WMITLV_GET_TAG_STRUCT_SIZE(val) ((val >> 12) & 0x000001FF)
#define WMITLV_GET_TAG_ARRAY_SIZE(val) ((val >> 21) & 0x000001FF)
#define WMITLV_GET_TAG_VARIED(val) ((val >> 30) & 0x00000001)
#define WMITLV_SET_ATTRB0(id) ((WMITLV_GET_TAG_NUM_TLV_ATTRIB(id) << 24) | \
(id & 0x00FFFFFF))
#define WMITLV_SET_ATTRB1(tagID, tagStructSize, tagArraySize, tagVaried) \
(((tagVaried&0x1)<<30) | ((tagArraySize&0x1FF)<<21) | \
((tagStructSize&0x1FF)<<12) | (tagID&0xFFF))
#define WMITLV_OP_SET_TLV_ATTRIB_macro(param_ptr, param_len, wmi_cmd_event_id, \
elem_tlv_tag, elem_struc_type, elem_name, var_len, arr_size) \
WMITLV_SET_ATTRB1(elem_tlv_tag, sizeof(elem_struc_type), arr_size, var_len),
#define WMITLV_GET_CMD_EVT_ATTRB_LIST(id) \
WMITLV_SET_ATTRB0(id), \
WMITLV_TABLE(id,SET_TLV_ATTRIB, NULL, 0)
A_UINT32 cmd_attr_list[] = {
WMITLV_ALL_CMD_LIST(WMITLV_GET_CMD_EVT_ATTRB_LIST)
};
A_UINT32 evt_attr_list[] = {
WMITLV_ALL_EVT_LIST(WMITLV_GET_CMD_EVT_ATTRB_LIST)
};
#ifdef NO_DYNAMIC_MEM_ALLOC
static wmitlv_cmd_param_info *g_wmi_static_cmd_param_info_buf;
A_UINT32 g_wmi_static_max_cmd_param_tlvs;
#endif
/**
* wmitlv_set_static_param_tlv_buf() - tlv helper function
* @param_tlv_buf: tlv buffer parameter
* @max_tlvs_accomodated: max no of tlv entries
*
*
* WMI TLV Helper function to set the static cmd_param_tlv structure
* and number of TLVs that can be accomodated in the structure.
* This function should be used when dynamic memory allocation is not
* supported. When dynamic memory allocation is not supported by any
* component then NO_DYNAMIC_MEMALLOC macro has to be defined in respective
* tlv_platform.c file. And respective component has to allocate
* cmd_param_tlv structure buffer to accomodate whatever number of TLV's.
* Both the buffer address and number of TLV's that can be accomodated in
* the buffer should be sent as arguments to this function.
*
* Return None
*/
void
wmitlv_set_static_param_tlv_buf(void *param_tlv_buf,
A_UINT32 max_tlvs_accomodated)
{
#ifdef NO_DYNAMIC_MEM_ALLOC
g_wmi_static_cmd_param_info_buf = param_tlv_buf;
g_wmi_static_max_cmd_param_tlvs = max_tlvs_accomodated;
#endif
}
/**
* wmitlv_get_attributes() - tlv helper function
* @is_cmd_id: boolean for command attribute
* @cmd_event_id: command event id
* @curr_tlv_order: tlv order
* @tlv_attr_ptr: pointer to tlv attribute
*
*
* WMI TLV Helper functions to find the attributes of the
* Command/Event TLVs.
*
* Return: 0 if success. Return >=1 if failure.
*/
static
A_UINT32 wmitlv_get_attributes(A_UINT32 is_cmd_id, A_UINT32 cmd_event_id,
A_UINT32 curr_tlv_order,
wmitlv_attributes_struc *tlv_attr_ptr)
{
A_UINT32 i, base_index, num_tlvs, num_entries;
A_UINT32 *pAttrArrayList;
if (is_cmd_id) {
pAttrArrayList = &cmd_attr_list[0];
num_entries = QDF_ARRAY_SIZE(cmd_attr_list);
} else {
pAttrArrayList = &evt_attr_list[0];
num_entries = QDF_ARRAY_SIZE(evt_attr_list);
}
for (i = 0; i < num_entries; i++) {
num_tlvs = WMITLV_GET_NUM_TLVS(pAttrArrayList[i]);
if (WMITLV_GET_CMDID(cmd_event_id) ==
WMITLV_GET_CMDID(pAttrArrayList[i])) {
tlv_attr_ptr->cmd_num_tlv = num_tlvs;
/* Return success from here when only number of TLVS for
* this command/event is required */
if (curr_tlv_order == WMITLV_GET_ATTRIB_NUM_TLVS) {
wmi_tlv_print_verbose
("%s: WMI TLV attribute definitions for %s:0x%x found; num_of_tlvs:%d\n",
__func__, (is_cmd_id ? "Cmd" : "Evt"),
cmd_event_id, num_tlvs);
return 0;
}
/* Return failure if tlv_order is more than the expected
* number of TLVs */
if (curr_tlv_order >= num_tlvs) {
wmi_tlv_print_error
("%s: ERROR: TLV order %d greater than num_of_tlvs:%d for %s:0x%x\n",
__func__, curr_tlv_order, num_tlvs,
(is_cmd_id ? "Cmd" : "Evt"), cmd_event_id);
return 1;
}
base_index = i + 1; /* index to first TLV attributes */
wmi_tlv_print_verbose
("%s: WMI TLV attributes for %s:0x%x tlv[%d]:0x%x\n",
__func__, (is_cmd_id ? "Cmd" : "Evt"),
cmd_event_id, curr_tlv_order,
pAttrArrayList[(base_index + curr_tlv_order)]);
tlv_attr_ptr->tag_order = curr_tlv_order;
tlv_attr_ptr->tag_id =
WMITLV_GET_TAGID(pAttrArrayList
[(base_index + curr_tlv_order)]);
tlv_attr_ptr->tag_struct_size =
WMITLV_GET_TAG_STRUCT_SIZE(pAttrArrayList
[(base_index +
curr_tlv_order)]);
tlv_attr_ptr->tag_varied_size =
WMITLV_GET_TAG_VARIED(pAttrArrayList
[(base_index +
curr_tlv_order)]);
tlv_attr_ptr->tag_array_size =
WMITLV_GET_TAG_ARRAY_SIZE(pAttrArrayList
[(base_index +
curr_tlv_order)]);
return 0;
}
i += num_tlvs;
}
wmi_tlv_print_error
("%s: ERROR: Didn't found WMI TLV attribute definitions for %s:0x%x\n",
__func__, (is_cmd_id ? "Cmd" : "Evt"), cmd_event_id);
return 1;
}
/**
* wmitlv_check_tlv_params() - tlv helper function
* @os_handle: os context handle
* @param_struc_ptr: pointer to tlv structure
* @is_cmd_id: boolean for command attribute
* @wmi_cmd_event_id: command event id
*
*
* Helper Function to vaidate the prepared TLV's for
* an WMI event/command to be sent.
*
* Return: 0 if success. Return < 0 if failure.
*/
static int
wmitlv_check_tlv_params(void *os_handle, void *param_struc_ptr,
A_UINT32 param_buf_len, A_UINT32 is_cmd_id,
A_UINT32 wmi_cmd_event_id)
{
wmitlv_attributes_struc attr_struct_ptr;
A_UINT32 buf_idx = 0;
A_UINT32 tlv_index = 0;
A_UINT8 *buf_ptr = (unsigned char *)param_struc_ptr;
A_UINT32 expected_num_tlvs, expected_tlv_len;
A_INT32 error = -1;
/* Get the number of TLVs for this command/event */
if (wmitlv_get_attributes
(is_cmd_id, wmi_cmd_event_id, WMITLV_GET_ATTRIB_NUM_TLVS,
&attr_struct_ptr) != 0) {
wmi_tlv_print_error
("%s: ERROR: Couldn't get expected number of TLVs for Cmd=%d\n",
__func__, wmi_cmd_event_id);
goto Error_wmitlv_check_tlv_params;
}
/* NOTE: the returned number of TLVs is in "attr_struct_ptr.cmd_num_tlv" */
expected_num_tlvs = attr_struct_ptr.cmd_num_tlv;
while ((buf_idx + WMI_TLV_HDR_SIZE) <= param_buf_len) {
A_UINT32 curr_tlv_tag =
WMITLV_GET_TLVTAG(WMITLV_GET_HDR(buf_ptr));
A_UINT32 curr_tlv_len =
WMITLV_GET_TLVLEN(WMITLV_GET_HDR(buf_ptr));
if ((buf_idx + WMI_TLV_HDR_SIZE + curr_tlv_len) > param_buf_len) {
wmi_tlv_print_error
("%s: ERROR: Invalid TLV length for Cmd=%d Tag_order=%d buf_idx=%d Tag:%d Len:%d TotalLen:%d\n",
__func__, wmi_cmd_event_id, tlv_index, buf_idx,
curr_tlv_tag, curr_tlv_len, param_buf_len);
goto Error_wmitlv_check_tlv_params;
}
/* Get the attributes of the TLV with the given order in "tlv_index" */
wmi_tlv_OS_MEMZERO(&attr_struct_ptr,
sizeof(wmitlv_attributes_struc));
if (wmitlv_get_attributes
(is_cmd_id, wmi_cmd_event_id, tlv_index,
&attr_struct_ptr) != 0) {
wmi_tlv_print_error
("%s: ERROR: No TLV attributes found for Cmd=%d Tag_order=%d\n",
__func__, wmi_cmd_event_id, tlv_index);
goto Error_wmitlv_check_tlv_params;
}
/* Found the TLV that we wanted */
wmi_tlv_print_verbose("%s: [tlv %d]: tag=%d, len=%d\n",
__func__, tlv_index, curr_tlv_tag,
curr_tlv_len);
/* Validating Tag ID order */
if (curr_tlv_tag != attr_struct_ptr.tag_id) {
wmi_tlv_print_error
("%s: ERROR: TLV has wrong tag in order for Cmd=0x%x. Given=%d, Expected=%d.\n",
__func__, wmi_cmd_event_id, curr_tlv_tag,
attr_struct_ptr.tag_id);
goto Error_wmitlv_check_tlv_params;
}
/* Validate Tag length */
/* Array TLVs length checking needs special handling */
if ((curr_tlv_tag >= WMITLV_TAG_FIRST_ARRAY_ENUM)
&& (curr_tlv_tag <= WMITLV_TAG_LAST_ARRAY_ENUM)) {
if (attr_struct_ptr.tag_varied_size == WMITLV_SIZE_FIX) {
/* Array size can't be invalid for fixed size Array TLV */
if (WMITLV_ARR_SIZE_INVALID ==
attr_struct_ptr.tag_array_size) {
wmi_tlv_print_error
("%s: ERROR: array_size can't be invalid for Array TLV Cmd=0x%x Tag=%d\n",
__func__, wmi_cmd_event_id,
curr_tlv_tag);
goto Error_wmitlv_check_tlv_params;
}
expected_tlv_len =
attr_struct_ptr.tag_array_size *
attr_struct_ptr.tag_struct_size;
/* Paddding is only required for Byte array Tlvs all other
* array tlv's should be aligned to 4 bytes during their
* definition */
if (WMITLV_TAG_ARRAY_BYTE ==
attr_struct_ptr.tag_id) {
expected_tlv_len =
roundup(expected_tlv_len,
sizeof(A_UINT32));
}
if (curr_tlv_len != expected_tlv_len) {
wmi_tlv_print_error
("%s: ERROR: TLV has wrong length for Cmd=0x%x. Tag_order=%d Tag=%d, Given_Len:%d Expected_Len=%d.\n",
__func__, wmi_cmd_event_id,
tlv_index, curr_tlv_tag,
curr_tlv_len, expected_tlv_len);
goto Error_wmitlv_check_tlv_params;
}
} else {
/* Array size should be invalid for variable size Array TLV */
if (WMITLV_ARR_SIZE_INVALID !=
attr_struct_ptr.tag_array_size) {
wmi_tlv_print_error
("%s: ERROR: array_size should be invalid for Array TLV Cmd=0x%x Tag=%d\n",
__func__, wmi_cmd_event_id,
curr_tlv_tag);
goto Error_wmitlv_check_tlv_params;
}
/* Incase of variable length TLV's, there is no expectation
* on the length field so do whatever checking you can
* depending on the TLV tag if TLV length is non-zero */
if (curr_tlv_len != 0) {
/* Verify TLV length is aligned to the size of structure */
if ((curr_tlv_len %
attr_struct_ptr.tag_struct_size) !=
0) {
wmi_tlv_print_error
("%s: ERROR: TLV length %d for Cmd=0x%x is not aligned to size of structure(%d bytes)\n",
__func__, curr_tlv_len,
wmi_cmd_event_id,
attr_struct_ptr.
tag_struct_size);
goto Error_wmitlv_check_tlv_params;
}
if (curr_tlv_tag ==
WMITLV_TAG_ARRAY_STRUC) {
A_UINT8 *tlv_buf_ptr = NULL;
A_UINT32 in_tlv_len;
A_UINT32 idx;
A_UINT32 num_of_elems;
/* Verify length of inner TLVs */
num_of_elems =
curr_tlv_len /
attr_struct_ptr.
tag_struct_size;
/* Set tlv_buf_ptr to the first inner TLV address */
tlv_buf_ptr =
buf_ptr + WMI_TLV_HDR_SIZE;
for (idx = 0;
idx < num_of_elems;
idx++) {
in_tlv_len =
WMITLV_GET_TLVLEN
(WMITLV_GET_HDR
(tlv_buf_ptr));
if ((in_tlv_len +
WMI_TLV_HDR_SIZE)
!=
attr_struct_ptr.
tag_struct_size) {
wmi_tlv_print_error
("%s: ERROR: TLV has wrong length for Cmd=0x%x. Tag_order=%d Tag=%d, Given_Len:%zu Expected_Len=%d.\n",
__func__,
wmi_cmd_event_id,
tlv_index,
curr_tlv_tag,
(in_tlv_len
+
WMI_TLV_HDR_SIZE),
attr_struct_ptr.
tag_struct_size);
goto Error_wmitlv_check_tlv_params;
}
tlv_buf_ptr +=
in_tlv_len +
WMI_TLV_HDR_SIZE;
}
} else
if ((curr_tlv_tag ==
WMITLV_TAG_ARRAY_UINT32)
|| (curr_tlv_tag ==
WMITLV_TAG_ARRAY_BYTE)
|| (curr_tlv_tag ==
WMITLV_TAG_ARRAY_FIXED_STRUC)) {
/* Nothing to verify here */
} else {
wmi_tlv_print_error
("%s ERROR Need to handle the Array tlv %d for variable length for Cmd=0x%x\n",
__func__,
attr_struct_ptr.tag_id,
wmi_cmd_event_id);
goto Error_wmitlv_check_tlv_params;
}
}
}
} else {
/* Non-array TLV. */
if ((curr_tlv_len + WMI_TLV_HDR_SIZE) !=
attr_struct_ptr.tag_struct_size) {
wmi_tlv_print_error
("%s: ERROR: TLV has wrong length for Cmd=0x%x. Given=%zu, Expected=%d.\n",
__func__, wmi_cmd_event_id,
(curr_tlv_len + WMI_TLV_HDR_SIZE),
attr_struct_ptr.tag_struct_size);
goto Error_wmitlv_check_tlv_params;
}
}
/* Check TLV length is aligned to 4 bytes or not */
if ((curr_tlv_len % sizeof(A_UINT32)) != 0) {
wmi_tlv_print_error
("%s: ERROR: TLV length %d for Cmd=0x%x is not aligned to %zu bytes\n",
__func__, curr_tlv_len, wmi_cmd_event_id,
sizeof(A_UINT32));
goto Error_wmitlv_check_tlv_params;
}
tlv_index++;
buf_ptr += curr_tlv_len + WMI_TLV_HDR_SIZE;
buf_idx += curr_tlv_len + WMI_TLV_HDR_SIZE;
}
if (tlv_index != expected_num_tlvs) {
wmi_tlv_print_verbose
("%s: INFO: Less number of TLVs filled for Cmd=0x%x Filled %d Expected=%d\n",
__func__, wmi_cmd_event_id, tlv_index, expected_num_tlvs);
}
return 0;
Error_wmitlv_check_tlv_params:
return error;
}
/**
* wmitlv_check_event_tlv_params() - tlv helper function
* @os_handle: os context handle
* @param_struc_ptr: pointer to tlv structure
* @is_cmd_id: boolean for command attribute
* @wmi_cmd_event_id: command event id
*
*
* Helper Function to vaidate the prepared TLV's for
* an WMI event/command to be sent.
*
* Return: 0 if success. Return < 0 if failure.
*/
int
wmitlv_check_event_tlv_params(void *os_handle, void *param_struc_ptr,
A_UINT32 param_buf_len, A_UINT32 wmi_cmd_event_id)
{
A_UINT32 is_cmd_id = 0;
return wmitlv_check_tlv_params
(os_handle, param_struc_ptr, param_buf_len, is_cmd_id,
wmi_cmd_event_id);
}
/**
* wmitlv_check_command_tlv_params() - tlv helper function
* @os_handle: os context handle
* @param_struc_ptr: pointer to tlv structure
* @is_cmd_id: boolean for command attribute
* @wmi_cmd_event_id: command event id
*
*
* Helper Function to vaidate the prepared TLV's for
* an WMI event/command to be sent.
*
* Return: 0 if success. Return < 0 if failure.
*/
int
wmitlv_check_command_tlv_params(void *os_handle, void *param_struc_ptr,
A_UINT32 param_buf_len,
A_UINT32 wmi_cmd_event_id)
{
A_UINT32 is_cmd_id = 1;
return wmitlv_check_tlv_params
(os_handle, param_struc_ptr, param_buf_len, is_cmd_id,
wmi_cmd_event_id);
}
/**
* wmitlv_check_and_pad_tlvs() - tlv helper function
* @os_handle: os context handle
* @param_buf_len: length of tlv parameter
* @param_struc_ptr: pointer to tlv structure
* @is_cmd_id: boolean for command attribute
* @wmi_cmd_event_id: command event id
* @wmi_cmd_struct_ptr: wmi command structure
*
*
* vaidate the TLV's coming for an event/command and
* also pads data to TLV's if necessary
*
* Return: 0 if success. Return < 0 if failure.
*/
static int
wmitlv_check_and_pad_tlvs(void *os_handle, void *param_struc_ptr,
A_UINT32 param_buf_len, A_UINT32 is_cmd_id,
A_UINT32 wmi_cmd_event_id, void **wmi_cmd_struct_ptr)
{
wmitlv_attributes_struc attr_struct_ptr;
A_UINT32 buf_idx = 0;
A_UINT32 tlv_index = 0;
A_UINT32 num_of_elems = 0;
int tlv_size_diff = 0;
A_UINT8 *buf_ptr = (unsigned char *)param_struc_ptr;
wmitlv_cmd_param_info *cmd_param_tlvs_ptr = NULL;
A_UINT32 remaining_expected_tlvs = 0xFFFFFFFF;
A_UINT32 len_wmi_cmd_struct_buf;
A_INT32 error = -1;
/* Get the number of TLVs for this command/event */
if (wmitlv_get_attributes
(is_cmd_id, wmi_cmd_event_id, WMITLV_GET_ATTRIB_NUM_TLVS,
&attr_struct_ptr) != 0) {
wmi_tlv_print_error
("%s: ERROR: Couldn't get expected number of TLVs for Cmd=%d\n",
__func__, wmi_cmd_event_id);
return error;
}
/* NOTE: the returned number of TLVs is in "attr_struct_ptr.cmd_num_tlv" */
if (param_buf_len < WMI_TLV_HDR_SIZE) {
wmi_tlv_print_error
("%s: ERROR: Incorrect param buf length passed\n",
__func__);
return error;
}
/* Create base structure of format wmi_cmd_event_id##_param_tlvs */
len_wmi_cmd_struct_buf =
attr_struct_ptr.cmd_num_tlv * sizeof(wmitlv_cmd_param_info);
#ifndef NO_DYNAMIC_MEM_ALLOC
/* Dynamic memory allocation supported */
wmi_tlv_os_mem_alloc(os_handle, *wmi_cmd_struct_ptr,
len_wmi_cmd_struct_buf);
#else
/* Dynamic memory allocation is not supported. Use the buffer
* g_wmi_static_cmd_param_info_buf, which should be set using
* wmi_tlv_set_static_param_tlv_buf(),
* for base structure of format wmi_cmd_event_id##_param_tlvs */
*wmi_cmd_struct_ptr = g_wmi_static_cmd_param_info_buf;
if (attr_struct_ptr.cmd_num_tlv > g_wmi_static_max_cmd_param_tlvs) {
/* Error: Expecting more TLVs that accomodated for static structure */
wmi_tlv_print_error
("%s: Error: Expecting more TLVs that accomodated for static structure. Expected:%d Accomodated:%d\n",
__func__, attr_struct_ptr.cmd_num_tlv,
g_wmi_static_max_cmd_param_tlvs);
return error;
}
#endif
if (*wmi_cmd_struct_ptr == NULL) {
/* Error: unable to alloc memory */
wmi_tlv_print_error
("%s: Error: unable to alloc memory (size=%d) for TLV\n",
__func__, len_wmi_cmd_struct_buf);
return error;
}
cmd_param_tlvs_ptr = (wmitlv_cmd_param_info *) *wmi_cmd_struct_ptr;
wmi_tlv_OS_MEMZERO(cmd_param_tlvs_ptr, len_wmi_cmd_struct_buf);
remaining_expected_tlvs = attr_struct_ptr.cmd_num_tlv;
while (((buf_idx + WMI_TLV_HDR_SIZE) <= param_buf_len)
&& (remaining_expected_tlvs)) {
A_UINT32 curr_tlv_tag =
WMITLV_GET_TLVTAG(WMITLV_GET_HDR(buf_ptr));
A_UINT32 curr_tlv_len =
WMITLV_GET_TLVLEN(WMITLV_GET_HDR(buf_ptr));
int num_padding_bytes = 0;
/* Get the attributes of the TLV with the given order in "tlv_index" */
wmi_tlv_OS_MEMZERO(&attr_struct_ptr,
sizeof(wmitlv_attributes_struc));
if (wmitlv_get_attributes
(is_cmd_id, wmi_cmd_event_id, tlv_index,
&attr_struct_ptr) != 0) {
wmi_tlv_print_error
("%s: ERROR: No TLV attributes found for Cmd=%d Tag_order=%d\n",
__func__, wmi_cmd_event_id, tlv_index);
goto Error_wmitlv_check_and_pad_tlvs;
}
/* Found the TLV that we wanted */
wmi_tlv_print_verbose("%s: [tlv %d]: tag=%d, len=%d\n",
__func__, tlv_index, curr_tlv_tag,
curr_tlv_len);
/* Validating Tag order */
if (curr_tlv_tag != attr_struct_ptr.tag_id) {
wmi_tlv_print_error
("%s: ERROR: TLV has wrong tag in order for Cmd=0x%x. Given=%d, Expected=%d.\n",
__func__, wmi_cmd_event_id, curr_tlv_tag,
attr_struct_ptr.tag_id);
goto Error_wmitlv_check_and_pad_tlvs;
}
if ((curr_tlv_tag >= WMITLV_TAG_FIRST_ARRAY_ENUM)
&& (curr_tlv_tag <= WMITLV_TAG_LAST_ARRAY_ENUM)) {
/* Current Tag is an array of some kind. */
/* Skip the TLV header of this array */
buf_ptr += WMI_TLV_HDR_SIZE;
buf_idx += WMI_TLV_HDR_SIZE;
} else {
/* Non-array TLV. */
curr_tlv_len += WMI_TLV_HDR_SIZE;
}
if (attr_struct_ptr.tag_varied_size == WMITLV_SIZE_FIX) {
/* This TLV is fixed length */
if (WMITLV_ARR_SIZE_INVALID ==
attr_struct_ptr.tag_array_size) {
tlv_size_diff =
curr_tlv_len -
attr_struct_ptr.tag_struct_size;
num_of_elems =
(curr_tlv_len > WMI_TLV_HDR_SIZE) ? 1 : 0;
} else {
tlv_size_diff =
curr_tlv_len -
(attr_struct_ptr.tag_struct_size *
attr_struct_ptr.tag_array_size);
num_of_elems = attr_struct_ptr.tag_array_size;
}
} else {
/* This TLV has a variable number of elements */
if (WMITLV_TAG_ARRAY_STRUC == attr_struct_ptr.tag_id) {
A_UINT32 in_tlv_len = 0;
if (curr_tlv_len != 0) {
in_tlv_len =
WMITLV_GET_TLVLEN(WMITLV_GET_HDR
(buf_ptr));
in_tlv_len += WMI_TLV_HDR_SIZE;
tlv_size_diff =
in_tlv_len -
attr_struct_ptr.tag_struct_size;
num_of_elems =
curr_tlv_len / in_tlv_len;
wmi_tlv_print_verbose
("%s: WARN: TLV array of structures in_tlv_len=%d struct_size:%d diff:%d num_of_elems=%d \n",
__func__, in_tlv_len,
attr_struct_ptr.tag_struct_size,
tlv_size_diff, num_of_elems);
} else {
tlv_size_diff = 0;
num_of_elems = 0;
}
} else
if ((WMITLV_TAG_ARRAY_UINT32 ==
attr_struct_ptr.tag_id)
|| (WMITLV_TAG_ARRAY_BYTE ==
attr_struct_ptr.tag_id)
|| (WMITLV_TAG_ARRAY_FIXED_STRUC ==
attr_struct_ptr.tag_id)) {
tlv_size_diff = 0;
num_of_elems =
curr_tlv_len /
attr_struct_ptr.tag_struct_size;
} else {
wmi_tlv_print_error
("%s ERROR Need to handle this tag ID for variable length %d\n",
__func__, attr_struct_ptr.tag_id);
goto Error_wmitlv_check_and_pad_tlvs;
}
}
if ((WMITLV_TAG_ARRAY_STRUC == attr_struct_ptr.tag_id) &&
(tlv_size_diff != 0)) {
void *new_tlv_buf = NULL;
A_UINT8 *tlv_buf_ptr = NULL;
A_UINT32 in_tlv_len;
A_UINT32 i;
if (attr_struct_ptr.tag_varied_size == WMITLV_SIZE_FIX) {
/* This is not allowed. The tag WMITLV_TAG_ARRAY_STRUC can
* only be used with variable-length structure array
* should not have a fixed number of elements (contradicting).
* Use WMITLV_TAG_ARRAY_FIXED_STRUC tag for fixed size
* structure array(where structure never change without
* breaking compatibility) */
wmi_tlv_print_error
("%s: ERROR: TLV (tag=%d) should be variable-length and not fixed length\n",
__func__, curr_tlv_tag);
goto Error_wmitlv_check_and_pad_tlvs;
}
/* Warning: Needs to allocate a larger structure and pad with zeros */
wmi_tlv_print_verbose
("%s: WARN: TLV array of structures needs padding. tlv_size_diff=%d\n",
__func__, tlv_size_diff);
/* incoming structure length */
in_tlv_len =
WMITLV_GET_TLVLEN(WMITLV_GET_HDR(buf_ptr)) +
WMI_TLV_HDR_SIZE;
#ifndef NO_DYNAMIC_MEM_ALLOC
wmi_tlv_os_mem_alloc(os_handle, new_tlv_buf,
(num_of_elems *
attr_struct_ptr.tag_struct_size));
if (new_tlv_buf == NULL) {
/* Error: unable to alloc memory */
wmi_tlv_print_error
("%s: Error: unable to alloc memory (size=%d) for padding the TLV array %d\n",
__func__,
(num_of_elems *
attr_struct_ptr.tag_struct_size),
curr_tlv_tag);
goto Error_wmitlv_check_and_pad_tlvs;
}
wmi_tlv_OS_MEMZERO(new_tlv_buf,
(num_of_elems *
attr_struct_ptr.tag_struct_size));
tlv_buf_ptr = (A_UINT8 *) new_tlv_buf;
for (i = 0; i < num_of_elems; i++) {
if (tlv_size_diff > 0) {
/* Incoming structure size is greater than expected
* structure size. so copy the number of bytes equal
* to expected structure size */
wmi_tlv_OS_MEMCPY(tlv_buf_ptr,
(void *)(buf_ptr +
i *
in_tlv_len),
attr_struct_ptr.
tag_struct_size);
} else {
/* Incoming structure size is smaller than expected
* structure size. so copy the number of bytes equal
* to incoming structure size */
wmi_tlv_OS_MEMCPY(tlv_buf_ptr,
(void *)(buf_ptr +
i *
in_tlv_len),
in_tlv_len);
}
tlv_buf_ptr += attr_struct_ptr.tag_struct_size;
}
#else
{
A_UINT8 *src_addr;
A_UINT8 *dst_addr;
A_UINT32 buf_mov_len;
if (tlv_size_diff < 0) {
/* Incoming structure size is smaller than expected size
* then this needs padding for each element in the array */
/* Find amount of bytes to be padded for one element */
num_padding_bytes = tlv_size_diff * -1;
/* Move subsequent TLVs by number of bytes to be padded
* for all elements */
if (param_buf_len >
(buf_idx + curr_tlv_len)) {
src_addr =
buf_ptr + curr_tlv_len;
dst_addr =
buf_ptr + curr_tlv_len +
(num_padding_bytes *
num_of_elems);
buf_mov_len =
param_buf_len - (buf_idx +
curr_tlv_len);
wmi_tlv_OS_MEMMOVE(dst_addr,
src_addr,
buf_mov_len);
}
/* Move subsequent elements of array down by number of
* bytes to be padded for one element and alse set
* padding bytes to zero */
tlv_buf_ptr = buf_ptr;
for (i = 0; i < num_of_elems; i++) {
src_addr =
tlv_buf_ptr + in_tlv_len;
if (i != (num_of_elems - 1)) {
/* Need not move anything for last element
* in the array */
dst_addr =
tlv_buf_ptr +
in_tlv_len +
num_padding_bytes;
buf_mov_len =
curr_tlv_len -
((i +
1) * in_tlv_len);
wmi_tlv_OS_MEMMOVE
(dst_addr, src_addr,
buf_mov_len);
}
/* Set the padding bytes to zeroes */
wmi_tlv_OS_MEMZERO(src_addr,
num_padding_bytes);
tlv_buf_ptr +=
attr_struct_ptr.
tag_struct_size;
}
/* Update the number of padding bytes to total number
* of bytes padded for all elements in the array */
num_padding_bytes =
num_padding_bytes * num_of_elems;
new_tlv_buf = buf_ptr;
} else {
/* Incoming structure size is greater than expected size
* then this needs shrinking for each element in the array */
/* Find amount of bytes to be shrinked for one element */
num_padding_bytes = tlv_size_diff * -1;
/* Move subsequent elements of array up by number of bytes
* to be shrinked for one element */
tlv_buf_ptr = buf_ptr;
for (i = 0; i < (num_of_elems - 1); i++) {
src_addr =
tlv_buf_ptr + in_tlv_len;
dst_addr =
tlv_buf_ptr + in_tlv_len +
num_padding_bytes;
buf_mov_len =
curr_tlv_len -
((i + 1) * in_tlv_len);
wmi_tlv_OS_MEMMOVE(dst_addr,
src_addr,
buf_mov_len);
tlv_buf_ptr +=
attr_struct_ptr.
tag_struct_size;
}
/* Move subsequent TLVs by number of bytes to be shrinked
* for all elements */
if (param_buf_len >
(buf_idx + curr_tlv_len)) {
src_addr =
buf_ptr + curr_tlv_len;
dst_addr =
buf_ptr + curr_tlv_len +
(num_padding_bytes *
num_of_elems);
buf_mov_len =
param_buf_len - (buf_idx +
curr_tlv_len);
wmi_tlv_OS_MEMMOVE(dst_addr,
src_addr,
buf_mov_len);
}
/* Update the number of padding bytes to total number of
* bytes shrinked for all elements in the array */
num_padding_bytes =
num_padding_bytes * num_of_elems;
new_tlv_buf = buf_ptr;
}
}
#endif
cmd_param_tlvs_ptr[tlv_index].tlv_ptr = new_tlv_buf;
cmd_param_tlvs_ptr[tlv_index].num_elements =
num_of_elems;
cmd_param_tlvs_ptr[tlv_index].buf_is_allocated = 1; /* Indicates that buffer is allocated */
} else if (tlv_size_diff >= 0) {
/* Warning: some parameter truncation */
if (tlv_size_diff > 0) {
wmi_tlv_print_verbose
("%s: WARN: TLV truncated. tlv_size_diff=%d, curr_tlv_len=%d\n",
__func__, tlv_size_diff, curr_tlv_len);
}
/* TODO: this next line needs more comments and explanation */
cmd_param_tlvs_ptr[tlv_index].tlv_ptr =
(attr_struct_ptr.tag_varied_size
&& !curr_tlv_len) ? NULL : (void *)buf_ptr;
cmd_param_tlvs_ptr[tlv_index].num_elements =
num_of_elems;
cmd_param_tlvs_ptr[tlv_index].buf_is_allocated = 0; /* Indicates that buffer is not allocated */
} else {
void *new_tlv_buf = NULL;
/* Warning: Needs to allocate a larger structure and pad with zeros */
wmi_tlv_print_verbose
("%s: WARN: TLV needs padding. tlv_size_diff=%d\n",
__func__, tlv_size_diff);
#ifndef NO_DYNAMIC_MEM_ALLOC
/* Dynamic memory allocation is supported */
wmi_tlv_os_mem_alloc(os_handle, new_tlv_buf,
(curr_tlv_len - tlv_size_diff));
if (new_tlv_buf == NULL) {
/* Error: unable to alloc memory */
wmi_tlv_print_error
("%s: Error: unable to alloc memory (size=%d) for padding the TLV %d\n",
__func__, (curr_tlv_len - tlv_size_diff),
curr_tlv_tag);
goto Error_wmitlv_check_and_pad_tlvs;
}
wmi_tlv_OS_MEMZERO(new_tlv_buf,
(curr_tlv_len - tlv_size_diff));
wmi_tlv_OS_MEMCPY(new_tlv_buf, (void *)buf_ptr,
curr_tlv_len);
#else
/* Dynamic memory allocation is not supported. Padding has
* to be done with in the existing buffer assuming we have
* enough space to grow */
{
/* Note: tlv_size_diff is a value less than zero */
/* Move the Subsequent TLVs by amount of bytes needs to be padded */
A_UINT8 *src_addr;
A_UINT8 *dst_addr;
A_UINT32 src_len;
num_padding_bytes = (tlv_size_diff * -1);
src_addr = buf_ptr + curr_tlv_len;
dst_addr =
buf_ptr + curr_tlv_len + num_padding_bytes;
src_len =
param_buf_len - (buf_idx + curr_tlv_len);
wmi_tlv_OS_MEMMOVE(dst_addr, src_addr, src_len);
/* Set the padding bytes to zeroes */
wmi_tlv_OS_MEMZERO(src_addr, num_padding_bytes);
new_tlv_buf = buf_ptr;
}
#endif
cmd_param_tlvs_ptr[tlv_index].tlv_ptr = new_tlv_buf;
cmd_param_tlvs_ptr[tlv_index].num_elements =
num_of_elems;
cmd_param_tlvs_ptr[tlv_index].buf_is_allocated = 1; /* Indicates that buffer is allocated */
}
tlv_index++;
remaining_expected_tlvs--;
buf_ptr += curr_tlv_len + num_padding_bytes;
buf_idx += curr_tlv_len + num_padding_bytes;
}
return 0;
Error_wmitlv_check_and_pad_tlvs:
if (is_cmd_id) {
wmitlv_free_allocated_command_tlvs(wmi_cmd_event_id,
wmi_cmd_struct_ptr);
} else {
wmitlv_free_allocated_event_tlvs(wmi_cmd_event_id,
wmi_cmd_struct_ptr);
}
*wmi_cmd_struct_ptr = NULL;
return error;
}
/**
* wmitlv_check_and_pad_event_tlvs() - tlv helper function
* @os_handle: os context handle
* @param_struc_ptr: pointer to tlv structure
* @param_buf_len: length of tlv parameter
* @wmi_cmd_event_id: command event id
* @wmi_cmd_struct_ptr: wmi command structure
*
*
* validate and pad(if necessary) for incoming WMI Event TLVs
*
* Return: 0 if success. Return < 0 if failure.
*/
int
wmitlv_check_and_pad_event_tlvs(void *os_handle, void *param_struc_ptr,
A_UINT32 param_buf_len,
A_UINT32 wmi_cmd_event_id,
void **wmi_cmd_struct_ptr)
{
A_UINT32 is_cmd_id = 0;
return wmitlv_check_and_pad_tlvs
(os_handle, param_struc_ptr, param_buf_len, is_cmd_id,
wmi_cmd_event_id, wmi_cmd_struct_ptr);
}
/**
* wmitlv_check_and_pad_command_tlvs() - tlv helper function
* @os_handle: os context handle
* @param_struc_ptr: pointer to tlv structure
* @param_buf_len: length of tlv parameter
* @wmi_cmd_event_id: command event id
* @wmi_cmd_struct_ptr: wmi command structure
*
*
* validate and pad(if necessary) for incoming WMI Command TLVs
*
* Return: 0 if success. Return < 0 if failure.
*/
int
wmitlv_check_and_pad_command_tlvs(void *os_handle, void *param_struc_ptr,
A_UINT32 param_buf_len,
A_UINT32 wmi_cmd_event_id,
void **wmi_cmd_struct_ptr)
{
A_UINT32 is_cmd_id = 1;
return wmitlv_check_and_pad_tlvs
(os_handle, param_struc_ptr, param_buf_len, is_cmd_id,
wmi_cmd_event_id, wmi_cmd_struct_ptr);
}
/**
* wmitlv_free_allocated_tlvs() - tlv helper function
* @is_cmd_id: bollean to check if cmd or event tlv
* @cmd_event_id: command or event id
* @wmi_cmd_struct_ptr: wmi command structure
*
*
* free any allocated buffers for WMI Event/Command TLV processing
*
* Return: none
*/
static void wmitlv_free_allocated_tlvs(A_UINT32 is_cmd_id,
A_UINT32 cmd_event_id,
void **wmi_cmd_struct_ptr)
{
void *ptr = *wmi_cmd_struct_ptr;
if (!ptr) {
wmi_tlv_print_error("%s: Nothing to free for CMD/Event 0x%x\n",
__func__, cmd_event_id);
return;
}
#ifndef NO_DYNAMIC_MEM_ALLOC
/* macro to free that previously allocated memory for this TLV. When (op==FREE_TLV_ELEM). */
#define WMITLV_OP_FREE_TLV_ELEM_macro(param_ptr, param_len, wmi_cmd_event_id, elem_tlv_tag, elem_struc_type, elem_name, var_len, arr_size) \
if ((((WMITLV_TYPEDEF_STRUCT_PARAMS_TLVS(wmi_cmd_event_id) *)ptr)->WMITLV_FIELD_BUF_IS_ALLOCATED(elem_name)) && \
(((WMITLV_TYPEDEF_STRUCT_PARAMS_TLVS(wmi_cmd_event_id) *)ptr)->elem_name != NULL)) \
{ \
wmi_tlv_os_mem_free(((WMITLV_TYPEDEF_STRUCT_PARAMS_TLVS(wmi_cmd_event_id) *)ptr)->elem_name); \
}
#define WMITLV_FREE_TLV_ELEMS(id) \
case id: \
{ \
WMITLV_TABLE(id, FREE_TLV_ELEM, NULL, 0) \
} \
break;
if (is_cmd_id) {
switch (cmd_event_id) {
WMITLV_ALL_CMD_LIST(WMITLV_FREE_TLV_ELEMS);
default:
wmi_tlv_print_error
("%s: ERROR: Cannot find the TLVs attributes for Cmd=0x%x, %d\n",
__func__, cmd_event_id, cmd_event_id);
}
} else {
switch (cmd_event_id) {
WMITLV_ALL_EVT_LIST(WMITLV_FREE_TLV_ELEMS);
default:
wmi_tlv_print_error
("%s: ERROR: Cannot find the TLVs attributes for Cmd=0x%x, %d\n",
__func__, cmd_event_id, cmd_event_id);
}
}
wmi_tlv_os_mem_free(*wmi_cmd_struct_ptr);
*wmi_cmd_struct_ptr = NULL;
#endif
return;
}
/**
* wmitlv_free_allocated_command_tlvs() - tlv helper function
* @cmd_event_id: command or event id
* @wmi_cmd_struct_ptr: wmi command structure
*
*
* free any allocated buffers for WMI Event/Command TLV processing
*
* Return: none
*/
void wmitlv_free_allocated_command_tlvs(A_UINT32 cmd_event_id,
void **wmi_cmd_struct_ptr)
{
wmitlv_free_allocated_tlvs(1, cmd_event_id, wmi_cmd_struct_ptr);
}
/**
* wmitlv_free_allocated_event_tlvs() - tlv helper function
* @cmd_event_id: command or event id
* @wmi_cmd_struct_ptr: wmi command structure
*
*
* free any allocated buffers for WMI Event/Command TLV processing
*
* Return: none
*/
void wmitlv_free_allocated_event_tlvs(A_UINT32 cmd_event_id,
void **wmi_cmd_struct_ptr)
{
wmitlv_free_allocated_tlvs(0, cmd_event_id, wmi_cmd_struct_ptr);
}
/**
* wmi_versions_are_compatible() - tlv helper function
* @vers1: host wmi version
* @vers2: target wmi version
*
*
* check if two given wmi versions are compatible
*
* Return: none
*/
int
wmi_versions_are_compatible(wmi_abi_version *vers1, wmi_abi_version *vers2)
{
if ((vers1->abi_version_ns_0 != vers2->abi_version_ns_0) ||
(vers1->abi_version_ns_1 != vers2->abi_version_ns_1) ||
(vers1->abi_version_ns_2 != vers2->abi_version_ns_2) ||
(vers1->abi_version_ns_3 != vers2->abi_version_ns_3)) {
/* The namespaces are different. Incompatible. */
return 0;
}
if (vers1->abi_version_0 != vers2->abi_version_0) {
/* The major or minor versions are different. Incompatible */
return 0;
}
/* We ignore the build version */
return 1;
}
/**
* wmi_versions_can_downgrade() - tlv helper function
* @version_whitelist_table: version table
* @my_vers: host version
* @opp_vers: target version
* @out_vers: downgraded version
*
*
* check if target wmi version can be downgraded
*
* Return: 0 if success. Return < 0 if failure.
*/
static int
wmi_versions_can_downgrade(int num_whitelist,
wmi_whitelist_version_info *version_whitelist_table,
wmi_abi_version *my_vers,
wmi_abi_version *opp_vers,
wmi_abi_version *out_vers)
{
A_UINT8 can_try_to_downgrade;
A_UINT32 my_major_vers = WMI_VER_GET_MAJOR(my_vers->abi_version_0);
A_UINT32 my_minor_vers = WMI_VER_GET_MINOR(my_vers->abi_version_0);
A_UINT32 opp_major_vers = WMI_VER_GET_MAJOR(opp_vers->abi_version_0);
A_UINT32 opp_minor_vers = WMI_VER_GET_MINOR(opp_vers->abi_version_0);
A_UINT32 downgraded_minor_vers;
if ((my_vers->abi_version_ns_0 != opp_vers->abi_version_ns_0) ||
(my_vers->abi_version_ns_1 != opp_vers->abi_version_ns_1) ||
(my_vers->abi_version_ns_2 != opp_vers->abi_version_ns_2) ||
(my_vers->abi_version_ns_3 != opp_vers->abi_version_ns_3)) {
/* The namespaces are different. Incompatible. */
can_try_to_downgrade = false;
} else if (my_major_vers != opp_major_vers) {
/* Major version is different. Incompatible and cannot downgrade. */
can_try_to_downgrade = false;
} else {
/* Same major version. */
if (my_minor_vers < opp_minor_vers) {
/* Opposite party is newer. Incompatible and cannot downgrade. */
can_try_to_downgrade = false;
} else if (my_minor_vers > opp_minor_vers) {
/* Opposite party is older. Check whitelist if we can downgrade */
can_try_to_downgrade = true;
} else {
/* Same version */
wmi_tlv_OS_MEMCPY(out_vers, my_vers,
sizeof(wmi_abi_version));
return 1;
}
}
if (!can_try_to_downgrade) {
wmi_tlv_print_error("%s: Warning: incompatible WMI version.\n",
__func__);
wmi_tlv_OS_MEMCPY(out_vers, my_vers, sizeof(wmi_abi_version));
return 0;
}
/* Try to see we can downgrade the supported version */
downgraded_minor_vers = my_minor_vers;
while (downgraded_minor_vers > opp_minor_vers) {
A_UINT8 downgraded = false;
int i;
for (i = 0; i < num_whitelist; i++) {
if (version_whitelist_table[i].major != my_major_vers) {
continue; /* skip */
}
if ((version_whitelist_table[i].namespace_0 !=
my_vers->abi_version_ns_0)
|| (version_whitelist_table[i].namespace_1 !=
my_vers->abi_version_ns_1)
|| (version_whitelist_table[i].namespace_2 !=
my_vers->abi_version_ns_2)
|| (version_whitelist_table[i].namespace_3 !=
my_vers->abi_version_ns_3)) {
continue; /* skip */
}
if (version_whitelist_table[i].minor ==
downgraded_minor_vers) {
/* Found the next version that I can downgrade */
wmi_tlv_print_error
("%s: Note: found a whitelist entry to downgrade. wh. list ver: %d,%d,0x%x 0x%x 0x%x 0x%x\n",
__func__, version_whitelist_table[i].major,
version_whitelist_table[i].minor,
version_whitelist_table[i].namespace_0,
version_whitelist_table[i].namespace_1,
version_whitelist_table[i].namespace_2,
version_whitelist_table[i].namespace_3);
downgraded_minor_vers--;
downgraded = true;
break;
}
}
if (!downgraded) {
break; /* Done since we did not find any whitelist to downgrade version */
}
}
wmi_tlv_OS_MEMCPY(out_vers, my_vers, sizeof(wmi_abi_version));
out_vers->abi_version_0 =
WMI_VER_GET_VERSION_0(my_major_vers, downgraded_minor_vers);
if (downgraded_minor_vers != opp_minor_vers) {
wmi_tlv_print_error
("%s: Warning: incompatible WMI version and cannot downgrade.\n",
__func__);
return 0; /* Incompatible */
} else {
return 1; /* Compatible */
}
}
/**
* wmi_cmp_and_set_abi_version() - tlv helper function
* @version_whitelist_table: version table
* @my_vers: host version
* @opp_vers: target version
* @out_vers: downgraded version
*
* This routine will compare and set the WMI ABI version.
* First, compare my version with the opposite side's version.
* If incompatible, then check the whitelist to see if our side can downgrade.
* Finally, fill in the final ABI version into the output, out_vers.
* Return 0 if the output version is compatible
* Else return 1 if the output version is incompatible
*
* Return: 0 if the output version is compatible else < 0.
*/
int
wmi_cmp_and_set_abi_version(int num_whitelist,
wmi_whitelist_version_info *
version_whitelist_table,
struct _wmi_abi_version *my_vers,
struct _wmi_abi_version *opp_vers,
struct _wmi_abi_version *out_vers)
{
wmi_tlv_print_verbose
("%s: Our WMI Version: Mj=%d, Mn=%d, bd=%d, ns0=0x%x ns1:0x%x ns2:0x%x ns3:0x%x\n",
__func__, WMI_VER_GET_MAJOR(my_vers->abi_version_0),
WMI_VER_GET_MINOR(my_vers->abi_version_0), my_vers->abi_version_1,
my_vers->abi_version_ns_0, my_vers->abi_version_ns_1,
my_vers->abi_version_ns_2, my_vers->abi_version_ns_3);
wmi_tlv_print_verbose
("%s: Opposite side WMI Version: Mj=%d, Mn=%d, bd=%d, ns0=0x%x ns1:0x%x ns2:0x%x ns3:0x%x\n",
__func__, WMI_VER_GET_MAJOR(opp_vers->abi_version_0),
WMI_VER_GET_MINOR(opp_vers->abi_version_0),
opp_vers->abi_version_1, opp_vers->abi_version_ns_0,
opp_vers->abi_version_ns_1, opp_vers->abi_version_ns_2,
opp_vers->abi_version_ns_3);
/* By default, the output version is our version. */
wmi_tlv_OS_MEMCPY(out_vers, my_vers, sizeof(wmi_abi_version));
if (!wmi_versions_are_compatible(my_vers, opp_vers)) {
/* Our host version and the given firmware version are incompatible. */
if (wmi_versions_can_downgrade
(num_whitelist, version_whitelist_table, my_vers, opp_vers,
out_vers)) {
/* We can downgrade our host versions to match firmware. */
wmi_tlv_print_error
("%s: Host downgraded WMI Versions to match fw. Ret version: Mj=%d, Mn=%d, bd=%d, ns0=0x%x ns1:0x%x ns2:0x%x ns3:0x%x\n",
__func__,
WMI_VER_GET_MAJOR(out_vers->abi_version_0),
WMI_VER_GET_MINOR(out_vers->abi_version_0),
out_vers->abi_version_1,
out_vers->abi_version_ns_0,
out_vers->abi_version_ns_1,
out_vers->abi_version_ns_2,
out_vers->abi_version_ns_3);
return 0; /* Compatible */
} else {
/* Warn: We cannot downgrade our host versions to match firmware. */
wmi_tlv_print_error
("%s: WARN: Host WMI Versions mismatch with fw. Ret version: Mj=%d, Mn=%d, bd=%d, ns0=0x%x ns1:0x%x ns2:0x%x ns3:0x%x\n",
__func__,
WMI_VER_GET_MAJOR(out_vers->abi_version_0),
WMI_VER_GET_MINOR(out_vers->abi_version_0),
out_vers->abi_version_1,
out_vers->abi_version_ns_0,
out_vers->abi_version_ns_1,
out_vers->abi_version_ns_2,
out_vers->abi_version_ns_3);
return 1; /* Incompatible */
}
} else {
/* We are compatible. Our host version is the output version */
wmi_tlv_print_verbose
("%s: Host and FW Compatible WMI Versions. Ret version: Mj=%d, Mn=%d, bd=%d, ns0=0x%x ns1:0x%x ns2:0x%x ns3:0x%x\n",
__func__, WMI_VER_GET_MAJOR(out_vers->abi_version_0),
WMI_VER_GET_MINOR(out_vers->abi_version_0),
out_vers->abi_version_1, out_vers->abi_version_ns_0,
out_vers->abi_version_ns_1, out_vers->abi_version_ns_2,
out_vers->abi_version_ns_3);
return 0; /* Compatible */
}
}