From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mails.dpdk.org (mails.dpdk.org [217.70.189.124]) by smtp.lore.kernel.org (Postfix) with ESMTP id CA68BC53219 for ; Mon, 27 Jul 2026 23:13:23 +0000 (UTC) Received: from mails.dpdk.org (localhost [127.0.0.1]) by mails.dpdk.org (Postfix) with ESMTP id C546A406BB; Tue, 28 Jul 2026 01:12:47 +0200 (CEST) Received: from mail-pl1-f180.google.com (mail-pl1-f180.google.com [209.85.214.180]) by mails.dpdk.org (Postfix) with ESMTP id 14D8140679 for ; Tue, 28 Jul 2026 01:12:44 +0200 (CEST) Received: by mail-pl1-f180.google.com with SMTP id d9443c01a7336-2cc7e86e7aeso36320965ad.2 for ; Mon, 27 Jul 2026 16:12:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=networkplumber-org.20251104.gappssmtp.com; s=20251104; t=1785193963; x=1785798763; darn=dpdk.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=uDjgwNSZFklGfTbn9rdyKvXqUSqfYlestcnO8tFOlaU=; b=wPuSbAJCqS6wi7P5OfEuJ3pUvFAie8Rp+0c4LKjjExpB7pfEfMSjcIXYKz8qp1mkL6 PDjkM+OechbuqhzQ6keHv5aI4KeVAf66NNhWOrwSnoz0/xjYKtr66AvvtzT1dd9+e/vW SuhbcGbqt43xb9gxM4cGviL7A4Fyrd2qUmAD5SD2ms7Z5DzCfgo0DanRsoCiTxrSxt7w TGrLsXGaxr525FW9VPEqi6cKfzOG8wNW58bASbyB1//ofcLiXEW1v4Rs1A20JbDXgSWl 7nz0bxCZSTk7OSjai9fHGfzFOBwrVNNebw279QsDXtRnnJMljph4R8IrX+cSCTAGT3pU a77A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785193963; x=1785798763; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=uDjgwNSZFklGfTbn9rdyKvXqUSqfYlestcnO8tFOlaU=; b=Zg5n2rIZ20bDC8Tb31ROlyaG8rpXELf+ZXpAMMLchciDQTBUkuy6H4iJ/Fg14sZMk5 9Ri3Kalq1y90X53Nne3fjkeyeW6jfaknisOmfss7OCjs6LwnCB9G/5s2QcfM4iDhHEWQ RVVmktfZNBIsgjKjvy6KGNECOvahu329UpasdN2vWj+LYTGe6vsPafUJMEJRWTp4ffmI zcHEvrRZi1HHCA7dtIDsbtMpaqub2PW6U5AyxZsmXvgUPcLWr7ejzHIARgKmhJCgtgIu dSLvYCyOy4B2/cely32JuIi12yZgEwJcWzjsr/4Mnk48YMuYysU9n2GQyVmtwRL8DsS5 SY1Q== X-Gm-Message-State: AOJu0YwcwyCBr2JlhIj3GmwquXQYWRS/LCZlp5ddDigxoGbKEMpgBqI9 X12aBYO8mo6feEQ3QGL8oNebzQUv8EOYJGFil52NuNMswwSRaBSSCBdZa8wwXfOlVgvi2uYfdaP Z8VLZ X-Gm-Gg: AR+sD11zyCc5HBbr8T9B5itmdHqw9aoz4J/ga5yWKS9sA8F9TepQuvYKHqltC383cxR 7tV9PRHbKx2sbQbMgZsluFiiRNMAuZSxPKz1PrAlztsZzNKdvUi0pKIUAfQiLPCyCrTGZND33vB 9su20sQIb9xfl9ZGLvg9vNQpelw/J0tiod3OwvBFG6XTWwfLm6wAC3iCeJDxb4IkVrTY68kbSol CoadSLzh6/JSdHXC/gfS/doEjoBtdEqqD+35rs+74JhQONV4ECQRDBlptZ44yknuwyzklv5gsFZ jvhMGt7l1m+DnDANoWZE6JkjAHvTE0HmT1AD8nyh3Di+w/T/x29uXDYxQBEpRCfrCdHQCQPIt8d pdGDv13B94+NKi9CsjJ0D48dz6kSQujH4ZsqKCti2gM6pv6mwQdzXDp3uEGdQGMfOgUA2D3A8h2 0xG5Tya+D1ZRfChng62AdbBpdoQRm+pfrHA08/FNMVhFUG8oUX9mI= X-Received: by 2002:a05:6a21:6b02:b0:3c3:954d:279a with SMTP id adf61e73a8af0-3c67daecd2bmr10041058637.33.1785193960993; Mon, 27 Jul 2026 16:12:40 -0700 (PDT) Received: from phoenix.lan (204-195-96-226.wavecable.com. [204.195.96.226]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-314bc3e261fsm40359802eec.6.2026.07.27.16.12.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 27 Jul 2026 16:12:39 -0700 (PDT) From: Stephen Hemminger To: dev@dpdk.org Cc: Stephen Hemminger , Cristian Dumitrescu , Wathsala Vithanage Subject: [RFC v2 5/6] table: remove legacy API Date: Mon, 27 Jul 2026 16:11:29 -0700 Message-ID: <20260727231222.1492544-6-stephen@networkplumber.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260727231222.1492544-1-stephen@networkplumber.org> References: <20260724164520.192203-1-stephen@networkplumber.org> <20260727231222.1492544-1-stephen@networkplumber.org> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: dev@dpdk.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: DPDK patches and discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@dpdk.org Remove the legacy table library API (rte_table_*) as announced for removal in the 24.11 release. The SWX table API remains. The lpm and port dependencies were only used by the legacy API, so drop them. The hash and acl dependencies are still needed by the SWX exact match and wildcard match tables. Signed-off-by: Stephen Hemminger --- doc/api/doxy-api-index.md | 7 - doc/guides/prog_guide/img/figure33.png | Bin 65216 -> 0 bytes doc/guides/prog_guide/img/figure34.png | Bin 11581 -> 0 bytes doc/guides/prog_guide/img/figure35.png | Bin 75012 -> 0 bytes doc/guides/prog_guide/img/figure37.png | Bin 6934 -> 0 bytes doc/guides/prog_guide/img/figure38.png | Bin 7372 -> 0 bytes doc/guides/prog_guide/img/figure39.png | Bin 55986 -> 0 bytes doc/guides/prog_guide/packet_framework.rst | 811 ------------- doc/guides/rel_notes/deprecation.rst | 5 - doc/guides/rel_notes/release_26_11.rst | 3 + lib/table/meson.build | 30 +- lib/table/rte_lru.h | 85 -- lib/table/rte_lru_arm64.h | 61 - lib/table/rte_lru_x86.h | 96 -- lib/table/rte_table.h | 263 ----- lib/table/rte_table_acl.c | 795 ------------- lib/table/rte_table_acl.h | 65 -- lib/table/rte_table_array.c | 210 ---- lib/table/rte_table_array.h | 46 - lib/table/rte_table_hash.h | 106 -- lib/table/rte_table_hash_cuckoo.c | 327 ------ lib/table/rte_table_hash_cuckoo.h | 57 - lib/table/rte_table_hash_ext.c | 1011 ---------------- lib/table/rte_table_hash_func.h | 263 ----- lib/table/rte_table_hash_func_arm64.h | 21 - lib/table/rte_table_hash_key16.c | 1190 ------------------- lib/table/rte_table_hash_key32.c | 1223 -------------------- lib/table/rte_table_hash_key8.c | 1157 ------------------ lib/table/rte_table_hash_lru.c | 959 --------------- lib/table/rte_table_lpm.c | 369 ------ lib/table/rte_table_lpm.h | 94 -- lib/table/rte_table_lpm_ipv6.c | 370 ------ lib/table/rte_table_stub.c | 95 -- lib/table/rte_table_stub.h | 30 - lib/table/table_log.c | 7 - lib/table/table_log.h | 11 - 36 files changed, 4 insertions(+), 9763 deletions(-) delete mode 100644 doc/guides/prog_guide/img/figure33.png delete mode 100644 doc/guides/prog_guide/img/figure34.png delete mode 100644 doc/guides/prog_guide/img/figure35.png delete mode 100644 doc/guides/prog_guide/img/figure37.png delete mode 100644 doc/guides/prog_guide/img/figure38.png delete mode 100644 doc/guides/prog_guide/img/figure39.png delete mode 100644 lib/table/rte_lru.h delete mode 100644 lib/table/rte_lru_arm64.h delete mode 100644 lib/table/rte_lru_x86.h delete mode 100644 lib/table/rte_table.h delete mode 100644 lib/table/rte_table_acl.c delete mode 100644 lib/table/rte_table_acl.h delete mode 100644 lib/table/rte_table_array.c delete mode 100644 lib/table/rte_table_array.h delete mode 100644 lib/table/rte_table_hash.h delete mode 100644 lib/table/rte_table_hash_cuckoo.c delete mode 100644 lib/table/rte_table_hash_cuckoo.h delete mode 100644 lib/table/rte_table_hash_ext.c delete mode 100644 lib/table/rte_table_hash_func.h delete mode 100644 lib/table/rte_table_hash_func_arm64.h delete mode 100644 lib/table/rte_table_hash_key16.c delete mode 100644 lib/table/rte_table_hash_key32.c delete mode 100644 lib/table/rte_table_hash_key8.c delete mode 100644 lib/table/rte_table_hash_lru.c delete mode 100644 lib/table/rte_table_lpm.c delete mode 100644 lib/table/rte_table_lpm.h delete mode 100644 lib/table/rte_table_lpm_ipv6.c delete mode 100644 lib/table/rte_table_stub.c delete mode 100644 lib/table/rte_table_stub.h delete mode 100644 lib/table/table_log.c delete mode 100644 lib/table/table_log.h diff --git a/doc/api/doxy-api-index.md b/doc/api/doxy-api-index.md index 17c15027b8..760b770d6f 100644 --- a/doc/api/doxy-api-index.md +++ b/doc/api/doxy-api-index.md @@ -189,13 +189,6 @@ The public API headers are grouped by topics: [reass](@ref rte_port_ras.h), [sched](@ref rte_port_sched.h), [src/sink](@ref rte_port_source_sink.h) - * [table](@ref rte_table.h): - [lpm IPv4](@ref rte_table_lpm.h), - [lpm IPv6](@ref rte_table_lpm_ipv6.h), - [ACL](@ref rte_table_acl.h), - [hash](@ref rte_table_hash.h), - [array](@ref rte_table_array.h), - [stub](@ref rte_table_stub.h) * SWX pipeline: [control](@ref rte_swx_ctl.h), [extern](@ref rte_swx_extern.h), diff --git a/doc/guides/prog_guide/img/figure33.png b/doc/guides/prog_guide/img/figure33.png deleted file mode 100644 index f0670eb0053da643e1ee932ee3f30fdd0aa973e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65216 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 diff --git a/doc/guides/prog_guide/img/figure34.png b/doc/guides/prog_guide/img/figure34.png deleted file mode 100644 index caa2517a4013acbfc7c405866abfa180dbcacb63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11581 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 L_p;=sN=d7LfOI#~Lw5`% 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ɔC+_Oy;qd`wL#8gbt= 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%0A!SKi^b(OG6h# zUjmO&-*{mEP`##9ow#JBeyG3lbR@&z9=6(gMP6S1pGSJxckxq_D!{AYJFx1+^Apn* zi_j`OQ+bh9O>~2%9YxE6$_#2zRMc8$3u>gk|07UkBiq(2#hLa!j25?580}`9VP3UDPsMu$ktHbU_RPbxJa$W$vi9mvc;U;h4CulAR~!!-Z> zvtKel!xie-f|~0-G{avIAE(z7?&Ks{%jqDQuTo>^K6&yO$;f-DS^7SP(4+7N(<}lM zkVjcW9ps3*961_6kGfW8OXT>yN?G$mzfj8k6|p!N#g?n%8CSIGxHFIR8Ojp#ad7B@ zPSk@iEAZ!>_ro0d`?B`-0Q_G+iCfNG=B_KPU?Ff1&a!h*Sx&u)8 zyOwSOC8>0Zu$^R=PQCU6nqdSg8izJyrFbZ`7yt#aL^C1aQ{mo^N72$W8n3{}MXrf# z%2V?y3*=g1%;G}G9CRnk#%F+gS|sP>Y)vJ`Pa(JSMXKhpR#uL1Hb45h2!TF=!TF~u?uQ+> zJk$<-FE~chr;;Nr6@B4ZUvffB8(>+oruxd zo!L)e{@~(Z@ zk3{2Tw8!`dcccl4L>ilK;b&3`sz~&iX>b_}U7WPE$4$wGjvL4zadWMF`mQ^uty=e5 zyuduorar}vk=VKiMI9x8+KTQYhVpTZC8lVV|4mcBulcNsMl*b=?Q{amTQqk+A&v9?w=gQAiOq9H0>*fhAvAc=CGEEYd8}ED?HKI56 zt9lW?c%q?bTU@U8BQr}ISXsUylX@{K{-u8i!AmNzm(P%O;ZaOU<5Y>>b2FlS9&x=v z7C&KHf{f6>RIvZtZX7w*tG{ljct3Y{mX$9sLLYcb*JI1*vc-#-{*jgNuMxoNpaH8l zTLq+jMb34INJLxq4+hOx7_PMmlOm|6HtHdSE*&n66i` zb#zm@oqKdHGNu!Gu~mwr3D0xRF|*=z3Cho0cOQgEH(oR^o$A%}{{4k`S+-vg!NlPR zKrwGDhBU$dq=N%ezIby%8fx2b)Fg)2SA0qoG`$#CKYBWtsXa4O)G!p?%Iw3uLhnD8sJrp_y5Zgp01|#b9WzwaQat*=%o!dBr4jHxK`IChDeB z24zYmy0}9A8TsSfY9JDW!95p#)6n0D+wkvkR{v!@@KC48AksE3NVK%$QQPnxd#5m- zhMap(PB0Q%QWext1#Wx)FRYdfo3yn3_6ep|(up+XH_L6ifMC@gHq|^)S@H;6Ot8#y zW+Euy)d9XxJ)_x5fex|&T^!~((n`8a)p9q_h}T?>I%n3B&V4-YTN9~vzzSY9qL+05 z;#twi>}!_i$rDa9QO^pw9p`tZDQoRpv4bFdk4SCG7hSPS!e>}Hnz}*r99}ZhHM`o- zM_BC?ALKfxYFTI(?4`MPx)GNx;#MUN$`1s#%G6it_n8PwF)@-%LZg;U-8pWqd@3cM zZ_Tvr9gT04mr5_4XuU7pS?o|ip_x;76ig^FU2<#h)8*lLYKx2lV#^I#d*IdOfWbZ8(;F*1*4P6KxI|LWxgUO3C}1q{@oohcBac}~zM(Gy`Z6Zbsfyc#85 zVJ7U%col{7d-ZrWG!rW|9cthicR%L*Y3tL|fL5}fbGNPu;5w<1h_fiZq}ss(<7TK1 z;H)}i^4e)Mu}*GrY9?-SjtP0ihLi{J=d%jSEvmhGkzXbx0tBMaFgmDSd7-(7sSfS) zM?zky<<9C(64iI~=0*=2pwldiT1xi@)@ohyTi6&uoPI)AN14!Z{8ewT&-PY1!>aYc z-uit;EY3o*SG;QQvbfoXJcdtAD)|0Lx&&`N4-DMF3nyi9Tf){+Ph~%i`1+4`;#_kj4{(TT^3BIZ#V8tY^!l{f zD>EJ4*iHrKPoE#{A|7R3-SL9jr;e*GN%2tqDS5*}&we)v#!zz6^OeXT)M1DVSVD~y z-eydcb-@_>3}lYa1MP8mFQW|B!n~dHrWnLrt8bw_Ra5O)iFVsSx?tM?)8myEl7FlH zTmlPdy&dmSe&KDTqP*s{GiF)fFQYW|`QmX|msN)v_@DFv?f6`o`u+u3e8c)XUr#!# z%xNiON5y~1ntljEJ-EQnGg$=EQ)xm7O(R|wJ{co2Wx)h z^m~<7|6oH>H(*Sny1xX*tFWp!pb1n8F(|IN98Lu=$~xzG^jB)Lvf%yaTIE?>>H$|` zW?r+AqjnqxK$R==Wrc0g0*(7rUYre%5Tf|Yqob0g?$scL&GVFCo9qeXPa`L1 z|63Mk(EsfGL))W}|Bs}W+$}&88@Ti@UH#Ch#IbwUjieGMQnNTtTpLbS?nBCip0I+x z+3SON4$01dA*Wu%Htm^%Z zu+K=%KoTqTwtlT-@*UbuqGo$?t;pGH;X8$f&HcKw{^e_z0pI_E41?2qyZ!**@!#1$ z;fApe7O173m_vI0rkW+>@XGrRubH2REUYNomBbO<)5+xUk{WwqQS!IGPax}e35qx zs8k!tfYmbw(Rb}@V_`l$4QvJ$v&C#aVnYYSe;)gGmuIdgn97CBRVaQ8x>&`HgS}yD zwb%_gp{|xWvq3w(X`8pAObt^t{RWHJinT7t2`)ssY+VuV8#7JnNa=ZVBZd z$PuMM?y@*QHIGZ8dwpvwkcWrIX9H6f%?N0cp)REhh>Y2>%mCeM*}s}Q5eF?3^Ze10 zS+HFY zIu9Wy`J2DSRu^G|&_fuG1b!jyLz8Pw10;8SS6q}-pyMq?B* z%F674-V&HqO`-|Ws6!Gksy`^e0Ty=s_U;E6rO;BVjeA_uCCeaykX0#QV&4;fVl-p! zey-^cC@7P%Yi(Y(pOeJS1#A&k!a<^2oGCC$p!k!3`({WCZ1?jJ_;%2Zt;x5^=%h7N zO-J8G`R73ENWJ5(vjnHJL>l;1X@M{>)Y?NOu|sY)dzKF{>TPnJ0dFg6*FOC>KMM{C z`O^$|2IdZJLK{(gC6Dwl=iD=rp)=u|UuAC>?M5)OH>b?pRAWxTt6$QB+`Wel#^el9nc%N|lDqYOc$X`ul+YC9YKCS55xc zx%!Q2@)0NW6e1cQvuH%O1frGIbMw8Q)s4BbZ7Q^QH%g9S>k6 zo#uFSN5*tEIwq7copeJJTAtXP{d1~kuIglK<(v+q5n*pwKE?#Dgv-(OrAxxCx3sz5 z7U0vud`5t!Y{Ar({6z_>-Dub?5f_s znQ(?1><;M_8G_sXMsJ!SwXDvwIE?2WD_o@OpVqQqu7sL|hg(Nslh|D^8@d3fO|IFa z(zQHx1*Z%ktFfHf#{KPm$;wh;PInuVwC$RDY;+sqxCor|e3N?ANqGIA-IA!R<~7ys zcBsZ2{Zd{45aO|&WDSOIpGyPN5k>BWLKKX$bA>oAA0M(jc26suqRTK&}e|CDf zP^|gpESDRP2--O{BvJ!ZMP3g+J(JBWf}9=}uc~!N9I$j*SBCb$I@7Z1H&iqx($lql znE{dsm7xkUPYUcgM4K=7o;w(hXNlPqn_@Q)dJ^eLVgNuz5^3wyZ^i5Y?yS{8D}r9N zOnMd}I3Xg5SyflMy?N^v5-@uAIj*}xD1tmezbfa;D^!vj{TcWff7cLhr!qgl+qrX7 zmpuYN2Cph{ka2!;X&^_Ie@~A9lVlf2F8_xOgDmym`$yiN^RY#*sA2rlFZRFMl8LmI za)fgq^gXMn>;HMQBbLZ4^ta`|HU7;rVna=iFTQ$c;Ryg& zz0Q9WGcR~v1Hd&KEw%e5udUXn=|6Bm(z~|qJ_`xqykXC9+4fs3&4rem~E5A0pt*j8Q+hRJDk$&dj8=t0ht zYv&cP>MIIzaUFEwe29R@{r?*d0!K$jPc${V%Cvd_K$0_z9za*j&)eLSky-KDR?>a& z;DPH{K4b`n7;~Ab-Fnk9$q)2J&1I|3N0k;7^pvRcnPd%bYW27*lwzZF&7QbN1k?Df zY;5>2Deio*j!w4QoM{@mR`pU*tEs6eh%vt}%qb0!6bOrVYeJy z?n_%v9u!Oxx8Bgu(8%^EK3wWcyLg)aEr@{9N!IWj#TQ#PWM?4z`}MFi_JMRc?Hads z1#ej6?LSIa8R<*4s;B%yJg)PwlT4O!!mGi>EI%8_ahca6g!$gYz-+(QdFG|bdcN2( z-)0pyRdZ1IrvG|q$X%t!g*4^2oNm~DrD@0>oH4$ahF86?2EiYs=$_SL?%>}+G5^QI#aV( zUc+Z_$dWPptF~WpY!7q71-W2)3v47G^{K5FKCbEC-Pt)n;ZY~~AjeizOITE3qC}l#HdK_tpKmwGliqDq*Pe0Hh|K(MZ=BI!!RRKF!daZ>LHP{uLGg3-&cNJmYIbg>=n zt}<+R{}y-~=3Lb9Et8R7nyzxUruK+fK;keV{OE`wAwPEl3MaqYgD#FC-ZI!xRQ@_eZU_#1UvF*GodV682G=UWz+1V_OV8dv z1Aq`|R2sV=-{8u7hp7pK97|&rj40fCekO!G`M`Ew{;QpKzVQtADW;E^nuyiP`=6{; zMZaw28c*@1Crr;7ACovnaZ~iUwL{9yOYgZAiR0wp+iGvJdNz$8Wa?fG-7MA6v==l++ zBKDUUxd48K%~AgAa8SBBXwi0~Ls5BKhuq>DXRCUIiACUVJl1RAYzM1vemeF$*Irh~ zqd_Bg^V6P1)yCp1{0w`ia#j=T9b*k^%ERu%R}`Ix%gbg<%-Kx7NRhI8_jQ{y!>%AV zG2Y#q1%VNn^uT0ap+&{ivhtZ9_5xzM%I;NGv|Uy-20R3t0q3l5j3r;H9TCV}sAig3 z9v^)R{PCK;oYvD}7buel1FfVv+#(g!CWK_MqbkbM0FCX})?!qO&14&m4 z{^5|}zfreRN4|BHP=qXvCbf3#?2AS%Sv1jzpB}B+7n^PvIdDQA&3+GzvmW8d^=ej5 z1rZ&}5V{EvoT$NFyc1$MxY;gUzIJtzlSS<)eNMtQRjm zf8p5K+_(7b>nhmY#A4Z@?R>N_qV}Nuo_azUwW68h?c6{g>);cT*x3YDEnfY2yguSy zU{6UD)wQO9)t4FT<18-+^re#<8OaMOFZ}lt=c(EuZ!fQFY2-8f>!sA6}KvrpKm zSr$zvSY^yhjFa}Qn<)_77T`lnN3KQ)lmJEt1jK|ZdgzI}Vg$;IXUe@NuW^l582HM0im zvp8`BSOR5?08cH++^LbDJoZSPohfK$8n5S)_fbnI?tzL` z8I{c0w!dZU+N%g69WjT|DxReb1n@)MN`}_ReU=V7HSeIr)d$?B>&HZ&)D9}s17*E) z&z?SQDKal>>-!>m7rH^FGo2)nD0~eir+${{`iC%p^Pph-&8k%PvWb?MG|0MSM5&$4 zAL3Ot6}yvWo@)H?ezFm~O{Et~V$E5-_mJSKaFp&GyclcyeZRMQB~S;2{n(taJ3KMF z@$Ibn@4h&i27LwFd~3>(6AZ80Xu=JB<@5}E+CSXsJ@o{(iW0bR$#=>IrR+s`KATG@Z)>3?g4f^39BqSz|J(;f|4R0YZqoF?lgTmF3ykkuX$co+B z>9DJ`#LNCKXwcWNgbygaz6?eA)HICo!rO#!S|*KcMgP6p9&xM!%(?C91t3S1)+_C~ zqsiy1b~|5Ci}qkC$kMmX;5DAAOFEu(Laz+el~0Ox2o^oTqs+If(_y@Y`>JcB^f}|X zaSMWK$42pA@eN9;N6Nx#nF7LmxD6va<4Kpu_3iJTcxGFIAmgJZDSQD15U1V5NQ^QF zv032j4W**Zt{X0D+hT!A#}<*XS6>oiQ4{=JIx1MD8hJ*4pVf+**`{89GGyK~PrC|I z7?-r`|Ay6Rx~e-!KsW!3FkI-7=!nN_Ujcs?C@#UuOpp~fGq9Jpf061UN{ui4`jFCk zpo@{k!{dy-FIquezhwcz=aZEVr-s!P1I1Rh5tIy^!Z%{rM5CAZuen{(+tC9A)gRM> zvY)CJP2fGab87`r{NED{^Y7-S^Yk&T`|+oy{k-!hFOh#OoiYSgQF)UwRi6T$}@0`=gfV9rm{I z70-w!&haOnx{0QCnU>qPz3G5kcWYNxSKXb)i@h6s)}xcWRfn!`n#zAf|0$WdsCDTC z5*@p^8!5YoG6Ki#+14EquMD|3Pyp+TGya=#VeeiAgmVk?t-j@S{apf%+c?TWsMkb{ zosPzQY>XKR+@JRS<)=$Nc#`bJCL@*14pJYx)-ApnU)hexwDd&j^3kSK*}HKej|AA* zVgCL|pnWCS@=Z;7Pdk}_r_Fe#Hj)kKL47hKMTZB@zGEPob5gF?AMg&A`}0XY;Goo+ z3IGPbc$w8{CGa1j%v&WuWI-{cJXj&L+42vsuGt?`Op~bq`18LsHaYy?lKbC>LlXJH zO_rMfu~H~1KqA}IVMNTvw~sfA`;$2k{CaY$x9-`ry+QXWk2i{C#GpUDIt!BN5<~5I>U1C4rtjQs~B5` zb=^hi^3I3;LvXMhcfQsl0avg4Wx-I_bcy=_Zawc%$F?{?gv=)v+Gah!0P54DolBri zKhE2i->)7a;lM-pefK8(1NQS@AAODJC#}WT9w4d!K;zH{lPh5{ zFLIfiFR4KO6!-zrRp2QOlNF^u{Rd(DZUhn?0%!8w>~;~IK~=Z|mo<|jC+&t@X9%p6 zyS_v`dU-5ozjjUpd%H6%rB!k38d@4cgw&bwVJBNFarKnZgR}k7%h(f31x<^;#8Tmx zRMe-Ue?hwS%S|wqu1cbL-X%_Hg(eAo4GZCltAVy_moCpEY1(i><`j2q{KlV)pl%iI zLe?sbbXiK*+Kks*xV~kVW470(g+e3XPHAl>U>zZ?>Si`MwsJJZMKtSf2BtTHF{Kk% zrHfw?ve->7w55PZxxSK6Q1~S$C-=nKI@3D*?W=`}`-x+^#Z=B`6QR$i1M*`rd@Ym= z)VK0;BE04>>aV*Qu)Oy{wUtRfYSP&T+UKahrQwpPN4jy%xMDGGSU;|j+bLb}@=)0) z=fEmDJncp9!4MOn@~3is8<6i~vX%oDDdV!Ul(BWoU$-^)Iy|=@STE^Z{9MsbQkgLc zg3JsXS+Uv+8$SrG#x*QKs{z~Ao!5ltmR~tEVno~VPO3;Fbvhalogg_2CFiBLO%C{q z(w>0Qf3{-6)o6RLo9~i~?yy!{S%46hiP5`BQKu9J>JjzbJQIgUDgrbI)(C;*JT-(b>&=#VY&vN$QnzLt-!R%gDkF!D=J1x z)E|_dY*u<*D!9$Zq$gTnr!x%o*fpnxc*rBJpnKQa5|a9}=n{svKbdp}Z)=~(6!DpX z3JNpi5E))#_?&3tzc0r?K8roN(2<}Yw=CYsF_0iym+DkEp>lr{xo>t`6?pGh0qZjh zx;F3te{fiQccHGVQ&(QY#I(JTL4FoQj4=3KJ)^9fef{P-G(6jm1~ffA?ds(vnTl-v zqAaU)+50jd6VUMc&Bxl@-yzIS-K;LiE)Z7AN-WZHBE)g9mKRc;$YBS%iu_ajCtYM4 z>c%4JbetE=DNb-l+FrC*!^85g?F?q!+k%IZ;qDybzc4W)`}W#eWA#8!?IM{g&n}bO}`r*>d*%y6b_?`JuDj^MyVoo3Y;xeoQvur z(0pcHF`at$R*$q38S`};9#x>liMTK%iK za^B^l`T${00jZ)!5uUr1eH`!Av0ZBP5+u?fE=hhLE(%wM&p$sjT`Ta8=d`SJ2A*Q> zi37wNMdc`4l|>nmS(qM%_XfS%&g3%~(JN@YEKwTGhK4_H$KZFY`X~DO@nnMtWu1^2su;deFMwT2Ht>}D?A3C|zJx!+(yOMy zr7IUbg}gL6To9cAe(^*h-?h`hIQvfUOcLkxhFPcB542*{S7=1k%)9)@N`avb-gqg) z6wP$>y$6hivF!O#$1j7y;Qg%L4Ep0`ztnRdi!e-dcXKNVrcTYMQe*XZQbl}+Z|i>z zd0|RazCx9n0-H<3fBjrvk7E$~Wm?&d!D45zl)Cyg(1rK1_MoIa!(UvUAusZ1ZlRJA zJ-e1>I_XbI5zBg?m5oY|J>MN`?;9DrPj)U(UTkgKFmpKT|MWdGZg|9^<| zUm5~(8oO>aYxW0&KE9hJHAj4is%iImfvNCgo*10sAaTayPY$|w_N4dT6nzQTo>M4( zO!1J?ckz3&x*fMY?Ksxf=D!ueloRb2|3y#`g}r-T6QK@iYj?VG150Ud<0d03wq0^E zRj1svWJW3K-1f7~!STsbsL!j(!~S#y(jg8-6hKiF*$OdozNTR1&LjX?;S2?zr1AK= zK5z0wo-It_>`tSkuh5vLf-i==4d&QHu0gTc4BZ39uCiLfzDEJTpa|JfL4;(LbijSn ze3W?14oohJz^m6t<<{uI4XS?cjD@M*GR(B#UxTx)r~I6!-|Ha0$)Kn9*4s*pAY9J= z*lep(JT8_JYedCBeWj=QRe*A8Qg4)UChQ}CV88qXP`T(+94{P@e*s_7HeL+ZrS`sQ zJ4>3ECq+x>fW8JZQ~!-JK;#8t_B9E~I!zj+vlURM7q+X8)vS|3Lnv6lB`<`x@@Qe} zq|ExOCt&YIHh&Fx3myh7M{Z5M!HA@aq^pY(ph;Cs+`%*8jHD!HJ&RvehT5YOd)?!C zsIgnZwa~nQ99&gXuKAv2n^o9z?GJ%&O>TdxG!$v@er$f2{DCd@SuD6i`t*JRv{>{U z1)jHPcaLLTlXYv+TA^7lVJ{ti=2k>-bQoWA<}^e-Q3Tt7lV?giovpjIYpVW465=&O zW2zfSoeQ=K!$yfaO}kAyq#fCD(#sPla+&3C(X)cr7Mc3Tm4Pe1eRNlMtSpz0B3gE8 zI<_?&=FKKkO*m>-YDV4AGh|FODXr>cO|nMLN{~q8Q04RPt2M8;o}GyRmG>a8AVX65 zERn3;PN2^1EpP)JTcRT?@o=s7kEOP7qm>hD@cM|kcu#uT4?n?2i|p}~l+ z*xsUAt;o8caf^yV^zodX=*C)2r>CL*+a|14L7=&trf})~W7Gw=PFSw1e`~^-lF8nETarbhfuqaxn3Us}jl0s)!;;4VV zmMLx2k!N_1ZOUAG^kl!YCp&|a{&rXbG6VplPU8j!5b z4vz^94HcB4118fU`7W2sz`fn>R2;#Ev6b#uIcf}w1I3SMDRdOU$ut6McZKX9RwE)8 zl-h(I?HWaYMrlL{8Qy5JA_!eJ)YmfA3B1GAHxVSfDv_UfnsgMm^Ed+I0Ea3FoRY}f zg@yGWGt6bC@%!%n{;?D=*z9}~hWg8*V~@zxxh<0aPQc-JIo=G)-t_C_wQD9{PLdbI zKsf8E3TrU6YEpB~T>fY6lIFB#q!F?$5iOadUAcU3OQCz_F8l58o8}JUx^*lJ-#;fm zpED5(9Iq^=0^;na=AONNJt6wIx|flW@#9r$#QN$U#xecUh}H0hNn#Qk%5?wd1TExk zJFXnOv6!Ropo}N-m^QBalq~){6L8?k{|OG%5~Y%^s_gJLw5^CVgV7K}2txNagwN}AL^2-4cSH?qm|UQ4U|@!pG}mwL_d zcs?vAAXy&P8bKN3x4l%y$SGaBvEhnk112Nms7kdGo0<0{(I}`WKL6?D+OWq4MAmFk zZYmk?&{7pf=I~>9T1iPDCd}!0IERHdaHer0Jxc`f&Ir*t(>|l0FpT~LccvHo)Fg?= zxeOrVBY_~52I@YKBYQJ?m@gn|0ZUKS}LIIXa;;s5|OOq^WkLa{hb55!Q?LPis6 z4n^TGK9RWsNpIaVW;I0_za3r*l?R`_v7W}s#}Vc)20BuuoU-eTvfF}l#t8(@B3b}p zzBk+3#hLtxmii7+{qKV>)`k?j+UR@e;TBcJ8$|XSrNAKZj=z}W+YEZZ-cNfAGp78e z!kr#&`m<=9H@@=DF4TVN!m2I+)z%)M{%>1<82;%WUmrkS|0jq3Ku`Y9g?!chM`6s- z8FSMtlf3Vo)&Ef0qo$dMoj@Za|*`)cEiLSpFt4)c$I<@4rR>UwI$ Is!zlI3v^8)F8}}l diff --git a/doc/guides/prog_guide/img/figure39.png b/doc/guides/prog_guide/img/figure39.png deleted file mode 100644 index d2db6a499a0f764b9e661b905b0f136ea871b457..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55986 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%0AVlC!xcL8)rfC`#0!um);sqTLXD>E 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 uxA&_N> mask) & 1; - - match_many = (0xFEE8LLU >> mask) & 1; - - match_pos = (0x12131210LLU >> (mask << 1)) & 3; - -Single Key Size Hash Tables -""""""""""""""""""""""""""" - -:numref:`packet_framework_figure_37`, :numref:`packet_framework_figure_38`, -:numref:`packet_framework_table_qos_30` and :numref:`packet_framework_table_qos_31` -detail the main data structures used to implement 8-byte and 16-byte key hash tables -(either LRU or extendable bucket, either with pre-computed signature or "do-sig"). - -.. _packet_framework_figure_37: - -.. figure:: img/figure37.* - - Data Structures for 8-byte Key Hash Tables - - -.. _packet_framework_figure_38: - -.. figure:: img/figure38.* - - Data Structures for 16-byte Key Hash Tables - - -.. _packet_framework_table_qos_30: - -.. table:: Main Large Data Structures (Arrays) used for 8-byte and 16-byte Key Size Hash Tables - - +---+-------------------------+------------------------------+----------------------+------------------------------------+ - | # | Array name | Number of entries | Entry size (bytes) | Description | - | | | | | | - +===+=========================+==============================+======================+====================================+ - | 1 | Bucket array | n_buckets (configurable) | *8-byte key size:* | Buckets of the hash table. | - | | | | | | - | | | | 64 + 4 x entry_size | | - | | | | | | - | | | | | | - | | | | *16-byte key size:* | | - | | | | | | - | | | | 128 + 4 x entry_size | | - | | | | | | - +---+-------------------------+------------------------------+----------------------+------------------------------------+ - | 2 | Bucket extensions array | n_buckets_ext (configurable) | *8-byte key size:* | This array is only created for | - | | | | | extendable bucket tables. | - | | | | | | - | | | | 64 + 4 x entry_size | | - | | | | | | - | | | | | | - | | | | *16-byte key size:* | | - | | | | | | - | | | | 128 + 4 x entry_size | | - | | | | | | - +---+-------------------------+------------------------------+----------------------+------------------------------------+ - -.. _packet_framework_table_qos_31: - -.. table:: Field Description for Bucket Array Entry (8-byte and 16-byte Key Hash Tables) - - +---+---------------+--------------------+-------------------------------------------------------------------------------+ - | # | Field name | Field size (bytes) | Description | - | | | | | - +===+===============+====================+===============================================================================+ - | 1 | Valid | 8 | Bit X (X = 0 .. 3) is set to 1 if key X is valid or to 0 otherwise. | - | | | | | - | | | | Bit 4 is only used for extendable bucket tables to help with the | - | | | | implementation of the branchless logic. In this case, bit 4 is set to 1 if | - | | | | next pointer is valid (not NULL) or to 0 otherwise. | - | | | | | - +---+---------------+--------------------+-------------------------------------------------------------------------------+ - | 2 | Next Ptr/LRU | 8 | For LRU tables, this fields represents the LRU list for the current bucket | - | | | | stored as array of 4 entries of 2 bytes each. Entry 0 stores the index | - | | | | (0 .. 3) of the MRU key, while entry 3 stores the index of the LRU key. | - | | | | | - | | | | For extendable bucket tables, this field represents the next pointer (i.e. | - | | | | the pointer to the next group of 4 keys linked to the current bucket). The | - | | | | next pointer is not NULL if the bucket is currently extended or NULL | - | | | | otherwise. | - | | | | | - +---+---------------+--------------------+-------------------------------------------------------------------------------+ - | 3 | Key [0 .. 3] | 4 x key_size | Full keys. | - | | | | | - +---+---------------+--------------------+-------------------------------------------------------------------------------+ - | 4 | Data [0 .. 3] | 4 x entry_size | Full key values (key data) associated with keys 0 .. 3. | - | | | | | - +---+---------------+--------------------+-------------------------------------------------------------------------------+ - -and detail the bucket search pipeline used to implement 8-byte and 16-byte key hash tables (either LRU or extendable bucket, -either with pre-computed signature or "do-sig"). -For each pipeline stage, the described operations are applied to each of the two packets handled by that stage. - -.. figure:: img/figure39.* - - Bucket Search Pipeline for Key Lookup Operation (Single Key Size Hash - Tables) - -.. table:: Description of the Bucket Search Pipeline Stages (8-byte and 16-byte Key Hash Tables) - - +---+---------------------------+-----------------------------------------------------------------------------+ - | # | Stage name | Description | - | | | | - +===+===========================+=============================================================================+ - | 0 | Prefetch packet meta-data | #. Select next two packets from the burst of input packets. | - | | | | - | | | #. Prefetch packet meta-data containing the key and key signature. | - | | | | - +---+---------------------------+-----------------------------------------------------------------------------+ - | 1 | Prefetch table bucket | #. Read the key signature from the packet meta-data (for extendable bucket | - | | | hash tables) or read the key from the packet meta-data and compute key | - | | | signature (for LRU tables). | - | | | | - | | | #. Identify the bucket ID using the key signature. | - | | | | - | | | #. Prefetch the bucket. | - | | | | - +---+---------------------------+-----------------------------------------------------------------------------+ - | 2 | Prefetch table data | #. Read the bucket. | - | | | | - | | | #. Compare all 4 bucket keys against the input key. | - | | | | - | | | #. Report input key as lookup hit only when a match is identified (more | - | | | than one key match is not possible) | - | | | | - | | | #. For LRU tables only, use branchless logic to update the bucket LRU list | - | | | (the current key becomes the new MRU) only on lookup hit. | - | | | | - | | | #. Prefetch the key value (key data) associated with the matched key (to | - | | | avoid branches, this is done on both lookup hit and miss). | - | | | | - +---+---------------------------+-----------------------------------------------------------------------------+ - -Additional notes: - -#. The pipelined version of the bucket search algorithm is executed only if there are at least 5 packets in the burst of input packets. - If there are less than 5 packets in the burst of input packets, a non-optimized implementation of the bucket search algorithm is executed. - -#. For extendable bucket hash tables only, - once the pipelined version of the bucket search algorithm has been executed for all the packets in the burst of input packets, - the non-optimized implementation of the bucket search algorithm is also executed for any packets that did not produce a lookup hit, - but have the bucket in extended state. - As result of executing the non-optimized version, some of these packets may produce a lookup hit or lookup miss. - This does not impact the performance of the key lookup operation, - as the probability of having the bucket in extended state is relatively small. - The Software Switch (SWX) Pipeline ---------------------------------- diff --git a/doc/guides/rel_notes/deprecation.rst b/doc/guides/rel_notes/deprecation.rst index 758652a492..9dcf86098f 100644 --- a/doc/guides/rel_notes/deprecation.rst +++ b/doc/guides/rel_notes/deprecation.rst @@ -148,11 +148,6 @@ Deprecation Notices The graph walk functions will process nodes in topological order using bitmap scanning instead of the circular buffer. -* table: The table library legacy API (functions rte_table_*) - will be deprecated and subsequently removed in DPDK 24.11 release. - Before this, the new table library API (functions rte_swx_table_*) - will gradually transition from experimental to stable status. - * port: The port library legacy API (functions rte_port_*) will be deprecated and subsequently removed in DPDK 24.11 release. Before this, the new port library API (functions rte_swx_port_*) diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst index f7d34aa1ee..50b0d3ac06 100644 --- a/doc/guides/rel_notes/release_26_11.rst +++ b/doc/guides/rel_notes/release_26_11.rst @@ -88,6 +88,9 @@ Removed Items ``rte_port_in_action_*`` and ``rte_table_action_*`` functions. The SWX pipeline API (``rte_swx_pipeline_*``) remains. +* Removed the legacy table library API (``rte_table_*`` functions). + The SWX table API (``rte_swx_table_*``) remains. + API Changes ----------- diff --git a/lib/table/meson.build b/lib/table/meson.build index e27957fe89..620c20e594 100644 --- a/lib/table/meson.build +++ b/lib/table/meson.build @@ -9,42 +9,14 @@ sources = files( 'rte_swx_table_learner.c', 'rte_swx_table_selector.c', 'rte_swx_table_wm.c', - 'rte_table_acl.c', - 'rte_table_array.c', - 'rte_table_hash_cuckoo.c', - 'rte_table_hash_ext.c', - 'rte_table_hash_key8.c', - 'rte_table_hash_key16.c', - 'rte_table_hash_key32.c', - 'rte_table_hash_lru.c', - 'rte_table_lpm.c', - 'rte_table_lpm_ipv6.c', - 'rte_table_stub.c', - 'table_log.c', ) headers = files( - 'rte_lru.h', 'rte_swx_hash_func.h', 'rte_swx_table.h', 'rte_swx_table_em.h', 'rte_swx_table_learner.h', 'rte_swx_table_selector.h', 'rte_swx_table_wm.h', - 'rte_table.h', - 'rte_table_acl.h', - 'rte_table_array.h', - 'rte_table_hash.h', - 'rte_table_hash_cuckoo.h', - 'rte_table_hash_func.h', - 'rte_table_lpm.h', - 'rte_table_lpm_ipv6.h', - 'rte_table_stub.h', -) -deps += ['mbuf', 'port', 'lpm', 'hash', 'acl'] - -indirect_headers += files( - 'rte_lru_arm64.h', - 'rte_lru_x86.h', - 'rte_table_hash_func_arm64.h', ) +deps += ['mbuf', 'hash', 'acl'] diff --git a/lib/table/rte_lru.h b/lib/table/rte_lru.h deleted file mode 100644 index 1436425e16..0000000000 --- a/lib/table/rte_lru.h +++ /dev/null @@ -1,85 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2014 Intel Corporation - */ - -#ifndef __INCLUDE_RTE_LRU_H__ -#define __INCLUDE_RTE_LRU_H__ - -#include -#ifdef RTE_ARCH_X86_64 -#include "rte_lru_x86.h" -#elif defined(RTE_ARCH_ARM64) -#include "rte_lru_arm64.h" -#else -#undef RTE_TABLE_HASH_LRU_STRATEGY -#define RTE_TABLE_HASH_LRU_STRATEGY 1 -#endif - -#if RTE_TABLE_HASH_LRU_STRATEGY == 0 - -#define lru_init(bucket) \ -do \ - bucket = bucket; \ -while (0) - -#define lru_pos(bucket) (bucket->lru_list & 0xFFFFLLU) - -#define lru_update(bucket, mru_val) \ -do { \ - bucket = bucket; \ - mru_val = mru_val; \ -} while (0) - -#elif RTE_TABLE_HASH_LRU_STRATEGY == 1 - -#define lru_init(bucket) \ -do \ - bucket->lru_list = 0x0000000100020003LLU; \ -while (0) - -#define lru_pos(bucket) (bucket->lru_list & 0xFFFFLLU) - -#define lru_update(bucket, mru_val) \ -do { \ - uint64_t _x, _pos, _x0, _x1, _x2, _mask; \ - \ - _x = bucket->lru_list; \ - \ - _pos = 4; \ - if ((_x >> 48) == ((uint64_t) mru_val)) \ - _pos = 3; \ - \ - if (((_x >> 32) & 0xFFFFLLU) == ((uint64_t) mru_val)) \ - _pos = 2; \ - \ - if (((_x >> 16) & 0xFFFFLLU) == ((uint64_t) mru_val)) \ - _pos = 1; \ - \ - if ((_x & 0xFFFFLLU) == ((uint64_t) mru_val)) \ - _pos = 0; \ - \ - \ - _pos <<= 4; \ - _mask = (~0LLU) << _pos; \ - _x0 = _x & (~_mask); \ - _x1 = (_x >> 16) & _mask; \ - _x2 = (_x << (48 - _pos)) & (0xFFFFLLU << 48); \ - _x = _x0 | _x1 | _x2; \ - \ - if (_pos != 64) \ - bucket->lru_list = _x; \ -} while (0) - -#elif (RTE_TABLE_HASH_LRU_STRATEGY == 2) || (RTE_TABLE_HASH_LRU_STRATEGY == 3) - -/** - * These strategies are implemented in architecture specific header files. - */ - -#else - -#error "Incorrect value for RTE_TABLE_HASH_LRU_STRATEGY" - -#endif - -#endif diff --git a/lib/table/rte_lru_arm64.h b/lib/table/rte_lru_arm64.h deleted file mode 100644 index 817b791b6e..0000000000 --- a/lib/table/rte_lru_arm64.h +++ /dev/null @@ -1,61 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2017 Cavium, Inc - */ - -#ifndef __RTE_LRU_ARM64_H__ -#define __RTE_LRU_ARM64_H__ - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef RTE_TABLE_HASH_LRU_STRATEGY -#ifdef __ARM_NEON -#define RTE_TABLE_HASH_LRU_STRATEGY 3 -#else /* if no NEON, use simple scalar version */ -#define RTE_TABLE_HASH_LRU_STRATEGY 1 -#endif -#endif - -#if RTE_TABLE_HASH_LRU_STRATEGY == 3 - -#define lru_init(bucket) \ - { bucket->lru_list = ~0LLU; } - -static inline int -f_lru_pos(uint64_t lru_list) -{ - /* Compare the vector to zero vector */ - uint16x4_t lru_vec = vld1_u16((uint16_t *)&lru_list); - uint16x4_t min_vec = vmov_n_u16(vminv_u16(lru_vec)); - uint64_t mask = vget_lane_u64(vreinterpret_u64_u16( - vceq_u16(min_vec, lru_vec)), 0); - return rte_clz64(mask) >> 4; -} -#define lru_pos(bucket) f_lru_pos(bucket->lru_list) - -#define lru_update(bucket, mru_val) \ -do { \ - const uint64_t _orvals[] = {0xFFFFLLU, 0xFFFFLLU << 16, \ - 0xFFFFLLU << 32, 0xFFFFLLU << 48, 0LLU}; \ - const uint64_t _decs[] = {0x1000100010001LLU, 0}; \ - uint64x1_t _lru = vdup_n_u64(bucket->lru_list); \ - uint64x1_t _vdec = vdup_n_u64(_decs[mru_val>>2]); \ - bucket->lru_list = vget_lane_u64(vreinterpret_u64_u16( \ - vsub_u16(vreinterpret_u16_u64(_lru), \ - vreinterpret_u16_u64(_vdec))), \ - 0); \ - bucket->lru_list |= _orvals[mru_val]; \ -} while (0) - -#endif - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/lib/table/rte_lru_x86.h b/lib/table/rte_lru_x86.h deleted file mode 100644 index de74513653..0000000000 --- a/lib/table/rte_lru_x86.h +++ /dev/null @@ -1,96 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2014 Intel Corporation - */ - -#ifndef __INCLUDE_RTE_LRU_X86_H__ -#define __INCLUDE_RTE_LRU_X86_H__ - -#include - -#include -#include - -#ifndef RTE_TABLE_HASH_LRU_STRATEGY -#define RTE_TABLE_HASH_LRU_STRATEGY 2 -#endif - -#if RTE_TABLE_HASH_LRU_STRATEGY == 2 - -#if RTE_CC_IS_GNU -#include -#else -#include -#include -#include -#endif - -#define lru_init(bucket) \ - { bucket->lru_list = 0x0000000100020003LLU; } - -#define lru_pos(bucket) (bucket->lru_list & 0xFFFFLLU) - -#define lru_update(bucket, mru_val) \ -do { \ - /* set up the masks for all possible shuffles, depends on pos */\ - static uint64_t masks[10] = { \ - /* Shuffle order; Make Zero (see _mm_shuffle_epi8 manual) */\ - 0x0100070605040302, 0x8080808080808080, \ - 0x0302070605040100, 0x8080808080808080, \ - 0x0504070603020100, 0x8080808080808080, \ - 0x0706050403020100, 0x8080808080808080, \ - 0x0706050403020100, 0x8080808080808080}; \ - /* load up one register with repeats of mru-val */ \ - uint64_t mru2 = mru_val; \ - uint64_t mru3 = mru2 | (mru2 << 16); \ - uint64_t lru = bucket->lru_list; \ - /* XOR to cause the word we're looking for to go to zero */ \ - uint64_t mru = lru ^ ((mru3 << 32) | mru3); \ - __m128i c = _mm_cvtsi64_si128(mru); \ - __m128i b = _mm_cvtsi64_si128(lru); \ - /* Find the minimum value (first zero word, if it's in there) */\ - __m128i d = _mm_minpos_epu16(c); \ - /* Second word is the index to found word (first word is the value) */\ - unsigned int _pos = _mm_extract_epi16(d, 1); \ - /* move the recently used location to top of list */ \ - __m128i k = _mm_shuffle_epi8(b, *((__m128i *) &masks[2 * _pos]));\ - /* Finally, update the original list with the reordered data */ \ - bucket->lru_list = _mm_extract_epi64(k, 0); \ - /* Phwew! */ \ -} while (0) - -#elif RTE_TABLE_HASH_LRU_STRATEGY == 3 - -#if RTE_CC_IS_GNU -#include -#else -#include -#include -#include -#endif - -#define lru_init(bucket) \ - { bucket->lru_list = ~0LLU; } - -static inline int -f_lru_pos(uint64_t lru_list) -{ - __m128i lst = _mm_set_epi64x((uint64_t)-1, lru_list); - __m128i min = _mm_minpos_epu16(lst); - return _mm_extract_epi16(min, 1); -} -#define lru_pos(bucket) f_lru_pos(bucket->lru_list) - -#define lru_update(bucket, mru_val) \ -do { \ - const uint64_t orvals[] = {0xFFFFLLU, 0xFFFFLLU << 16, \ - 0xFFFFLLU << 32, 0xFFFFLLU << 48, 0LLU}; \ - const uint64_t decs[] = {0x1000100010001LLU, 0}; \ - __m128i lru = _mm_cvtsi64_si128(bucket->lru_list); \ - __m128i vdec = _mm_cvtsi64_si128(decs[mru_val>>2]); \ - lru = _mm_subs_epu16(lru, vdec); \ - bucket->lru_list = _mm_extract_epi64(lru, 0) | orvals[mru_val]; \ -} while (0) - -#endif - -#endif diff --git a/lib/table/rte_table.h b/lib/table/rte_table.h deleted file mode 100644 index 2743070b32..0000000000 --- a/lib/table/rte_table.h +++ /dev/null @@ -1,263 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2014 Intel Corporation - */ - -#ifndef __INCLUDE_RTE_TABLE_H__ -#define __INCLUDE_RTE_TABLE_H__ - -/** - * @file - * RTE Table - * - * This tool is part of the DPDK Packet Framework tool suite and provides - * a standard interface to implement different types of lookup tables for data - * plane processing. - * - * Virtually any search algorithm that can uniquely associate data to a lookup - * key can be fitted under this lookup table abstraction. For the flow table - * use-case, the lookup key is an n-tuple of packet fields that uniquely - * identifies a traffic flow, while data represents actions and action - * meta-data associated with the same traffic flow. - */ - -#include -#include - -struct rte_mbuf; - -/** Lookup table statistics */ -struct rte_table_stats { - uint64_t n_pkts_in; - uint64_t n_pkts_lookup_miss; -}; - -/** - * Lookup table create - * - * @param params - * Parameters for lookup table creation. The underlying data structure is - * different for each lookup table type. - * @param socket_id - * CPU socket ID (e.g. for memory allocation purpose) - * @param entry_size - * Data size of each lookup table entry (measured in bytes) - * @return - * Handle to lookup table instance - */ -typedef void* (*rte_table_op_create)(void *params, int socket_id, - uint32_t entry_size); - -/** - * Lookup table free - * - * @param table - * Handle to lookup table instance - * @return - * 0 on success, error code otherwise - */ -typedef int (*rte_table_op_free)(void *table); - -/** - * Lookup table entry add - * - * @param table - * Handle to lookup table instance - * @param key - * Lookup key - * @param entry - * Data to be associated with the current key. This parameter has to point to - * a valid memory buffer where the first entry_size bytes (table create - * parameter) are populated with the data. - * @param key_found - * After successful invocation, *key_found is set to a value different than 0 - * if the current key is already present in the table and to 0 if not. This - * pointer has to be set to a valid memory location before the table entry add - * function is called. - * @param entry_ptr - * After successful invocation, *entry_ptr stores the handle to the table - * entry containing the data associated with the current key. This handle can - * be used to perform further read-write accesses to this entry. This handle - * is valid until the key is deleted from the table or the same key is - * re-added to the table, typically to associate it with different data. This - * pointer has to be set to a valid memory location before the function is - * called. - * @return - * 0 on success, error code otherwise - */ -typedef int (*rte_table_op_entry_add)( - void *table, - void *key, - void *entry, - int *key_found, - void **entry_ptr); - -/** - * Lookup table entry delete - * - * @param table - * Handle to lookup table instance - * @param key - * Lookup key - * @param key_found - * After successful invocation, *key_found is set to a value different than 0 - * if the current key was present in the table before the delete operation - * was performed and to 0 if not. This pointer has to be set to a valid - * memory location before the table entry delete function is called. - * @param entry - * After successful invocation, if the key is found in the table (*key found - * is different than 0 after function call is completed) and entry points to - * a valid buffer (entry is set to a value different than NULL before the - * function is called), then the first entry_size bytes (table create - * parameter) in *entry store a copy of table entry that contained the data - * associated with the current key before the key was deleted. - * @return - * 0 on success, error code otherwise - */ -typedef int (*rte_table_op_entry_delete)( - void *table, - void *key, - int *key_found, - void *entry); - -/** - * Lookup table entry add bulk - * - * @param table - * Handle to lookup table instance - * @param keys - * Array containing lookup keys - * @param entries - * Array containing data to be associated with each key. Every item in the - * array has to point to a valid memory buffer where the first entry_size - * bytes (table create parameter) are populated with the data. - * @param n_keys - * Number of keys to add - * @param key_found - * After successful invocation, key_found for every item in the array is set - * to a value different than 0 if the current key is already present in the - * table and to 0 if not. This pointer has to be set to a valid memory - * location before the table entry add function is called. - * @param entries_ptr - * After successful invocation, array *entries_ptr stores the handle to the - * table entry containing the data associated with every key. This handle can - * be used to perform further read-write accesses to this entry. This handle - * is valid until the key is deleted from the table or the same key is - * re-added to the table, typically to associate it with different data. This - * pointer has to be set to a valid memory location before the function is - * called. - * @return - * 0 on success, error code otherwise - */ -typedef int (*rte_table_op_entry_add_bulk)( - void *table, - void **keys, - void **entries, - uint32_t n_keys, - int *key_found, - void **entries_ptr); - -/** - * Lookup table entry delete bulk - * - * @param table - * Handle to lookup table instance - * @param keys - * Array containing lookup keys - * @param n_keys - * Number of keys to delete - * @param key_found - * After successful invocation, key_found for every item in the array is set - * to a value different than 0if the current key was present in the table - * before the delete operation was performed and to 0 if not. This pointer - * has to be set to a valid memory location before the table entry delete - * function is called. - * @param entries - * If entries pointer is NULL, this pointer is ignored for every entry found. - * Else, after successful invocation, if specific key is found in the table - * (key_found is different than 0 for this item after function call is - * completed) and item of entry array points to a valid buffer (entry is set - * to a value different than NULL before the function is called), then the - * first entry_size bytes (table create parameter) in *entry store a copy of - * table entry that contained the data associated with the current key before - * the key was deleted. - * @return - * 0 on success, error code otherwise - */ -typedef int (*rte_table_op_entry_delete_bulk)( - void *table, - void **keys, - uint32_t n_keys, - int *key_found, - void **entries); - -/** - * Lookup table lookup - * - * @param table - * Handle to lookup table instance - * @param pkts - * Burst of input packets specified as array of up to 64 pointers to struct - * rte_mbuf - * @param pkts_mask - * 64-bit bitmask specifying which packets in the input burst are valid. When - * pkts_mask bit n is set, then element n of pkts array is pointing to a - * valid packet. Otherwise, element n of pkts array does not point to a valid - * packet, therefore it will not be accessed. - * @param lookup_hit_mask - * Once the table lookup operation is completed, this 64-bit bitmask - * specifies which of the valid packets in the input burst resulted in lookup - * hit. For each valid input packet (pkts_mask bit n is set), the following - * are true on lookup hit: lookup_hit_mask bit n is set, element n of entries - * array is valid and it points to the lookup table entry that was hit. For - * each valid input packet (pkts_mask bit n is set), the following are true - * on lookup miss: lookup_hit_mask bit n is not set and element n of entries - * array is not valid. - * @param entries - * Once the table lookup operation is completed, this array provides the - * lookup table entries that were hit, as described above. It is required - * that this array is always pre-allocated by the caller of this function - * with exactly 64 elements. The implementation is allowed to speculatively - * modify the elements of this array, so elements marked as invalid in - * lookup_hit_mask once the table lookup operation is completed might have - * been modified by this function. - * @return - * 0 on success, error code otherwise - */ -typedef int (*rte_table_op_lookup)( - void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries); - -/** - * Lookup table stats read - * - * @param table - * Handle to lookup table instance - * @param stats - * Handle to table stats struct to copy data - * @param clear - * Flag indicating that stats should be cleared after read - * - * @return - * Error code or 0 on success. - */ -typedef int (*rte_table_op_stats_read)( - void *table, - struct rte_table_stats *stats, - int clear); - -/** Lookup table interface defining the lookup table operation */ -struct rte_table_ops { - rte_table_op_create f_create; /**< Create */ - rte_table_op_free f_free; /**< Free */ - rte_table_op_entry_add f_add; /**< Entry add */ - rte_table_op_entry_delete f_delete; /**< Entry delete */ - rte_table_op_entry_add_bulk f_add_bulk; /**< Add entry bulk */ - rte_table_op_entry_delete_bulk f_delete_bulk; /**< Delete entry bulk */ - rte_table_op_lookup f_lookup; /**< Lookup */ - rte_table_op_stats_read f_stats; /**< Stats */ -}; - -#endif diff --git a/lib/table/rte_table_acl.c b/lib/table/rte_table_acl.c deleted file mode 100644 index 74fa0145d8..0000000000 --- a/lib/table/rte_table_acl.c +++ /dev/null @@ -1,795 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2014 Intel Corporation - */ - -#include -#include -#include - -#include -#include -#include -#include - -#include "rte_table_acl.h" - -#include "table_log.h" - -#ifdef RTE_TABLE_STATS_COLLECT - -#define RTE_TABLE_ACL_STATS_PKTS_IN_ADD(table, val) \ - table->stats.n_pkts_in += val -#define RTE_TABLE_ACL_STATS_PKTS_LOOKUP_MISS(table, val) \ - table->stats.n_pkts_lookup_miss += val - -#else - -#define RTE_TABLE_ACL_STATS_PKTS_IN_ADD(table, val) -#define RTE_TABLE_ACL_STATS_PKTS_LOOKUP_MISS(table, val) - -#endif - -struct rte_table_acl { - struct rte_table_stats stats; - - /* Low-level ACL table */ - char name[2][RTE_ACL_NAMESIZE]; - struct rte_acl_param acl_params; /* for creating low level acl table */ - struct rte_acl_config cfg; /* Holds the field definitions (metadata) */ - struct rte_acl_ctx *ctx; - uint32_t name_id; - - /* Input parameters */ - uint32_t n_rules; - uint32_t entry_size; - - /* Internal tables */ - uint8_t *action_table; - struct rte_acl_rule **acl_rule_list; /* Array of pointers to rules */ - uint8_t *acl_rule_memory; /* Memory to store the rules */ - - /* Memory to store the action table and stack of free entries */ - alignas(RTE_CACHE_LINE_SIZE) uint8_t memory[]; -}; - - -static void * -rte_table_acl_create( - void *params, - int socket_id, - uint32_t entry_size) -{ - struct rte_table_acl_params *p = params; - struct rte_table_acl *acl; - uint32_t action_table_size, acl_rule_list_size, acl_rule_memory_size; - uint32_t total_size; - - RTE_BUILD_BUG_ON(((sizeof(struct rte_table_acl) % RTE_CACHE_LINE_SIZE) - != 0)); - - /* Check input parameters */ - if (p == NULL) { - TABLE_LOG(ERR, "%s: Invalid value for params", __func__); - return NULL; - } - if (p->name == NULL) { - TABLE_LOG(ERR, "%s: Invalid value for name", __func__); - return NULL; - } - if (p->n_rules == 0) { - TABLE_LOG(ERR, "%s: Invalid value for n_rules", - __func__); - return NULL; - } - if ((p->n_rule_fields == 0) || - (p->n_rule_fields > RTE_ACL_MAX_FIELDS)) { - TABLE_LOG(ERR, "%s: Invalid value for n_rule_fields", - __func__); - return NULL; - } - - entry_size = RTE_ALIGN(entry_size, sizeof(uint64_t)); - - /* Memory allocation */ - action_table_size = RTE_CACHE_LINE_ROUNDUP(p->n_rules * entry_size); - acl_rule_list_size = - RTE_CACHE_LINE_ROUNDUP(p->n_rules * sizeof(struct rte_acl_rule *)); - acl_rule_memory_size = RTE_CACHE_LINE_ROUNDUP(p->n_rules * - RTE_ACL_RULE_SZ(p->n_rule_fields)); - total_size = sizeof(struct rte_table_acl) + action_table_size + - acl_rule_list_size + acl_rule_memory_size; - - acl = rte_zmalloc_socket("TABLE", total_size, RTE_CACHE_LINE_SIZE, - socket_id); - if (acl == NULL) { - TABLE_LOG(ERR, - "%s: Cannot allocate %u bytes for ACL table", - __func__, total_size); - return NULL; - } - - acl->action_table = &acl->memory[0]; - acl->acl_rule_list = - (struct rte_acl_rule **) &acl->memory[action_table_size]; - acl->acl_rule_memory = (uint8_t *) - &acl->memory[action_table_size + acl_rule_list_size]; - - /* Initialization of internal fields */ - snprintf(acl->name[0], RTE_ACL_NAMESIZE, "%s_a", p->name); - snprintf(acl->name[1], RTE_ACL_NAMESIZE, "%s_b", p->name); - acl->name_id = 1; - - acl->acl_params.name = acl->name[acl->name_id]; - acl->acl_params.socket_id = socket_id; - acl->acl_params.rule_size = RTE_ACL_RULE_SZ(p->n_rule_fields); - acl->acl_params.max_rule_num = p->n_rules; - - acl->cfg.num_categories = 1; - acl->cfg.num_fields = p->n_rule_fields; - memcpy(&acl->cfg.defs[0], &p->field_format[0], - p->n_rule_fields * sizeof(struct rte_acl_field_def)); - - acl->ctx = NULL; - - acl->n_rules = p->n_rules; - acl->entry_size = entry_size; - - return acl; -} - -static int -rte_table_acl_free(void *table) -{ - struct rte_table_acl *acl = table; - - /* Check input parameters */ - if (table == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - - /* Free previously allocated resources */ - rte_acl_free(acl->ctx); - - rte_free(acl); - - return 0; -} - -RTE_ACL_RULE_DEF(rte_pipeline_acl_rule, RTE_ACL_MAX_FIELDS); - -static int -rte_table_acl_build(struct rte_table_acl *acl, struct rte_acl_ctx **acl_ctx) -{ - struct rte_acl_ctx *ctx = NULL; - uint32_t n_rules, i; - int status; - - /* Create low level ACL table */ - ctx = rte_acl_create(&acl->acl_params); - if (ctx == NULL) { - TABLE_LOG(ERR, "%s: Cannot create low level ACL table", - __func__); - return -1; - } - - /* Add rules to low level ACL table */ - n_rules = 0; - for (i = 1; i < acl->n_rules; i++) { - if (acl->acl_rule_list[i] != NULL) { - status = rte_acl_add_rules(ctx, acl->acl_rule_list[i], - 1); - if (status != 0) { - TABLE_LOG(ERR, - "%s: Cannot add rule to low level ACL table", - __func__); - rte_acl_free(ctx); - return -1; - } - - n_rules++; - } - } - - if (n_rules == 0) { - rte_acl_free(ctx); - *acl_ctx = NULL; - return 0; - } - - /* Build low level ACl table */ - status = rte_acl_build(ctx, &acl->cfg); - if (status != 0) { - TABLE_LOG(ERR, - "%s: Cannot build the low level ACL table", - __func__); - rte_acl_free(ctx); - return -1; - } - - *acl_ctx = ctx; - return 0; -} - -static int -rte_table_acl_entry_add( - void *table, - void *key, - void *entry, - int *key_found, - void **entry_ptr) -{ - struct rte_table_acl *acl = table; - struct rte_table_acl_rule_add_params *rule = - key; - struct rte_pipeline_acl_rule acl_rule; - struct rte_acl_rule *rule_location; - struct rte_acl_ctx *ctx; - uint32_t free_pos, free_pos_valid, i; - int status; - - /* Check input parameters */ - if (table == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - if (key == NULL) { - TABLE_LOG(ERR, "%s: key parameter is NULL", __func__); - return -EINVAL; - } - if (entry == NULL) { - TABLE_LOG(ERR, "%s: entry parameter is NULL", __func__); - return -EINVAL; - } - if (key_found == NULL) { - TABLE_LOG(ERR, "%s: key_found parameter is NULL", - __func__); - return -EINVAL; - } - if (entry_ptr == NULL) { - TABLE_LOG(ERR, "%s: entry_ptr parameter is NULL", - __func__); - return -EINVAL; - } - if (rule->priority > RTE_ACL_MAX_PRIORITY) { - TABLE_LOG(ERR, "%s: Priority is too high", __func__); - return -EINVAL; - } - - /* Setup rule data structure */ - memset(&acl_rule, 0, sizeof(acl_rule)); - acl_rule.data.category_mask = 1; - acl_rule.data.priority = RTE_ACL_MAX_PRIORITY - rule->priority; - acl_rule.data.userdata = 0; /* To be set up later */ - memcpy(&acl_rule.field[0], - &rule->field_value[0], - acl->cfg.num_fields * sizeof(struct rte_acl_field)); - - /* Look to see if the rule exists already in the table */ - free_pos = 0; - free_pos_valid = 0; - for (i = 1; i < acl->n_rules; i++) { - if (acl->acl_rule_list[i] == NULL) { - if (free_pos_valid == 0) { - free_pos = i; - free_pos_valid = 1; - } - - continue; - } - - /* Compare the key fields */ - status = memcmp(&acl->acl_rule_list[i]->field[0], - &rule->field_value[0], - acl->cfg.num_fields * sizeof(struct rte_acl_field)); - - /* Rule found: update data associated with the rule */ - if (status == 0) { - *key_found = 1; - *entry_ptr = &acl->memory[i * acl->entry_size]; - memcpy(*entry_ptr, entry, acl->entry_size); - - return 0; - } - } - - /* Return if max rules */ - if (free_pos_valid == 0) { - TABLE_LOG(ERR, "%s: Max number of rules reached", - __func__); - return -ENOSPC; - } - - /* Add the new rule to the rule set */ - acl_rule.data.userdata = free_pos; - rule_location = (struct rte_acl_rule *) - &acl->acl_rule_memory[free_pos * acl->acl_params.rule_size]; - memcpy(rule_location, &acl_rule, acl->acl_params.rule_size); - acl->acl_rule_list[free_pos] = rule_location; - - /* Build low level ACL table */ - acl->name_id ^= 1; - acl->acl_params.name = acl->name[acl->name_id]; - status = rte_table_acl_build(acl, &ctx); - if (status != 0) { - /* Roll back changes */ - acl->acl_rule_list[free_pos] = NULL; - acl->name_id ^= 1; - - return -EINVAL; - } - - /* Commit changes */ - rte_acl_free(acl->ctx); - acl->ctx = ctx; - *key_found = 0; - *entry_ptr = &acl->memory[free_pos * acl->entry_size]; - memcpy(*entry_ptr, entry, acl->entry_size); - - return 0; -} - -static int -rte_table_acl_entry_delete( - void *table, - void *key, - int *key_found, - void *entry) -{ - struct rte_table_acl *acl = table; - struct rte_table_acl_rule_delete_params *rule = - key; - struct rte_acl_rule *deleted_rule = NULL; - struct rte_acl_ctx *ctx; - uint32_t pos, pos_valid, i; - int status; - - /* Check input parameters */ - if (table == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - if (key == NULL) { - TABLE_LOG(ERR, "%s: key parameter is NULL", __func__); - return -EINVAL; - } - if (key_found == NULL) { - TABLE_LOG(ERR, "%s: key_found parameter is NULL", - __func__); - return -EINVAL; - } - - /* Look for the rule in the table */ - pos = 0; - pos_valid = 0; - for (i = 1; i < acl->n_rules; i++) { - if (acl->acl_rule_list[i] != NULL) { - /* Compare the key fields */ - status = memcmp(&acl->acl_rule_list[i]->field[0], - &rule->field_value[0], acl->cfg.num_fields * - sizeof(struct rte_acl_field)); - - /* Rule found: remove from table */ - if (status == 0) { - pos = i; - pos_valid = 1; - - deleted_rule = acl->acl_rule_list[i]; - acl->acl_rule_list[i] = NULL; - } - } - } - - /* Return if rule not found */ - if (pos_valid == 0) { - *key_found = 0; - return 0; - } - - /* Build low level ACL table */ - acl->name_id ^= 1; - acl->acl_params.name = acl->name[acl->name_id]; - status = rte_table_acl_build(acl, &ctx); - if (status != 0) { - /* Roll back changes */ - acl->acl_rule_list[pos] = deleted_rule; - acl->name_id ^= 1; - - return -EINVAL; - } - - /* Commit changes */ - rte_acl_free(acl->ctx); - - acl->ctx = ctx; - *key_found = 1; - if (entry != NULL) - memcpy(entry, &acl->memory[pos * acl->entry_size], - acl->entry_size); - - return 0; -} - -static int -rte_table_acl_entry_add_bulk( - void *table, - void **keys, - void **entries, - uint32_t n_keys, - int *key_found, - void **entries_ptr) -{ - struct rte_table_acl *acl = table; - struct rte_acl_ctx *ctx; - uint32_t rule_pos[n_keys]; - uint32_t i; - int err = 0, build = 0; - int status; - - /* Check input parameters */ - if (table == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - if (keys == NULL) { - TABLE_LOG(ERR, "%s: keys parameter is NULL", __func__); - return -EINVAL; - } - if (entries == NULL) { - TABLE_LOG(ERR, "%s: entries parameter is NULL", __func__); - return -EINVAL; - } - if (n_keys == 0) { - TABLE_LOG(ERR, "%s: 0 rules to add", __func__); - return -EINVAL; - } - if (key_found == NULL) { - TABLE_LOG(ERR, "%s: key_found parameter is NULL", - __func__); - return -EINVAL; - } - if (entries_ptr == NULL) { - TABLE_LOG(ERR, "%s: entries_ptr parameter is NULL", - __func__); - return -EINVAL; - } - - /* Check input parameters in arrays */ - for (i = 0; i < n_keys; i++) { - struct rte_table_acl_rule_add_params *rule; - - if (keys[i] == NULL) { - TABLE_LOG(ERR, "%s: keys[%" PRIu32 "] parameter is NULL", - __func__, i); - return -EINVAL; - } - - if (entries[i] == NULL) { - TABLE_LOG(ERR, "%s: entries[%" PRIu32 "] parameter is NULL", - __func__, i); - return -EINVAL; - } - - rule = keys[i]; - if (rule->priority > RTE_ACL_MAX_PRIORITY) { - TABLE_LOG(ERR, "%s: Priority is too high", __func__); - return -EINVAL; - } - } - - memset(rule_pos, 0, n_keys * sizeof(uint32_t)); - memset(key_found, 0, n_keys * sizeof(int)); - for (i = 0; i < n_keys; i++) { - struct rte_table_acl_rule_add_params *rule = - keys[i]; - struct rte_pipeline_acl_rule acl_rule; - struct rte_acl_rule *rule_location; - uint32_t free_pos, free_pos_valid, j; - - /* Setup rule data structure */ - memset(&acl_rule, 0, sizeof(acl_rule)); - acl_rule.data.category_mask = 1; - acl_rule.data.priority = RTE_ACL_MAX_PRIORITY - rule->priority; - acl_rule.data.userdata = 0; /* To be set up later */ - memcpy(&acl_rule.field[0], - &rule->field_value[0], - acl->cfg.num_fields * sizeof(struct rte_acl_field)); - - /* Look to see if the rule exists already in the table */ - free_pos = 0; - free_pos_valid = 0; - for (j = 1; j < acl->n_rules; j++) { - if (acl->acl_rule_list[j] == NULL) { - if (free_pos_valid == 0) { - free_pos = j; - free_pos_valid = 1; - } - - continue; - } - - /* Compare the key fields */ - status = memcmp(&acl->acl_rule_list[j]->field[0], - &rule->field_value[0], - acl->cfg.num_fields * sizeof(struct rte_acl_field)); - - /* Rule found: update data associated with the rule */ - if (status == 0) { - key_found[i] = 1; - entries_ptr[i] = &acl->memory[j * acl->entry_size]; - memcpy(entries_ptr[i], entries[i], acl->entry_size); - - break; - } - } - - /* Key already in the table */ - if (key_found[i] != 0) - continue; - - /* Maximum number of rules reached */ - if (free_pos_valid == 0) { - err = 1; - break; - } - - /* Add the new rule to the rule set */ - acl_rule.data.userdata = free_pos; - rule_location = (struct rte_acl_rule *) - &acl->acl_rule_memory[free_pos * acl->acl_params.rule_size]; - memcpy(rule_location, &acl_rule, acl->acl_params.rule_size); - acl->acl_rule_list[free_pos] = rule_location; - rule_pos[i] = free_pos; - build = 1; - } - - if (err != 0) { - for (i = 0; i < n_keys; i++) { - if (rule_pos[i] == 0) - continue; - - acl->acl_rule_list[rule_pos[i]] = NULL; - } - - return -ENOSPC; - } - - if (build == 0) - return 0; - - /* Build low level ACL table */ - acl->name_id ^= 1; - acl->acl_params.name = acl->name[acl->name_id]; - status = rte_table_acl_build(acl, &ctx); - if (status != 0) { - /* Roll back changes */ - for (i = 0; i < n_keys; i++) { - if (rule_pos[i] == 0) - continue; - - acl->acl_rule_list[rule_pos[i]] = NULL; - } - acl->name_id ^= 1; - - return -EINVAL; - } - - /* Commit changes */ - rte_acl_free(acl->ctx); - acl->ctx = ctx; - - for (i = 0; i < n_keys; i++) { - if (rule_pos[i] == 0) - continue; - - key_found[i] = 0; - entries_ptr[i] = &acl->memory[rule_pos[i] * acl->entry_size]; - memcpy(entries_ptr[i], entries[i], acl->entry_size); - } - - return 0; -} - -static int -rte_table_acl_entry_delete_bulk( - void *table, - void **keys, - uint32_t n_keys, - int *key_found, - void **entries) -{ - struct rte_table_acl *acl = table; - struct rte_acl_rule *deleted_rules[n_keys]; - uint32_t rule_pos[n_keys]; - struct rte_acl_ctx *ctx; - uint32_t i; - int status; - int build = 0; - - /* Check input parameters */ - if (table == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - if (keys == NULL) { - TABLE_LOG(ERR, "%s: key parameter is NULL", __func__); - return -EINVAL; - } - if (n_keys == 0) { - TABLE_LOG(ERR, "%s: 0 rules to delete", __func__); - return -EINVAL; - } - if (key_found == NULL) { - TABLE_LOG(ERR, "%s: key_found parameter is NULL", - __func__); - return -EINVAL; - } - - for (i = 0; i < n_keys; i++) { - if (keys[i] == NULL) { - TABLE_LOG(ERR, "%s: keys[%" PRIu32 "] parameter is NULL", - __func__, i); - return -EINVAL; - } - } - - memset(deleted_rules, 0, n_keys * sizeof(struct rte_acl_rule *)); - memset(rule_pos, 0, n_keys * sizeof(uint32_t)); - for (i = 0; i < n_keys; i++) { - struct rte_table_acl_rule_delete_params *rule = - keys[i]; - uint32_t pos_valid, j; - - /* Look for the rule in the table */ - pos_valid = 0; - for (j = 1; j < acl->n_rules; j++) { - if (acl->acl_rule_list[j] == NULL) - continue; - - /* Compare the key fields */ - status = memcmp(&acl->acl_rule_list[j]->field[0], - &rule->field_value[0], - acl->cfg.num_fields * sizeof(struct rte_acl_field)); - - /* Rule found: remove from table */ - if (status == 0) { - pos_valid = 1; - - deleted_rules[i] = acl->acl_rule_list[j]; - acl->acl_rule_list[j] = NULL; - rule_pos[i] = j; - - build = 1; - } - } - - if (pos_valid == 0) { - key_found[i] = 0; - continue; - } - } - - /* Return if no changes to acl table */ - if (build == 0) { - return 0; - } - - /* Build low level ACL table */ - acl->name_id ^= 1; - acl->acl_params.name = acl->name[acl->name_id]; - status = rte_table_acl_build(acl, &ctx); - if (status != 0) { - /* Roll back changes */ - for (i = 0; i < n_keys; i++) { - if (rule_pos[i] == 0) - continue; - - acl->acl_rule_list[rule_pos[i]] = deleted_rules[i]; - } - - acl->name_id ^= 1; - - return -EINVAL; - } - - /* Commit changes */ - rte_acl_free(acl->ctx); - - acl->ctx = ctx; - for (i = 0; i < n_keys; i++) { - if (rule_pos[i] == 0) - continue; - - key_found[i] = 1; - if (entries != NULL && entries[i] != NULL) - memcpy(entries[i], &acl->memory[rule_pos[i] * acl->entry_size], - acl->entry_size); - } - - return 0; -} - -static int -rte_table_acl_lookup( - void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries) -{ - struct rte_table_acl *acl = (struct rte_table_acl *) table; - const uint8_t *pkts_data[RTE_PORT_IN_BURST_SIZE_MAX]; - uint32_t results[RTE_PORT_IN_BURST_SIZE_MAX]; - uint64_t pkts_out_mask; - uint32_t n_pkts, i, j; - - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - RTE_TABLE_ACL_STATS_PKTS_IN_ADD(acl, n_pkts_in); - - /* Input conversion */ - for (i = 0, j = 0; i < (uint32_t)(RTE_PORT_IN_BURST_SIZE_MAX - - rte_clz64(pkts_mask)); i++) { - uint64_t pkt_mask = 1LLU << i; - - if (pkt_mask & pkts_mask) { - pkts_data[j] = rte_pktmbuf_mtod(pkts[i], uint8_t *); - j++; - } - } - n_pkts = j; - - /* Low-level ACL table lookup */ - if (acl->ctx != NULL) - rte_acl_classify(acl->ctx, pkts_data, results, n_pkts, 1); - else - n_pkts = 0; - - /* Output conversion */ - pkts_out_mask = 0; - for (i = 0; i < n_pkts; i++) { - uint32_t action_table_pos = results[i]; - uint32_t pkt_pos = rte_ctz64(pkts_mask); - uint64_t pkt_mask = 1LLU << pkt_pos; - - pkts_mask &= ~pkt_mask; - - if (action_table_pos != 0) { - pkts_out_mask |= pkt_mask; - entries[pkt_pos] = (void *) - &acl->memory[action_table_pos * - acl->entry_size]; - rte_prefetch0(entries[pkt_pos]); - } - } - - *lookup_hit_mask = pkts_out_mask; - RTE_TABLE_ACL_STATS_PKTS_LOOKUP_MISS(acl, n_pkts_in - rte_popcount64(pkts_out_mask)); - - return 0; -} - -static int -rte_table_acl_stats_read(void *table, struct rte_table_stats *stats, int clear) -{ - struct rte_table_acl *acl = table; - - if (stats != NULL) - memcpy(stats, &acl->stats, sizeof(acl->stats)); - - if (clear) - memset(&acl->stats, 0, sizeof(acl->stats)); - - return 0; -} - -RTE_EXPORT_SYMBOL(rte_table_acl_ops) -struct rte_table_ops rte_table_acl_ops = { - .f_create = rte_table_acl_create, - .f_free = rte_table_acl_free, - .f_add = rte_table_acl_entry_add, - .f_delete = rte_table_acl_entry_delete, - .f_add_bulk = rte_table_acl_entry_add_bulk, - .f_delete_bulk = rte_table_acl_entry_delete_bulk, - .f_lookup = rte_table_acl_lookup, - .f_stats = rte_table_acl_stats_read, -}; diff --git a/lib/table/rte_table_acl.h b/lib/table/rte_table_acl.h deleted file mode 100644 index 61af7b88e4..0000000000 --- a/lib/table/rte_table_acl.h +++ /dev/null @@ -1,65 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2014 Intel Corporation - */ - -#ifndef __INCLUDE_RTE_TABLE_ACL_H__ -#define __INCLUDE_RTE_TABLE_ACL_H__ - -/** - * @file - * RTE Table ACL - * - * This table uses the Access Control List (ACL) algorithm to uniquely - * associate data to lookup keys. - * - * Use-cases: Firewall rule database, etc. - */ - -#include - -#include "rte_acl.h" - -#include "rte_table.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** ACL table parameters */ -struct rte_table_acl_params { - /** Name */ - const char *name; - - /** Maximum number of ACL rules in the table */ - uint32_t n_rules; - - /** Number of fields in the ACL rule specification */ - uint32_t n_rule_fields; - - /** Format specification of the fields of the ACL rule */ - struct rte_acl_field_def field_format[RTE_ACL_MAX_FIELDS]; -}; - -/** ACL rule specification for entry add operation */ -struct rte_table_acl_rule_add_params { - /** ACL rule priority, with 0 as the highest priority */ - int32_t priority; - - /** Values for the fields of the ACL rule to be added to the table */ - struct rte_acl_field field_value[RTE_ACL_MAX_FIELDS]; -}; - -/** ACL rule specification for entry delete operation */ -struct rte_table_acl_rule_delete_params { - /** Values for the fields of the ACL rule to be deleted from table */ - struct rte_acl_field field_value[RTE_ACL_MAX_FIELDS]; -}; - -/** ACL table operations */ -extern struct rte_table_ops rte_table_acl_ops; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/lib/table/rte_table_array.c b/lib/table/rte_table_array.c deleted file mode 100644 index 55356e5999..0000000000 --- a/lib/table/rte_table_array.c +++ /dev/null @@ -1,210 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2014 Intel Corporation - */ - -#include -#include -#include - -#include -#include -#include -#include - -#include "rte_table_array.h" - -#include "table_log.h" - -#ifdef RTE_TABLE_STATS_COLLECT - -#define RTE_TABLE_ARRAY_STATS_PKTS_IN_ADD(table, val) \ - table->stats.n_pkts_in += val -#define RTE_TABLE_ARRAY_STATS_PKTS_LOOKUP_MISS(table, val) \ - table->stats.n_pkts_lookup_miss += val - -#else - -#define RTE_TABLE_ARRAY_STATS_PKTS_IN_ADD(table, val) -#define RTE_TABLE_ARRAY_STATS_PKTS_LOOKUP_MISS(table, val) - -#endif - -struct __rte_cache_aligned rte_table_array { - struct rte_table_stats stats; - - /* Input parameters */ - uint32_t entry_size; - uint32_t n_entries; - uint32_t offset; - - /* Internal fields */ - uint32_t entry_pos_mask; - - /* Internal table */ - alignas(RTE_CACHE_LINE_SIZE) uint8_t array[]; -}; - -static void * -rte_table_array_create(void *params, int socket_id, uint32_t entry_size) -{ - struct rte_table_array_params *p = params; - struct rte_table_array *t; - uint32_t total_cl_size, total_size; - - /* Check input parameters */ - if ((p == NULL) || - (p->n_entries == 0) || - (!rte_is_power_of_2(p->n_entries))) - return NULL; - - /* Memory allocation */ - total_cl_size = (sizeof(struct rte_table_array) + - RTE_CACHE_LINE_SIZE) / RTE_CACHE_LINE_SIZE; - total_cl_size += (p->n_entries * entry_size + - RTE_CACHE_LINE_SIZE) / RTE_CACHE_LINE_SIZE; - total_size = total_cl_size * RTE_CACHE_LINE_SIZE; - t = rte_zmalloc_socket("TABLE", total_size, RTE_CACHE_LINE_SIZE, socket_id); - if (t == NULL) { - TABLE_LOG(ERR, - "%s: Cannot allocate %u bytes for array table", - __func__, total_size); - return NULL; - } - - /* Memory initialization */ - t->entry_size = entry_size; - t->n_entries = p->n_entries; - t->offset = p->offset; - t->entry_pos_mask = t->n_entries - 1; - - return t; -} - -static int -rte_table_array_free(void *table) -{ - struct rte_table_array *t = table; - - /* Check input parameters */ - if (t == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - - /* Free previously allocated resources */ - rte_free(t); - - return 0; -} - -static int -rte_table_array_entry_add( - void *table, - void *key, - void *entry, - int *key_found, - void **entry_ptr) -{ - struct rte_table_array *t = table; - struct rte_table_array_key *k = key; - uint8_t *table_entry; - - /* Check input parameters */ - if (table == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - if (key == NULL) { - TABLE_LOG(ERR, "%s: key parameter is NULL", __func__); - return -EINVAL; - } - if (entry == NULL) { - TABLE_LOG(ERR, "%s: entry parameter is NULL", __func__); - return -EINVAL; - } - if (key_found == NULL) { - TABLE_LOG(ERR, "%s: key_found parameter is NULL", - __func__); - return -EINVAL; - } - if (entry_ptr == NULL) { - TABLE_LOG(ERR, "%s: entry_ptr parameter is NULL", - __func__); - return -EINVAL; - } - - table_entry = &t->array[k->pos * t->entry_size]; - memcpy(table_entry, entry, t->entry_size); - *key_found = 1; - *entry_ptr = (void *) table_entry; - - return 0; -} - -static int -rte_table_array_lookup( - void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries) -{ - struct rte_table_array *t = (struct rte_table_array *) table; - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - RTE_TABLE_ARRAY_STATS_PKTS_IN_ADD(t, n_pkts_in); - *lookup_hit_mask = pkts_mask; - - if ((pkts_mask & (pkts_mask + 1)) == 0) { - uint64_t n_pkts = rte_popcount64(pkts_mask); - uint32_t i; - - for (i = 0; i < n_pkts; i++) { - struct rte_mbuf *pkt = pkts[i]; - uint32_t entry_pos = RTE_MBUF_METADATA_UINT32(pkt, - t->offset) & t->entry_pos_mask; - - entries[i] = (void *) &t->array[entry_pos * - t->entry_size]; - } - } else { - for ( ; pkts_mask; ) { - uint32_t pkt_index = rte_ctz64(pkts_mask); - uint64_t pkt_mask = 1LLU << pkt_index; - struct rte_mbuf *pkt = pkts[pkt_index]; - uint32_t entry_pos = RTE_MBUF_METADATA_UINT32(pkt, - t->offset) & t->entry_pos_mask; - - entries[pkt_index] = (void *) &t->array[entry_pos * - t->entry_size]; - pkts_mask &= ~pkt_mask; - } - } - - return 0; -} - -static int -rte_table_array_stats_read(void *table, struct rte_table_stats *stats, int clear) -{ - struct rte_table_array *array = table; - - if (stats != NULL) - memcpy(stats, &array->stats, sizeof(array->stats)); - - if (clear) - memset(&array->stats, 0, sizeof(array->stats)); - - return 0; -} - -RTE_EXPORT_SYMBOL(rte_table_array_ops) -struct rte_table_ops rte_table_array_ops = { - .f_create = rte_table_array_create, - .f_free = rte_table_array_free, - .f_add = rte_table_array_entry_add, - .f_delete = NULL, - .f_add_bulk = NULL, - .f_delete_bulk = NULL, - .f_lookup = rte_table_array_lookup, - .f_stats = rte_table_array_stats_read, -}; diff --git a/lib/table/rte_table_array.h b/lib/table/rte_table_array.h deleted file mode 100644 index b2a7b95d68..0000000000 --- a/lib/table/rte_table_array.h +++ /dev/null @@ -1,46 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2014 Intel Corporation - */ - -#ifndef __INCLUDE_RTE_TABLE_ARRAY_H__ -#define __INCLUDE_RTE_TABLE_ARRAY_H__ - -/** - * @file - * RTE Table Array - * - * Simple array indexing. Lookup key is the array entry index. - */ - -#include - -#include "rte_table.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** Array table parameters */ -struct rte_table_array_params { - /** Number of array entries. Has to be a power of two. */ - uint32_t n_entries; - - /** Byte offset within input packet meta-data where lookup key (i.e. the - array entry index) is located. */ - uint32_t offset; -}; - -/** Array table key format */ -struct rte_table_array_key { - /** Array entry index */ - uint32_t pos; -}; - -/** Array table operations */ -extern struct rte_table_ops rte_table_array_ops; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/lib/table/rte_table_hash.h b/lib/table/rte_table_hash.h deleted file mode 100644 index ff8fc9e9ce..0000000000 --- a/lib/table/rte_table_hash.h +++ /dev/null @@ -1,106 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2017 Intel Corporation - */ - -#ifndef __INCLUDE_RTE_TABLE_HASH_H__ -#define __INCLUDE_RTE_TABLE_HASH_H__ - -/** - * @file - * RTE Table Hash - * - * These tables use the exact match criterion to uniquely associate data to - * lookup keys. - * - * Hash table types: - * 1. Entry add strategy on bucket full: - * a. Least Recently Used (LRU): One of the existing keys in the bucket is - * deleted and the new key is added in its place. The number of keys in - * each bucket never grows bigger than 4. The logic to pick the key to - * be dropped from the bucket is LRU. The hash table lookup operation - * maintains the order in which the keys in the same bucket are hit, so - * every time a key is hit, it becomes the new Most Recently Used (MRU) - * key, i.e. the most unlikely candidate for drop. When a key is added - * to the bucket, it also becomes the new MRU key. When a key needs to - * be picked and dropped, the most likely candidate for drop, i.e. the - * current LRU key, is always picked. The LRU logic requires maintaining - * specific data structures per each bucket. Use-cases: flow cache, etc. - * b. Extendable bucket (ext): The bucket is extended with space for 4 more - * keys. This is done by allocating additional memory at table init time, - * which is used to create a pool of free keys (the size of this pool is - * configurable and always a multiple of 4). On key add operation, the - * allocation of a group of 4 keys only happens successfully within the - * limit of free keys, otherwise the key add operation fails. On key - * delete operation, a group of 4 keys is freed back to the pool of free - * keys when the key to be deleted is the only key that was used within - * its group of 4 keys at that time. On key lookup operation, if the - * current bucket is in extended state and a match is not found in the - * first group of 4 keys, the search continues beyond the first group of - * 4 keys, potentially until all keys in this bucket are examined. The - * extendable bucket logic requires maintaining specific data structures - * per table and per each bucket. Use-cases: flow table, etc. - * 2. Key size: - * a. Configurable key size - * b. Single key size (8-byte, 16-byte or 32-byte key size) - */ - -#include - -#include "rte_table.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** Hash function */ -typedef uint64_t (*rte_table_hash_op_hash)( - void *key, - void *key_mask, - uint32_t key_size, - uint64_t seed); - -/** Hash table parameters */ -struct rte_table_hash_params { - /** Name */ - const char *name; - - /** Key size (number of bytes) */ - uint32_t key_size; - - /** Byte offset within packet meta-data where the key is located */ - uint32_t key_offset; - - /** Key mask */ - uint8_t *key_mask; - - /** Number of keys */ - uint32_t n_keys; - - /** Number of buckets */ - uint32_t n_buckets; - - /** Hash function */ - rte_table_hash_op_hash f_hash; - - /** Seed value for the hash function */ - uint64_t seed; -}; - -/** Extendable bucket hash table operations */ -extern struct rte_table_ops rte_table_hash_ext_ops; -extern struct rte_table_ops rte_table_hash_key8_ext_ops; -extern struct rte_table_ops rte_table_hash_key16_ext_ops; -extern struct rte_table_ops rte_table_hash_key32_ext_ops; - -/** LRU hash table operations */ -extern struct rte_table_ops rte_table_hash_lru_ops; - -extern struct rte_table_ops rte_table_hash_key8_lru_ops; -extern struct rte_table_ops rte_table_hash_key16_lru_ops; -extern struct rte_table_ops rte_table_hash_key32_lru_ops; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/lib/table/rte_table_hash_cuckoo.c b/lib/table/rte_table_hash_cuckoo.c deleted file mode 100644 index a2b920fa92..0000000000 --- a/lib/table/rte_table_hash_cuckoo.c +++ /dev/null @@ -1,327 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2017 Intel Corporation - */ - -#include -#include -#include - -#include -#include -#include -#include - -#include "rte_table_hash_cuckoo.h" - -#include "table_log.h" - -#ifdef RTE_TABLE_STATS_COLLECT - -#define RTE_TABLE_HASH_CUCKOO_STATS_PKTS_IN_ADD(table, val) \ - (table->stats.n_pkts_in += val) -#define RTE_TABLE_HASH_CUCKOO_STATS_PKTS_LOOKUP_MISS(table, val) \ - (table->stats.n_pkts_lookup_miss += val) - -#else - -#define RTE_TABLE_HASH_CUCKOO_STATS_PKTS_IN_ADD(table, val) -#define RTE_TABLE_HASH_CUCKOO_STATS_PKTS_LOOKUP_MISS(table, val) - -#endif - - -struct rte_table_hash { - struct rte_table_stats stats; - - /* Input parameters */ - uint32_t key_size; - uint32_t entry_size; - uint32_t n_keys; - rte_hash_function f_hash; - uint32_t seed; - uint32_t key_offset; - - /* cuckoo hash table object */ - struct rte_hash *h_table; - - /* Lookup table */ - alignas(RTE_CACHE_LINE_SIZE) uint8_t memory[]; -}; - -static int -check_params_create_hash_cuckoo(struct rte_table_hash_cuckoo_params *params) -{ - if (params == NULL) { - TABLE_LOG(ERR, "NULL Input Parameters."); - return -EINVAL; - } - - if (params->name == NULL) { - TABLE_LOG(ERR, "Table name is NULL."); - return -EINVAL; - } - - if (params->key_size == 0) { - TABLE_LOG(ERR, "Invalid key_size."); - return -EINVAL; - } - - if (params->n_keys == 0) { - TABLE_LOG(ERR, "Invalid n_keys."); - return -EINVAL; - } - - if (params->f_hash == NULL) { - TABLE_LOG(ERR, "f_hash is NULL."); - return -EINVAL; - } - - return 0; -} - -static void * -rte_table_hash_cuckoo_create(void *params, - int socket_id, - uint32_t entry_size) -{ - struct rte_table_hash_cuckoo_params *p = params; - struct rte_hash *h_table; - struct rte_table_hash *t; - uint32_t total_size; - - /* Check input parameters */ - if (check_params_create_hash_cuckoo(params)) - return NULL; - - /* Memory allocation */ - total_size = sizeof(struct rte_table_hash) + - RTE_CACHE_LINE_ROUNDUP(p->n_keys * entry_size); - - t = rte_zmalloc_socket(p->name, total_size, RTE_CACHE_LINE_SIZE, socket_id); - if (t == NULL) { - TABLE_LOG(ERR, - "%s: Cannot allocate %u bytes for cuckoo hash table %s", - __func__, total_size, p->name); - return NULL; - } - - /* Create cuckoo hash table */ - struct rte_hash_parameters hash_cuckoo_params = { - .entries = p->n_keys, - .key_len = p->key_size, - .hash_func = p->f_hash, - .hash_func_init_val = p->seed, - .socket_id = socket_id, - .name = p->name - }; - - h_table = rte_hash_find_existing(p->name); - if (h_table == NULL) { - h_table = rte_hash_create(&hash_cuckoo_params); - if (h_table == NULL) { - TABLE_LOG(ERR, - "%s: failed to create cuckoo hash table %s", - __func__, p->name); - rte_free(t); - return NULL; - } - } - - /* initialize the cuckoo hash parameters */ - t->key_size = p->key_size; - t->entry_size = entry_size; - t->n_keys = p->n_keys; - t->f_hash = p->f_hash; - t->seed = p->seed; - t->key_offset = p->key_offset; - t->h_table = h_table; - - TABLE_LOG(INFO, - "%s: Cuckoo hash table %s memory footprint is %u bytes", - __func__, p->name, total_size); - return t; -} - -static int -rte_table_hash_cuckoo_free(void *table) { - struct rte_table_hash *t = table; - - if (table == NULL) - return -EINVAL; - - rte_hash_free(t->h_table); - rte_free(t); - - return 0; -} - -static int -rte_table_hash_cuckoo_entry_add(void *table, void *key, void *entry, - int *key_found, void **entry_ptr) -{ - struct rte_table_hash *t = table; - int pos = 0; - - /* Check input parameters */ - if ((table == NULL) || - (key == NULL) || - (entry == NULL) || - (key_found == NULL) || - (entry_ptr == NULL)) - return -EINVAL; - - /* Find Existing entries */ - pos = rte_hash_lookup(t->h_table, key); - if (pos >= 0) { - uint8_t *existing_entry; - - *key_found = 1; - existing_entry = &t->memory[pos * t->entry_size]; - memcpy(existing_entry, entry, t->entry_size); - *entry_ptr = existing_entry; - - return 0; - } - - if (pos == -ENOENT) { - /* Entry not found. Adding new entry */ - uint8_t *new_entry; - - pos = rte_hash_add_key(t->h_table, key); - if (pos < 0) - return pos; - - new_entry = &t->memory[pos * t->entry_size]; - memcpy(new_entry, entry, t->entry_size); - - *key_found = 0; - *entry_ptr = new_entry; - return 0; - } - - return pos; -} - -static int -rte_table_hash_cuckoo_entry_delete(void *table, void *key, - int *key_found, void *entry) -{ - struct rte_table_hash *t = table; - int pos = 0; - - /* Check input parameters */ - if ((table == NULL) || - (key == NULL) || - (key_found == NULL)) - return -EINVAL; - - pos = rte_hash_del_key(t->h_table, key); - if (pos >= 0) { - *key_found = 1; - uint8_t *entry_ptr = &t->memory[pos * t->entry_size]; - - if (entry) - memcpy(entry, entry_ptr, t->entry_size); - - memset(&t->memory[pos * t->entry_size], 0, t->entry_size); - return 0; - } - - *key_found = 0; - return pos; -} - -static int -rte_table_hash_cuckoo_lookup(void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries) -{ - struct rte_table_hash *t = table; - uint64_t pkts_mask_out = 0; - uint32_t i; - - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - - RTE_TABLE_HASH_CUCKOO_STATS_PKTS_IN_ADD(t, n_pkts_in); - - if ((pkts_mask & (pkts_mask + 1)) == 0) { - const uint8_t *keys[RTE_PORT_IN_BURST_SIZE_MAX]; - int32_t positions[RTE_PORT_IN_BURST_SIZE_MAX], status; - - /* Keys for bulk lookup */ - for (i = 0; i < n_pkts_in; i++) - keys[i] = RTE_MBUF_METADATA_UINT8_PTR(pkts[i], - t->key_offset); - - /* Bulk Lookup */ - status = rte_hash_lookup_bulk(t->h_table, - (const void **) keys, - n_pkts_in, - positions); - if (status == 0) { - for (i = 0; i < n_pkts_in; i++) { - if (likely(positions[i] >= 0)) { - uint64_t pkt_mask = 1LLU << i; - - entries[i] = &t->memory[positions[i] - * t->entry_size]; - pkts_mask_out |= pkt_mask; - } - } - } - } else - for (i = 0; i < (uint32_t)(RTE_PORT_IN_BURST_SIZE_MAX - - rte_clz64(pkts_mask)); i++) { - uint64_t pkt_mask = 1LLU << i; - - if (pkt_mask & pkts_mask) { - struct rte_mbuf *pkt = pkts[i]; - uint8_t *key = RTE_MBUF_METADATA_UINT8_PTR(pkt, - t->key_offset); - int pos; - - pos = rte_hash_lookup(t->h_table, key); - if (likely(pos >= 0)) { - entries[i] = &t->memory[pos - * t->entry_size]; - pkts_mask_out |= pkt_mask; - } - } - } - - *lookup_hit_mask = pkts_mask_out; - RTE_TABLE_HASH_CUCKOO_STATS_PKTS_LOOKUP_MISS(t, - n_pkts_in - rte_popcount64(pkts_mask_out)); - - return 0; - -} - -static int -rte_table_hash_cuckoo_stats_read(void *table, struct rte_table_stats *stats, - int clear) -{ - struct rte_table_hash *t = table; - - if (stats != NULL) - memcpy(stats, &t->stats, sizeof(t->stats)); - - if (clear) - memset(&t->stats, 0, sizeof(t->stats)); - - return 0; -} - -RTE_EXPORT_SYMBOL(rte_table_hash_cuckoo_ops) -struct rte_table_ops rte_table_hash_cuckoo_ops = { - .f_create = rte_table_hash_cuckoo_create, - .f_free = rte_table_hash_cuckoo_free, - .f_add = rte_table_hash_cuckoo_entry_add, - .f_delete = rte_table_hash_cuckoo_entry_delete, - .f_add_bulk = NULL, - .f_delete_bulk = NULL, - .f_lookup = rte_table_hash_cuckoo_lookup, - .f_stats = rte_table_hash_cuckoo_stats_read, -}; diff --git a/lib/table/rte_table_hash_cuckoo.h b/lib/table/rte_table_hash_cuckoo.h deleted file mode 100644 index 55aa12216a..0000000000 --- a/lib/table/rte_table_hash_cuckoo.h +++ /dev/null @@ -1,57 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2018 Intel Corporation - */ - -#ifndef __INCLUDE_RTE_TABLE_HASH_CUCKOO_H__ -#define __INCLUDE_RTE_TABLE_HASH_CUCKOO_H__ - -/** - * @file - * RTE Table Hash Cuckoo - */ - -#include - -#include - -#include "rte_table.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** Hash table parameters */ -struct rte_table_hash_cuckoo_params { - /** Name */ - const char *name; - - /** Key size (number of bytes) */ - uint32_t key_size; - - /** Byte offset within packet meta-data where the key is located */ - uint32_t key_offset; - - /** Key mask */ - uint8_t *key_mask; - - /** Number of keys */ - uint32_t n_keys; - - /** Number of buckets */ - uint32_t n_buckets; - - /** Hash function */ - rte_hash_function f_hash; - - /** Seed value for the hash function */ - uint32_t seed; -}; - -/** Cuckoo hash table operations */ -extern struct rte_table_ops rte_table_hash_cuckoo_ops; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/lib/table/rte_table_hash_ext.c b/lib/table/rte_table_hash_ext.c deleted file mode 100644 index 86e8eeb4c8..0000000000 --- a/lib/table/rte_table_hash_ext.c +++ /dev/null @@ -1,1011 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2017 Intel Corporation - */ - -#include -#include -#include - -#include -#include -#include -#include - -#include "rte_table_hash.h" - -#include "table_log.h" - -#define KEYS_PER_BUCKET 4 - -struct bucket { - union { - uintptr_t next; - uint64_t lru_list; - }; - uint16_t sig[KEYS_PER_BUCKET]; - uint32_t key_pos[KEYS_PER_BUCKET]; -}; - -#define BUCKET_NEXT(bucket) \ - ((void *) ((bucket)->next & (~1LU))) - -#define BUCKET_NEXT_VALID(bucket) \ - ((bucket)->next & 1LU) - -#define BUCKET_NEXT_SET(bucket, bucket_next) \ -do \ - (bucket)->next = (((uintptr_t) ((void *) (bucket_next))) | 1LU);\ -while (0) - -#define BUCKET_NEXT_SET_NULL(bucket) \ -do \ - (bucket)->next = 0; \ -while (0) - -#define BUCKET_NEXT_COPY(bucket, bucket2) \ -do \ - (bucket)->next = (bucket2)->next; \ -while (0) - -#ifdef RTE_TABLE_STATS_COLLECT - -#define RTE_TABLE_HASH_EXT_STATS_PKTS_IN_ADD(table, val) \ - table->stats.n_pkts_in += val -#define RTE_TABLE_HASH_EXT_STATS_PKTS_LOOKUP_MISS(table, val) \ - table->stats.n_pkts_lookup_miss += val - -#else - -#define RTE_TABLE_HASH_EXT_STATS_PKTS_IN_ADD(table, val) -#define RTE_TABLE_HASH_EXT_STATS_PKTS_LOOKUP_MISS(table, val) - -#endif - -struct grinder { - struct bucket *bkt; - uint64_t sig; - uint64_t match; - uint32_t key_index; -}; - -struct rte_table_hash { - struct rte_table_stats stats; - - /* Input parameters */ - uint32_t key_size; - uint32_t entry_size; - uint32_t n_keys; - uint32_t n_buckets; - uint32_t n_buckets_ext; - rte_table_hash_op_hash f_hash; - uint64_t seed; - uint32_t key_offset; - - /* Internal */ - uint64_t bucket_mask; - uint32_t key_size_shl; - uint32_t data_size_shl; - uint32_t key_stack_tos; - uint32_t bkt_ext_stack_tos; - - /* Grinder */ - struct grinder grinders[RTE_PORT_IN_BURST_SIZE_MAX]; - - /* Tables */ - uint64_t *key_mask; - struct bucket *buckets; - struct bucket *buckets_ext; - uint8_t *key_mem; - uint8_t *data_mem; - uint32_t *key_stack; - uint32_t *bkt_ext_stack; - - /* Table memory */ - alignas(RTE_CACHE_LINE_SIZE) uint8_t memory[]; -}; - -static int -keycmp(void *a, void *b, void *b_mask, uint32_t n_bytes) -{ - uint64_t *a64 = a, *b64 = b, *b_mask64 = b_mask; - uint32_t i; - - for (i = 0; i < n_bytes / sizeof(uint64_t); i++) - if (a64[i] != (b64[i] & b_mask64[i])) - return 1; - - return 0; -} - -static void -keycpy(void *dst, void *src, void *src_mask, uint32_t n_bytes) -{ - uint64_t *dst64 = dst, *src64 = src, *src_mask64 = src_mask; - uint32_t i; - - for (i = 0; i < n_bytes / sizeof(uint64_t); i++) - dst64[i] = src64[i] & src_mask64[i]; -} - -static int -check_params_create(struct rte_table_hash_params *params) -{ - /* name */ - if (params->name == NULL) { - TABLE_LOG(ERR, "%s: name invalid value", __func__); - return -EINVAL; - } - - /* key_size */ - if ((params->key_size < sizeof(uint64_t)) || - (!rte_is_power_of_2(params->key_size))) { - TABLE_LOG(ERR, "%s: key_size invalid value", __func__); - return -EINVAL; - } - - /* n_keys */ - if (params->n_keys == 0) { - TABLE_LOG(ERR, "%s: n_keys invalid value", __func__); - return -EINVAL; - } - - /* n_buckets */ - if ((params->n_buckets == 0) || - (!rte_is_power_of_2(params->n_buckets))) { - TABLE_LOG(ERR, "%s: n_buckets invalid value", __func__); - return -EINVAL; - } - - /* f_hash */ - if (params->f_hash == NULL) { - TABLE_LOG(ERR, "%s: f_hash invalid value", __func__); - return -EINVAL; - } - - return 0; -} - -static void * -rte_table_hash_ext_create(void *params, int socket_id, uint32_t entry_size) -{ - struct rte_table_hash_params *p = params; - struct rte_table_hash *t; - uint64_t table_meta_sz, key_mask_sz, bucket_sz, bucket_ext_sz, key_sz; - uint64_t key_stack_sz, bkt_ext_stack_sz, data_sz, total_size; - uint64_t key_mask_offset, bucket_offset, bucket_ext_offset, key_offset; - uint64_t key_stack_offset, bkt_ext_stack_offset, data_offset; - uint32_t n_buckets_ext, i; - - /* Check input parameters */ - if ((check_params_create(p) != 0) || - (!rte_is_power_of_2(entry_size)) || - ((sizeof(struct rte_table_hash) % RTE_CACHE_LINE_SIZE) != 0) || - (sizeof(struct bucket) != (RTE_CACHE_LINE_SIZE / 2))) - return NULL; - - /* - * Table dimensioning - * - * Objective: Pick the number of bucket extensions (n_buckets_ext) so that - * it is guaranteed that n_keys keys can be stored in the table at any time. - * - * The worst case scenario takes place when all the n_keys keys fall into - * the same bucket. Actually, due to the KEYS_PER_BUCKET scheme, the worst - * case takes place when (n_keys - KEYS_PER_BUCKET + 1) keys fall into the - * same bucket, while the remaining (KEYS_PER_BUCKET - 1) keys each fall - * into a different bucket. This case defeats the purpose of the hash table. - * It indicates unsuitable f_hash or n_keys to n_buckets ratio. - * - * n_buckets_ext = n_keys / KEYS_PER_BUCKET + KEYS_PER_BUCKET - 1 - */ - n_buckets_ext = p->n_keys / KEYS_PER_BUCKET + KEYS_PER_BUCKET - 1; - - /* Memory allocation */ - table_meta_sz = RTE_CACHE_LINE_ROUNDUP(sizeof(struct rte_table_hash)); - key_mask_sz = RTE_CACHE_LINE_ROUNDUP(p->key_size); - bucket_sz = RTE_CACHE_LINE_ROUNDUP(p->n_buckets * sizeof(struct bucket)); - bucket_ext_sz = - RTE_CACHE_LINE_ROUNDUP(n_buckets_ext * sizeof(struct bucket)); - key_sz = RTE_CACHE_LINE_ROUNDUP(p->n_keys * p->key_size); - key_stack_sz = RTE_CACHE_LINE_ROUNDUP(p->n_keys * sizeof(uint32_t)); - bkt_ext_stack_sz = - RTE_CACHE_LINE_ROUNDUP(n_buckets_ext * sizeof(uint32_t)); - data_sz = RTE_CACHE_LINE_ROUNDUP(p->n_keys * entry_size); - total_size = table_meta_sz + key_mask_sz + bucket_sz + bucket_ext_sz + - key_sz + key_stack_sz + bkt_ext_stack_sz + data_sz; - - if (total_size > SIZE_MAX) { - TABLE_LOG(ERR, "%s: Cannot allocate %" PRIu64 " bytes" - " for hash table %s", - __func__, total_size, p->name); - return NULL; - } - - t = rte_zmalloc_socket(p->name, - (size_t)total_size, - RTE_CACHE_LINE_SIZE, - socket_id); - if (t == NULL) { - TABLE_LOG(ERR, "%s: Cannot allocate %" PRIu64 " bytes" - " for hash table %s", - __func__, total_size, p->name); - return NULL; - } - TABLE_LOG(INFO, "%s (%u-byte key): Hash table %s memory " - "footprint is %" PRIu64 " bytes", - __func__, p->key_size, p->name, total_size); - - /* Memory initialization */ - t->key_size = p->key_size; - t->entry_size = entry_size; - t->n_keys = p->n_keys; - t->n_buckets = p->n_buckets; - t->n_buckets_ext = n_buckets_ext; - t->f_hash = p->f_hash; - t->seed = p->seed; - t->key_offset = p->key_offset; - - /* Internal */ - t->bucket_mask = t->n_buckets - 1; - t->key_size_shl = rte_ctz32(p->key_size); - t->data_size_shl = rte_ctz32(entry_size); - - /* Tables */ - key_mask_offset = 0; - bucket_offset = key_mask_offset + key_mask_sz; - bucket_ext_offset = bucket_offset + bucket_sz; - key_offset = bucket_ext_offset + bucket_ext_sz; - key_stack_offset = key_offset + key_sz; - bkt_ext_stack_offset = key_stack_offset + key_stack_sz; - data_offset = bkt_ext_stack_offset + bkt_ext_stack_sz; - - t->key_mask = (uint64_t *) &t->memory[key_mask_offset]; - t->buckets = (struct bucket *) &t->memory[bucket_offset]; - t->buckets_ext = (struct bucket *) &t->memory[bucket_ext_offset]; - t->key_mem = &t->memory[key_offset]; - t->key_stack = (uint32_t *) &t->memory[key_stack_offset]; - t->bkt_ext_stack = (uint32_t *) &t->memory[bkt_ext_stack_offset]; - t->data_mem = &t->memory[data_offset]; - - /* Key mask */ - if (p->key_mask == NULL) - memset(t->key_mask, 0xFF, p->key_size); - else - memcpy(t->key_mask, p->key_mask, p->key_size); - - /* Key stack */ - for (i = 0; i < t->n_keys; i++) - t->key_stack[i] = t->n_keys - 1 - i; - t->key_stack_tos = t->n_keys; - - /* Bucket ext stack */ - for (i = 0; i < t->n_buckets_ext; i++) - t->bkt_ext_stack[i] = t->n_buckets_ext - 1 - i; - t->bkt_ext_stack_tos = t->n_buckets_ext; - - return t; -} - -static int -rte_table_hash_ext_free(void *table) -{ - struct rte_table_hash *t = table; - - /* Check input parameters */ - if (t == NULL) - return -EINVAL; - - rte_free(t); - return 0; -} - -static int -rte_table_hash_ext_entry_add(void *table, void *key, void *entry, - int *key_found, void **entry_ptr) -{ - struct rte_table_hash *t = table; - struct bucket *bkt0, *bkt, *bkt_prev; - uint64_t sig; - uint32_t bkt_index, i; - - sig = t->f_hash(key, t->key_mask, t->key_size, t->seed); - bkt_index = sig & t->bucket_mask; - bkt0 = &t->buckets[bkt_index]; - sig = (sig >> 16) | 1LLU; - - /* Key is present in the bucket */ - for (bkt = bkt0; bkt != NULL; bkt = BUCKET_NEXT(bkt)) - for (i = 0; i < KEYS_PER_BUCKET; i++) { - uint64_t bkt_sig = (uint64_t) bkt->sig[i]; - uint32_t bkt_key_index = bkt->key_pos[i]; - uint8_t *bkt_key = - &t->key_mem[bkt_key_index << t->key_size_shl]; - - if ((sig == bkt_sig) && (keycmp(bkt_key, key, t->key_mask, - t->key_size) == 0)) { - uint8_t *data = &t->data_mem[bkt_key_index << - t->data_size_shl]; - - memcpy(data, entry, t->entry_size); - *key_found = 1; - *entry_ptr = (void *) data; - return 0; - } - } - - /* Key is not present in the bucket */ - for (bkt_prev = NULL, bkt = bkt0; bkt != NULL; bkt_prev = bkt, - bkt = BUCKET_NEXT(bkt)) - for (i = 0; i < KEYS_PER_BUCKET; i++) { - uint64_t bkt_sig = (uint64_t) bkt->sig[i]; - - if (bkt_sig == 0) { - uint32_t bkt_key_index; - uint8_t *bkt_key, *data; - - /* Allocate new key */ - if (t->key_stack_tos == 0) /* No free keys */ - return -ENOSPC; - - bkt_key_index = t->key_stack[ - --t->key_stack_tos]; - - /* Install new key */ - bkt_key = &t->key_mem[bkt_key_index << - t->key_size_shl]; - data = &t->data_mem[bkt_key_index << - t->data_size_shl]; - - bkt->sig[i] = (uint16_t) sig; - bkt->key_pos[i] = bkt_key_index; - keycpy(bkt_key, key, t->key_mask, t->key_size); - memcpy(data, entry, t->entry_size); - - *key_found = 0; - *entry_ptr = (void *) data; - return 0; - } - } - - /* Bucket full: extend bucket */ - if ((t->bkt_ext_stack_tos > 0) && (t->key_stack_tos > 0)) { - uint32_t bkt_key_index; - uint8_t *bkt_key, *data; - - /* Allocate new bucket ext */ - bkt_index = t->bkt_ext_stack[--t->bkt_ext_stack_tos]; - bkt = &t->buckets_ext[bkt_index]; - - /* Chain the new bucket ext */ - BUCKET_NEXT_SET(bkt_prev, bkt); - BUCKET_NEXT_SET_NULL(bkt); - - /* Allocate new key */ - bkt_key_index = t->key_stack[--t->key_stack_tos]; - bkt_key = &t->key_mem[bkt_key_index << t->key_size_shl]; - - data = &t->data_mem[bkt_key_index << t->data_size_shl]; - - /* Install new key into bucket */ - bkt->sig[0] = (uint16_t) sig; - bkt->key_pos[0] = bkt_key_index; - keycpy(bkt_key, key, t->key_mask, t->key_size); - memcpy(data, entry, t->entry_size); - - *key_found = 0; - *entry_ptr = (void *) data; - return 0; - } - - return -ENOSPC; -} - -static int -rte_table_hash_ext_entry_delete(void *table, void *key, int *key_found, -void *entry) -{ - struct rte_table_hash *t = table; - struct bucket *bkt0, *bkt, *bkt_prev; - uint64_t sig; - uint32_t bkt_index, i; - - sig = t->f_hash(key, t->key_mask, t->key_size, t->seed); - bkt_index = sig & t->bucket_mask; - bkt0 = &t->buckets[bkt_index]; - sig = (sig >> 16) | 1LLU; - - /* Key is present in the bucket */ - for (bkt_prev = NULL, bkt = bkt0; bkt != NULL; bkt_prev = bkt, - bkt = BUCKET_NEXT(bkt)) - for (i = 0; i < KEYS_PER_BUCKET; i++) { - uint64_t bkt_sig = (uint64_t) bkt->sig[i]; - uint32_t bkt_key_index = bkt->key_pos[i]; - uint8_t *bkt_key = &t->key_mem[bkt_key_index << - t->key_size_shl]; - - if ((sig == bkt_sig) && (keycmp(bkt_key, key, t->key_mask, - t->key_size) == 0)) { - uint8_t *data = &t->data_mem[bkt_key_index << - t->data_size_shl]; - - /* Uninstall key from bucket */ - bkt->sig[i] = 0; - *key_found = 1; - if (entry) - memcpy(entry, data, t->entry_size); - - /* Free key */ - t->key_stack[t->key_stack_tos++] = - bkt_key_index; - - /*Check if bucket is unused */ - if ((bkt_prev != NULL) && - (bkt->sig[0] == 0) && (bkt->sig[1] == 0) && - (bkt->sig[2] == 0) && (bkt->sig[3] == 0)) { - /* Unchain bucket */ - BUCKET_NEXT_COPY(bkt_prev, bkt); - - /* Clear bucket */ - memset(bkt, 0, sizeof(struct bucket)); - - /* Free bucket back to buckets ext */ - bkt_index = bkt - t->buckets_ext; - t->bkt_ext_stack[t->bkt_ext_stack_tos++] - = bkt_index; - } - - return 0; - } - } - - /* Key is not present in the bucket */ - *key_found = 0; - return 0; -} - -static int rte_table_hash_ext_lookup_unoptimized( - void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries) -{ - struct rte_table_hash *t = (struct rte_table_hash *) table; - uint64_t pkts_mask_out = 0; - - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - - for ( ; pkts_mask; ) { - struct bucket *bkt0, *bkt; - struct rte_mbuf *pkt; - uint8_t *key; - uint64_t pkt_mask, sig; - uint32_t pkt_index, bkt_index, i; - - pkt_index = rte_ctz64(pkts_mask); - pkt_mask = 1LLU << pkt_index; - pkts_mask &= ~pkt_mask; - - pkt = pkts[pkt_index]; - key = RTE_MBUF_METADATA_UINT8_PTR(pkt, t->key_offset); - sig = (uint64_t) t->f_hash(key, t->key_mask, t->key_size, t->seed); - - bkt_index = sig & t->bucket_mask; - bkt0 = &t->buckets[bkt_index]; - sig = (sig >> 16) | 1LLU; - - /* Key is present in the bucket */ - for (bkt = bkt0; bkt != NULL; bkt = BUCKET_NEXT(bkt)) - for (i = 0; i < KEYS_PER_BUCKET; i++) { - uint64_t bkt_sig = (uint64_t) bkt->sig[i]; - uint32_t bkt_key_index = bkt->key_pos[i]; - uint8_t *bkt_key = &t->key_mem[bkt_key_index << - t->key_size_shl]; - - if ((sig == bkt_sig) && (keycmp(bkt_key, key, - t->key_mask, t->key_size) == 0)) { - uint8_t *data = &t->data_mem[ - bkt_key_index << t->data_size_shl]; - - pkts_mask_out |= pkt_mask; - entries[pkt_index] = (void *) data; - break; - } - } - } - - *lookup_hit_mask = pkts_mask_out; - return 0; -} - -/* - * mask = match bitmask - * match = at least one match - * match_many = more than one match - * match_pos = position of first match - * - *---------------------------------------- - * mask match match_many match_pos - *---------------------------------------- - * 0000 0 0 00 - * 0001 1 0 00 - * 0010 1 0 01 - * 0011 1 1 00 - *---------------------------------------- - * 0100 1 0 10 - * 0101 1 1 00 - * 0110 1 1 01 - * 0111 1 1 00 - *---------------------------------------- - * 1000 1 0 11 - * 1001 1 1 00 - * 1010 1 1 01 - * 1011 1 1 00 - *---------------------------------------- - * 1100 1 1 10 - * 1101 1 1 00 - * 1110 1 1 01 - * 1111 1 1 00 - *---------------------------------------- - * - * match = 1111_1111_1111_1110 - * match_many = 1111_1110_1110_1000 - * match_pos = 0001_0010_0001_0011__0001_0010_0001_0000 - * - * match = 0xFFFELLU - * match_many = 0xFEE8LLU - * match_pos = 0x12131210LLU - */ - -#define LUT_MATCH 0xFFFELLU -#define LUT_MATCH_MANY 0xFEE8LLU -#define LUT_MATCH_POS 0x12131210LLU - -#define lookup_cmp_sig(mbuf_sig, bucket, match, match_many, match_pos) \ -{ \ - uint64_t bucket_sig[4], mask[4], mask_all; \ - \ - bucket_sig[0] = bucket->sig[0]; \ - bucket_sig[1] = bucket->sig[1]; \ - bucket_sig[2] = bucket->sig[2]; \ - bucket_sig[3] = bucket->sig[3]; \ - \ - bucket_sig[0] ^= mbuf_sig; \ - bucket_sig[1] ^= mbuf_sig; \ - bucket_sig[2] ^= mbuf_sig; \ - bucket_sig[3] ^= mbuf_sig; \ - \ - mask[0] = 0; \ - mask[1] = 0; \ - mask[2] = 0; \ - mask[3] = 0; \ - \ - if (bucket_sig[0] == 0) \ - mask[0] = 1; \ - if (bucket_sig[1] == 0) \ - mask[1] = 2; \ - if (bucket_sig[2] == 0) \ - mask[2] = 4; \ - if (bucket_sig[3] == 0) \ - mask[3] = 8; \ - \ - mask_all = (mask[0] | mask[1]) | (mask[2] | mask[3]); \ - \ - match = (LUT_MATCH >> mask_all) & 1; \ - match_many = (LUT_MATCH_MANY >> mask_all) & 1; \ - match_pos = (LUT_MATCH_POS >> (mask_all << 1)) & 3; \ -} - -#define lookup_cmp_key(mbuf, key, match_key, f) \ -{ \ - uint64_t *pkt_key = RTE_MBUF_METADATA_UINT64_PTR(mbuf, f->key_offset);\ - uint64_t *bkt_key = (uint64_t *) key; \ - uint64_t *key_mask = f->key_mask; \ - \ - switch (f->key_size) { \ - case 8: \ - { \ - uint64_t xor = (pkt_key[0] & key_mask[0]) ^ bkt_key[0]; \ - match_key = 0; \ - if (xor == 0) \ - match_key = 1; \ - } \ - break; \ - \ - case 16: \ - { \ - uint64_t xor[2], or; \ - \ - xor[0] = (pkt_key[0] & key_mask[0]) ^ bkt_key[0]; \ - xor[1] = (pkt_key[1] & key_mask[1]) ^ bkt_key[1]; \ - or = xor[0] | xor[1]; \ - match_key = 0; \ - if (or == 0) \ - match_key = 1; \ - } \ - break; \ - \ - case 32: \ - { \ - uint64_t xor[4], or; \ - \ - xor[0] = (pkt_key[0] & key_mask[0]) ^ bkt_key[0]; \ - xor[1] = (pkt_key[1] & key_mask[1]) ^ bkt_key[1]; \ - xor[2] = (pkt_key[2] & key_mask[2]) ^ bkt_key[2]; \ - xor[3] = (pkt_key[3] & key_mask[3]) ^ bkt_key[3]; \ - or = xor[0] | xor[1] | xor[2] | xor[3]; \ - match_key = 0; \ - if (or == 0) \ - match_key = 1; \ - } \ - break; \ - \ - case 64: \ - { \ - uint64_t xor[8], or; \ - \ - xor[0] = (pkt_key[0] & key_mask[0]) ^ bkt_key[0]; \ - xor[1] = (pkt_key[1] & key_mask[1]) ^ bkt_key[1]; \ - xor[2] = (pkt_key[2] & key_mask[2]) ^ bkt_key[2]; \ - xor[3] = (pkt_key[3] & key_mask[3]) ^ bkt_key[3]; \ - xor[4] = (pkt_key[4] & key_mask[4]) ^ bkt_key[4]; \ - xor[5] = (pkt_key[5] & key_mask[5]) ^ bkt_key[5]; \ - xor[6] = (pkt_key[6] & key_mask[6]) ^ bkt_key[6]; \ - xor[7] = (pkt_key[7] & key_mask[7]) ^ bkt_key[7]; \ - or = xor[0] | xor[1] | xor[2] | xor[3] | \ - xor[4] | xor[5] | xor[6] | xor[7]; \ - match_key = 0; \ - if (or == 0) \ - match_key = 1; \ - } \ - break; \ - \ - default: \ - match_key = 0; \ - if (keycmp(bkt_key, pkt_key, key_mask, f->key_size) == 0) \ - match_key = 1; \ - } \ -} - -#define lookup2_stage0(t, g, pkts, pkts_mask, pkt00_index, pkt01_index) \ -{ \ - uint64_t pkt00_mask, pkt01_mask; \ - struct rte_mbuf *mbuf00, *mbuf01; \ - uint32_t key_offset = t->key_offset; \ - \ - pkt00_index = rte_ctz64(pkts_mask); \ - pkt00_mask = 1LLU << pkt00_index; \ - pkts_mask &= ~pkt00_mask; \ - mbuf00 = pkts[pkt00_index]; \ - \ - pkt01_index = rte_ctz64(pkts_mask); \ - pkt01_mask = 1LLU << pkt01_index; \ - pkts_mask &= ~pkt01_mask; \ - mbuf01 = pkts[pkt01_index]; \ - \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf00, key_offset));\ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf01, key_offset));\ -} - -#define lookup2_stage0_with_odd_support(t, g, pkts, pkts_mask, pkt00_index, \ - pkt01_index) \ -{ \ - uint64_t pkt00_mask, pkt01_mask; \ - struct rte_mbuf *mbuf00, *mbuf01; \ - uint32_t key_offset = t->key_offset; \ - \ - pkt00_index = rte_ctz64(pkts_mask); \ - pkt00_mask = 1LLU << pkt00_index; \ - pkts_mask &= ~pkt00_mask; \ - mbuf00 = pkts[pkt00_index]; \ - \ - pkt01_index = rte_ctz64(pkts_mask); \ - if (pkts_mask == 0) \ - pkt01_index = pkt00_index; \ - pkt01_mask = 1LLU << pkt01_index; \ - pkts_mask &= ~pkt01_mask; \ - mbuf01 = pkts[pkt01_index]; \ - \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf00, key_offset));\ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf01, key_offset));\ -} - -#define lookup2_stage1(t, g, pkts, pkt10_index, pkt11_index) \ -{ \ - struct grinder *g10, *g11; \ - uint64_t sig10, sig11, bkt10_index, bkt11_index; \ - struct rte_mbuf *mbuf10, *mbuf11; \ - struct bucket *bkt10, *bkt11, *buckets = t->buckets; \ - uint8_t *key10, *key11; \ - uint64_t bucket_mask = t->bucket_mask; \ - rte_table_hash_op_hash f_hash = t->f_hash; \ - uint64_t seed = t->seed; \ - uint32_t key_size = t->key_size; \ - uint32_t key_offset = t->key_offset; \ - \ - mbuf10 = pkts[pkt10_index]; \ - key10 = RTE_MBUF_METADATA_UINT8_PTR(mbuf10, key_offset); \ - sig10 = (uint64_t) f_hash(key10, t->key_mask, key_size, seed); \ - bkt10_index = sig10 & bucket_mask; \ - bkt10 = &buckets[bkt10_index]; \ - \ - mbuf11 = pkts[pkt11_index]; \ - key11 = RTE_MBUF_METADATA_UINT8_PTR(mbuf11, key_offset); \ - sig11 = (uint64_t) f_hash(key11, t->key_mask, key_size, seed); \ - bkt11_index = sig11 & bucket_mask; \ - bkt11 = &buckets[bkt11_index]; \ - \ - rte_prefetch0(bkt10); \ - rte_prefetch0(bkt11); \ - \ - g10 = &g[pkt10_index]; \ - g10->sig = sig10; \ - g10->bkt = bkt10; \ - \ - g11 = &g[pkt11_index]; \ - g11->sig = sig11; \ - g11->bkt = bkt11; \ -} - -#define lookup2_stage2(t, g, pkt20_index, pkt21_index, pkts_mask_match_many)\ -{ \ - struct grinder *g20, *g21; \ - uint64_t sig20, sig21; \ - struct bucket *bkt20, *bkt21; \ - uint8_t *key20, *key21, *key_mem = t->key_mem; \ - uint64_t match20, match21, match_many20, match_many21; \ - uint64_t match_pos20, match_pos21; \ - uint32_t key20_index, key21_index, key_size_shl = t->key_size_shl;\ - \ - g20 = &g[pkt20_index]; \ - sig20 = g20->sig; \ - bkt20 = g20->bkt; \ - sig20 = (sig20 >> 16) | 1LLU; \ - lookup_cmp_sig(sig20, bkt20, match20, match_many20, match_pos20);\ - match20 <<= pkt20_index; \ - match_many20 |= BUCKET_NEXT_VALID(bkt20); \ - match_many20 <<= pkt20_index; \ - key20_index = bkt20->key_pos[match_pos20]; \ - key20 = &key_mem[key20_index << key_size_shl]; \ - \ - g21 = &g[pkt21_index]; \ - sig21 = g21->sig; \ - bkt21 = g21->bkt; \ - sig21 = (sig21 >> 16) | 1LLU; \ - lookup_cmp_sig(sig21, bkt21, match21, match_many21, match_pos21);\ - match21 <<= pkt21_index; \ - match_many21 |= BUCKET_NEXT_VALID(bkt21); \ - match_many21 <<= pkt21_index; \ - key21_index = bkt21->key_pos[match_pos21]; \ - key21 = &key_mem[key21_index << key_size_shl]; \ - \ - rte_prefetch0(key20); \ - rte_prefetch0(key21); \ - \ - pkts_mask_match_many |= match_many20 | match_many21; \ - \ - g20->match = match20; \ - g20->key_index = key20_index; \ - \ - g21->match = match21; \ - g21->key_index = key21_index; \ -} - -#define lookup2_stage3(t, g, pkts, pkt30_index, pkt31_index, pkts_mask_out, \ - entries) \ -{ \ - struct grinder *g30, *g31; \ - struct rte_mbuf *mbuf30, *mbuf31; \ - uint8_t *key30, *key31, *key_mem = t->key_mem; \ - uint8_t *data30, *data31, *data_mem = t->data_mem; \ - uint64_t match30, match31, match_key30, match_key31, match_keys;\ - uint32_t key30_index, key31_index; \ - uint32_t key_size_shl = t->key_size_shl; \ - uint32_t data_size_shl = t->data_size_shl; \ - \ - mbuf30 = pkts[pkt30_index]; \ - g30 = &g[pkt30_index]; \ - match30 = g30->match; \ - key30_index = g30->key_index; \ - key30 = &key_mem[key30_index << key_size_shl]; \ - lookup_cmp_key(mbuf30, key30, match_key30, t); \ - match_key30 <<= pkt30_index; \ - match_key30 &= match30; \ - data30 = &data_mem[key30_index << data_size_shl]; \ - entries[pkt30_index] = data30; \ - \ - mbuf31 = pkts[pkt31_index]; \ - g31 = &g[pkt31_index]; \ - match31 = g31->match; \ - key31_index = g31->key_index; \ - key31 = &key_mem[key31_index << key_size_shl]; \ - lookup_cmp_key(mbuf31, key31, match_key31, t); \ - match_key31 <<= pkt31_index; \ - match_key31 &= match31; \ - data31 = &data_mem[key31_index << data_size_shl]; \ - entries[pkt31_index] = data31; \ - \ - rte_prefetch0(data30); \ - rte_prefetch0(data31); \ - \ - match_keys = match_key30 | match_key31; \ - pkts_mask_out |= match_keys; \ -} - -/* - * The lookup function implements a 4-stage pipeline, with each stage processing - * two different packets. The purpose of pipelined implementation is to hide the - * latency of prefetching the data structures and loosen the data dependency - * between instructions. - * - * p00 _______ p10 _______ p20 _______ p30 _______ - *----->| |----->| |----->| |----->| |-----> - * | 0 | | 1 | | 2 | | 3 | - *----->|_______|----->|_______|----->|_______|----->|_______|-----> - * p01 p11 p21 p31 - * - * The naming convention is: - * pXY = packet Y of stage X, X = 0 .. 3, Y = 0 .. 1 - */ -static int rte_table_hash_ext_lookup( - void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries) -{ - struct rte_table_hash *t = (struct rte_table_hash *) table; - struct grinder *g = t->grinders; - uint64_t pkt00_index, pkt01_index, pkt10_index, pkt11_index; - uint64_t pkt20_index, pkt21_index, pkt30_index, pkt31_index; - uint64_t pkts_mask_out = 0, pkts_mask_match_many = 0; - int status = 0; - - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - RTE_TABLE_HASH_EXT_STATS_PKTS_IN_ADD(t, n_pkts_in); - - /* Cannot run the pipeline with less than 7 packets */ - if (rte_popcount64(pkts_mask) < 7) { - status = rte_table_hash_ext_lookup_unoptimized(table, pkts, - pkts_mask, lookup_hit_mask, entries); - RTE_TABLE_HASH_EXT_STATS_PKTS_LOOKUP_MISS(t, n_pkts_in - - rte_popcount64(*lookup_hit_mask)); - return status; - } - - /* Pipeline stage 0 */ - lookup2_stage0(t, g, pkts, pkts_mask, pkt00_index, pkt01_index); - - /* Pipeline feed */ - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0(t, g, pkts, pkts_mask, pkt00_index, pkt01_index); - - /* Pipeline stage 1 */ - lookup2_stage1(t, g, pkts, pkt10_index, pkt11_index); - - /* Pipeline feed */ - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0(t, g, pkts, pkts_mask, pkt00_index, pkt01_index); - - /* Pipeline stage 1 */ - lookup2_stage1(t, g, pkts, pkt10_index, pkt11_index); - - /* Pipeline stage 2 */ - lookup2_stage2(t, g, pkt20_index, pkt21_index, pkts_mask_match_many); - - /* - * Pipeline run - * - */ - for ( ; pkts_mask; ) { - /* Pipeline feed */ - pkt30_index = pkt20_index; - pkt31_index = pkt21_index; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0_with_odd_support(t, g, pkts, pkts_mask, - pkt00_index, pkt01_index); - - /* Pipeline stage 1 */ - lookup2_stage1(t, g, pkts, pkt10_index, pkt11_index); - - /* Pipeline stage 2 */ - lookup2_stage2(t, g, pkt20_index, pkt21_index, - pkts_mask_match_many); - - /* Pipeline stage 3 */ - lookup2_stage3(t, g, pkts, pkt30_index, pkt31_index, - pkts_mask_out, entries); - } - - /* Pipeline feed */ - pkt30_index = pkt20_index; - pkt31_index = pkt21_index; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 1 */ - lookup2_stage1(t, g, pkts, pkt10_index, pkt11_index); - - /* Pipeline stage 2 */ - lookup2_stage2(t, g, pkt20_index, pkt21_index, pkts_mask_match_many); - - /* Pipeline stage 3 */ - lookup2_stage3(t, g, pkts, pkt30_index, pkt31_index, pkts_mask_out, - entries); - - /* Pipeline feed */ - pkt30_index = pkt20_index; - pkt31_index = pkt21_index; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - - /* Pipeline stage 2 */ - lookup2_stage2(t, g, pkt20_index, pkt21_index, pkts_mask_match_many); - - /* Pipeline stage 3 */ - lookup2_stage3(t, g, pkts, pkt30_index, pkt31_index, pkts_mask_out, - entries); - - /* Pipeline feed */ - pkt30_index = pkt20_index; - pkt31_index = pkt21_index; - - /* Pipeline stage 3 */ - lookup2_stage3(t, g, pkts, pkt30_index, pkt31_index, pkts_mask_out, - entries); - - /* Slow path */ - pkts_mask_match_many &= ~pkts_mask_out; - if (pkts_mask_match_many) { - uint64_t pkts_mask_out_slow = 0; - - status = rte_table_hash_ext_lookup_unoptimized(table, pkts, - pkts_mask_match_many, &pkts_mask_out_slow, entries); - pkts_mask_out |= pkts_mask_out_slow; - } - - *lookup_hit_mask = pkts_mask_out; - RTE_TABLE_HASH_EXT_STATS_PKTS_LOOKUP_MISS(t, n_pkts_in - rte_popcount64(pkts_mask_out)); - return status; -} - -static int -rte_table_hash_ext_stats_read(void *table, struct rte_table_stats *stats, int clear) -{ - struct rte_table_hash *t = table; - - if (stats != NULL) - memcpy(stats, &t->stats, sizeof(t->stats)); - - if (clear) - memset(&t->stats, 0, sizeof(t->stats)); - - return 0; -} - -RTE_EXPORT_SYMBOL(rte_table_hash_ext_ops) -struct rte_table_ops rte_table_hash_ext_ops = { - .f_create = rte_table_hash_ext_create, - .f_free = rte_table_hash_ext_free, - .f_add = rte_table_hash_ext_entry_add, - .f_delete = rte_table_hash_ext_entry_delete, - .f_add_bulk = NULL, - .f_delete_bulk = NULL, - .f_lookup = rte_table_hash_ext_lookup, - .f_stats = rte_table_hash_ext_stats_read, -}; diff --git a/lib/table/rte_table_hash_func.h b/lib/table/rte_table_hash_func.h deleted file mode 100644 index ca56e6c885..0000000000 --- a/lib/table/rte_table_hash_func.h +++ /dev/null @@ -1,263 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2018 Intel Corporation - */ - -#ifndef __INCLUDE_RTE_TABLE_HASH_FUNC_H__ -#define __INCLUDE_RTE_TABLE_HASH_FUNC_H__ - -#include - -#include -#include - -#if defined(RTE_ARCH_X86_64) - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -static inline uint64_t -rte_crc32_u64(uint64_t crc, uint64_t v) -{ - return _mm_crc32_u64(crc, v); -} - -#ifdef __cplusplus -} -#endif - -#elif defined(RTE_ARCH_ARM64) && defined(__ARM_FEATURE_CRC32) -#include "rte_table_hash_func_arm64.h" -#else - -#ifdef __cplusplus -extern "C" { -#endif - -static inline uint64_t -rte_crc32_u64(uint64_t crc, uint64_t v) -{ - int i; - - crc = (crc & 0xFFFFFFFFLLU) ^ v; - for (i = 63; i >= 0; i--) { - uint64_t mask; - - mask = -(crc & 1LLU); - crc = (crc >> 1LLU) ^ (0x82F63B78LLU & mask); - } - - return crc; -} - -#ifdef __cplusplus -} -#endif - -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -__rte_experimental -static inline uint64_t -rte_table_hash_crc_key8(void *key, void *mask, __rte_unused uint32_t key_size, - uint64_t seed) -{ - uint64_t *k = (uint64_t *)key; - uint64_t *m = (uint64_t *)mask; - uint64_t crc0; - - crc0 = rte_crc32_u64(seed, k[0] & m[0]); - - return crc0; -} - -__rte_experimental -static inline uint64_t -rte_table_hash_crc_key16(void *key, void *mask, __rte_unused uint32_t key_size, - uint64_t seed) -{ - uint64_t *k = (uint64_t *)key; - uint64_t *m = (uint64_t *)mask; - uint64_t k0, crc0, crc1; - - k0 = k[0] & m[0]; - - crc0 = rte_crc32_u64(k0, seed); - crc1 = rte_crc32_u64(k0 >> 32, k[1] & m[1]); - - crc0 ^= crc1; - - return crc0; -} - -__rte_experimental -static inline uint64_t -rte_table_hash_crc_key24(void *key, void *mask, __rte_unused uint32_t key_size, - uint64_t seed) -{ - uint64_t *k = (uint64_t *)key; - uint64_t *m = (uint64_t *)mask; - uint64_t k0, k2, crc0, crc1; - - k0 = k[0] & m[0]; - k2 = k[2] & m[2]; - - crc0 = rte_crc32_u64(k0, seed); - crc1 = rte_crc32_u64(k0 >> 32, k[1] & m[1]); - - crc0 = rte_crc32_u64(crc0, k2); - - crc0 ^= crc1; - - return crc0; -} - -__rte_experimental -static inline uint64_t -rte_table_hash_crc_key32(void *key, void *mask, __rte_unused uint32_t key_size, - uint64_t seed) -{ - uint64_t *k = (uint64_t *)key; - uint64_t *m = (uint64_t *)mask; - uint64_t k0, k2, crc0, crc1, crc2, crc3; - - k0 = k[0] & m[0]; - k2 = k[2] & m[2]; - - crc0 = rte_crc32_u64(k0, seed); - crc1 = rte_crc32_u64(k0 >> 32, k[1] & m[1]); - - crc2 = rte_crc32_u64(k2, k[3] & m[3]); - crc3 = k2 >> 32; - - crc0 = rte_crc32_u64(crc0, crc1); - crc1 = rte_crc32_u64(crc2, crc3); - - crc0 ^= crc1; - - return crc0; -} - -__rte_experimental -static inline uint64_t -rte_table_hash_crc_key40(void *key, void *mask, __rte_unused uint32_t key_size, - uint64_t seed) -{ - uint64_t *k = (uint64_t *)key; - uint64_t *m = (uint64_t *)mask; - uint64_t k0, k2, crc0, crc1, crc2, crc3; - - k0 = k[0] & m[0]; - k2 = k[2] & m[2]; - - crc0 = rte_crc32_u64(k0, seed); - crc1 = rte_crc32_u64(k0 >> 32, k[1] & m[1]); - - crc2 = rte_crc32_u64(k2, k[3] & m[3]); - crc3 = rte_crc32_u64(k2 >> 32, k[4] & m[4]); - - crc0 = rte_crc32_u64(crc0, crc1); - crc1 = rte_crc32_u64(crc2, crc3); - - crc0 ^= crc1; - - return crc0; -} - -__rte_experimental -static inline uint64_t -rte_table_hash_crc_key48(void *key, void *mask, __rte_unused uint32_t key_size, - uint64_t seed) -{ - uint64_t *k = (uint64_t *)key; - uint64_t *m = (uint64_t *)mask; - uint64_t k0, k2, k5, crc0, crc1, crc2, crc3; - - k0 = k[0] & m[0]; - k2 = k[2] & m[2]; - k5 = k[5] & m[5]; - - crc0 = rte_crc32_u64(k0, seed); - crc1 = rte_crc32_u64(k0 >> 32, k[1] & m[1]); - - crc2 = rte_crc32_u64(k2, k[3] & m[3]); - crc3 = rte_crc32_u64(k2 >> 32, k[4] & m[4]); - - crc0 = rte_crc32_u64(crc0, (crc1 << 32) ^ crc2); - crc1 = rte_crc32_u64(crc3, k5); - - crc0 ^= crc1; - - return crc0; -} - -__rte_experimental -static inline uint64_t -rte_table_hash_crc_key56(void *key, void *mask, __rte_unused uint32_t key_size, - uint64_t seed) -{ - uint64_t *k = (uint64_t *)key; - uint64_t *m = (uint64_t *)mask; - uint64_t k0, k2, k5, crc0, crc1, crc2, crc3, crc4, crc5; - - k0 = k[0] & m[0]; - k2 = k[2] & m[2]; - k5 = k[5] & m[5]; - - crc0 = rte_crc32_u64(k0, seed); - crc1 = rte_crc32_u64(k0 >> 32, k[1] & m[1]); - - crc2 = rte_crc32_u64(k2, k[3] & m[3]); - crc3 = rte_crc32_u64(k2 >> 32, k[4] & m[4]); - - crc4 = rte_crc32_u64(k5, k[6] & m[6]); - crc5 = k5 >> 32; - - crc0 = rte_crc32_u64(crc0, (crc1 << 32) ^ crc2); - crc1 = rte_crc32_u64(crc3, (crc4 << 32) ^ crc5); - - crc0 ^= crc1; - - return crc0; -} - -__rte_experimental -static inline uint64_t -rte_table_hash_crc_key64(void *key, void *mask, __rte_unused uint32_t key_size, - uint64_t seed) -{ - uint64_t *k = (uint64_t *)key; - uint64_t *m = (uint64_t *)mask; - uint64_t k0, k2, k5, crc0, crc1, crc2, crc3, crc4, crc5; - - k0 = k[0] & m[0]; - k2 = k[2] & m[2]; - k5 = k[5] & m[5]; - - crc0 = rte_crc32_u64(k0, seed); - crc1 = rte_crc32_u64(k0 >> 32, k[1] & m[1]); - - crc2 = rte_crc32_u64(k2, k[3] & m[3]); - crc3 = rte_crc32_u64(k2 >> 32, k[4] & m[4]); - - crc4 = rte_crc32_u64(k5, k[6] & m[6]); - crc5 = rte_crc32_u64(k5 >> 32, k[7] & m[7]); - - crc0 = rte_crc32_u64(crc0, (crc1 << 32) ^ crc2); - crc1 = rte_crc32_u64(crc3, (crc4 << 32) ^ crc5); - - crc0 ^= crc1; - - return crc0; -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/lib/table/rte_table_hash_func_arm64.h b/lib/table/rte_table_hash_func_arm64.h deleted file mode 100644 index eb04c1ff54..0000000000 --- a/lib/table/rte_table_hash_func_arm64.h +++ /dev/null @@ -1,21 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2017-2018 Linaro Limited - */ - -#ifndef __INCLUDE_RTE_TABLE_HASH_FUNC_ARM64_H__ -#define __INCLUDE_RTE_TABLE_HASH_FUNC_ARM64_H__ - -#define _CRC32CX(crc, val) \ - __asm__("crc32cx %w[c], %w[c], %x[v]":[c] "+r" (crc):[v] "r" (val)) - -static inline uint64_t -rte_crc32_u64(uint64_t crc, uint64_t v) -{ - uint32_t crc32 = crc; - - _CRC32CX(crc32, v); - - return crc32; -} - -#endif diff --git a/lib/table/rte_table_hash_key16.c b/lib/table/rte_table_hash_key16.c deleted file mode 100644 index 5b69106dd7..0000000000 --- a/lib/table/rte_table_hash_key16.c +++ /dev/null @@ -1,1190 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2017 Intel Corporation - */ - -#include -#include -#include - -#include -#include -#include -#include - -#include "rte_table_hash.h" -#include "rte_lru.h" - -#include "table_log.h" - -#define KEY_SIZE 16 - -#define KEYS_PER_BUCKET 4 - -#define RTE_BUCKET_ENTRY_VALID 0x1LLU - -#ifdef RTE_TABLE_STATS_COLLECT - -#define RTE_TABLE_HASH_KEY16_STATS_PKTS_IN_ADD(table, val) \ - table->stats.n_pkts_in += val -#define RTE_TABLE_HASH_KEY16_STATS_PKTS_LOOKUP_MISS(table, val) \ - table->stats.n_pkts_lookup_miss += val - -#else - -#define RTE_TABLE_HASH_KEY16_STATS_PKTS_IN_ADD(table, val) -#define RTE_TABLE_HASH_KEY16_STATS_PKTS_LOOKUP_MISS(table, val) - -#endif - -#ifdef RTE_ARCH_64 -struct rte_bucket_4_16 { - /* Cache line 0 */ - uint64_t signature[4 + 1]; - uint64_t lru_list; - struct rte_bucket_4_16 *next; - uint64_t next_valid; - - /* Cache line 1 */ - uint64_t key[4][2]; - - /* Cache line 2 */ - uint8_t data[]; -}; -#else -struct rte_bucket_4_16 { - /* Cache line 0 */ - uint64_t signature[4 + 1]; - uint64_t lru_list; - struct rte_bucket_4_16 *next; - uint32_t pad; - uint64_t next_valid; - - /* Cache line 1 */ - uint64_t key[4][2]; - - /* Cache line 2 */ - uint8_t data[]; -}; -#endif - -struct rte_table_hash { - struct rte_table_stats stats; - - /* Input parameters */ - uint32_t n_buckets; - uint32_t key_size; - uint32_t entry_size; - uint32_t bucket_size; - uint32_t key_offset; - uint64_t key_mask[2]; - rte_table_hash_op_hash f_hash; - uint64_t seed; - - /* Extendible buckets */ - uint32_t n_buckets_ext; - uint32_t stack_pos; - uint32_t *stack; - - /* Lookup table */ - alignas(RTE_CACHE_LINE_SIZE) uint8_t memory[]; -}; - -static int -keycmp(void *a, void *b, void *b_mask) -{ - uint64_t *a64 = a, *b64 = b, *b_mask64 = b_mask; - - return (a64[0] != (b64[0] & b_mask64[0])) || - (a64[1] != (b64[1] & b_mask64[1])); -} - -static void -keycpy(void *dst, void *src, void *src_mask) -{ - uint64_t *dst64 = dst, *src64 = src, *src_mask64 = src_mask; - - dst64[0] = src64[0] & src_mask64[0]; - dst64[1] = src64[1] & src_mask64[1]; -} - -static int -check_params_create(struct rte_table_hash_params *params) -{ - /* name */ - if (params->name == NULL) { - TABLE_LOG(ERR, "%s: name invalid value", __func__); - return -EINVAL; - } - - /* key_size */ - if (params->key_size != KEY_SIZE) { - TABLE_LOG(ERR, "%s: key_size invalid value", __func__); - return -EINVAL; - } - - /* n_keys */ - if (params->n_keys == 0) { - TABLE_LOG(ERR, "%s: n_keys is zero", __func__); - return -EINVAL; - } - - /* n_buckets */ - if ((params->n_buckets == 0) || - (!rte_is_power_of_2(params->n_buckets))) { - TABLE_LOG(ERR, "%s: n_buckets invalid value", __func__); - return -EINVAL; - } - - /* f_hash */ - if (params->f_hash == NULL) { - TABLE_LOG(ERR, "%s: f_hash function pointer is NULL", - __func__); - return -EINVAL; - } - - return 0; -} - -static void * -rte_table_hash_create_key16_lru(void *params, - int socket_id, - uint32_t entry_size) -{ - struct rte_table_hash_params *p = params; - struct rte_table_hash *f; - uint64_t bucket_size, total_size; - uint32_t n_buckets, i; - - /* Check input parameters */ - if ((check_params_create(p) != 0) || - ((sizeof(struct rte_table_hash) % RTE_CACHE_LINE_SIZE) != 0) || - ((sizeof(struct rte_bucket_4_16) % 64) != 0)) - return NULL; - - /* - * Table dimensioning - * - * Objective: Pick the number of buckets (n_buckets) so that there a chance - * to store n_keys keys in the table. - * - * Note: Since the buckets do not get extended, it is not possible to - * guarantee that n_keys keys can be stored in the table at any time. In the - * worst case scenario when all the n_keys fall into the same bucket, only - * a maximum of KEYS_PER_BUCKET keys will be stored in the table. This case - * defeats the purpose of the hash table. It indicates unsuitable f_hash or - * n_keys to n_buckets ratio. - * - * MIN(n_buckets) = (n_keys + KEYS_PER_BUCKET - 1) / KEYS_PER_BUCKET - */ - n_buckets = rte_align32pow2( - (p->n_keys + KEYS_PER_BUCKET - 1) / KEYS_PER_BUCKET); - n_buckets = RTE_MAX(n_buckets, p->n_buckets); - - /* Memory allocation */ - bucket_size = RTE_CACHE_LINE_ROUNDUP(sizeof(struct rte_bucket_4_16) + - KEYS_PER_BUCKET * entry_size); - total_size = sizeof(struct rte_table_hash) + n_buckets * bucket_size; - - if (total_size > SIZE_MAX) { - TABLE_LOG(ERR, "%s: Cannot allocate %" PRIu64 " bytes " - "for hash table %s", - __func__, total_size, p->name); - return NULL; - } - - f = rte_zmalloc_socket(p->name, - (size_t)total_size, - RTE_CACHE_LINE_SIZE, - socket_id); - if (f == NULL) { - TABLE_LOG(ERR, "%s: Cannot allocate %" PRIu64 " bytes " - "for hash table %s", - __func__, total_size, p->name); - return NULL; - } - TABLE_LOG(INFO, "%s: Hash table %s memory footprint " - "is %" PRIu64 " bytes", - __func__, p->name, total_size); - - /* Memory initialization */ - f->n_buckets = n_buckets; - f->key_size = KEY_SIZE; - f->entry_size = entry_size; - f->bucket_size = bucket_size; - f->key_offset = p->key_offset; - f->f_hash = p->f_hash; - f->seed = p->seed; - - if (p->key_mask != NULL) { - f->key_mask[0] = ((uint64_t *)p->key_mask)[0]; - f->key_mask[1] = ((uint64_t *)p->key_mask)[1]; - } else { - f->key_mask[0] = 0xFFFFFFFFFFFFFFFFLLU; - f->key_mask[1] = 0xFFFFFFFFFFFFFFFFLLU; - } - - for (i = 0; i < n_buckets; i++) { - struct rte_bucket_4_16 *bucket; - - bucket = (struct rte_bucket_4_16 *) &f->memory[i * - f->bucket_size]; - lru_init(bucket); - } - - return f; -} - -static int -rte_table_hash_free_key16_lru(void *table) -{ - struct rte_table_hash *f = table; - - /* Check input parameters */ - if (f == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - - rte_free(f); - return 0; -} - -static int -rte_table_hash_entry_add_key16_lru( - void *table, - void *key, - void *entry, - int *key_found, - void **entry_ptr) -{ - struct rte_table_hash *f = table; - struct rte_bucket_4_16 *bucket; - uint64_t signature, pos; - uint32_t bucket_index, i; - - signature = f->f_hash(key, f->key_mask, f->key_size, f->seed); - bucket_index = signature & (f->n_buckets - 1); - bucket = (struct rte_bucket_4_16 *) - &f->memory[bucket_index * f->bucket_size]; - signature |= RTE_BUCKET_ENTRY_VALID; - - /* Key is present in the bucket */ - for (i = 0; i < 4; i++) { - uint64_t bucket_signature = bucket->signature[i]; - uint8_t *bucket_key = (uint8_t *) &bucket->key[i]; - - if ((bucket_signature == signature) && - (keycmp(bucket_key, key, f->key_mask) == 0)) { - uint8_t *bucket_data = &bucket->data[i * f->entry_size]; - - memcpy(bucket_data, entry, f->entry_size); - lru_update(bucket, i); - *key_found = 1; - *entry_ptr = (void *) bucket_data; - return 0; - } - } - - /* Key is not present in the bucket */ - for (i = 0; i < 4; i++) { - uint64_t bucket_signature = bucket->signature[i]; - uint8_t *bucket_key = (uint8_t *) &bucket->key[i]; - - if (bucket_signature == 0) { - uint8_t *bucket_data = &bucket->data[i * f->entry_size]; - - bucket->signature[i] = signature; - keycpy(bucket_key, key, f->key_mask); - memcpy(bucket_data, entry, f->entry_size); - lru_update(bucket, i); - *key_found = 0; - *entry_ptr = (void *) bucket_data; - - return 0; - } - } - - /* Bucket full: replace LRU entry */ - pos = lru_pos(bucket); - bucket->signature[pos] = signature; - keycpy(&bucket->key[pos], key, f->key_mask); - memcpy(&bucket->data[pos * f->entry_size], entry, f->entry_size); - lru_update(bucket, pos); - *key_found = 0; - *entry_ptr = (void *) &bucket->data[pos * f->entry_size]; - - return 0; -} - -static int -rte_table_hash_entry_delete_key16_lru( - void *table, - void *key, - int *key_found, - void *entry) -{ - struct rte_table_hash *f = table; - struct rte_bucket_4_16 *bucket; - uint64_t signature; - uint32_t bucket_index, i; - - signature = f->f_hash(key, f->key_mask, f->key_size, f->seed); - bucket_index = signature & (f->n_buckets - 1); - bucket = (struct rte_bucket_4_16 *) - &f->memory[bucket_index * f->bucket_size]; - signature |= RTE_BUCKET_ENTRY_VALID; - - /* Key is present in the bucket */ - for (i = 0; i < 4; i++) { - uint64_t bucket_signature = bucket->signature[i]; - uint8_t *bucket_key = (uint8_t *) &bucket->key[i]; - - if ((bucket_signature == signature) && - (keycmp(bucket_key, key, f->key_mask) == 0)) { - uint8_t *bucket_data = &bucket->data[i * f->entry_size]; - - bucket->signature[i] = 0; - *key_found = 1; - if (entry) - memcpy(entry, bucket_data, f->entry_size); - return 0; - } - } - - /* Key is not present in the bucket */ - *key_found = 0; - return 0; -} - -static void * -rte_table_hash_create_key16_ext(void *params, - int socket_id, - uint32_t entry_size) -{ - struct rte_table_hash_params *p = params; - struct rte_table_hash *f; - uint64_t bucket_size, stack_size, total_size; - uint32_t n_buckets_ext, i; - - /* Check input parameters */ - if ((check_params_create(p) != 0) || - ((sizeof(struct rte_table_hash) % RTE_CACHE_LINE_SIZE) != 0) || - ((sizeof(struct rte_bucket_4_16) % 64) != 0)) - return NULL; - - /* - * Table dimensioning - * - * Objective: Pick the number of bucket extensions (n_buckets_ext) so that - * it is guaranteed that n_keys keys can be stored in the table at any time. - * - * The worst case scenario takes place when all the n_keys keys fall into - * the same bucket. Actually, due to the KEYS_PER_BUCKET scheme, the worst - * case takes place when (n_keys - KEYS_PER_BUCKET + 1) keys fall into the - * same bucket, while the remaining (KEYS_PER_BUCKET - 1) keys each fall - * into a different bucket. This case defeats the purpose of the hash table. - * It indicates unsuitable f_hash or n_keys to n_buckets ratio. - * - * n_buckets_ext = n_keys / KEYS_PER_BUCKET + KEYS_PER_BUCKET - 1 - */ - n_buckets_ext = p->n_keys / KEYS_PER_BUCKET + KEYS_PER_BUCKET - 1; - - /* Memory allocation */ - bucket_size = RTE_CACHE_LINE_ROUNDUP(sizeof(struct rte_bucket_4_16) + - KEYS_PER_BUCKET * entry_size); - stack_size = RTE_CACHE_LINE_ROUNDUP(n_buckets_ext * sizeof(uint32_t)); - total_size = sizeof(struct rte_table_hash) + - (p->n_buckets + n_buckets_ext) * bucket_size + stack_size; - if (total_size > SIZE_MAX) { - TABLE_LOG(ERR, "%s: Cannot allocate %" PRIu64 " bytes " - "for hash table %s", - __func__, total_size, p->name); - return NULL; - } - - f = rte_zmalloc_socket(p->name, - (size_t)total_size, - RTE_CACHE_LINE_SIZE, - socket_id); - if (f == NULL) { - TABLE_LOG(ERR, "%s: Cannot allocate %" PRIu64 " bytes " - "for hash table %s", - __func__, total_size, p->name); - return NULL; - } - TABLE_LOG(INFO, "%s: Hash table %s memory footprint " - "is %" PRIu64 " bytes", - __func__, p->name, total_size); - - /* Memory initialization */ - f->n_buckets = p->n_buckets; - f->key_size = KEY_SIZE; - f->entry_size = entry_size; - f->bucket_size = bucket_size; - f->key_offset = p->key_offset; - f->f_hash = p->f_hash; - f->seed = p->seed; - - f->n_buckets_ext = n_buckets_ext; - f->stack_pos = n_buckets_ext; - f->stack = (uint32_t *) - &f->memory[(p->n_buckets + n_buckets_ext) * f->bucket_size]; - - if (p->key_mask != NULL) { - f->key_mask[0] = (((uint64_t *)p->key_mask)[0]); - f->key_mask[1] = (((uint64_t *)p->key_mask)[1]); - } else { - f->key_mask[0] = 0xFFFFFFFFFFFFFFFFLLU; - f->key_mask[1] = 0xFFFFFFFFFFFFFFFFLLU; - } - - for (i = 0; i < n_buckets_ext; i++) - f->stack[i] = i; - - return f; -} - -static int -rte_table_hash_free_key16_ext(void *table) -{ - struct rte_table_hash *f = table; - - /* Check input parameters */ - if (f == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - - rte_free(f); - return 0; -} - -static int -rte_table_hash_entry_add_key16_ext( - void *table, - void *key, - void *entry, - int *key_found, - void **entry_ptr) -{ - struct rte_table_hash *f = table; - struct rte_bucket_4_16 *bucket0, *bucket, *bucket_prev; - uint64_t signature; - uint32_t bucket_index, i; - - signature = f->f_hash(key, f->key_mask, f->key_size, f->seed); - bucket_index = signature & (f->n_buckets - 1); - bucket0 = (struct rte_bucket_4_16 *) - &f->memory[bucket_index * f->bucket_size]; - signature |= RTE_BUCKET_ENTRY_VALID; - - /* Key is present in the bucket */ - for (bucket = bucket0; bucket != NULL; bucket = bucket->next) - for (i = 0; i < 4; i++) { - uint64_t bucket_signature = bucket->signature[i]; - uint8_t *bucket_key = (uint8_t *) &bucket->key[i]; - - if ((bucket_signature == signature) && - (keycmp(bucket_key, key, f->key_mask) == 0)) { - uint8_t *bucket_data = &bucket->data[i * - f->entry_size]; - - memcpy(bucket_data, entry, f->entry_size); - *key_found = 1; - *entry_ptr = (void *) bucket_data; - return 0; - } - } - - /* Key is not present in the bucket */ - for (bucket_prev = NULL, bucket = bucket0; bucket != NULL; - bucket_prev = bucket, bucket = bucket->next) - for (i = 0; i < 4; i++) { - uint64_t bucket_signature = bucket->signature[i]; - uint8_t *bucket_key = (uint8_t *) &bucket->key[i]; - - if (bucket_signature == 0) { - uint8_t *bucket_data = &bucket->data[i * - f->entry_size]; - - bucket->signature[i] = signature; - keycpy(bucket_key, key, f->key_mask); - memcpy(bucket_data, entry, f->entry_size); - *key_found = 0; - *entry_ptr = (void *) bucket_data; - - return 0; - } - } - - /* Bucket full: extend bucket */ - if (f->stack_pos > 0) { - bucket_index = f->stack[--f->stack_pos]; - - bucket = (struct rte_bucket_4_16 *) &f->memory[(f->n_buckets + - bucket_index) * f->bucket_size]; - bucket_prev->next = bucket; - bucket_prev->next_valid = 1; - - bucket->signature[0] = signature; - keycpy(&bucket->key[0], key, f->key_mask); - memcpy(&bucket->data[0], entry, f->entry_size); - *key_found = 0; - *entry_ptr = (void *) &bucket->data[0]; - return 0; - } - - return -ENOSPC; -} - -static int -rte_table_hash_entry_delete_key16_ext( - void *table, - void *key, - int *key_found, - void *entry) -{ - struct rte_table_hash *f = table; - struct rte_bucket_4_16 *bucket0, *bucket, *bucket_prev; - uint64_t signature; - uint32_t bucket_index, i; - - signature = f->f_hash(key, f->key_mask, f->key_size, f->seed); - bucket_index = signature & (f->n_buckets - 1); - bucket0 = (struct rte_bucket_4_16 *) - &f->memory[bucket_index * f->bucket_size]; - signature |= RTE_BUCKET_ENTRY_VALID; - - /* Key is present in the bucket */ - for (bucket_prev = NULL, bucket = bucket0; bucket != NULL; - bucket_prev = bucket, bucket = bucket->next) - for (i = 0; i < 4; i++) { - uint64_t bucket_signature = bucket->signature[i]; - uint8_t *bucket_key = (uint8_t *) &bucket->key[i]; - - if ((bucket_signature == signature) && - (keycmp(bucket_key, key, f->key_mask) == 0)) { - uint8_t *bucket_data = &bucket->data[i * - f->entry_size]; - - bucket->signature[i] = 0; - *key_found = 1; - if (entry) - memcpy(entry, bucket_data, f->entry_size); - - if ((bucket->signature[0] == 0) && - (bucket->signature[1] == 0) && - (bucket->signature[2] == 0) && - (bucket->signature[3] == 0) && - (bucket_prev != NULL)) { - bucket_prev->next = bucket->next; - bucket_prev->next_valid = - bucket->next_valid; - - memset(bucket, 0, - sizeof(struct rte_bucket_4_16)); - bucket_index = (((uint8_t *)bucket - - (uint8_t *)f->memory)/f->bucket_size) - f->n_buckets; - f->stack[f->stack_pos++] = bucket_index; - } - - return 0; - } - } - - /* Key is not present in the bucket */ - *key_found = 0; - return 0; -} - -#define lookup_key16_cmp(key_in, bucket, pos, f) \ -{ \ - uint64_t xor[4][2], or[4], signature[4], k[2]; \ - \ - k[0] = key_in[0] & f->key_mask[0]; \ - k[1] = key_in[1] & f->key_mask[1]; \ - signature[0] = (~bucket->signature[0]) & 1; \ - signature[1] = (~bucket->signature[1]) & 1; \ - signature[2] = (~bucket->signature[2]) & 1; \ - signature[3] = (~bucket->signature[3]) & 1; \ - \ - xor[0][0] = k[0] ^ bucket->key[0][0]; \ - xor[0][1] = k[1] ^ bucket->key[0][1]; \ - \ - xor[1][0] = k[0] ^ bucket->key[1][0]; \ - xor[1][1] = k[1] ^ bucket->key[1][1]; \ - \ - xor[2][0] = k[0] ^ bucket->key[2][0]; \ - xor[2][1] = k[1] ^ bucket->key[2][1]; \ - \ - xor[3][0] = k[0] ^ bucket->key[3][0]; \ - xor[3][1] = k[1] ^ bucket->key[3][1]; \ - \ - or[0] = xor[0][0] | xor[0][1] | signature[0]; \ - or[1] = xor[1][0] | xor[1][1] | signature[1]; \ - or[2] = xor[2][0] | xor[2][1] | signature[2]; \ - or[3] = xor[3][0] | xor[3][1] | signature[3]; \ - \ - pos = 4; \ - if (or[0] == 0) \ - pos = 0; \ - if (or[1] == 0) \ - pos = 1; \ - if (or[2] == 0) \ - pos = 2; \ - if (or[3] == 0) \ - pos = 3; \ -} - -#define lookup1_stage0(pkt0_index, mbuf0, pkts, pkts_mask, f) \ -{ \ - uint64_t pkt_mask; \ - uint32_t key_offset = f->key_offset;\ - \ - pkt0_index = rte_ctz64(pkts_mask); \ - pkt_mask = 1LLU << pkt0_index; \ - pkts_mask &= ~pkt_mask; \ - \ - mbuf0 = pkts[pkt0_index]; \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf0, key_offset));\ -} - -#define lookup1_stage1(mbuf1, bucket1, f) \ -{ \ - uint64_t *key; \ - uint64_t signature = 0; \ - uint32_t bucket_index; \ - \ - key = RTE_MBUF_METADATA_UINT64_PTR(mbuf1, f->key_offset);\ - signature = f->f_hash(key, f->key_mask, KEY_SIZE, f->seed); \ - \ - bucket_index = signature & (f->n_buckets - 1); \ - bucket1 = (struct rte_bucket_4_16 *) \ - &f->memory[bucket_index * f->bucket_size]; \ - rte_prefetch0(bucket1); \ - rte_prefetch0((void *)(((uintptr_t) bucket1) + RTE_CACHE_LINE_SIZE));\ -} - -#define lookup1_stage2_lru(pkt2_index, mbuf2, bucket2, \ - pkts_mask_out, entries, f) \ -{ \ - void *a; \ - uint64_t pkt_mask; \ - uint64_t *key; \ - uint32_t pos; \ - \ - key = RTE_MBUF_METADATA_UINT64_PTR(mbuf2, f->key_offset);\ - lookup_key16_cmp(key, bucket2, pos, f); \ - \ - pkt_mask = (bucket2->signature[pos] & 1LLU) << pkt2_index;\ - pkts_mask_out |= pkt_mask; \ - \ - a = (void *) &bucket2->data[pos * f->entry_size]; \ - rte_prefetch0(a); \ - entries[pkt2_index] = a; \ - lru_update(bucket2, pos); \ -} - -#define lookup1_stage2_ext(pkt2_index, mbuf2, bucket2, pkts_mask_out, entries, \ - buckets_mask, buckets, keys, f) \ -{ \ - struct rte_bucket_4_16 *bucket_next; \ - void *a; \ - uint64_t pkt_mask, bucket_mask; \ - uint64_t *key; \ - uint32_t pos; \ - \ - key = RTE_MBUF_METADATA_UINT64_PTR(mbuf2, f->key_offset);\ - lookup_key16_cmp(key, bucket2, pos, f); \ - \ - pkt_mask = (bucket2->signature[pos] & 1LLU) << pkt2_index;\ - pkts_mask_out |= pkt_mask; \ - \ - a = (void *) &bucket2->data[pos * f->entry_size]; \ - rte_prefetch0(a); \ - entries[pkt2_index] = a; \ - \ - bucket_mask = (~pkt_mask) & (bucket2->next_valid << pkt2_index);\ - buckets_mask |= bucket_mask; \ - bucket_next = bucket2->next; \ - buckets[pkt2_index] = bucket_next; \ - keys[pkt2_index] = key; \ -} - -#define lookup_grinder(pkt_index, buckets, keys, pkts_mask_out, entries,\ - buckets_mask, f) \ -{ \ - struct rte_bucket_4_16 *bucket, *bucket_next; \ - void *a; \ - uint64_t pkt_mask, bucket_mask; \ - uint64_t *key; \ - uint32_t pos; \ - \ - bucket = buckets[pkt_index]; \ - key = keys[pkt_index]; \ - lookup_key16_cmp(key, bucket, pos, f); \ - \ - pkt_mask = (bucket->signature[pos] & 1LLU) << pkt_index;\ - pkts_mask_out |= pkt_mask; \ - \ - a = (void *) &bucket->data[pos * f->entry_size]; \ - rte_prefetch0(a); \ - entries[pkt_index] = a; \ - \ - bucket_mask = (~pkt_mask) & (bucket->next_valid << pkt_index);\ - buckets_mask |= bucket_mask; \ - bucket_next = bucket->next; \ - rte_prefetch0(bucket_next); \ - rte_prefetch0((void *)(((uintptr_t) bucket_next) + RTE_CACHE_LINE_SIZE));\ - buckets[pkt_index] = bucket_next; \ - keys[pkt_index] = key; \ -} - -#define lookup2_stage0(pkt00_index, pkt01_index, mbuf00, mbuf01,\ - pkts, pkts_mask, f) \ -{ \ - uint64_t pkt00_mask, pkt01_mask; \ - uint32_t key_offset = f->key_offset; \ - \ - pkt00_index = rte_ctz64(pkts_mask); \ - pkt00_mask = 1LLU << pkt00_index; \ - pkts_mask &= ~pkt00_mask; \ - \ - mbuf00 = pkts[pkt00_index]; \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf00, key_offset));\ - \ - pkt01_index = rte_ctz64(pkts_mask); \ - pkt01_mask = 1LLU << pkt01_index; \ - pkts_mask &= ~pkt01_mask; \ - \ - mbuf01 = pkts[pkt01_index]; \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf01, key_offset));\ -} - -#define lookup2_stage0_with_odd_support(pkt00_index, pkt01_index,\ - mbuf00, mbuf01, pkts, pkts_mask, f) \ -{ \ - uint64_t pkt00_mask, pkt01_mask; \ - uint32_t key_offset = f->key_offset; \ - \ - pkt00_index = rte_ctz64(pkts_mask); \ - pkt00_mask = 1LLU << pkt00_index; \ - pkts_mask &= ~pkt00_mask; \ - \ - mbuf00 = pkts[pkt00_index]; \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf00, key_offset)); \ - \ - pkt01_index = rte_ctz64(pkts_mask); \ - if (pkts_mask == 0) \ - pkt01_index = pkt00_index; \ - pkt01_mask = 1LLU << pkt01_index; \ - pkts_mask &= ~pkt01_mask; \ - \ - mbuf01 = pkts[pkt01_index]; \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf01, key_offset)); \ -} - -#define lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f) \ -{ \ - uint64_t *key10, *key11; \ - uint64_t signature10, signature11; \ - uint32_t bucket10_index, bucket11_index; \ - \ - key10 = RTE_MBUF_METADATA_UINT64_PTR(mbuf10, f->key_offset);\ - signature10 = f->f_hash(key10, f->key_mask, KEY_SIZE, f->seed);\ - bucket10_index = signature10 & (f->n_buckets - 1); \ - bucket10 = (struct rte_bucket_4_16 *) \ - &f->memory[bucket10_index * f->bucket_size]; \ - rte_prefetch0(bucket10); \ - rte_prefetch0((void *)(((uintptr_t) bucket10) + RTE_CACHE_LINE_SIZE));\ - \ - key11 = RTE_MBUF_METADATA_UINT64_PTR(mbuf11, f->key_offset);\ - signature11 = f->f_hash(key11, f->key_mask, KEY_SIZE, f->seed);\ - bucket11_index = signature11 & (f->n_buckets - 1); \ - bucket11 = (struct rte_bucket_4_16 *) \ - &f->memory[bucket11_index * f->bucket_size]; \ - rte_prefetch0(bucket11); \ - rte_prefetch0((void *)(((uintptr_t) bucket11) + RTE_CACHE_LINE_SIZE));\ -} - -#define lookup2_stage2_lru(pkt20_index, pkt21_index, mbuf20, mbuf21,\ - bucket20, bucket21, pkts_mask_out, entries, f) \ -{ \ - void *a20, *a21; \ - uint64_t pkt20_mask, pkt21_mask; \ - uint64_t *key20, *key21; \ - uint32_t pos20, pos21; \ - \ - key20 = RTE_MBUF_METADATA_UINT64_PTR(mbuf20, f->key_offset);\ - key21 = RTE_MBUF_METADATA_UINT64_PTR(mbuf21, f->key_offset);\ - \ - lookup_key16_cmp(key20, bucket20, pos20, f); \ - lookup_key16_cmp(key21, bucket21, pos21, f); \ - \ - pkt20_mask = (bucket20->signature[pos20] & 1LLU) << pkt20_index;\ - pkt21_mask = (bucket21->signature[pos21] & 1LLU) << pkt21_index;\ - pkts_mask_out |= pkt20_mask | pkt21_mask; \ - \ - a20 = (void *) &bucket20->data[pos20 * f->entry_size]; \ - a21 = (void *) &bucket21->data[pos21 * f->entry_size]; \ - rte_prefetch0(a20); \ - rte_prefetch0(a21); \ - entries[pkt20_index] = a20; \ - entries[pkt21_index] = a21; \ - lru_update(bucket20, pos20); \ - lru_update(bucket21, pos21); \ -} - -#define lookup2_stage2_ext(pkt20_index, pkt21_index, mbuf20, mbuf21, bucket20, \ - bucket21, pkts_mask_out, entries, buckets_mask, buckets, keys, f) \ -{ \ - struct rte_bucket_4_16 *bucket20_next, *bucket21_next; \ - void *a20, *a21; \ - uint64_t pkt20_mask, pkt21_mask, bucket20_mask, bucket21_mask;\ - uint64_t *key20, *key21; \ - uint32_t pos20, pos21; \ - \ - key20 = RTE_MBUF_METADATA_UINT64_PTR(mbuf20, f->key_offset);\ - key21 = RTE_MBUF_METADATA_UINT64_PTR(mbuf21, f->key_offset);\ - \ - lookup_key16_cmp(key20, bucket20, pos20, f); \ - lookup_key16_cmp(key21, bucket21, pos21, f); \ - \ - pkt20_mask = (bucket20->signature[pos20] & 1LLU) << pkt20_index;\ - pkt21_mask = (bucket21->signature[pos21] & 1LLU) << pkt21_index;\ - pkts_mask_out |= pkt20_mask | pkt21_mask; \ - \ - a20 = (void *) &bucket20->data[pos20 * f->entry_size]; \ - a21 = (void *) &bucket21->data[pos21 * f->entry_size]; \ - rte_prefetch0(a20); \ - rte_prefetch0(a21); \ - entries[pkt20_index] = a20; \ - entries[pkt21_index] = a21; \ - \ - bucket20_mask = (~pkt20_mask) & (bucket20->next_valid << pkt20_index);\ - bucket21_mask = (~pkt21_mask) & (bucket21->next_valid << pkt21_index);\ - buckets_mask |= bucket20_mask | bucket21_mask; \ - bucket20_next = bucket20->next; \ - bucket21_next = bucket21->next; \ - buckets[pkt20_index] = bucket20_next; \ - buckets[pkt21_index] = bucket21_next; \ - keys[pkt20_index] = key20; \ - keys[pkt21_index] = key21; \ -} - -static int -rte_table_hash_lookup_key16_lru( - void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries) -{ - struct rte_table_hash *f = (struct rte_table_hash *) table; - struct rte_bucket_4_16 *bucket10, *bucket11, *bucket20, *bucket21; - struct rte_mbuf *mbuf00, *mbuf01, *mbuf10, *mbuf11, *mbuf20, *mbuf21; - uint32_t pkt00_index, pkt01_index, pkt10_index; - uint32_t pkt11_index, pkt20_index, pkt21_index; - uint64_t pkts_mask_out = 0; - - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - - RTE_TABLE_HASH_KEY16_STATS_PKTS_IN_ADD(f, n_pkts_in); - - /* Cannot run the pipeline with less than 5 packets */ - if (rte_popcount64(pkts_mask) < 5) { - for ( ; pkts_mask; ) { - struct rte_bucket_4_16 *bucket; - struct rte_mbuf *mbuf; - uint32_t pkt_index; - - lookup1_stage0(pkt_index, mbuf, pkts, pkts_mask, f); - lookup1_stage1(mbuf, bucket, f); - lookup1_stage2_lru(pkt_index, mbuf, bucket, - pkts_mask_out, entries, f); - } - - *lookup_hit_mask = pkts_mask_out; - RTE_TABLE_HASH_KEY16_STATS_PKTS_LOOKUP_MISS(f, n_pkts_in - - rte_popcount64(pkts_mask_out)); - return 0; - } - - /* - * Pipeline fill - * - */ - /* Pipeline stage 0 */ - lookup2_stage0(pkt00_index, pkt01_index, mbuf00, mbuf01, pkts, - pkts_mask, f); - - /* Pipeline feed */ - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0(pkt00_index, pkt01_index, mbuf00, mbuf01, pkts, - pkts_mask, f); - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* - * Pipeline run - * - */ - for ( ; pkts_mask; ) { - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0_with_odd_support(pkt00_index, pkt01_index, - mbuf00, mbuf01, pkts, pkts_mask, f); - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* Pipeline stage 2 */ - lookup2_stage2_lru(pkt20_index, pkt21_index, mbuf20, mbuf21, - bucket20, bucket21, pkts_mask_out, entries, f); - } - - /* - * Pipeline flush - * - */ - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* Pipeline stage 2 */ - lookup2_stage2_lru(pkt20_index, pkt21_index, mbuf20, mbuf21, - bucket20, bucket21, pkts_mask_out, entries, f); - - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - - /* Pipeline stage 2 */ - lookup2_stage2_lru(pkt20_index, pkt21_index, mbuf20, mbuf21, - bucket20, bucket21, pkts_mask_out, entries, f); - - *lookup_hit_mask = pkts_mask_out; - RTE_TABLE_HASH_KEY16_STATS_PKTS_LOOKUP_MISS(f, n_pkts_in - - rte_popcount64(pkts_mask_out)); - return 0; -} /* lookup LRU */ - -static int -rte_table_hash_lookup_key16_ext( - void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries) -{ - struct rte_table_hash *f = (struct rte_table_hash *) table; - struct rte_bucket_4_16 *bucket10, *bucket11, *bucket20, *bucket21; - struct rte_mbuf *mbuf00, *mbuf01, *mbuf10, *mbuf11, *mbuf20, *mbuf21; - uint32_t pkt00_index, pkt01_index, pkt10_index; - uint32_t pkt11_index, pkt20_index, pkt21_index; - uint64_t pkts_mask_out = 0, buckets_mask = 0; - struct rte_bucket_4_16 *buckets[RTE_PORT_IN_BURST_SIZE_MAX]; - uint64_t *keys[RTE_PORT_IN_BURST_SIZE_MAX]; - - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - - RTE_TABLE_HASH_KEY16_STATS_PKTS_IN_ADD(f, n_pkts_in); - - /* Cannot run the pipeline with less than 5 packets */ - if (rte_popcount64(pkts_mask) < 5) { - for ( ; pkts_mask; ) { - struct rte_bucket_4_16 *bucket; - struct rte_mbuf *mbuf; - uint32_t pkt_index; - - lookup1_stage0(pkt_index, mbuf, pkts, pkts_mask, f); - lookup1_stage1(mbuf, bucket, f); - lookup1_stage2_ext(pkt_index, mbuf, bucket, - pkts_mask_out, entries, buckets_mask, - buckets, keys, f); - } - - goto grind_next_buckets; - } - - /* - * Pipeline fill - * - */ - /* Pipeline stage 0 */ - lookup2_stage0(pkt00_index, pkt01_index, mbuf00, mbuf01, pkts, - pkts_mask, f); - - /* Pipeline feed */ - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0(pkt00_index, pkt01_index, mbuf00, mbuf01, pkts, - pkts_mask, f); - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* - * Pipeline run - * - */ - for ( ; pkts_mask; ) { - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0_with_odd_support(pkt00_index, pkt01_index, - mbuf00, mbuf01, pkts, pkts_mask, f); - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* Pipeline stage 2 */ - lookup2_stage2_ext(pkt20_index, pkt21_index, mbuf20, mbuf21, - bucket20, bucket21, pkts_mask_out, entries, - buckets_mask, buckets, keys, f); - } - - /* - * Pipeline flush - * - */ - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* Pipeline stage 2 */ - lookup2_stage2_ext(pkt20_index, pkt21_index, mbuf20, mbuf21, - bucket20, bucket21, pkts_mask_out, entries, - buckets_mask, buckets, keys, f); - - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - - /* Pipeline stage 2 */ - lookup2_stage2_ext(pkt20_index, pkt21_index, mbuf20, mbuf21, - bucket20, bucket21, pkts_mask_out, entries, - buckets_mask, buckets, keys, f); - -grind_next_buckets: - /* Grind next buckets */ - for ( ; buckets_mask; ) { - uint64_t buckets_mask_next = 0; - - for ( ; buckets_mask; ) { - uint32_t pkt_index; - - pkt_index = rte_ctz64(buckets_mask); - buckets_mask &= ~(1LLU << pkt_index); - - lookup_grinder(pkt_index, buckets, keys, pkts_mask_out, - entries, buckets_mask_next, f); - } - - buckets_mask = buckets_mask_next; - } - - *lookup_hit_mask = pkts_mask_out; - RTE_TABLE_HASH_KEY16_STATS_PKTS_LOOKUP_MISS(f, n_pkts_in - - rte_popcount64(pkts_mask_out)); - return 0; -} /* lookup EXT */ - -static int -rte_table_hash_key16_stats_read(void *table, struct rte_table_stats *stats, int clear) -{ - struct rte_table_hash *t = table; - - if (stats != NULL) - memcpy(stats, &t->stats, sizeof(t->stats)); - - if (clear) - memset(&t->stats, 0, sizeof(t->stats)); - - return 0; -} - -RTE_EXPORT_SYMBOL(rte_table_hash_key16_lru_ops) -struct rte_table_ops rte_table_hash_key16_lru_ops = { - .f_create = rte_table_hash_create_key16_lru, - .f_free = rte_table_hash_free_key16_lru, - .f_add = rte_table_hash_entry_add_key16_lru, - .f_delete = rte_table_hash_entry_delete_key16_lru, - .f_add_bulk = NULL, - .f_delete_bulk = NULL, - .f_lookup = rte_table_hash_lookup_key16_lru, - .f_stats = rte_table_hash_key16_stats_read, -}; - -RTE_EXPORT_SYMBOL(rte_table_hash_key16_ext_ops) -struct rte_table_ops rte_table_hash_key16_ext_ops = { - .f_create = rte_table_hash_create_key16_ext, - .f_free = rte_table_hash_free_key16_ext, - .f_add = rte_table_hash_entry_add_key16_ext, - .f_delete = rte_table_hash_entry_delete_key16_ext, - .f_add_bulk = NULL, - .f_delete_bulk = NULL, - .f_lookup = rte_table_hash_lookup_key16_ext, - .f_stats = rte_table_hash_key16_stats_read, -}; diff --git a/lib/table/rte_table_hash_key32.c b/lib/table/rte_table_hash_key32.c deleted file mode 100644 index 0963f57828..0000000000 --- a/lib/table/rte_table_hash_key32.c +++ /dev/null @@ -1,1223 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2017 Intel Corporation - */ - -#include -#include -#include - -#include -#include -#include -#include - -#include "rte_table_hash.h" -#include "rte_lru.h" - -#include "table_log.h" - -#define KEY_SIZE 32 - -#define KEYS_PER_BUCKET 4 - -#define RTE_BUCKET_ENTRY_VALID 0x1LLU - -#ifdef RTE_TABLE_STATS_COLLECT - -#define RTE_TABLE_HASH_KEY32_STATS_PKTS_IN_ADD(table, val) \ - table->stats.n_pkts_in += val -#define RTE_TABLE_HASH_KEY32_STATS_PKTS_LOOKUP_MISS(table, val) \ - table->stats.n_pkts_lookup_miss += val - -#else - -#define RTE_TABLE_HASH_KEY32_STATS_PKTS_IN_ADD(table, val) -#define RTE_TABLE_HASH_KEY32_STATS_PKTS_LOOKUP_MISS(table, val) - -#endif - -#ifdef RTE_ARCH_64 -struct rte_bucket_4_32 { - /* Cache line 0 */ - uint64_t signature[4 + 1]; - uint64_t lru_list; - struct rte_bucket_4_32 *next; - uint64_t next_valid; - - /* Cache lines 1 and 2 */ - uint64_t key[4][4]; - - /* Cache line 3 */ - uint8_t data[]; -}; -#else -struct rte_bucket_4_32 { - /* Cache line 0 */ - uint64_t signature[4 + 1]; - uint64_t lru_list; - struct rte_bucket_4_32 *next; - uint32_t pad; - uint64_t next_valid; - - /* Cache lines 1 and 2 */ - uint64_t key[4][4]; - - /* Cache line 3 */ - uint8_t data[]; -}; -#endif - -struct rte_table_hash { - struct rte_table_stats stats; - - /* Input parameters */ - uint32_t n_buckets; - uint32_t key_size; - uint32_t entry_size; - uint32_t bucket_size; - uint32_t key_offset; - uint64_t key_mask[4]; - rte_table_hash_op_hash f_hash; - uint64_t seed; - - /* Extendible buckets */ - uint32_t n_buckets_ext; - uint32_t stack_pos; - uint32_t *stack; - - /* Lookup table */ - alignas(RTE_CACHE_LINE_SIZE) uint8_t memory[]; -}; - -static int -keycmp(void *a, void *b, void *b_mask) -{ - uint64_t *a64 = a, *b64 = b, *b_mask64 = b_mask; - - return (a64[0] != (b64[0] & b_mask64[0])) || - (a64[1] != (b64[1] & b_mask64[1])) || - (a64[2] != (b64[2] & b_mask64[2])) || - (a64[3] != (b64[3] & b_mask64[3])); -} - -static void -keycpy(void *dst, void *src, void *src_mask) -{ - uint64_t *dst64 = dst, *src64 = src, *src_mask64 = src_mask; - - dst64[0] = src64[0] & src_mask64[0]; - dst64[1] = src64[1] & src_mask64[1]; - dst64[2] = src64[2] & src_mask64[2]; - dst64[3] = src64[3] & src_mask64[3]; -} - -static int -check_params_create(struct rte_table_hash_params *params) -{ - /* name */ - if (params->name == NULL) { - TABLE_LOG(ERR, "%s: name invalid value", __func__); - return -EINVAL; - } - - /* key_size */ - if (params->key_size != KEY_SIZE) { - TABLE_LOG(ERR, "%s: key_size invalid value", __func__); - return -EINVAL; - } - - /* n_keys */ - if (params->n_keys == 0) { - TABLE_LOG(ERR, "%s: n_keys is zero", __func__); - return -EINVAL; - } - - /* n_buckets */ - if ((params->n_buckets == 0) || - (!rte_is_power_of_2(params->n_buckets))) { - TABLE_LOG(ERR, "%s: n_buckets invalid value", __func__); - return -EINVAL; - } - - /* f_hash */ - if (params->f_hash == NULL) { - TABLE_LOG(ERR, "%s: f_hash function pointer is NULL", - __func__); - return -EINVAL; - } - - return 0; -} - -static void * -rte_table_hash_create_key32_lru(void *params, - int socket_id, - uint32_t entry_size) -{ - struct rte_table_hash_params *p = params; - struct rte_table_hash *f; - uint64_t bucket_size, total_size; - uint32_t n_buckets, i; - - /* Check input parameters */ - if ((check_params_create(p) != 0) || - ((sizeof(struct rte_table_hash) % RTE_CACHE_LINE_SIZE) != 0) || - ((sizeof(struct rte_bucket_4_32) % 64) != 0)) - return NULL; - - /* - * Table dimensioning - * - * Objective: Pick the number of buckets (n_buckets) so that there a chance - * to store n_keys keys in the table. - * - * Note: Since the buckets do not get extended, it is not possible to - * guarantee that n_keys keys can be stored in the table at any time. In the - * worst case scenario when all the n_keys fall into the same bucket, only - * a maximum of KEYS_PER_BUCKET keys will be stored in the table. This case - * defeats the purpose of the hash table. It indicates unsuitable f_hash or - * n_keys to n_buckets ratio. - * - * MIN(n_buckets) = (n_keys + KEYS_PER_BUCKET - 1) / KEYS_PER_BUCKET - */ - n_buckets = rte_align32pow2( - (p->n_keys + KEYS_PER_BUCKET - 1) / KEYS_PER_BUCKET); - n_buckets = RTE_MAX(n_buckets, p->n_buckets); - - /* Memory allocation */ - bucket_size = RTE_CACHE_LINE_ROUNDUP(sizeof(struct rte_bucket_4_32) + - KEYS_PER_BUCKET * entry_size); - total_size = sizeof(struct rte_table_hash) + n_buckets * bucket_size; - if (total_size > SIZE_MAX) { - TABLE_LOG(ERR, "%s: Cannot allocate %" PRIu64 " bytes " - "for hash table %s", - __func__, total_size, p->name); - return NULL; - } - - f = rte_zmalloc_socket(p->name, - (size_t)total_size, - RTE_CACHE_LINE_SIZE, - socket_id); - if (f == NULL) { - TABLE_LOG(ERR, "%s: Cannot allocate %" PRIu64 " bytes " - "for hash table %s", - __func__, total_size, p->name); - return NULL; - } - TABLE_LOG(INFO, - "%s: Hash table %s memory footprint " - "is %" PRIu64 " bytes", - __func__, p->name, total_size); - - /* Memory initialization */ - f->n_buckets = n_buckets; - f->key_size = KEY_SIZE; - f->entry_size = entry_size; - f->bucket_size = bucket_size; - f->key_offset = p->key_offset; - f->f_hash = p->f_hash; - f->seed = p->seed; - - if (p->key_mask != NULL) { - f->key_mask[0] = ((uint64_t *)p->key_mask)[0]; - f->key_mask[1] = ((uint64_t *)p->key_mask)[1]; - f->key_mask[2] = ((uint64_t *)p->key_mask)[2]; - f->key_mask[3] = ((uint64_t *)p->key_mask)[3]; - } else { - f->key_mask[0] = 0xFFFFFFFFFFFFFFFFLLU; - f->key_mask[1] = 0xFFFFFFFFFFFFFFFFLLU; - f->key_mask[2] = 0xFFFFFFFFFFFFFFFFLLU; - f->key_mask[3] = 0xFFFFFFFFFFFFFFFFLLU; - } - - for (i = 0; i < n_buckets; i++) { - struct rte_bucket_4_32 *bucket; - - bucket = (struct rte_bucket_4_32 *) &f->memory[i * - f->bucket_size]; - bucket->lru_list = 0x0000000100020003LLU; - } - - return f; -} - -static int -rte_table_hash_free_key32_lru(void *table) -{ - struct rte_table_hash *f = table; - - /* Check input parameters */ - if (f == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - - rte_free(f); - return 0; -} - -static int -rte_table_hash_entry_add_key32_lru( - void *table, - void *key, - void *entry, - int *key_found, - void **entry_ptr) -{ - struct rte_table_hash *f = table; - struct rte_bucket_4_32 *bucket; - uint64_t signature, pos; - uint32_t bucket_index, i; - - signature = f->f_hash(key, f->key_mask, f->key_size, f->seed); - bucket_index = signature & (f->n_buckets - 1); - bucket = (struct rte_bucket_4_32 *) - &f->memory[bucket_index * f->bucket_size]; - signature |= RTE_BUCKET_ENTRY_VALID; - - /* Key is present in the bucket */ - for (i = 0; i < 4; i++) { - uint64_t bucket_signature = bucket->signature[i]; - uint8_t *bucket_key = (uint8_t *) &bucket->key[i]; - - if ((bucket_signature == signature) && - (keycmp(bucket_key, key, f->key_mask) == 0)) { - uint8_t *bucket_data = &bucket->data[i * f->entry_size]; - - memcpy(bucket_data, entry, f->entry_size); - lru_update(bucket, i); - *key_found = 1; - *entry_ptr = (void *) bucket_data; - return 0; - } - } - - /* Key is not present in the bucket */ - for (i = 0; i < 4; i++) { - uint64_t bucket_signature = bucket->signature[i]; - uint8_t *bucket_key = (uint8_t *) &bucket->key[i]; - - if (bucket_signature == 0) { - uint8_t *bucket_data = &bucket->data[i * f->entry_size]; - - bucket->signature[i] = signature; - keycpy(bucket_key, key, f->key_mask); - memcpy(bucket_data, entry, f->entry_size); - lru_update(bucket, i); - *key_found = 0; - *entry_ptr = (void *) bucket_data; - - return 0; - } - } - - /* Bucket full: replace LRU entry */ - pos = lru_pos(bucket); - bucket->signature[pos] = signature; - keycpy(&bucket->key[pos], key, f->key_mask); - memcpy(&bucket->data[pos * f->entry_size], entry, f->entry_size); - lru_update(bucket, pos); - *key_found = 0; - *entry_ptr = (void *) &bucket->data[pos * f->entry_size]; - - return 0; -} - -static int -rte_table_hash_entry_delete_key32_lru( - void *table, - void *key, - int *key_found, - void *entry) -{ - struct rte_table_hash *f = table; - struct rte_bucket_4_32 *bucket; - uint64_t signature; - uint32_t bucket_index, i; - - signature = f->f_hash(key, f->key_mask, f->key_size, f->seed); - bucket_index = signature & (f->n_buckets - 1); - bucket = (struct rte_bucket_4_32 *) - &f->memory[bucket_index * f->bucket_size]; - signature |= RTE_BUCKET_ENTRY_VALID; - - /* Key is present in the bucket */ - for (i = 0; i < 4; i++) { - uint64_t bucket_signature = bucket->signature[i]; - uint8_t *bucket_key = (uint8_t *) &bucket->key[i]; - - if ((bucket_signature == signature) && - (keycmp(bucket_key, key, f->key_mask) == 0)) { - uint8_t *bucket_data = &bucket->data[i * f->entry_size]; - - bucket->signature[i] = 0; - *key_found = 1; - if (entry) - memcpy(entry, bucket_data, f->entry_size); - - return 0; - } - } - - /* Key is not present in the bucket */ - *key_found = 0; - return 0; -} - -static void * -rte_table_hash_create_key32_ext(void *params, - int socket_id, - uint32_t entry_size) -{ - struct rte_table_hash_params *p = params; - struct rte_table_hash *f; - uint64_t bucket_size, stack_size, total_size; - uint32_t n_buckets_ext, i; - - /* Check input parameters */ - if ((check_params_create(p) != 0) || - ((sizeof(struct rte_table_hash) % RTE_CACHE_LINE_SIZE) != 0) || - ((sizeof(struct rte_bucket_4_32) % 64) != 0)) - return NULL; - - /* - * Table dimensioning - * - * Objective: Pick the number of bucket extensions (n_buckets_ext) so that - * it is guaranteed that n_keys keys can be stored in the table at any time. - * - * The worst case scenario takes place when all the n_keys keys fall into - * the same bucket. Actually, due to the KEYS_PER_BUCKET scheme, the worst - * case takes place when (n_keys - KEYS_PER_BUCKET + 1) keys fall into the - * same bucket, while the remaining (KEYS_PER_BUCKET - 1) keys each fall - * into a different bucket. This case defeats the purpose of the hash table. - * It indicates unsuitable f_hash or n_keys to n_buckets ratio. - * - * n_buckets_ext = n_keys / KEYS_PER_BUCKET + KEYS_PER_BUCKET - 1 - */ - n_buckets_ext = p->n_keys / KEYS_PER_BUCKET + KEYS_PER_BUCKET - 1; - - /* Memory allocation */ - bucket_size = RTE_CACHE_LINE_ROUNDUP(sizeof(struct rte_bucket_4_32) + - KEYS_PER_BUCKET * entry_size); - stack_size = RTE_CACHE_LINE_ROUNDUP(n_buckets_ext * sizeof(uint32_t)); - total_size = sizeof(struct rte_table_hash) + - (p->n_buckets + n_buckets_ext) * bucket_size + stack_size; - if (total_size > SIZE_MAX) { - TABLE_LOG(ERR, "%s: Cannot allocate %" PRIu64 " bytes " - "for hash table %s", - __func__, total_size, p->name); - return NULL; - } - - f = rte_zmalloc_socket(p->name, - (size_t)total_size, - RTE_CACHE_LINE_SIZE, - socket_id); - if (f == NULL) { - TABLE_LOG(ERR, "%s: Cannot allocate %" PRIu64 " bytes " - "for hash table %s", - __func__, total_size, p->name); - return NULL; - } - TABLE_LOG(INFO, - "%s: Hash table %s memory footprint " - "is %" PRIu64" bytes", - __func__, p->name, total_size); - - /* Memory initialization */ - f->n_buckets = p->n_buckets; - f->key_size = KEY_SIZE; - f->entry_size = entry_size; - f->bucket_size = bucket_size; - f->key_offset = p->key_offset; - f->f_hash = p->f_hash; - f->seed = p->seed; - - f->n_buckets_ext = n_buckets_ext; - f->stack_pos = n_buckets_ext; - f->stack = (uint32_t *) - &f->memory[(p->n_buckets + n_buckets_ext) * f->bucket_size]; - - if (p->key_mask != NULL) { - f->key_mask[0] = (((uint64_t *)p->key_mask)[0]); - f->key_mask[1] = (((uint64_t *)p->key_mask)[1]); - f->key_mask[2] = (((uint64_t *)p->key_mask)[2]); - f->key_mask[3] = (((uint64_t *)p->key_mask)[3]); - } else { - f->key_mask[0] = 0xFFFFFFFFFFFFFFFFLLU; - f->key_mask[1] = 0xFFFFFFFFFFFFFFFFLLU; - f->key_mask[2] = 0xFFFFFFFFFFFFFFFFLLU; - f->key_mask[3] = 0xFFFFFFFFFFFFFFFFLLU; - } - - for (i = 0; i < n_buckets_ext; i++) - f->stack[i] = i; - - return f; -} - -static int -rte_table_hash_free_key32_ext(void *table) -{ - struct rte_table_hash *f = table; - - /* Check input parameters */ - if (f == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - - rte_free(f); - return 0; -} - -static int -rte_table_hash_entry_add_key32_ext( - void *table, - void *key, - void *entry, - int *key_found, - void **entry_ptr) -{ - struct rte_table_hash *f = table; - struct rte_bucket_4_32 *bucket0, *bucket, *bucket_prev; - uint64_t signature; - uint32_t bucket_index, i; - - signature = f->f_hash(key, f->key_mask, f->key_size, f->seed); - bucket_index = signature & (f->n_buckets - 1); - bucket0 = (struct rte_bucket_4_32 *) - &f->memory[bucket_index * f->bucket_size]; - signature |= RTE_BUCKET_ENTRY_VALID; - - /* Key is present in the bucket */ - for (bucket = bucket0; bucket != NULL; bucket = bucket->next) { - for (i = 0; i < 4; i++) { - uint64_t bucket_signature = bucket->signature[i]; - uint8_t *bucket_key = (uint8_t *) &bucket->key[i]; - - if ((bucket_signature == signature) && - (keycmp(bucket_key, key, f->key_mask) == 0)) { - uint8_t *bucket_data = &bucket->data[i * - f->entry_size]; - - memcpy(bucket_data, entry, f->entry_size); - *key_found = 1; - *entry_ptr = (void *) bucket_data; - - return 0; - } - } - } - - /* Key is not present in the bucket */ - for (bucket_prev = NULL, bucket = bucket0; bucket != NULL; - bucket_prev = bucket, bucket = bucket->next) - for (i = 0; i < 4; i++) { - uint64_t bucket_signature = bucket->signature[i]; - uint8_t *bucket_key = (uint8_t *) &bucket->key[i]; - - if (bucket_signature == 0) { - uint8_t *bucket_data = &bucket->data[i * - f->entry_size]; - - bucket->signature[i] = signature; - keycpy(bucket_key, key, f->key_mask); - memcpy(bucket_data, entry, f->entry_size); - *key_found = 0; - *entry_ptr = (void *) bucket_data; - - return 0; - } - } - - /* Bucket full: extend bucket */ - if (f->stack_pos > 0) { - bucket_index = f->stack[--f->stack_pos]; - - bucket = (struct rte_bucket_4_32 *) - &f->memory[(f->n_buckets + bucket_index) * - f->bucket_size]; - bucket_prev->next = bucket; - bucket_prev->next_valid = 1; - - bucket->signature[0] = signature; - keycpy(&bucket->key[0], key, f->key_mask); - memcpy(&bucket->data[0], entry, f->entry_size); - *key_found = 0; - *entry_ptr = (void *) &bucket->data[0]; - return 0; - } - - return -ENOSPC; -} - -static int -rte_table_hash_entry_delete_key32_ext( - void *table, - void *key, - int *key_found, - void *entry) -{ - struct rte_table_hash *f = table; - struct rte_bucket_4_32 *bucket0, *bucket, *bucket_prev; - uint64_t signature; - uint32_t bucket_index, i; - - signature = f->f_hash(key, f->key_mask, f->key_size, f->seed); - bucket_index = signature & (f->n_buckets - 1); - bucket0 = (struct rte_bucket_4_32 *) - &f->memory[bucket_index * f->bucket_size]; - signature |= RTE_BUCKET_ENTRY_VALID; - - /* Key is present in the bucket */ - for (bucket_prev = NULL, bucket = bucket0; bucket != NULL; - bucket_prev = bucket, bucket = bucket->next) - for (i = 0; i < 4; i++) { - uint64_t bucket_signature = bucket->signature[i]; - uint8_t *bucket_key = (uint8_t *) &bucket->key[i]; - - if ((bucket_signature == signature) && - (keycmp(bucket_key, key, f->key_mask) == 0)) { - uint8_t *bucket_data = &bucket->data[i * - f->entry_size]; - - bucket->signature[i] = 0; - *key_found = 1; - if (entry) - memcpy(entry, bucket_data, f->entry_size); - - if ((bucket->signature[0] == 0) && - (bucket->signature[1] == 0) && - (bucket->signature[2] == 0) && - (bucket->signature[3] == 0) && - (bucket_prev != NULL)) { - bucket_prev->next = bucket->next; - bucket_prev->next_valid = - bucket->next_valid; - - memset(bucket, 0, - sizeof(struct rte_bucket_4_32)); - bucket_index = (((uint8_t *)bucket - - (uint8_t *)f->memory)/f->bucket_size) - f->n_buckets; - f->stack[f->stack_pos++] = bucket_index; - } - - return 0; - } - } - - /* Key is not present in the bucket */ - *key_found = 0; - return 0; -} - -#define lookup_key32_cmp(key_in, bucket, pos, f) \ -{ \ - uint64_t xor[4][4], or[4], signature[4], k[4]; \ - \ - k[0] = key_in[0] & f->key_mask[0]; \ - k[1] = key_in[1] & f->key_mask[1]; \ - k[2] = key_in[2] & f->key_mask[2]; \ - k[3] = key_in[3] & f->key_mask[3]; \ - \ - signature[0] = ((~bucket->signature[0]) & 1); \ - signature[1] = ((~bucket->signature[1]) & 1); \ - signature[2] = ((~bucket->signature[2]) & 1); \ - signature[3] = ((~bucket->signature[3]) & 1); \ - \ - xor[0][0] = k[0] ^ bucket->key[0][0]; \ - xor[0][1] = k[1] ^ bucket->key[0][1]; \ - xor[0][2] = k[2] ^ bucket->key[0][2]; \ - xor[0][3] = k[3] ^ bucket->key[0][3]; \ - \ - xor[1][0] = k[0] ^ bucket->key[1][0]; \ - xor[1][1] = k[1] ^ bucket->key[1][1]; \ - xor[1][2] = k[2] ^ bucket->key[1][2]; \ - xor[1][3] = k[3] ^ bucket->key[1][3]; \ - \ - xor[2][0] = k[0] ^ bucket->key[2][0]; \ - xor[2][1] = k[1] ^ bucket->key[2][1]; \ - xor[2][2] = k[2] ^ bucket->key[2][2]; \ - xor[2][3] = k[3] ^ bucket->key[2][3]; \ - \ - xor[3][0] = k[0] ^ bucket->key[3][0]; \ - xor[3][1] = k[1] ^ bucket->key[3][1]; \ - xor[3][2] = k[2] ^ bucket->key[3][2]; \ - xor[3][3] = k[3] ^ bucket->key[3][3]; \ - \ - or[0] = xor[0][0] | xor[0][1] | xor[0][2] | xor[0][3] | signature[0];\ - or[1] = xor[1][0] | xor[1][1] | xor[1][2] | xor[1][3] | signature[1];\ - or[2] = xor[2][0] | xor[2][1] | xor[2][2] | xor[2][3] | signature[2];\ - or[3] = xor[3][0] | xor[3][1] | xor[3][2] | xor[3][3] | signature[3];\ - \ - pos = 4; \ - if (or[0] == 0) \ - pos = 0; \ - if (or[1] == 0) \ - pos = 1; \ - if (or[2] == 0) \ - pos = 2; \ - if (or[3] == 0) \ - pos = 3; \ -} - -#define lookup1_stage0(pkt0_index, mbuf0, pkts, pkts_mask, f) \ -{ \ - uint64_t pkt_mask; \ - uint32_t key_offset = f->key_offset; \ - \ - pkt0_index = rte_ctz64(pkts_mask); \ - pkt_mask = 1LLU << pkt0_index; \ - pkts_mask &= ~pkt_mask; \ - \ - mbuf0 = pkts[pkt0_index]; \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf0, key_offset));\ -} - -#define lookup1_stage1(mbuf1, bucket1, f) \ -{ \ - uint64_t *key; \ - uint64_t signature; \ - uint32_t bucket_index; \ - \ - key = RTE_MBUF_METADATA_UINT64_PTR(mbuf1, f->key_offset); \ - signature = f->f_hash(key, f->key_mask, KEY_SIZE, f->seed); \ - \ - bucket_index = signature & (f->n_buckets - 1); \ - bucket1 = (struct rte_bucket_4_32 *) \ - &f->memory[bucket_index * f->bucket_size]; \ - rte_prefetch0(bucket1); \ - rte_prefetch0((void *)(((uintptr_t) bucket1) + RTE_CACHE_LINE_SIZE));\ - rte_prefetch0((void *)(((uintptr_t) bucket1) + 2 * RTE_CACHE_LINE_SIZE));\ -} - -#define lookup1_stage2_lru(pkt2_index, mbuf2, bucket2, \ - pkts_mask_out, entries, f) \ -{ \ - void *a; \ - uint64_t pkt_mask; \ - uint64_t *key; \ - uint32_t pos; \ - \ - key = RTE_MBUF_METADATA_UINT64_PTR(mbuf2, f->key_offset);\ - lookup_key32_cmp(key, bucket2, pos, f); \ - \ - pkt_mask = (bucket2->signature[pos] & 1LLU) << pkt2_index;\ - pkts_mask_out |= pkt_mask; \ - \ - a = (void *) &bucket2->data[pos * f->entry_size]; \ - rte_prefetch0(a); \ - entries[pkt2_index] = a; \ - lru_update(bucket2, pos); \ -} - -#define lookup1_stage2_ext(pkt2_index, mbuf2, bucket2, pkts_mask_out,\ - entries, buckets_mask, buckets, keys, f) \ -{ \ - struct rte_bucket_4_32 *bucket_next; \ - void *a; \ - uint64_t pkt_mask, bucket_mask; \ - uint64_t *key; \ - uint32_t pos; \ - \ - key = RTE_MBUF_METADATA_UINT64_PTR(mbuf2, f->key_offset);\ - lookup_key32_cmp(key, bucket2, pos, f); \ - \ - pkt_mask = (bucket2->signature[pos] & 1LLU) << pkt2_index;\ - pkts_mask_out |= pkt_mask; \ - \ - a = (void *) &bucket2->data[pos * f->entry_size]; \ - rte_prefetch0(a); \ - entries[pkt2_index] = a; \ - \ - bucket_mask = (~pkt_mask) & (bucket2->next_valid << pkt2_index);\ - buckets_mask |= bucket_mask; \ - bucket_next = bucket2->next; \ - buckets[pkt2_index] = bucket_next; \ - keys[pkt2_index] = key; \ -} - -#define lookup_grinder(pkt_index, buckets, keys, pkts_mask_out, \ - entries, buckets_mask, f) \ -{ \ - struct rte_bucket_4_32 *bucket, *bucket_next; \ - void *a; \ - uint64_t pkt_mask, bucket_mask; \ - uint64_t *key; \ - uint32_t pos; \ - \ - bucket = buckets[pkt_index]; \ - key = keys[pkt_index]; \ - \ - lookup_key32_cmp(key, bucket, pos, f); \ - \ - pkt_mask = (bucket->signature[pos] & 1LLU) << pkt_index;\ - pkts_mask_out |= pkt_mask; \ - \ - a = (void *) &bucket->data[pos * f->entry_size]; \ - rte_prefetch0(a); \ - entries[pkt_index] = a; \ - \ - bucket_mask = (~pkt_mask) & (bucket->next_valid << pkt_index);\ - buckets_mask |= bucket_mask; \ - bucket_next = bucket->next; \ - rte_prefetch0(bucket_next); \ - rte_prefetch0((void *)(((uintptr_t) bucket_next) + RTE_CACHE_LINE_SIZE));\ - rte_prefetch0((void *)(((uintptr_t) bucket_next) + \ - 2 * RTE_CACHE_LINE_SIZE)); \ - buckets[pkt_index] = bucket_next; \ - keys[pkt_index] = key; \ -} - -#define lookup2_stage0(pkt00_index, pkt01_index, mbuf00, mbuf01,\ - pkts, pkts_mask, f) \ -{ \ - uint64_t pkt00_mask, pkt01_mask; \ - uint32_t key_offset = f->key_offset; \ - \ - pkt00_index = rte_ctz64(pkts_mask); \ - pkt00_mask = 1LLU << pkt00_index; \ - pkts_mask &= ~pkt00_mask; \ - \ - mbuf00 = pkts[pkt00_index]; \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf00, key_offset));\ - \ - pkt01_index = rte_ctz64(pkts_mask); \ - pkt01_mask = 1LLU << pkt01_index; \ - pkts_mask &= ~pkt01_mask; \ - \ - mbuf01 = pkts[pkt01_index]; \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf01, key_offset));\ -} - -#define lookup2_stage0_with_odd_support(pkt00_index, pkt01_index,\ - mbuf00, mbuf01, pkts, pkts_mask, f) \ -{ \ - uint64_t pkt00_mask, pkt01_mask; \ - uint32_t key_offset = f->key_offset; \ - \ - pkt00_index = rte_ctz64(pkts_mask); \ - pkt00_mask = 1LLU << pkt00_index; \ - pkts_mask &= ~pkt00_mask; \ - \ - mbuf00 = pkts[pkt00_index]; \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf00, key_offset)); \ - \ - pkt01_index = rte_ctz64(pkts_mask); \ - if (pkts_mask == 0) \ - pkt01_index = pkt00_index; \ - \ - pkt01_mask = 1LLU << pkt01_index; \ - pkts_mask &= ~pkt01_mask; \ - \ - mbuf01 = pkts[pkt01_index]; \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf01, key_offset)); \ -} - -#define lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f) \ -{ \ - uint64_t *key10, *key11; \ - uint64_t signature10, signature11; \ - uint32_t bucket10_index, bucket11_index; \ - \ - key10 = RTE_MBUF_METADATA_UINT64_PTR(mbuf10, f->key_offset); \ - signature10 = f->f_hash(key10, f->key_mask, KEY_SIZE, f->seed); \ - \ - bucket10_index = signature10 & (f->n_buckets - 1); \ - bucket10 = (struct rte_bucket_4_32 *) \ - &f->memory[bucket10_index * f->bucket_size]; \ - rte_prefetch0(bucket10); \ - rte_prefetch0((void *)(((uintptr_t) bucket10) + RTE_CACHE_LINE_SIZE));\ - rte_prefetch0((void *)(((uintptr_t) bucket10) + 2 * RTE_CACHE_LINE_SIZE));\ - \ - key11 = RTE_MBUF_METADATA_UINT64_PTR(mbuf11, f->key_offset); \ - signature11 = f->f_hash(key11, f->key_mask, KEY_SIZE, f->seed);\ - \ - bucket11_index = signature11 & (f->n_buckets - 1); \ - bucket11 = (struct rte_bucket_4_32 *) \ - &f->memory[bucket11_index * f->bucket_size]; \ - rte_prefetch0(bucket11); \ - rte_prefetch0((void *)(((uintptr_t) bucket11) + RTE_CACHE_LINE_SIZE));\ - rte_prefetch0((void *)(((uintptr_t) bucket11) + 2 * RTE_CACHE_LINE_SIZE));\ -} - -#define lookup2_stage2_lru(pkt20_index, pkt21_index, mbuf20, mbuf21,\ - bucket20, bucket21, pkts_mask_out, entries, f) \ -{ \ - void *a20, *a21; \ - uint64_t pkt20_mask, pkt21_mask; \ - uint64_t *key20, *key21; \ - uint32_t pos20, pos21; \ - \ - key20 = RTE_MBUF_METADATA_UINT64_PTR(mbuf20, f->key_offset);\ - key21 = RTE_MBUF_METADATA_UINT64_PTR(mbuf21, f->key_offset);\ - \ - lookup_key32_cmp(key20, bucket20, pos20, f); \ - lookup_key32_cmp(key21, bucket21, pos21, f); \ - \ - pkt20_mask = (bucket20->signature[pos20] & 1LLU) << pkt20_index;\ - pkt21_mask = (bucket21->signature[pos21] & 1LLU) << pkt21_index;\ - pkts_mask_out |= pkt20_mask | pkt21_mask; \ - \ - a20 = (void *) &bucket20->data[pos20 * f->entry_size]; \ - a21 = (void *) &bucket21->data[pos21 * f->entry_size]; \ - rte_prefetch0(a20); \ - rte_prefetch0(a21); \ - entries[pkt20_index] = a20; \ - entries[pkt21_index] = a21; \ - lru_update(bucket20, pos20); \ - lru_update(bucket21, pos21); \ -} - -#define lookup2_stage2_ext(pkt20_index, pkt21_index, mbuf20, mbuf21, bucket20, \ - bucket21, pkts_mask_out, entries, buckets_mask, buckets, keys, f)\ -{ \ - struct rte_bucket_4_32 *bucket20_next, *bucket21_next; \ - void *a20, *a21; \ - uint64_t pkt20_mask, pkt21_mask, bucket20_mask, bucket21_mask;\ - uint64_t *key20, *key21; \ - uint32_t pos20, pos21; \ - \ - key20 = RTE_MBUF_METADATA_UINT64_PTR(mbuf20, f->key_offset);\ - key21 = RTE_MBUF_METADATA_UINT64_PTR(mbuf21, f->key_offset);\ - \ - lookup_key32_cmp(key20, bucket20, pos20, f); \ - lookup_key32_cmp(key21, bucket21, pos21, f); \ - \ - pkt20_mask = (bucket20->signature[pos20] & 1LLU) << pkt20_index;\ - pkt21_mask = (bucket21->signature[pos21] & 1LLU) << pkt21_index;\ - pkts_mask_out |= pkt20_mask | pkt21_mask; \ - \ - a20 = (void *) &bucket20->data[pos20 * f->entry_size]; \ - a21 = (void *) &bucket21->data[pos21 * f->entry_size]; \ - rte_prefetch0(a20); \ - rte_prefetch0(a21); \ - entries[pkt20_index] = a20; \ - entries[pkt21_index] = a21; \ - \ - bucket20_mask = (~pkt20_mask) & (bucket20->next_valid << pkt20_index);\ - bucket21_mask = (~pkt21_mask) & (bucket21->next_valid << pkt21_index);\ - buckets_mask |= bucket20_mask | bucket21_mask; \ - bucket20_next = bucket20->next; \ - bucket21_next = bucket21->next; \ - buckets[pkt20_index] = bucket20_next; \ - buckets[pkt21_index] = bucket21_next; \ - keys[pkt20_index] = key20; \ - keys[pkt21_index] = key21; \ -} - -static int -rte_table_hash_lookup_key32_lru( - void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries) -{ - struct rte_table_hash *f = (struct rte_table_hash *) table; - struct rte_bucket_4_32 *bucket10, *bucket11, *bucket20, *bucket21; - struct rte_mbuf *mbuf00, *mbuf01, *mbuf10, *mbuf11, *mbuf20, *mbuf21; - uint32_t pkt00_index, pkt01_index, pkt10_index; - uint32_t pkt11_index, pkt20_index, pkt21_index; - uint64_t pkts_mask_out = 0; - - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - RTE_TABLE_HASH_KEY32_STATS_PKTS_IN_ADD(f, n_pkts_in); - - /* Cannot run the pipeline with less than 5 packets */ - if (rte_popcount64(pkts_mask) < 5) { - for ( ; pkts_mask; ) { - struct rte_bucket_4_32 *bucket; - struct rte_mbuf *mbuf; - uint32_t pkt_index; - - lookup1_stage0(pkt_index, mbuf, pkts, pkts_mask, f); - lookup1_stage1(mbuf, bucket, f); - lookup1_stage2_lru(pkt_index, mbuf, bucket, - pkts_mask_out, entries, f); - } - - *lookup_hit_mask = pkts_mask_out; - RTE_TABLE_HASH_KEY32_STATS_PKTS_LOOKUP_MISS(f, n_pkts_in - rte_popcount64(pkts_mask_out)); - return 0; - } - - /* - * Pipeline fill - * - */ - /* Pipeline stage 0 */ - lookup2_stage0(pkt00_index, pkt01_index, mbuf00, mbuf01, pkts, - pkts_mask, f); - - /* Pipeline feed */ - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0(pkt00_index, pkt01_index, mbuf00, mbuf01, pkts, - pkts_mask, f); - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* - * Pipeline run - * - */ - for ( ; pkts_mask; ) { - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0_with_odd_support(pkt00_index, pkt01_index, - mbuf00, mbuf01, pkts, pkts_mask, f); - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* Pipeline stage 2 */ - lookup2_stage2_lru(pkt20_index, pkt21_index, - mbuf20, mbuf21, bucket20, bucket21, pkts_mask_out, - entries, f); - } - - /* - * Pipeline flush - * - */ - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* Pipeline stage 2 */ - lookup2_stage2_lru(pkt20_index, pkt21_index, - mbuf20, mbuf21, bucket20, bucket21, pkts_mask_out, entries, f); - - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - - /* Pipeline stage 2 */ - lookup2_stage2_lru(pkt20_index, pkt21_index, - mbuf20, mbuf21, bucket20, bucket21, pkts_mask_out, entries, f); - - *lookup_hit_mask = pkts_mask_out; - RTE_TABLE_HASH_KEY32_STATS_PKTS_LOOKUP_MISS(f, n_pkts_in - rte_popcount64(pkts_mask_out)); - return 0; -} /* rte_table_hash_lookup_key32_lru() */ - -static int -rte_table_hash_lookup_key32_ext( - void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries) -{ - struct rte_table_hash *f = (struct rte_table_hash *) table; - struct rte_bucket_4_32 *bucket10, *bucket11, *bucket20, *bucket21; - struct rte_mbuf *mbuf00, *mbuf01, *mbuf10, *mbuf11, *mbuf20, *mbuf21; - uint32_t pkt00_index, pkt01_index, pkt10_index; - uint32_t pkt11_index, pkt20_index, pkt21_index; - uint64_t pkts_mask_out = 0, buckets_mask = 0; - struct rte_bucket_4_32 *buckets[RTE_PORT_IN_BURST_SIZE_MAX]; - uint64_t *keys[RTE_PORT_IN_BURST_SIZE_MAX]; - - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - RTE_TABLE_HASH_KEY32_STATS_PKTS_IN_ADD(f, n_pkts_in); - - /* Cannot run the pipeline with less than 5 packets */ - if (rte_popcount64(pkts_mask) < 5) { - for ( ; pkts_mask; ) { - struct rte_bucket_4_32 *bucket; - struct rte_mbuf *mbuf; - uint32_t pkt_index; - - lookup1_stage0(pkt_index, mbuf, pkts, pkts_mask, f); - lookup1_stage1(mbuf, bucket, f); - lookup1_stage2_ext(pkt_index, mbuf, bucket, - pkts_mask_out, entries, buckets_mask, buckets, - keys, f); - } - - goto grind_next_buckets; - } - - /* - * Pipeline fill - * - */ - /* Pipeline stage 0 */ - lookup2_stage0(pkt00_index, pkt01_index, mbuf00, mbuf01, pkts, - pkts_mask, f); - - /* Pipeline feed */ - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0(pkt00_index, pkt01_index, mbuf00, mbuf01, pkts, - pkts_mask, f); - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* - * Pipeline run - * - */ - for ( ; pkts_mask; ) { - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0_with_odd_support(pkt00_index, pkt01_index, - mbuf00, mbuf01, pkts, pkts_mask, f); - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* Pipeline stage 2 */ - lookup2_stage2_ext(pkt20_index, pkt21_index, mbuf20, mbuf21, - bucket20, bucket21, pkts_mask_out, entries, - buckets_mask, buckets, keys, f); - } - - /* - * Pipeline flush - * - */ - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* Pipeline stage 2 */ - lookup2_stage2_ext(pkt20_index, pkt21_index, mbuf20, mbuf21, - bucket20, bucket21, pkts_mask_out, entries, - buckets_mask, buckets, keys, f); - - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - - /* Pipeline stage 2 */ - lookup2_stage2_ext(pkt20_index, pkt21_index, mbuf20, mbuf21, - bucket20, bucket21, pkts_mask_out, entries, - buckets_mask, buckets, keys, f); - -grind_next_buckets: - /* Grind next buckets */ - for ( ; buckets_mask; ) { - uint64_t buckets_mask_next = 0; - - for ( ; buckets_mask; ) { - uint32_t pkt_index; - - pkt_index = rte_ctz64(buckets_mask); - buckets_mask &= ~(1LLU << pkt_index); - - lookup_grinder(pkt_index, buckets, keys, pkts_mask_out, - entries, buckets_mask_next, f); - } - - buckets_mask = buckets_mask_next; - } - - *lookup_hit_mask = pkts_mask_out; - RTE_TABLE_HASH_KEY32_STATS_PKTS_LOOKUP_MISS(f, n_pkts_in - rte_popcount64(pkts_mask_out)); - return 0; -} /* rte_table_hash_lookup_key32_ext() */ - -static int -rte_table_hash_key32_stats_read(void *table, struct rte_table_stats *stats, int clear) -{ - struct rte_table_hash *t = table; - - if (stats != NULL) - memcpy(stats, &t->stats, sizeof(t->stats)); - - if (clear) - memset(&t->stats, 0, sizeof(t->stats)); - - return 0; -} - -RTE_EXPORT_SYMBOL(rte_table_hash_key32_lru_ops) -struct rte_table_ops rte_table_hash_key32_lru_ops = { - .f_create = rte_table_hash_create_key32_lru, - .f_free = rte_table_hash_free_key32_lru, - .f_add = rte_table_hash_entry_add_key32_lru, - .f_delete = rte_table_hash_entry_delete_key32_lru, - .f_add_bulk = NULL, - .f_delete_bulk = NULL, - .f_lookup = rte_table_hash_lookup_key32_lru, - .f_stats = rte_table_hash_key32_stats_read, -}; - -RTE_EXPORT_SYMBOL(rte_table_hash_key32_ext_ops) -struct rte_table_ops rte_table_hash_key32_ext_ops = { - .f_create = rte_table_hash_create_key32_ext, - .f_free = rte_table_hash_free_key32_ext, - .f_add = rte_table_hash_entry_add_key32_ext, - .f_delete = rte_table_hash_entry_delete_key32_ext, - .f_add_bulk = NULL, - .f_delete_bulk = NULL, - .f_lookup = rte_table_hash_lookup_key32_ext, - .f_stats = rte_table_hash_key32_stats_read, -}; diff --git a/lib/table/rte_table_hash_key8.c b/lib/table/rte_table_hash_key8.c deleted file mode 100644 index 5e9dcf10ee..0000000000 --- a/lib/table/rte_table_hash_key8.c +++ /dev/null @@ -1,1157 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2017 Intel Corporation - */ - -#include -#include -#include - -#include -#include -#include -#include - -#include "rte_table_hash.h" -#include "rte_lru.h" - -#include "table_log.h" - -#define KEY_SIZE 8 - -#define KEYS_PER_BUCKET 4 - -#ifdef RTE_TABLE_STATS_COLLECT - -#define RTE_TABLE_HASH_KEY8_STATS_PKTS_IN_ADD(table, val) \ - table->stats.n_pkts_in += val -#define RTE_TABLE_HASH_KEY8_STATS_PKTS_LOOKUP_MISS(table, val) \ - table->stats.n_pkts_lookup_miss += val - -#else - -#define RTE_TABLE_HASH_KEY8_STATS_PKTS_IN_ADD(table, val) -#define RTE_TABLE_HASH_KEY8_STATS_PKTS_LOOKUP_MISS(table, val) - -#endif - -#ifdef RTE_ARCH_64 -struct rte_bucket_4_8 { - /* Cache line 0 */ - uint64_t signature; - uint64_t lru_list; - struct rte_bucket_4_8 *next; - uint64_t next_valid; - - uint64_t key[4]; - - /* Cache line 1 */ - uint8_t data[]; -}; -#else -struct rte_bucket_4_8 { - /* Cache line 0 */ - uint64_t signature; - uint64_t lru_list; - struct rte_bucket_4_8 *next; - uint32_t pad; - uint64_t next_valid; - - uint64_t key[4]; - - /* Cache line 1 */ - uint8_t data[]; -}; -#endif - -struct rte_table_hash { - struct rte_table_stats stats; - - /* Input parameters */ - uint32_t n_buckets; - uint32_t key_size; - uint32_t entry_size; - uint32_t bucket_size; - uint32_t key_offset; - uint64_t key_mask; - rte_table_hash_op_hash f_hash; - uint64_t seed; - - /* Extendible buckets */ - uint32_t n_buckets_ext; - uint32_t stack_pos; - uint32_t *stack; - - /* Lookup table */ - alignas(RTE_CACHE_LINE_SIZE) uint8_t memory[]; -}; - -static int -keycmp(void *a, void *b, void *b_mask) -{ - uint64_t *a64 = a, *b64 = b, *b_mask64 = b_mask; - - return a64[0] != (b64[0] & b_mask64[0]); -} - -static void -keycpy(void *dst, void *src, void *src_mask) -{ - uint64_t *dst64 = dst, *src64 = src, *src_mask64 = src_mask; - - dst64[0] = src64[0] & src_mask64[0]; -} - -static int -check_params_create(struct rte_table_hash_params *params) -{ - /* name */ - if (params->name == NULL) { - TABLE_LOG(ERR, "%s: name invalid value", __func__); - return -EINVAL; - } - - /* key_size */ - if (params->key_size != KEY_SIZE) { - TABLE_LOG(ERR, "%s: key_size invalid value", __func__); - return -EINVAL; - } - - /* n_keys */ - if (params->n_keys == 0) { - TABLE_LOG(ERR, "%s: n_keys is zero", __func__); - return -EINVAL; - } - - /* n_buckets */ - if ((params->n_buckets == 0) || - (!rte_is_power_of_2(params->n_buckets))) { - TABLE_LOG(ERR, "%s: n_buckets invalid value", __func__); - return -EINVAL; - } - - /* f_hash */ - if (params->f_hash == NULL) { - TABLE_LOG(ERR, "%s: f_hash function pointer is NULL", - __func__); - return -EINVAL; - } - - return 0; -} - -static void * -rte_table_hash_create_key8_lru(void *params, int socket_id, uint32_t entry_size) -{ - struct rte_table_hash_params *p = params; - struct rte_table_hash *f; - uint64_t bucket_size, total_size; - uint32_t n_buckets, i; - - /* Check input parameters */ - if ((check_params_create(p) != 0) || - ((sizeof(struct rte_table_hash) % RTE_CACHE_LINE_SIZE) != 0) || - ((sizeof(struct rte_bucket_4_8) % 64) != 0)) - return NULL; - - /* - * Table dimensioning - * - * Objective: Pick the number of buckets (n_buckets) so that there a chance - * to store n_keys keys in the table. - * - * Note: Since the buckets do not get extended, it is not possible to - * guarantee that n_keys keys can be stored in the table at any time. In the - * worst case scenario when all the n_keys fall into the same bucket, only - * a maximum of KEYS_PER_BUCKET keys will be stored in the table. This case - * defeats the purpose of the hash table. It indicates unsuitable f_hash or - * n_keys to n_buckets ratio. - * - * MIN(n_buckets) = (n_keys + KEYS_PER_BUCKET - 1) / KEYS_PER_BUCKET - */ - n_buckets = rte_align32pow2( - (p->n_keys + KEYS_PER_BUCKET - 1) / KEYS_PER_BUCKET); - n_buckets = RTE_MAX(n_buckets, p->n_buckets); - - /* Memory allocation */ - bucket_size = RTE_CACHE_LINE_ROUNDUP(sizeof(struct rte_bucket_4_8) + - KEYS_PER_BUCKET * entry_size); - total_size = sizeof(struct rte_table_hash) + n_buckets * bucket_size; - - if (total_size > SIZE_MAX) { - TABLE_LOG(ERR, "%s: Cannot allocate %" PRIu64 " bytes" - " for hash table %s", - __func__, total_size, p->name); - return NULL; - } - - f = rte_zmalloc_socket(p->name, - (size_t)total_size, - RTE_CACHE_LINE_SIZE, - socket_id); - if (f == NULL) { - TABLE_LOG(ERR, "%s: Cannot allocate %" PRIu64 " bytes" - " for hash table %s", - __func__, total_size, p->name); - return NULL; - } - - TABLE_LOG(INFO, "%s: Hash table %s memory footprint " - "is %" PRIu64 " bytes", - __func__, p->name, total_size); - - /* Memory initialization */ - f->n_buckets = n_buckets; - f->key_size = KEY_SIZE; - f->entry_size = entry_size; - f->bucket_size = bucket_size; - f->key_offset = p->key_offset; - f->f_hash = p->f_hash; - f->seed = p->seed; - - if (p->key_mask != NULL) - f->key_mask = ((uint64_t *)p->key_mask)[0]; - else - f->key_mask = 0xFFFFFFFFFFFFFFFFLLU; - - for (i = 0; i < n_buckets; i++) { - struct rte_bucket_4_8 *bucket; - - bucket = (struct rte_bucket_4_8 *) &f->memory[i * - f->bucket_size]; - bucket->lru_list = 0x0000000100020003LLU; - } - - return f; -} - -static int -rte_table_hash_free_key8_lru(void *table) -{ - struct rte_table_hash *f = table; - - /* Check input parameters */ - if (f == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - - rte_free(f); - return 0; -} - -static int -rte_table_hash_entry_add_key8_lru( - void *table, - void *key, - void *entry, - int *key_found, - void **entry_ptr) -{ - struct rte_table_hash *f = table; - struct rte_bucket_4_8 *bucket; - uint64_t signature, mask, pos; - uint32_t bucket_index, i; - - signature = f->f_hash(key, &f->key_mask, f->key_size, f->seed); - bucket_index = signature & (f->n_buckets - 1); - bucket = (struct rte_bucket_4_8 *) - &f->memory[bucket_index * f->bucket_size]; - - /* Key is present in the bucket */ - for (i = 0, mask = 1LLU; i < 4; i++, mask <<= 1) { - uint64_t bucket_signature = bucket->signature; - uint64_t *bucket_key = &bucket->key[i]; - - if ((bucket_signature & mask) && - (keycmp(bucket_key, key, &f->key_mask) == 0)) { - uint8_t *bucket_data = &bucket->data[i * f->entry_size]; - - memcpy(bucket_data, entry, f->entry_size); - lru_update(bucket, i); - *key_found = 1; - *entry_ptr = (void *) bucket_data; - return 0; - } - } - - /* Key is not present in the bucket */ - for (i = 0, mask = 1LLU; i < 4; i++, mask <<= 1) { - uint64_t bucket_signature = bucket->signature; - - if ((bucket_signature & mask) == 0) { - uint8_t *bucket_data = &bucket->data[i * f->entry_size]; - - bucket->signature |= mask; - keycpy(&bucket->key[i], key, &f->key_mask); - memcpy(bucket_data, entry, f->entry_size); - lru_update(bucket, i); - *key_found = 0; - *entry_ptr = (void *) bucket_data; - - return 0; - } - } - - /* Bucket full: replace LRU entry */ - pos = lru_pos(bucket); - keycpy(&bucket->key[pos], key, &f->key_mask); - memcpy(&bucket->data[pos * f->entry_size], entry, f->entry_size); - lru_update(bucket, pos); - *key_found = 0; - *entry_ptr = (void *) &bucket->data[pos * f->entry_size]; - - return 0; -} - -static int -rte_table_hash_entry_delete_key8_lru( - void *table, - void *key, - int *key_found, - void *entry) -{ - struct rte_table_hash *f = table; - struct rte_bucket_4_8 *bucket; - uint64_t signature, mask; - uint32_t bucket_index, i; - - signature = f->f_hash(key, &f->key_mask, f->key_size, f->seed); - bucket_index = signature & (f->n_buckets - 1); - bucket = (struct rte_bucket_4_8 *) - &f->memory[bucket_index * f->bucket_size]; - - /* Key is present in the bucket */ - for (i = 0, mask = 1LLU; i < 4; i++, mask <<= 1) { - uint64_t bucket_signature = bucket->signature; - uint64_t *bucket_key = &bucket->key[i]; - - if ((bucket_signature & mask) && - (keycmp(bucket_key, key, &f->key_mask) == 0)) { - uint8_t *bucket_data = &bucket->data[i * f->entry_size]; - - bucket->signature &= ~mask; - *key_found = 1; - if (entry) - memcpy(entry, bucket_data, f->entry_size); - - return 0; - } - } - - /* Key is not present in the bucket */ - *key_found = 0; - return 0; -} - -static void * -rte_table_hash_create_key8_ext(void *params, int socket_id, uint32_t entry_size) -{ - struct rte_table_hash_params *p = params; - struct rte_table_hash *f; - uint64_t bucket_size, stack_size, total_size; - uint32_t n_buckets_ext, i; - - /* Check input parameters */ - if ((check_params_create(p) != 0) || - ((sizeof(struct rte_table_hash) % RTE_CACHE_LINE_SIZE) != 0) || - ((sizeof(struct rte_bucket_4_8) % 64) != 0)) - return NULL; - - /* - * Table dimensioning - * - * Objective: Pick the number of bucket extensions (n_buckets_ext) so that - * it is guaranteed that n_keys keys can be stored in the table at any time. - * - * The worst case scenario takes place when all the n_keys keys fall into - * the same bucket. Actually, due to the KEYS_PER_BUCKET scheme, the worst - * case takes place when (n_keys - KEYS_PER_BUCKET + 1) keys fall into the - * same bucket, while the remaining (KEYS_PER_BUCKET - 1) keys each fall - * into a different bucket. This case defeats the purpose of the hash table. - * It indicates unsuitable f_hash or n_keys to n_buckets ratio. - * - * n_buckets_ext = n_keys / KEYS_PER_BUCKET + KEYS_PER_BUCKET - 1 - */ - n_buckets_ext = p->n_keys / KEYS_PER_BUCKET + KEYS_PER_BUCKET - 1; - - /* Memory allocation */ - bucket_size = RTE_CACHE_LINE_ROUNDUP(sizeof(struct rte_bucket_4_8) + - KEYS_PER_BUCKET * entry_size); - stack_size = RTE_CACHE_LINE_ROUNDUP(n_buckets_ext * sizeof(uint32_t)); - total_size = sizeof(struct rte_table_hash) + - (p->n_buckets + n_buckets_ext) * bucket_size + stack_size; - - if (total_size > SIZE_MAX) { - TABLE_LOG(ERR, "%s: Cannot allocate %" PRIu64 " bytes " - "for hash table %s", - __func__, total_size, p->name); - return NULL; - } - - f = rte_zmalloc_socket(p->name, - (size_t)total_size, - RTE_CACHE_LINE_SIZE, - socket_id); - if (f == NULL) { - TABLE_LOG(ERR, - "%s: Cannot allocate %" PRIu64 " bytes " - "for hash table %s", - __func__, total_size, p->name); - return NULL; - } - TABLE_LOG(INFO, "%s: Hash table %s memory footprint " - "is %" PRIu64 " bytes", - __func__, p->name, total_size); - - /* Memory initialization */ - f->n_buckets = p->n_buckets; - f->key_size = KEY_SIZE; - f->entry_size = entry_size; - f->bucket_size = bucket_size; - f->key_offset = p->key_offset; - f->f_hash = p->f_hash; - f->seed = p->seed; - - f->n_buckets_ext = n_buckets_ext; - f->stack_pos = n_buckets_ext; - f->stack = (uint32_t *) - &f->memory[(p->n_buckets + n_buckets_ext) * f->bucket_size]; - - if (p->key_mask != NULL) - f->key_mask = ((uint64_t *)p->key_mask)[0]; - else - f->key_mask = 0xFFFFFFFFFFFFFFFFLLU; - - for (i = 0; i < n_buckets_ext; i++) - f->stack[i] = i; - - return f; -} - -static int -rte_table_hash_free_key8_ext(void *table) -{ - struct rte_table_hash *f = table; - - /* Check input parameters */ - if (f == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - - rte_free(f); - return 0; -} - -static int -rte_table_hash_entry_add_key8_ext( - void *table, - void *key, - void *entry, - int *key_found, - void **entry_ptr) -{ - struct rte_table_hash *f = table; - struct rte_bucket_4_8 *bucket0, *bucket, *bucket_prev; - uint64_t signature; - uint32_t bucket_index, i; - - signature = f->f_hash(key, &f->key_mask, f->key_size, f->seed); - bucket_index = signature & (f->n_buckets - 1); - bucket0 = (struct rte_bucket_4_8 *) - &f->memory[bucket_index * f->bucket_size]; - - /* Key is present in the bucket */ - for (bucket = bucket0; bucket != NULL; bucket = bucket->next) { - uint64_t mask; - - for (i = 0, mask = 1LLU; i < 4; i++, mask <<= 1) { - uint64_t bucket_signature = bucket->signature; - uint64_t *bucket_key = &bucket->key[i]; - - if ((bucket_signature & mask) && - (keycmp(bucket_key, key, &f->key_mask) == 0)) { - uint8_t *bucket_data = &bucket->data[i * - f->entry_size]; - - memcpy(bucket_data, entry, f->entry_size); - *key_found = 1; - *entry_ptr = (void *) bucket_data; - return 0; - } - } - } - - /* Key is not present in the bucket */ - for (bucket_prev = NULL, bucket = bucket0; - bucket != NULL; bucket_prev = bucket, bucket = bucket->next) { - uint64_t mask; - - for (i = 0, mask = 1LLU; i < 4; i++, mask <<= 1) { - uint64_t bucket_signature = bucket->signature; - - if ((bucket_signature & mask) == 0) { - uint8_t *bucket_data = &bucket->data[i * - f->entry_size]; - - bucket->signature |= mask; - keycpy(&bucket->key[i], key, &f->key_mask); - memcpy(bucket_data, entry, f->entry_size); - *key_found = 0; - *entry_ptr = (void *) bucket_data; - - return 0; - } - } - } - - /* Bucket full: extend bucket */ - if (f->stack_pos > 0) { - bucket_index = f->stack[--f->stack_pos]; - - bucket = (struct rte_bucket_4_8 *) &f->memory[(f->n_buckets + - bucket_index) * f->bucket_size]; - bucket_prev->next = bucket; - bucket_prev->next_valid = 1; - - bucket->signature = 1; - keycpy(&bucket->key[0], key, &f->key_mask); - memcpy(&bucket->data[0], entry, f->entry_size); - *key_found = 0; - *entry_ptr = (void *) &bucket->data[0]; - return 0; - } - - return -ENOSPC; -} - -static int -rte_table_hash_entry_delete_key8_ext( - void *table, - void *key, - int *key_found, - void *entry) -{ - struct rte_table_hash *f = table; - struct rte_bucket_4_8 *bucket0, *bucket, *bucket_prev; - uint64_t signature; - uint32_t bucket_index, i; - - signature = f->f_hash(key, &f->key_mask, f->key_size, f->seed); - bucket_index = signature & (f->n_buckets - 1); - bucket0 = (struct rte_bucket_4_8 *) - &f->memory[bucket_index * f->bucket_size]; - - /* Key is present in the bucket */ - for (bucket_prev = NULL, bucket = bucket0; bucket != NULL; - bucket_prev = bucket, bucket = bucket->next) { - uint64_t mask; - - for (i = 0, mask = 1LLU; i < 4; i++, mask <<= 1) { - uint64_t bucket_signature = bucket->signature; - uint64_t *bucket_key = &bucket->key[i]; - - if ((bucket_signature & mask) && - (keycmp(bucket_key, key, &f->key_mask) == 0)) { - uint8_t *bucket_data = &bucket->data[i * - f->entry_size]; - - bucket->signature &= ~mask; - *key_found = 1; - if (entry) - memcpy(entry, bucket_data, - f->entry_size); - - if ((bucket->signature == 0) && - (bucket_prev != NULL)) { - bucket_prev->next = bucket->next; - bucket_prev->next_valid = - bucket->next_valid; - - memset(bucket, 0, - sizeof(struct rte_bucket_4_8)); - bucket_index = (((uint8_t *)bucket - - (uint8_t *)f->memory)/f->bucket_size) - f->n_buckets; - f->stack[f->stack_pos++] = bucket_index; - } - - return 0; - } - } - } - - /* Key is not present in the bucket */ - *key_found = 0; - return 0; -} - -#define lookup_key8_cmp(key_in, bucket, pos, f) \ -{ \ - uint64_t xor[4], signature, k; \ - \ - signature = ~bucket->signature; \ - \ - k = key_in[0] & f->key_mask; \ - xor[0] = (k ^ bucket->key[0]) | (signature & 1); \ - xor[1] = (k ^ bucket->key[1]) | (signature & 2); \ - xor[2] = (k ^ bucket->key[2]) | (signature & 4); \ - xor[3] = (k ^ bucket->key[3]) | (signature & 8); \ - \ - pos = 4; \ - if (xor[0] == 0) \ - pos = 0; \ - if (xor[1] == 0) \ - pos = 1; \ - if (xor[2] == 0) \ - pos = 2; \ - if (xor[3] == 0) \ - pos = 3; \ -} - -#define lookup1_stage0(pkt0_index, mbuf0, pkts, pkts_mask, f) \ -{ \ - uint64_t pkt_mask; \ - uint32_t key_offset = f->key_offset;\ - \ - pkt0_index = rte_ctz64(pkts_mask); \ - pkt_mask = 1LLU << pkt0_index; \ - pkts_mask &= ~pkt_mask; \ - \ - mbuf0 = pkts[pkt0_index]; \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf0, key_offset)); \ -} - -#define lookup1_stage1(mbuf1, bucket1, f) \ -{ \ - uint64_t *key; \ - uint64_t signature; \ - uint32_t bucket_index; \ - \ - key = RTE_MBUF_METADATA_UINT64_PTR(mbuf1, f->key_offset);\ - signature = f->f_hash(key, &f->key_mask, KEY_SIZE, f->seed); \ - bucket_index = signature & (f->n_buckets - 1); \ - bucket1 = (struct rte_bucket_4_8 *) \ - &f->memory[bucket_index * f->bucket_size]; \ - rte_prefetch0(bucket1); \ -} - -#define lookup1_stage2_lru(pkt2_index, mbuf2, bucket2, \ - pkts_mask_out, entries, f) \ -{ \ - void *a; \ - uint64_t pkt_mask; \ - uint64_t *key; \ - uint32_t pos; \ - \ - key = RTE_MBUF_METADATA_UINT64_PTR(mbuf2, f->key_offset);\ - lookup_key8_cmp(key, bucket2, pos, f); \ - \ - pkt_mask = ((bucket2->signature >> pos) & 1LLU) << pkt2_index;\ - pkts_mask_out |= pkt_mask; \ - \ - a = (void *) &bucket2->data[pos * f->entry_size]; \ - rte_prefetch0(a); \ - entries[pkt2_index] = a; \ - lru_update(bucket2, pos); \ -} - -#define lookup1_stage2_ext(pkt2_index, mbuf2, bucket2, pkts_mask_out,\ - entries, buckets_mask, buckets, keys, f) \ -{ \ - struct rte_bucket_4_8 *bucket_next; \ - void *a; \ - uint64_t pkt_mask, bucket_mask; \ - uint64_t *key; \ - uint32_t pos; \ - \ - key = RTE_MBUF_METADATA_UINT64_PTR(mbuf2, f->key_offset);\ - lookup_key8_cmp(key, bucket2, pos, f); \ - \ - pkt_mask = ((bucket2->signature >> pos) & 1LLU) << pkt2_index;\ - pkts_mask_out |= pkt_mask; \ - \ - a = (void *) &bucket2->data[pos * f->entry_size]; \ - rte_prefetch0(a); \ - entries[pkt2_index] = a; \ - \ - bucket_mask = (~pkt_mask) & (bucket2->next_valid << pkt2_index);\ - buckets_mask |= bucket_mask; \ - bucket_next = bucket2->next; \ - buckets[pkt2_index] = bucket_next; \ - keys[pkt2_index] = key; \ -} - -#define lookup_grinder(pkt_index, buckets, keys, pkts_mask_out, entries,\ - buckets_mask, f) \ -{ \ - struct rte_bucket_4_8 *bucket, *bucket_next; \ - void *a; \ - uint64_t pkt_mask, bucket_mask; \ - uint64_t *key; \ - uint32_t pos; \ - \ - bucket = buckets[pkt_index]; \ - key = keys[pkt_index]; \ - lookup_key8_cmp(key, bucket, pos, f); \ - \ - pkt_mask = ((bucket->signature >> pos) & 1LLU) << pkt_index;\ - pkts_mask_out |= pkt_mask; \ - \ - a = (void *) &bucket->data[pos * f->entry_size]; \ - rte_prefetch0(a); \ - entries[pkt_index] = a; \ - \ - bucket_mask = (~pkt_mask) & (bucket->next_valid << pkt_index);\ - buckets_mask |= bucket_mask; \ - bucket_next = bucket->next; \ - rte_prefetch0(bucket_next); \ - buckets[pkt_index] = bucket_next; \ - keys[pkt_index] = key; \ -} - -#define lookup2_stage0(pkt00_index, pkt01_index, mbuf00, mbuf01,\ - pkts, pkts_mask, f) \ -{ \ - uint64_t pkt00_mask, pkt01_mask; \ - uint32_t key_offset = f->key_offset; \ - \ - pkt00_index = rte_ctz64(pkts_mask); \ - pkt00_mask = 1LLU << pkt00_index; \ - pkts_mask &= ~pkt00_mask; \ - \ - mbuf00 = pkts[pkt00_index]; \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf00, key_offset));\ - \ - pkt01_index = rte_ctz64(pkts_mask); \ - pkt01_mask = 1LLU << pkt01_index; \ - pkts_mask &= ~pkt01_mask; \ - \ - mbuf01 = pkts[pkt01_index]; \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf01, key_offset));\ -} - -#define lookup2_stage0_with_odd_support(pkt00_index, pkt01_index,\ - mbuf00, mbuf01, pkts, pkts_mask, f) \ -{ \ - uint64_t pkt00_mask, pkt01_mask; \ - uint32_t key_offset = f->key_offset; \ - \ - pkt00_index = rte_ctz64(pkts_mask); \ - pkt00_mask = 1LLU << pkt00_index; \ - pkts_mask &= ~pkt00_mask; \ - \ - mbuf00 = pkts[pkt00_index]; \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf00, key_offset));\ - \ - pkt01_index = rte_ctz64(pkts_mask); \ - if (pkts_mask == 0) \ - pkt01_index = pkt00_index; \ - \ - pkt01_mask = 1LLU << pkt01_index; \ - pkts_mask &= ~pkt01_mask; \ - \ - mbuf01 = pkts[pkt01_index]; \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf01, key_offset));\ -} - -#define lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f)\ -{ \ - uint64_t *key10, *key11; \ - uint64_t signature10, signature11; \ - uint32_t bucket10_index, bucket11_index; \ - rte_table_hash_op_hash f_hash = f->f_hash; \ - uint64_t seed = f->seed; \ - uint32_t key_offset = f->key_offset; \ - \ - key10 = RTE_MBUF_METADATA_UINT64_PTR(mbuf10, key_offset);\ - key11 = RTE_MBUF_METADATA_UINT64_PTR(mbuf11, key_offset);\ - \ - signature10 = f_hash(key10, &f->key_mask, KEY_SIZE, seed); \ - bucket10_index = signature10 & (f->n_buckets - 1); \ - bucket10 = (struct rte_bucket_4_8 *) \ - &f->memory[bucket10_index * f->bucket_size]; \ - rte_prefetch0(bucket10); \ - \ - signature11 = f_hash(key11, &f->key_mask, KEY_SIZE, seed); \ - bucket11_index = signature11 & (f->n_buckets - 1); \ - bucket11 = (struct rte_bucket_4_8 *) \ - &f->memory[bucket11_index * f->bucket_size]; \ - rte_prefetch0(bucket11); \ -} - -#define lookup2_stage2_lru(pkt20_index, pkt21_index, mbuf20, mbuf21,\ - bucket20, bucket21, pkts_mask_out, entries, f) \ -{ \ - void *a20, *a21; \ - uint64_t pkt20_mask, pkt21_mask; \ - uint64_t *key20, *key21; \ - uint32_t pos20, pos21; \ - \ - key20 = RTE_MBUF_METADATA_UINT64_PTR(mbuf20, f->key_offset);\ - key21 = RTE_MBUF_METADATA_UINT64_PTR(mbuf21, f->key_offset);\ - \ - lookup_key8_cmp(key20, bucket20, pos20, f); \ - lookup_key8_cmp(key21, bucket21, pos21, f); \ - \ - pkt20_mask = ((bucket20->signature >> pos20) & 1LLU) << pkt20_index;\ - pkt21_mask = ((bucket21->signature >> pos21) & 1LLU) << pkt21_index;\ - pkts_mask_out |= pkt20_mask | pkt21_mask; \ - \ - a20 = (void *) &bucket20->data[pos20 * f->entry_size]; \ - a21 = (void *) &bucket21->data[pos21 * f->entry_size]; \ - rte_prefetch0(a20); \ - rte_prefetch0(a21); \ - entries[pkt20_index] = a20; \ - entries[pkt21_index] = a21; \ - lru_update(bucket20, pos20); \ - lru_update(bucket21, pos21); \ -} - -#define lookup2_stage2_ext(pkt20_index, pkt21_index, mbuf20, mbuf21, bucket20, \ - bucket21, pkts_mask_out, entries, buckets_mask, buckets, keys, f)\ -{ \ - struct rte_bucket_4_8 *bucket20_next, *bucket21_next; \ - void *a20, *a21; \ - uint64_t pkt20_mask, pkt21_mask, bucket20_mask, bucket21_mask;\ - uint64_t *key20, *key21; \ - uint32_t pos20, pos21; \ - \ - key20 = RTE_MBUF_METADATA_UINT64_PTR(mbuf20, f->key_offset);\ - key21 = RTE_MBUF_METADATA_UINT64_PTR(mbuf21, f->key_offset);\ - \ - lookup_key8_cmp(key20, bucket20, pos20, f); \ - lookup_key8_cmp(key21, bucket21, pos21, f); \ - \ - pkt20_mask = ((bucket20->signature >> pos20) & 1LLU) << pkt20_index;\ - pkt21_mask = ((bucket21->signature >> pos21) & 1LLU) << pkt21_index;\ - pkts_mask_out |= pkt20_mask | pkt21_mask; \ - \ - a20 = (void *) &bucket20->data[pos20 * f->entry_size]; \ - a21 = (void *) &bucket21->data[pos21 * f->entry_size]; \ - rte_prefetch0(a20); \ - rte_prefetch0(a21); \ - entries[pkt20_index] = a20; \ - entries[pkt21_index] = a21; \ - \ - bucket20_mask = (~pkt20_mask) & (bucket20->next_valid << pkt20_index);\ - bucket21_mask = (~pkt21_mask) & (bucket21->next_valid << pkt21_index);\ - buckets_mask |= bucket20_mask | bucket21_mask; \ - bucket20_next = bucket20->next; \ - bucket21_next = bucket21->next; \ - buckets[pkt20_index] = bucket20_next; \ - buckets[pkt21_index] = bucket21_next; \ - keys[pkt20_index] = key20; \ - keys[pkt21_index] = key21; \ -} - -static int -rte_table_hash_lookup_key8_lru( - void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries) -{ - struct rte_table_hash *f = (struct rte_table_hash *) table; - struct rte_bucket_4_8 *bucket10, *bucket11, *bucket20, *bucket21; - struct rte_mbuf *mbuf00, *mbuf01, *mbuf10, *mbuf11, *mbuf20, *mbuf21; - uint32_t pkt00_index, pkt01_index, pkt10_index; - uint32_t pkt11_index, pkt20_index, pkt21_index; - uint64_t pkts_mask_out = 0; - - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - RTE_TABLE_HASH_KEY8_STATS_PKTS_IN_ADD(f, n_pkts_in); - - /* Cannot run the pipeline with less than 5 packets */ - if (rte_popcount64(pkts_mask) < 5) { - for ( ; pkts_mask; ) { - struct rte_bucket_4_8 *bucket; - struct rte_mbuf *mbuf; - uint32_t pkt_index; - - lookup1_stage0(pkt_index, mbuf, pkts, pkts_mask, f); - lookup1_stage1(mbuf, bucket, f); - lookup1_stage2_lru(pkt_index, mbuf, bucket, - pkts_mask_out, entries, f); - } - - *lookup_hit_mask = pkts_mask_out; - RTE_TABLE_HASH_KEY8_STATS_PKTS_LOOKUP_MISS(f, n_pkts_in - rte_popcount64(pkts_mask_out)); - return 0; - } - - /* - * Pipeline fill - * - */ - /* Pipeline stage 0 */ - lookup2_stage0(pkt00_index, pkt01_index, mbuf00, mbuf01, pkts, - pkts_mask, f); - - /* Pipeline feed */ - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0(pkt00_index, pkt01_index, mbuf00, mbuf01, pkts, - pkts_mask, f); - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* - * Pipeline run - * - */ - for ( ; pkts_mask; ) { - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0_with_odd_support(pkt00_index, pkt01_index, - mbuf00, mbuf01, pkts, pkts_mask, f); - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* Pipeline stage 2 */ - lookup2_stage2_lru(pkt20_index, pkt21_index, mbuf20, mbuf21, - bucket20, bucket21, pkts_mask_out, entries, f); - } - - /* - * Pipeline flush - * - */ - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* Pipeline stage 2 */ - lookup2_stage2_lru(pkt20_index, pkt21_index, mbuf20, mbuf21, - bucket20, bucket21, pkts_mask_out, entries, f); - - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - - /* Pipeline stage 2 */ - lookup2_stage2_lru(pkt20_index, pkt21_index, mbuf20, mbuf21, - bucket20, bucket21, pkts_mask_out, entries, f); - - *lookup_hit_mask = pkts_mask_out; - RTE_TABLE_HASH_KEY8_STATS_PKTS_LOOKUP_MISS(f, n_pkts_in - rte_popcount64(pkts_mask_out)); - return 0; -} /* lookup LRU */ - -static int -rte_table_hash_lookup_key8_ext( - void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries) -{ - struct rte_table_hash *f = (struct rte_table_hash *) table; - struct rte_bucket_4_8 *bucket10, *bucket11, *bucket20, *bucket21; - struct rte_mbuf *mbuf00, *mbuf01, *mbuf10, *mbuf11, *mbuf20, *mbuf21; - uint32_t pkt00_index, pkt01_index, pkt10_index; - uint32_t pkt11_index, pkt20_index, pkt21_index; - uint64_t pkts_mask_out = 0, buckets_mask = 0; - struct rte_bucket_4_8 *buckets[RTE_PORT_IN_BURST_SIZE_MAX]; - uint64_t *keys[RTE_PORT_IN_BURST_SIZE_MAX]; - - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - RTE_TABLE_HASH_KEY8_STATS_PKTS_IN_ADD(f, n_pkts_in); - - /* Cannot run the pipeline with less than 5 packets */ - if (rte_popcount64(pkts_mask) < 5) { - for ( ; pkts_mask; ) { - struct rte_bucket_4_8 *bucket; - struct rte_mbuf *mbuf; - uint32_t pkt_index; - - lookup1_stage0(pkt_index, mbuf, pkts, pkts_mask, f); - lookup1_stage1(mbuf, bucket, f); - lookup1_stage2_ext(pkt_index, mbuf, bucket, - pkts_mask_out, entries, buckets_mask, - buckets, keys, f); - } - - goto grind_next_buckets; - } - - /* - * Pipeline fill - * - */ - /* Pipeline stage 0 */ - lookup2_stage0(pkt00_index, pkt01_index, mbuf00, mbuf01, pkts, - pkts_mask, f); - - /* Pipeline feed */ - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0(pkt00_index, pkt01_index, mbuf00, mbuf01, pkts, - pkts_mask, f); - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* - * Pipeline run - * - */ - for ( ; pkts_mask; ) { - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0_with_odd_support(pkt00_index, pkt01_index, - mbuf00, mbuf01, pkts, pkts_mask, f); - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* Pipeline stage 2 */ - lookup2_stage2_ext(pkt20_index, pkt21_index, mbuf20, mbuf21, - bucket20, bucket21, pkts_mask_out, entries, - buckets_mask, buckets, keys, f); - } - - /* - * Pipeline flush - * - */ - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - mbuf10 = mbuf00; - mbuf11 = mbuf01; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 1 */ - lookup2_stage1(mbuf10, mbuf11, bucket10, bucket11, f); - - /* Pipeline stage 2 */ - lookup2_stage2_ext(pkt20_index, pkt21_index, mbuf20, mbuf21, - bucket20, bucket21, pkts_mask_out, entries, - buckets_mask, buckets, keys, f); - - /* Pipeline feed */ - bucket20 = bucket10; - bucket21 = bucket11; - mbuf20 = mbuf10; - mbuf21 = mbuf11; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - - /* Pipeline stage 2 */ - lookup2_stage2_ext(pkt20_index, pkt21_index, mbuf20, mbuf21, - bucket20, bucket21, pkts_mask_out, entries, - buckets_mask, buckets, keys, f); - -grind_next_buckets: - /* Grind next buckets */ - for ( ; buckets_mask; ) { - uint64_t buckets_mask_next = 0; - - for ( ; buckets_mask; ) { - uint32_t pkt_index; - - pkt_index = rte_ctz64(buckets_mask); - buckets_mask &= ~(1LLU << pkt_index); - - lookup_grinder(pkt_index, buckets, keys, pkts_mask_out, - entries, buckets_mask_next, f); - } - - buckets_mask = buckets_mask_next; - } - - *lookup_hit_mask = pkts_mask_out; - RTE_TABLE_HASH_KEY8_STATS_PKTS_LOOKUP_MISS(f, n_pkts_in - rte_popcount64(pkts_mask_out)); - return 0; -} /* lookup EXT */ - -static int -rte_table_hash_key8_stats_read(void *table, struct rte_table_stats *stats, int clear) -{ - struct rte_table_hash *t = table; - - if (stats != NULL) - memcpy(stats, &t->stats, sizeof(t->stats)); - - if (clear) - memset(&t->stats, 0, sizeof(t->stats)); - - return 0; -} - -RTE_EXPORT_SYMBOL(rte_table_hash_key8_lru_ops) -struct rte_table_ops rte_table_hash_key8_lru_ops = { - .f_create = rte_table_hash_create_key8_lru, - .f_free = rte_table_hash_free_key8_lru, - .f_add = rte_table_hash_entry_add_key8_lru, - .f_delete = rte_table_hash_entry_delete_key8_lru, - .f_add_bulk = NULL, - .f_delete_bulk = NULL, - .f_lookup = rte_table_hash_lookup_key8_lru, - .f_stats = rte_table_hash_key8_stats_read, -}; - -RTE_EXPORT_SYMBOL(rte_table_hash_key8_ext_ops) -struct rte_table_ops rte_table_hash_key8_ext_ops = { - .f_create = rte_table_hash_create_key8_ext, - .f_free = rte_table_hash_free_key8_ext, - .f_add = rte_table_hash_entry_add_key8_ext, - .f_delete = rte_table_hash_entry_delete_key8_ext, - .f_add_bulk = NULL, - .f_delete_bulk = NULL, - .f_lookup = rte_table_hash_lookup_key8_ext, - .f_stats = rte_table_hash_key8_stats_read, -}; diff --git a/lib/table/rte_table_hash_lru.c b/lib/table/rte_table_hash_lru.c deleted file mode 100644 index 548f5eebf2..0000000000 --- a/lib/table/rte_table_hash_lru.c +++ /dev/null @@ -1,959 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2017 Intel Corporation - */ - -#include -#include -#include - -#include -#include -#include -#include - -#include "rte_table_hash.h" -#include "rte_lru.h" - -#include "table_log.h" - -#define KEYS_PER_BUCKET 4 - -#ifdef RTE_TABLE_STATS_COLLECT - -#define RTE_TABLE_HASH_LRU_STATS_PKTS_IN_ADD(table, val) \ - table->stats.n_pkts_in += val -#define RTE_TABLE_HASH_LRU_STATS_PKTS_LOOKUP_MISS(table, val) \ - table->stats.n_pkts_lookup_miss += val - -#else - -#define RTE_TABLE_HASH_LRU_STATS_PKTS_IN_ADD(table, val) -#define RTE_TABLE_HASH_LRU_STATS_PKTS_LOOKUP_MISS(table, val) - -#endif - -struct bucket { - union { - struct bucket *next; - uint64_t lru_list; - }; - uint16_t sig[KEYS_PER_BUCKET]; - uint32_t key_pos[KEYS_PER_BUCKET]; -}; - -struct grinder { - struct bucket *bkt; - uint64_t sig; - uint64_t match; - uint64_t match_pos; - uint32_t key_index; -}; - -struct rte_table_hash { - struct rte_table_stats stats; - - /* Input parameters */ - uint32_t key_size; - uint32_t entry_size; - uint32_t n_keys; - uint32_t n_buckets; - rte_table_hash_op_hash f_hash; - uint64_t seed; - uint32_t key_offset; - - /* Internal */ - uint64_t bucket_mask; - uint32_t key_size_shl; - uint32_t data_size_shl; - uint32_t key_stack_tos; - - /* Grinder */ - struct grinder grinders[RTE_PORT_IN_BURST_SIZE_MAX]; - - /* Tables */ - uint64_t *key_mask; - struct bucket *buckets; - uint8_t *key_mem; - uint8_t *data_mem; - uint32_t *key_stack; - - /* Table memory */ - alignas(RTE_CACHE_LINE_SIZE) uint8_t memory[]; -}; - -static int -keycmp(void *a, void *b, void *b_mask, uint32_t n_bytes) -{ - uint64_t *a64 = a, *b64 = b, *b_mask64 = b_mask; - uint32_t i; - - for (i = 0; i < n_bytes / sizeof(uint64_t); i++) - if (a64[i] != (b64[i] & b_mask64[i])) - return 1; - - return 0; -} - -static void -keycpy(void *dst, void *src, void *src_mask, uint32_t n_bytes) -{ - uint64_t *dst64 = dst, *src64 = src, *src_mask64 = src_mask; - uint32_t i; - - for (i = 0; i < n_bytes / sizeof(uint64_t); i++) - dst64[i] = src64[i] & src_mask64[i]; -} - -static int -check_params_create(struct rte_table_hash_params *params) -{ - /* name */ - if (params->name == NULL) { - TABLE_LOG(ERR, "%s: name invalid value", __func__); - return -EINVAL; - } - - /* key_size */ - if ((params->key_size < sizeof(uint64_t)) || - (!rte_is_power_of_2(params->key_size))) { - TABLE_LOG(ERR, "%s: key_size invalid value", __func__); - return -EINVAL; - } - - /* n_keys */ - if (params->n_keys == 0) { - TABLE_LOG(ERR, "%s: n_keys invalid value", __func__); - return -EINVAL; - } - - /* n_buckets */ - if ((params->n_buckets == 0) || - (!rte_is_power_of_2(params->n_buckets))) { - TABLE_LOG(ERR, "%s: n_buckets invalid value", __func__); - return -EINVAL; - } - - /* f_hash */ - if (params->f_hash == NULL) { - TABLE_LOG(ERR, "%s: f_hash invalid value", __func__); - return -EINVAL; - } - - return 0; -} - -static void * -rte_table_hash_lru_create(void *params, int socket_id, uint32_t entry_size) -{ - struct rte_table_hash_params *p = params; - struct rte_table_hash *t; - uint64_t table_meta_sz, key_mask_sz, bucket_sz, key_sz, key_stack_sz; - uint64_t data_sz, total_size; - uint64_t key_mask_offset, bucket_offset, key_offset, key_stack_offset; - uint64_t data_offset; - uint32_t n_buckets, i; - - /* Check input parameters */ - if ((check_params_create(p) != 0) || - (!rte_is_power_of_2(entry_size)) || - ((sizeof(struct rte_table_hash) % RTE_CACHE_LINE_SIZE) != 0) || - (sizeof(struct bucket) != (RTE_CACHE_LINE_SIZE / 2))) { - return NULL; - } - - /* - * Table dimensioning - * - * Objective: Pick the number of buckets (n_buckets) so that there a chance - * to store n_keys keys in the table. - * - * Note: Since the buckets do not get extended, it is not possible to - * guarantee that n_keys keys can be stored in the table at any time. In the - * worst case scenario when all the n_keys fall into the same bucket, only - * a maximum of KEYS_PER_BUCKET keys will be stored in the table. This case - * defeats the purpose of the hash table. It indicates unsuitable f_hash or - * n_keys to n_buckets ratio. - * - * MIN(n_buckets) = (n_keys + KEYS_PER_BUCKET - 1) / KEYS_PER_BUCKET - */ - n_buckets = rte_align32pow2( - (p->n_keys + KEYS_PER_BUCKET - 1) / KEYS_PER_BUCKET); - n_buckets = RTE_MAX(n_buckets, p->n_buckets); - - /* Memory allocation */ - table_meta_sz = RTE_CACHE_LINE_ROUNDUP(sizeof(struct rte_table_hash)); - key_mask_sz = RTE_CACHE_LINE_ROUNDUP(p->key_size); - bucket_sz = RTE_CACHE_LINE_ROUNDUP(n_buckets * sizeof(struct bucket)); - key_sz = RTE_CACHE_LINE_ROUNDUP(p->n_keys * p->key_size); - key_stack_sz = RTE_CACHE_LINE_ROUNDUP(p->n_keys * sizeof(uint32_t)); - data_sz = RTE_CACHE_LINE_ROUNDUP(p->n_keys * entry_size); - total_size = table_meta_sz + key_mask_sz + bucket_sz + key_sz + - key_stack_sz + data_sz; - - if (total_size > SIZE_MAX) { - TABLE_LOG(ERR, - "%s: Cannot allocate %" PRIu64 " bytes for hash " - "table %s", - __func__, total_size, p->name); - return NULL; - } - - t = rte_zmalloc_socket(p->name, - (size_t)total_size, - RTE_CACHE_LINE_SIZE, - socket_id); - if (t == NULL) { - TABLE_LOG(ERR, - "%s: Cannot allocate %" PRIu64 " bytes for hash " - "table %s", - __func__, total_size, p->name); - return NULL; - } - TABLE_LOG(INFO, "%s (%u-byte key): Hash table %s memory footprint" - " is %" PRIu64 " bytes", - __func__, p->key_size, p->name, total_size); - - /* Memory initialization */ - t->key_size = p->key_size; - t->entry_size = entry_size; - t->n_keys = p->n_keys; - t->n_buckets = n_buckets; - t->f_hash = p->f_hash; - t->seed = p->seed; - t->key_offset = p->key_offset; - - /* Internal */ - t->bucket_mask = t->n_buckets - 1; - t->key_size_shl = rte_ctz32(p->key_size); - t->data_size_shl = rte_ctz32(entry_size); - - /* Tables */ - key_mask_offset = 0; - bucket_offset = key_mask_offset + key_mask_sz; - key_offset = bucket_offset + bucket_sz; - key_stack_offset = key_offset + key_sz; - data_offset = key_stack_offset + key_stack_sz; - - t->key_mask = (uint64_t *) &t->memory[key_mask_offset]; - t->buckets = (struct bucket *) &t->memory[bucket_offset]; - t->key_mem = &t->memory[key_offset]; - t->key_stack = (uint32_t *) &t->memory[key_stack_offset]; - t->data_mem = &t->memory[data_offset]; - - /* Key mask */ - if (p->key_mask == NULL) - memset(t->key_mask, 0xFF, p->key_size); - else - memcpy(t->key_mask, p->key_mask, p->key_size); - - /* Key stack */ - for (i = 0; i < t->n_keys; i++) - t->key_stack[i] = t->n_keys - 1 - i; - t->key_stack_tos = t->n_keys; - - /* LRU */ - for (i = 0; i < t->n_buckets; i++) { - struct bucket *bkt = &t->buckets[i]; - - lru_init(bkt); - } - - return t; -} - -static int -rte_table_hash_lru_free(void *table) -{ - struct rte_table_hash *t = table; - - /* Check input parameters */ - if (t == NULL) - return -EINVAL; - - rte_free(t); - return 0; -} - -static int -rte_table_hash_lru_entry_add(void *table, void *key, void *entry, - int *key_found, void **entry_ptr) -{ - struct rte_table_hash *t = table; - struct bucket *bkt; - uint64_t sig; - uint32_t bkt_index, i; - - sig = t->f_hash(key, t->key_mask, t->key_size, t->seed); - bkt_index = sig & t->bucket_mask; - bkt = &t->buckets[bkt_index]; - sig = (sig >> 16) | 1LLU; - - /* Key is present in the bucket */ - for (i = 0; i < KEYS_PER_BUCKET; i++) { - uint64_t bkt_sig = (uint64_t) bkt->sig[i]; - uint32_t bkt_key_index = bkt->key_pos[i]; - uint8_t *bkt_key = &t->key_mem[bkt_key_index << - t->key_size_shl]; - - if ((sig == bkt_sig) && (keycmp(bkt_key, key, t->key_mask, - t->key_size) == 0)) { - uint8_t *data = &t->data_mem[bkt_key_index << - t->data_size_shl]; - - memcpy(data, entry, t->entry_size); - lru_update(bkt, i); - *key_found = 1; - *entry_ptr = (void *) data; - return 0; - } - } - - /* Key is not present in the bucket */ - for (i = 0; i < KEYS_PER_BUCKET; i++) { - uint64_t bkt_sig = (uint64_t) bkt->sig[i]; - - if (bkt_sig == 0) { - uint32_t bkt_key_index; - uint8_t *bkt_key, *data; - - /* Allocate new key */ - if (t->key_stack_tos == 0) { - /* No keys available */ - return -ENOSPC; - } - bkt_key_index = t->key_stack[--t->key_stack_tos]; - - /* Install new key */ - bkt_key = &t->key_mem[bkt_key_index << t->key_size_shl]; - data = &t->data_mem[bkt_key_index << t->data_size_shl]; - - bkt->sig[i] = (uint16_t) sig; - bkt->key_pos[i] = bkt_key_index; - keycpy(bkt_key, key, t->key_mask, t->key_size); - memcpy(data, entry, t->entry_size); - lru_update(bkt, i); - - *key_found = 0; - *entry_ptr = (void *) data; - return 0; - } - } - - /* Bucket full */ - { - uint64_t pos = lru_pos(bkt); - uint32_t bkt_key_index = bkt->key_pos[pos]; - uint8_t *bkt_key = &t->key_mem[bkt_key_index << - t->key_size_shl]; - uint8_t *data = &t->data_mem[bkt_key_index << t->data_size_shl]; - - bkt->sig[pos] = (uint16_t) sig; - keycpy(bkt_key, key, t->key_mask, t->key_size); - memcpy(data, entry, t->entry_size); - lru_update(bkt, pos); - - *key_found = 0; - *entry_ptr = (void *) data; - return 0; - } -} - -static int -rte_table_hash_lru_entry_delete(void *table, void *key, int *key_found, - void *entry) -{ - struct rte_table_hash *t = table; - struct bucket *bkt; - uint64_t sig; - uint32_t bkt_index, i; - - sig = t->f_hash(key, t->key_mask, t->key_size, t->seed); - bkt_index = sig & t->bucket_mask; - bkt = &t->buckets[bkt_index]; - sig = (sig >> 16) | 1LLU; - - /* Key is present in the bucket */ - for (i = 0; i < KEYS_PER_BUCKET; i++) { - uint64_t bkt_sig = (uint64_t) bkt->sig[i]; - uint32_t bkt_key_index = bkt->key_pos[i]; - uint8_t *bkt_key = &t->key_mem[bkt_key_index << - t->key_size_shl]; - - if ((sig == bkt_sig) && - (keycmp(bkt_key, key, t->key_mask, t->key_size) == 0)) { - uint8_t *data = &t->data_mem[bkt_key_index << - t->data_size_shl]; - - bkt->sig[i] = 0; - t->key_stack[t->key_stack_tos++] = bkt_key_index; - *key_found = 1; - if (entry) - memcpy(entry, data, t->entry_size); - return 0; - } - } - - /* Key is not present in the bucket */ - *key_found = 0; - return 0; -} - -static int rte_table_hash_lru_lookup_unoptimized( - void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries) -{ - struct rte_table_hash *t = (struct rte_table_hash *) table; - uint64_t pkts_mask_out = 0; - - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - RTE_TABLE_HASH_LRU_STATS_PKTS_IN_ADD(t, n_pkts_in); - - for ( ; pkts_mask; ) { - struct bucket *bkt; - struct rte_mbuf *pkt; - uint8_t *key; - uint64_t pkt_mask, sig; - uint32_t pkt_index, bkt_index, i; - - pkt_index = rte_ctz64(pkts_mask); - pkt_mask = 1LLU << pkt_index; - pkts_mask &= ~pkt_mask; - - pkt = pkts[pkt_index]; - key = RTE_MBUF_METADATA_UINT8_PTR(pkt, t->key_offset); - sig = (uint64_t) t->f_hash(key, t->key_mask, t->key_size, t->seed); - - bkt_index = sig & t->bucket_mask; - bkt = &t->buckets[bkt_index]; - sig = (sig >> 16) | 1LLU; - - /* Key is present in the bucket */ - for (i = 0; i < KEYS_PER_BUCKET; i++) { - uint64_t bkt_sig = (uint64_t) bkt->sig[i]; - uint32_t bkt_key_index = bkt->key_pos[i]; - uint8_t *bkt_key = &t->key_mem[bkt_key_index << - t->key_size_shl]; - - if ((sig == bkt_sig) && (keycmp(bkt_key, key, t->key_mask, - t->key_size) == 0)) { - uint8_t *data = &t->data_mem[bkt_key_index << - t->data_size_shl]; - - lru_update(bkt, i); - pkts_mask_out |= pkt_mask; - entries[pkt_index] = (void *) data; - break; - } - } - } - - *lookup_hit_mask = pkts_mask_out; - RTE_TABLE_HASH_LRU_STATS_PKTS_LOOKUP_MISS(t, n_pkts_in - rte_popcount64(pkts_mask_out)); - return 0; -} - -/* - * mask = match bitmask - * match = at least one match - * match_many = more than one match - * match_pos = position of first match - * - * ---------------------------------------- - * mask match match_many match_pos - * ---------------------------------------- - * 0000 0 0 00 - * 0001 1 0 00 - * 0010 1 0 01 - * 0011 1 1 00 - * ---------------------------------------- - * 0100 1 0 10 - * 0101 1 1 00 - * 0110 1 1 01 - * 0111 1 1 00 - * ---------------------------------------- - * 1000 1 0 11 - * 1001 1 1 00 - * 1010 1 1 01 - * 1011 1 1 00 - * ---------------------------------------- - * 1100 1 1 10 - * 1101 1 1 00 - * 1110 1 1 01 - * 1111 1 1 00 - * ---------------------------------------- - * - * match = 1111_1111_1111_1110 - * match_many = 1111_1110_1110_1000 - * match_pos = 0001_0010_0001_0011__0001_0010_0001_0000 - * - * match = 0xFFFELLU - * match_many = 0xFEE8LLU - * match_pos = 0x12131210LLU - */ - -#define LUT_MATCH 0xFFFELLU -#define LUT_MATCH_MANY 0xFEE8LLU -#define LUT_MATCH_POS 0x12131210LLU - -#define lookup_cmp_sig(mbuf_sig, bucket, match, match_many, match_pos)\ -{ \ - uint64_t bucket_sig[4], mask[4], mask_all; \ - \ - bucket_sig[0] = bucket->sig[0]; \ - bucket_sig[1] = bucket->sig[1]; \ - bucket_sig[2] = bucket->sig[2]; \ - bucket_sig[3] = bucket->sig[3]; \ - \ - bucket_sig[0] ^= mbuf_sig; \ - bucket_sig[1] ^= mbuf_sig; \ - bucket_sig[2] ^= mbuf_sig; \ - bucket_sig[3] ^= mbuf_sig; \ - \ - mask[0] = 0; \ - mask[1] = 0; \ - mask[2] = 0; \ - mask[3] = 0; \ - \ - if (bucket_sig[0] == 0) \ - mask[0] = 1; \ - if (bucket_sig[1] == 0) \ - mask[1] = 2; \ - if (bucket_sig[2] == 0) \ - mask[2] = 4; \ - if (bucket_sig[3] == 0) \ - mask[3] = 8; \ - \ - mask_all = (mask[0] | mask[1]) | (mask[2] | mask[3]); \ - \ - match = (LUT_MATCH >> mask_all) & 1; \ - match_many = (LUT_MATCH_MANY >> mask_all) & 1; \ - match_pos = (LUT_MATCH_POS >> (mask_all << 1)) & 3; \ -} - -#define lookup_cmp_key(mbuf, key, match_key, f) \ -{ \ - uint64_t *pkt_key = RTE_MBUF_METADATA_UINT64_PTR(mbuf, f->key_offset);\ - uint64_t *bkt_key = (uint64_t *) key; \ - uint64_t *key_mask = f->key_mask; \ - \ - switch (f->key_size) { \ - case 8: \ - { \ - uint64_t xor = (pkt_key[0] & key_mask[0]) ^ bkt_key[0]; \ - match_key = 0; \ - if (xor == 0) \ - match_key = 1; \ - } \ - break; \ - \ - case 16: \ - { \ - uint64_t xor[2], or; \ - \ - xor[0] = (pkt_key[0] & key_mask[0]) ^ bkt_key[0]; \ - xor[1] = (pkt_key[1] & key_mask[1]) ^ bkt_key[1]; \ - or = xor[0] | xor[1]; \ - match_key = 0; \ - if (or == 0) \ - match_key = 1; \ - } \ - break; \ - \ - case 32: \ - { \ - uint64_t xor[4], or; \ - \ - xor[0] = (pkt_key[0] & key_mask[0]) ^ bkt_key[0]; \ - xor[1] = (pkt_key[1] & key_mask[1]) ^ bkt_key[1]; \ - xor[2] = (pkt_key[2] & key_mask[2]) ^ bkt_key[2]; \ - xor[3] = (pkt_key[3] & key_mask[3]) ^ bkt_key[3]; \ - or = xor[0] | xor[1] | xor[2] | xor[3]; \ - match_key = 0; \ - if (or == 0) \ - match_key = 1; \ - } \ - break; \ - \ - case 64: \ - { \ - uint64_t xor[8], or; \ - \ - xor[0] = (pkt_key[0] & key_mask[0]) ^ bkt_key[0]; \ - xor[1] = (pkt_key[1] & key_mask[1]) ^ bkt_key[1]; \ - xor[2] = (pkt_key[2] & key_mask[2]) ^ bkt_key[2]; \ - xor[3] = (pkt_key[3] & key_mask[3]) ^ bkt_key[3]; \ - xor[4] = (pkt_key[4] & key_mask[4]) ^ bkt_key[4]; \ - xor[5] = (pkt_key[5] & key_mask[5]) ^ bkt_key[5]; \ - xor[6] = (pkt_key[6] & key_mask[6]) ^ bkt_key[6]; \ - xor[7] = (pkt_key[7] & key_mask[7]) ^ bkt_key[7]; \ - or = xor[0] | xor[1] | xor[2] | xor[3] | \ - xor[4] | xor[5] | xor[6] | xor[7]; \ - match_key = 0; \ - if (or == 0) \ - match_key = 1; \ - } \ - break; \ - \ - default: \ - match_key = 0; \ - if (keycmp(bkt_key, pkt_key, key_mask, f->key_size) == 0) \ - match_key = 1; \ - } \ -} - -#define lookup2_stage0(t, g, pkts, pkts_mask, pkt00_index, pkt01_index)\ -{ \ - uint64_t pkt00_mask, pkt01_mask; \ - struct rte_mbuf *mbuf00, *mbuf01; \ - uint32_t key_offset = t->key_offset; \ - \ - pkt00_index = rte_ctz64(pkts_mask); \ - pkt00_mask = 1LLU << pkt00_index; \ - pkts_mask &= ~pkt00_mask; \ - mbuf00 = pkts[pkt00_index]; \ - \ - pkt01_index = rte_ctz64(pkts_mask); \ - pkt01_mask = 1LLU << pkt01_index; \ - pkts_mask &= ~pkt01_mask; \ - mbuf01 = pkts[pkt01_index]; \ - \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf00, key_offset));\ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf01, key_offset));\ -} - -#define lookup2_stage0_with_odd_support(t, g, pkts, pkts_mask, pkt00_index, \ - pkt01_index) \ -{ \ - uint64_t pkt00_mask, pkt01_mask; \ - struct rte_mbuf *mbuf00, *mbuf01; \ - uint32_t key_offset = t->key_offset; \ - \ - pkt00_index = rte_ctz64(pkts_mask); \ - pkt00_mask = 1LLU << pkt00_index; \ - pkts_mask &= ~pkt00_mask; \ - mbuf00 = pkts[pkt00_index]; \ - \ - pkt01_index = rte_ctz64(pkts_mask); \ - if (pkts_mask == 0) \ - pkt01_index = pkt00_index; \ - \ - pkt01_mask = 1LLU << pkt01_index; \ - pkts_mask &= ~pkt01_mask; \ - mbuf01 = pkts[pkt01_index]; \ - \ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf00, key_offset));\ - rte_prefetch0(RTE_MBUF_METADATA_UINT8_PTR(mbuf01, key_offset));\ -} - -#define lookup2_stage1(t, g, pkts, pkt10_index, pkt11_index)\ -{ \ - struct grinder *g10, *g11; \ - uint64_t sig10, sig11, bkt10_index, bkt11_index; \ - struct rte_mbuf *mbuf10, *mbuf11; \ - struct bucket *bkt10, *bkt11, *buckets = t->buckets; \ - uint8_t *key10, *key11; \ - uint64_t bucket_mask = t->bucket_mask; \ - rte_table_hash_op_hash f_hash = t->f_hash; \ - uint64_t seed = t->seed; \ - uint32_t key_size = t->key_size; \ - uint32_t key_offset = t->key_offset; \ - \ - mbuf10 = pkts[pkt10_index]; \ - key10 = RTE_MBUF_METADATA_UINT8_PTR(mbuf10, key_offset);\ - sig10 = (uint64_t) f_hash(key10, t->key_mask, key_size, seed);\ - bkt10_index = sig10 & bucket_mask; \ - bkt10 = &buckets[bkt10_index]; \ - \ - mbuf11 = pkts[pkt11_index]; \ - key11 = RTE_MBUF_METADATA_UINT8_PTR(mbuf11, key_offset);\ - sig11 = (uint64_t) f_hash(key11, t->key_mask, key_size, seed);\ - bkt11_index = sig11 & bucket_mask; \ - bkt11 = &buckets[bkt11_index]; \ - \ - rte_prefetch0(bkt10); \ - rte_prefetch0(bkt11); \ - \ - g10 = &g[pkt10_index]; \ - g10->sig = sig10; \ - g10->bkt = bkt10; \ - \ - g11 = &g[pkt11_index]; \ - g11->sig = sig11; \ - g11->bkt = bkt11; \ -} - -#define lookup2_stage2(t, g, pkt20_index, pkt21_index, pkts_mask_match_many)\ -{ \ - struct grinder *g20, *g21; \ - uint64_t sig20, sig21; \ - struct bucket *bkt20, *bkt21; \ - uint8_t *key20, *key21, *key_mem = t->key_mem; \ - uint64_t match20, match21, match_many20, match_many21; \ - uint64_t match_pos20, match_pos21; \ - uint32_t key20_index, key21_index, key_size_shl = t->key_size_shl;\ - \ - g20 = &g[pkt20_index]; \ - sig20 = g20->sig; \ - bkt20 = g20->bkt; \ - sig20 = (sig20 >> 16) | 1LLU; \ - lookup_cmp_sig(sig20, bkt20, match20, match_many20, match_pos20);\ - match20 <<= pkt20_index; \ - match_many20 <<= pkt20_index; \ - key20_index = bkt20->key_pos[match_pos20]; \ - key20 = &key_mem[key20_index << key_size_shl]; \ - \ - g21 = &g[pkt21_index]; \ - sig21 = g21->sig; \ - bkt21 = g21->bkt; \ - sig21 = (sig21 >> 16) | 1LLU; \ - lookup_cmp_sig(sig21, bkt21, match21, match_many21, match_pos21);\ - match21 <<= pkt21_index; \ - match_many21 <<= pkt21_index; \ - key21_index = bkt21->key_pos[match_pos21]; \ - key21 = &key_mem[key21_index << key_size_shl]; \ - \ - rte_prefetch0(key20); \ - rte_prefetch0(key21); \ - \ - pkts_mask_match_many |= match_many20 | match_many21; \ - \ - g20->match = match20; \ - g20->match_pos = match_pos20; \ - g20->key_index = key20_index; \ - \ - g21->match = match21; \ - g21->match_pos = match_pos21; \ - g21->key_index = key21_index; \ -} - -#define lookup2_stage3(t, g, pkts, pkt30_index, pkt31_index, pkts_mask_out, \ - entries) \ -{ \ - struct grinder *g30, *g31; \ - struct rte_mbuf *mbuf30, *mbuf31; \ - struct bucket *bkt30, *bkt31; \ - uint8_t *key30, *key31, *key_mem = t->key_mem; \ - uint8_t *data30, *data31, *data_mem = t->data_mem; \ - uint64_t match30, match31, match_pos30, match_pos31; \ - uint64_t match_key30, match_key31, match_keys; \ - uint32_t key30_index, key31_index; \ - uint32_t key_size_shl = t->key_size_shl; \ - uint32_t data_size_shl = t->data_size_shl; \ - \ - mbuf30 = pkts[pkt30_index]; \ - g30 = &g[pkt30_index]; \ - bkt30 = g30->bkt; \ - match30 = g30->match; \ - match_pos30 = g30->match_pos; \ - key30_index = g30->key_index; \ - key30 = &key_mem[key30_index << key_size_shl]; \ - lookup_cmp_key(mbuf30, key30, match_key30, t); \ - match_key30 <<= pkt30_index; \ - match_key30 &= match30; \ - data30 = &data_mem[key30_index << data_size_shl]; \ - entries[pkt30_index] = data30; \ - \ - mbuf31 = pkts[pkt31_index]; \ - g31 = &g[pkt31_index]; \ - bkt31 = g31->bkt; \ - match31 = g31->match; \ - match_pos31 = g31->match_pos; \ - key31_index = g31->key_index; \ - key31 = &key_mem[key31_index << key_size_shl]; \ - lookup_cmp_key(mbuf31, key31, match_key31, t); \ - match_key31 <<= pkt31_index; \ - match_key31 &= match31; \ - data31 = &data_mem[key31_index << data_size_shl]; \ - entries[pkt31_index] = data31; \ - \ - rte_prefetch0(data30); \ - rte_prefetch0(data31); \ - \ - match_keys = match_key30 | match_key31; \ - pkts_mask_out |= match_keys; \ - \ - if (match_key30 == 0) \ - match_pos30 = 4; \ - lru_update(bkt30, match_pos30); \ - \ - if (match_key31 == 0) \ - match_pos31 = 4; \ - lru_update(bkt31, match_pos31); \ -} - -/* - * The lookup function implements a 4-stage pipeline, with each stage processing - * two different packets. The purpose of pipelined implementation is to hide the - * latency of prefetching the data structures and loosen the data dependency - * between instructions. - * - * p00 _______ p10 _______ p20 _______ p30 _______ - * ----->| |----->| |----->| |----->| |-----> - * | 0 | | 1 | | 2 | | 3 | - * ----->|_______|----->|_______|----->|_______|----->|_______|-----> - * p01 p11 p21 p31 - * - * The naming convention is: - * pXY = packet Y of stage X, X = 0 .. 3, Y = 0 .. 1 - */ -static int rte_table_hash_lru_lookup( - void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries) -{ - struct rte_table_hash *t = (struct rte_table_hash *) table; - struct grinder *g = t->grinders; - uint64_t pkt00_index, pkt01_index, pkt10_index, pkt11_index; - uint64_t pkt20_index, pkt21_index, pkt30_index, pkt31_index; - uint64_t pkts_mask_out = 0, pkts_mask_match_many = 0; - int status = 0; - - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - RTE_TABLE_HASH_LRU_STATS_PKTS_IN_ADD(t, n_pkts_in); - - /* Cannot run the pipeline with less than 7 packets */ - if (rte_popcount64(pkts_mask) < 7) - return rte_table_hash_lru_lookup_unoptimized(table, pkts, - pkts_mask, lookup_hit_mask, entries); - - /* Pipeline stage 0 */ - lookup2_stage0(t, g, pkts, pkts_mask, pkt00_index, pkt01_index); - - /* Pipeline feed */ - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0(t, g, pkts, pkts_mask, pkt00_index, pkt01_index); - - /* Pipeline stage 1 */ - lookup2_stage1(t, g, pkts, pkt10_index, pkt11_index); - - /* Pipeline feed */ - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0(t, g, pkts, pkts_mask, pkt00_index, pkt01_index); - - /* Pipeline stage 1 */ - lookup2_stage1(t, g, pkts, pkt10_index, pkt11_index); - - /* Pipeline stage 2 */ - lookup2_stage2(t, g, pkt20_index, pkt21_index, pkts_mask_match_many); - - /* - * Pipeline run - * - */ - for ( ; pkts_mask; ) { - /* Pipeline feed */ - pkt30_index = pkt20_index; - pkt31_index = pkt21_index; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 0 */ - lookup2_stage0_with_odd_support(t, g, pkts, pkts_mask, - pkt00_index, pkt01_index); - - /* Pipeline stage 1 */ - lookup2_stage1(t, g, pkts, pkt10_index, pkt11_index); - - /* Pipeline stage 2 */ - lookup2_stage2(t, g, pkt20_index, pkt21_index, - pkts_mask_match_many); - - /* Pipeline stage 3 */ - lookup2_stage3(t, g, pkts, pkt30_index, pkt31_index, - pkts_mask_out, entries); - } - - /* Pipeline feed */ - pkt30_index = pkt20_index; - pkt31_index = pkt21_index; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - pkt10_index = pkt00_index; - pkt11_index = pkt01_index; - - /* Pipeline stage 1 */ - lookup2_stage1(t, g, pkts, pkt10_index, pkt11_index); - - /* Pipeline stage 2 */ - lookup2_stage2(t, g, pkt20_index, pkt21_index, pkts_mask_match_many); - - /* Pipeline stage 3 */ - lookup2_stage3(t, g, pkts, pkt30_index, pkt31_index, pkts_mask_out, - entries); - - /* Pipeline feed */ - pkt30_index = pkt20_index; - pkt31_index = pkt21_index; - pkt20_index = pkt10_index; - pkt21_index = pkt11_index; - - /* Pipeline stage 2 */ - lookup2_stage2(t, g, pkt20_index, pkt21_index, pkts_mask_match_many); - - /* Pipeline stage 3 */ - lookup2_stage3(t, g, pkts, pkt30_index, pkt31_index, pkts_mask_out, - entries); - - /* Pipeline feed */ - pkt30_index = pkt20_index; - pkt31_index = pkt21_index; - - /* Pipeline stage 3 */ - lookup2_stage3(t, g, pkts, pkt30_index, pkt31_index, pkts_mask_out, - entries); - - /* Slow path */ - pkts_mask_match_many &= ~pkts_mask_out; - if (pkts_mask_match_many) { - uint64_t pkts_mask_out_slow = 0; - - status = rte_table_hash_lru_lookup_unoptimized(table, pkts, - pkts_mask_match_many, &pkts_mask_out_slow, entries); - pkts_mask_out |= pkts_mask_out_slow; - } - - *lookup_hit_mask = pkts_mask_out; - RTE_TABLE_HASH_LRU_STATS_PKTS_LOOKUP_MISS(t, n_pkts_in - rte_popcount64(pkts_mask_out)); - return status; -} - -static int -rte_table_hash_lru_stats_read(void *table, struct rte_table_stats *stats, int clear) -{ - struct rte_table_hash *t = table; - - if (stats != NULL) - memcpy(stats, &t->stats, sizeof(t->stats)); - - if (clear) - memset(&t->stats, 0, sizeof(t->stats)); - - return 0; -} - -RTE_EXPORT_SYMBOL(rte_table_hash_lru_ops) -struct rte_table_ops rte_table_hash_lru_ops = { - .f_create = rte_table_hash_lru_create, - .f_free = rte_table_hash_lru_free, - .f_add = rte_table_hash_lru_entry_add, - .f_delete = rte_table_hash_lru_entry_delete, - .f_add_bulk = NULL, - .f_delete_bulk = NULL, - .f_lookup = rte_table_hash_lru_lookup, - .f_stats = rte_table_hash_lru_stats_read, -}; diff --git a/lib/table/rte_table_lpm.c b/lib/table/rte_table_lpm.c deleted file mode 100644 index 6fd0c30f85..0000000000 --- a/lib/table/rte_table_lpm.c +++ /dev/null @@ -1,369 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2014 Intel Corporation - */ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "rte_table_lpm.h" - -#include "table_log.h" - -#ifndef RTE_TABLE_LPM_MAX_NEXT_HOPS -#define RTE_TABLE_LPM_MAX_NEXT_HOPS 65536 -#endif - -#ifdef RTE_TABLE_STATS_COLLECT - -#define RTE_TABLE_LPM_STATS_PKTS_IN_ADD(table, val) \ - table->stats.n_pkts_in += val -#define RTE_TABLE_LPM_STATS_PKTS_LOOKUP_MISS(table, val) \ - table->stats.n_pkts_lookup_miss += val - -#else - -#define RTE_TABLE_LPM_STATS_PKTS_IN_ADD(table, val) -#define RTE_TABLE_LPM_STATS_PKTS_LOOKUP_MISS(table, val) - -#endif - -struct rte_table_lpm { - struct rte_table_stats stats; - - /* Input parameters */ - uint32_t entry_size; - uint32_t entry_unique_size; - uint32_t n_rules; - uint32_t offset; - - /* Handle to low-level LPM table */ - struct rte_lpm *lpm; - - /* Next Hop Table (NHT) */ - uint32_t nht_users[RTE_TABLE_LPM_MAX_NEXT_HOPS]; - alignas(RTE_CACHE_LINE_SIZE) uint8_t nht[]; -}; - -static void * -rte_table_lpm_create(void *params, int socket_id, uint32_t entry_size) -{ - struct rte_table_lpm_params *p = params; - struct rte_table_lpm *lpm; - struct rte_lpm_config lpm_config; - - uint32_t total_size, nht_size; - - /* Check input parameters */ - if (p == NULL) { - TABLE_LOG(ERR, "%s: NULL input parameters", __func__); - return NULL; - } - if (p->n_rules == 0) { - TABLE_LOG(ERR, "%s: Invalid n_rules", __func__); - return NULL; - } - if (p->number_tbl8s == 0) { - TABLE_LOG(ERR, "%s: Invalid number_tbl8s", __func__); - return NULL; - } - if (p->entry_unique_size == 0) { - TABLE_LOG(ERR, "%s: Invalid entry_unique_size", - __func__); - return NULL; - } - if (p->entry_unique_size > entry_size) { - TABLE_LOG(ERR, "%s: Invalid entry_unique_size", - __func__); - return NULL; - } - if (p->name == NULL) { - TABLE_LOG(ERR, "%s: Table name is NULL", - __func__); - return NULL; - } - entry_size = RTE_ALIGN(entry_size, sizeof(uint64_t)); - - /* Memory allocation */ - nht_size = RTE_TABLE_LPM_MAX_NEXT_HOPS * entry_size; - total_size = sizeof(struct rte_table_lpm) + nht_size; - lpm = rte_zmalloc_socket("TABLE", total_size, RTE_CACHE_LINE_SIZE, - socket_id); - if (lpm == NULL) { - TABLE_LOG(ERR, - "%s: Cannot allocate %u bytes for LPM table", - __func__, total_size); - return NULL; - } - - /* LPM low-level table creation */ - lpm_config.max_rules = p->n_rules; - lpm_config.number_tbl8s = p->number_tbl8s; - lpm_config.flags = p->flags; - lpm->lpm = rte_lpm_create(p->name, socket_id, &lpm_config); - - if (lpm->lpm == NULL) { - rte_free(lpm); - TABLE_LOG(ERR, "Unable to create low-level LPM table"); - return NULL; - } - - /* Memory initialization */ - lpm->entry_size = entry_size; - lpm->entry_unique_size = p->entry_unique_size; - lpm->n_rules = p->n_rules; - lpm->offset = p->offset; - - return lpm; -} - -static int -rte_table_lpm_free(void *table) -{ - struct rte_table_lpm *lpm = table; - - /* Check input parameters */ - if (lpm == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - - /* Free previously allocated resources */ - rte_lpm_free(lpm->lpm); - rte_free(lpm); - - return 0; -} - -static int -nht_find_free(struct rte_table_lpm *lpm, uint32_t *pos) -{ - uint32_t i; - - for (i = 0; i < RTE_TABLE_LPM_MAX_NEXT_HOPS; i++) { - if (lpm->nht_users[i] == 0) { - *pos = i; - return 1; - } - } - - return 0; -} - -static int -nht_find_existing(struct rte_table_lpm *lpm, void *entry, uint32_t *pos) -{ - uint32_t i; - - for (i = 0; i < RTE_TABLE_LPM_MAX_NEXT_HOPS; i++) { - uint8_t *nht_entry = &lpm->nht[i * lpm->entry_size]; - - if ((lpm->nht_users[i] > 0) && (memcmp(nht_entry, entry, - lpm->entry_unique_size) == 0)) { - *pos = i; - return 1; - } - } - - return 0; -} - -static int -rte_table_lpm_entry_add( - void *table, - void *key, - void *entry, - int *key_found, - void **entry_ptr) -{ - struct rte_table_lpm *lpm = table; - struct rte_table_lpm_key *ip_prefix = key; - uint32_t nht_pos, nht_pos0_valid; - int status; - uint32_t nht_pos0 = 0; - - /* Check input parameters */ - if (lpm == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - if (ip_prefix == NULL) { - TABLE_LOG(ERR, "%s: ip_prefix parameter is NULL", - __func__); - return -EINVAL; - } - if (entry == NULL) { - TABLE_LOG(ERR, "%s: entry parameter is NULL", __func__); - return -EINVAL; - } - - if ((ip_prefix->depth == 0) || (ip_prefix->depth > 32)) { - TABLE_LOG(ERR, "%s: invalid depth (%d)", - __func__, ip_prefix->depth); - return -EINVAL; - } - - /* Check if rule is already present in the table */ - status = rte_lpm_is_rule_present(lpm->lpm, ip_prefix->ip, - ip_prefix->depth, &nht_pos0); - nht_pos0_valid = status > 0; - - /* Find existing or free NHT entry */ - if (nht_find_existing(lpm, entry, &nht_pos) == 0) { - uint8_t *nht_entry; - - if (nht_find_free(lpm, &nht_pos) == 0) { - TABLE_LOG(ERR, "%s: NHT full", __func__); - return -1; - } - - nht_entry = &lpm->nht[nht_pos * lpm->entry_size]; - memcpy(nht_entry, entry, lpm->entry_size); - } - - /* Add rule to low level LPM table */ - if (rte_lpm_add(lpm->lpm, ip_prefix->ip, ip_prefix->depth, nht_pos) < 0) { - TABLE_LOG(ERR, "%s: LPM rule add failed", __func__); - return -1; - } - - /* Commit NHT changes */ - lpm->nht_users[nht_pos]++; - lpm->nht_users[nht_pos0] -= nht_pos0_valid; - - *key_found = nht_pos0_valid; - *entry_ptr = (void *) &lpm->nht[nht_pos * lpm->entry_size]; - return 0; -} - -static int -rte_table_lpm_entry_delete( - void *table, - void *key, - int *key_found, - void *entry) -{ - struct rte_table_lpm *lpm = table; - struct rte_table_lpm_key *ip_prefix = key; - uint32_t nht_pos; - int status; - - /* Check input parameters */ - if (lpm == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - if (ip_prefix == NULL) { - TABLE_LOG(ERR, "%s: ip_prefix parameter is NULL", - __func__); - return -EINVAL; - } - if ((ip_prefix->depth == 0) || (ip_prefix->depth > 32)) { - TABLE_LOG(ERR, "%s: invalid depth (%d)", __func__, - ip_prefix->depth); - return -EINVAL; - } - - /* Return if rule is not present in the table */ - status = rte_lpm_is_rule_present(lpm->lpm, ip_prefix->ip, - ip_prefix->depth, &nht_pos); - if (status < 0) { - TABLE_LOG(ERR, "%s: LPM algorithmic error", __func__); - return -1; - } - if (status == 0) { - *key_found = 0; - return 0; - } - - /* Delete rule from the low-level LPM table */ - status = rte_lpm_delete(lpm->lpm, ip_prefix->ip, ip_prefix->depth); - if (status) { - TABLE_LOG(ERR, "%s: LPM rule delete failed", __func__); - return -1; - } - - /* Commit NHT changes */ - lpm->nht_users[nht_pos]--; - - *key_found = 1; - if (entry) - memcpy(entry, &lpm->nht[nht_pos * lpm->entry_size], - lpm->entry_size); - - return 0; -} - -static int -rte_table_lpm_lookup( - void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries) -{ - struct rte_table_lpm *lpm = (struct rte_table_lpm *) table; - uint64_t pkts_out_mask = 0; - uint32_t i; - - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - RTE_TABLE_LPM_STATS_PKTS_IN_ADD(lpm, n_pkts_in); - - pkts_out_mask = 0; - for (i = 0; i < (uint32_t)(RTE_PORT_IN_BURST_SIZE_MAX - - rte_clz64(pkts_mask)); i++) { - uint64_t pkt_mask = 1LLU << i; - - if (pkt_mask & pkts_mask) { - struct rte_mbuf *pkt = pkts[i]; - uint32_t ip = rte_bswap32( - RTE_MBUF_METADATA_UINT32(pkt, lpm->offset)); - int status; - uint32_t nht_pos; - - status = rte_lpm_lookup(lpm->lpm, ip, &nht_pos); - if (status == 0) { - pkts_out_mask |= pkt_mask; - entries[i] = (void *) &lpm->nht[nht_pos * - lpm->entry_size]; - } - } - } - - *lookup_hit_mask = pkts_out_mask; - RTE_TABLE_LPM_STATS_PKTS_LOOKUP_MISS(lpm, n_pkts_in - rte_popcount64(pkts_out_mask)); - return 0; -} - -static int -rte_table_lpm_stats_read(void *table, struct rte_table_stats *stats, int clear) -{ - struct rte_table_lpm *t = table; - - if (stats != NULL) - memcpy(stats, &t->stats, sizeof(t->stats)); - - if (clear) - memset(&t->stats, 0, sizeof(t->stats)); - - return 0; -} - -RTE_EXPORT_SYMBOL(rte_table_lpm_ops) -struct rte_table_ops rte_table_lpm_ops = { - .f_create = rte_table_lpm_create, - .f_free = rte_table_lpm_free, - .f_add = rte_table_lpm_entry_add, - .f_delete = rte_table_lpm_entry_delete, - .f_add_bulk = NULL, - .f_delete_bulk = NULL, - .f_lookup = rte_table_lpm_lookup, - .f_stats = rte_table_lpm_stats_read, -}; diff --git a/lib/table/rte_table_lpm.h b/lib/table/rte_table_lpm.h deleted file mode 100644 index 59b9bdee89..0000000000 --- a/lib/table/rte_table_lpm.h +++ /dev/null @@ -1,94 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2014 Intel Corporation - */ - -#ifndef __INCLUDE_RTE_TABLE_LPM_H__ -#define __INCLUDE_RTE_TABLE_LPM_H__ - -/** - * @file - * RTE Table LPM for IPv4 - * - * This table uses the Longest Prefix Match (LPM) algorithm to uniquely - * associate data to lookup keys. - * - * Use-case: IP routing table. Routes that are added to the table associate a - * next hop to an IP prefix. The IP prefix is specified as IP address and depth - * and cover for a multitude of lookup keys (i.e. destination IP addresses) - * that all share the same data (i.e. next hop). The next hop information - * typically contains the output interface ID, the IP address of the next hop - * station (which is part of the same IP network the output interface is - * connected to) and other flags and counters. - * - * The LPM primitive only allows associating an 8-bit number (next hop ID) to - * an IP prefix, while a routing table can potentially contain thousands of - * routes or even more. This means that the same next hop ID (and next hop - * information) has to be shared by multiple routes, which makes sense, as - * multiple remote networks could be reached through the same next hop. - * Therefore, when a route is added or updated, the LPM table has to check - * whether the same next hop is already in use before using a new next hop ID - * for this route. - * - * The comparison between different next hops is done for the first - * “entry_unique_size” bytes of the next hop information (configurable - * parameter), which have to uniquely identify the next hop, therefore the user - * has to carefully manage the format of the LPM table entry (i.e. the next - * hop information) so that any next hop data that changes value during - * run-time (e.g. counters) is placed outside of this area. - */ - -#include - -#include "rte_table.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** LPM table parameters */ -struct rte_table_lpm_params { - /** Table name */ - const char *name; - - /** Maximum number of LPM rules (i.e. IP routes) */ - uint32_t n_rules; - - /**< Number of tbl8s to allocate. */ - uint32_t number_tbl8s; - - /**< This field is currently unused. */ - int flags; - - /** Number of bytes at the start of the table entry that uniquely - identify the entry. Cannot be bigger than table entry size. */ - uint32_t entry_unique_size; - - /** Byte offset within input packet meta-data where lookup key (i.e. - the destination IP address) is located. */ - uint32_t offset; -}; - -/** LPM table rule (i.e. route), specified as IP prefix. While the key used by -the lookup operation is the destination IP address (read from the input packet -meta-data), the entry add and entry delete operations work with LPM rules, with -each rule covering for a multitude of lookup keys (destination IP addresses) -that share the same data (next hop). */ -struct rte_table_lpm_key { - /** IP address */ - uint32_t ip; - - /** IP address depth. The most significant "depth" bits of the IP - address specify the network part of the IP address, while the rest of - the bits specify the host part of the address and are ignored for the - purpose of route specification. */ - uint8_t depth; -}; - -/** LPM table operations */ -extern struct rte_table_ops rte_table_lpm_ops; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/lib/table/rte_table_lpm_ipv6.c b/lib/table/rte_table_lpm_ipv6.c deleted file mode 100644 index 9159784dfa..0000000000 --- a/lib/table/rte_table_lpm_ipv6.c +++ /dev/null @@ -1,370 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2014 Intel Corporation - */ - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "rte_table_lpm_ipv6.h" - -#include "table_log.h" - -#define RTE_TABLE_LPM_MAX_NEXT_HOPS 256 - -#ifdef RTE_TABLE_STATS_COLLECT - -#define RTE_TABLE_LPM_IPV6_STATS_PKTS_IN_ADD(table, val) \ - table->stats.n_pkts_in += val -#define RTE_TABLE_LPM_IPV6_STATS_PKTS_LOOKUP_MISS(table, val) \ - table->stats.n_pkts_lookup_miss += val - -#else - -#define RTE_TABLE_LPM_IPV6_STATS_PKTS_IN_ADD(table, val) -#define RTE_TABLE_LPM_IPV6_STATS_PKTS_LOOKUP_MISS(table, val) - -#endif - -struct rte_table_lpm_ipv6 { - struct rte_table_stats stats; - - /* Input parameters */ - uint32_t entry_size; - uint32_t entry_unique_size; - uint32_t n_rules; - uint32_t offset; - - /* Handle to low-level LPM table */ - struct rte_lpm6 *lpm; - - /* Next Hop Table (NHT) */ - uint32_t nht_users[RTE_TABLE_LPM_MAX_NEXT_HOPS]; - alignas(RTE_CACHE_LINE_SIZE) uint8_t nht[]; -}; - -static void * -rte_table_lpm_ipv6_create(void *params, int socket_id, uint32_t entry_size) -{ - struct rte_table_lpm_ipv6_params *p = - params; - struct rte_table_lpm_ipv6 *lpm; - struct rte_lpm6_config lpm6_config; - uint32_t total_size, nht_size; - - /* Check input parameters */ - if (p == NULL) { - TABLE_LOG(ERR, "%s: NULL input parameters", __func__); - return NULL; - } - if (p->n_rules == 0) { - TABLE_LOG(ERR, "%s: Invalid n_rules", __func__); - return NULL; - } - if (p->number_tbl8s == 0) { - TABLE_LOG(ERR, "%s: Invalid n_rules", __func__); - return NULL; - } - if (p->entry_unique_size == 0) { - TABLE_LOG(ERR, "%s: Invalid entry_unique_size", - __func__); - return NULL; - } - if (p->entry_unique_size > entry_size) { - TABLE_LOG(ERR, "%s: Invalid entry_unique_size", - __func__); - return NULL; - } - if (p->name == NULL) { - TABLE_LOG(ERR, "%s: Table name is NULL", - __func__); - return NULL; - } - entry_size = RTE_ALIGN(entry_size, sizeof(uint64_t)); - - /* Memory allocation */ - nht_size = RTE_TABLE_LPM_MAX_NEXT_HOPS * entry_size; - total_size = sizeof(struct rte_table_lpm_ipv6) + nht_size; - lpm = rte_zmalloc_socket("TABLE", total_size, RTE_CACHE_LINE_SIZE, - socket_id); - if (lpm == NULL) { - TABLE_LOG(ERR, - "%s: Cannot allocate %u bytes for LPM IPv6 table", - __func__, total_size); - return NULL; - } - - /* LPM low-level table creation */ - lpm6_config.max_rules = p->n_rules; - lpm6_config.number_tbl8s = p->number_tbl8s; - lpm6_config.flags = 0; - lpm->lpm = rte_lpm6_create(p->name, socket_id, &lpm6_config); - if (lpm->lpm == NULL) { - rte_free(lpm); - TABLE_LOG(ERR, - "Unable to create low-level LPM IPv6 table"); - return NULL; - } - - /* Memory initialization */ - lpm->entry_size = entry_size; - lpm->entry_unique_size = p->entry_unique_size; - lpm->n_rules = p->n_rules; - lpm->offset = p->offset; - - return lpm; -} - -static int -rte_table_lpm_ipv6_free(void *table) -{ - struct rte_table_lpm_ipv6 *lpm = table; - - /* Check input parameters */ - if (lpm == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - - /* Free previously allocated resources */ - rte_lpm6_free(lpm->lpm); - rte_free(lpm); - - return 0; -} - -static int -nht_find_free(struct rte_table_lpm_ipv6 *lpm, uint32_t *pos) -{ - uint32_t i; - - for (i = 0; i < RTE_TABLE_LPM_MAX_NEXT_HOPS; i++) { - if (lpm->nht_users[i] == 0) { - *pos = i; - return 1; - } - } - - return 0; -} - -static int -nht_find_existing(struct rte_table_lpm_ipv6 *lpm, void *entry, uint32_t *pos) -{ - uint32_t i; - - for (i = 0; i < RTE_TABLE_LPM_MAX_NEXT_HOPS; i++) { - uint8_t *nht_entry = &lpm->nht[i * lpm->entry_size]; - - if ((lpm->nht_users[i] > 0) && (memcmp(nht_entry, entry, - lpm->entry_unique_size) == 0)) { - *pos = i; - return 1; - } - } - - return 0; -} - -static int -rte_table_lpm_ipv6_entry_add( - void *table, - void *key, - void *entry, - int *key_found, - void **entry_ptr) -{ - struct rte_table_lpm_ipv6 *lpm = table; - struct rte_table_lpm_ipv6_key *ip_prefix = - key; - uint32_t nht_pos = 0, nht_pos0 = 0, nht_pos0_valid = 0; - int status; - - /* Check input parameters */ - if (lpm == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - if (ip_prefix == NULL) { - TABLE_LOG(ERR, "%s: ip_prefix parameter is NULL", - __func__); - return -EINVAL; - } - if (entry == NULL) { - TABLE_LOG(ERR, "%s: entry parameter is NULL", __func__); - return -EINVAL; - } - - if ((ip_prefix->depth == 0) || (ip_prefix->depth > 128)) { - TABLE_LOG(ERR, "%s: invalid depth (%d)", __func__, - ip_prefix->depth); - return -EINVAL; - } - - /* Check if rule is already present in the table */ - status = rte_lpm6_is_rule_present(lpm->lpm, &ip_prefix->ip, - ip_prefix->depth, &nht_pos0); - nht_pos0_valid = status > 0; - - /* Find existing or free NHT entry */ - if (nht_find_existing(lpm, entry, &nht_pos) == 0) { - uint8_t *nht_entry; - - if (nht_find_free(lpm, &nht_pos) == 0) { - TABLE_LOG(ERR, "%s: NHT full", __func__); - return -1; - } - - nht_entry = &lpm->nht[nht_pos * lpm->entry_size]; - memcpy(nht_entry, entry, lpm->entry_size); - } - - /* Add rule to low level LPM table */ - if (rte_lpm6_add(lpm->lpm, &ip_prefix->ip, ip_prefix->depth, - nht_pos) < 0) { - TABLE_LOG(ERR, "%s: LPM IPv6 rule add failed", __func__); - return -1; - } - - /* Commit NHT changes */ - lpm->nht_users[nht_pos]++; - lpm->nht_users[nht_pos0] -= nht_pos0_valid; - - *key_found = nht_pos0_valid; - *entry_ptr = (void *) &lpm->nht[nht_pos * lpm->entry_size]; - return 0; -} - -static int -rte_table_lpm_ipv6_entry_delete( - void *table, - void *key, - int *key_found, - void *entry) -{ - struct rte_table_lpm_ipv6 *lpm = table; - struct rte_table_lpm_ipv6_key *ip_prefix = - key; - uint32_t nht_pos; - int status; - - /* Check input parameters */ - if (lpm == NULL) { - TABLE_LOG(ERR, "%s: table parameter is NULL", __func__); - return -EINVAL; - } - if (ip_prefix == NULL) { - TABLE_LOG(ERR, "%s: ip_prefix parameter is NULL", - __func__); - return -EINVAL; - } - if ((ip_prefix->depth == 0) || (ip_prefix->depth > 128)) { - TABLE_LOG(ERR, "%s: invalid depth (%d)", __func__, - ip_prefix->depth); - return -EINVAL; - } - - /* Return if rule is not present in the table */ - status = rte_lpm6_is_rule_present(lpm->lpm, &ip_prefix->ip, - ip_prefix->depth, &nht_pos); - if (status < 0) { - TABLE_LOG(ERR, "%s: LPM IPv6 algorithmic error", - __func__); - return -1; - } - if (status == 0) { - *key_found = 0; - return 0; - } - - /* Delete rule from the low-level LPM table */ - status = rte_lpm6_delete(lpm->lpm, &ip_prefix->ip, ip_prefix->depth); - if (status) { - TABLE_LOG(ERR, "%s: LPM IPv6 rule delete failed", - __func__); - return -1; - } - - /* Commit NHT changes */ - lpm->nht_users[nht_pos]--; - - *key_found = 1; - if (entry) - memcpy(entry, &lpm->nht[nht_pos * lpm->entry_size], - lpm->entry_size); - - return 0; -} - -static int -rte_table_lpm_ipv6_lookup( - void *table, - struct rte_mbuf **pkts, - uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - void **entries) -{ - struct rte_table_lpm_ipv6 *lpm = (struct rte_table_lpm_ipv6 *) table; - uint64_t pkts_out_mask = 0; - uint32_t i; - - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - RTE_TABLE_LPM_IPV6_STATS_PKTS_IN_ADD(lpm, n_pkts_in); - - pkts_out_mask = 0; - for (i = 0; i < (uint32_t)(RTE_PORT_IN_BURST_SIZE_MAX - - rte_clz64(pkts_mask)); i++) { - uint64_t pkt_mask = 1LLU << i; - - if (pkt_mask & pkts_mask) { - struct rte_mbuf *pkt = pkts[i]; - const struct rte_ipv6_addr *ip; - int status; - uint32_t nht_pos; - - ip = (struct rte_ipv6_addr *)RTE_MBUF_METADATA_UINT8_PTR(pkt, lpm->offset); - status = rte_lpm6_lookup(lpm->lpm, ip, &nht_pos); - if (status == 0) { - pkts_out_mask |= pkt_mask; - entries[i] = (void *) &lpm->nht[nht_pos * - lpm->entry_size]; - } - } - } - - *lookup_hit_mask = pkts_out_mask; - RTE_TABLE_LPM_IPV6_STATS_PKTS_LOOKUP_MISS(lpm, n_pkts_in - rte_popcount64(pkts_out_mask)); - return 0; -} - -static int -rte_table_lpm_ipv6_stats_read(void *table, struct rte_table_stats *stats, int clear) -{ - struct rte_table_lpm_ipv6 *t = table; - - if (stats != NULL) - memcpy(stats, &t->stats, sizeof(t->stats)); - - if (clear) - memset(&t->stats, 0, sizeof(t->stats)); - - return 0; -} - -RTE_EXPORT_SYMBOL(rte_table_lpm_ipv6_ops) -struct rte_table_ops rte_table_lpm_ipv6_ops = { - .f_create = rte_table_lpm_ipv6_create, - .f_free = rte_table_lpm_ipv6_free, - .f_add = rte_table_lpm_ipv6_entry_add, - .f_delete = rte_table_lpm_ipv6_entry_delete, - .f_add_bulk = NULL, - .f_delete_bulk = NULL, - .f_lookup = rte_table_lpm_ipv6_lookup, - .f_stats = rte_table_lpm_ipv6_stats_read, -}; diff --git a/lib/table/rte_table_stub.c b/lib/table/rte_table_stub.c deleted file mode 100644 index 3d2ac55c49..0000000000 --- a/lib/table/rte_table_stub.c +++ /dev/null @@ -1,95 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2014 Intel Corporation - */ - -#include - -#include -#include - -#include "rte_table_stub.h" - -#include "table_log.h" - -#ifdef RTE_TABLE_STATS_COLLECT - -#define RTE_TABLE_LPM_STATS_PKTS_IN_ADD(table, val) \ - table->stats.n_pkts_in += val -#define RTE_TABLE_LPM_STATS_PKTS_LOOKUP_MISS(table, val) \ - table->stats.n_pkts_lookup_miss += val - -#else - -#define RTE_TABLE_LPM_STATS_PKTS_IN_ADD(table, val) -#define RTE_TABLE_LPM_STATS_PKTS_LOOKUP_MISS(table, val) - -#endif - -struct rte_table_stub { - struct rte_table_stats stats; -}; - -static void * -rte_table_stub_create(__rte_unused void *params, - __rte_unused int socket_id, - __rte_unused uint32_t entry_size) -{ - struct rte_table_stub *stub; - uint32_t size; - - size = sizeof(struct rte_table_stub); - stub = rte_zmalloc_socket("TABLE", size, RTE_CACHE_LINE_SIZE, - socket_id); - if (stub == NULL) { - TABLE_LOG(ERR, - "%s: Cannot allocate %u bytes for stub table", - __func__, size); - return NULL; - } - - return stub; -} - -static int -rte_table_stub_lookup( - __rte_unused void *table, - __rte_unused struct rte_mbuf **pkts, - __rte_unused uint64_t pkts_mask, - uint64_t *lookup_hit_mask, - __rte_unused void **entries) -{ - __rte_unused struct rte_table_stub *stub = (struct rte_table_stub *) table; - __rte_unused uint32_t n_pkts_in = rte_popcount64(pkts_mask); - - RTE_TABLE_LPM_STATS_PKTS_IN_ADD(stub, n_pkts_in); - *lookup_hit_mask = 0; - RTE_TABLE_LPM_STATS_PKTS_LOOKUP_MISS(stub, n_pkts_in); - - return 0; -} - -static int -rte_table_stub_stats_read(void *table, struct rte_table_stats *stats, int clear) -{ - struct rte_table_stub *t = table; - - if (stats != NULL) - memcpy(stats, &t->stats, sizeof(t->stats)); - - if (clear) - memset(&t->stats, 0, sizeof(t->stats)); - - return 0; -} - -RTE_EXPORT_SYMBOL(rte_table_stub_ops) -struct rte_table_ops rte_table_stub_ops = { - .f_create = rte_table_stub_create, - .f_free = NULL, - .f_add = NULL, - .f_delete = NULL, - .f_add_bulk = NULL, - .f_delete_bulk = NULL, - .f_lookup = rte_table_stub_lookup, - .f_stats = rte_table_stub_stats_read, -}; diff --git a/lib/table/rte_table_stub.h b/lib/table/rte_table_stub.h deleted file mode 100644 index f7e589df16..0000000000 --- a/lib/table/rte_table_stub.h +++ /dev/null @@ -1,30 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright(c) 2010-2014 Intel Corporation - */ - -#ifndef __INCLUDE_RTE_TABLE_STUB_H__ -#define __INCLUDE_RTE_TABLE_STUB_H__ - -/** - * @file - * RTE Table Stub - * - * The stub table lookup operation produces lookup miss for all input packets. - */ - -#include "rte_table.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** Stub table parameters: NONE */ - -/** Stub table operations */ -extern struct rte_table_ops rte_table_stub_ops; - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/lib/table/table_log.c b/lib/table/table_log.c deleted file mode 100644 index b329edd2e6..0000000000 --- a/lib/table/table_log.c +++ /dev/null @@ -1,7 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright (c) 2024 Stephen Hemminger - */ - -#include - -RTE_LOG_REGISTER_DEFAULT(table_logtype, INFO); diff --git a/lib/table/table_log.h b/lib/table/table_log.h deleted file mode 100644 index b24b8614c2..0000000000 --- a/lib/table/table_log.h +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: BSD-3-Clause - * Copyright (c) 2023 Red Hat, Inc. - */ - -#include - -extern int table_logtype; -#define RTE_LOGTYPE_TABLE table_logtype - -#define TABLE_LOG(level, ...) \ - RTE_LOG_LINE(level, TABLE, "" __VA_ARGS__) -- 2.53.0