/******************************************************************************
 *
 * Copyright(c) 2019 - 2021 Realtek Corporation.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 *****************************************************************************/
#define _PHL_IE_C_
#include "phl_headers.h"

u8 _phl_build_ml_common_info(struct rtw_phl_com_t *phl_com,
                             struct rtw_phl_ml_ie_info *info,
                             u8 *pbuf,
                             u8 *ml_ctrl)
{
	struct rtw_phl_stainfo_t *sta = NULL;
	void *phl = phl_com->phl_priv;
	void *drv = phl_com->drv_priv;
	u8 len = 0;
	u8 *pstart = pbuf;
	u8 *p = pbuf + 1; /* skip Common Info Length subfield */

	if (info->rlink == NULL) {
		PHL_WARN("%s: wrong rlink assignment!\n", __func__);
		goto _exit;
	}

	sta = rtw_phl_get_stainfo_self(phl, info->rlink);

	/* subfields */
	switch (GET_ML_ELE_ML_CTRL_TYPE(ml_ctrl)) {
	case BASIC_ML:
		/* MLD Mac Address subfield */
		_os_mem_cpy(drv, p, info->rlink->wrole->mac_addr, ETH_ALEN);
		p += ETH_ALEN;

		if (GET_ML_ELE_ML_CTRL_LINK_ID_INFO_PRESENT(ml_ctrl)) {
			SET_ML_ELE_COMMON_INFO_LINK_ID(p, sta->link_id);
			p += 1;
		}
		if (GET_ML_ELE_ML_CTRL_BSS_PARAMS_CHG_CNT_PRESENT(ml_ctrl)) {
			u8 val = info->rlink->bss_params_chg_cnt;

			SET_ML_ELE_COMMON_INFO_BSS_PARAMS_CHG_CNT(p, val);
			p += 1;
		}
		if (GET_ML_ELE_ML_CTRL_MEDIUM_SYNC_DELAY_INFO_PRESENT(ml_ctrl)) {
			p += 2;
		}
		if (GET_ML_ELE_ML_CTRL_EML_CAP_PRESENT(ml_ctrl)) {
			p += 3;
		}
		if (GET_ML_ELE_ML_CTRL_MLD_CAP_PRESENT(ml_ctrl)) {
			p += 2;
		}
		break;
	case PROBE_REQUEST_ML:
		if (GET_ML_ELE_ML_CTRL_MLD_ID_PRESENT(ml_ctrl)) {
			SET_ML_ELE_COMMON_INFO_MLD_ID(p, info->mld_id);
			p += 1;
		}
		break;
	default:
		break;
	}

_exit:
	len = (u8)(p - pstart);

	*pstart = len; /* Common Info Length */

	return len;
}

u8 _phl_build_basic_ml_ie(struct rtw_phl_com_t *phl_com,
                          struct rtw_phl_ml_ie_info *info,
                          u8 *pbuf,
                          u8 *ml_ctrl)
{
	void *drv = phl_com->drv_priv;
	u8 len = 0;
	u8 *pstart = pbuf;
	u8 *p = pbuf;

	/* Element ID Extension field */
	*p++ = EID_EXT_MULTI_LINK;

	/* ML Control field */
	_os_mem_cpy(drv, p, ml_ctrl, 2);
	p += 2;

	/* ML Common Info field */
	p += _phl_build_ml_common_info(phl_com, info, p, ml_ctrl);

	/* optional subelements */
	if (info->opt_len) {
		_os_mem_cpy(drv, p, info->opt, info->opt_len);
		p += info->opt_len;
	}

	len = (u8)(p - pstart);

	return len;
}

u8 _phl_build_probe_request_ml_ie(struct rtw_phl_com_t *phl_com,
                                  struct rtw_phl_ml_ie_info *info,
                                  u8 *pbuf,
                                  u8 *ml_ctrl)
{
	void *drv = phl_com->drv_priv;
	u8 len = 0;
	u8 *pstart = pbuf;
	u8 *p = pbuf;

	/* Element ID Extension field */
	*p++ = EID_EXT_MULTI_LINK;

	/* ML Control field */
	_os_mem_cpy(drv, p, ml_ctrl, 2);
	p += 2;

	/* ML Common Info field */
	p += _phl_build_ml_common_info(phl_com, info, p, ml_ctrl);

	/* optional subelements */
	if (info->opt_len) {
		_os_mem_cpy(drv, p, info->opt, info->opt_len);
		p += info->opt_len;
	}

	len = (u8)(p - pstart);

	return len;
}

u8 rtw_phl_build_ml_ie(struct rtw_phl_com_t *phl_com,
                       struct rtw_phl_ml_ie_info *info,
                       u8 *pbuf)
{
	u8 len = 0;
	u8 *pstart = pbuf;
	u8 *p = pbuf + 2;
	u8 ml_ctrl[2] = {0};

	/* set ML Control field in advance */
	switch (info->pkt_type) {
	case PACKET_BEACON:
		if (info->critical_update)
			info->rlink->bss_params_chg_cnt++;
		fallthrough;
	case PACKET_PROBE_RESPONSE:
	case PACKET_PROBE_RESPONSE_ML:
		SET_ML_ELE_ML_CTRL_TYPE(ml_ctrl, BASIC_ML);
		SET_ML_ELE_ML_CTRL_LINK_ID_INFO_PRESENT(ml_ctrl, true);
		SET_ML_ELE_ML_CTRL_BSS_PARAMS_CHG_CNT_PRESENT(ml_ctrl, true);
		len = _phl_build_basic_ml_ie(phl_com, info, p, ml_ctrl);
		break;
	case PACKET_PROBE_REQUEST_ML:
		SET_ML_ELE_ML_CTRL_TYPE(ml_ctrl, PROBE_REQUEST_ML);
		if (info->mld_id_present)
			SET_ML_ELE_ML_CTRL_MLD_ID_PRESENT(ml_ctrl, true);

		len = _phl_build_probe_request_ml_ie(phl_com,
		                                     info,
		                                     p,
		                                     ml_ctrl);
		break;
	case PACKET_AUTH:
		SET_ML_ELE_ML_CTRL_TYPE(ml_ctrl, BASIC_ML);
		/* LINK_ID_INFO, BSS_PARAMS_CHG_CNT, MEDIUM_SYNC_DELAY_INFO are false */
		len = _phl_build_basic_ml_ie(phl_com, info, p, ml_ctrl);
		break;
	case PACKET_ASSOC_REQUEST:
		SET_ML_ELE_ML_CTRL_TYPE(ml_ctrl, BASIC_ML);
		/* LINK_ID_INFO, BSS_PARAMS_CHG_CNT, MEDIUM_SYNC_DELAY_INFO are false */
		SET_ML_ELE_ML_CTRL_EML_CAP_PRESENT(ml_ctrl, true);
		SET_ML_ELE_ML_CTRL_MLD_CAP_PRESENT(ml_ctrl, true);
		len = _phl_build_basic_ml_ie(phl_com, info, p, ml_ctrl);
		break;
	case PACKET_ASSOC_RESPONSE:
		SET_ML_ELE_ML_CTRL_TYPE(ml_ctrl, BASIC_ML);
		SET_ML_ELE_ML_CTRL_LINK_ID_INFO_PRESENT(ml_ctrl, true);
		SET_ML_ELE_ML_CTRL_BSS_PARAMS_CHG_CNT_PRESENT(ml_ctrl, true);
		SET_ML_ELE_ML_CTRL_EML_CAP_PRESENT(ml_ctrl, true);
		SET_ML_ELE_ML_CTRL_MLD_CAP_PRESENT(ml_ctrl, true);
		len = _phl_build_basic_ml_ie(phl_com, info, p, ml_ctrl);
		break;
	default:
		break;
	}

	*pstart = EID_EXTENSION;
	*(pstart + 1) = len;
	len += 2;

	return len;
}

u8 _phl_build_sta_info(struct rtw_phl_com_t *phl_com,
                       struct rtw_phl_per_sta_profile_info *info,
                       u8 *pbuf,
                       u8 *sta_ctrl)
{
	void *drv = phl_com->drv_priv;
	u8 len = 0;
	u8 *pstart = pbuf;
	u8 *p = pbuf + 1; /* skip STA Info Length subfield */

	/* subfields */
	if (GET_STA_CTRL_MAC_ADDR_PRESENT(sta_ctrl)) {
		_os_mem_cpy(drv, p, info->rlink->mac_addr, ETH_ALEN);
		p += ETH_ALEN;
	}
	if (GET_STA_CTRL_BEACON_INTEREVAL_PRESENT(sta_ctrl)) {
#ifdef RTW_PHL_BCN
		u16 bcn_interval = (u16)info->rlink->bcn_cmn.bcn_interval;
#else
		u16 bcn_interval = 100;
#endif

		_os_mem_cpy(drv, p, &(bcn_interval), 2);
		p += 2;
	}
	if (GET_STA_CTRL_DTIM_INFO_PRESENT(sta_ctrl)) {
		u16 dtim_info = 0;
		u8 dtim_cnt = 0;
		u8 dtim_period = 0;

		/* DTIM cnt should be refine later if DTIM period is not 1 */
		dtim_period = (u8)info->rlink->dtim_period;
		dtim_info = (dtim_period << 8) | dtim_cnt;
		_os_mem_cpy(drv, p, &(dtim_info), 2);
		p += 2;
	}
	if (GET_STA_CTRL_NSTR_LINK_PAIR_PRESENT(sta_ctrl)) {
		if (GET_STA_CTRL_NSTR_BITMAP_SIZE(sta_ctrl) == 0) {
			/* TODO */
			p += 1;
		}
		else if (GET_STA_CTRL_NSTR_BITMAP_SIZE(sta_ctrl) == 1) {
			/* TODO */
			p += 2;
		}
	}

	len = (u8)(p - pstart);

	*pstart = len; /* STA Info Length */

	return len;
}

u8 rtw_phl_build_per_sta_profile(struct rtw_phl_com_t *phl_com,
                                 struct rtw_phl_per_sta_profile_info *info,
                                 u8 *pbuf)
{
	struct rtw_phl_stainfo_t *sta = NULL;
	void *phl = phl_com->phl_priv;
	void *drv = phl_com->drv_priv;
	u8 len = 0, total_len = 0;
	u8 *pstart = pbuf;
	u8 *p = pbuf + 2;
	u8 sta_ctrl[2] = {0};

	if (info->rlink == NULL) {
		PHL_WARN("%s: wrong rlink assignment!\n", __func__);
		return total_len;
	}

	sta = rtw_phl_get_stainfo_self(phl, info->rlink);

	/* STA Control field */
	switch (info->pkt_type) {
	case PACKET_BEACON:
	case PACKET_PROBE_RESPONSE:
		SET_STA_CTRL_LINK_ID(sta_ctrl, sta->link_id);
		break;
	case PACKET_PROBE_RESPONSE_ML:
		SET_STA_CTRL_LINK_ID(sta_ctrl, sta->link_id);
		if (info->complete_profile) {
			SET_STA_CTRL_COMPLETE_PROFILE(sta_ctrl, true);
			SET_STA_CTRL_MAC_ADDR_PRESENT(sta_ctrl, true);
			SET_STA_CTRL_BEACON_INTEREVAL_PRESENT(sta_ctrl, true);
			SET_STA_CTRL_DTIM_INFO_PRESENT(sta_ctrl, true);
		}
		break;
	case PACKET_ASSOC_RESPONSE:
		SET_STA_CTRL_LINK_ID(sta_ctrl, sta->link_id);
		SET_STA_CTRL_COMPLETE_PROFILE(sta_ctrl, true);
		SET_STA_CTRL_MAC_ADDR_PRESENT(sta_ctrl, true);
		SET_STA_CTRL_BEACON_INTEREVAL_PRESENT(sta_ctrl, true);
		SET_STA_CTRL_DTIM_INFO_PRESENT(sta_ctrl, true);
		break;
	case PACKET_PROBE_REQUEST_ML:
		SET_STA_CTRL_LINK_ID(sta_ctrl, info->link_id);
		if (info->complete_profile) {
			SET_STA_CTRL_COMPLETE_PROFILE(sta_ctrl, true);
		}
		break;
	case PACKET_ASSOC_REQUEST:
		SET_STA_CTRL_LINK_ID(sta_ctrl, info->link_id);
		SET_STA_CTRL_COMPLETE_PROFILE(sta_ctrl, true);
		SET_STA_CTRL_MAC_ADDR_PRESENT(sta_ctrl, true);
		break;
	default:
		break;
	}

	/* STA Control field */
	_os_mem_cpy(drv, p, sta_ctrl, 2);
	p += 2;
	total_len += 2;

	if (info->pkt_type != PACKET_PROBE_REQUEST_ML) {
		/* STA Info field */
		len = _phl_build_sta_info(phl_com, info, p, sta_ctrl);
		p += len;
		total_len += len;
	}

	/* STA Profile field */
	if (info->sta_profile_len) {
		_os_mem_cpy(drv, p, info->sta_profile, info->sta_profile_len);
		total_len += info->sta_profile_len;
	}

	*pstart = WLAN_SUBEID_PER_STA_PROFILE;
	*(pstart + 1) = total_len;
	total_len += 2;

	return total_len;
}

void _set_link_mapping(u8 *ele_pos,
                       u16 *tid2link,
                       u8 *indicator,
                       u8 *total_len)
{
	u8 idx = 0;
	u8 *ele_linkmap = ele_pos;

	*indicator = 0;
	for(idx = 0; idx < WMM_AC_TID_NUM; idx++) {
		if(tid2link[idx]) {
			SET_LINK_MAPPING_TID(ele_linkmap, tid2link[idx]);
			ele_linkmap +=2;
			*total_len += 2;
			*indicator |= BIT(idx);
		}
	}
}

/*
 * (Re)Association Request/Response
 * Tid-To-Link Mapping Request/Response action frame
*/
u8 rtw_phl_build_tid2link(struct rtw_wifi_role_link_t *rlink,
                          u8 *ele_start)
{
	struct protocol_cap_t *protocol_cap = &rlink->protocol_cap;
	u8 *ele_pos = NULL, *ele_linkmap = 0;
	u8  def_mapping = true, indicator = 0, idx = 0;
	u8 total_len = 0;

	/* Element ID */
	ele_start[0] = EID_EXTENSION;

	/* Element ID Extension field */
	ele_start[2] = EID_EXT_TID_TO_LINK_MAPPING;

	for(idx = 0; idx < WMM_AC_TID_NUM; idx++) {
		if(protocol_cap->tid2link_ul[idx] != 0x7fff ||
		   protocol_cap->tid2link_dl[idx] != 0x7fff ) {
			def_mapping = false;
			break;
		}
	}

	ele_pos = ele_start+3; /* start of tid2link control */
	if(def_mapping) {
		SET_TID2LINK_CTRL_DIRECT(ele_pos, 2);
		SET_TID2LINK_CTRL_DEFAULT(ele_pos, 1);
		SET_TID2LINK_CTRL_INDIC(ele_pos, indicator); /* indicator is 0 */
		total_len += 2;
	}
	else {
		/* 0: uplink */
		SET_TID2LINK_CTRL_DIRECT(ele_pos, 0);
		SET_TID2LINK_CTRL_DEFAULT(ele_pos, 0);
		total_len += 2;
		ele_linkmap = ele_pos+2;
		_set_link_mapping(ele_linkmap, protocol_cap->tid2link_ul, &indicator, &total_len);
		SET_TID2LINK_CTRL_INDIC(ele_pos, indicator);
		ele_pos = ele_pos + total_len;

		/* 1: downlink */
		SET_TID2LINK_CTRL_DIRECT(ele_pos, 1);
		SET_TID2LINK_CTRL_DEFAULT(ele_pos, 0);
		total_len += 2;
		ele_linkmap = ele_pos+2;
		_set_link_mapping(ele_linkmap, protocol_cap->tid2link_dl, &indicator, &total_len);
		SET_TID2LINK_CTRL_INDIC(ele_pos, indicator);
	}

	ele_start[1]= total_len + 1; /* Ext ID field is included */

	return (total_len +3); /* From Element ID/ Len/ Ext ID */
}

void
_dump_tid2link(struct rtw_phl_stainfo_t *sta)
{
	u8 idx =0;
	PHL_INFO("###### _dump_tid2link #######\n");
	for (idx = 0; idx < WMM_AC_TID_NUM; idx++) {
		PHL_INFO("\t[TID-%d] uplink mapp:0x%02X, downlink map:%02X\n",
		         idx, sta->asoc_cap.tid2link_ul[idx],
		         sta->asoc_cap.tid2link_dl[idx]);

		if(!sta->asoc_cap.tid2link_ul[idx] && !sta->asoc_cap.tid2link_dl[idx])
			PHL_ERR("\t[TID-%d]:: a TID shall be mapped to at least one setup link\n",
			         idx);
	}
}

void rtw_phl_parse_tid2link(struct rtw_phl_stainfo_t *sta,
                            u8 *ele_start,
                            u16 ele_len)
{
	u8 *ele_pos = NULL;
	u8 direction = 0, def_mapping = 0, indicator = 0, idx = 0;
	bool downlink = true, uplink = true;

	ele_pos = ele_start;
	do {
		direction = GET_TID2LINK_CTRL_DIRECT(ele_pos);
		downlink = (direction == 0)? false: true; /* 0: uplink */
		uplink  = (direction == 1)? false: true;  /* 1: downlink */
		def_mapping = GET_TID2LINK_CTRL_DEFAULT(ele_pos);
		if (!def_mapping) {

			indicator = GET_TID2LINK_CTRL_INDIC(ele_pos);
			ele_pos += 2; /* skip mapping control */
			for(idx = 0; idx < WMM_AC_TID_NUM; idx++) {
				if(BIT(idx) & indicator) {
					if(uplink)
						sta->asoc_cap.tid2link_ul[idx] =
							GET_LINK_MAPPING_TID(ele_pos);
					if(downlink)
						sta->asoc_cap.tid2link_dl[idx] =
							GET_LINK_MAPPING_TID(ele_pos);
					ele_pos += 2;
				}
			}
		}
		else {
			ele_pos += 2; /* skip mapping control */

			/* 35.3.6.1.2 Default mapping mode */
			for(idx = 0; idx < WMM_AC_TID_NUM; idx++) {
				/* Link id 15 : if the reported AP is not part of an AP MLD. */
				sta->asoc_cap.tid2link_ul[idx] = 0x7fff;
				sta->asoc_cap.tid2link_dl[idx] = 0x7fff;
			}
		}
	} while(ele_pos < (ele_start + ele_len));

	_dump_tid2link(sta);

	phl_mld_link2tid(sta);
}

void rtw_phl_tid2link_not_present(struct rtw_phl_stainfo_t *sta, u8 nego)
{
	struct protocol_cap_t *protocol_cap = &sta->rlink->protocol_cap;
	u8 idx = 0;
	u16 link_ul = 0, link_dl = 0;

	for(idx = 0; idx < WMM_AC_TID_NUM; idx++) {
		link_ul = 0x7fff;
		link_dl = 0x7fff;

		/* AP accept our tid2link negotiation */
		if(nego) {
			link_ul = protocol_cap->tid2link_ul[idx];
			link_dl = protocol_cap->tid2link_dl[idx];
		}

		sta->asoc_cap.tid2link_ul[idx] = link_ul;
		sta->asoc_cap.tid2link_dl[idx] = link_dl;
	}

	phl_mld_link2tid(sta);
}

void
_dump_per_sta_profile(struct rtw_phl_per_sta_profile_element ele)
{
	PHL_INFO("###### _dump_per_sta_profile #######\n");
	PHL_INFO("%-25s: %d\n", "Link ID", ele.link_id);
	PHL_INFO("%-25s: %s\n", "Complete Profile", (ele.complete_profile == true)?"Yes":"No");
	if (ele.mac_addr_present)
		PHL_INFO("%-25s: %2x:%2x:%2x:%2x:%2x:%2x\n",
		         "MAC address",
		         ele.mac_addr[0],
		         ele.mac_addr[1],
		         ele.mac_addr[2],
		         ele.mac_addr[3],
		         ele.mac_addr[4],
		         ele.mac_addr[5]
		         );
	else
		PHL_INFO("%-25s: Not present\n", "MAC address");

	if (ele.bcn_interval_present)
		PHL_INFO("%-25s: %d\n", "Beacon Interval", ele.bcn_interval);
	else
		PHL_INFO("%-25s: Not present\n", "Beacon Interval");

	if (ele.dtim_info_present) {
		PHL_INFO("%-25s: %d\n", "DTIM Count", ele.dtim_cnt);
		PHL_INFO("%-25s: %d\n", "DTIM Period", ele.dtim_period);
	} else {
		PHL_INFO("%-25s: Not present\n", "DTIM Info");
	}

	if (ele.nstr_link_pair_present) {
		PHL_INFO("%-25s: %d\n", "NSTR Bitmap Size", ele.nstr_bitmap_size);
		PHL_INFO("%-25s: 0x%X\n", "NSTR Indication Bitmap", ele.nstr_indication_bitmap);
	} else {
		PHL_INFO("%-25s: Not present\n", "NSTR Link Pair");
	}

	if (ele.sta_profile_len != 0) {
		PHL_INFO("%-25s: %d\n", "STA Profile Length", ele.sta_profile_len);
		PHL_INFO("%-25s: %s\n", "STA Profile Fragment", (ele.sta_profile_frag_len == 0)?"No":"Yes");
	}
}

/*
 *
 * +--------+--------+----------+----------+-------------+
 * | Subele | Length | STA Ctrl | STA Info | STA Profile |
 * | ID     |        |          |          |             |
 * +--------+--------+----------+----------+-------------+
 *                           |
 *                           |
 *            ele_len        v
 *              ^
 *              |      len_before_frag   1     1      len_before_frag - ele_len
 * +--------+---+----+-----------------+-----+------+-----------------+-------------+
 * | Subele | Length | Data 0          | FID | FLEN | DATA 1                        |
 * | ID     |        |                 |     |      |                               |
 * +--------+--------------------------+-----+------+-------------------------------+
 *                   |
 *                   v
 *                   ele_pos
 */
void
phl_parse_per_sta_profile_ie(struct rtw_phl_com_t *phl_com,
                             u8 *ele_pos,
                             u16 ele_len,
                             u8 *ele_frag,
                             u16 len_before_frag,
                             struct rtw_phl_per_sta_profile_element *ele
)
{
	void *d = phlcom_to_drvpriv(phl_com);
	u8 tmp[STA_CTRL_LEN + MAX_STA_INFO_LEN] = {0};
	u8 *sta_ctrl = tmp;
	u8 *sta_info = tmp+2;
	u8 sta_info_len = 0;
	u8 sta_info_offset = 0;

	/* Copy STA Ctrl and STA Info field to tmp for parsing */
	if (len_before_frag == 0) {
		/* Per-STA profile without fragment */
		sta_info_len = ele_pos[STA_CTRL_LEN];
		_os_mem_cpy(d, tmp, ele_pos, (STA_CTRL_LEN + sta_info_len));
	} else {
		/* Per-STA profile with fragment */
		if (len_before_frag <= STA_CTRL_LEN)
			sta_info_len = ele_frag[STA_CTRL_LEN - len_before_frag];
		else
			sta_info_len = ele_pos[STA_CTRL_LEN];

		if (len_before_frag < (STA_CTRL_LEN + sta_info_len)) {
			_os_mem_cpy(d, tmp, ele_pos, len_before_frag);
			_os_mem_cpy(d,
			            (tmp + len_before_frag),
			            ele_frag,
			            (STA_CTRL_LEN + sta_info_len - len_before_frag));
		} else {
			_os_mem_cpy(d, tmp, ele_pos, (STA_CTRL_LEN + sta_info_len));
		}
	}

	sta_info_offset++;

	ele->link_id = (u8)GET_STA_CTRL_LINK_ID(sta_ctrl);

	if (GET_STA_CTRL_COMPLETE_PROFILE(sta_ctrl)) {
		ele->complete_profile = true;
	}

	if (GET_STA_CTRL_MAC_ADDR_PRESENT(sta_ctrl)) {
		ele->mac_addr_present = true;
		_os_mem_cpy(d, ele->mac_addr, (sta_info+sta_info_offset), MAC_ALEN);
		sta_info_offset += 6;
	}

	if (GET_STA_CTRL_BEACON_INTEREVAL_PRESENT(sta_ctrl)) {
		ele->bcn_interval_present = true;
		ele->bcn_interval = LE_BITS_TO_2BYTE(sta_info+sta_info_offset, 0, 16);
		sta_info_offset += 2;
	}

	if (GET_STA_CTRL_DTIM_INFO_PRESENT(sta_ctrl)) {
		ele->dtim_info_present = true;
		ele->dtim_cnt = LE_BITS_TO_1BYTE(sta_info+sta_info_offset, 0, 8);
		sta_info_offset += 1;
		ele->dtim_period = LE_BITS_TO_1BYTE(sta_info+sta_info_offset, 0, 8);
		sta_info_offset += 1;
	}

	if (GET_STA_CTRL_NSTR_LINK_PAIR_PRESENT(sta_ctrl)) {
		ele->nstr_link_pair_present = true;
		if (GET_STA_CTRL_NSTR_BITMAP_SIZE(sta_ctrl)) {
			ele->nstr_indication_bitmap = LE_BITS_TO_2BYTE(sta_info+sta_info_offset, 0, 16);
			ele->nstr_bitmap_size = 2;
			sta_info_offset += 2;
		} else {
			ele->nstr_indication_bitmap = LE_BITS_TO_1BYTE(sta_info+sta_info_offset, 0, 8);
			ele->nstr_bitmap_size = 1;
			sta_info_offset += 1;
		}
	}

	if (len_before_frag == 0) {
		/* Per-STA profile without fragment */
		ele->sta_profile = ele_pos + STA_CTRL_LEN + sta_info_len;
	} else if (len_before_frag <= (STA_CTRL_LEN + sta_info_len)) {
		/* STA profile located in fragment part */
		ele->sta_profile = ele_frag + (STA_CTRL_LEN + sta_info_len - len_before_frag + 2);
	} else {
		/* STA profile is truncated by fragment */
		ele->sta_profile = ele_pos + STA_CTRL_LEN + sta_info_len;
		ele->sta_profile_frag = ele_frag + 2;
		ele->sta_profile_frag_len = (u8)(len_before_frag - (STA_CTRL_LEN + sta_info_len));
	}
	ele->sta_profile_len = (u8)(ele_len - STA_CTRL_LEN - sta_info_len);

	_dump_per_sta_profile(*ele);
}

void
_parse_ml_link_info(struct rtw_phl_com_t *phl_com,
                    u8 *ie_buf,
                    u16 ie_len,
                    u16 link_info_offset,
                    struct rtw_phl_ml_element *ml_ele
)
{
	u16 offset = link_info_offset;
	u8 sub_eid = 0;
	u8 *sub_ele = NULL, *sub_ele_frag = NULL;
	u16 sub_ele_len = 0, sub_ele_len_before_frag = 0;
	u16 len_before_frag = 0;
	/* Exclude the Element ID extension field */
	u16 next_frag_offset = MAX_ELE_LEN - 1;

	do {
		if (offset >= (ie_len-1))
			break;

		sub_ele_frag = NULL;
		sub_ele_len_before_frag = 0;
		len_before_frag = 0;

		/* Check fragment */
		len_before_frag = next_frag_offset - offset;
		if (len_before_frag == 1) {
			sub_eid = ie_buf[offset];
			/* Skip FID and FLEN */
			sub_ele_len = ie_buf[offset+3];
			sub_ele = ie_buf + offset + 4;
		} else if (len_before_frag == 2){
			sub_eid = ie_buf[offset];
			sub_ele_len = ie_buf[offset+1];
			/* Skip FID and FLEN */
			sub_ele = ie_buf + offset + 4;
		} else {
			sub_eid = ie_buf[offset];
			sub_ele_len = ie_buf[offset+1];
			sub_ele = ie_buf + offset + 2;
			if (len_before_frag < (sub_ele_len + 2)) {
				sub_ele_frag = ie_buf + offset + len_before_frag + 2;
				sub_ele_len_before_frag = len_before_frag - 2;
			}
		}
		/*
		 * Next fragment offset would be
		 * 255: Max element length
		 * 1: Fragment ID field
		 * 1: Fragment Length field
		 */
		if (len_before_frag <= (sub_ele_len + 2))
			next_frag_offset += (MAX_ELE_LEN + 2);

		if (sub_eid == WLAN_SUBEID_PER_STA_PROFILE) {
			phl_parse_per_sta_profile_ie(phl_com,
			                             sub_ele,
			                             sub_ele_len,
			                             sub_ele_frag,
			                             sub_ele_len_before_frag,
			                             &(ml_ele->profile[ml_ele->profile_num]));
			ml_ele->profile_num++;
		}

		/* Move offset to the end of the element */
		if (len_before_frag <= (sub_ele_len + 2)) {
			/*
			 * Subele ID field (1) +
			 * Subele len field (1) +
			 * Fragment ID field (1) +
			 * Fragment len field (1) +
			 * Subele length
			 */
			offset += (sub_ele_len + 2 + 2);
		} else {
			/*
			 * Subele ID field (1) +
			 * Subele len field (1) +
			 * Subele length
			 */
			offset += (sub_ele_len + 2);
		}
	} while(1);
}

void
_dump_ml_basic(struct rtw_phl_ml_element ml_ele)
{
	PHL_INFO("###### _dump_ml_basic #######\n");
	if (ml_ele.common_info.basic_ml.link_id_info_present)
		PHL_INFO("%-25s: %d\n", "Link ID", ml_ele.common_info.basic_ml.link_id);
	else
		PHL_INFO("%-25s: Not present\n", "Link ID");
	if (ml_ele.common_info.basic_ml.bss_param_chg_cnt_present)
		PHL_INFO("%-25s: %d\n", "BSS Param Chg Cnt", ml_ele.common_info.basic_ml.bss_param_chg_cnt);
	else
		PHL_INFO("%-25s: Not present\n", "BSS Param Chg Cnt");
	if (ml_ele.common_info.basic_ml.msd_info_present)
		PHL_INFO("%-25s: Present\n", "MSD Info");
	else
		PHL_INFO("%-25s: Not present\n", "MSD Info");
	if (ml_ele.common_info.basic_ml.eml_cap_present)
		PHL_INFO("%-25s: Present\n", "EML Capability");
	else
		PHL_INFO("%-25s: Not present\n", "EML Capability");
	if (ml_ele.common_info.basic_ml.mld_cap_present) {
		PHL_INFO("%-25s =>\n", "MLD Capability");
		PHL_INFO("%-25s: %d\n", "Max Num of SL",
		         ml_ele.common_info.basic_ml.mld_cap.max_num_sl);
		PHL_INFO("%-25s: %s\n", "SRS Support",
		         (ml_ele.common_info.basic_ml.mld_cap.srs_support == true)?"True":"False");
		if (ml_ele.common_info.basic_ml.mld_cap.tid_to_link_nego_support == 0)
			PHL_INFO("%-25s: %s\n",
			         "TID-To-Link Mapping Nego",
			         "Not support");
		else if (ml_ele.common_info.basic_ml.mld_cap.tid_to_link_nego_support == 1)
			PHL_INFO("%-25s: %s\n",
			         "TID-To-Link Mapping Nego",
			         "Same or different link set");
		else
			PHL_INFO("%-25s: %s\n",
			         "TID-To-Link Mapping Nego",
			         "Same link set only");
		if (ml_ele.common_info.basic_ml.mld_cap.freq_sep_for_str)
			PHL_INFO("%-25s: %dMHz\n", "Freq Separation for STR",
			         ((ml_ele.common_info.basic_ml.mld_cap.freq_sep_for_str - 1) * 80));
		PHL_INFO("%-25s: %s\n", "AAR Support",
		         (ml_ele.common_info.basic_ml.mld_cap.aar_support == true)?"True":"False");
	} else {
		PHL_INFO("%-25s: Not present\n", "MLD Capability");
	}
}

void
_parse_ml_basic(struct rtw_phl_com_t *phl_com,
                u8 *ie_buf,
                u16 ie_len,
                struct rtw_phl_ml_element *ml_ele
)
{
	struct basic_ml *basic_ml = &(ml_ele->common_info.basic_ml);
	u8 *ml_ctrl = ie_buf;
	u8 *common_info = ie_buf+2;
	u8 common_info_len = 0;
	u8 common_info_offset = 0;

	common_info_len = common_info[0];
	common_info_offset++;

	basic_ml->mld_address = common_info+1;
	common_info_offset += 6;

	if (GET_ML_ELE_ML_CTRL_LINK_ID_INFO_PRESENT(ml_ctrl)) {
		basic_ml->link_id_info_present = true;
		basic_ml->link_id = LE_BITS_TO_1BYTE(common_info+common_info_offset, 0, 4);
		common_info_offset += 1;
	}

	if (GET_ML_ELE_ML_CTRL_BSS_PARAMS_CHG_CNT_PRESENT(ml_ctrl)) {
		basic_ml->bss_param_chg_cnt_present = true;
		basic_ml->bss_param_chg_cnt = LE_BITS_TO_1BYTE(common_info+common_info_offset, 0, 8);
		common_info_offset += 1;
	}

	if (GET_ML_ELE_ML_CTRL_MEDIUM_SYNC_DELAY_INFO_PRESENT(ml_ctrl)) {
		basic_ml->msd_info_present = true;
		/* TODO: Parse MSD INFO */
		common_info_offset += 2;
	}

	if (GET_ML_ELE_ML_CTRL_EML_CAP_PRESENT(ml_ctrl)) {
		basic_ml->eml_cap_present = true;
		/* TODO: Parse EML cap */
		common_info_offset += 3;
	}

	if (GET_ML_ELE_ML_CTRL_MLD_CAP_PRESENT(ml_ctrl)) {
		basic_ml->mld_cap_present = true;
		basic_ml->mld_cap.max_num_sl = GET_MLD_CAP_MAX_NUM_OF_SL(common_info+common_info_offset);
		basic_ml->mld_cap.srs_support = GET_MLD_CAP_SRS_SUPPORT(common_info+common_info_offset);
		basic_ml->mld_cap.tid_to_link_nego_support = GET_MLD_CAP_TID_TO_LINK_NEGO_SUPPORT(common_info+common_info_offset);
		basic_ml->mld_cap.freq_sep_for_str = GET_MLD_CAP_FREQ_SEP_FOR_STR(common_info+common_info_offset);
		basic_ml->mld_cap.aar_support = GET_MLD_CAP_AAR_SUPPORT(common_info+common_info_offset);
		common_info_offset += 2;
	}

	_dump_ml_basic(*ml_ele);

	_parse_ml_link_info(phl_com,
	                    ie_buf,
	                    ie_len,
	                    (2 + common_info_len),
	                    ml_ele);
}

void
_dump_ml_probe_req(struct rtw_phl_ml_element ml_ele)
{
	PHL_INFO("###### _dump_ml_probe_req #######\n");
	if (ml_ele.common_info.probe_req_ml.mld_id_present)
		PHL_INFO("%-25s: %d\n", "MLD ID", ml_ele.common_info.probe_req_ml.mld_id);
	else
		PHL_INFO("%-25s: Not present\n", "MLD ID");
}

void
_parse_ml_probe_req(struct rtw_phl_com_t *phl_com,
                    u8 *ie_buf,
                    u16 ie_len,
                    struct rtw_phl_ml_element *ml_ele
)
{
	struct probe_req_ml *probe_req_ml = &(ml_ele->common_info.probe_req_ml);
	u8 *ml_ctrl = ie_buf;
	u8 *common_info = ie_buf+2;
	u8 common_info_len = 0;
	u8 common_info_offset = 0;

	common_info_len = common_info[0];
	common_info_offset++;

	if (GET_ML_ELE_ML_CTRL_MLD_ID_PRESENT(ml_ctrl)) {
		probe_req_ml->mld_id_present = true;
		probe_req_ml->mld_id = LE_BITS_TO_1BYTE(common_info+common_info_offset, 0, 8);
		common_info_offset += 1;
	}

	_dump_ml_probe_req(*ml_ele);

	_parse_ml_link_info(phl_com,
	                    ie_buf,
	                    ie_len,
	                    (2 + common_info_len),
	                    ml_ele);
}

void
rtw_phl_parse_ml_ie(struct rtw_phl_com_t *phl_com,
                    u8 *ele_pos,
                    u16 ele_len,
                    struct rtw_phl_ml_element *ml_ele
)
{
	if ((ele_pos == NULL) || (ele_len == 0))
		return;

	if (ml_ele == NULL)
		return;

	ml_ele->type = GET_ML_ELE_ML_CTRL_TYPE(ele_pos);

	if (ml_ele->type == BASIC_ML) {
		_parse_ml_basic(phl_com, ele_pos, ele_len, ml_ele);
	} else if  (ml_ele->type == PROBE_REQUEST_ML) {
		_parse_ml_probe_req(phl_com, ele_pos, ele_len, ml_ele);
	} else {
		PHL_WARN("Unknown type!\n");
	}
}
/*
 * rtw_phl_get_ie: Return the total length of the element (include the fragment
 * element)
 *
 * Ex. Element fragmentation without Element ID Extension
 *                ele_len = 255 + 1 + 1 + 255 + 1 + 1 +n
 *             |------------------------------------------|
 *             |                                          |
 *             v 255    1     1     255    1     1   n    v
 * +------------------------------------------------------+
 * | EID | 255 | Data | FID | 255 | Data | FID | n | Data |
 * +------------------------------------------------------+
 *             ^
 *             |
 *             out_ele
 * Ex. Element fragmentation with Element ID Extension
 *                       ele_len = 254 + 1 + 1 + 255 + 1 + 1 +n
 *                   +----------------+---+---+-----+---+-------+
 *                   v                                          v
 *               1     254    1     1     255    1     1   n
 * +-----+-----+-----+------+-----+-----+------+-----+---+------+
 * | EID | 255 | EXT | Data | FID | 255 | Data | FID | n | Data |
 * +-----+-----+-----+------+-----+-----+------+-----+---+------+
 *                   ^
 *                   +
 *                   out_ele
 */
u16
rtw_phl_get_ie(u8 *ie_start,
               u16 ies_len,
               u8 target_id,
               u8 target_ext_id,
               u8 **out_ele
)
{
	u16 offset = 0, frag_offset = 0;
	u16 ele_len = 0;
	u8 tmp_id = 0;
	u8 tmp_ext_id = 0;
	u16 tmp_ele_len = 0;
	u8 frag_len = 0;
	bool is_fragmentable = false;

	do {
		if ((offset + 2) >= ies_len)
			break;

		/* Get current element ID */
		tmp_id = ie_start[offset];
		tmp_ele_len = ie_start[offset+1];
		if (tmp_id == EID_EXTENSION)
			tmp_ext_id = ie_start[offset+2];
		is_fragmentable = rtw_phl_is_ie_fragmentable(tmp_id, tmp_ext_id);
		if (is_fragmentable) {
			frag_offset = offset + 2 + tmp_ele_len;
			frag_len = (u8)tmp_ele_len;
			while (frag_len == 255) {
				/*
				 * Check there is more data at the end of the IE
				 * and is followed by fragment element
				 */
				if (((frag_offset + 2) < ies_len) &&
				    (ie_start[frag_offset] == EID_FRAGMENT)){
					frag_len = ie_start[frag_offset+1];
					frag_offset += (2+frag_len);
					tmp_ele_len += (2+frag_len);
				}
			}
		}

		/* Check element length is valid (ele length + N * frag length) */
		if ((offset + 2 + tmp_ele_len) > ies_len) {
			PHL_WARN("%s: Get invalid length!\n", __func__);
			return 0;
		}
		if (target_id == tmp_id) {
			if (target_id == EID_EXTENSION) {
				/* Check Extension ID */
				if (target_ext_id != tmp_ext_id) {
					/* Extension ID is different */
					offset += (tmp_ele_len + 2);
					continue;
				}
				/* Return pointer of the first byte after extension ID */
				*out_ele = ie_start + offset + 3;
				/* Return length - extension id field (1) */
				ele_len = tmp_ele_len - 1;
				break;
			}
			/* Return pointer of the first byte after lenght field */
			*out_ele = ie_start + offset + 2;
			ele_len = tmp_ele_len;
			break;
		} else {
			/* Element ID is different */
			offset += (tmp_ele_len + 2);
		}
	} while(1);

	return ele_len;
}

bool
rtw_phl_is_ie_fragmentable(u32 eid,
                           u32 eid_ext
)
{
	bool ret = false;

	if (eid == EID_FILS_INDICATION)
		ret = true;
	else if (eid == EID_EXTENSION) {
		if ((eid_ext == EID_EXT_FILS_KEY_CONFIRM) ||
		    (eid_ext == EID_EXT_FILS_HLP_CONTAINER) ||
		    (eid_ext == EID_EXT_KEY_DELIVERY) ||
		    (eid_ext == EID_EXT_FILS_WRAPPED_DATA) ||
		    (eid_ext == EID_EXT_FILS_PUBLIC_KEY) ||
		    (eid_ext == EID_EXT_CDMG_EXTEND_SCHEDULE) ||
		    (eid_ext == EID_EXT_SSW_REPORT) ||
		    (eid_ext == EID_EXT_SPSH_REPORT) ||
		    (eid_ext == EID_EXT_GAS_EXTENSION) ||
		    (eid_ext == EID_EXT_MULTI_LINK) ||
		    (eid_ext == EID_EXT_TID_TO_LINK_MAPPING))
			ret = true;
	}

	return ret;
}

/*
 * Currently, the reported APs in Reduced Neighbor Report are
 * affiliated with the same MLD as the reported AP. Therefore,
 * MLD ID would be 0.
 */
u8 _build_mld_parameters(void *phl,
                         struct rtw_wifi_role_link_t *rlink,
                         u8 *pbuf)
{
	struct rtw_phl_stainfo_t *sta = rtw_phl_get_stainfo_self(phl, rlink);
	u8 *p = pbuf;

	SET_MLD_PARAMS_MLD_ID(p, 0);
	SET_MLD_PARAMS_LINK_ID(p, sta->link_id); /* = rlink->id for AP mode */
	SET_MLD_PARAMS_BSS_PARAMS_CHG_CNT(p, rlink->bss_params_chg_cnt);

	return 3;
}

/*
 * Currently, the reported APs in Reduced Neighbor Report are
 * affiliated with the same MLD as the reported AP.
 */
u8 rtw_phl_build_reduced_nb_rpt(struct rtw_wifi_role_t *wrole,
                                struct rtw_wifi_role_link_t *rlink,
                                u8 *pbuf)
{
	void *phl = wrole->phl_com->phl_priv;
	void *drv = wrole->phl_com->drv_priv;
	u8 tbtt_info_cnt;
	u8 tbtt_info_len;
	u8 *pstart = pbuf;
	u8 *p = pbuf + 2;
	struct rtw_phl_mld_t *mld = rtw_phl_get_mld_self(phl, wrole);
	struct rtw_wifi_role_link_t *another_rlink;
	u8 lidx;

	/* only support MLD currently */
	if (mld == NULL || mld->type != DEV_TYPE_MLD || wrole->rlink_num == 1)
		return 0;

	tbtt_info_cnt = 0;
	tbtt_info_len = 16;

	for (lidx = 0; lidx < wrole->rlink_num; lidx++) {
		another_rlink = get_rlink(wrole, lidx);
		if (another_rlink == rlink)
			continue;

		/* TBTT Information Header */
		SET_TBTT_INFO_FIELD_TYPE(p, 0);
		SET_FILTED_NB_AP(p, 0);
		SET_TBTT_INFO_CNT(p, tbtt_info_cnt);
		SET_TBTT_INFO_LEN(p, tbtt_info_len);
		p += 2;

		/* Operating Class */
		*p++ = rtw_phl_get_operating_class(another_rlink->chandef);
		/* Channel Number */
		*p++ = another_rlink->chandef.chan;

		/* TBTT Information Set */
		*p++ = 0;      /* Neighbor AP TBTT Offset */
		_os_mem_cpy(drv, p, another_rlink->mac_addr, ETH_ALEN); /* BSSID */
		p += ETH_ALEN;
		p += 4;  /* TODO: Short SSID */
		p += 1;  /* TODO: BSS Parameters */
		p += 1;  /* TODO: 20 MHz PSD */
		p += _build_mld_parameters(phl, another_rlink, p);
	}

	*pstart = EID_REDUCED_NEIGHBOR_REPORT;
	*(pstart + 1) = (u8)(p - pstart - 2);

	return (u8)(p - pstart);
}

void _dump_reduced_nb_rpt(struct rtw_phl_rnb_rpt_element *reduced_nb_rpt)
{
	struct tbtt_info_header *hdr;
	struct rtw_phl_tbtt_info *tbtt_info;
	u8 i;
	int	j;

	PHL_INFO("###### _dump_reduced_nb_rpt #######\n");
	for (i = 0; i < reduced_nb_rpt->nb_ap_num; i++) {
		PHL_INFO("%s - %d\n", "Neighbor AP", i);
		hdr = &reduced_nb_rpt->nb_aps[i].tbtt_info_hdr;

		if (!hdr->is_legal) {
			PHL_INFO("%-25s %d %s\n", "Length", hdr->len, "is unrecognized");
			continue;
		}

		PHL_INFO("%-25s: %d\n", "Info Field Type", hdr->type);
		PHL_INFO("%-25s: %s\n", "Filtered Neighbor AP",
			(hdr->filtered_nb_ap == true ? "True" : "False"));
		PHL_INFO("%-25s: %d\n", "TBTT Info Count", hdr->cnt);
		PHL_INFO("%-25s: %d\n", "TBTT Info Length", hdr->len);
		PHL_INFO("%-25s: %d\n", "Operating Class", reduced_nb_rpt->nb_aps[i].op_class);
		PHL_INFO("%-25s: %d\n", "Channel", reduced_nb_rpt->nb_aps[i].ch);

		for (j = 0; j < (hdr->cnt + 1); j++) {
			tbtt_info = &reduced_nb_rpt->nb_aps[i].tbtt_infos[j];
			PHL_INFO("%s - %d\n", "TBTT Info", j);
			PHL_INFO("%-25s: %d\n", "Neighbor AP Offset", tbtt_info->offset);

			if (hdr->bssid_is_present)
				PHL_INFO("%-25s: %02x:%02x:%02x:%02x:%02x:%02x\n", "BSSID",
					tbtt_info->bssid[0], tbtt_info->bssid[1],
					tbtt_info->bssid[2], tbtt_info->bssid[3],
					tbtt_info->bssid[4], tbtt_info->bssid[5]);
			else
				PHL_INFO("%-25s: Not present\n", "BSSID");

			if (hdr->short_ssid_is_present)
				PHL_INFO("%-25s: %s\n", "Short SSID", (char *)tbtt_info->short_ssid);
			else
				PHL_INFO("%-25s: Not present\n", "Short SSID");

			if (hdr->bss_param_is_present) {
				PHL_INFO("%-25s: Present\n", "BSS Parameters");
				PHL_INFO("%-25s: %s\n", "- OCT recommended",
						(tbtt_info->bss_param.oct_recomm == true ? "True" : "False"));
				PHL_INFO("%-25s: %s\n", "- Same SSID",
						(tbtt_info->bss_param.same_ssid == true ? "True" : "False"));
				PHL_INFO("%-25s: %s\n", "- Multiple BSSID",
						(tbtt_info->bss_param.multi_bssid == true ? "True" : "False"));
				PHL_INFO("%-25s: %s\n", "- Transmitted BSSID",
						(tbtt_info->bss_param.transmitted_bssid == true ? "True" : "False"));
				PHL_INFO("%-25s: %s\n", "- 24G/5G colocated AP",
						(tbtt_info->bss_param.mem_24G_5G_colated_ap == true ? "True" : "False"));
				PHL_INFO("%-25s: %s\n", "- Unsolicited pbrsp active",
						(tbtt_info->bss_param.unsolicited_probe_resp_act == true ? "True" : "False"));
				PHL_INFO("%-25s: %s\n", "- Colocated AP",
						(tbtt_info->bss_param.colated_ap == true ? "True" : "False"));
			} else
				PHL_INFO("%-25s: Not present\n", "BSS Parameters");

			if (hdr->max_tx_pwr_is_present)
				PHL_INFO("%-25s: %d\n", "20MHz PSD", tbtt_info->max_tx_pwr);
			else
				PHL_INFO("%-25s: Not present\n", "20MHz PSD");

			if (hdr->mld_param_is_present) {
				PHL_INFO("%-25s: Present\n", "MLD Parameters");
				PHL_INFO("%-25s: %d\n", "- MLD ID", tbtt_info->mld_param.mld_id);
				PHL_INFO("%-25s: %d\n", "- Link ID", tbtt_info->mld_param.link_id);
				PHL_INFO("%-25s: %d\n", "- BSS Param Chg Cnt",
						tbtt_info->mld_param.bss_params_chg_cnt);
			} else
				PHL_INFO("%-25s: Not present\n", "MLD Parameters");
		}
	}
	PHL_INFO("###### _dump_reduced_nb_rpt #######\n");
}

u8 _parse_tbtt_header(struct rtw_phl_com_t *phl_com,
                      u8 *pos_start,
                      struct tbtt_info_header *hdr)
{
	u8 *pos = pos_start;

	hdr->type = GET_TBTT_INFO_FIELD_TYPE(pos);
	hdr->filtered_nb_ap = GET_FILTED_NB_AP(pos);
	hdr->cnt = GET_TBTT_INFO_CNT(pos);
	hdr->len = GET_TBTT_INFO_LEN(pos);

	if (hdr->len < 1 || hdr->len == 3) {
		hdr->is_legal = false;
		pos += hdr->len * hdr->cnt;
		goto exit;
	}

	hdr->is_legal = true;
	pos += 2;

	if (hdr->len >= 7)
		hdr->bssid_is_present = true;
	if (hdr->len == 5 || hdr->len == 6 || hdr->len >= 11)
		hdr->short_ssid_is_present = true;
	if (hdr->len == 2 || hdr->len == 6 || hdr->len == 8 || hdr->len == 9 || hdr->len >= 12)
		hdr->bss_param_is_present = true;
	if (hdr->len == 9 || hdr->len >= 13)
		hdr->max_tx_pwr_is_present = true;
	if (hdr->len == 4 || hdr->len == 10 || hdr->len >= 16)
		hdr->mld_param_is_present = true;

exit:
	return (u8)(pos - pos_start);

}

u8 _parse_nb_info(struct rtw_phl_com_t *phl_com,
                  u8 *pos_start,
                  struct rtw_phl_neighbor_ap *nb_ap)
{
	void *drv = phl_com->drv_priv;
	u8 *pos = pos_start;
	u8 *pos_tbtt_info;
	struct tbtt_info_header *hdr = &nb_ap->tbtt_info_hdr;
	struct rtw_phl_tbtt_info *tbtt_info;
	int i = 0;

	/* TBTT Information Header */
	pos += _parse_tbtt_header(phl_com, pos, hdr);

	if (!hdr->is_legal)
		goto exit;

	nb_ap->op_class = *pos++;
	nb_ap->ch = *pos++;

	/* translate to chan_def */
	if (!rtw_phl_get_chandef_from_operating_class(nb_ap->ch,
		nb_ap->op_class, &nb_ap->chan_def)) {
		PHL_TRACE(COMP_PHL_DBG, _PHL_INFO_,
			"%s: getting channel definition failed !!! \n", __func__);
	}

	/* TBTT Information Set */
	do {
		pos_tbtt_info = pos;
		tbtt_info = &nb_ap->tbtt_infos[i];

		/* TBTT Offset*/
		tbtt_info->offset = *pos++;

		/* BSSID */
		if (hdr->bssid_is_present) {
			_os_mem_cpy(drv, tbtt_info->bssid, pos, MAC_ALEN);
			pos += MAC_ALEN;
		}

		/* Short SSID */
		if (hdr->short_ssid_is_present) {
			_os_mem_cpy(drv, tbtt_info->short_ssid, pos, 4);
			pos += 4;
		}

		/* BSS Parameters */
		if (hdr->bss_param_is_present) {
			tbtt_info->bss_param.oct_recomm =
				GET_BSS_PARAMS_OCT_RECOMM(pos);
			tbtt_info->bss_param.same_ssid =
				GET_BSS_PARAMS_SAME_SSID(pos);
			tbtt_info->bss_param.multi_bssid =
				GET_BSS_PARAMS_MULTI_BSSID(pos);
			tbtt_info->bss_param.transmitted_bssid =
				GET_BSS_PARAMS_TRANSMITTED_BSSID(pos);
			tbtt_info->bss_param.mem_24G_5G_colated_ap =
				GET_BSS_PARAMS_MEM_24G_5G_COLOCATED_AP(pos);
			tbtt_info->bss_param.unsolicited_probe_resp_act =
				GET_BSS_PARAMS_UNSOLICITED_PROBE_RESP_ACTIVE(pos);
			tbtt_info->bss_param.colated_ap =
				GET_BSS_PARAMS_COLOACTED_AP(pos);
			pos += 1;
		}

		/* 20Mhz psd */
		if (hdr->max_tx_pwr_is_present)
			tbtt_info->max_tx_pwr = *pos++;

		/* MLD parameters */
		if (hdr->mld_param_is_present) {
			tbtt_info->mld_param.mld_id = GET_MLD_PARAMS_MLD_ID(pos);
			tbtt_info->mld_param.link_id = GET_MLD_PARAMS_LINK_ID(pos);
			tbtt_info->mld_param.bss_params_chg_cnt = GET_MLD_PARAMS_BSS_PARAMS_CHG_CNT(pos);
			pos += 3;
		}

		/* ignore the rest reserved fields */
		pos = pos_tbtt_info + hdr->len;
		i++;

	} while (i < (hdr->cnt + 1) && i < MAX_TBTT_INFO_NUM);

exit:
	return (u8)(pos - pos_start);
}

void rtw_phl_parse_reduced_nb_rpt(struct rtw_phl_com_t *phl_com,
                                  u8 *ele_start,
                                  u16 ele_len,
                                  struct rtw_phl_rnb_rpt_element *reduced_nb_rpt)
{
	u8 *ele_pos = NULL;

	if ((ele_start == NULL) || (ele_len == 0))
		return;

	if (reduced_nb_rpt == NULL)
		return;

	ele_pos = ele_start;

	do {
		if (reduced_nb_rpt->nb_ap_num >= MAX_NEIGHBOR_AP_NUM)
			break;

		ele_pos += _parse_nb_info(phl_com, ele_pos, &reduced_nb_rpt->nb_aps[reduced_nb_rpt->nb_ap_num]);
		reduced_nb_rpt->nb_ap_num++;
	} while(ele_pos < (ele_start + ele_len));

}

