--- /dev/null
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2021 Marvell.
+ */
+#include "roc_api.h"
+#include "roc_priv.h"
+
+const struct roc_npc_item_info *
+npc_parse_skip_void_and_any_items(const struct roc_npc_item_info *pattern)
+{
+ while ((pattern->type == ROC_NPC_ITEM_TYPE_VOID) ||
+ (pattern->type == ROC_NPC_ITEM_TYPE_ANY))
+ pattern++;
+
+ return pattern;
+}
+
+int
+npc_parse_meta_items(struct npc_parse_state *pst)
+{
+ PLT_SET_USED(pst);
+ return 0;
+}
+
+int
+npc_parse_cpt_hdr(struct npc_parse_state *pst)
+{
+ uint8_t hw_mask[NPC_MAX_EXTRACT_HW_LEN];
+ struct npc_parse_item_info info;
+ int lid, lt;
+ int rc;
+
+ /* Identify the pattern type into lid, lt */
+ if (pst->pattern->type != ROC_NPC_ITEM_TYPE_CPT_HDR)
+ return 0;
+
+ lid = NPC_LID_LA;
+ lt = NPC_LT_LA_CPT_HDR;
+ info.hw_hdr_len = 0;
+
+ /* Prepare for parsing the item */
+ info.hw_mask = &hw_mask;
+ info.len = pst->pattern->size;
+ npc_get_hw_supp_mask(pst, &info, lid, lt);
+ info.spec = NULL;
+ info.mask = NULL;
+
+ /* Basic validation of item parameters */
+ rc = npc_parse_item_basic(pst->pattern, &info);
+ if (rc)
+ return rc;
+
+ /* Update pst if not validate only? clash check? */
+ return npc_update_parse_state(pst, &info, lid, lt, 0);
+}
+
+int
+npc_parse_higig2_hdr(struct npc_parse_state *pst)
+{
+ uint8_t hw_mask[NPC_MAX_EXTRACT_HW_LEN];
+ struct npc_parse_item_info info;
+ int lid, lt;
+ int rc;
+
+ /* Identify the pattern type into lid, lt */
+ if (pst->pattern->type != ROC_NPC_ITEM_TYPE_HIGIG2)
+ return 0;
+
+ lid = NPC_LID_LA;
+ lt = NPC_LT_LA_HIGIG2_ETHER;
+ info.hw_hdr_len = 0;
+
+ if (pst->flow->nix_intf == NIX_INTF_TX) {
+ lt = NPC_LT_LA_IH_NIX_HIGIG2_ETHER;
+ info.hw_hdr_len = NPC_IH_LENGTH;
+ }
+
+ /* Prepare for parsing the item */
+ info.hw_mask = &hw_mask;
+ info.len = pst->pattern->size;
+ npc_get_hw_supp_mask(pst, &info, lid, lt);
+ info.spec = NULL;
+ info.mask = NULL;
+
+ /* Basic validation of item parameters */
+ rc = npc_parse_item_basic(pst->pattern, &info);
+ if (rc)
+ return rc;
+
+ /* Update pst if not validate only? clash check? */
+ return npc_update_parse_state(pst, &info, lid, lt, 0);
+}
+
+int
+npc_parse_la(struct npc_parse_state *pst)
+{
+ uint8_t hw_mask[NPC_MAX_EXTRACT_HW_LEN];
+ struct npc_parse_item_info info;
+ int lid, lt;
+ int rc;
+
+ /* Identify the pattern type into lid, lt */
+ if (pst->pattern->type != ROC_NPC_ITEM_TYPE_ETH)
+ return 0;
+
+ lid = NPC_LID_LA;
+ lt = NPC_LT_LA_ETHER;
+ info.hw_hdr_len = 0;
+
+ if (pst->flow->nix_intf == NIX_INTF_TX) {
+ lt = NPC_LT_LA_IH_NIX_ETHER;
+ info.hw_hdr_len = NPC_IH_LENGTH;
+ if (pst->npc->switch_header_type == ROC_PRIV_FLAGS_HIGIG) {
+ lt = NPC_LT_LA_IH_NIX_HIGIG2_ETHER;
+ info.hw_hdr_len += NPC_HIGIG2_LENGTH;
+ }
+ } else {
+ if (pst->npc->switch_header_type == ROC_PRIV_FLAGS_HIGIG) {
+ lt = NPC_LT_LA_HIGIG2_ETHER;
+ info.hw_hdr_len = NPC_HIGIG2_LENGTH;
+ }
+ }
+
+ /* Prepare for parsing the item */
+ info.hw_mask = &hw_mask;
+ info.len = pst->pattern->size;
+ npc_get_hw_supp_mask(pst, &info, lid, lt);
+ info.spec = NULL;
+ info.mask = NULL;
+
+ /* Basic validation of item parameters */
+ rc = npc_parse_item_basic(pst->pattern, &info);
+ if (rc)
+ return rc;
+
+ /* Update pst if not validate only? clash check? */
+ return npc_update_parse_state(pst, &info, lid, lt, 0);
+}
+
+int
+npc_parse_lb(struct npc_parse_state *pst)
+{
+ const struct roc_npc_item_info *pattern = pst->pattern;
+ const struct roc_npc_item_info *last_pattern;
+ char hw_mask[NPC_MAX_EXTRACT_HW_LEN];
+ struct npc_parse_item_info info;
+ int lid, lt, lflags;
+ int nr_vlans = 0;
+ int rc;
+
+ info.spec = NULL;
+ info.mask = NULL;
+ info.def_mask = NULL;
+ info.hw_hdr_len = NPC_TPID_LENGTH;
+
+ lid = NPC_LID_LB;
+ lflags = 0;
+ last_pattern = pattern;
+
+ if (pst->pattern->type == ROC_NPC_ITEM_TYPE_VLAN) {
+ /* RTE vlan is either 802.1q or 802.1ad,
+ * this maps to either CTAG/STAG. We need to decide
+ * based on number of VLANS present. Matching is
+ * supported on first tag only.
+ */
+ info.hw_mask = NULL;
+ info.len = pst->pattern->size;
+
+ pattern = pst->pattern;
+ while (pattern->type == ROC_NPC_ITEM_TYPE_VLAN) {
+ nr_vlans++;
+
+ /* Basic validation of Second/Third vlan item */
+ if (nr_vlans > 1) {
+ rc = npc_parse_item_basic(pattern, &info);
+ if (rc != 0)
+ return rc;
+ }
+ last_pattern = pattern;
+ pattern++;
+ pattern = npc_parse_skip_void_and_any_items(pattern);
+ }
+
+ switch (nr_vlans) {
+ case 1:
+ lt = NPC_LT_LB_CTAG;
+ break;
+ case 2:
+ lt = NPC_LT_LB_STAG_QINQ;
+ lflags = NPC_F_STAG_CTAG;
+ break;
+ case 3:
+ lt = NPC_LT_LB_STAG_QINQ;
+ lflags = NPC_F_STAG_STAG_CTAG;
+ break;
+ default:
+ return NPC_ERR_PATTERN_NOTSUP;
+ }
+ } else if (pst->pattern->type == ROC_NPC_ITEM_TYPE_E_TAG) {
+ /* we can support ETAG and match a subsequent CTAG
+ * without any matching support.
+ */
+ lt = NPC_LT_LB_ETAG;
+ lflags = 0;
+
+ last_pattern = pst->pattern;
+ pattern = npc_parse_skip_void_and_any_items(pst->pattern + 1);
+ if (pattern->type == ROC_NPC_ITEM_TYPE_VLAN) {
+ /* set supported mask to NULL for vlan tag */
+ info.hw_mask = NULL;
+ info.len = pattern->size;
+ rc = npc_parse_item_basic(pattern, &info);
+ if (rc != 0)
+ return rc;
+
+ lflags = NPC_F_ETAG_CTAG;
+ last_pattern = pattern;
+ }
+ info.len = pattern->size;
+ } else if (pst->pattern->type == ROC_NPC_ITEM_TYPE_QINQ) {
+ info.hw_mask = NULL;
+ info.len = pst->pattern->size;
+ lt = NPC_LT_LB_STAG_QINQ;
+ lflags = NPC_F_STAG_CTAG;
+ } else {
+ return 0;
+ }
+
+ info.hw_mask = &hw_mask;
+ info.spec = NULL;
+ info.mask = NULL;
+ npc_get_hw_supp_mask(pst, &info, lid, lt);
+
+ rc = npc_parse_item_basic(pst->pattern, &info);
+ if (rc != 0)
+ return rc;
+
+ /* Point pattern to last item consumed */
+ pst->pattern = last_pattern;
+ return npc_update_parse_state(pst, &info, lid, lt, lflags);
+}
+
+static int
+npc_parse_mpls_label_stack(struct npc_parse_state *pst, int *flag)
+{
+ uint8_t flag_list[] = {0, NPC_F_MPLS_2_LABELS, NPC_F_MPLS_3_LABELS,
+ NPC_F_MPLS_4_LABELS};
+ const struct roc_npc_item_info *pattern = pst->pattern;
+ struct npc_parse_item_info info;
+ int nr_labels = 0;
+ int rc;
+
+ /*
+ * pst->pattern points to first MPLS label. We only check
+ * that subsequent labels do not have anything to match.
+ */
+ info.hw_mask = NULL;
+ info.len = pattern->size;
+ info.spec = NULL;
+ info.mask = NULL;
+ info.hw_hdr_len = 0;
+ info.def_mask = NULL;
+
+ while (pattern->type == ROC_NPC_ITEM_TYPE_MPLS) {
+ nr_labels++;
+
+ /* Basic validation of Second/Third/Fourth mpls item */
+ if (nr_labels > 1) {
+ rc = npc_parse_item_basic(pattern, &info);
+ if (rc != 0)
+ return rc;
+ }
+ pst->last_pattern = pattern;
+ pattern++;
+ pattern = npc_parse_skip_void_and_any_items(pattern);
+ }
+
+ if (nr_labels < 1 || nr_labels > 4)
+ return NPC_ERR_PATTERN_NOTSUP;
+
+ *flag = flag_list[nr_labels - 1];
+ return 0;
+}
+
+static int
+npc_parse_mpls(struct npc_parse_state *pst, int lid)
+{
+ /* Find number of MPLS labels */
+ uint8_t hw_mask[NPC_MAX_EXTRACT_HW_LEN];
+ struct npc_parse_item_info info;
+ int lt, lflags;
+ int rc;
+
+ lflags = 0;
+
+ if (lid == NPC_LID_LC)
+ lt = NPC_LT_LC_MPLS;
+ else if (lid == NPC_LID_LD)
+ lt = NPC_LT_LD_TU_MPLS_IN_IP;
+ else
+ lt = NPC_LT_LE_TU_MPLS_IN_UDP;
+
+ /* Prepare for parsing the first item */
+ info.hw_mask = &hw_mask;
+ info.len = pst->pattern->size;
+ info.spec = NULL;
+ info.mask = NULL;
+ info.hw_hdr_len = 0;
+
+ npc_get_hw_supp_mask(pst, &info, lid, lt);
+ rc = npc_parse_item_basic(pst->pattern, &info);
+ if (rc != 0)
+ return rc;
+
+ /*
+ * Parse for more labels.
+ * This sets lflags and pst->last_pattern correctly.
+ */
+ rc = npc_parse_mpls_label_stack(pst, &lflags);
+ if (rc != 0)
+ return rc;
+
+ pst->tunnel = 1;
+ pst->pattern = pst->last_pattern;
+
+ return npc_update_parse_state(pst, &info, lid, lt, lflags);
+}
+
+static inline void
+npc_check_lc_ip_tunnel(struct npc_parse_state *pst)
+{
+ const struct roc_npc_item_info *pattern = pst->pattern + 1;
+
+ pattern = npc_parse_skip_void_and_any_items(pattern);
+ if (pattern->type == ROC_NPC_ITEM_TYPE_MPLS ||
+ pattern->type == ROC_NPC_ITEM_TYPE_IPV4 ||
+ pattern->type == ROC_NPC_ITEM_TYPE_IPV6)
+ pst->tunnel = 1;
+}
+
+int
+npc_parse_lc(struct npc_parse_state *pst)
+{
+ uint8_t hw_mask[NPC_MAX_EXTRACT_HW_LEN];
+ struct npc_parse_item_info info;
+ int lid, lt;
+ int rc;
+
+ if (pst->pattern->type == ROC_NPC_ITEM_TYPE_MPLS)
+ return npc_parse_mpls(pst, NPC_LID_LC);
+
+ info.hw_mask = &hw_mask;
+ info.spec = NULL;
+ info.mask = NULL;
+ info.hw_hdr_len = 0;
+ lid = NPC_LID_LC;
+
+ switch (pst->pattern->type) {
+ case ROC_NPC_ITEM_TYPE_IPV4:
+ lt = NPC_LT_LC_IP;
+ info.len = pst->pattern->size;
+ break;
+ case ROC_NPC_ITEM_TYPE_IPV6:
+ lid = NPC_LID_LC;
+ lt = NPC_LT_LC_IP6;
+ info.len = pst->pattern->size;
+ break;
+ case ROC_NPC_ITEM_TYPE_ARP_ETH_IPV4:
+ lt = NPC_LT_LC_ARP;
+ info.len = pst->pattern->size;
+ break;
+ case ROC_NPC_ITEM_TYPE_IPV6_EXT:
+ lid = NPC_LID_LC;
+ lt = NPC_LT_LC_IP6_EXT;
+ info.len = pst->pattern->size;
+ info.hw_hdr_len = 40;
+ break;
+ case ROC_NPC_ITEM_TYPE_L3_CUSTOM:
+ lt = NPC_LT_LC_CUSTOM0;
+ info.len = pst->pattern->size;
+ break;
+ default:
+ /* No match at this layer */
+ return 0;
+ }
+
+ /* Identify if IP tunnels MPLS or IPv4/v6 */
+ npc_check_lc_ip_tunnel(pst);
+
+ npc_get_hw_supp_mask(pst, &info, lid, lt);
+ rc = npc_parse_item_basic(pst->pattern, &info);
+ if (rc != 0)
+ return rc;
+
+ return npc_update_parse_state(pst, &info, lid, lt, 0);
+}
+
+int
+npc_parse_ld(struct npc_parse_state *pst)
+{
+ char hw_mask[NPC_MAX_EXTRACT_HW_LEN];
+ struct npc_parse_item_info info;
+ int lid, lt, lflags;
+ int rc;
+
+ if (pst->tunnel) {
+ /* We have already parsed MPLS or IPv4/v6 followed
+ * by MPLS or IPv4/v6. Subsequent TCP/UDP etc
+ * would be parsed as tunneled versions. Skip
+ * this layer, except for tunneled MPLS. If LC is
+ * MPLS, we have anyway skipped all stacked MPLS
+ * labels.
+ */
+ if (pst->pattern->type == ROC_NPC_ITEM_TYPE_MPLS)
+ return npc_parse_mpls(pst, NPC_LID_LD);
+ return 0;
+ }
+ info.hw_mask = &hw_mask;
+ info.spec = NULL;
+ info.mask = NULL;
+ info.def_mask = NULL;
+ info.len = 0;
+ info.hw_hdr_len = 0;
+
+ lid = NPC_LID_LD;
+ lflags = 0;
+
+ switch (pst->pattern->type) {
+ case ROC_NPC_ITEM_TYPE_ICMP:
+ if (pst->lt[NPC_LID_LC] == NPC_LT_LC_IP6)
+ lt = NPC_LT_LD_ICMP6;
+ else
+ lt = NPC_LT_LD_ICMP;
+ info.len = pst->pattern->size;
+ break;
+ case ROC_NPC_ITEM_TYPE_UDP:
+ lt = NPC_LT_LD_UDP;
+ info.len = pst->pattern->size;
+ break;
+ case ROC_NPC_ITEM_TYPE_IGMP:
+ lt = NPC_LT_LD_IGMP;
+ info.len = pst->pattern->size;
+ break;
+ case ROC_NPC_ITEM_TYPE_TCP:
+ lt = NPC_LT_LD_TCP;
+ info.len = pst->pattern->size;
+ break;
+ case ROC_NPC_ITEM_TYPE_SCTP:
+ lt = NPC_LT_LD_SCTP;
+ info.len = pst->pattern->size;
+ break;
+ case ROC_NPC_ITEM_TYPE_GRE:
+ lt = NPC_LT_LD_GRE;
+ info.len = pst->pattern->size;
+ break;
+ case ROC_NPC_ITEM_TYPE_GRE_KEY:
+ lt = NPC_LT_LD_GRE;
+ info.len = pst->pattern->size;
+ info.hw_hdr_len = 4;
+ break;
+ case ROC_NPC_ITEM_TYPE_NVGRE:
+ lt = NPC_LT_LD_NVGRE;
+ lflags = NPC_F_GRE_NVGRE;
+ info.len = pst->pattern->size;
+ /* Further IP/Ethernet are parsed as tunneled */
+ pst->tunnel = 1;
+ break;
+ default:
+ return 0;
+ }
+
+ npc_get_hw_supp_mask(pst, &info, lid, lt);
+ rc = npc_parse_item_basic(pst->pattern, &info);
+ if (rc != 0)
+ return rc;
+
+ return npc_update_parse_state(pst, &info, lid, lt, lflags);
+}
+
+int
+npc_parse_le(struct npc_parse_state *pst)
+{
+ const struct roc_npc_item_info *pattern = pst->pattern;
+ char hw_mask[NPC_MAX_EXTRACT_HW_LEN];
+ struct npc_parse_item_info info;
+ int lid, lt, lflags;
+ int rc;
+
+ if (pst->tunnel)
+ return 0;
+
+ if (pst->pattern->type == ROC_NPC_ITEM_TYPE_MPLS)
+ return npc_parse_mpls(pst, NPC_LID_LE);
+
+ info.spec = NULL;
+ info.mask = NULL;
+ info.hw_mask = NULL;
+ info.def_mask = NULL;
+ info.len = 0;
+ info.hw_hdr_len = 0;
+ lid = NPC_LID_LE;
+ lflags = 0;
+
+ /* Ensure we are not matching anything in UDP */
+ rc = npc_parse_item_basic(pattern, &info);
+ if (rc)
+ return rc;
+
+ info.hw_mask = &hw_mask;
+ pattern = npc_parse_skip_void_and_any_items(pattern);
+ switch (pattern->type) {
+ case ROC_NPC_ITEM_TYPE_VXLAN:
+ lflags = NPC_F_UDP_VXLAN;
+ info.len = pattern->size;
+ lt = NPC_LT_LE_VXLAN;
+ break;
+ case ROC_NPC_ITEM_TYPE_GTPC:
+ lflags = NPC_F_UDP_GTP_GTPC;
+ info.len = pattern->size;
+ lt = NPC_LT_LE_GTPC;
+ break;
+ case ROC_NPC_ITEM_TYPE_GTPU:
+ lflags = NPC_F_UDP_GTP_GTPU_G_PDU;
+ info.len = pattern->size;
+ lt = NPC_LT_LE_GTPU;
+ break;
+ case ROC_NPC_ITEM_TYPE_GENEVE:
+ lflags = NPC_F_UDP_GENEVE;
+ info.len = pattern->size;
+ lt = NPC_LT_LE_GENEVE;
+ break;
+ case ROC_NPC_ITEM_TYPE_VXLAN_GPE:
+ lflags = NPC_F_UDP_VXLANGPE;
+ info.len = pattern->size;
+ lt = NPC_LT_LE_VXLANGPE;
+ break;
+ case ROC_NPC_ITEM_TYPE_ESP:
+ lt = NPC_LT_LE_ESP;
+ info.len = pst->pattern->size;
+ break;
+ default:
+ return 0;
+ }
+
+ pst->tunnel = 1;
+
+ npc_get_hw_supp_mask(pst, &info, lid, lt);
+ rc = npc_parse_item_basic(pattern, &info);
+ if (rc != 0)
+ return rc;
+
+ return npc_update_parse_state(pst, &info, lid, lt, lflags);
+}
+
+int
+npc_parse_lf(struct npc_parse_state *pst)
+{
+ const struct roc_npc_item_info *pattern, *last_pattern;
+ char hw_mask[NPC_MAX_EXTRACT_HW_LEN];
+ struct npc_parse_item_info info;
+ int lid, lt, lflags;
+ int nr_vlans = 0;
+ int rc;
+
+ /* We hit this layer if there is a tunneling protocol */
+ if (!pst->tunnel)
+ return 0;
+
+ if (pst->pattern->type != ROC_NPC_ITEM_TYPE_ETH)
+ return 0;
+
+ lid = NPC_LID_LF;
+ lt = NPC_LT_LF_TU_ETHER;
+ lflags = 0;
+
+ /* No match support for vlan tags */
+ info.hw_mask = NULL;
+ info.len = pst->pattern->size;
+ info.spec = NULL;
+ info.mask = NULL;
+ info.hw_hdr_len = 0;
+
+ /* Look ahead and find out any VLAN tags. These can be
+ * detected but no data matching is available.
+ */
+ last_pattern = pst->pattern;
+ pattern = pst->pattern + 1;
+ pattern = npc_parse_skip_void_and_any_items(pattern);
+ while (pattern->type == ROC_NPC_ITEM_TYPE_VLAN) {
+ nr_vlans++;
+ last_pattern = pattern;
+ pattern++;
+ pattern = npc_parse_skip_void_and_any_items(pattern);
+ }
+ switch (nr_vlans) {
+ case 0:
+ break;
+ case 1:
+ lflags = NPC_F_TU_ETHER_CTAG;
+ break;
+ case 2:
+ lflags = NPC_F_TU_ETHER_STAG_CTAG;
+ break;
+ default:
+ return NPC_ERR_PATTERN_NOTSUP;
+ }
+
+ info.hw_mask = &hw_mask;
+ info.len = pst->pattern->size;
+ info.hw_hdr_len = 0;
+ npc_get_hw_supp_mask(pst, &info, lid, lt);
+ info.spec = NULL;
+ info.mask = NULL;
+
+ rc = npc_parse_item_basic(pst->pattern, &info);
+ if (rc != 0)
+ return rc;
+
+ pst->pattern = last_pattern;
+
+ return npc_update_parse_state(pst, &info, lid, lt, lflags);
+}
+
+int
+npc_parse_lg(struct npc_parse_state *pst)
+{
+ char hw_mask[NPC_MAX_EXTRACT_HW_LEN];
+ struct npc_parse_item_info info;
+ int lid, lt;
+ int rc;
+
+ if (!pst->tunnel)
+ return 0;
+
+ info.hw_mask = &hw_mask;
+ info.spec = NULL;
+ info.mask = NULL;
+ info.hw_hdr_len = 0;
+ lid = NPC_LID_LG;
+
+ if (pst->pattern->type == ROC_NPC_ITEM_TYPE_IPV4) {
+ lt = NPC_LT_LG_TU_IP;
+ info.len = pst->pattern->size;
+ } else if (pst->pattern->type == ROC_NPC_ITEM_TYPE_IPV6) {
+ lt = NPC_LT_LG_TU_IP6;
+ info.len = pst->pattern->size;
+ } else {
+ /* There is no tunneled IP header */
+ return 0;
+ }
+
+ npc_get_hw_supp_mask(pst, &info, lid, lt);
+ rc = npc_parse_item_basic(pst->pattern, &info);
+ if (rc != 0)
+ return rc;
+
+ return npc_update_parse_state(pst, &info, lid, lt, 0);
+}
+
+int
+npc_parse_lh(struct npc_parse_state *pst)
+{
+ char hw_mask[NPC_MAX_EXTRACT_HW_LEN];
+ struct npc_parse_item_info info;
+ int lid, lt;
+ int rc;
+
+ if (!pst->tunnel)
+ return 0;
+
+ info.hw_mask = &hw_mask;
+ info.spec = NULL;
+ info.mask = NULL;
+ info.hw_hdr_len = 0;
+ lid = NPC_LID_LH;
+
+ switch (pst->pattern->type) {
+ case ROC_NPC_ITEM_TYPE_UDP:
+ lt = NPC_LT_LH_TU_UDP;
+ info.len = pst->pattern->size;
+ break;
+ case ROC_NPC_ITEM_TYPE_TCP:
+ lt = NPC_LT_LH_TU_TCP;
+ info.len = pst->pattern->size;
+ break;
+ case ROC_NPC_ITEM_TYPE_SCTP:
+ lt = NPC_LT_LH_TU_SCTP;
+ info.len = pst->pattern->size;
+ break;
+ case ROC_NPC_ITEM_TYPE_ESP:
+ lt = NPC_LT_LH_TU_ESP;
+ info.len = pst->pattern->size;
+ break;
+ default:
+ return 0;
+ }
+
+ npc_get_hw_supp_mask(pst, &info, lid, lt);
+ rc = npc_parse_item_basic(pst->pattern, &info);
+ if (rc != 0)
+ return rc;
+
+ return npc_update_parse_state(pst, &info, lid, lt, 0);
+}