diff options
author | sam <sam@FreeBSD.org> | 2009-06-01 18:07:01 +0000 |
---|---|---|
committer | sam <sam@FreeBSD.org> | 2009-06-01 18:07:01 +0000 |
commit | f0cca176f4e17d9ff6a1088929ee2c843e1000ab (patch) | |
tree | 4aa57b01fcce3ef5523174cf4075c853234998b3 | |
parent | d54138ae871ddc515927682de43257eeddad4d7d (diff) | |
download | FreeBSD-src-f0cca176f4e17d9ff6a1088929ee2c843e1000ab.zip FreeBSD-src-f0cca176f4e17d9ff6a1088929ee2c843e1000ab.tar.gz |
driver for Marvell 88W8363 Wireless LAN controller
-rw-r--r-- | sys/conf/files | 27 | ||||
-rw-r--r-- | sys/conf/options | 8 | ||||
-rw-r--r-- | sys/contrib/dev/mwl/LICENSE | 43 | ||||
-rw-r--r-- | sys/contrib/dev/mwl/Makefile | 22 | ||||
-rw-r--r-- | sys/contrib/dev/mwl/mw88W8363.fw.uu | 2179 | ||||
-rw-r--r-- | sys/contrib/dev/mwl/mwlboot.fw.uu | 97 | ||||
-rw-r--r-- | sys/dev/mwl/if_mwl.c | 4990 | ||||
-rw-r--r-- | sys/dev/mwl/if_mwl_pci.c | 316 | ||||
-rw-r--r-- | sys/dev/mwl/if_mwlioctl.h | 136 | ||||
-rw-r--r-- | sys/dev/mwl/if_mwlvar.h | 354 | ||||
-rw-r--r-- | sys/dev/mwl/mwldiag.h | 108 | ||||
-rw-r--r-- | sys/dev/mwl/mwlhal.c | 2703 | ||||
-rw-r--r-- | sys/dev/mwl/mwlhal.h | 666 | ||||
-rw-r--r-- | sys/dev/mwl/mwlreg.h | 1352 | ||||
-rw-r--r-- | sys/modules/Makefile | 1 | ||||
-rw-r--r-- | sys/modules/mwl/Makefile | 41 | ||||
-rw-r--r-- | sys/modules/mwlfw/Makefile | 14 |
17 files changed, 13057 insertions, 0 deletions
diff --git a/sys/conf/files b/sys/conf/files index c1f9a42..a074189 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1162,6 +1162,33 @@ dev/mpt/mpt_pci.c optional mpt pci dev/mpt/mpt_raid.c optional mpt dev/mpt/mpt_user.c optional mpt dev/msk/if_msk.c optional msk +dev/mwl/if_mwl.c optional mwl +dev/mwl/if_mwl_pci.c optional mwl pci +dev/mwl/mwlhal.c optional mwl +mwlfw.c optional mwlfw \ + compile-with "${AWK} -f $S/tools/fw_stub.awk mw88W8363.fw:mw88W8363fw mwlboot.fw:mwlboot -mmwl -c${.TARGET}" \ + no-implicit-rule before-depend local \ + clean "mwlfw.c" +mw88W8363.fwo optional mwlfw \ + dependency "mw88W8363.fw" \ + compile-with "${LD} -b binary -d -warn-common -r -d -o ${.TARGET} mw88W8363.fw" \ + no-implicit-rule \ + clean "mw88W8363.fwo" +mw88W8363.fw optional mwlfw \ + dependency ".PHONY" \ + compile-with "uudecode -o ${.TARGET} $S/contrib/dev/mwl/mw88W8363.fw.uu" \ + no-obj no-implicit-rule \ + clean "mw88W8363.fw" +mwlboot.fwo optional mwlfw \ + dependency "mwlboot.fw" \ + compile-with "${LD} -b binary -d -warn-common -r -d -o ${.TARGET} mwlboot.fw" \ + no-implicit-rule \ + clean "mwlboot.fwo" +mwlboot.fw optional mwlfw \ + dependency ".PHONY" \ + compile-with "uudecode -o ${.TARGET} $S/contrib/dev/mwl/mwlboot.fw.uu" \ + no-obj no-implicit-rule \ + clean "mwlboot.fw" dev/mxge/if_mxge.c optional mxge pci dev/mxge/mxge_lro.c optional mxge pci dev/mxge/mxge_eth_z8e.c optional mxge pci diff --git a/sys/conf/options b/sys/conf/options index 426d983..1d9a67b 100644 --- a/sys/conf/options +++ b/sys/conf/options @@ -777,6 +777,14 @@ MALO_DEBUG opt_malo.h MALO_TXBUF opt_malo.h MALO_RXBUF opt_malo.h +# options for the Marvell wireless driver +MWL_DEBUG opt_mwl.h +MWL_TXBUF opt_mwl.h +MWL_RXBUF opt_mwl.h +MWL_DIAGAPI opt_mwl.h +MWL_AGGR_SIZE opt_mwl.h +MWL_TX_NODROP opt_mwl.h + # dcons options DCONS_BUF_SIZE opt_dcons.h DCONS_POLL_HZ opt_dcons.h diff --git a/sys/contrib/dev/mwl/LICENSE b/sys/contrib/dev/mwl/LICENSE new file mode 100644 index 0000000..2206210 --- /dev/null +++ b/sys/contrib/dev/mwl/LICENSE @@ -0,0 +1,43 @@ +FIRMWARE LICENSE TERMS + + +Copyright (c) Marvell International Ltd. + +All rights reserved. + +Redistribution. Redistribution and use in binary form, without +modification, are permitted provided that the following conditions are +met: + +* Redistributions must reproduce the above copyright notice and the +following disclaimer in the documentation and/or other materials +provided with the distribution. + +* Neither the name of Marvell International Ltd. nor the names of its +suppliers may be used to endorse or promote products derived from this +software without specific prior written permission. + +* No reverse engineering, decompilation, or disassembly of this software +is permitted. + +Limited patent license. Marvell International Ltd. grants a world-wide, +royalty-free, non-exclusive license under patents it now or hereafter +owns or controls to make, have made, use, import, offer to sell and sell +("Utilize") this software, but solely to the extent that any such patent +is necessary to Utilize the software alone, or in combination with an +operating system licensed under an approved Open Source license as +listed by the Open Source Initiative at http://opensource.org/licenses. +The patent license shall not apply to any other combinations which +include this software. No hardware per se is licensed hereunder. + +DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIPOSSIBILITY OF SUCH DAMAGE. + + diff --git a/sys/contrib/dev/mwl/Makefile b/sys/contrib/dev/mwl/Makefile new file mode 100644 index 0000000..13dfa69 --- /dev/null +++ b/sys/contrib/dev/mwl/Makefile @@ -0,0 +1,22 @@ +# $FreeBSD$ + +FILES= mw88W8363.fw.uu mwlboot.fw.uu + +mw88W8363.fw.uu: mv88W8363fw.h LICENSE + (cat mv88W8363fw.h; \ + echo 'int main(void) { \ + write(1, fmimage, sizeof(fmimage)); return 0; \ + }') | ${CC} -o build -x c - + (sed 's/^/# /' LICENSE; ./build | uuencode mw88W8363.fw) > ${.TARGET} + +mwlboot.fw.uu: mvbootfw.h LICENSE + (cat mvbootfw.h; \ + echo 'int main(void) { \ + write(1, hlpimage, sizeof(hlpimage)); return 0; \ + }') | ${CC} -o build -x c - + (sed 's/^/# /' LICENSE; ./build | uuencode mwlboot.fw) > ${.TARGET} + +clean: + rm -f build build.c ${FILES} + +.include <bsd.prog.mk> diff --git a/sys/contrib/dev/mwl/mw88W8363.fw.uu b/sys/contrib/dev/mwl/mw88W8363.fw.uu new file mode 100644 index 0000000..9a18aff --- /dev/null +++ b/sys/contrib/dev/mwl/mw88W8363.fw.uu @@ -0,0 +1,2179 @@ +# FIRMWARE LICENSE TERMS +# +# +# Copyright (c) Marvell International Ltd. +# +# All rights reserved. +# +# Redistribution. Redistribution and use in binary form, without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions must reproduce the above copyright notice and the +# following disclaimer in the documentation and/or other materials +# provided with the distribution. +# +# * Neither the name of Marvell International Ltd. nor the names of its +# suppliers may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# * No reverse engineering, decompilation, or disassembly of this software +# is permitted. +# +# Limited patent license. Marvell International Ltd. grants a world-wide, +# royalty-free, non-exclusive license under patents it now or hereafter +# owns or controls to make, have made, use, import, offer to sell and sell +# ("Utilize") this software, but solely to the extent that any such patent +# is necessary to Utilize the software alone, or in combination with an +# operating system licensed under an approved Open Source license as +# listed by the Open Source Initiative at http://opensource.org/licenses. +# The patent license shall not apply to any other combinations which +# include this software. No hardware per se is licensed hereunder. +# +# DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIPOSSIBILITY OF SUCH DAMAGE. +# +# +begin 644 mw88W8363.fw +M`0```````````@``!$>Z"!CPG^48\)_E&/"?Y1CPG^48\)_E&/"?Y1CPG^48 +M\)_E8#4!`#PU`0!`-0$`1#4!`$@U`0!,-0$`4#4!`%PU`0#$@(_B`P"8Z`@` +M@.`($('@`;!`X@$`4.$3```*<`"PZ`4`5.'Z__\*`0`4XPM`A!`!`!7C"U"% +M$`(`%>,)4(40`U#%XQ!@5N*,$+0HC!"E*/O__XJ&;K#A#`"T*`P`I2@$<)1$ +M!'"%1.G__^H(()CE##"8Y0@@@N`(,(/@`<!"X@!PH.,``*#C`&"@XP"PH.,# +M`%+AMDL`"S``LN@!`!3C#$"$$`(`%.,)0(00`T#$XQ!05>+!"*0H_/__BH5> +ML.%!`*0H!'"$1/#__^H`0P$`/$,!`#Q#`0!D0P$`@+0/(``#@+P`27!'`"`` +M!/=*`""1:``I!-`%*0/1]4@`:`#@]$@!,9%@<$<0M1/P._L,(`;P"_SM3&!A +M$"`&\`;\(&%,(`;P`OP0,`;P__L``@`.`0<)#P`&``\A<>!P`"`0O8"U"?"Q +M^P`@@+WXM=]-_R$E,2`U*!P2\*/]+AQ`-@$D-',*(%,A2%4I''(Q,!SA,!+P +M[OX(('"#!R"P@P8@\(,H'&`P""$!@`HA08`,(8&`#B'!@!`A`8$2(4&!%"&! +M@3&)@"`@(H%#0""!0S&!Z8PO'!`W(4,10X%#Z82N-\]K`0```/P!`````@`` +MP))E(RD<`"8B,08@_G,2\,/^`B$H'"`PN7.$<L1R[&(HC@0B&">@0XA#_R'Y +M,9!#B$-@,"P<,#0HAJ-X8'@&(1L"&$.(0SA#8".80X`AB$,#"F!PHW!P-2YP +M;G``(`PA@"6L2X(`(#/6&'-X,GC_)QL"&D-3"%L``B*30P0BDT,*')(&^3>[ +M0](-&D,R<!,*<W"S>')X`3D;`AI#!B/;0Q-`&"(30Q\<8".?0Z]#=W`["K-P +M,WGW>`$P&P(?0S\)/P$"-SL*]W`S<00HR=.12`$F(#`!B`0G,4,Y0PL<_R'Y +M,8M#>#,#@`-YQG@;`AY#-@DV`0$VQG`S"@-Q@X@!)C-#.T.+0V@S@X`#>L9Y +M&P(>0S8)-@$"-L9Q,PH#<@.)`28S0SM#BT,#@0-[QGH;`AY#-@DV`08VQG(S +M"@-S@XE;"%L`NT.+0R@S@X$#><%XQGD;`AE#`WIN3QL"'D.)&0-[QGH;`AY# +MB1D/(UD:`WS&>PD'&P(>0S,)&P$)#QE#"PK!<P-T#B&!80`A`6$!)D9A`"`8 +M-P(F@@#1&8MX37@!,!L"'4,8(QU#37`K"HMPNUH$);-#6PA;`*M#NU*+>$IX +M@"4;`AI#JD-*<!,*BW!*B`\C&P(:0TJ``BC<TTU-(#4H'#(P!QP3\,#YP`C` +M``4P."$(0\D`B$/_,($P.1S.-?*[`0```/@#`````@``-'K;GA+PROTH'#,P +M!QP3\*_Y,$,,(8A#"#`P(8A#_R'!,2`PB$.`,#D<$O"W_6AK#B&(0P8P$">X +M0V`C&$-H8R@<-3`%'!/PD_FP0P0AB$,((8A#N$,@(A!#*1P2\)[]_R"@<>!Q +M`B``(34]*A@P,@$P$"B1<?G3*!R@,(%S_S4A-8%R*7!`(`CP(O\`(/B]\+6% +ML``E"?!3_?_W?/XH0P<<($B!:2!.L78)##(<$8,!'`EH&$I)!TD/`29P,@$I +M`=%6<07@`6A)!TD/`BD`T5%Q`6A)!P31%$L9:!`B$4,98!!+&#,9:/\B$4,9 +M8`U(@&D00,$H"$T`T2YP#/"O_`DH`-&N<`=(`&@)\//Y`"`X0P$<`Y$+X#!- +M`,#4)@#`````P``@`(`44`+`)-``@'E/.!P)\-GZ/F#_]PO^`YD(0P.0.&@` +M*`?0=$@3\!#Y`1P`(`SPV_L$'`SPPOMI1@$@$/`[^0"K&'@0*`#1;G`3\#;Y +M:4]R/VXAR%43\#CY`)`X'#(P!)`3\/+X`)G`",``20=)#PA#!)D2\/_\`:D" +MJ!/P+?D`JQEZ.!P@,,%R&7D!)H%R9F!A:0,@"(!A:5=("&$@'`SP'OP!*&71 +M4DA43P!H`"@!T%-(`.!32'AA(2!32<`""&(`)KYA.&D^8:!H4$H!'/\P83`0 +M8$Y*3TO@=:5%`0```/0%`````@``+8(%[A%@"AS8,AI@DR+2`$Q+BA@:8`H< +M2TO@,AI@2TJ)&$M*$6!I:;EB*FGZ8@%@*6F!@!/P[O@`(!/P\_A%20Y@1$A$ +M2D`P`F`&8@$D!&%.8$)@1F)$88Y@@F"&8H1ASF#"8,9BQ&$\2#U)"((1\`#_ +M/$XO23"+P#$4*`'1#&`*X!,H"&D-T04B$@400PAA"&@U2A!#"&`#F``H!M`! +M(`6P\+TQ2A!#"&'UYP;PTOD&\.OY+DH0:!4AB0$(0Q!@+$H0:`0A"$,08``@ +MZ&`J2`D#`6$120EH`"D$T'EI`2*2!A%#>6$>2P0S&6@/(A(%$4,98!I+(#O9 +M:O\B$@*10]EB,8L3*0?1&TEE(L`Y2F+%(HIB)R'!80`@P>=T)0#`PDT`P"T! +M````I0"`@J@"E+^H`@8`J`"`S!,"P-`3`L#4$P+`W!,"P-@3`L!T!```X!," +MP$#H`(#___\/-CX``"`@`(`44`+`_P$``#8(```(@`"0*)``D,"B`("`M1/P +M6?@3\%OX`""`O>1)@+6(:`$PB&#B2$%J`2%!8A/P5O@!(("]_[4)G@8H`=(2 +M*0+3F"`$L/"]VTV"`*Q8`"P!T)<@]N?73P0!8#_D&:Q0(6"O6`*<B0!\8*Q8 +MHV"J6-9@T$H8,E!0S4G(:0$PR&$`(-_GF+7)3``AX&#A81/P+OA`WG[T`0`` +M`/`'`````@``V6J[4P`B`)+@:<A+`AP$(?_WRO_@:0$PX&&8O<1(P$D(8<%( +M=#B(80`@<$?_M8>P!!P&+!&8$)V61IQ&`M.8(`NP\+VZ2:8`&#F)60`I`="7 +M(/7G`"("(021!9*P3P(<`Y`X:;-)`)"4(&!#1!@"D@B:(!QA1@&5<T82\/G_ +M`"@'T3AIJDE`&3AA&#F,40`@UN<!(-3G@+4&*`72I$F``!@Y"%@2\.S_@+V` +MM08H!=*?28``&#D(6!+PZO^`O;"U!B@*TII-A``8/2A9$O#H_RA9$O#M_P`@ +M*%&PO8"U$O#O_X"]^+4!*`'3F"#XO8Q.A0`4-G%9`"D!T)<@]N<X(XE)6$,4 +M(@"2K#E$&(1/`"$!(B`<NVD2\-O_`"@%T711N&D4,+AA`"#@YP$@WN>PM0$H +M!])Z380`%#4H61+PT/\`("A1L+T(M0$H"])T28``%#$(6&E&`"(2\,K_`"@! +MT0"8"+T`(/SG@[4!*`K2;$F``!0Q"%@!J0`B$O#!_P`H`-&,O0$@_.<`M0,< +M`2B)L!/2!*D%J`"0`9%A29@``ZH"DA0Q"%@(J0>J!JL2\+#_`"@"T0>8";`` +MO0`@^^=P1X"U@"`2\*S_@+V`M0`@$O"G_X"]<$=P1W!'@+6`(!+PG_^`O8"U +M$O";_X"]@+4%*`C2'"-,2EA#8#*`&`H<`"&RL.CN`0```.P)`````@``'G.Y +M#A+PEO^`O8"U!2@&TAPC1DE80V`Q0!@2\)/_@+V`M04H"-(<(T!)6$-@,4`8 +M`"')0Q+PCO^`O8"U!2@&TAPC.DE80V`Q0!@2\(O_@+V`M08H!M(U24`![#%` +M&``A$O"(_X"]@+4&*`;2+TI``>PR@!@`(A+PA?^`O1RU%!P&*`[2`"+20P"2 +M*$I``>PR@!@B'`&K$O!]_P`H`=$!F!R]`"#\YX"U!B@%TA])0`'L,4`8$O!V +M_X"]@+4&*`;2&DI``>PR@!@"(A+P6_^`O0"UA[`&*!'2`ZD`D1-)`JI``>PQ +M0!@!D@6J!JD$JQ+P8?\`*`+1!9@'L`"]`"#[YQ"U!!P$*`S2'"`,26!#0!@! +M(@`A$O`._PA*`""A`!`Z4%`0O00```0`D`"0G!<`!'L'``"\!``$+`$`!%@9 +M``2`M00H!](<(W5)6$-`&``AR4,2\/_^@+V`M00H#-)P2P$A@@`0.YE0'"-M +M25A#0!@`(<E#$O#N_H"]@+4$*`W29TN!`!`[6E@!*@?1`"):4!PC8TE80T`8 +M$O#D_H"]@+4$*`72'"->25A#0!@2\-K^@+V`M<%H`AP`:))H$O`"_X"]\+4" +M*`'3F"#PO55-A``K60`K`="7(/"]+"-03D-#<#:;&4Y/*U$&`<@WN%'P&8%@ +MPF``(4%@2TD982E92&&"X3Q3`0```.@+`````@``ZIL'LP`@\+W^M0L<$1P" +M*!;21$P"`<@T%1EJ:(0``"I!3@[1`2("D@"3`9$P60`A`FE#:1+PT/X`*`'1 +M`2!H8/Z]-1P*'!D<,%D2\,W^*%D2\-+^].>PM0(H!M(R380`*%D2\-'^`"`H +M4;"]@+4"*`32+4F```A8$O"^_H"]@+4"*`32*$F```A8$O#%_H"]$+4$'!+P +MR/X@8!"]`","X`%P`3`!,Y-"^M-P1_BU($@`:`5H`"T@T!Y/`"0!(*!`*$`7 +MT!M)H``8,0A8A@"Y60`I#]`(:(IH*1P2\'7^`B@(T;A9PV@`*P30*AP!(0!H +M$O"B_@$T$BS@VPM,(#P#S`@\@4(+T`A)"#$(7!+PG/X`*`30(&@!,``&``X@ +M8``@^+T``%@9``0D```$FPL```````2<%P`$@+4+\.7__R$#(`_P9OWK200@ +M"&`&\(C]!/!<^X"]YTAPM8`P!&D`:^5-`"9I:Z%"!M'D28Q"`]!IBP$Q:8,! +MX&Z#;&.J:P$#$@,2"PD+D4(>T0$#'-"IBP$Q"00)#*F#"BD,V=5*0#(1:5)I +M$4,&T:Z#*&2H;`$PJ&3_]\'_:(L**`39;H/L8VAL`3!H9'"]KH.H8_/G<$<! +M('!'`2!P1P`@<$>`M0`H!=`!'``@__=`_@`@@+T!(("]PT@'(0%B'R)"8H%B +MPF*A>`!V`0```.0-`````@``\V/9PP%C0F.!8\)CODDD(`A@2&"(8,A@O$D! +M(`AP<$=P1X"U2W@*>!L"&D,!(]L#&D,*<!,*2W``(HIQRG&*>,MX&P(:0P7P +M_?R`O8"U2W@*>!L"&D,!(]L#&D,*<!,*2W``(HIQRG$((@7PZ_R`O1"U"O#^ +M^:9)"2*):)(!B1B):P`&``X`*0/1G$D*?@$J!=$`(`SP6_H$'``@$>`"*`'0 +M`R@%T04@#/!1^@0<!2`'X,A[`"CVT`4@#/!(^@0<"2`,\,GZCTC`,(1CD4B$ +M8)%(A&`0O8"U2W@*>!L"&D,!(]L#&D,*<!,*2W`!(HIQ$PK+<0@B!?"E_("] +M,+4`((9)`B+3`)L86Q@;>@$K`M$!,``&``X!,A(&$@X'*O'3`20`(P$H=4I] +M3038`2@(T8A^`2@%T%1R*&L`*`#0*V,PO5-R*&L`*/K19B`H8S"]L+4,'!4< +M`2$(\.OZ`"@`T;"]1($%@;"]^+4/'`0<`"AQT"`=`)`2\`7\T")I27I#5A@` +M*!/0!"!<23!W"&H!,`AB`2@+T5Y(6TD`BT`Y#R@!T0P@`N`3*`'1!2!(8S4< +M'C4@'!+PYOLI'#DQ$?#X_P"8$O#?^RD</3$1\/'_XWJ@>BEY&P(80VMYP`?` +M#QL"&4-)"$D`"$,H<0,*:W'C>J%Z`B(;`AE#B0?)#Y!#20#]0"E3`0```.`/ +M`````@``!XMG?@%#*7$+"FMQXWJ@>B`B&P(80X`&P`^10T`!"$,H<0,*:W'C +M>J%Z0"(;`AE#20;)#XD!D$,(0RAQ`PH@'`@P:W$2\*#[:7BK>(`&P`\;`AE# +M("*10P#@3N!``0A#:'`#"JMP8'M!(0PB2%4@>ZAQXWJ@>G&,&P(80P`'@`^` +M`)%#"$-PA``'@`\!)0$H`M$;2<US`N`920`@R'/_]^G^H'KC>AL"&$,`!X`/ +M`=$`(0#@`2$X'`OP3?P52`"+#R@:T1E(`GH"*@'0`RH4T0-[%DF0.0`K!M&* +M>P(J`=`#*@K117(#X(M[`2L%T4)R0'J(<P$@`_#1^/B]`*@`@"0S`,!"P`(` +M@*``@("F`(#P)0#`%%`"P$"J`(!`JP"`-&@"P$"B`(#@2P$`\$T`P/^UL;`& +M'!0<!B(!J#*9.YT\GQ'P#?X`J]R!`2#8<@`@&'0MJ0YS2',!J`CP./D$'`$H +M#M$`JYJ)V8D!JS`<#?`C^P`M'M$`JUE\.IC_]_;^).`'+"+1,I@(\#?Z!B@" +MT0H@-;#PO0&H"/`9^00<`2@4T0"KFHG9B0&K,!P-\`3[`"W@T`"K6'S0(^U) +M*GA80T`8R"$*5,`P1W+4YR`<X.<PM>A-`"((X(1"!=%0`$`90'@(<`$@,+T! +M,E,`[%QC'/+1`"`PO?BU!1S?2E`U*'FC3X2%`0```-P1`````@``>9#`SFEY +M_R<6'!0<<#00-ODW`"DDT6E&__?<_P"K&'C722!P2W@*>!L"&D,"(YI#4@A2 +M``0CFD,*<!,*2W#S>P`K`]&Z0X`&@`X&X`4C`R@`TP,<ND.8!H`.P``00Q[@ +MQTD@<$MX"7@"(AL"&4.10\)*@`8P,E-X$G@;`AI#DP?;#UL`&4,!(U('T@\9 +M0P0CD@"90Q%#N4/`#0A#N$D(<`,*2W"H>6E&__>4_P"K&'A@</%[`"D'T;%) +M@`:*B,`-ND,00XB`^+T%(0,H`-,!'`@<\>?PM:E/!"8`(F`ED0#)&0QX2W@; +M`AQ#XP<5U+1#(PH,<$MP`R@&T4QXBW@;`AQ#K$,@-`3@3'B+>!L"'$.L0TQP +M(PJ+<`3@8P<"U`,H\M'JYP$R!"K9T_"]$+5+>`IX!'@;`AI#0WB2!](/&P(< +M0P(CG$-2`")#`G`3"D-PBWA*>$1X&P(:0X-XT@:2#QL"'$,8(YQ#T@`B0T)P +M$PJ#<(MX27@;`AE#20:)#V`CFD-)`1%#07`+"GY*@W"2:/\R83+3>8`B`2L! +MT1%#`."10T%P"PJ#<!"]<+5S3@`D-1PP-:``@!DI'/_WM_\!-`0L]]-PO?BU +M;$TL'"`TH'GC>1L"&$/`!S#4#_!4^B@<0###>H!Z`28;`AA#P`8`*`K:94F( +M:#(X"&!C2$`P!F!#\=N$`0```-@3`````@``C7A^<P$="&@P0PA@`2`2\!?Z +MXWF@>1L"&$,P0Z!Q`PHH'"(PXW$2\+/YH7H`(P"1*1P0,8I[S'L!'"`<$/!! +M^OB]\+5.3K^P,1P@,0$@(9`^D8AYRWD;`AA#P`<$U$Q)"&A`(A!#"&`(\!S[ +M!!P(\"O[/ID%'(AYRWD;`AA#P`<)U"!X8W@;`AA#0`1`#!PH`=#_]YK_(7AC +M>#E*%AQ@-CV6%AP;`AE#H#8\EA8<2`0X2T`,$#:80EO06]P7'%`W.Y<7'"(W +M,#+_(Q`SF$(YDCJ78]!CW/\G`3<=*`'1`?!M^W3<&2@!T0'P0OM8W`,H`=$! +M\`'X!"@!T0'P0O@4*`'0`O#K^6!Y`2@!T0'PM?D"*`'1`?#+^0,H`=`!\`3Z +M`"$@'`@P"/`*^`8<(!RZ(0XP$?#'^P`N`=$!\,[YO""`7;@C%$D@<W")J")@ +M<W![H'-P>UA#0!BP,(!X,1P.,>!S<(D@="`<(#`1\/W[`?#6^=+C#^$``.!+ +M`0#\)0#`4$T`P$A-`,`44`+``*8`@`P@`(`"`@``2,,!`+'C9N`:*`'1`?!4 +M^QLH`=$!\'O['"BFT2!Z8WH;`AA#`2@!T`+P,?F@>N-Z&P(80P#@$N`!*`'1 +M`?`4^0,H`=$!\`CY!2CMT?=)`2"):/\Q83&(<0`@`?`+^;A"<=`BW!XH`=%$ +M-U_<`0```-05`````@``E("@`P'P!OD@*`'1`?!5^%`H`-!WYP$@P`,(0R!P +M`PIC<``@H''@<2`<)AP(,!+PL?CF20`H8GELT`$@D$`*>!!#"'"WX_\C"S.8 +M0F/0`S.80G'0`3.80MS1`"`,\(OYVTD`(`A@VTD(:$`B$$,(8*CG_R,D,YA" +M<M!-W/\C&C.80F[0&=P&.YA"`=$!\)OX`C.80F;0`S.80KO1('IC>AL"&$,` +M*`'0`O`5^<I($O!Q^"$<"C$!\(#Z_R,;,YA":=`#,YA"9]`#,YA"HM$@>F-Z +M&P(80P`H<M`!*''0`B@`X.7B;M`#*,+1H7KC>CV8&P(90X![$/`Y_[A)`2`( +M<3J8$O!'^#N9`"<)>@"1LGOV>Y#BU^,VX_\C*3.80@'1`?"8_R/<!#N80G+0 +M`C.80G#0`3.80@#@QN,`T./F('IC>AL"&$,!*)+1XWJ@>B$<&P(80P(&$@ZA +M2`PQ`G`"X-C@4^'CX`$P$?``^RKG_R,J,YA"`=$!\&#_FDN80MW18WH@>I!) +M&P*):!A##B#_,6$QB'(6YP+BA.*03Y)+4#>80@'1`?#Q^''<CTL8.YA"`=$! +M\)'Y.MP1(QL"F$("X"SB/N(4XFC0$=R&2P(SF$(!T0'P`O@!,YA"<=`!,YA" +MK-&"20AX('*J(`#PGO]^2R@[F$)ET`(SF$)JT'I+&3O),`,:`0```-`7```` +M`@``8&@>OIA"F]$!(,`#"$,@<`,*8W`!X(SCP.,`(*!QX'$@'`@P$?"X_VU) +M#"+(<0+P9/AN2P<[F$(!T0'P$?DOW&I+%SN80I/0`C.80@'1`?"/^69+"#N8 +M0@#05N9>24`Y"&H!,!V0(!P(,!'PE?\8J]AV(!P,,!'PC_\8JYAV(!P0,!'P +MB?\8JQAW(!P`)A0P`.`AX":0`3`ED`'P8?CGXU)+!#N80@'1`?`S^@$SF$(! +MT0'P'_H",YA"SM%&24`Y"&H!X&#AU.,!,``F)QP(-Q^0`?!8^,WC1$L',\`: +M%"@$T@.C&Q@;6EL`GT0-Y@``90@Z`D`"R0[0#G0/I@BE#[</GPC[#_L/^P_[ +M#_L/^P_'#\L/V@_C#R")`"@#T#!)`2#(<%#F+DD`(,AP__=M^DKF8'T!,`8& +M-@X@'!`P!QQF=1'P*?\!'#`<#O#W^#@<$?`B_V%]'",@3EE#=CZ)&;0Q$?`O +M^S@?!QP1\!7_`1Q@?0[P[O@X'!'P#O]A?1PC64.)&;@Q$?`=^R!Z8WH;`AA# +M@`<1U>-ZH7I@?1L"&4,.\.+X8'T<(UA#@1F@>N-ZQ#$;`AA#$?`%^R!Z8WH; +M`AA#0`<@U2%]8'T3X!10`L#$)@#``*4`@`"C`(#&30#`)#,`P+E(`L`!`@`` +M*1$``'!$`0`.\+[X8'T<(UA#@1G;DPP&`0```,P9`````@``IW$<X[PQ('T1 +M\-[Z[$D!((APV^7C>J!ZZ4D;`AA#"'`@>NA)8WH.'*(Q&P(80Y8V`"@XD5S1 +M)QP((!^0*#<"(!#P$_@*X#B8$?"H_C%H"@)1&A'P5_HX8`0_!#X?F`$X'Y!# +M'._1V$@!)P%H.4,!8`OP6/F`(1\@#O#9_M`A&R`.\-7^8"$G(`[PT?Y0(2@@ +M#O#-_B@A*R`.\,G^/IF(><MY&P(80X`&!=4D(2P@#O"^_B`A!.`O(2P@#O"X +M_C`A/R`.\+3^."$O(`[PL/YU(<L@#O"L_CTAS2`.\*C^/R'.(`[PI/ZV20`@ +M2'`!\(CX(!PH,`@A`"<#X`=@-V`$.`0^`3GYTC@<.)D1\%WZKDD(:$`(0``( +M8`OP!/F((1\@#O"%_@`A&R`.\('^/R$G(`[P??X_(2@@#O!Y_BDA*R`.\'7^ +M-B$L(`[P<?YX(2\@#O!M_C\A/R`.\&G^12'+(`[P9?Y=(<T@#O!A_K\ASB`. +M\%W^DDD!($AP_R/X,P$A`2("(`_P7O\!\$_]`"`8JQAW"_#'^!^I9R`.\-?] +M&*L8?Z!RA.4@>H9)8WH.'`\<&P(80YHWGC8`*!'1,1PX'`WP\O\X'!'PZ?T! +M`@\:,!P1\.3].1P1\)7Y(1P,,6[E.1P`)S@<$?#O^3$<.!P1\.OY#?#-_UOE +M=$H0:`$A"02?"8S/`0```,@;`````@``4YFB7@A#$&`!(`[PE/AQ24AI,"*0 +M0TAA;4D@.4AJ!"*00TAB"_#N^69)`"</<1#PQ?PZF!'PM/T[F0EZ`)&R>_9[ +M`1PP'#L<#_!#_L#D74D!(`AQ.I@1\*/].Y\`(SEZ`)&R>_9[`1PP'`_P,OY8 +M2B`Z$&H!(0D$B$,08E1)!#$(:`0B$$,(8``@#O!4^%%)%#$(:#`B$$,(8`OP +MN?FA>N-Z/9@;`AE#@'L0\&7\C^1)2$%H20A)`$%@B>3^]Z'^!AQ@>?\C73-$ +M3UA#P!DP,!HB(1P(,1'P3_CC>*)X8'D;`AI#_R-=,UA#P!E0,!HZ(1PB,1'P +M0/A@>?\A)C$X2D%#B1@W2H``$5`(\&SX,!SQXS5(`&@`*!70+DD(:@$P`"8G +M'`@W'Y`*X*WB.!P1\#?]`1PP!@$B``X(\'_X`38?F(9"\M,@'`@P$?`I_8`' +M`=4!(`#@`"`D20DBB6B2`8D8B&/_]Y+X$.<@'`@P$?`7_3R9P.,720]J`3<` +M)@7@,`8`#B%Z"/#"^`$VOD+WT_SF('IC>AL"&$,!*(?1(!P+,`<<$?#\_,(' +M$4DFU0`@\',(<`])6B`(8`])9"`(8"'@E^(D,P#`4$T`P%3Q`0`@J`"`P*(` +M@$"F`(#H(P+`Z#X"P.@8`L!T)0#`%%`"P'\L`,!`10$`1$4!`$`'`M4]*@'' +M`0```,0=`````@``2F%\+@$@\',(<#@<$?#*_$`%P`X!*`S1`""P<SF:!"%3 +M>!!X&P(80XA#$'`#"E-P5N!@)P(H%=$!(+!S.9H$(5-X$'@;`AA#B$,0<`,* +M4W`YFI-X4'@;`AA#N$-0<`,*DW`]X#F:!"%3>!!X&P(80PA#$'`#"B`<##!3 +M<#>0$?"1_,`&/IF`#PAP-Y@1\(K\P`:`#P$H#-$YFI-X4'@;`AA#N$-0<`,* +MDW`"(+!S`"`.X`,H$=$YFI-X4'@;`AA#N$,@,%!P`PJ3<`,@L',!(.Q)B'0$ +MX`KB8>`^F0(@"'"@>CJ9$?!T^+![/)D(<CR92'("\&__.9I0>)-X&P(80T`& +M@`\+\![].I@1\$O\.YF$1@EZ`",`D;)[\'MA1@_PVORP>__WXOG620$@B'%( +M:@`H`-`BY+!["O#.^![DT4@$8R`<"#`1\"W\STD!*!S1X&@1\#WX(!P,,!'P +M(_R`!P?5.9H0>%-X&P(80P(C&$,&X#F:$'A3>!L"&$,"(YA#.9H#"A!P4W`, +MY``HTM$('!'P"/PA'`PQ$?`:^,CD(!P*,`OP=O_#Y/[W+?T=D+5(Q&(@'`@P +M$?#U^[-/&#\!*''1/)C`>P`H%]"O2-PP$?#I^P$<`B`-\+?]JTCL,!'PX?L! +M'`(@#?#%_:=(Y#`1\-G[`1P"(`WPP_VB2,!J__<#H9R@`0```,`?`````@`` +MOHG"DPCYH$C`:C:0##`1\,O[GDD4.361`2AKT3D<`"`0\-C_-I@4,!'POON7 +M2<`'+#E+>`EXP`\;`AE#20A)``%#DD@/'"PX!W`["D-P-I@<,!'PJ?L`!@`. +M^0<3U!ZI__?&^(E)!"(L.4MX"'@;`AA#D$,"(YA#"'`#"DMP'.!SX3_A!.(8 +MJQAV.9H$(8]#$7A3>'U(&P(90TH'T@\L.)(`.D,"(YI#`.#/X(D'R0])`!%# +M`7`+"D-P=$C_(BPX0W@!>/DR&P(90Y%#&*L:?I(&T@T10P%P"PI#<`-YP7@/ +M(AL"&4,10\%P"PH#<369#B"<X/_G.1P!(!#P:_\`(#69$/!G_P`A@>`?F0@! +M-)#`&3.0%#`1\$?['YE;2HD`+#J/&'MX.7C`!QL"&4-)"$D`P`\(0SAP`PI[ +M<#*0,9`SF!PP$?`P^P`&,9D`#LD'"]0>J?_W3/@X>'MX!"$;`AA#B$,"(8A# +M$N`8JQAV,I@$(0A#.'`#"GMP.9H"(8A#$7A3>!L"&4.)!\D/20`(0SAP`PK_ +M(?DQ>W`8JXA#&7Z)!LD-"$,X<`,*>W`V2328R6I`&#"0&#`1\/;Z`"@-T3"8 +M(#`1\/#Z^7@[>0`'&P(90PD)"0$`#PA#!N#X>#MY&P(80P`)``$",/AP`PH[ +M<368$?#9^OEX.WD;`AE#"0=VFW*-`0```+PA`````@``ME8S3@D/0!@UF1#P +MY?X?F0$Q'D@?D<=J.!P0,!'PQOH?F8A"`-ERYS68$?"_^C69`3@0\-'^`2`- +M\+;]__?%^#$<`2`0\,C^L'O_]U/X&>`.20`H%M`"*!31(!S^]]W_"DG(:@PP +M$?"A^@$H`M$Y'``@`>`Y'`$@$/"N_C$<`"`0\*K^'9C^]\?[A>0D,P#`?$T` +MP"!Z8WH;`AA#`"@6T?E.!B(A'`HQ,&@0\&O],&@!:""1"ASU28IB@(@@JQB` +M()C(8F%Y\DK_(%!4Q.0!*`;1(AP*,@$A8'D.\,KXN^0"*//1(!P*,`8<"_#6 +M_3`<"_`&_K#D`2"00`IX@D,*<`AX"_#P_<'@8'G_(R8SX4Y80X`9&APA'`@Q +M$/`S_6-X('@!(1L"&$/)`PA#('`#"F-P`""@<>!Q/)C)",-Y@'G_(AL"&$,( +M0SR9`PJ(<<MQ(1PN,B@<!/")^F!Y_R$F,4%#B1G,2H``$5`!\&WY`2#``PA# +M('`#"F-P`""@<>!Q(!PF'`@P$?`1^@$H)-$\F`$GPWF`>3R9&P(80SA#B'$# +M"KY(RW$`:`OP\?I@>0@H!]+_(28QMTI!0XD8"/!S_0/@LTD(7`WPO/ZU20AH +M""(00PA@!"!*X``&#=5@>0@H!=(`(@`A!_`O_6!Y`>"H20A<#?"]_CS@#?#+ +M_@<<J4C0&7>/`0```+@C`````@``0KZ-\P!H`"@0T0`O#M!@>0@H!=(`(@`A +M!_`8_6!Y`>"<20A<#?"F_@$O)-$\F0`@B''(<9U)"2*):)(!B1B(8_[W+/U@ +M>0@H`](`(@`A!_#]_`KP1?N620AH!"(00PA@D$D(:`@BD$,(8``@#?!K_`$@ +M$?#U^8](PWJ!>AL"&4/)",D``C$0(YE#@7(+"L-R#"(Q'`'P/OHA'(=."#$& +M(C`<$/!Q_&-X('@!(1L"&$/)`PA#('`#"F-P`""@<>!Q/)A)",-Y@'D;`AA# +M"$,\F0,*B'%R2,MQ`&@`*`K0,'AS>!L"&$,`*`318'G`&0%X`3$!<+`<$?!; +M^7-X,7AF>1L"&4,!X/+BH>("'#`<"/!%_`XB`?#]^:![_R@'T5M(!B(!:"`< +M#C`0\"[\$>!73@8B(1P.,3!H$/`F_#!H`6@@D0H<4DF*8H"((*L8@""8R&([ +MF0$G2'J@=0,*XW4["B=S(!P<,&-S!_`N^@KPD?P@<CN92'DA'&!R/9B`>P,* +M('5C=4U(&#$0\"C]/IG+>8AY&P(80SA#B'$#"LMQ`?"H^2`<)#`OD"Z0$?`! +M^0<<(!P@,!'P_/@B'"@R.1P'\!/Z+I@1\/3X/4D`)TAT(!P\,"V0%^"Y``@9 +M*#`KD"R1$?#F^#=*+)E04"N8$?#@^"J0+9@1\-SX*IE``0@8+)D#'DUE`0`` +M`+0E`````@``6T93@S%*`3=04"^8$?#2^+A"XM@@'#@P!QP1\,OX`0<B2LD/ +M$6`]F<`&R7O`#TD(20`(0SV9R',ID#@<$?"Z^(`&*9G`#P(CF4-```A#/9G( +M<RB0.!P1\*WX0`8HF<`/$".90P`!"$,]F<AS)Y`X'!'PH/@`!L$/)Y@@(I!# +M20$(0SV9R'-P>[-[&P(80Q_@S!,"P`"E`(!<+`#`Z#X"P.@8`L!T)0#``*,` +M@%A0`L`44`+`)*@`@)!-`,`82P+``1D$`R0S`,"(5`#`F%0`P!!#<',#"K-S +M<N$B>F-Z\D_R2!L"&D,!*O%)&-`"*@O0!"INT`@J`-!3YPAX`BAITZ=R.PKC +M<DSGH'KC>CN9&P(80P`&``[(<0KPY_E!YZ)ZXWH;`AI#ND(1T3N92'H!*.+9 +M/IG+>8AY&P(80Q`C&$.(<0,*RW';20`@"'`IYSZ?UTG[>;IY&P(:0Q`CFD.Z +M<1,*^W$`)P]P.YD)>@(I`=$'<`#@`7"@>N-Z.YD;`AA#``8`#@ARS$D)BQ0I +M#M`3*0S0`R@*T1^I`2`-\./_&*L8?P,H`M`[F0(@"'(ZF!'P`?@[F0EZ`)&R +M>_9[`1PP'#L<#_"0^+I)`2`)>`[P"/CGY@#@`N``>`(HD=(!,*!R`PKC<MWF +MXWJS2:!ZB6@;`AA#_S%A,0B!;.4@>F-Z&P*K3PB>`0```+`G`````@``KZ[M +M/AA#`2B*T:!ZXWH;`AA#"O"_^<;FJ$D`((EH_S%A,8AQ`2`%X*1)`""):/\Q +M83&(<<AQ3^4@>F-Z&P(80P$H$-&@>N-Z&P(80P`&``X-\"O\.I@0\*__LWL" +M'``@\7L*\(+\('IC>AL"&$,"*`G1.I@0\*#_`AP@'+=[\7L0,`#P"/\ZF!#P +MEO\[F0`C"7H`D;)[]GL!'#`<#_`E^('F(!P(,,`A!AP0\!_Z`2$P'`;PF/X@ +M'$0P"_`[^P`F!B!P0P$92C$P'`WPP_T!-A`N]=MVX``A(!P(,`;P0_X&'"`< +MNB$.,!#P`/H`+FG0NR"`73$<6#&@<R`<N2(/,"K@.9I3>!!X&P(80T`'P`\@ +M=#F:DWA0>!L"&$-`!H`/8'0]F,![@`?`#Z!T/9C`>\`'P`_@=`+P+?DA'"`Q +M2'!?2`!H"'$,(B`<%#!=20/@;")=22`<"#`0\!KZ,N`EF!#P*O_`!H</)I@0 +M\"7_0`7"#C`&``X['!ZI"/#*^P$V'9B&0NO3=^,X'!#P%?\`*`30,`8`#@CP +MX/L#X#`&``X(\/3[`38?F(9"[=-DXT=)#VH!-R`<"#`0\/[^&*N8=P`F!N`J +MX3`&``X?J0CP]OL!-KY"]]-/XSQ)#VH!-R`<"#`0\.G^`"@)T0`F!.`P!@`. +M"/`@_`$VOD+XTSSC!R`W5:SA`0```*PI`````@``:+?O8P"K&'`@>YAP8'O8 +M<*![&''A?%@=`"D%T`,Q67`A'!0QXGP&X.%[`*L#,5EPXGLA'!`Q$/"M^0`F +M!>`P!@`.:48(\.?[`3:^0O?3%>,@>F-Z/9D;`AA#B',^Y"!Z8WHA'!L"&$,` +M*`318'D*,0CP]?LRY`$H\-%@>0HQ"/``_"OD_??+_P<<)AP0-B`<"#`0\)/^ +M`08<T0$A,!P&\&3]`"@HT*%]L##!<B3@``#__P``4?$!`%#Q`0!(\0$`%%`" +MP/03`L!N30#`(#0`P,"B`(``!@`.`2@!T0`A`N`#*`31`2$P'`3PR_L$X`(H +M`M$P'`3PB/LX'/WWE?]_YJ!\XWP`)AL"&$,`*!G1*R`!72`<*C`"D2$<%C$` +MD0&08WPG?")Z&P(?0V-Z##E@>1L"&D,['/[W%?L!*!'0`28/X`$H$-%C?")\ +M(7H;`AI#8WH@'!L"&4,*,/[W%?H`*.W0!?#J^0C@`B@&T2`<"C`&\%W]!B@` +MT0$F`"$@'`HP!O#S_"$<+#$0\"[Z`"X`T0OE@>$@>O=)"':"X_5(0&H`*`/0 +M(1PH'/[WL?D@>F-Z`2;V!QL"&$,`*`K0(!P,,!#P_/WC>J%Z&P(90S%#"&#K +MY.-Z(1R@>@PQ&P(80S!#`&@0\`'ZX.3B2$!J`"@#T"$<*!S^]XOYH'OC>QL" +M&$-E0?''`0```*@K`````@``G%]1W@`H$M`!*"[0`B@)T2`<"#`0\-3]`1P@ +M'#H<$#`#\$#^(1PH'/[W&?FMXR![8WL;`AA#0"CTV"`<"#`0\+_]!QP`)@G@ +ML``!&3AH$#$0\,SY!#<!-C8&-@X@>V-[&P(80[!"W=GNYR`<"#`0\*?]!AP@ +M'!`P$/"B_3!@T>>]2$!J`"@#T"$<*!S^]T'Y"O!:^"!Z8WH;`AA#`"@2T"%[ +MH'KC>AL"&$,-\&G\H'KC>AL"&$.$*#[1L$D@>XEHT#$(<3C@H'KC>A^I&P(8 +M0PWP3OT>X*A(0&H`*`/0(1PH'/[W%OD*\"3X('IC>AL"&$,`*`?0(7N@>N-Z +M&P(80PWP,OP8X*!ZXWH?J1L"&$,-\([]&*L8?R!S#>"82`$G!W!]()=)P`#( +M8R!Z""@#T)%)3W$/\/S[P>,@'`@P$/`__9%/`"ALT2$B(1P,,1>H$/`B^!>8 +M`"'`!\`/%I`3J`+``L`7F!VJ`C(`!T$/`"DDDFW1%Z@&\"OZ`"@$T7Y)B&H! +M,(ABB>`8JQA]&ZDCD0`H;]%X:``H!]%\2``A0G@`*@+1@'@`*`+0!2!X21'A +M<4C`>@`H<=$$\%#^`"@%T03PB_]L20$@R')GX`$A(Y@&\,W[:$FP,4AA!QQ> +MT&M(0'P`*`+1`2`%\,3X`"`%\,'X`"$!(`7P2/@`(A>I%IAY`H3_`0```*0M +M`````@``A:>/K@3PWOXDF1#P\?@8JUA]84F`"!606'V`!X`/`R@"T1@@">#K +MX@(H`=$0(`3@`2@!T0@@`.`$(!BK"?%-)B'%-2$!Y`"@$T`4@#_!Q^P7@ +MP.)1204@%9H/\-;Z,!P0\*_\`2@.T!68!B@$T0$@NB'(50/@GN``(;H@P55X +M>P`A"?#%_@`AO2#!51BKV'P`*`_1/$A!8P\A!2``X!?A#/!A_C\A!2`,\&C^ +M/$E!($AA(.`8J]A\.DXP7`$P#/!8_@$<!2`,\$[^&*O8?#!<`3`,\%G^`1P% +M(`SP3_X8J]A\+DDP7(``0AA2:$IA)4E`&$!J2&,E3@`@%ZD)&`E\,50!,`8H +M^-LCF(![`"@%T#J)&*O9?``@"_`V^AI(0&L`*`70&$J`,I!I("$(0P3@%4J` +M,I!I("&(0Y!A`2<W<B.8@'L`*`#0]W$!(`Y)0`.`,4A@_??Q_R.8P7L#?!L" +M&4-('L(7$@T2&!(3$@.`&@`$``P*`0M)%^`D,P#`%%`"P#%0`L!`H@"`*'0" +MP#1H`L!P1`$`4%`"P/]G``"`I@"`'"<`P$"J`(`*8@`!2&+W2$=Q#N(8JQA] +M`2AQT3AK`"@&T?-(@7H`*0+1P'H`*`?0\$D&(`AP`2#O28`"R&)VX.Y(0'L` +M*'+1`2`$\#?]`"@&T0$@!/!Q_NA)`2`19$QE`0```*`O`````@``<4\Q$TAS +M9N`!(2.8!O"S^N1)L#&(80<<7=#?2`!Z`"@!T03PJ_\!(`3PJ/\!(0$@!/`O +M_P$B%ZD6F`3PQ?TDF0_PV/\8JUA]UTF`"!606'V`!X`/`R@!T1@@".`"*`'1 +M$"`$X`$H`=$((`#@!"`8JPA@V'S(2<ASRDA`>0`H!-`&(`_P6?H%X`?BR$D& +M(!6:#_"^^3`<$/"7^P$H#=`5F`8H`]$!(`#@A^``X``@NB'(57A[`2$)\*[] +M`"&](,%5&*O8?``H#]&Y2(%C#R$&(`SP3/T`X.G@/R$&(`SP4?VT24$@B&$@ +MX!BKV'RR3C!<`3`,\$']`1P&(`SP-_T8J]A\,%P!,`SP0OT!'`8@#/`X_1BK +MV'RG23!<@`!"&%)HBF&C24`80&J(8YM.`"`7J0D8"7PR&`$P!BA1<O?;(YB` +M>P`H!=`ZB1BKV7P`(`OP'OF72(!K`"@%T)5*@#*0:4`A"$,$X))*@#*0:4`A +MB$.082.8@'L`*`'0`2`P=`$GBTEX`X`Q=W2(8/WWV?XCF`-\P7L;`AE#2![" +M%Q(-$A@2$Q(#@!H`!``,"@IB``%(8G=(AW$/X7I)`2"(<ABK&'T&*''8 +M?4C!:L(%D4/!8AA]!3!'!W)(?P]8,,!=`"ACT2P@=TEX0T`8@&H`*`'0:D@4 +MX@$A(Y@&\+SY:4H^\`91`0```)PQ`````@``#U26H[D`L#(BD5!0$I``*$[0 +M.AP7J1:8!/"+_229#_#K_D,<`=%>2/SA&*M8?1E]@@C8?,L`61A82\D8B'%< +M23@<#_#G^#`<$/#`^@$H!=`8JQE]$IA`>PGPW_P2F`$AL#!!<X=S$*N9C1BK +M&'U+2L,`&!@14A"KV8V`&$&`&8Z!@".8@'L`*`;0$I@8JP*)V7P`(`OP??@\ +MGO![`"@2T!BKV'P`*`[1/$K!``D8`>".X07BB1@)>@$I`M`!,`@H\]L(*"S1 +M!"\!V``O-=$8J]A\.DXP7`$P#/!0_`$<.!P,\$;\&*O8?#!<`3`,\%'\`1PX +M'`SP1_P8J]A\+DDP7(``0!A`:"*:41@(8-A\*4DP7(``0!A`:B*:41@(8@S@ +M!"$X'`SP)/P/(3@<#/`K_"%*(ID@((D8"&`!+P'0`B\(T1BKV'P$*`32&DHB +MF0`@B1@(8B.8@'L`*`?0&*L8?0Y*`2'#`!@8@!C!<1BK&'T*2@$APP`8&(`8 +M`7(BF0U("!@`:@`H(M`*2H`R4&D!(0D$B$,:X&3A@E`"P#1H`L!P1`$```P` +M@"0S`,!44`+`_V<``$"B`("`I@"`'"<`P$"K`(!`I`"`*',"P%!A`2#E2B*9 +M0`.)&`AC_?>S_0G@0`\!*`;1%ZD6F`3PIOTDF0_P`OXDF!#PZ?G_*$_1(1RR +M=^7Z`0```)@S`````@``^[PH'B@<_?>,_2SA`BA?T0@B(1P,,1VH#_#&_!Z8 +MU$H&!C8.<!WQ`(D90`=`#X\8T$H?D``@B1@*7#I4`"(*5`$P!BCXVP`@"'(? +MF,A)@``''$@Q"%@`*`+0`(D)\.C[`BXFTL)*`"!(,M!1`"X%T;]):#G(>@`H +M$]$&X`$N!-&[26@Y2'L`*`O1'Y@.\/__,!P$\"K\`"X$T;5)`2!H.<AR9>`! +M+F/1L4D!(&@Y2'->X*]*`"!(,M!1K4@?F1`X05P`*531'YD!)T=44.`\X0$H +M$-$*(B$<##$=J`_P9/P=F!Z9P`?`#Q"K"08)#IJ/!/`H_3S@`R@+T0@B(1P, +M,1ZH#_!1_!^8``8`#@3P&_TNX`0H+-$A(B$<##$7J`_P0_P7F``'0`\BT1>H +M!?!8_@`H`-$KY!BK&'T;K@`H+-%X:``H1M&+2$%X`"E"T8!X`"@_T89/:#_X +M>@`H.M$$\(7Z`"@%T03PP/L!(/AR,>!UX`$A,!P&\`+X?4E(,4AA`"@GT$![ +M"?!?^@"(P06)#@,I']/`!V+4'.`8JQA]`2@MT3AK`"@5T7)(@7H`*1'1P'H` +M*`[1;D]H/WA[`"@)T0$@!/!3^@`H!=$!(`3PC?L!('AS/N`!(3`<!?#0_V1) +M2#&(80`H-=!`>PGP+?H`B,$%B0X#*2W3P`<PU"K@&*MV,=T1`0```)0U```` +M`@``XD3V;AA]!B@FV!A]!3!'!UA(?P\0.,!=`"@=T2P@5TEX0T`8@&H`*`+0 +M54@'<-SD`2$P'`7PI?].2KD`2#)04``H"=!`>PGP`?H`B,$%B0X#*0'3P`<$ +MU"$<*!S]]V/\!N`A'"@<_?<9_"&8`"@"T$1)!"#(8C^P\+T@>F-Z&P(80P$H +M8]&@>N-Z&P(80PGPL?A<X"!Z8WH;`AA#``8`#@SP$?TZF!#PE?BW>_%[`AP` +M(#L<"?!G_?_W\_@@>CR9R',L26@Y"'+/X"!Z+DD>*&C0'MP1*%W0$-P`*#_0 +M#B@VT`\H/-`0*#'18'H.*"[2="-80T`8*2$)`87@$RA2T!4H4M`:*%30'2@@ +MT2<@@`%-X",H%=`)W!\H7-`@*&/0(2@+T"(H$M$82$#@)"@+T"4H8M#_*`K1 +M%4@XX!<@P`$UX!)(,#`RX!!(T#`OX'3@8'H"*''27"-80T$80#%4X&!Z`BAI +MTLPC6$-!&/@Q3."`H@"`C#,`P#1H`L`H<P+`<$0!```,`(#$/@#`Q`L``(P- +M``!@>@,H3](P(UA#0!CA23'@#N#A2`'@X$A$,`D8*^!@>@(H0-(D(UA#0!C: +M29`Q(>!@>@(H-](8(UA#0!C623`Y&.!@>@,H+M(4(UA#0!C22=PQ#^#_YV!Z +M`R@DTD`!0!A9(4D!!N!@>@,H'-*C8G,>`0```)`W`````@``%JQ(TUPC6$-` +M&,I)01@`*170H")/X`$G`2$@'`@P!?"R_@8<(]"A>PP@`"D-T'**X7N"0X@' +M``\00W""`.!LX.![`"@$T0`G`N!QB@A#<()PB@`'@`\!*`#1`.``(+9)R'/] +M]R_[<'LY'`GPG/@A'"@<_?<5^_WF`2$@'`@P!?""_@`H2=!`>[@CK$E80T`8 +M`1QH,2`<'B(.,"C@J$@!:$MX"'@;`AA#('(#"F-R"GA+>!L"&D,@'`PP&.!@ +M>2%Z.50JX&=Y)GIC>B`<&P(>0PHP#_"!_P(<,1PX'`?P<OH;X&!YETG[(@D8 +M(!P(,`_P7_H2X`$A(!P(,`7P1/X`*`O0H7L(*0C2`(F/2DD```&`&$!:X',# +M"B-T(1PH'/WWJOJGY@$@P`,(0R!P`PIC<`(@`PJ@<>-Q""(A'"@<`O"K_Y?F +M^+5\3.%L`3'A9``H;=']]RSZ"O">_N!Z`"4`*!/0('M!'"%S`2@.V?WW9?@& +M'"5S`2$`(.5R!?"+^P4@`?!N^3`<_?==^&M,8'L`*!30)ARP>T$<L7,!*`[9 +M_?=,^`0<M7,!(0$@=7,%\'+[!B`!\%7Y(!S]]T3X7D\`)&`W74A8,`!=`"@7 +MT#A=01PY50`H$M#]]S#X!AP@'`[P6OT@'`3PQO@@'`'P.?E22#U56#`%53`< +M_?<D^`$T""RNS4Q,`0```(PY`````@``T;5*CM_;34I13M!\=PU!'-%T"B@B +MV4](`6@`*0K1`"`$\"KX`"@&T4M)!2`(</=B`>![X`5@24@!:``I"=$!(`3P +M&O@`*`710TD&(`AP]V(`X`5@`?!2^3A*U70W2I!]01R1=00H&=D`(#U+`"&, +M`!Q99"P"V0$P``8`#@$Q""GUVP`DH0`!-`@L75#ZVP(H`MD!(-!U`.#5=95U +M)TH0?4$<$74!*!G9`"0#X`4L$-`&+`[0*TF@``I8`"H(T2`<!/`P^0`H!-`C +M2`1P]V(`X`U0`30(+.G;&$H5=1=*4'U!'%%U'B@;V2P@'TE@0T`8@&H`*!+0 +M(!P$\!3Y`"@-T?SWG?\`D"`<`?#5^``H`M`12`1P]V(`F/SWEO\'2E5U!DH0 +M>@`H`=`+\#O[^+T``.@(```("@``U`P``"0S`,!(PP$`T"8`P!`W`L#01P$` +M``P`@$!0`L!P1`$`1%`"P+10`L"44`+`*',"P/BU!?#H_PGP:_H7(4D"2D@/ +M\.SX24]*30`F`2(Y'``@_/>F_P0<^-#@!`+5`"#_]]7^H`<!U?WW=/P@!.W5 +M*&@N8`$'0`#HYQRU`"#\]WK_/$@#\,_Y/$P`*`/006BA8``A`6!](<D`"2(! +MD@`B`)$X20(@-:/\]U7^`"@+T?WW,/DU20$@"'1D(F0AH&@#\-+Y`""@9&W' +M`0```(@[`````@``)5WT,QR]`2#\YX"U`B#\]X#^@+UP1RQ)"&H!.`AB"M$J +M2"M)`(L/*`'1""`"X!,H`=$%($AC<$<C2$!J<$<E2(![<$>`M21(`_",^0`H +M!-`92D%H46``(0%@@+T`(@$<%4B`M4!H`_">^8"]$DB`M4!H`_"@^8"]$+4/ +M3&!H`"@'VP/PF/E@:`/PF?D`(,!#8&`0O8"U`2```_WWS_B`O8"U#O!+_H"] +M2,,!`/__``#L$P+`&3P``/`E`,!#0B!0<F]C`/4Z```D,P#`%%`"P$"F`("P +M30#`)3P``!"UZT@#\)?\ZDP@8@/PO/T@8^=((#`#\([\8&(#\+3]8&/C2&`P +M`_"&_*!BX$A`,`/P@?S@8A"]_K7?20`@`I`)>/\CR3/<2EE###*.&#$<_S&! +M,=E*CF(2>-E,_R"3`!H90#K5:P$P36+23=PU71E`/>UKTFNJ&A($$@P`)8)" +M`M,(8LUB$."`&@ACR4@W'/\WS#`8&+$W"F+/8D`XP&M(8XAJ@!B(8\UC,!S_ +M,,5)H3#(8\-*0#+0:(D-B$/08,!+3#,8:#TA"0((0QA@T&A`!/S4_/='_C<< +M_S?!-P"0O8!6X!0A04.-&?\U0`&$&0$U(!P/\`;]`"A-VF%Y`"`(*0'1`2!@ +M<:Q)"7@$*0#1`3!A>0#PYO\!D``H!-$!\"GX`?!FF'RR`0```(0]`````@`` +M/*4J0RCX-^`T(0&8#O"$_P&8-#`!D"A@(!P/\./\0`!`""$<#_#T^"`<%#`/ +M\-K\FDF;2@EXB0")&$`YR&-C>R![&P(80VA@`9CH8"`<"#`/\,C\J&``("AA +MN(@!*`73%"-80X`9!#7`,,5CN(@!,+B`N(@(**73`)C\]^G]N(@`*`[0`2`" +MD(-)`""(8(9)@TH(8(%(`'B``(`80#C`:SA@`IC^O7Q(_R,`>,DS>DE80PPQ +M0!C_,'I)!3#(8WE*0#+0:(D-B$/08'9*3#(0:#TA"0((0Q!@<$=R24`QR&A` +M!/S4<$=L2'"U`'C_(\DS:4E80PPQ01@+'/\SP3,8'$`X'&A":@`EE$(%V:(: +M`F)":D%B@F(3X$%B8$R"8B1X7$X::,PVI`"D&4`\Y&NB0@;9$AL"8T)KA&N" +M8T1C`.#%8O\Q5TBA,<%C5DI`,M!HD0V(0]!@4TQ,-"!H/2$)`@A#(�:$`$ +M_-1029B("FB`&`A@<+WXM1<<'AP-'`0<!#`/\"_\PWB`>!L"&$,!!XH/`-$! +M)00M!MB)#P(I$]$`!@`/""@/T0`N`="X-C9H(!P@,$-X`'@Z'!L"&$,!!PD/ +M(!P*\/GY,QPZ'"D<(!P+\$_Y^+VPM35,`"4@>``H!]`-\)+^(7@!*0'1,4D( +M8"5P,4P@>``H!]`.\!?Y(7@%*WC``0```(`_`````@``R$V4_@$I`=$M20A@ +M)7"PO?"U#QP4'`0PA[`&D`_PY/LH204<R7N)!@;5@7C#>!L"&4,)!XD/<=`` +M+&_0NR$)72%*$7`(*6G2`:-;7%L`GT0#965E/64]/28<0#8PBX$'7-7`!(`. +M(1P#\&3Z`"A5T`:8#_"Y^T-X`'@QBQL"&$/*!`@X``22#@`,203+#P"2P>`` +M`#`F`,"\4P#`/%``P(@/`,!D#P#``.@`@!@T`,"$#P#`I3X`P*P^`,"D/@#` +MJ#X`P+!-`,`=)@#`@7D_!C\.S@<A'%`Q]@\`+@+1BGH!*@30`"Y=T,IZ`2I: +MT0`N!=`,,`$A!?!.^@0<#M`B'`$A,!P#\$SZ`"@'T``N%-"T(`%=`2`#D`21 +M`.`SX`$A`I$!EREX:WBL(!L"&4,4.0"1`UNH(`)9#^``(0$@`Y`$D0*1`9<I +M>&MXH"`;`AE#%#D`D0-;G"`"62D<(#$H'`WPK_\"(/-)`"X(<`W0(!R@,(&) +M`3$)!`D,@8$#T8`TH&H!,*!B![#PO2`<H#`!B`$Q"00)#`&`]=&`-.!I`3#@ +M8?#GB'H"*`+0R'H"*!O1(1PP'`/PV_D$'.30!I@/\!/[0W@`>`$B&P(80Q`X +M`00K'`&2(APH,PD,*!P`EPWPF?P"(-1)'N``+LS0`"C*T2$<`_"#^0`HQ=`& +MF`_P]/IR6`9>`0```'Q!`````@``+1K)^4-X`'@`(AL"&$,(.``$``R`-`"2 +MXWHI'"0Q`AP('PWP@?[$20(@"'"MY_BU#AP$'!<<_/<*_`4<__?/_@`L!-`Z +M'#$<(!S_]^/^*!S\]P+\^+WXM04<!#`/\,;Z@7C#>"P<+#0;`AE#"0>)#P(I +MLTH%T8!YP`<"U2$<$!P!X"$<$!T.\,?^KD@!>``I!M"M2QEH`2(10QE@`"$! +M<"`<#_"C^@$P0W@&>&`G&P(>0[Y#!G`S"D-PI$B`>P,H"=$@'`_PDOJ^0S$< +M(#%!<`L*@W`(X`(H!M$@'`_PAOJ^0T9P,PJ#<"@<'#`$'`_P??H!(0D#B$,A +M'`[PC?[XO9-)2&!P1Y)(0&AP1_"UF[``(`B0C4B-21`P&I`",!F0"C@6D`0X +M0#$8D160`",$DQPP$#$4D"`X$Y`7D0$B`R"$2?SWT_L$'(-(`(L4*`O1&)E( +M>0(H!]&`2E!H0`0#U%!HT0$(0U!@(!P$\.?[`"@"T`#P=/WAYR`<`/"@_AJ8 +M<DK#>8!Y$#H;`AA#P`?5U1.8@WM`>QL"&$.`!L[5<$D!(`AW;TD$(PMP`"`' +MD/_WJO\`*!S0!/"?_0`H9-#_]T'\`"@4T1F8#_`0^AB9`"0)>B,<`)$3F8I[ +MS7L!'"@<#?"=^A.8@'O\]Z3_6$A$8`B8`2@>T5Q(`6E`:8%"`=`RO9E:`0`` +M`'A#`````@``V?)W1`#P+?T`\"K]__=K_0>8`"@%T`>8!ID%F@2;__>P_?_W +M9OW_]WW\`2@"T?_W0/T6X``@$^#_]W3\`"AQT/_W-_W_]T_]0DD!(@AX4$`( +M</_W9_P!*`/1__<J_0$@")`\20$B"'C_(\DS4$`(<%A###%`&`*0`"`'D`.0 +M`I@`(_\PP3`2D`23$.,UXP.8%","FEA#@!C_,`$P`&@#F0`E20&.&#))"7@` +M*0'0<7D#X``AL7'Q<0$A!!PT/`F1(1T1D0[PJOTD2(!H_S!A,(!Z`3`@<``@ +M8'"@<.!P"9D$*0K8$9@/\(/Y!B$)6"%*D4("T4")_RA`T/-YL'D;`AA#((0) +MF00I`]D)F8@`&DD-6!\@@%V!!P'5`2$)D<`'+]4`X.'BL(L)21`Y"("PBXB` +ML(L(@;"+B(&.X*0^`,"E/@#`2$T`P"HS`,"4H0"`8$T`P#Q0`,#_?P``%%`" +MP(```("\4P#`B`\`P`"@`(`G,P#`__\``-0S`,``(""$Q^<)F04I"M(1F`_P +M,?G#>(!X&P(80P`'@`\"*`;1$9@/\";Y!QR`><`'4=4@'/_W5/X1F`_P'/F! +M>,-X&P(90PH'D@]#T0D&"0\%*3_1%YD'',E[B0<9U.I)"6@`*0/0.1P@,0`@ +M!N`!'!(Q`"`*\"O^.1P@,07P`?L!D!&8#_#3(;L>`0```'1%`````@``P`JI +M-/?X`9D!<`L*0W`'X`%X0W@;`AE###DL,`;P@/T"(-I)('`(:$EH.AP@,@,$ +M&PX0<%-P`P(;#I-P``[0<`@$``X1<5!Q"`(`#I!Q"`[0<9?A$Y@/\,WX`2AF +MT2$<+#$0D<I(#O#;_``G,!P8,`^0#_"_^`$H`]`"*`'0""ABT0`@`*L8<3!Y +M`:G\]];]OTFX`$$8#I%+>`AX_R$;`AA#^3&(0P"K&7F)!LD-"$-`"$``!"&( +M0PZ9`PH(<$MP"/"`^@Z9&"*+>$EX@`<;`AE#D4/`#@A##ID#"DAPBW`/F`_P +MA_@!*`31`*L8>0$H"-`#X`"K&'D$*`/0`*L8>?\P&'$!-S\&/PX$+\#3#Y@/ +M\'#X`2@*T9U)_R((>TM[^3(;`AA#D$,(,`G@P."82?\B"'M+>_DR&P(80Y!# +M*#`(<P,*2W-TX`0H<M$Q>0`M`9$3T1&8#_!-^`8P`"$$\"#_!1P*T1280W@` +M>!L"&$-!!\D/@`?`#P"1"N!H>P3PN_\!!@D.`)%H>P3PR/\`!@`.`)G`!\D' +M20\-D8$/#)%[2;@`01@+D0AX2W@!(1L"&$,(0P(AB$,,F0A#_R'Y,8A#`9F) +M!LD-"$,$(8A##9D(0PN9`PH(<$MP"/#S^0N9&"*+>$EX@`<;`AE#D4/`#@A# +M"YD#"DAPBW`!F0`I!]`F8H6>`0```'!'`````@``-.(7B0&9("D$T`&9_S$) +M!@D.`9$!-S\&/PX$+\+374K_(5-[$'OY,1L"&$.(0Q!S`PI3<P#@_^<5F`[P +MW?\`*!+0,'MS>U1)&P(80XA""]E03Q:8#O#0__E[.WP;`AE#"0<)#T`:`N`6 +MF`[PQ?\!,"!P(!P<,`<<#O"^_P$A"0.(0SD<#O#.^T1)"&@!(A!#"&`0F`[P +ML/\!>#L=&7)!>%ER"9D$*6_2.D\X>#!W>'AP=VG@`"T2T;@=`"$$\'/^!1P, +MT0`A,B`!52$<"B`@,8AR`PK+<B`<`/"B^N;@:'NX(2]/04/)&;`QB7C_*0?1 +M(1P@,4MX"7@;`AE#"08"U0CPMO@!X`CPI_@A'"PQ#O")^R1(`'@`*![0:'NX +M(UA#P!FP,(!X_R@6T#!Y`"@3T"`<+#`.\&#_`8C)!8D.#"D*V4-X`7C_(AL" +M&4/Y,I%#8#$!<`L*0W`@'"PP#O!,_P%X(QP@,QER07A9<A68#O!#_P`H&-`@ +M'!PP!QP.\#S_`2$)`XA#%^`9X```6%`"P`"F`(!030#`_P\``)2A`(!(PP$` +M.S,`P"`<'#`''`[P(_\!(0D#"$,Y'`[P,_LC'"`S&'H?'#!W6'IP=SAZ>WH; +M`AA#P`<*U'MX.'A@(1L"&$.(0P`A"$,X<`,*>W`P'`[P`O\!(A!#,1P.\!/[ +M`"UL.Y/1`0```&Q)`````@``\_L5U`C1$9@.\/C^!C`!(03PR_T%'!K0+HEH +MB@`'@`\!*"#1.'I[>AL"&$/!!QK5P`6`#@<H%MD@'!PP"I`.\-W^("$(0PJ9 +M#O#N^@O@`"81F`[PT_Z!><D'!-4,,`$A!/"C_04<$9@.\,C^`WG`>!L"&$-` +M!@G5.'A[>"H<&P(80P$'"0\@'/_WV?O%28AL`3"(9`>8`"@%T`>8!ID%F@2; +M__=T^@F9!Y0%E@25!I$#F`$P``0`#`.0$IB`B`.9B$(`V>GD^_?*_P0<__>/ +M^B`<^_?)_PB8`"@`T)#D!Y@`*`70!Y@&F06:!)O_]T_Z__<%^H3DK$D(>/\P +M``8`#@AP`-!9Y``DJ$D`(`QW!/#'_QOD?+4#(/OWVO^D2`+PAOVB32AB`O"K +M_BACH$@@,`+P??UH8@+PH_YH8YQ(8#`"\'7]J&*:2$`P`O!P_>AB`R#\]Q7X +M?2')``4B`9(`(@"1EDD#().C^_>B_@`H`=`!('R]!_#:_XQ.`"0X-BAJ`O"? +M_:$``305+'!0]]L%X&AJ`O"6_:$`<%`!-"4L]]L`)*``,%@"\.S]`30E+/C; +M`"#>YQ"U?4P@:@+P8?XA:XA"!]%@:@+P6_YA:XA"`=$!(!"]`"`0O71(@+4` +M:@+P3_Z`O8"U`R#[]Z'^@+V`M0`H!=`!'`,@^_=U_P`@@+T/>.QV`0```&A+ +M`````@``!Q.K:0$@@+WXM08<;4@/'`!X-#<-'``D`"@3T`PB,1QI2`'P7_X! +M\'[^9T@`(08P!/"]_``H!=``B0GPL/P`*`#0`20A'#@<`/#*^`0<`"A>20?1 +MB&@!,(A@4TE(;`$P2&0/X%I*`"`08(A@64D(8#0A(!P.\%[X-#0J'#$<(!P! +M\##^(!SXO?BU!1S[]^G^!AP`)``G1DF@`#@Q"%@!(8D'!2+2`T$8D4(/V`$< +M(#&*>LMZ&P(:0ZI"!]$R(A=4""**<A,*RW("\%W]`30E+.';,!S[]\K^^+WX +MM0$F-$^V!P4<`"0X-Z``.%@%(M(#@1F10@_8`1P@,<MZB7H;`AE#J4('T1PP +M#O!Z_8`%@`\!T`$@^+T!-"4LY=L`(/GG^+7[]Y[^!AP`)0`G($FH`#@Q#%@! +M((`'!2')`R`8B$(KV"`=#O!<_0-YP'@;`AA#@0<BU<`'(-4A'"`QB'K+>AL" +M&$,%*`'0!B@6T2!X_B@!T/\H$=$@'#`P@G@!,A(&$@Z"<`(J"-D((HIR$PK+ +M<B=PAW`@'`+P]OP!-24MQ=LP'/OW8_[XO2`T`,"(#P#`O%,`P#`F`,!-04,@ +M5'@``(5"``!T,P#`0!H`!#Q0`,``-`#`�`P("U_O>/_X"]L+4$'``H$TT, +MT2P@$DI(0X`8@&H`*`70!2D#T`8I`=!->.N;`0```&1-`````@``'NMU&2AH +M#N`-2`!H`O"$_``H"M$H:`+P?_P`*`71`BP#T0A(`&@"\'?\L+V`M0+PK/R` +MO7!'<$?@4P#`*',"P-Q3`,#H4P#`H"%+`%D8`"!)"`#@`3"!0OS8<$=!!DD/ +M@+4%T`$<`R#[]T'^`""`O0$@@+UP1_"US$J%L!%H`Y$0:5=I`"3*2\I-`"$! +MKLE-,54I50$T""SXT^O@.`8#F0`."$,-(0D"`"4(0P!H`"AIT,%)"$`)!T08 +M(!PD,`20#O"@_`8&-@XP!S?4(!P@,,-Z@'H;`AA#!2@*T`8H"-`L(;-*04.) +M&(EJ`"D$T'$'`M6R2H$`55@`+1+0!2@!T`8H&]%H>[@CK4E80T$8#1P,-2@< +M#O!V_`$P*1P.\(CX#.`@'0[P;OP&,``A!/!!^P`H`]!!>R`<!_"M_:!XXW@; +M`AA#0`0?T9Y)('@):(EZB$(+T@$Y@4($T9M)"&@!,`A@`^"824AH`3!(8"`< +M'#`.\$C\@`6`#P$H$M&224AM`3!(90W@<.`P!P/4CDF(:@$PB&*,28AH`3"( +M8$AI`3!(88E))1P(;"`U`3`(9*AZZWH;`AA#`1P(.00I!-*#20AH`3`(8%#@ +M!2@#T`8H`=`,*`?1"""H<@,*`"`R(>MR"%4_X"PC<DI80X`8@&H`*#'0,`<> +MU0`FZWJH>@`A&P)T[H>*`0```&!/`````@``Z@/+I!A#`:H15*IZZWH!J!L" +M&D,1&"`<`_#._>MZJ'H!J1L"&$,(7``H$=#_]P[_`38(+N+3"^!P!PG4`"`R +M(0A5"B"H<@,*ZW(@'/_W\/X`(`29#?#N_PG@`"`R(0A5"B"H<@,*ZW(@'/_W +MX?XX'4]*QP40:?\-N$(`T!#G3$I7851(`'@`*"70^_?N_`4<""1)3@`G`:@` +M&1`XP'L!*`O1A2```4Q)8$-`&(4A"0%`&@\A`_#.^0C@,!D0.,%[`2D#T<=S +M8!X#\$/[`3SBT2@<^_?/_`6P\+WXM00<P`8^30?5.4F(:P$PB&/_]\7^.T@H +M86`&`M4!(,!#*&&@!E75.$D(:3)*4&`(:39)B$)-T`4D-4XV3P7@,'@`*`W0 +M`2#[]R/\.&B`!P?4(!S_,``&``XA'`0<`"GNT0GP?/DH3`(@@#P@8!E(0#!! +M:`%@P6B!8$%I`6'!:8%A06H!8L%J@6)!:P%CP6N!8Q!(`6E!8?_W)?T?2?\@ +M"'``(`U*`"$L(T-#`3``!IL8``X(*)EB]M,A(,`"(&("(,!#*&$!(!5)@`+( +M8@$@__<F_?B]`*``@"AS`L!4%0+`_/__$-0S`,!(PP$`S!,"P"`T`,`H9`'` +M+C,`P+R``0``I0"`_W___X"H`(!"P`(`V%,`P#Q-`,!P1`$```P`@/BU.$C2 +M9;,4`0```%Q1`````@``E!AL%``D!AP@-@<<0#<"(/OWKONP>?-Y&P(80\`' +M]M4`+`K1N'K[>AL"&$.!!L`&P`_)#P@8`-`!)"M-*'A!'"EP"B@0V:AH`1PI +M.2<I`=(#(`3@42@!V00@`.`"(`[P/?P`(*A@*'#H:$$<Z6`"*`?9:&A!'&E@ +M!"@"V0`@#O`M_!I(`'@`*`K0&4@!(4%A@6(`(<%B_R`528DP0#$(8!1)"&@! +M,`A@$TD>((ABKN>,M2,A"0$>(@&2`"(`D1%)`2`.H_OW_/H`*`/1!DG(8`AA +MC+T!(/SG@+4!(/OW+_N`O0``4$T`P`0T`,`<)@#``*8`@$SQ`0!`I`"`261L +M90````!940``\+7M2`0A`6'L2>U/06%+(4D!"QS_.X%A.3O#80(A`6(`(0$B +M$@-'8H%BPF+C2TLFY$UV`0%CS``]4>098V";&0$Q`2GWV^!,R0!\4,D92V#9 +M208C0#&+8=Q+I`W+80QBVTP!(DQB"B2,8M5,$@,0-,QB2F,`)-).#&,`(M5- +M$#:,8]0`-5&D&6-@`21D`AL9`3()*O7;T$S2`+10DAE38`4B0F/-2@,C&P*" +M8\-CS$OD#`M@`B-+8,%+P4T0.XM@`"/+8`QA2V$`(<9,$#W+`.Q06QE:8`,C +M&P+2&`$Q`2GUV[E+R0!K4$D92F"R2@,A@#K19KQ)L$N6:O1<`0```%A3```` +M`@``8/#2J1%G@#O4(EIG`2)2`YIG:2+:9ZU,`2,`(F`T&P-"8(-@!&#"8+-+ +M`"#"`*-0$AE18-0Q`3!H*/?;KTK``")0HDI@,H`806"M2``B`F"K24`QS6$" +M(XMAJ4K##$`Z4V)/80(D#&$38DYB"B0,8I-BEDE@,4%C:2$!8Q-AH4D`((A@ +MGDEC(,`Y"&*=2<`'0#D(88AA2&$(8/"]-N?_M0P<'AQ+>`EXA;`;`AE#`9'A +M>"-Y`"4;`AE#2086U0`N%-"[(8E=D$H''!%P(AP@,@.2!#(@-P@I!))ST@&C +M6UQ;`)]$!@-O;T-O0T,`(`FP\+TP'$`P`I``BX`'8M4#F()*P7B-"0%X$7!! +M>%%P@'B0</[W=OTQ'"@<`O`?^``H:=#@>"-Y&P(80T`'`R#[]U'[`I@`E`"+ +M!)E`!,,/`9@D.`($$@PX'`SP:OT%'`,@^_=O^P`M!=$@>&-X&P(80P@X=>#[ +M]X7Z!"4_X*!YQ0?M#_[W1OTP'%`P`"T#T8%Z`2D_T0+@P7H!*3O1,AP`(2@< +M`O`9^``H+=`#(/OW&OL!FCD<*#H@'`>;#/!L_@4<`R#[]S[[`"T3T>!X(WD; +M`AA#`.!)X$`'!=0@>&-X&P(80Q0X/.`@>&-X&P(80PPX-N#[]T;Z*0>)#P70 +M_R%(2@$QT6(#X&/@J0<`U0(E^_=!1=Y-`0```%15`````@``>0@,V3WZ*N"! +M>@(I`M#`>@(H)M$Q'"@<`?"__P4<4=`#(/OWV?H'FBL<`)(!FCD<,#H@'`SP +ME?L%'`,@^_?[^@`M`=!K'`G1('AC>``E&P(80Q`X('`#"F-P`.`0)2@<1^<` +M+2[0`"@LT0.8*$K!>(T)`7@1<$%X47"`>)!P_O?"_#$<*!P!\$__`"@:T.!X +M(WD;`AA#0`<#(/OWG?H`E(`V\WH!F`29)#@"!!(,.!P,\+C\!1P#(/OWO?H` +M+0#04N=+YRC@``!D$0+`<&\!P#P-`L``E@"`@((`@/B``<`4!````"``@$!! +M`(!(9`'`*`@````P`(`,EP'`0`T`@````H#`HP"`P*0`@!TF`,!\-@#```P` +M@`$@ZN;PM96P`*L`(1ER)"/W25A#1!C@:0`%P`V@86%IP``/&'YH`2`.\-#Y +M\4D(:`$P"&"P>/-X[TD;`AA#B$($T.Y)2&D!,$AA?N``(+!P\'`P'"`P!Y`Q +MC`$@0`,@.0B1@4)QV`B8`"ANT`>8!9##>(!X&P(80P$'B0\!*1K1``8`#PHH +M`]$%F`3P8OA<X`DH$-':2`!X`"A6T``A!Y@"\$S]UTD(:4EIB$)-T!`@__<= +M^DG@")@'F0B`H&@-\(/Z!/!Q^04</]`%F,-X@'@;`AA#`0>)#P(I"M$`!@`/ +M""@&T7-[,'NZBQ(D`0```%!7`````@``C>"R9!L"&$,H=`,*:W0H?&M\&P(8 +M0T`'0`\$D,%(P'N`!A+4!9@!(1(P`_#,_@.0*!P(,`WP\/\"J@"2!)H'F0.; +M__<P_@`H#=$H'`@P#?#C_P>9")H!\!GXL'D'\#SYL4D(7`#@D^#H<3%YR0<# +MU`(H`=$8(.AQ\'EH</-[L'L;`AA#J'$'F4MX"'@;`AA#*'$#"FMQ@"`H<`$@ +MJ'`*\,[_Z'!P>BAWL7TH'!0P$Y`4D0WPL?\`"A29``((0Q.9#?#`^W%]*!P5 +M,!&0$I$-\*/_``H2F0`""$,1F0WPLOLQ?2@<%C`/D!"1#?"5_P`*$)D``@A# +M#YD-\*3[L7XH'!@P#9`.D0WPA_\`"@Z9``((0PV9#?"6^W%^*!P9,`N0#)$- +M\'G_``H,F0`""$,+F0WPB/LQ?B@<&C`)D`J1#?!K_P`*"ID``@A#"9D-\'K[ +M*!P$\,[X,'ET20:0<4@@.,-Z@'H;`AA#@`8$U0680'P*>)!""-!N2`!H`"@( +MT`68P'H)>(A"`]$P'`B9!_#;^`$F-@,`(:!I]00!(SIH&P4:0"-I`3N#0@K1 +MX&@``2A#.&#C:3`<F$/@86=I`"`*X*-H`3`;`2M#.V#C:0@SVP3;#.-A"#<` +M*@/1(FD!,8I"V])42(!H_S!A,(%Y`"D*T$])"6@`*0+1!IG)!P'5`2&&EYE^ +M`0```$Q9`````@``2OFP.0#@`"'!<0$@%;#PO?"U/TR1L+0TX6D!(`D%R0VA +M86)IR0!5&$1*BQA?:+YX^W@;`AY#.$N>0FC1`""X</AP.!P@,`.0.(P#F2`X +M`I``!``,$)`(@`.8`9`$\#3X!AP`T23A,!P(,`WPU_X#F0*:`/`-_[AY!_`P +M^"M)"%SP<?AY<'#[>[A[&P(80[!Q$)@P<0,*<W&`(#!P`2"P<`KPS_[P<'AZ +M,'>Y?3`<%#`.D`^1#?"R_@`*#YD``@A##ID-\,'Z>7TP'!4P#)`-D0WPI/X` +M"@V9``((0PR9#?"S^CE],!P6,`J0"Y$-\);^``H+F0`""$,*F0WPI?JY?C`< +M&#`(D`F1#?"(_AC@^>#D$`+`##0`P.^^```P9`'`+C,`P`"@`("P30#`L"8` +MP$GQ`0!T)0#`%%`"P"P-`L``"@F9``((0PB9#?!]^GE^,!P9,`:0!Y$-\&#^ +M``H'F0`""$,&F0WP;_HY?C`<&C`$D`61#?!2_@`*!9D``@A#!)D-\&'Z,!P# +M\+7_\$XP:`$P,&#O3K!Z\WH;`AA#@`8-U0&8[$E`?`EXB$('T.M(`&@`*`/1 +M.!P"F0;PQO^P>O-Z&P(80X`&1M7A3AX@,6@-\-CY`2D\T0.8(#!">P$<##%7 +M&/%H`"D*T0HP#?`6_D`&!=1X>`4H*='X>2PH)M'L[<#Z`0```$A;`````@`` +MOA$.A-=(`'@`*"31UDD)($AC`B$`(`GPU?\/(0`@"?#<_P(A`2`)\,W_#R$! +M(`GPU/\"(0,@"?#%_P\A`R`)\,S_R4D6($`Q"&!(8,A@`2``X``@L&`P:`$P +M,&`!(A(#`"&@:=<$`28K:#8%,T`F:0$^AD(*T>!H``$X0RA@Y6D0'*A#X&%E +M:0`@"N"F:`$P-@$^0RY@YFD(-0@V]@3V#.9A`"L#T2-I`3&+0MO2`2`1L/"] +M`"(!)Z%I_P<!)BMH-@4S0"9I`3Z.0@O1X6@)`3E#*6`!(>5I"0.I0^%A96D` +M(0K@IF@!,38!/D,N8.9I"#4(-O8$]@SF80`KV-$C:0$RDT+:TM/GF$M34-+G +M\+5!:8)I`2?4``D9/P,`(_X$`24,:"T%+$`%:0$]E4(*T<)H$@$R0PI@PFDY +M')%#P6%!:0`B"N"%:`$R+0$U0PU@Q6D(->T$[0S%80@Q`"P#T01I`3.<0MO2 +M\+WXM8!.`"0`)Z``A1FH:@`H`M`!\!/]KV(!-!`L]-,W<'>$^+WPM<%I`"8) +M!<H-@F%!:=,`R1@DX`MHG`(#U=L'`=4!)BO@`VD!.Y-"#-'":`$DY`<2`2)# +M"F#":>$,D4/!84%I`"(,X(-H`23D!QL!(T,+8,-I`3((,]L$VPS#80@QQ&D# +M:B4%'P4_#2T-O4+2T0$E+0/J5/,=`0```$1=`````@``I^G0]"Q`*T"<0LS0 +M@F$P'/"]\+6OL`"K`"$9=P`B!)(D(D)#5$@2&"Z23$I`.A,<0#-@,H`P*Y`L +MDBV3`"0`(`B0`"$*E`"K&7<%D`.0+I@.D/_WGO\`*`+1!)@OL/"]#IB`:0Z9 +MP`!):0@816@!(`V5#?`__D%)"&@!,`A@#9T_2:AXZW@;`AA#B$)CT0`@J'#H +M<`V?`2`@-SJ(0`,@.@R2@D)7V`R:`"I4T`R:/AP0!``,*I`X@/AX.WD!(1L" +M&$/`!S@<##`#\(/[`Y#P>#-Y&P(80T$&6-4#F2E*L#')>A%P!"D&T`<I4M$# +MF5`QB7H!*4W10`<%U#!^<WX;`AA#``=%T#!^<WX;`AA#`0<)#P*1`0D<2`&1 +M`&@!\.7[!I``*'+0#)H&F`V9(#(,\&7_`ID`*6K1#DF(:@`H`=#_]Q7_"TD& +MF``BB&(JF$B``9A(A`IP;>`P9`'`D$T`P$GQ`0!T)0#`_#,`P$"F`(``,`"` +ME!("P.00`L`,-`#`[[X``!TF`,#<4P#`[$D!(`AP#IB`:`SPJ_X#\)G]!!Q( +MT+!X\W@;`AA#``8`#P@H"=%K>RA[&P(80R!T`PI`!T`/8W0*D`28`3`$D/!X +M,WD;`AA#0`9DU0$@")`@'`@P&Y`-\!_\!ZH`D@J:.1P#F__W7_H`*''0`B@7 +MT1N8#?`WPXN<`0```$!?`````@``4P%N21'\("(Y'`#P1_P!X(;@$N`@("!Q +M`PIC<8`@('""(*!P`"`@=&!T(!P#\&G]Q4F(;@$PB&9TX,1*4(P!F8A")=$0 +M>`*9`3"(0B#1`ID&F(D`B1B(8@*9$7`"F2J820")&$B`\'@S>1L"&$-`!UC4 +MMT@`>``H#M!K>RA[&P(80P,*('1C=$$'20\#F@7@!I@!\(S[0^`#F@`A,!P# +M\/O[`"#-X-S@`_`4_00<-]`)F*9)@`!`&"F0A6H)F"\<0`!`&$"((#<^'"B$ +MN'C[>!L"&$,!!XD/`BD,T0`&`."WX``/""@&T6M[*'L;`AA#('0#"F-T('QC +M?!L"&$-`!T`/"I`@'`@P*)`-\(W[!ZH`D@J:.1P#F__WS?D`*`+0__<G_I+@ +M!)@!,`20*)@-\'O[("(Y'`#PL?NH>0;PU/R$20A<X'$I><D'`]0"*`'1&"#@ +M<>AY8'#K>ZA[&P(80Z!Q>W@X>!L"&$,@<0,*8W&`("!P`2"@<`KP:?O@<&AZ +M('>I?2`<%#`FD">1#?!,^P`*)YD``@A#)ID,\%O_:7T@'!4P))`ED0WP/OL` +M"B69``((0R29#/!-_RE](!P6,"*0(Y$-\##[``HCF0`""$,BF0SP/_^I?B`< +M&#`@D"&1#?`B^P`*(9D``@A#()D,\#'_:7X@'!DP'I`?D0WP%/M\`J@;`0`` +M`#QA`````@``6]Z?E``*'YD``@A#'ID,\"/_*7X@'!HP')`=D0WP!OL`"AV9 +M``((0QR9#/`5_R`<`_!I_"F8@&H!\+OZ*9@`(8%B"9@!,`*9"9"(0@#8+><! +M(`60,>![>#AX&P(80R!Q`PIC<0[@>W@X>!L"&$,@<0,*(!P(,&-Q#?#8^CD< +M#)H`\`[[*'D+D"V8PWJ`>AL"&$.`!@35,4EP?`EXB$((T"](`&@`*`C0+$GP +M>@EXB$(#T2@<#)D&\%#\#IC_]RW]!9@!*&C0+)C`>\`&*]0#F``H!]$!(3`< +M##`#\'[Y`Y``*!O0`YBP,$!Z`"@6T+!X\W@;`AA#``8`#P0H`]`,*`'0""@/ +MT6M[*'L;`AA#`PH@=&-T00=)#P.:`>`#F@`A,!P#\+OZ#TB`:/\P83"!>0`I +M&]`*20EH`"D"T0N9R0<2U0$A$>```!TF`,`@-`#`E!("P"<S`,"P)@#`2?$! +M`'0E`,`44`+``"'!<2AYP`83U2@<$#`"D`WP6/I`!D`.@##@<1J0`I@-\%#Z +M``81U1J80"$(0PS@C."H>0;PIOOU20A<X'$I><D'`]0"*`'1&"#@<>AY8'#K +M>ZA[&P(80Z!Q@"`@<`$@H'`*\$+ZX'!K>"AX&P(80Z!T`PKC=&AZ('>I?2`< +M%#`8D!F1#?`>^@`*&9D``@A#&)D,\"W^:7V#&M6U`0```#AC`````@``KS8A +M*2`<%3`6D!>1#?`0^@`*%YD``@A#%ID,\!_^*7T@'!8P%)`5D0WP`OH`"A69 +M``((0Q29#/`1_JE^(!P8,!*0$Y$-\/3Y``H3F0`""$,2F0SP`_YI?B`<&3`0 +MD!&1#?#F^0`*$9D``@A#$)D,\/7]*7X@'!HP!1P/D0WPV/D`"@^9``((0RD< +M#/#G_0#P*OH+\&#\!1P(F``H!]`@'`@P#?#%^2`B.1P`\/OY`"T"T2`<`_`I +M^RN8L4F`:`AAPN3XM:](!!S`-`4<@#4!(@(@K4GZ]RS[!ASXT'`'2M6G2(`P +M`&D`#"AC`"<2X*=(`'@`*`K0ID@!(4%A@6(`(<%B_R&B2(DQ0#`!8`0@__<3 +M^0$WZ6HH:PH%`P4;#1(-FD+DT0$B$@,10!!`@4+>T)-)Z&H(8I=)"&C`&0A@ +M`"\)T``@`_#A^I-(`"&32@%@4&@!,%!@DDB23SAAD4E`.4AHP`4'U8U*$&D! +M,!!ACDA(8(Y(.&'P!SG5@4E`,0AK``SH8`,@__=*_'U*J6@188!*$6@)&!%@ +M`"@"T``@`_"T^H`@?T_`0SAA=4FH:$`Q"6L)#`H%`P4;#1(-FD($T?H,$4`0 +M0(%"`]$!(0(@^O>D^G1)0#E(:``'"-5O2A!I`3`080@@P$-(8'!(.&&P!SG5 +M8TB`,(!I``Q@80`G!.`,\UI``0```#1E`````@``ML[_60CP4_K_]P[Z`3<A +M:6!I"@4#!1L-$@V:0O+1`2(2`Q%`$$"!0NS0`"\"T``@`_!M^E-)(&E(8E=) +M"&C`&0A@5DD`(`A@6DA73SAA5DE`.4AHP`8(U5%*$&D!,!!A$"#`0TA@4D@X +M83`&2-5%28`Q"&H`#*!C`"<#X`8@__==^`$W86N@:PH%`P4;#1(-FD+ST0$B +M$@,10!!`@4+MT#A)8&N(8@`O!=``(`/P+_HZ20`@"&!`2#M/.&$Q26!K@#$) +M:@D,"@4#!1L-$@V:0@31^@P10!!`@4(#T8`A`B#Z]QWZ,$E`.4AH``8(U2Q* +M$&D!,!!A@"#`0TA@+$@X8?`&"M4M200@"&`!\`/Y_O</_@@@)$_`0SAA\`4= +MU2))0#E/:#@'`]4!(0(@^O?W^?@&`]4"(0(@^O?Q^?@%`]4$(0(@^O?K^3@& +M`]6`(0(@^O?E^19(%$\X8;`&`-2XYOKWD/L`*/K0%$HD,A%H@"`!0Q%@$4I1 +M:H%#46*IY@``L"8`P("C`(#D$`+`_P$``!PF`,``I@"`^#,`P!`T`,`P9`'` +M__?__P"E`(#__O___]________O___]_`*@`@(RU`B#Z]Z#Y_O>R_7TAR0`" +M(@&2`"(`D1=)!"`4H_KW@_@`*`#1C+T!(/SG@+4$(/KWN?B`O8"U`"@%T`$< +M`B"TI64@`0```#!G`````@``0B9!Y/KWC?D`(("]`2"`O?BU"TX`)``GH`"% +M&:AJ`"@"T`#PS/^O8@$T$"STTS=P=X3XO4U!0R!2>```]V,``)02`L"PM0T< +M`0,(&($-04`(`4`800I!0(@"0!B!"$%`R`%`&`$+2$`-\,KZ7R')!0WP_/H$ +M'"@<#?#"^B$<#?#U^@WPC?JPO0L<G$D+80AB"F``(`ACF4M`,QAH&R(2`A!# +M&&`(:``H_-%P1PL<DTE+84AB2F``($ACD$M$,QAH&R(2`A!#&&!(:``H_-%P +M1PL<BDE+84AB2F``($ACATI$,A!H&R$)`@A#$&!P1X-)2&@`*/S1<$<+'(!) +MBV&(8HI@`""(8WU+2#,8:!LB$@(00QA@B&@`*/S1<$<+''=)RV'(8LI@`"#( +M8W1+3#,8:!LB$@(00QA@R&@`*/S1<$<+'&Y)RV'(8LI@`"#(8VM*3#(0:!LA +M"0((0Q!@<$=G2<AH`"C\T7!'@+4@(@SP4/J`O8"U$"(,\$OZ@+V`M0PB#/!& +M^H"]<$==2!0A`8!4(0%Q<$=:2Q`S&GL`*OS16$J08E=((#`!<7!'54H0,A%[ +M`"G\T5))B&)12"`P`'EP1U!(`'AP1Q,<P#M`*Q"U"])-2QQIY`/\U%AAF6$! +M((`"$$,880`@$+T!(!"]1$@4(0&`2"$!<7!'04L0,QI[`"H:9B79`0```"QI +M`````@``A3]#N?S1/TH@,A!R$7%P1SQ*$#(1>P`I_-$Z22`Q"'((>7!'-T@( +M(0&`@B$!<3A(`(C`!C=)`=4"(`#@`R`(<'!'-$HPM11X`1PS2^(<D@$`(!I# +M*TL:@2I-`R(@-2IQ"@8+!!L.$@X"+`30`RP$T0D""0XI<2MQ*G$`(0("*'D! +M,0D$"0P00P0I]],PO2%)$+4+>"!*F0$10QI*$8$93`,A(#0A<0$&`@02#@D. +M`BL$T`,K!-$``@`.('$B<2%Q('D0O1"U`AP,'/_W7_^D`0`J"=$_(/_W:O^` +M!H`.($,!!@D./R`(X$`@__=@_X`&@`X@0P$&"0Y`(/_W3?\0O0#H`(``P0"` +M?2P`P`"C`(``(`"`Y!,"P`($``#XM>Q)`"#(8!@Q"'#J3@`E`"1(<N``,U@` +M*PC0@!E":"$<`)(J'"@<^?>2_@$U`302+._3^+UPM=].`"4`).``,%@`*`/0 +M*!SY]XW_`34!-!(L]--PO?BUV$@%:`$DUD\!)C`<H$`H0`30X``Y6``@#/#& +M_P$T$"SSV_B]`2#/28`"R&`"('!'^+7-3C1IS$E`,0]H^`:`#\I-"M`+\+/^ +M`"@!T(`AZ6(8(,5)P$-`,0A@N``6U<1(0&G$20`H!-$%(`AP`2"``NABP4A$ +M:0`L!-$&(`AP`2"``NABN$F]2$`Q"&#H1E6N`0```"AK`````@``<=?]!&`! +MO$^R30_5NTDX:`SP+/H!(KI(JF`P86AI.6@X:`(A:&$"(/GW?_\`+`/:@"$" +M(/GW>?\@!0/5!"$"(/GW<__@!`?5.&A`(2AA`B#Y]VO_JT@P82`&!=4X:`$A +M*&$"(/GW8?^G2*A/`&@`*!/0X``#U0?P(/^E2#!A(`$+U:1)`"`):``I`=&B +M2(!J``8`#@3P'O@W82`$`]40(0,@^?="_YA(`&@`*`+1(`$`U3=AX`>93PK5 +M>&L$(0$P>&-H:`A#:&!`(0,@^?<M_Z`'.-7H:)))`3#H8`IID4N:0BO0D$L! +M(AI@.FH!,CIB`3#H8&AH""(00VA@"FDI>'1+)#.(`!I0A4I`,I)I%#,:4(-* +M0#+2:10S&E"`2D`R$FH4,QI02!PH<`0I`=D`("AP("$#(/GW^/X$X"$@>$G` +M`H`Y"&)@!PG5:&@0(0A#:&"X:@$PN&($(,!#,&$@!PK5:&@@(0A#:&#X:A`A +M`3#X8@(@^??9_N`&$=5H:$`A"$-H8#AK!"$!,#AC9$B`.`%@`/#%_?[WF?L0 +M(,!#,&&@!A'5:&B`(0A#:&!X:@0A`3!X8EI(@#@!8`#PL?W^]X7[("#`0S!A +MH`0$U?\A`3$"(/GWJOYH:``H`]`!(,`#^O=<^#U)"#$(:`$BD@(00PA@^+W_ +M(#E)`3#(8`(@<$=<3CVX`0```"1M`````@``:"\C=("U`O!%_C5*"#(0:/\A +M`3$(0Q!@@+TQ2`!H`B!P1W!'_K4N2$`FQF!`3WAJ!?#,_P*I/2`)\$O]`:D[ +M(`GP1_T`JQAZ&GJ`(0A`?R&10RM,"$-V/"$<(#'+>8EY``8;`AE#`!:)!@#5 +M!3!`0@`&5B')0P`6B$("W"`<>C!>X`$<5C$$*0+8(!Q^,%?@`1Q1,00I`M@@ +M'((P4.`!'$PQ!"D"V"`<AC!)X`$<1S$$*0+8(!R*,$+@`1Q",00I`M@@'(XP +M.^`!'#TQ!"DQV"`<DC`TX```Z!,"P'1$`0``@`"0`*4`@``,`("`J@"`<$0! +M`("K`(#____?`*8`@,9-`,#____[_^___W0E`,#____W____[UA0`L#`H@"` +M(#0`P("H`(`B8`$`/$T`P`"0`)`X(<E#B$((W2`<EC`%'`SP?_P!,"D<#/"1 +M^"`<HC`$'`SP=OP!,"$<#/"(^`0@>&()20AH,$,(8`(@_KUP1P9)@"`(.<A@ +M!4E):@-*$6@(0Q!@`B!P1W!'"(``D`"0`)`O2!"U`2%!80(<$#(L3,)@`",@ +M-$-@*DF$8(!A,#'!8>!@HV"*8,M@`2$!(/GW>OT`(!"]L+4%'`$@^?>+_2!) +M'T@@,<QH,#"$0@_0X&C(8(%@`""@8.!@`2#Y]XC](API'&!H^?='_B`<L+WV +M*F%:`0```"!O`````@``G,>=R0$@^?=^_0`@L+V`M?GW7OZ`O1!)``%`&'!' +M@+7Y]Y'^@+T0M00<`2#Y]U[]"4D@`4`8@6@`*0G1P6@`*0;1!4H@,M%HB� +M8,%@@F`!(/GW6/T0O5P4`L!PM0`@BTH`)($``3`**%10^M.(38=)*#V&3B@< +M4#D-810P/#[P8"AAZ6`&(2@=+&`+\';^+6)L808A*!P8,&YB"_!N_G"]@+7_ +M]]K_`2$"(/GW!OUW20$@5#D(<``@@+W.Y_BU%AP$(@4<#!R!'&A&"_"H_@HA +M`)C_][[[,&!M28``"%@@8``H%=`$,`0B*1P+\!K^`"@*T0?@(&`$,`0B*1P+ +M\!'^`"@!T0(@^+T@:`!I`"CQT00@^.=\M08<`"``D%Q(#!Q4.`!X`"@!T0,@ +M?+T"(/GWW/P!JFE&(!S_]\#_!"@$T`(@^???_`4@[^=035!)4#TH:3PYB$(D +MT`0P!B(A'`OP7OXI:0`B#F``F``H"M$`D0AI*&'%8,I@"F$!F$1*@``14`G@ +M`6$I:0EI*6'-8`%IR&``:0"0`F$"(/GWL_P`(,/G`B#Y]Z[\!R"^YWRU!1P` +M(`"0-D@,'%0X`'@`*`'1`R!\O0(@^?>0_`&J:48@'/_W=/\$'`(H!-`"(/GW +MDOP@'.[G`)@I3@!H4#XH8`"8,FDF3=!@,&$!:0`I!=%`5K<[`0```!QQ```` +M`@``XMPZ><-H`"L"T0&;FP`$X`&;FP#L6(1"`='I4`/@PV@`*P#0&6$!:0`I +M`=##:,M@`F'&8`(@^?=H_`$@Q.?XM0(@^?=5_!-/`":U`!?@(&C_]^?^(&C_ +M]^C^(&D`*`+1`"%Y40/@`"'!8"!I>%$)25`Y"&G$8.%@"&D@80QA?%D`+.31 +M`38*+M_3`B#Y]SW\^+T``/`4`L!PM04<`2$Z,`+PJ_D$'"70Z4B`(H-[07L; +M`AE#D4-!<PL*@W.H>.MX&P(80P`H%M`!*`'0`B@1T2@=!1P,\+KZ``<`)@`H +M`=I9(`95*!P,\+'Z0`>`#P'04#1F<G"]*!P(,`SPI_H.(]1)6$,):$`8`3`- +M(0OP./UPO?.U!1P!(3HPB;`"\&WY!!QTT*AXZW@N'`XVL1\;`AA#+QT`*`B1 +M!-`!*$;0`BAET4/@"ID`*4#1`""[(0A5")@,\'OZ!"A9T@`&``X!D"A[:WL! +M(I(#&P(80PTH!=$@'$`P`8L10P&#!.`A'$`Q"(N00PB#.!P,\&#ZP`$,U0B8 +M#/!;^B$<0#$*BS\CVP&`!L`,FD,00PB#(!Q`,`&+`B(10P&#*GMK>P&8&P(: +M0PXC6$,`&5LP,1RNX"$<L#$'D0<@R'(X'`SP./HJ'#`R!I(&,@62$#H$D@@Z +M`0<`*0.22-H*F0`I"=$`!R$<4#'6V3[+`0```!AS`````@``%C2$Q,`/"'*H +M>.MX&P(80P#@>N``!@`.B'(I>VM[&P(90P`I<=`!*"C1$"(Q'"`<7#`+\/G\ +M.!P,\`KZ@`$+U0@B(!QL,`.9"_#N_`@B(!QT,`29"_#H_#@<#/#Y^4`&4]4% +MF`SP]/F<(0A1!II3>1!Y&P(80Z`T((!&X`(H1-$0(C$<(!Q<,%'@(!Q0,`$A +M07+K>*EX&P(90PD&"0[!<@*1")@,\-3Y`R@!V0`@`>"`!X`/!YD(<2I[:WL; +M`AI#`"HBT`*9`2DJT1`B,1P@''PP"_"I_#@<#/"Z^8`!"]4((B`<C#`#F0OP +MGOP((B`<E#`$F0OPF/PX'`SPJ?E`!@/5!9@,\*3Y`.`HX*@A"%$&FE-Y$'D; +M`AA#H#2@@1[@`ID"*0;1$"(Q'"`<?#`+\'O\%.`"F0`I$=$Q'"`<?#`+\'+\ +M*'MK>QL"&$,-*`/1`2&`-.%R`N``((`TX'(`(`NP\+TPM0`I#-"+($!<`"@! +MT0@C`N`!*`31$",`(-P>-DH'X``@,+T+&'`S&WL5&`$PZW"@0O?3`2`PO3"U +M`"D!T0`@,+U8(E):4@0!U`@D`.`0)`XC6$-`&``BXQXG307@@1A0,<EZK!@! +M,N%PFD+WTP$@,+T"'`@<4#``*@C00GH`*@O0P'H"*`C1"!Q\,'!'`GH`*@+0 +M@'H"*`'0`""4\-YC`0```!1U`````@``#\Q:M'!'"!Q<,'!'`QP0'%`P`"N` +MM1#00WH`*Q/0P'H!*!#1$!Q\,``I`M`!'!`P&^`!'!`<E#`7X`-Z`"L"T(!Z +M`2@!T``@@+T0'%PP`"D(T`$<$#`(X&!-`,#8$P+`?#8`P`$<$!QT,`KPV?X! +M(("]``"!0@'8`2!P1ZA*4&"18``@<$?PM86P!1P`\#SYZ&DH80\P``FIB@0! +MH$CI@D%HC$("TX!HA$("V0`@!;#PO1(@`*L8<30@6'%6()AQ>"#8<9`@&'*@ +M(%AR`""8<@20`Y5IBR`<$#``&T`8$#``"0`!`)`0(B`<`:D+\)[[KHH!/@S@ +M`)@G'"08$"(@'`&I"_"3^R$<##$X'`OPN/P!/O#2*!PL8<GG<+4$'`#P\_CY +M]\CY!AS@B@`H$=`!.``$``S@@B&+B$(`TB"#)6DH'`PP#/"&^"!A`2#H<:AQ +M`.``)3`<^?>S^0`M`M`H'!`P<+T`('"]L+4$'`#PS/C@B@`H%=`!.``$``S@ +M@B&+B$(`TB"#)6DH'`PP#/!B^"!A`2#H<:AQ`"T"T"@<$#"PO0`@L+T!'!`Y +M$+4*:%U+6TR:0@/0('@!,"!P">!B:)%"!M.B:)%"`]@*'!`R@D(!T``@$+T( +M'!"]<+7_]^/_!!P(T"`<"#`,\#3X!1P`\(OX`"@!T`$@<+V@>0`H!='GS<M. +M`0```!!W`````@``^R3D"4=)##$(:`$P"&#TY^!Y_S``!@`.X'$0T?GW3?D& +M'"$<##$H:0OP+/PL8>B*`3#H@@`@H'$P'/GW0_D`('"]L+7_]['_!!P(T"`< +M"#`,\`+X!1P`\%GX`"@!T`$@L+V@>0`H!=$N20PQ"&@!,`A@].?@>?\P``8` +M#N!Q"M$A'`PQ*&D+\/W[+&'HB@$PZ((`(*!Q`""PO1"U__>%_P0<$-`@'`@P +M"_#6_P#P+O@`*`C1^?<!^>%Y`"D!T`$QX7'Y]__X$+T0M00<`/`>^``H`=`` +M(!"]((L0O1"U!!P`\!3X`"@!T``@$+W@BA"]$#B">1%#@7%P1Q`X@GF*0X)Q +M<$<0.(!Y"$!P1P5)2FB00@+3B6B(0@'9`2!P1P`@<$<``!@5`L`2-%9X@+4! +M8`$@"?`&^("][TCP20A@<$?XM>Y.@"0@-C<=`"7K2"@P!&`!(`CP]?\]8`$@ +M"/#Q_S5@`2`(\.W__R!!,`$TA$+LT^-(16#_]]__^+WXM0`DA2```=])8$-% +M&(4A"0$H'`OP(/I8(-Q)8$-!&`$@P`(H&$$B4@$!80`AJA@1<X,B$@&J&!%R +M_R%1<@9I6"$P'`OPY/DQ'0T<,!PT,`OP6OLQ'!PQ"!T$(@(G$PJ"<,-PRD@+ +M\$_[R$HQ'.``0#J`&"PQ!AP+\$;[QDK%21*(`"!W\>_#`0````QY`````@`` +M/#WF5%(`B5J)!LL-G$:!`'$82W@*>`$P&P(:0_\C^3.:0V-&&D,*<!,*2W"+ +M>$]X2AP;`A]#&",?0T]P.PJ+<$MX#W@`!AL"'T,"(Y]#?PA_``0CGT,/<#L* +M2W!3>!=X`C$;`A]#@".?0Q=P.PI3<`J(`2/;`YI#``X"*`J`QM-QB`\B$@*1 +M0YL)R1AQ@/&(D4,#(I("B1CQ@"@<"_#>_@0A`7`+"D-P*!P+\-?^`C!#>`9X +M#"$;`AY#CD,$-@9P,PI#<"@<"_#)_O`BED,Q'(`Q@7`+"L-P*!P+\+_^CTD, +M,`8B"6@+\*7Y`30D!B0.""P`TD;G^+V)2@`H&M&`2X`[&&M9:\D%P`7`#<D- +M@4(LT9AKV6O)!<`%P`W)#8%")-'0:I%JR07`!<`-R0V!0A[0&^`!*!O1<4N` +M,QAK66O)!<`%P`W)#8%"#]&8:]EKR07`!<`-R0V!0@?14&L1:\D%P`7`#<D- +M@4(!T``@<$<!('!'^+6%(QL!8TE80T<8`2#``CX8(#`]H)N#H>``B_S#H +M<")S(6@('"`PPWJ`>AL"&$,'*`;9#"@$TEU)"&@!,`A@$.`D,0`@"_!D^B%H +M"2`@,8AR`PK+<B%H`"`P,8AP(&C]]U7Y9&@`+-;1`")R8#)@`2!H<X$@``$X +M&`)Q\F&R8?B]^+4''/CW7__RN/SQ`0````A[`````@``R-58Z04<A2```4!) +M>$-$&$$@0`$F&#![`"@=T#@<__>K_P$G_P(`(3%SX!F!8@%I#APD-C`<"_`3 +M_D`(0``Q'`OP)/H`(``A(A@!,``&TAD`#@8H$7+WTR@<^/<W__B]^+4-'!0< +M)T@`(0%@+TH"8"]*@F#!8"-*##(0:!,AR04(0Q!@'TX@-C`<"#`W'0`L`M'_ +M(1DQ`^`!+!+1_R$;,0%@`2`(\%'^Z7VHB@D'"$,X8`$@"/!)_BAI,&`!(`CP +M1/X)(<D#%B<^(!XB`"P931E.-=$+2\`[&F.98PE,@#Q@8"!@^?<R^2=A!DB& +M8P5(0#@%81%(06CB!1%#06#XO0"P$`#`J@"`P*L`@+R``0"\%0+``B```.8H +M`,#6*`#`S!,"P$"@`(`H9`'``[`0,RQW`L`!#!``@(0>`$"E`(`!+-S1]DL: +M8QH<D6/T3$`T8&`@8/GW^O@G8?%(0#C&8^](@#`%8>](06@!(E('$4-!8.U) +M`2`(8,'GZ4I`.M%H`"@'T0$@P`6!0]%@$&A`"$``!^`!*`;1P`:!0]%@$&@" +M(8A#$&!P1X4C&P'?25A#0!A!(4D!0!C`>``H`=`!('!'`"!P1_>UA+`-'/CW +M>?[52@`A$6`&F84C&P'32EE#CAA!(4D!=!@A>P`I#-$!)R=S!ID`D?CW:?X` +MF0@I!M'\.?81`0````1]`````@``T2V&F0`@P$,'L/"]^/=@_OCG@R```3`8 +M`Y`'<@`@`"$#Q@/&`2')`A`^=Q@"EWA@.&`!(&!S$#%Q&``@"''X8;AAX'!@ +M<2@<##`+\`O]('$`F"D<H'&W2!`Q/VD&(C`8`9$*\.O_Z'TY'"`QH'!+>`IX +M``<;`AI#$@D2`0`/$$,(<`,*2W!H?@:9A2,;`:A*64.*&"$AB0&`!X`/41@# +M*`'1&"`(X`(H`=$/(`3@`2@!T0@@`.`$(`AS`9@`(0'PJ/L`*`+00'L#F4AR +M.!T$'`OPR/P@,$-X`7@$(AL"&4,10P%P"PI#<`*9`""(8CD<)#$*(`OPS/@@ +M'`OPL_P&,`8B`9D*\)K_[7T@'`OPJOPA,$-X`7CP(AL"&4.10RH'$@X10P%P +M"PI#<`"8;.?XM7Q-`"88/2P<*#P@:``I(-$!)S\'.$,@8`$@"/``_2!HN$,@ +M8`$@"/#Z_/\@"3`H8`$@"/#T_"@?!F`!(`CP[_QL2"`X!F`!(`CPZ?QI2,`X +M(.`!)W\'.$,@8`$@"/#?_"!HN$,@8`$@"/#9_/\@"C`H8`$@"/#3_%Y('#@& +M8`$@"/#-_%M((#@&8`$@"/#'_%A(0#`&8EQ)06(!(`CPO_SXO0`@<$=P1W!' +M<$<!('!'<$?XM4]._R0A-``E'#XW'3Q@`2`(\*O\-6`!(`CPI_P?&?V@`0`` +M``!_`````@``)<4X)$A((#@%8`$@"/"A_/\@,#`!-(1"[-/XO?BU`1Q!2``E +M(#@''`@W!AT`*7+0P"0\8`$@"/",_#5@`2`(\(C\.4@@.`5@`2`(\(+\`33_ +M+.[3_R`*,#A@`2`(\'G\-6`!(`CP=?PO3"`\)6`!(`CP;_S_(!,P.&`!(`CP +M:?PU8`$@"/!E_"5@`2`(\&'\_R`4,#A@`2`(\%O\-6`!(`CP5_PE8`$@"/!3 +M_/\@&S`X8`$@"/!-_#5@`2`(\$G\)6`!(`CP1?S_(!PP.&`!(`CP/_PU8`$@ +M"/`[_"5@`2`(\#?\_R`C,#A@`2`(\#'\-6`!(`CP+?PE8`$@"/`I_/\@)#`X +M8`$@"/`C_#5@`2`(\!_\`.`3X"5@`2`(\!G\_R`R,(+@````JP"`0*4`@$@5 +M`L"\@`$`"`@``/#_``"`)#Q@`2`(\`3\-6`!(`CP`/SW2`5@`2`(\/O[`32_ +M+._3_R`),#A@`2`(\/+[-6`!(`CP[OON3"5@`2`(\.G[_R`1,#A@`2`(\./[ +M-6`!(`CPW_LE8`$@"/#;^_\@$C`X8`$@"/#5^S5@`2`(\-'[)6`!(`CPS?O_ +M(!DP.&`!(`CPQ_LU8`$@"/##^R5@`2`(\+_[_R`:,#A@`2`(\+G[-6`!(`CP +MM?LE8`$@"/"Q^_\@(3`X8`$@"/"K^S5@`2#:FDA&`0```/R``````@``HA7C +M5PCPI_LE8`$@"/"C^_\@(C`X8`$@"/"=^S5@`2`(\)G[)6`!(`CPE?O_(#$P +M.&`!(`CPC_LU8`$@"/"+^R5@`2`(\(?[^+WXM0X&-@X,'`@I`M,`(,!#^+V% +M(``!M4E@0T48^/<R_`<<,!S_]XG\02%)`6D8`"`(<X,A"0%M&"AR_R!H<H4@ +M``&J27!#0!BJ24`8!B$*\(+].!SX]QO\:'H$\!+](!S6Y_"UI$FC2HEHH$X! +M,9%@HDD%>W$8"WH"'"`RG4(DT4-[3'JC0B#1@WN,>J-"'-'#>\QZHT(8T0-\ +M#'NC0A310WQ)>XM"$-&3>%%X&P(90Y)+"08@,_,8G'@)#Z%"!-$9>P`I`=`' +M(37@`"&%(QL!ATQ+0QP9`2/;`N,8'GJU0B+11GM?>KY"'M&&>Y]ZOD(:T<9[ +MWWJ^0A;1!GP?>[Y"$M%&?%M[GD(.T9-X5G@;`AY#,P9!)G8!I!FF>!L/LT(" +MT2-[`"L$T0$Q"08)#@@IS-,(*1[0A2,;`5E#;$X!).0"B1D,&0`CHV+3>))X +M020;`AI#$@ED`0L9VH(`(H,8C1@@,QMY`3(2!BT9$@X(*JMS]-/PO?.UA;`, +M'/CW@OL!D(4@``%@0UE)`I0%G4$8`"0$-0.1*!P+\$/Z`!F`>0.9`2+2`@D9 +MB1@)>HA"-]$!-"0&)`[?Z6'L`0```/B"`````@``5OU=Z@8L[=-!(`.90`$/ +M&#A[`"@KT'UY`YP`)B@!`!D`>P`H&=$H`009`2`@<P69`"`P,<AP!9E@8"%@ +M)#$$D0@<"_`5^D`(0``$F0KP)OX$(068__=8^@?@`34!-C8&;09M#C8.@"[8 +MTX`N!=$!F/CW-/L`(`>P\+T!-6@&0`YX<0$@`YG``@@8`6@`*0'1!&`!X$%H +M3&!$8/AX`3#X<'A[`2@%T0*<H$#!`0,@^/=4^P&8^/<2^P$@W.?XM0\<02%) +M`448Z7@`*7+0`2')`D08(&@`:`0P"_#*^4-^`'X;`AA#!@DN@"!I!#`+\,#Y +M(C!#>`%X#R(;`AE#$4`R`1%#`7`+"D-P(&D'<"%I`"!(<"%IB'#(<"%I$#$* +M\+[](&D$,`OPI/D#,`%X0W@((AL"&4.10PG@X*H`@+R``0`("```2!4"P#!" +M```!<`L*0W`A:20Q"R`*\*#](6D/'!PW.!P+\(3Y`2%)`PA#.1P*\)3](6D! +M(APQ$@,00PKPC?T@:2@P0W@!>!L"&4-)"$D``7`+"D-P_R/Y,YE#"!PB:2@P +M*#(0<`,*4W`@:2DP`.`.X$-X`7@8(AL"&4,10P%P"PI#<*EY(&D%\-?]`"@! +MT0`@^+VH>?!)@``*6K)"!=%`&`(P`H@!,@*`!>`.4JAY`"*``$`80H"H>>A+ +M@@`;8+T'`0```/2$`````@``3P6#FE(84H@;:)I""MGF2A!P`2#E2H`"T&*J +M>0`@D@!1&$B``2#B2:!B2&@!,$A@`2#1Y_&UB+`(F$$A20%`&`>0P(H(F@`F +M`Y`('"`X%1@L:``@!I`%D`B8!9E`&$$A20%`&(![`9``(`20`9C`!VG5`Y@& +MF4`8`04)#0*1`"QAT"!H!#`+\/7X0WX`?@*9&P(80P`)"!H!(0D#0!C!%PD- +M"1@)$PD#0!H`!``,0"A)V"!H`"<$,`OPW/A#?@!^`ID;`AA#``F(0CW1`"(B +M<RAH)QR@0@'18&@H8&AHH$(`T6Y@`"X!T&!H<&`'F<AX_S#(<"%H"!P@,,-Z +M@'H;`AA#!R@&V0PH!-*J20AH`3`(8!3@)#$`(`KPPOP@:`DA(#"!<@L*PW(@ +M:``B,#""<"!H_/>S^R!H`"(P,,)PZ&D!,.AA`>`)X"8<9&@`+P/0`"`X8'A@ +M`>``+*K1!)@&F0$P``8`#@20`9@!,4`(`9`)!@D.!)@&D0@H`-)VYP69`3$) +M!@D.!9$'*0#89.>!(0B:"0%1&``@"'$'F<EX`"D$T2$A")J)`5$82&`)L/"] +MA2,;`8!)6$-`&``A@.;QM9*P^/>(^0:0$IB%(QL!>DE80T$8!9$`(0`D`)0! +MD4$@!9E``0\8.'L`*`71!ICX]W?Y`"`3L/"]`2`%F<`""1@0]0QC`0```/"& +M`````@``N^T])Q&1#F@`+@G0,&@$,`OP,OA#?@!^&P(80P$)!)$`(0*1!9F# +M(``!"!@0D!*8!R$``0A##Y`A(`69@`$)&!*8+",.D5Y)6$-!&`V174D,D6GA +M,&@$'`0A__=C^``H"]%221*8"'`!(%%)@`+(8E5)2&@!,$A@D^`0F`!Z`2AR +MT0`E(!T+D`N8"O#U_T`9@'D%F0$BT@))&8D8"7J(0@?00DG_(`AP04E0",AB +M`2$!D0$U+08M#@8MY=,!F0`I;]$!("!P`"!@<"$<)#$/F`KPZ/MP:``H)]`` +M:`4<!#`*\,K_0WX`?O\A&P(80P`)`Y`H'"`PPWB`>`$Q&P(80XA"N'D'TBM) +M@``4,4`8`6@!,0%@">`G28``%#$*6`0J`=D!(@"2`"(*4`Z9"'L"F8A"#M!P +M:``H"]``F@`J"-$#F`290!H`(4`8``4`#4`H!M,-F0$@B&$!(0&1#ID(<`N8 +M"O")_P#@UN"!>,-X&P(90PH'D@\"*@;1"08)#P@I`M&`><`'(=4`(#(A"%4A +M'`H@(#&(<@,*RW(@'/[W+_^VX,3@?!@"P,`F`,!P1`$```P`@$@5`L`H9`'` +MO(`!`"AS`L!,30#`)!4"P"4<(#4I>FMZ&P(90PJ1R`<PU"`<+#`)D`KP2/_H +M28A"`M`,F8A""]'F21*8"'`!(.5)@`*=X>&L`0```.R(`````@``?/0_>LAB +MY4F(:`$PB&"0X`J9`2(10RER"PIK<D-X`7C_(AL"&4/Y,I%#`7`+"D-P")$) +MF`KP(O\(F0$B$4,!<`L*0W#62`!X`"@BT"$<'#$#(``""O`H^R`<+#`*\`[_ +M`8C)!8D.#"D3V4-X`7C_(AL"&4/Y,I%#8#$!<`L*0W!K>BAZ&P(80Y!#8#`H +M<@,*:W(@'#`P!Y#`>``H']`$*!/2N'F_28``"%@`*`W00'L$\"#X(1PL,0KP +M]OH!>",<(#,9<D%X67()X&MZ*'K_(AL"&$/Y,I!#*'(#"FMRN7D@'`7P4?L! +M*`;0JDG_(`AP`2"I28`"R&('F,!X!YD!,,AP$9F(:0$PB&$"F0$Q"08)#@*1 +M`9D`*0O1!N"=2?\@"'`!()Q)@`+(8G9H`"X`T)+F`ID`*0C0N'G_]SW^`"1\ +M<Q`@_/>3^`'@`2!X<P:8]_?*_P$@4>:%(QL!DDI80X`8(2*2`0`A@!@!<'!' +M<$>%(QL!6D.,2S"UTQ@!(M("FA@$>!5ZK$(>T41X57JL0AK1A'B5>JQ"%M'$ +M>-5ZK$(2T01Y%7NL0@[10'E2>Y!""M%!($`!&!B">(I"!-$`>P`H`=`!(#"] +M`"`PO1"U!!P`((`L*=-A!`/5!R#_]_/]`2`A!@/5`"#_]^W]`2#A!0/5`2#_ +M]^?]`2"A!0/5`B"TW?OL`0```.B*`````@``B!R!Q__WX?T!(&$%`]4#(/_W +MV_T!("$%`]4$(/_WU?T!(.$$`-4!(*$$`-4!(!"]^+4,'!4<]_=4_P8<A2`` +M`5I):$-`&`$AR0)!&(EJ`2<`*0'0)W`#X/_W\OQ42$=5,!SW]T/_('CXO7"U +M!AP,'/?W-_\%'(4@``%,26!#0Q@!(<D"`"!9&`QH/N`B:+)".=$`)B9S"FBB +M0@'18F@*8$IHHD(`T4A@`"@!T&%H06!!($`!&!C!>/\QP7`A:`@<(###>H!Z +M&P(80P<H!MD,*`32.$D(:`$P"&`/X"0Q,!P*\.3Y(6@)("`QB'(#"LMR(&@P +M,(9P(&C\]];X*!SW]_7^`2!PO2`<9&@`++[1*!SW]^S^`"!PO?BUA2,;`2-/ +M6$,,'(,B$@'`&8(8`2$1<B%X8W@;`AE#R@=OU8D&20\=2DD`45H=2DD`55H! +M(<D"0!@`:2PP"O"3_4-X`7C_(AL"&4/Y,I%#J@;2#1%#`7`+"A-)0W`(>$MX +M&P(80T`'*-4@>&-X&P(80T`'&M4!(!G@``!(30#`<$0!```,`(`D%0+`.S,` +MP-0S`,"\@`$`5!4"P"AD`<#6*`#`YB@`P(!-`,``((MX27@;`AE#20:)#P'@ +M`"``(<`'1@_G((D'30Z``0`D/Q@X:RPP"O!(_:$`0!@!B`0B`3210S%#`8`X +M,[2&`0```.2,`````@``D>1?MX-X07A@(AL"&4.10RE#"PHD!B0.07"#<`(L +MY=/XO?BU02;O3P4<`"1V`84@``%@0\`9@!D`>P`H`]`I'"`<__=K_P$T)`8D +M#@@L[M/XO;@A\+7D2D%#CQBR(<Q=A;#_+''0N"-80Q$<0!@!'`@Q!)$0,0.1 +M!#$&'&0V!1T`+`*1`=`!+"S1]_<L_@&0UDD@`D`8AVI$:0`L`]`Q'``@"O`% +M^1`O`-D0)R@<"O#I_``9*1P*\/OX`I@&'`KPX?P`&3$<"O#S^#@<`YD*\._X +M!)@%'`KPU?P`&2D<"O#G^$'@]_?__0&0!31@!X4C&P%`#[M)6$-`&`$AR0)$ +M&*!I`"@#T#$<`"`*\-'X`I@&'`KPM_RA:4`8,1P*\,CX`"8P'`.9"O##^"@< +M"O"J_*%I0!@I'`KPN_@$F`4<"O"A_*%I0!@I'`#@$.`*\+#X.!P,,`4<"O"5 +M_.%I0!@I'`KPIOBF8>9A`9CW]\']!;#PO0(H"=*=2@EH``((,H`8"0()"@%@ +M`2!P1P`@<$=P1Y=*T&J1:LD%P`7`#<D-@4);T9)+&&M9:\D%P`7`#<D-@4)2 +MT9AKV6O)!<`%P`W)#8%"2M&+2QAK66O)!<`%P`W)#8%"0=&8:]EKR07`!<`- +MR0V!0CG14&L1:\D%P`7`#<D-@4(QT7Y)0#E(:0EIR06#/_SN`0```.".```` +M`@``90SA"L`%P`W)#8%")]%0:A%JR07`!<`-R0V!0A_1T&F1:<D%P`7`#<D- +M@4(7T5!I$6G)!<`%P`W)#8%"#]'0:)%HR07`!<`-R0V!0@?14&@1:,D%P`7` +M#<D-@4(!T``@<$<!('!'8DF(:\EKR07`!<`-R0V!0@C17TF(:\EKR07`!<`- +MR0V!0@'0`"!P1P$@<$<`*`'15DD`X%=)"&I):E9*$$`10(A"!-D0,1%`@4(& +MT`/@$#@00(A"`=``('!'`2!P1TI+$+4<'/\T`30`*`+1&6I::@'@(6IB:D=* +M$4`"T!`Y$4`*'``H`=%:8A"]8F(0O0,</DB`,(-B`VH+8$!J$&`!('!'^+4Y +M3Q8<#1P*)(`WN&)^8CUB">`'\"3\.&IY:JA"`]&Q0@'1`2#XO2`<_S``!@`. +M(1P$'``I[=$`(/3G\[4`(0`HA[`H3`K1HFOC:]L%T@72#=L-DT(/T*%CX6,, +MX`$H"M$C3:IKZVO;!=(%T@W;#9-"`="I8^EC`@(2&0:2%6L&FE)K`Y*'`6[@ +M*`:`#@60.!B`,`&J`JG_]ZC_`2`"G(`'!2')`R`8B$)8V`&8!)#``U34`"Q2 +MT`B8`"@NT2`<$#`*\%W[!AP@'!0P"O!8^PE)"FA):(%"$-E]($`#%."\@`$` +M2,,!`$"J`(!`H`"`0*L`@/#_```?"72;`0```-R0`````@``&Q=&N@"F`("! +M0@/1LD(!TY`;`.``('TA20.(0@'2`"``X`$@`"@@T`$A!)@)!`A#`AP%F`&2 +MP!F`,"$<__=F_^-XH'@!(1L"&$,(0Z!P`PKC<``@,B$(52$<""`@,8AR`PK+ +M<B`<_O?4^B@=Q04&F.T-!6,#F(5"C=$!(`FP\+T`*`'1!2``X`8@%4J10@#9 +M$1P42H``@!@!8W!'^+4''`8<`"00-X4@``$/26!#11@.2#D<!B(H&`GP5OT` +M*`C102!``2@8@'CQ?8A"`=$`(/B]`30D!B0.""SDTP$@]^?_9P```*X`@+R` +M`0`("````"$!8$%@@6!P1["U!!SW]_;[H6@`*0/1]_?V^P`@L+UE:``B:6@` +M*0+086`*8`'@8F`B8&I@*F"A:`$YH6#W]^/[*!RPO;"U!!P-'!'0]_?6^R%H +M*6``(6E@(6@`*0'036``X&5@)6"A:`$QH6#W]\O[L+VPM00<#1P1T/?WO_MA +M:&E@`"$I8&%H`"D!T`U@`.`E8&5@H6@!,:%@]_>T^["]L+4%'`P<`M"H:``H +M`=$`(+"]]_>C^V%H`",`*0O0(F@`*@30"F!A:")H46`,X`M@86AI8`C@(6@` +M*0/02V`A:"E@`>!K8"M@(V!C8*EH`3FI8/?WB/L@'+"]^+7C3P8<.("X:``H +M`='A2+A@WTH.Z;'!`0```-B2`````@``[__X!P`@*#(`)8$``3!`*%50^M/: +M2!`P__=L_]A('##_]VC_`"0:X-`EN6AE0P`@2%%I&$A@B&#(8(AU`2+*=<DB +M4%0('!`P1',&(0GPNORX:"D8RD@0,/_W;_\!-+1"XM,%\$/Y!/#O__SWMOP! +M(GI@`"#XO?BU%AP$(@4<#!R!'&A&"?#P_+])`)@):`KP;_N`!H`.NDDP8(`` +M*#$(6"!@`"@5T!`P!B(I'`GP7OP`*`K1!^`@8!`P!B(I'`GP5?P`*`'1`"#X +MO2!H@&@`*/'1!B#XY_ZU!!P`(`*0J$A`:``H`=$%(/Z]`:H"J2`<__?!_P8H +M`=`'(/7GH$@0,/_W_?X&'#[0`9B=3X``*#<]6#`<$#`"ED%[87/`(0GP=?P" +MF`8B(1P0,`GPG?RA>0*8S"*!=>%YP75AB4&#(7L!=[PA"5T15&%['2(15&%[ +M`3$!@R&!`"$`+0C0`.`5'*IH`"K[T:A@@6#%8`3@P6"!8`&9B0!X4(%(,1P< +M,/_WW/X!(+#G"2"NYWRU`"$!D7M)26@`*0'1!2!\O6I&`:G_]VG_`"CXT0"8 +M=4Z``"@V-%@`)0`L+-``X*1H`9D&(B`<$#$0,`GPT?L`*/71`"P?T`&8`"@< +MT.%H`"D+T*)H`"H&T,%H@FC18(%HPFB18`W@C6`+X*%H`"D%T,U@H6CZNM.) +M`0```-24`````@``]@<F=P":D@"Q4`+@`)F)`'50Q6"%8%Y*`9D`(`N+1``! +M,!L!FQ@=4P@H]]M52!PP__>S_@$<4T@0,/_W@/X"(*WGC+4`(0&13DE):``I +M!=!J1@&I__<1_P`H`=``((R]`9@0,/OG<+5&3APV,&@`)0_@`"$0,`0<__?C +M_P`H"M"%<0")!/#__P`A(!S_]W__,&@`*.W1/TD`(`U4`3`(*/O;<+W!`\E# +M"!B!"D%`R`!`&($)04#(`L!#0!@!#$A`<$<P21"U26@`*0'1`"`0O2Q)`"(< +M,1'@#'Q3'(143'Q:',14C'Q3'(14S'Q:',14#'U3'(143'U:',14"6@`*>K1 +M`2`0O;"U!1P`(?_WF?\$'`'1!B"PO2![!"@!T?KWU/K@>0(H"=&\(`!=&4D* +M7``J`]!`&`%X_S$!<``@H'$@B03PH/\`(2@<__<@_V![`_#`^0(@L+T*2`I) +M'#``:,A@<$<'2E%H`"D)T-%H`"D&T!`Q`6#0:`!HT&`!('!'`"!P1ZA&`0#@ +M2P$`!$4!`-!'`0!T,P#``1R22))*0W@`>!L"&$/0(UE#B1A)C$`'P`^)!\D/ +M@4(`T``@<$<0M00<B$A#>`!X&P(80X`'P`\!*`[1(!S_]]__T"&"2F%#B1@` +M*$B,`=&`!@#@0`;`#Q"]`"`0O7"U!1QZ2`X<0WB/"/-B`0```-"6`````@`` +M`N^8R@!X&P(80X`'Q`\H'/_WQ/^P0@G9`2P+T=`@<TEH0T`80(R`!L0/`^`H +M'/_WR/\$'"`<<+UK2(-X0'@;`AA#0`:`#W!'T"-H25A#0!@0,`-\P'L;`AA# +M@`;`#W!'$+4$'&!(!#`*\!;XT"%?2F%#B1A)C,`&P`_)!\D/@4(`T``@$+TP +MM40&9`X`(%A-!^"C0@314AE2B`N(&D,*@`$P@@"K6O\K\]$`(#"]T"-.25A# +M0!B`M5<P"?#M_P`$``R`O=`C24E80T`8@+5;,`GPXO^`O0$<T"-$2EE#B1A0 +M,<E[`2``*0#1`"!P1X"U`2$,,/_WI_X`*`30N"$)6`")!?"U^8"]?+4.'``J +M.-#3>1$<%!RP-+@Q`2LU31;1`WG`>!L"&$/`!BK5`B#0<0AH,QP!D&%Z`)$0 +MB0`B`B$%\$7Z('M`&0%X`3$8X`-YP'@;`AA#P`84U`$@T'$(:#,<`9!A>@"1 +M$(D`(@$A!?`N^B![*5P`*0/00!D!>/\Q`7!\O1Q(`'@`*/K0('K#!P75@P<# +MU0,'`=5#!Q'4%TN;70,K`='%!PO4`BL!T84'!]0!*P'1!0<#U``KX=%`!]_5 +M"&@S'`&087H`D1")`2("(07P_/G4YP-(@WA`>!L"&$-`!H`/<$>`30#`X$L! +M``A%`0!T,P#`)S,`P!PG`,#-"S#V`0```,R8`````@``Q?::EX"UK+!H1JTB +MD$D)\#'ZK2)I1H]("?`L^BRP@+VPM8Y-C4P`((M+$#T`(H$`:E!B4$$``C$9 +M5`$P!"CVVX=(B$P@8A4<XF%@8H%(`2$`!``,"$.$28AB__?3_R!H`2')!@A# +M(&"`2`"+%"@.T7](0'D"*`K1!R!^20`'"&!X2D`R4&J)!0A#4&(#X'9)>4A` +M,0A@=$H,,A!H#R$)`PA#$&!U2'9)R&-K2`PX!6``(+"]:4EH2A`Y"#H&P`@X +M!#*"8`H=`F$*'`@R##&!84)A<$<PM6%-`"`0/0/@@P#46`$P[%"(0OG3,+UP +MM5U-[&G@!P+5`2#[]\GXH`<"U0(@]_?Y^2`$`"8`*`_:;F)<2%U)R&!=2`%X +M74@`*0'07$D`X%Q)P6#N80KP+_A@!P/5`2"``_?WW_GN87"]<$=(20$@R&)P +M1T-($+4,.$%H`"D,T$L<"M!`3!TB+SP@'/SWUOX@>`$&`=1`!P'5`"`0O2`< +M$+WXM3=-`"<")@0<##T`*!#0@"`@<!TB(1QH:/SWOOX@'`PP"?"`_FA@*&@! +M,"A@&"@"T2Y(+V#&8OB]L+4L2`1I*$T((B$<!#4H'/SWS/XH:"$<`@PH'/SW +MQOXH'+"](T@`:7!'`R`A20`&2FD`*@'0`3CZT@`@2&%P1Q"U!!S_]_'_&DA$ +M80@AP6(M^:'6`0```,B:`````@``,1XD*A"])D@!:!=(`"D!T"1)`.`D24%A +M<$<`(Q))`V`)::4I`=$!(@)@6BD`T0-@<$<0M0I,`"`(2Q`\`"&"`*%00@`" +M,AI4`3`$*/?;$+UP1P``-$(!`'1/`,`,#@#`=`\`P`^`````#`"```0`@!10 +M`L"@30#`@```@`$`PL'P\?+TP+\`P/__#P``@`"0,DT`P``@`(!S-```8U0` +M`'0E`,"E]/+Q]/+Q\`(<"!S_(5TQ44/D2A"UB1@,'#`T(GAC>"$<&P(:0R`Q +M"?#4^&-X('@;`AA#$+T`(@7@@U0!,A(&$@X@*@+2BUP`*_;1'RH!T@`A@500 +M''!'`AS32P`@00!96I%"`]`!,`TH^-,/('!'_K4&',U(`"%!86PB@F'!8@(B +MPF'*2`XA"?!V^,A(#B$..`GP3?@`)"4<`"?P&3`P`I!`>X$>?2D5V/_WU?\. +M*`[2`IF^2DE[T56\2I-IF4(!V%!AD6'3:9E"`=/08M%A`3<.+^+3M4H`(!!B +MUV`08VP@4&("()!B`"?P&4`P`9#`>H$>?2D3V/_WKO\.*`_2`9FK2LEZ#CK1 +M5:A*4VJ90@'8$&)18I-JF4(!TQ!CD6(!-PXOX=.A2@HAHD@780CP__^?3PHA +M&#\X'`GP'?B=20$@"'`R(#AP`"$VX)A/#C]Z7``J,-"52P`@VVBS*0?A`0`` +M`,2<`````@``*.;Z6A7@E$\_7+I"$-$(+`72@",30R(<`3203P7@@",30RH< +MC$\!-1@_TAF3<`+@`3"80N?3ATO::)!"$-$(+`;2A4\@'`X_>ER$3P$T!>"! +M3R@<#C]Z7`$U"C_`&8)P`3%\2QAI@4+$TWQ/,!Q\<'E,_S`8/&5P'S`)\!/] +M!"@=T0`@0".`)0C@.1B*>!8<=@B>0RI`,D.*<`$P>7B!0O/8`"`(X"$8BG@6 +M''8(GD,J0#)#BG`!,&%X@4+SV/Z]L+4J(0%P`2%!<&5)8DT)>`HB&#V!<$1X +M`C0`&2D<"/#-_VAX`C`@&+"]7DI0:0$AR08(0U!A<$?_(R8S<+5;3$-#&QE3 +M2AP</#2```0F3#(`*55-"=`34"`<"?##_#!#(1P)\-7X"2`(X!-0(!P)\+G\ +ML$,A'`GPR_@4("AC<+T0M00<1D@`*@%P!]"(!P'5`"$`X`$A(!S_]\[__R`F +M,$))8$-`&#M*H0!,,E!0__>\_Q"]/DL0M=MH`AP<>%MX&P(<0P?@$WB+0@'1 +M$!P0O5-X`C+2&!,:HT+TVP`@$+WXM0`E#!P](?_WY/\`*"30`7G\(Z('D@\9 +M0`I#%AP&<1_@8'DT*!G1*4D#(J`<!3$''`CPU?X`*`'0`"4(X"1)`R(",3@< +M"/#+_@`H`-`!)0`L`M``+0K1)G+XO6!XH1QUFV[0`0```,">`````@``W`Y$ +MYP@8W2'_][;_!!S:T?7G9G+SYW"U$DX%'(`^L'H,'(A"$M`.2"$<B##_][K_ +M_R`F,`Y):$-`&`=*J0!,,E!0M'+_]U/_]O>D_W"]``#H(P+`WB8`P)P8`L#@ +M+@+`,D@"P'!.`,``I0"`0*8`@.@^`L#$)@#`^+7J3P`E`";_("8P:$/$&2`< +M(#`!(8%Q9"'!<0L*`W(!(4%RXDBF<0%H!B(@'`CPZ_X@'#`P#"%!<Q@B@G,P +M(\-S$#`&<,%R$B$!<T)S)"&!<\-S(1Q0,4@@"'!@($AP;""(<`$U""W.<,W; +M^+V`M1@BT$D(\,C^&""`O<U)@+46(A@Q"/#`_A8@@+T0M<I,87@`*0;0BAPA +M'`CPM?Y@>`(P$+T`(!"]$+7$3&%X`"D&T(H<(1P(\*?^8'@",!"]`"`0O1"U +MO4PZ/&%X`"D&T(H<(1P(\)C^8'@",!"]`"`0O1"UMDQA>``I!M"*'"$<"/"* +M_F!X`C`0O0`@$+W_(_"U73,.'*])6$-%&"P<,#0`((6P('%@<>-XH'@,(1L" +M&$.`"(``B$/P(8A#4#"@<`,*XW`C>>!X`B$;`AA#0`A``(A#!"&(0P@AB$,0 +M(8A#("&(0T`AB$.`(8A#X'`#"@8BH!@C<9A)"/!._@8B,1P@'`PP"/!(_@8B +M,1P@'!(P"/!"_C`<(##9@M@Q`0```+R@`````@``U-&U.@20`WK`>2$<&P(8 +M0R`Q"'(#"DMR,QP@,]A^(QP@,YAR,QP@,QA_(QP@,]AR)QPL-P`@.'"Q';@< +M__=3_0`&``YX<,`9AQQ\20$@.'`*(@HY.!P(\!7^>$@Q'`HX0'@R,<<9,B"` +M70(W`B@%T0<B.!P(\`;^!S<&X`,H!-$#(C@<"/#^_0,W!)@!EX!Y,1PJ,0$H +M!=$((@&8"/#R_0@W!N`"*`31!"(!F`CPZOT$-S@<__=3_S\8(!PK,`GP]?J` +M!X`/`2@'T3@<__<X_S\8.!S_]R;_/QA;2`!X`"@(T5I(`'@!*`30!2$X'/_W +MZ_T_&'$@@%TP*!31(!PJ,`.0"?#3^A`A"$,#F0CPY/XQ'$=(<3$6(A@P"/"S +M_3@<__?J_C\814E],0IX`3$X'`CPJ/U!27TQ"'C'&5D@@%W=*!71/T@4.,!H +M*C`"D`GPK?H0(0A#`ID(\+[^&"(Q'%DQ,T@(\([].!S_][[^/QB'((!=W2@- +MT3$<+DZ',1HB9#XP'`CP?OT:(C$<.!P(\'G]&C<X'/_WM_XX&`<;.!P@."!P +M`PIC<"X<(#:P<`,*\W`#("AP`""H<.AP*1T@'`CPBOXQ'P$@``,(\(7^`"`H +M<FAR'$@!>/\@^3``*3?0<WHQ>AL"&4.!0R@Q,7(+"G-R`O!#_+-Z<7H8(AL" +M&4.&^<#*`0```+BB`````@``(#D+AX`'P`Z10PA#<'(#"K-R<WHP>@0A&P(8 +M0T`(0`"(0S!R`PIS<C[@``#H/@+`S!,"P.=*`L#F20+`V!@"P#Q(`L#H(P+` +MV"8`P'\L`,`\,P#`<WHQ>AL"&4.!0S%R"PIS<@+P#/RS>G%Z&"(;`AE#@`?` +M#I%#"$-P<@,*LW)S>C!Z!"$;`AA#0`A``(A#,'(#"G-RLWIP>H`A&P(80XA# +M<'(#"K-R]T@!>&`@`"D/T'-Z,7H$(AL"&4.10S%R"PIS<G%ZLWH;`AE#@4,@ +M,0W@<WHQ>@0B&P(90Y%#,7(+"G-R<7JS>AL"&4.!0W%R"PJS<@`F[G(!F"D< +M`!OX,"AS.!SX,&AS8W@@>!`Q&P(80QPPJ',#"NMS,!P(\-[]*1P4,3`<"/#9 +M_2YV;G:N=NYVZWNH>P6P&P(80_"]$+723&%X`"D&T(H<(1P(\)S\8'@",!"] +M`"`0O?.UE;`5FO\E734`(`>0R4A50RH8#AP1'#`Q!)$1'121!)@#D@CPK?W_ +M(5TQ%9@%\%3^!)D`(`AQ2'&_2/\A$Y#`>Q6:73%10[M*-QR,&+$=$I$L,16: +M$9&1`!"1N$D2`E$84#0@-\`'`"@/D0Z2<=JT2`8B*1@P,028#9$-'`*1##$( +MD#`<"/!2_!.8P'L!!XD/!-$,(8A#$YD$,,AS`2"X<0B8'B*GC/VD`0```+2D +M`````@``.<'5]ZD<`C`(\$#\(#4I'`PB(!P,E`CP.?P,F`PT0WH`>@PU&P(8 +M0_AQ`PH[<@$@>'($F"LP"Y#0X"AX!"APT`C<`"A:T`$H#=`"*&K0`RAIT%[@ +M!2AGT"4H9M`R*%C1`94`($;@`",&DP63`"`!E1C@`9D)&(Q&B7@*!@?52@8& +MF5(.2QR)&3`Q!I-*<V%&B7A*!@694@Y+'(D90#$%D\IR`3!I>(%"X]@&FP`@ +M\1@`X*'@,#%(<P6;\1A`,<AR)^`!F0D8C$:)>`H&!]5*!@:94@Y+'(D9,#$& +MDTIS84:)>$H&!9E2#DL<B1E`,063RG(!,&EX@4+CV-;G:G@2F*D<"/##^VAX +M("@"T@`A@!F!<6IX*1P",B`<"/"W^R@<2N`#X!G@&^`@X$G@!Y2X<JAX.'/H +M>'AS:WDH>1L"&$.X<P,*^W-K>2AY&P(80[AS`PK[<VAX^'+;YRD<!R(!X"D< +M`R(1F`CPD?O2Y^AX34EX<A"8#%`.FDA(@5P%*031#YG)>'IZD4(-T`Z:!2&! +M5'AZ#YG(<`^9`"`(<0^92'$/F00@2'`/F4IX`C(@'`CP;?L\21"8"%A`>`09 +M`C0*X`N8"?!W^(`'@`\!*`/1(!S_]ZS\)!AH>$49`C4"F$-X`'@-F1L"&$,& +M,&D:B$(`V2/G!)@@&DOA!)@,(<-X@'AK.X)7`0```+"F`````@``S2EK2@0E +M&P(80X`(@`"(0_`AB$.`,`29`PJ(<,MP!)@"(0-YP'@&(AL"&$-`"$``B$.H +M0P@AB$,0(8A#("&(0T`AB$.`(8A#!)D#"LAP"W$$F!9)@!@(\!O[!)@&(C$< +M##`(\!7[!)@&(C$<$C`(\`_[.WKX>1L"&$,@<@,*8W(S'"`SV'X$F0L<(#.8 +M<@_@-C,`P)Q)`L`(&0+`L$T`P.XN`L#H(P+`\#8"P-@F`,`S'"`S&'\+'"`S +MV'($G``@+#0)E"!P$IF@'`J0__<1^@$&"0YA<!6:`2"00/1*$G@00`+1"I@( +M\('Z8'@)G`HB`!F$'`$@('`@'.Y)"/#(^NQ)2'@$&3(@@%T"-`(H!M$'(B`< +M$9D(\+OZ!S0'X`,H!=$#(B`<$9D(\++Z`S0'E+AY,1PJ,0$H!=$((@>8"/"G +M^@@T!N`"*`31!"('F`CPG_H$--A+$)@<4`Z:UTB!7`4I!-$/F<EX>GJ10@S0 +M#IH%(8%4>'H/F<AP#YD`(`AQ#YE(<0^817`/F!T<0G@/F0(R(!P(\'SZ$)@H +M6$!X`C`D&"`<__?A^R08!)@K,`CP@_^`!X`/`2@'T2`<__?&^R08(!S_][3[ +M)!B]32AX`"@(T;Q(`'@!*`30""$@'/_W>?HD&'$@@%T5FO\A73%10[5*C1A: +M-3`H$=$H'`CP7/]%TG8[`0```*RH`````@``"C!I%Q`A"$,I'`CP;?L6(C$< +M<3&N2`CP/?H@'/_W=/LD&*5/AS=Y'"`<.G@(\#+Z.'@$&5D@@%W=*!+1*!P( +M\#W_$"$(0RD<"/!.^S$<H$A9,1@B&#@(\!WZ(!S_]TW[)!B<2`!X`"@0T(<@ +M@%W=*`S1,1R838<Q&B(H'`CP"OH:(BD<(!P(\`7Z&C0@'/_W7/T$F2`80!H@ +M.`29`PH(<$MP`YP`("`TH'#@<`.:`2$1<`.92'`#F8APR'`#F0AR2'(#F1$@ +M'#$``@CP#?M]3?\@*7CY,!@G`"DAT&-Z(7H;`AE#@4,H,2%R"PIC<@+PS?BC +M>F%Z@`<;`AE#N4/`#@A#8'(#"J-R8WH@>@0A&P(80T`(0`"(0R!R`PIC<BC@ +M8WHA>AL"&4.!0R%R"PIC<@+PK/BC>F%Z@`<;`AE#N4/`#@A#8'(#"J-R8WH@ +M>@0A&P(80T`(0`"(0R!R`PIC<J-Z8'J`(1L"&$.(0V!R`PJC<EI(`7A@(``I +M#]!C>B%Z!"(;`AE#D4,A<@L*8W)A>J-Z&P(90X%#(#$-X&-Z(7H$(AL"&4.1 +M0R%R"PIC<F%ZHWH;`AE#@4-A<@L*HW(#F0`D#'9,=@.9C';,=@.9S'($F!29 +M"/"(^@>804\`*`K0!Y@$F4`:`YGX,`AS>&D$(8A#>&$%X#I)%#$(:`0B$$/Y +MLDR8`0```*BJ`````@``_MC7J@A@*'@`*`S0-D@`BQ0H"-`3*`;0*4L0F!A8 +M!)E`&O<P!>`F2Q"8&%@$F4`:^#`#F4AS$YC`>\`'!]0Q'!68__>D^@.9`PJ( +M<\MS`YD`)!`Q(!P(\$GZ`YD4,2`<"/!$^B%(`&@`*!S1($B%:3]J`3<5X/\@ +M73`526!#1A@Q'!`Q*!Q@0PCP,/KS>+!X`2$;`AA#R0,(0P,*L'#S<`$TO$+G +MTP.9`"`(=DAV`YF(=LAV%[#PO<0F`,`R2`+`\#8"P.XN`L!_+`#`/#,`P`@9 +M`L#_2@+`)S,`P())`L`V,P#`P*(`@!10`L!84`+``*8`@/=*4&D!(0D'"$-0 +M87!']$I0:0$A"0>(0U!A<$?P2E!I`2')!HA#4&%P1Q"U!!SM20`@"&`(<4AQ +MZDJ(<0@Z4'`%(1`<"/!<^/\@)C#F26!#0!CD2J$`"#)04/_WVO@0O?ZU!AS? +M2+0``&@!*''1_R,F,]Q)W$H(.0AY<T.?&`$H:-':2==-`I$H>0CPM?G72&EY +M(C@%'!`U,#`$*3#10W@!>`0B&P(90Q%#`7`+"LU)0W")><U*`B,".A%P`2D- +MT:MS@WA!>&`B&P(90Y%#07`+"H-PQDD`(PMP$>`#*0[1J7.#>$%X8"(;`AE# +MD4,@,4%P"PJ^2H-P`2$1<`#@$W"Z27XQ2WJJ>Y-""="6QY)1`0```*2L```` +M`@``YR`)V@IR2G*#>$!X&P(80T`&@`\"\";^`I@(\%/]L4D`(RXQ"7H`D:I[ +M[7L!'"@<!O#A_:E(K4D`>0EH"'"G20@Q#U$T(<A5`>`4X`S@,!S_]V?_H$I0 +M:1$AR0$(0U!AHTG0#,AB`>#_,`AQFTT(-2]1.1P$X)E-"#4I60`I!-`P'/_W +M=/L`(RM1E$T`(`@U@0!I6``I`M$!,`@H^-L(*`71CD@`:``H`='_]S/__KWX +MM0T<!!P6'/7W-?X`D*``A$;_("8P8$.&1H5)B4H@`HY$`2&`&``M$M$!+@'1 +M`GE>X`%Y_B(10%O@?$IP1F%&"#)04/_W"?@`F/7W&?[XO6L'6P\!(II`^R/I +M"'I-<T]C0UL91#\`+@C0O&@]'(Q"`-*I8%Q<(D-:5`3@7%S_)6I`%$!<5``A +M`"(]'+QH".">7``N#M&>&'9X`"X*T0(Q`C*B0O39!>`>70`N`="L8`+@`3R4 +M0O?8C$("V64:!#4`X`0E`")"<45P!.!6&)Y=AQ@!,GYQHD+XV8Q"`=`$+0/1 +M0GD`*@#1`"$">=('T@\10P%QH>>PM51,54VA:&E@!#'@:`CPG/@@::A@L+W^ +MM08<2$@,'#XP`I#`>\`'`]4A'#`<__?+^B4<(#6H>0$H"-'H>2MZ&P(80P$H +M`M-H>@$H`=(!(/Z]0D\`(#APH1W3!"$%`0```*"N`````@``$\BW9[@<`9#^ +M]WO^>'`@'/[WE/XC'"`SV7XQ2$0X`7`9?T%P`3`''`CP3OR`"(``&"&(0R`A +MB$/`(8A#.1P(\%GXJ'H$*`G1Z'H&*`;1ZWNI>VA[&P(90P3P#O\"F,![P`<! +MU0`A`>`G2$%X`9@"\-_^(!P"\)__(!P"\,__(AP`(3`<!?"#^B!((4D`:``B +M0'D(<.EY*WH;`AE#,!P%\!3Y:7H*'#`<!/"B_A1(0'@`*`/0`"$`(`3PM/XA +M'#`<__=7^@A(6"$B.`E<$#`P-`"1@GLA><!['.````"E`(#@&`+`Z#X"P')- +M`,`V,P#`X!,"P``,`(#N+@+`$#<"P,0F`,``H`"`$$@"P,P3`L!)\0$``"0C +M'`;P;_RJ2`%H"0D)`0HB$4,!8``@!/#B_:9(`24`BQ0H#]`3*`W0I$@`>``H +M"="@2BPZ$&@$(0A#$&"@20AH*$,(8)M(`6B>2A%#`6`!:`(B$4,!8`%HP@V1 +M0P%@F4XP>``H`M`!(`+P0/\X'`CPH/M`!PG5"2`$\%C^'"`$\%G^D4B$8T1C +M`N"/2(5C16-P>``H!="-2`1@1&"$8,1@='"&2:PY"&@$(A!#"&"`2C`Z$&@( +M(0A#$&`$,A!HR0,(0Q!@$&A)``A#$&#U]^?^!/`U_@$@!/`W_@`@`N=Z2D0R +M$F@!*@C1`").;'C0`0```)RP`````@``;=,0UPS@`W@E*P31=4D\,0EY`7%P +M1T-X&!@",)H8`C**0O#;<$?PM0P<;DD6'"4B/#$*<*)X:TT?'(IPXGA$-<IP +M(GD*<0,B2G`!(2E@_R$F,69*04.)&&)*@`!,,A%0_O=7_N!X*'%N<:]Q\+UP +MM?\C)C,%'%U)6$-&�</#0@'`CP(_N`"(```3`A'`?P,_]42:@`3#$.4/[W +M.OYPO7"U_R,F,P4<4$E80T88-!P\-"`<"/`*^X`(@``A'`?P&_](2:@`3#$. +M4/[W(OYPO0"UBG@`*A+00DH@(P(R$W")>)%P`2%1</\A)C$_2D%#B1@\2H`` +M3#(14/[W"OX`O1"U!!PW2`,A`C`'\'G]_R`F,#9)8$-`&#)*H0!,,E!0_O?W +M_1"]$+4$'#%(?2('\+G]_R`F,"U)8$-`&"E*H0!,,E!0_O?E_1"]$+4$'"A( +M?2$'\'G]_R`F,"1)8$-`&"!*H0!,,E!0_O?3_1"]$+4$'"!(2B('\)7]_R`F +M,!M)8$-`&!=*H0!,,E!0_O?!_1"]$+4$'!=(_R(",DHP!_"!_?\@)C`126!# +M0!@-2J$`3#)04/[WK?T0O0](@7L`*0'0@'IP1P`@<$<```"C`(`44`+`?RP` +MP-"H`(`($P``Q"8`P)P8`L``I@"`Z#X"P#Q(`L"<20+`\$T`P/=)2&#+FT2A +M`0```)BR`````@``F3NN:@$@<$>PM0`E!!P!'2@<!_!S_B$<"#$H'`?P;OXA +M'`PQ*!P'\&G^(1P0,2@<!_!D_B$<&#$H'`?P7_XA'"`Q*!P'\%K^`2"PO7"U +MY4P%'"`<#AP0,`CP.OH`*$G1L#:P>/\H1=$@'*`PP7L`*1_00'L!*!S1*'AK +M>!L"&$/!!P;4P`6`#M=)0``)6@4@"N#!!=1*B0Z)`$`''#*)&,`/0``)6CP@ +M20B!0@'3`R``X`(@RDJ1:(%"&]`<(Y!@6$,$&2`<M#`(\`+Z`1P"(`3PT/L@ +M',0P"/#Z^0$<`B`$\-[[(!R\,`CP\OD!'`(@!/#<^W"]^+4&'$``0!@-'&`P +M0WH`>AL"&$-`!\$/MT@`>`'1`20`X`0<J"%)78Y"`-$$'*]/$#>X>Z!".-`! +M\+3Z`"@TT/CW]_L`*##1L#6H>/\H+-$!+`71J4D!(`AP`"!(@`#@`2"B2;QS +M,#%+>`IXP`<;`AI#!".:0T`/$$,(<`,*8"`#+$MP!M%*>(MX&P(:0X)#(#($ +MX$IXBW@;`AI#@D,3"DIPBW`!(/CW(O\P'/B]T"-80Y)+P!@@,`!Y@P>;#PTS +M`2"80(]+PQA(`(Y)`"H(6@/1C4F(0@#00`B80@#9&!QP1_"U!ARP-@<<L'B% +ML/\H<=``(`&0.!P(\&_Y?TE``00Q0!@!?$-\@4KUW5^:`0```)2T`````@`` +M@,-P&AL"&4/)!8D.`I%)!PD/45I]2@"K20!16IJ(_R/Y,YI#B0;)#1%#`*N9 +M@&Y),#%+>`EX&P(90TD'$M5#?`!\&P(80T$'9T@P,(-X0'@;`AA#0`:`#P`I +M`=H!)0/@`"4!X``E`""`!T(.!)+H!P`D0@\#DK!XA2,;`5E)6$,,,4`8`2') +M`D`8`&DL,`CP(?FA`$`8`8@$(@$TD4,#FB0&$4,!@$%X@WA@(B0.&P(90Y%# +M!)H10PL*07"#<`(LV=,X'`CP!OD`!@`.*AP`X"/@`IG_]VC_M'@`+`'0`2P% +MT0$$"0P@'/WW[OT2X`$<X2#```?PHOP!.!@H`-D8((4A"0$W2F%###*)&"$B +MD@&)&`AS`2`%L/"]`"#[Y_^U'AQ#>`0<`'@;`LH'&$-`"$``T@\00X&P"IT# +M"B!P!"<`*6-P!M&X0P(GN$,@<`,*8W`MX``N`=&X0PK@*!S^]SOX(7AC>,`' +M&P(90[E#0`\(0R!P`PHQ'"@<8W#^]UOX8W@A>`(G&P(90\`'@`^Y0P%##APS +M"A1()G!C<"PP"/"9^`,A@4,#T;Y#)G`S"F-P('AC>/\A^3$;`AA#B$,#F8D& +MR0T(0R!P`PIC<"@<_O=E^&%XHW@&(AL"&4.10X`'%N```+"``0!030#`2$4! +M`!XF`,",-@#`X$L!`/_W__^9&-ZV`0```)"V`````@``="O.IX!&`0#_9P`` +MUB@`P.8H`,!`#PA#8'`#"J-P`?!#^J-X87@8(AL"&4.`!\`.D4,(0V!P`PJC +M<"@<_O<<^*-X87A@(AL"&4.`!T`.D4,(0V!P`PJC<"@<_O<5^*-X87B`(AL" +M&4/`!P`.D4,(0V!P`PJC<`6P\+WSM8.P!!P(\"KX[$E``448(1PP,028!_`X +M_``G(!RN,`*0!)FY0@S3!)FX`,D;20`)&0L<0!E@,QIZ`G!:>D)P!^"X`$`9 +M(QQ@,QEZ`7!9>D%PN`!&&3!X<W@;`AA#P`<@U"`<!_#[__[W#_C`!PW5(!P' +M\/3_`@82#@"2,!P0,``B`",!(?_W#_\/X`*8!_#F_T```!D#'&`S&7HQ=%EZ +M<70#X#!X,'1P>'!T,WGP>(`A&P(80P`)``$",(A#\'`#"C-Q,WWP?`$W&P(8 +M0P`)``$",(A#`PKP=#-U`R^@TR,<8#,8>BAS6'IH<RA[:WL;`AA#P`<-U"`< +M!_"P_P(&$@X`D@`B*!P`(P$A'##_]\O^!>`C'&`S&'HH=UAZ:'<K>>AXZ7D; +M`AA#*WHG'+`W&P(90T`8*WOI>AL"&4-`&*!)*WR*:/\R83*2>AL"$!KJ>P$P +M``<:0Q()$@$`#Q!#Z',#"BMT*WWH?.I]&P(80RM^B6@;`AI#@!@K?^I^_S%A +M,8EZ&P)]EOUD`0```(RX`````@``LS+,^AI#@!@(&BD<$#$+?,I[`3`;`AI# +M$@D`!P`/$@$00\AS`PH+=/AX!)F(0@'8`2$`X``A(!S_]]3](1P$F/_W7_T& +M'$```!EH,"$<__<#_7Q*$&@!(0A#$&"X>/\H-M`".`,H,]+U]VGXAD:X>'5) +M!3!'!W\/N``(6`%H<TJX`!!8`FB41AC@<4@`:#H"$$,*!A(.$$,":`$@@`<% +M(]L#$!B80@78*'P3'"`S&')H?%AR"!P$,,$%R0W(!<`-8D;2!=(-D$+?T7!& +M]?<\^"`<__>?_"$<'#$`)2@<!_`3^R$<)#$H'`?P#OLP'`6P\+VPM0T<`1PP +M,00<*!P'\`/[(1PL,2@<!_#^^BD<(!S_][7^(1Q@,0`@!_#U^B`<__=X_"`< +M`/"%_@$@L+T0M00<6#`'\-+^`08)#B`<__?8_R$<9#$`(`?PWOH!(!"]_[6# +ML`0<!)@7'``H`]$]2;@`#E@&X#M)^``&FS@Q0!B9`$98(!Q8,`*0!_"O_@`& +M``X>*`+1_R`'L/"]_R@!T0`E`N`!,`4&+0X@'`?PGOX"!F@``!D2#@"2:#`Z +M'`29!IO_][C]+QPWX'@``!D!'&`QRWF)>1L"&4/*!PG5R@4@2Y(.T@!)!S@S +MTAC)#XD``^#)!8D.&TJ)`%%8L4(>V0,<8#.9>0"K&7$#'&`SV7G=[8B3`0`` +M`(BZ`````@``1]IR1P"K67$#'&`S&7J9<5EZV7$`JQEY`QQ@,QER`*M9>0,< +M8#/_-S\&/PY9<@`OQ=$H'`*9!_!I^C@<IN<``)`V`,`44`+`E*$`@"0G`,!< +M)P#``*``@+1%`0#PM00<`"`/'(^P_R$`D0&0`?`C^`$<^$KX`(`8B0!&6D`8 +M18@@'`?P+?X`!@`.")#]]ZK]``8`#@*0")C]]RW^`00)#`:1")C]]S+^!9#K +M21\@0%P!*`?1`IH!*@31`2\"T>=.?R5M`@6:$@H!T1XF_"4!*`/1!ID)"0D! +M!I$%F0`I"-`!*`+1X"`&F0'@W4@&F0%`!I$%F#!``Y`"F``H!=$%F0.8*4`( +M0P.0!>`%F`*:*$"1``.J4%#32,Y/07@0-P!X`"D"T8``.5@&X,I)P``"FD@Q +M0!B1`$%8!Y$\(2`<:#`'\&CX(1Q8,0Z1_R`'\-[Y`"8!);``.%@'F8A"$M,% +M+@'0!BX#T0:9"`<`U0<F*!P&F;!`"$`%T``C,AP`(2`<__?H_@$V-@8V#@XN +MXM,A'*XQ`">('`R0#9$!(;E`"Y&L20`E^`!(,4$8"I$NX*@`"ID)D`A8!YF( +M0B33"9@#J0A8"YD(0![0*QPZ'`$A(!S_]\#^!@8V#O\N&M`!F``H!=$P'`V9 +M!_"1^0$@`9`,F,!X_R@'T0F8"ID(6)I)B$)]AZ.U`0```(2\`````@``7B*L +M-P'3#)C&<`$U+08M#@*8A4+-V0$W/P8_#A`OOM,"F`$H'-$/(`$B$QP#F8-` +M&4`"T0$X^-42X``H$-L"!A(.`",!(2`<__>&_@$&"0[_*031#I@'\$7]`08) +M#@"1#I@'\#_]!09\2"T.+#`'\#G]`R&!0RG1_RTGT`$U*`;`#0`98###>8!Y +M&P(80\$''-5!!\`%A@[-#RD<")C]]]+\`2@2T2L<,AP!(2`<__=3_@`&P`T` +M&6`P0WH!>@(B&P(90Q%#`7(+"D-R#I@'\`G]`08)#HQ&!IE?30`@#@3(-069 +M`"D#T($`21G*>`C@<0T#T($`21F*>`+@@0!)&4IX_RH$T0"9_RD5T`"9#.!A +M1@_@2P`;&6`S'WI;>AL"'T/[!9L.FD(#T2(8H#(1<@+@`3D`*>W:`3``!@`. +M!2C/TZ`T('K_*`'1`",C<@$@#[#PO7"U!!P.'`#PJ_X!*`'0`B@!T0`A`.`! +M(0$@/DT`+FA@`-$`(2`<__=Q_B`<__?9_0`@:&`!('"]_R&P,(%PP7!P1_BU +M#AP!'1<<`"4$'"@<!_"T^"$<"#$H'`?PK_@A'`PQ*!P'\*KX(1P0,2@<!_"E +M^"$<%#$H'`?PH/@A'!PQ*!P'\)OX(1P8,2@<!_"6^"$<(#$H'`?PD?@A'"0Q +M*!P'\(SX(1PH,0H@!_">4URT`0```("^`````@``JLH2BH?X(1Q@,2@<!_"" +M^"$<,!P'\'[X%DEP`4`8("$&\/S^(1RD,2@<!_!S^"`<__>N_P`@_R$B&*`R +M`3`%*!%R^=.M(`55.1P@'/_WA/\A'&0Q*!P'\%WX#>"D10$`4$T`P`8"```/ +M`/__L(`!`(`X`0"0-@#``2#XO?BU!AP-'/3W8OT''#`<"#`'\"S\!!PP'`PP +M!_`G_`"0LB"`7?\H!M`P'!@P!_`>_*!"`M@D&@`L`]$X'/3W3/U#X`"89"%! +M0R`<!O##_RD<!_`B^`$<9"@$T@@<9"$(&BD<`>`I'``@!_`6^#@<]/<S_?)/ +M%"P&TR@<!_#W^SEHB$(@TQW@""P&TR@<!_#N^WEHB$(9TQ3@!"P%TR@<!_#E +M^V0H$=,,X#`<(#`'\-[[?2')`(A""-,H'`?PU_MD*`'3`"#XO0$@_.<"(/KG +M^+4/'`0<8#`'\,G[!1P@'%PP!AP'\,/[`3`Q'`;PU?\@'#`P!_"[^PPA:4,, +M&2$<-#$&\,K_.!P'\+'[(1PX,0;PP_\!(/B]$+7W]]3]`"@!T``@$+W&20`@ +M"'#&2,9*@7L3>)E",M"#<R`P8"$$(@$K"-%#>`1X&P(<0Y1#!'`C"D-P">`" +M*PW10W@$>!L"'$,B0P)P$PI#<$)X@W@;`AI#BD,-X$-X!'@;`AQ#(D,"<!,* +M0W!8*E+``0```'S``````@``3YU/C4)X@W@;`AI#BD,@,D)P$PJ#<`$@^/?R +M^`$@$+VI20AP<$?_(;`P@7`!('!'$+4$'+`P@7"B2`?P5_L!*`'1`"`0O0$A +M(!S_]]/Y$+W^M04<#"-80X`8#AP4'#0P!_!$^T```!EH,`)X0W@,(&A#&P(: +M0P`9`QPT,P*2T0>:")(`FP?;#H3*VD!;0B`SGT`70WH`$AEH,E-X$G@X,!L" +M&D/2!9<.`"D,V@?P'OMD(0@:A4KY`(D8`II2!](/D@")6`?@!_`1^V0A"!I_ +M2KD`.#I16$A#!QP,('!#`!DT,`?P`_M````9:#!#>`!X&P(80P&0P0<,('!# +M`!F$1C0P`QR8"(``FP?;#@7(V$!;0B`SFD`00T```!EH,$-X`'@;`AA#P`6$ +M#F!&.#``*0S:!_#;^F0A"!ID2N$`B1@!FE('T@^2`(E8!^`'\,[Z9"$(&EU* +MH0`X.E%82$.'0@'3*!S^O3`<_.?PM00<H#`!>H6P(!P`D0&I__>!_@(H<=`@ +M'/_W3O@!J2`<__??_B`<8#`&'`?PJ?H`!@`.(AP`(?_W5?\G'"@W/1T`*"#0 +M*1T$D0$H7M`"*%31.1P*(`;PJOX$F`?PD?H"D`"9`3"(0@C1.D@`>`$H!-$Y +M''T@P``&\)G^,1P`(`;PE?X"F%O@(!Q<,`?P>?H#*!#2,!P`%^7J`0```'C" +M`````@``NW7Q,`?P=/H#D`$H&]$M2`!X`2@7T2@<!_!J^@"9`3"(0A#1,1P` +M(`;P>/XH'`?P7_H!'"`<__<L^CD<?2#```;P;/XZX`.8`2@0T3$<`B`&\&3^ +M*!P'\$OZ(1P`\`'Z`1P@'/_W%?H`X`/@`N`#F`(HU]``(`6P\+TY'&0@!O!- +M_B@<!_`T^@"9B$((T0U(`'@!*`31.1Q](,``!O`^_C$<`"`&\#K^!)@'\"'Z +M*1P&\#3^`1P@'/_WZ_D!(-KG0$4!`(PV`,!@30#`'B8`P.Q%`0"`M0`A__<5 +M^P$@@+WE2$!H<$>`M0`A__<Y_8"]@+4`(?_W-/V`O?ZU!!S_]^__)1Q@-0`H +M!=`H'`?P\/D`!@`._KT@'"`P!AP'\.CY"C`Q'`;P^OTP'0<<!_#@^0HP.1P& +M\/+]T$E](DB(T@`*,``$``Q(@)!"`=,`(`AP*!P'\,WY`"A3T0&I(!S_]Y'] +M!AP"*%30(!S^]UW_,!PF'"PV`"@9T0&I(!S_]^G],!P'\+7Y!AP)T"D<`2`& +M\,7]<!XSX"`<__=[^3G@*1P"(`;PN_TA'#`<)N`P'`?PG_D"D"`<6#`'\)KY +M`IF!0B?0.!P'\)3Y!QP@'"@P!_"/^8="'=,@'!PP!_")^0`H%]`!J2`<__>T +M_2D<`B`&\)7],!P'\'SY(1P`\#+Y`1RW'NRS`0```'3$`````@``HHTO0,GG +M(!S_][/^`2@"T2`<`/`;^2@<!_!L^0`H`-%ZYP$@>.<!('!'^+4%')5(@&C_ +M,&$P@'H)>$8:*!T$'`?P6/F`&0$P(1P&\&G]*!P<,`0<!_!.^8`9`3`A'`;P +M7_TO'`PW%#P!+@39(!P'\$'Y`C`+X#@<!_`\^0$P.1P&\$[](!P'\#7Y@!D! +M,"$<!O!&_2`<!_`M^0`H"-$Y'`$F,!P&\#S](1PP'`;P./TI'&0Q`"`&\#/] +M`2#XO6]*$'!1<`$@<$=L2&Q)0'@)>``H`M&(`&Q)`N#(`&I).#$(6``&``YP +M1["U!!QG20;PM?P%'/\UOS7@(2`<!O"N_$`9:C``!``,84D&\*?\``0`#+"] +M^+5=200<,#$&\)[\``0`#(((`"8Q')(`!">"0@'0@1IY&@@843`%!/\A$3$M +M#"`<!O"*_($(B0"!0@'001I^&C`8*!@D,``$``Q,20;P?/P`!``,^+T0M4E) +M!O!U_(((D@``(8)"`M"!&@0B41H(&`0<5309(<D!(!P0,`;P9/P`*`#1`2`! +M`2TQ!O!=_``9/$D&\%G\$+WXM39.-4T`)&P^!2P$TF``,%K_]Y/_`^!@`#!: +M__>D_Z$``30D!B0.#BQH4.W3*T\`)#@W*4BE`%`X0%O_][__Y@"X425(4#@H +M&$"(__>W_P$T)`9W;@5K`0```'#&`````@``5F61_?$9)`X0+$A@ZM/XO7"U +M!1RD,`8<S'D'\&WX`"@'T>``*1R@,4I[`2,:0TIS`N`!&<`("!HQ'`;P<_P! +M('"]@+6D,`?P5_A``P`,@+T0M00<)"$T,`;PZ/HA'%PQ`"`&\%_\`2`0O1"U +M!!P('%@P!_!!^*!"`-E@'!"]``"P@`$`C#8`P!10`L"T10$`X%\```"PLP`0 +MUP$``'""`_BU`"7\]XK_]TP`(.!R('/V3F!S<'#V3R#@`)A!B0`I'-`!-2T& +M+0X!+0'107MQ<$%[T"-90\D9"7\$*0O1X7H!,>%R0'M80\`90(R`!P+5('L! +M,"!S8'L!,&!S:$;\]V/_`2C9T#5P^+W@2(!X<$>`M0/PZ_^`O;"UW$P%'*!P +MW$@!:'T@P``&\)K[H&#:2`"+%"@!T!,H#-'82`!X`"@#T,$@('$<(`+@R2`@ +M<1(@8'$!X*D@(''12`!X`"@#T.$@('$,(&!Q`_#._P`@`_"T_P$M`M%A>2!Y +M$N``(>%P`BW(2`O1#"&`>`/P?_\*(2@@`_"K_T`@`_"?_["]@'@,(0/P<_^P +MO?BUND@!:'TG_P`X'`;P5/NT30`FJ6B(0@'2KF#XO40:LT@`BQ0H`=`3*`S1 +ML4@`>``H`]#!("AQ'"`"X,D@*'$2(&AQ`>"I("AQJT@`>``H`]#A("AQ#"#* +M*PDO`0```&S(`````@``D7R3H&AQ`_!@_P`&#M7H>``H!=#N<*1(:7F`>`/P +M./^H:``9J&`#\''_S>>\0LO9J&@`&:A@Z'@!*,70`2#H<&EY*'D#\"3_ON>1 +M2#BU@'@"*`'1__>J_XQ(D#@&\$[_`"@6T?SWK/Z)30PU#.``F$&)`"D(T$1[ +M(!S\]R;ZN"!@0T`9__<__6A&_/>?_@$H[=`XO1"UA$Q@:$,<`=``(!"]@DCZ +M]^SZ`"@#T$%H86``(0%@`2(!(6!H^O<"^__W>?X!(!"]`"!P1_.U@;`%'/3W +M1?@&'/_WY?ZX(&Q):$,,,408LB``76E/_R@&T3AX`B@#T?_W5_MK24AP(1RT +M,0`@!O`1^P*8`"@!T`$B`.``(BD<(!S_]TKZ__>[_[AX`2@'V3AX`2@!V0,@ +M`.`"(/_W]/XP'/3W%_C^O8"U__>Q_E-)B'@!*`?9"7@!*038`B@"T`(@__?A +M_H"]N"-,25A#@+4,,4`8!O#(_E!)0`%`&("]N"-&25A#@+4,,4`8!O"\_DI) +M0`%`&!`P@+VPM0T<!!P$,`;PL?[#>(!X&P(80P`'@`\"*`?1N"`X26A###%` +M&"$<__<\_;"]#2@GT`G<!S@&*"O2`:,;7%L`GT0E(1TG%R,W*`[0!]P.*!/0 +M#R@/T!0H&]$!('!';B@%T-PH%=$$('!'`B!P1P,@<$?F?-$,`0```&C*```` +M`@``990M'04@<$<&('!'""!P1PD@<$<*('!'"R!P1PP@<$<-('!'`"!P1W!' +M<$=P1W!'<$=P1W!'\+4521@F<#F+>$IX'4\;`AI#LD,3'(('T@X30QP<3'`C +M"HMP`"&+`-P9HWAE>`$Q&P(=0[5#%4,K"F5PHW`"*?';!4\`(:`_BP#<&65X +MHW@;`AU#M4,50Q7@\$T`P#S#`0#@2P$``*8`@!10`L!_+`#`*#,`P*!&`0"S +MR```D#8`P$A-`,`K"F5P`3$$*:-PVMLJ20AP`2#PO2A(`'AP1RA(P(AP1_BU +M)DX$'/"(H$(:T//W._\%'/2`_/=H_2!,##0)X`"808D`*0700'NX(UA#`!G_ +M]_G[:$;\]U[]`2CPT"@<\_<G_P$@^+VX(Q1*64,,,HH8`1R`M1`<__=Q_0$@ +M@+VX(PY)6$,,,4`8@+6N,`;PVOV`O;@C"4I80X"U##*`&/_W<_J`O;@C!4E8 +M0X"U##%`&/_W9?J`O0``H$8!`#S#`0#J20`@"8KJ2A%`!RD2T@*C6UQ;`)]$ +M```%#@0&"`H,``(@<$<#('!'!"!P1P4@<$<&('!'`2!P1Q"U!!P!(``L`=$` +M(!"]VDG82C@Q!RPCT@&C&UU;`)]$`P8*#A(6&@`3BHM#%N`3BHM#"3,2X!.* +MBT,2,P[@$XJ+0QLS"N`3BHM#)#.))0S.`0```&3,`````@``?&SS;0;@$XJ+ +M0RTS`N`3BHM#-C,3@@#@`"``*-+0Q4E,8!"]$+4!)/_WJO\,(@<HP4D4T@&C +M&UQ;`)]$`P40!PD+#0`&(`C@"H`*X!0@!.`8(`+@("``X"@@"(`!X`J``"0` +M(`B`(!P0O;-)$#$(>P`H_-%P1[!)`+58($A@__?1_P`H`-$`O0$@`+VJ20"U +M5"!(8/_WQO\`*`#1`+T!(`"]L+6C30P<!B$#Q0$<HTBC2P@]`AVJ8,,8*V&; +M(]L`P!AH80`I`M$0'`7PO?X`+`'0[&`!X``@Z&`H'+"]E4B`M<!H`_!D_("] +MDTD`M8(@"''_]Y?_`"@`T0"]BT@@.`"(P`:-20'5`B``X`,@"'`!(`"]BT@! +M:(`B$4,!8`%H("(10P%@ATH8.I!I40R(0Y!AA4I0:HD"B$-08GU(`(L3*`G1 +M$!T!:`$B$@010P%@`6A"!1%#`6!P1WI*&#J0:8`AB$.087A*)#(0:$D$"$,0 +M8'!'=4D0M0$@"'``(@IQ<4P@8`0@(&#X]P+[`B`@8&9)`S@@.8B!$+V8M00< +M`-&8O?_WY_]@:/_W!O__]]7_86E@3"!H`/"B_6-*0#)0:PXAB$-08P(B`",& +M(0`@`)($\#']__>>_R!H`"@!T/_WA/\!(-SG44@@.`"+P`;`#G!'3D@@.(!I +M``QP1T]*,+43."*X`0```&#.`````@``B(1-T!1X`1Q.2^(<D@&Z.P`@&D-) +M2QJ!2$T#(B`U*G$*!@L$&PX2#@(L!-`#+`31"0()#BEQ*W$J<0`A`@(H>3Q+ +M`3$(.QE`$$,$*?;3,+T`M?_WU?\`!@`.`+V`M0`H`-&`O31)R&`#\*3[__?@ +M_0$@@+T!''T@@+7```7P^/\`!``,`-$!(`,P@`>`#@$A"$,P28B"`2"`O2Y) +M`""(@@$@<$?_("M)`3"(@@$@<$<I2`!H<$?PM2-,V#PC'$`SG$8C'(`SGD8/ +M*$W8`2E+V$,(P`>;`!P9P`].`:09(8C_)2T"`"@$T)=X"08)#C\"`>"7>"E` +M.4,A@!D<842,&2&(`"@$T%9X"08)#C8"`>!6>"E`,4,A@'-$&8@`*!W0"`;1 +M>``.B0:)#`A#&^```"`@`(`'`/__%%`"P`#!`(`@2P+`O`0``-C%`(``J`"` +M`,``@""0`)"0/@#`T'@I0(`&@`X(0QB`\+T``/=*$&@((0A#$&!P1_1*$&@( +M(8A#$&!P1_))%"`(8_%)%B!`,0A@2&"(8,A@"&%(88AAR&$!('!'^+7K3>A) +M`""L:`A@_?<;_^A.`R"P8P\G,!S'8^5)`"!`,0ABY$@&(@%H(!S_,&$P!?"$ +M_B`<_S`!,`%NWTH`D9%B`QQ`,YB,`*L1'!B``)C(8MM(VTF(8]A)'B!(K^;# +M`0```%S0`````@``]I_J8,`YB&+_-$$TT$E@:\`Q"&(H'`"+SDD/*`'1""`$ +MX!,H`=$%(`#@"B!(8\A*STC`.A!@STA08!`@R&.(8P0B,F)W8O_WG_\HB\5+ +M@#,3*`O1OTB`.`%K20A)``%C&&@'(0D""$,88`7@N4Q0/"!H`2$(0R!@OTB8 +M8$`)V&"Z21PY"&@00PA@LDD0,0AH$$,(8#\@KTE``4`Y2&/Z]\?[`2#XO:I) +M`"#`.0AB2&*(8LAB"&-(8XACR&-P1_"U`"0`*1W0`BL!T`,K$=$`(ZI/%A^= +M`'U=E4(!T+5"`M&:`-09'.`!,QL&&PX+*_'3%N"C2YH8$#K2>Z)+D@#4&`[@ +ME`"@2J(8`BL!T00Z`N`#*P+1%#H4'`+@G$JD&`0\FT^<3@`B`"H!T0$I"M`` +M*`+04P##6J-4I5RU5#MYG4(`V;-4`3(2!A(.!"KKT_"]\+6EL`(A`"`"J@;P +M4_P")0`H&-``(0`@B@`"J]08(EQ#``$P``8`#FY&\E($*/;3*QQ*'!0<`"%H +M1O_WF/\A!@D."2GGTP$A`"`"J@;P,OP!)@`H&-``(0`@B@`"J]08(EQ#``$P +M``8`#F]&^E($*/;3,QQ*'!0<`"%H1O_W=_\A!@D.#BGGTP(A`2`"J@;P$?P` +M*!G0`"0`(*$``JJ+&!E<0@`!,``&``YO1KE2!"A=&N8]`0```%C2`````@`` +M`G=4W?;3`*L:B"L<`2%H1O_W5_\!-"0&)`X++.;3`2$!(`*J!O#P^P`H&=`` +M)``@H0`"JHL8&5Q"``$P``8`#FU&J5($*/;3`*L:B#,<`2%H1O_W-O\!-"0& +M)`XC+.;3);#PO?BU2TA,3@`D!6D!X``L!]%*3S!HP1D%\/W]Z#!(3Q/@!2P( +MTJ``,%A$24$8!?#R_4`9*#`(X*``,%C!&07PZOT#,(`(@``],"I*H0!`,HD8 +M"&,!-`XLV=/XO0$<`"`%*0+3"2D!T@$@<$<+*0'2`B!P1P,@<$<XM0`B,DD` +MD@EX`"D&T`4A!2@`V0$<B`:`#@'@@`:`#L``$$,`D$`(0``"(8A#!"&(0P"0 +M`*@!,`0<!O#\^08AB$,A'`7P#?[_]]W[!1P@'`;P\?D8(8A#J0?)#@A#(1P% +M\/_]_/>'^@4<(!P&\./Y8"&(0ZD'+.````"C`(!`I@"`%%`"P("@`(#,$P+` +M`*4`@/S_`P!`J`"``,!@`"`@%%`!`#``JB@`P.LQ`,`>*`#`^B<`P,(G`,#\ +M)@#`>%`"P`0L`,"_*P``'S4``'\L`,!)#@A#@"&(0R$<!?#!_0"8P`6`#O_W +M??\$'`"H`C`%'`;PH/F`":$&B0Z``0A#*1P%\*[]`)@XO0(<XT@0M0%XXDP/ +M(``"8VG`0QA``"D&T`4A!2I.5<=6`0```%34`````@``&X^*K0#9$1P)!PD/ +M`>`1!PD/"0((0V!A$!S_]U+_#R,;`V%I``<`#)E#"$-@8=1,H&,0'/_W4__@ +M8Q"]^+7/2-!.!VD`),Y)H`#`,448*6@`+`;1S$DP:$$8!?`/_<HP$N`%+`?2 +M,%C'24$8!?`&_<`9"C`(X#!8Q$E!&`7P_OP#,(`(@``D,``$``P!!`A#*&`! +M-`XLUM/XO?"UA;`!(`&0_/?7^0`&``Z!!\D-`"7'!021J`"R20.001@"D0YH +MLTE$&"`<!O`E^0`*,08)#@`""$,A'`7P,_T%+0;29APP'`;P%_D`"0`!'>!H +M'P,H!]AF'#`<!O`-^0`)``$!,!+@*!P).`$H!]AF'#`<!O`!^0`)``$",`;@ +M9APP'`;P^?@`"0`!`S`Q'`7P"?UF'#`<!O#O^/`AB$,Q'`7P`/T%+0_2IAPP +M'`;PY/@`"2D'"0\``0A#,1P%\/+\__?"^@"0&>`)+0?9IAPP'`;PTO@`"0`! +M"3`(X*8<,!P&\,KX``D``2D'"0\(0S$<!?#8_/_WJ/H`D*8<,!P&\+OX8"&( +M0P"9B0=)#@A#,1P%\,C\IAPP'`;PKOC_(8$QB$,$F0A#,1P%\+S\`S0@'`;P +MHOC^(8A#(1P%\+/\;4D#F`A8`IIG23A#$&`!-0XM`-)AYP`@9TH!)H0`8Q@= +M:!19,QQ?=.^U`0```%#6`````@``[V<T$*Q"`-$`(SM#`M``(`&0`N`!,`XH +M[],!F`6P\+WXM0$@`"7P)@"0Z`9`#UI)0``(6EE)6D]```A:J0#,&6-X(7C_ +M(AL"&4/Y,I%#@@;2#1%#(7`+"F-PXWBA>!L"&4,*"0$'"0\2`0I#HG`3"N-P +M(WGB>!L"&D,2"1(!$4,+"N%P(W$%*`72H'CC>!L"&$.P0QG@01\#*0;8H'CC +M>!L"&$.P0Q`P#^`).`$H!MB@>.-X&P(80[!#(#`%X*!XXW@;`AA#L$,P,*!P +M`PJH!X`/`BCC<!32_/>^^`$H!]%@>*-X8"$;`AA#B$,@,`7@8'BC>&`A&P(8 +M0XA#8'`#"J-P`36$+9#3_R4!-2@<A"$`(H8`(TP.X"-IVP/\U%,<D@"Z6&)A +M6AR;`/M8HV$#'#-#(V$!,`$Y[M(H'(0B`"$5X!QIY`/\U`$D9`($0QQA76F; +M:8P`/EFN0@/1Y!ED:)Q"`M``(`"0!>`!,`(Q`3H!TPQ+Y><`F/B]``!_+`#` +M_"8`P`"A`(`$+`#`ORL``!\U``",:@+`UB@`P.8H`,!\:`+``*,`@.I)L+6* +M>4MYZ4P`(($`"1D+<`$PA"A*</C3__<M_["]@+4!8`$@`_`8^("]^+4!($`F +M`"0`)0"0&.#>2`1@`2`#\`OXW$BO`#@8VDE`:`0Y"&`!(`/P`?B*R<*;`0`` +M`$S8`````@``*'XV3==(UDG`60@Y"&`!(`+P^?\!-`(U`3[DT@`D0"8`)=!/ +M%>#.2`1@`2`"\.O_RT@H.`)J0&JI`'M8DT(#T<D926B!0@+0`"``D`/@`30" +M-0$^Y](`F/B]^+7"3@`D&2%)!`'.!?`-^P<<O4F@`$48*!P%\%+_``PY!`D, +M``0(0RD<!?!@^^`&0`^W24``"%JW20(U0``/6B@<!?`^_P`).0<)#P`!"$,I +M'`7P3/L!-$`LU-/_]X__^+V`M?_WS/__]W/___?L_?_WN_T`(/_WCOW_]\S\ +MID@0(0%@%"%!8$`A`6&D2.PA@6&A2F0A@#(184%I`B*10T%A@+T!')Y(!="> +M2<%B!"')0P%C<$><2<%B<$?XM0X<FTF;3YQ(SF"\:(!I`@82#@$@0RH`T0AP +MF$D(8`<A20(!(``#,FD&\!#X`"@`T?B]DTD(8))(`")`,`)@0F""8,)@`F%" +M88)APF$"8D)B@F+"8@)C0F."8\)CB4@"8T)C@F/"8X=(0#`"8$)@@F#"8`)A +M0F&"8<)A`F)"8H)BPF(('()APF$"8D)B@F+"8H)APF$"84)A(!S_,&$P@7IO +M2T`[66+`>AD<B&*R(`!;=4F(8;0@`%T(8F!ZB&)@B<AB_R%E2#LQ0#`!8/\A +M`S%!8-XA@6#4(<%@SR$!830A06$M(8%A*B'(9RD3`0```$C:`````@``W):( +M\,%A)R$!8B8A06(D(8%B(R'!8@%C06/_]T[[8$Q`/")A.(L3*`;1N'X#*`'0 +M74@#X%U(`>"`(,!#8&$!(DU(T@-`.`)@00Q!8%A)@6!)",%@5TD!85=)06%7 +M28%A5TG!85=)`6)227PY06)628%B54GD.<%B5$D!8_\AR#%!8U-(`&C_]SS_ +M1DU0($`U*&)02$!*4&(P'/_WC_HXBQ0H`=`3*`[1Z6L&(UE#R0A:!`I#.TF` +M,<IB$R@#T<IJ$@P2!,IB+4E`,0IH$@D2`0HC&D,*8`IHBPT:0PI@"FA`)C)# +M"F`E3Q0W$R@2T0AHR@400PA@*$HD,A!HT08(0Q!@)4I0:DD`B$-08CAH"0P( +M0SA@!/"6_`3P<?PA24`Y"&L*#)!#"&,`(/_WR?[_]QG[.&@P0SA@)$Y@/BY@ +M^?=C_A-(`(L4*`+182`@8`+@$R@`T29@`2#XY@``_"8`P'QH`L#HJ@"`!"H` +MP`0I`,#6*`#`YB@`P("A`(#`H@"`0*0`@`#PP2T5\-V],%`"P!10`L``(`"` +M`*@`@`"@`(``J0"``*8`@/#]___P_=__1A<``-(%``!6%0``.0X``*L*```= +M!P``CP,``%\"``!T)0#`"``(`#"UXDS_(Z)H`3.:0@W2X$L=:>T#_-1889EA +M`2"``A!#&&$!,J)@`2#16-ZT`0```$3<`````@``Q6Y6@#"]`"`PO?"UUD[` +M)?\GP"0!-[5@`"$`(/_WWO\!-+Q"^-,!(+5@\+W020`@R&((8P$@<$?-2Q"U +MV6H!)`!X!N`9*`'3`"`0O2(<@D`10_\H]M'98@`@P$,88P$@$+W#2Q"UV6H! +M)`!X!N`9*`'3`"`0O2(<@D"10_\H]M'98@`@P$,88P$@$+UPM08<#!P`*8BP +MMTT!T"`L`]D`("AP"+!PO2`A:$8$\/G_(APQ'&A&!?`B^"QPKD@`F1`X06$! +MF8%A`IG!80.9`6($F4%B!9F!8@:9P6('F0%C`2#?Y_BU!1P,'`0I`=.@!P'0 +M`"#XO9].$#YP:0"0!"(H'&E&!/#[_P@L0M.P:0"0!"*H&&E&!/#R_PPL.=/P +M:0"0*!P$(@@P:48$\.C_$"POTS!J`)`H'`0B##!I1@3PWO\4+"73<&H`D"@< +M!"(0,&E&!/#4_Q@L&].P:@"0*!P$(A0P:48$\,K_'"P1T_!J`)`H'`0B&#!I +M1@3PP/\@+`?3,&L`D"@<!"(<,&E&!/"V_P$@KN<"''5(""$!*@)H`=$*0`+@ +M"D`"(0I#`6@10P%@`2!P1P$<`2EL2`"U!M$"'`!H"$,08/_W,?\'X`%H20A) +M``%@:$@`:/_WE?T!(`"]8D@`:,`'P`]P1P`'7TD`#Z`Q"'(!('!'``1>20`, +M4#ES[TVN`0```$#>`````@``,8;H/8AC`2!P1UM(4#B`:P`$``QP1YBU!!P$ +M(@$<:$8$\&[_5$@`F5`X`6,A>0"K&7!A>5EP`)E!8P$@F+TXM4U-!!Q0/2AK +M`)`$(B`<:48$\%;_:&L`D"!Y`*L8<`$@.+T$(0`H0DH#T1!HB$,08`+@$&@( +M0Q!@`2!P1YBU!!P$(@$<:$8$\#O_.T@`F5`X@6(A>0"K&7!A>5EP`)G!8@$@ +MF+TXM31-!!Q0/:AJ`)`$(B`<:48$\"/_Z&H`D`"K&'@@<5AX8'$!(#B]+$D" +M(`A@`2!P1RI)@+4$(`A@]_=I^@$@@+TD20D@\#$(8R5)$"`(8$A@B&#(8`AA +M2&&(8<AA`2!P1W!'<$<=20IK`F!*:T)@BFN"8,EKP6`824`Q"F@"84IH0F&* +M:()ARFC"80II`F)*:4)BBFF"8LIIPF():@%C`2!P1PE*K#H1:`$C@T`90P@< +M$&!P1P5*P#I1:9,7@T`90%%A<$<``/PF`,``HP"`0*0`@%"E`(!T)0#``*@` +M@("F`(#H2(!X#R@!T@$@<$<`('!'Y4APM4!I`6C*!>1,W"5A&<MHT@V;`-(8 +M8UE.:)L9CFB;&1L$&D,"8``@8%%(8(A@R&!PO=E(W##`:``H`-#?YW!'^+76 +M2H$`4E@4:-)*45@.:`<"&^#32`!H(08)#CA#"$,`:`$AB0<S6UU4`0```#S@ +M`````@``.5D9X`4BT@-!&)%""=@<,`4<!?"+^P,A"0((0RD<!/";_R`<!##$ +M!>0-X`7Q!<D-P`V(0MW1^+WSM8>P!QSR]Z;\`)`(F+M*@0!56"IH%!P2!A(. +M!I*Y2E%8"6@B'062$@8)!@D.$@Z10D#0.1P(FB`Q!)&*<A,*RW((FA$"`Y$( +M*'':+"%!0ZM(`I$.&+!J`"AIT#@<'#`!D`7P1_N`!&+4^WBX>`$A&P(80\D# +M"$,#"KAP.1P4,0$@P$/[<`3P2_\(F`4H9M`(F`8H8]`X'07P+/L$F08P2W@) +M>`B:&P(90PD'"0_Z]YO\`"@%T0"8\O=2_``@";#PO0&8!?`6^P$A"0,(0P&9 +M!/`F_XI(`IE!6`2:TWB2>!L"&D.)&`*:@5!Q:`2:TWB2>!L"&D,$(YH:D@>2 +M#XD8<6"Q:(!*R1BQ8/%H`3'Q8!)HB0`)&?PQ`YL)!@D.&D,10P]@L6D`*1K0 +M`IE!6`#@/^!R:(D8LFB)&/)H"022`!(9T@72#1%#*6`"F@`A@5!Q8+%@\6!Q +M8C%BL6$TX!#@`IE`6'%H0!BQ:$`8\6@`!(D`"1G)!<D-"$-P8@$@,&(BX&%* +M$&@#F0::"$,00P=@!)D%FHAXRW@;`AA#!",9&HD'B0]`&,`8``31!<D-"$,) +MX%5*$&@#F0::"$,00P=@!9K0!<`-*&#XU8?5`0```#CB`````@``S;&G70"8 +M\O?)^P$@=>>PM04<`"3R][W[1TFJ`(E8"6A'2YI8$FC+!=(%T@W;#9-""-!$ +M2OPQ$F@K`@D&"0X:0Q%##&CR]ZK[(!RPO;"U!1P`)/+WGOL\2>H`41@):#I+ +M!#/2&!)HRP;2!M(.VPZ30@S0"APS21PR"6C2!M(.$4-J`1%#`R*2`A%##&CR +M]X;[(!RPO?BU!1P,'/+W>OLJ2>,`61@*:"A.!#:;&1MHW@83'=\&_P[V#KY" +M`]'R]V[[`"#XO1].-FC2!M(.,D-D`2)#`R2D`B)#%6":!I(."F#R]US[`2#L +MYX"U&$CY]SGY$$E(9("]%DL2`=(820``(%!2`2!P1["U%1P,'`0P!?`/^@]* +M*0&)&&(`BUH<`01V(PI#=HA:`3``!0`-B%(!(+"]```P4`+`7"<`P"AS`L`D +M)P#``*``@#"I`("4)P#`T$<!`!`!ZDJ`&$D`0%IP1_ZU`";G2;``"%@!D.9) +ML``(6`"0`9@$:`"8!V@EX.-(`&@Q`@A#(08)#@A#!6@!((`'!2')`R@8B$(2 +MV"@<%#`%\,;Y@QP,T``A*!PP,(%P*1P@,0H@B'(#"LMR*!SV]\O\(!P$,,0% +MY`W@!?D%R0W`#8A"T]'-3SAH,0((0R$&"0X(0P1H?01@&;H`D$(2V"`<%#`% +M\)SY@QP,T``A(!S`PQ5C`0```#3D`````@``U$EY+3`P@7`A'"`Q"B"(<@,* +MRW(@'/;WH?R]2$1I`6D"D;Q/)>`@!@`..$,`:`$AB0<%(M(#01B10A;8MDD( +M0`D'11@H'!0P!?!T^8,<#-``(2@<,#"!<"D<(#$*((AR`PK+<B@<]O=Y_"`< +M!##$!>0-X`4"F<`-R07)#8A"TM$@!@`..$,`:`$AB0<%(M(#01B10@_8H$D` +M(@A`"0=`&`$<,#&*<`(<(#(*(9%R"PK3<O;W4_P!F``A`6``F`%@DTA!80%A +M`38'+@#83.<`(/Z]6".125A#0!B`>0(H`=$!('!'`"!P1U@C6$.+2Y(`P!B" +M&-!I$N"(0@_1@6@`*0+00VA+8`'@06C184%H@&@`*0'0B&`$X-!B`N!`:``H +MZM$`('!'^+4%'/+W-/I8(0"0>D@O!#\,:4,`)`T8%N`P:``A,B(15`(<(#(* +M(9%R"PK3<OGWL?@B'#$<.!S_]\/_,!SY]ZGX*&P!."ADH``H&,9I`"[CT0$T +M)`8D#@0L]=,`("AA:&0`F/+W"?KXO?BU8DX`)0`G6"!@26A#A!D&(E@Y(!P$ +M\+'[IV#G8*=A)V$Z'``@9V2!`&$8RF'*8B$8`3``!D`Q``X$*`IR\],!(*!Q +MHH),(`)5(!Q0,`$U+08"<2)E+0Y!+4)QU-D`(/B]_K4!)_+WSOD!D%@@1TG, +MY'6,`0```##F`````@``(*''D'A#`"9%&+``*!C$::@90#`"D"_@(&@P,$-X +M`G@;`AI#41X+"@%P0W``*@'09&@AX"!H`"(R(0I4`1P@,0HBBG(3"LMR^?<Y +M^#(<(1PX'/_W2_\@'/GW,?@"F`!Z`"@#T&AL`3AH9`+@*&D!."AA*&P!."AD +M`"S-T0$V-@8V#@0NP=,!F/+WC/D!-S\$/PQ`+['3_KW_M2%(@;`/'*0X"IY` +M;/CWJO\%'`/1.!SY]P7X4^``(6E@J6#I8"EA+V#R]VKY`9E8(Q5*64.,&!1* +MD5V)`&$8RVD`*P'1S6$)X,EJ36"178D`81C):JE@D5V)`&$8S6(+F0`I%-!A +M;`$Q860!(1/@T$<!`"0G`,!<)P#``*``@`"M`(#\__\0U%`"P!PG`,`A:0$Q +M(6$`(9)=HAA`,A%R(6P!,2%D\O<Q^0(@,#<X<`,*>W`@:3(H`MA@;#(H!=D! +MF/_WYOX!(`6P\+T`(/OG<+4.'!D<!1Q!*A;86"!00^%*A!@&(B`<!/#&^@`@ +MH&#@8*!A(&%@9.!AX&(!(*!QIH)0-&5Q`"!PO0$@<+W_M08<UDB#L`!H^/<E +M_P0<<M`T(2`<!/!6^B$=J2```0T<(!@$\,O[`S!#>`%X`B(;`AE#$4,/'`=P +M.PI#<"@<!/"G_WD(20#!<`L*(1P<,0-Q`"<X'`*1!/#SU<+3`0```"SH```` +M`@``Y[C%S;'[)W)G<J=SYW,H'`3PE/\",$-X`7@,(AL"&4.10P@Q`7`+"D-P +M`9$H'`3PA?\!F?`BD4-`,8%P"PK#<"@<!/![_P=P1W`H'`3P=O\'=D=V`2`@ +M<&=P*!P$\&[_,8C!@"@<!/!I_W&(`8$H'`3P9/^QB$&!*!P$\%__HTD.:#&( +M@8$H'`3P6/]QB,&!*!P$\%/_L8@!@B@<!/!._P#@A>`QB$&"*!P$\$?_<8B! +M@B@<!/!"_[&(P8(@'/7W<OP&F``H5=`F'"`V)X0P>'-X!9D;`AA#``D)!PD/ +M``$(0S!P`PIS<"@<!/`G_P(P0W@!>/`B&P(90PL<DT,)!@D/""(10PD!&4,! +M<`L*0W`,F1`@`"D2T'-X,7@;`AE#"$,P<`,*<W`H'`3P!_\#,`%X0W@@(AL" +M&4.10Q'@<W@Q>!L"&4.!0PL*,7!S<"@<!/#T_@,P`7A#>"`B&P(90Q%#`7`+ +M"D-P*!P$\.?^!W8["D-V`I@$\.'^`2%)`PA#`ID$\/'Z!"`E'"`UJ'(#"@0A +M(!SK<O_W5_L`*`C1,B`'50H@J'(#"NMR(!SX]XG^`"`'L/"]^+552`!H^/<A +M_@4<<=`T(2@<!/!2^2D=J2```0P<*!@$\,?Z`S!#>`%X`B(;`AE#$4,.'`9P +M,PI#<"`<!/"C_G$(20#!<`L**1R=OS$T`0```"CJ`````@``$U![<!PQ#QP# +M<0`F,!P$\*WZ+G)N<JYS[G,@'`3PD/X",$-X`7@,(AL"&4.10P@Q`7`+"D-P +M`)$@'`3P@?X`F?`BD4-`,8%P"PK#<"`<!/!W_@9P1G`@'`3P<OX&=D9V+G!N +M<#@<!/!K_@$A20,(0SD<!/![^B`<!/!B_B1)#V@YB,&`(!P$\%O^>8@!@2`< +M!/!6_KF(08$@'`3P4?XYB(&!(!P$\$S^>8@`X"O@P8$@'`3P1?ZYB`&"(!P$ +M\$#^.8A!@B`<!/`[_GF(@8(@'`3P-OZYB,&"*!SU]V;[!"`<-*!R`PH$(2@< +MXW+_]Z[Z`"@(T3(@1E4*(*!R`PKC<B@<^/?@_?B]U%`"P-Q3`,#,$P+`Y%,` +MP/.UA;``)O'W0/\"D`"K&'X%FE@C]DE:0U48``<`*`W:`*L8?D`'"=48?H`' +M!M48?L`'`]4`(@"2`2-\X`0@_S``!@`.!!P#*`31`*L9?LD':=03X`(L!-$` +MJQE^B0=BU`S@`2P$T0"K&7X)!UO4!>``+`/1`*L9?DD'5-2A`&D8SVD`+T_0 +M*&D!."AA*6P^:`$Y*60`*`K0,!T$\,7]`S`!>$-X("(;`AE#$4,)X#`=!/"Z +M_0,P`7A#>"`B&P(90Y%#`7`+"D-PRDB`:0`H"M`P'!PP!)`$\*?]`2%)`PA# +M!)D$\+?Y,1Q*Q7PL`0```"3L`````@``"JBE`"`Q!"`#D8AR`PK+<@0A,!S_ +M]QSZ`"@)T3(AB%4#F0H@B'(#"LMR,!SX]TW](APY'`68__=?_#@<^/=%_0S@ +M_^<`*(C1`"X'T0`B`)(`(P`B*!P%F?_WJ_TH:0`H!=&IBE`U:'D`(OSW8?@" +MF/'WGOX`(`>P\+W_M8VP#9I8(P`@`Y"A20Z86D-5&`$H`M`/F``H;]">2)U) +M`'A@)`R1`"AIT!:8`"AFT!"K&'_`!PC4&'^`!P74&'\`!P+4&']`!UG5\?=N +M_@*0!";_-C8&#Y@V#@`H&M`#+@31$*L8?\`'<=43X`(N!-$0JQA_@`=JU0S@ +M`2X$T1"K&'\`!V/5!>``+F#1$*L8?T`'7-6P`"@8"Y!_X&AL`3AH9"EL`3DI +M9#QH`"@-T"`=!/`*_0,P0W@!>"`B&P(90Q%#`7`+"D-P#^`@'03P_/P#,$-X +M`7@@(AL"&4.10P%P"PI#<`Z8`2@*T2`<(#`!>$-X$"(;`AE#D4,)X$[A9.`@ +M'"`P`7A#>!`B&P(90Q%#`7`+"D-P`Y@A'`$P``8`#@.0`B`@<"`Q"I%+>`AX +M8"(;`AA#D$,`(A!#"'`#"DMP#)B`:0`H"]`('PF0`.`JX`3PNOP!(4D#"$,) +MF03PRO@$(`J9`PJ(<LMR!"$@'/_W,?D`*`K1`"(R(`)5"ID*((AR`PK[U:D- +M`0```"#N`````@``_D`;O<MR(!SX]V'\,APY'`V8__=S^S@<^/=9_`N8QVD` +M+P#0>N<`+@#04N<#F``H;=$/F``H:M`!(@"2`2,H'!":#9G_][?\8>#Q]Z[] +M`I#8X"AI`3@H8029#&@I;`$Y*60`*`K0(!T$\&[\`S`!>$-X("(;`AE#$4,) +MX"`=!/!C_`,P`7A#>"`B&P(90Y%#`7`+"D-P(!T$\%;\PWB`>!L"&$,!!XD/ +M`BEQT0`&``\(*&W1)AP@-C%X<W@;`AE#2`:`#P,H")%BT;=Z\WH;`A]#!2\! +MT`8O6M$@'!PP!Y`&D`3P,?P#(0D"B$,(0P:9!/!`^`B98"*10S%P"PH!(7-P +M(7`%+PW1`4JD.@;@@N#44`+`Z',"P"<S`,`1:0$Q$6$%X`8O`]'Y2E%I`3%1 +M804O`=`&+US1L7CS>/\B`3(;`AE#D4((TO%)N`!<,4`80#C!:@$QP6(1X.U* +MN0!<,HD80#D%D<EJ!"D%V0$AB0,(0P>9`_#^_P69`"#(8CD<(!S_]V?X`"@S +MT3(A"%4`X`#@)^`F'"`V<W@P>&`B&P(80Y!#`"$(0S!P`PIS<`R8@&D`*`G0 +M,!\''`3PQ?L!(4D#"$,Y'`/PU?\$(+!R`PH$(2`<\W+_]SWX`"@)T0`A,B`! +M50H@L'(#"O-R(!SX]V[[R$@">`V8!)G_]W_Z!)C[.O(&`0```!SP`````@`` +M@%N\#?CW9?O#2`!X@``H&,!I!)``*`#0'><"F/'WS/R^2`!X`"@"T1:8`"@9 +MT1"K&'_`!PC5&'^`!P75&'\`!P+5&']`!PS4$*L8?\`'#M08?X`'"]08?P`' +M"-08?T`'!=15(*F*0%T`(OOW9?X.F*AQ$;``(/"]+"&I2D%#B1B):@`I`]`% +M*`'0!B@!T0$@<$<`('!'P0"B2@D8B1B)>0`I`]&@2H``@!A!8W!'P0"<2@D8 +MB1B)>0`I$-&;28(`F4@):Q`8`"D'T9E)"7@`*0'0$"$"X'`A`.``(4%C<$?! +M`)!*"1B)&(EY`"D0T8])@@"-2`EK$!@`*0?1C$D)>``I`=`0(0+@<"$`X``A +M06-P1W"UATP`)2!X?DX!,``&``X@<&%XB$(9TR5P\&HQ:R(<TFB(0@;90!J0 +M0@K9`"#_]ZG_">`(&I!"`]D`(/_WQ_\"X``@__>J__5B-6.@>`$P``8`#J!P +MX7B(0AG3I7!P:[%KB$('V4`:X6B(0@O9`2#_]XC_"N`(&N%HB$(#V0$@__>E +M_P+@`2#_]XC_=6.U8W"]P"-80V-+,+4`(L,8`"!$`:08'!DD>0U<K$("T0$P +M!BCUVP8H`M$0!@`.,+T!,B`J[-L`(#"]_[4%'`\<'"&+L`*1_/<H^``@*')H +M<JASZ',H'0J0!/"H^@F0`WE<TZK:`0```!CR`````@``=+,"L,!X+AP;`AA# +M@0<<-@`I"]K`!PG5,!P$\)GZ`2$(0S$<`_"J_B(A`I$L'"`T('AC>"D<+#$; +M`AA#")``!@`H!Y%4VPF8PWB`>!L"&$,`!X`/`B@9T0F80W@`>`*9&P(80T`8 +M-DF):/\Q83$)B8A""]D)F(!YP`<'U#`<!/!G^B`A"$,Q'`/P>/XP'`3P7_H& +MD,`$`M4I'"@Q`^`'F`3P5OH!'`F8@'G`!R#4"9C#>(!X&P(80P`'@`\"*!?1 +M'T@)(H!HD@&`&(!K`"@/T`AX2W@;`AA#P06)#@,I!]G`!P74!IB`(0A#,1P# +M\$;^"9A#>`!X&P(80QPP`00)#`L*H7#C<`0O)-P)F,-X@'@;`AA#%N```#!0 +M`L`<)P#`)S,`P"AS`L`T:`+`0*(`@"AT`L!_+`#`_"8`P,1J`L`44`+``@>2 +M#P(J!]$`!@`/""@#T8@<H'`#"N-P"9B`><`'"]4P'`3P\_D#(0D"B$,A(0D" +M"$,Q'`/P`/X)F,-X@'@;`AA#`0>)#P(I#M$`!@`/""@*T3`<!/#:^0,A"0*( +M0PB920:)#PD"!.`P'`3PS_D!(4D#"$,Q'`/PW_TP'`3PQOD!(8D"B$,Q'`/P +MUOT"'`B8*1PP,4`&@`\#*`61#]`%+P/0!B\!T`<O!-$%F0`@B'`*(%WA`3>G +M<CL*XW+#.Z\2`0```!3T`````@``;4O<P!3@+"#X27A#0!B`:@`H#=$%F8AP +M"B"@<@,*XW(H'/CW6OGR2<AK`3#(8TGAIW(["N](XW(`:``H<=`)F(!YP`=M +MU0F8`WG`>!L"&$.`!V;5"9@!'`PQ`"#_]Z;^Y4D!D`A<`"@\T.%(#":`:@`H +M&]`,(/[WW/X`*`S0!#`$\&CY`S!#>`%X("(;`AE#$4,!<`L*0W`S"J9R#"$H +M'.-R_O?=_0$HH]$EX0&8_O??_@`H#-`$,`3P3/D#,$-X`7@@(AL"&4,10P%P +M"PI#<#,*IG+C<@&9*!S^]^W^`2B'T0GA+"#!27A#0!B`:@`H"-`P'`3P+OD! +M(4D#"$,Q'`/P/OV]2`!X`"@$T`0@`PJ@<N-R!"<Y'"@<_O>A_0$H`.`"X`#0 +M9.?FX`V8`"ACT`V86".N25A#I#%`&`20@'D"*&[1"9C#>(!X&P(80P$'B0\" +M*671#9E8(Z5*64.D,HX8``8`#U`V""A;T0B9`"`*!Z-)$@^)7``D`"DMT`$I +M,-`"*3/0`RD$T0BK&7[)!P#5`2`(JQE^"0<(U1E^20<%U1E^B0<"U1E^R0<! +MU``H`-$!)``H(M$(JQA^P`<<U!A^@`<9U!A^``<6U!A^0`<3U``B`)(!DA3@ +M"*L9?@D'V-76YPBK&7Y)!]/5T><(JQE^B0?.U<SG/.``(0#@`2'<CR%.`0`` +M`!#V`````@``F:-B?0"2`9$`(P`B*1P-F/_W4O@`+''0!)@!(H&*<'G[]Y#[ +M:N`(X``B`",I'`"2`9(-F/_W0/CNYVY(0'@`*!K0"9C#>(!X&P(80P`'@`\" +M*!'1`B]QW0B88"&(0R`P_R$!,2!P`PH*0Q`<,1QC<`/PB/P')V'@8T@%+P.0 +M<M%;2-PP@&H`*!30"9C#>(!X&P(80P$'B0\"*0O1``8`#P@H!]$)F($=6$@& +M(@/PPOH`*`S0!9D`((AP"""@<@,*XW(H'/CW"_@`(`^P\+U)2%(P0'D!*!K1 +M"I@$\#WX`7Y#?AL"&4,("0$XPA<2#1(8$A,2`X`:``0`#`#@_.%$2A%B``%0 +M8CM)`"!2,4AQ,!P$\"+X`R$)`HA#"$,Q'`/P,?QC>"!X8"$;`AA#B$,@<`,* +M8W`!("AP+DD`X.O@"&D!,`AAA#%(:0$P2&&@>.-X_R$!,1L"&$.(0@?2)4E< +M,0AH`3``X&_@"&`3X"%)7#$(:`0H"-DP'`/P[O\!(8D#"$,Q'`/P_OL:20`@ +M7#$(8%PYB&$72I!I01R181%JB$(+V3`<`_#8_P$AB0,(0S$<`_#H^P]*`""0 +M829Z8WH;`AY#\`<TU`>8`_#&_PY)!#F(0G'0`YF(0F[0`2$Q0R%R"PIC<@9X +M0W@;`AY#$N```"AS`L`P4`+`="4`P'0S`,"+.;K[`0````SX`````@``7KI@ +M("TS`,`<)P#`3$T`P#1H`L!`J@"`_R'Y,8Y#!G`S"D-P!Y@#\)G_`2$Q0P%P +M"PI#<.)*.1P0:P$P$&,H'/[W$OP!*`#0U^57X08O9-'<2(!J`"@`T2[G"9C# +M>(!X&P(80P$'B0\"*?71``8`#P@H\=$)F($=TT@&(@/PW/D`*.G1SDA2,(!Y +M`2@:T0J8`_!D_T-^`7X;`AE#"`D!.,(7$@T2&!(3$@.`&L=*``0`#!%B``%0 +M8@`@`.!DX,!)4C&(<3`<`_!)_P,A"0*(0PA#,1P#\%C[8W@@>&`A&P(80XA# +M('`#"F-P`2"T22AP2&D!,$AAA#&(:0$PB&&@>.-X_R$!,1L"&$.(0@;2K$E< +M,4AH`3!(8!3@7>"H25PQ2&@$*`C9,!P#\!C_`2&)`PA#,1P#\"C[H4D`(%PQ +M2&!<.<AAGDK0:4$<T6%1:HA""]DP'`/P`O\!(8D#"$,Q'`/P$ON62@`@T&$F +M>F-Z&P(>0_`')-0'F`/P\/Z428A"`M`#F8A"!-&22<AH`3#(8)+F`2$Q0R%R +M"PIC<D-X!GC_(1L"'D/Y,8Y#!G`S"D-P!Y@#\-/^`2$Q0P%P"PI#<']*.1R0 +M:P$PD&,H'/[W3/L!*`#0$>61X"P@?TEX0T`8@&H`*&/0"9@A>&-X!C`;`AE# +M"0<)#SH<^?<J^``H.=#'Y196`0````CZ`````@``JE+>G0J8`_"K_L-X@'@; +M`AA#`0>)#P(I+M$`!@`/""@JT3`<`_"<_@,A"0*(0PA#,1P#\*OZ8W@@>&`A +M&P(80XA#('`#"E]*8W`!(2EPN`!D,A%8`3$14%I)A#$*6`$R"E`Y!@D.*!SX +M]R3\`2A'T`69`""(<`O@('AC>!L"&$-`!H`/`R@+T4Y)"&P!,`AD"B"@<@,* +MXW(H'/7W=?DOX#`<`_!=_@$A20,(0S$<`_!M^DM(`'@`*`30!"`#"J!RXW($ +M)T=(`'@`*!+1,!P#\$?^``8-U0$O`]`"+P'0!"\'T4%(N0`(&`%J`"D!T0LA +M`6(Y'"@<_O>Y^@$H`-!^Y`$@YN4Y2L``@!@!8@$@<$<V2<``0!@`:G!'-$K` +M`(`806(!('!',4G``$`80&IP1RU*@`"`&`%B`2!P1RQ+&FM*0UEK41@J2H`` +M0#*`&`%@`2!P1R9*8#(0:)$&"$,08"-(0#!!:H!J`2!P1R!+0#,::@)@FFH* +M8`%H20!)"`%@`2!P1QM)`2`(8'!'&DD`*`30`2@$T`(H!M$"X`$@`.``(`A@ +M`2!P1P`@<$<.24`Y2&,!('!'$$@`:'!'#D@`:7!'```P4`+`,'0"P#UH`L!` +MJP"`2$T`P"05`L`H<P+`+3,`P'\L`,!`H@"`@*``@$"F`(``J`"``*4`@.Q* +M$6A*T.MJ`0````3\`````@``LZH`[0A#$&`!('!'Z4H4.E%I@4-180$@<$<. +M*"O2`J,;7%L`GT0```<)"PT/$1,5%QD;'B$D"B!P1Q0@<$<W('!';B!P1]P@ +M<$<\('!'6B!P1W@@<$>T('!'\"!P1_\@:3!P1_\@X3!P1X<@@`!P1RT@``%P +M1P`@<$<!!@D6#BD"VLU)"%QP1P`@<$=PM0P<%AQ%!VT/\/>6_LA)"XC%2>PQ +M"AQ`,@`K`]"58`QB#F$`X`QB1"$18/#WBOX`('"]"AR\2>PQ"F!(8+U)`2`( +M8'!'P`:[2<`."&`!('!'N$D(8P$@<$>V24`QB&$!('!'M4BS20A@`2!P1P`H +M`M&P24A@!>"N2@0R$&@!(0A#$&`!('!'`ARJ2$`X@F+!8@$@<$<0M:E(JDP! +M>/\@(HCY,,!#$$``*0#0*#`@@/SW^OZC>&%X&"(;`AE#@`?`#I%#"$-@<`,* +MHW`@:)Y)B&,0O0(A`2@%T9=*0#I0:8A#4&%P1Y1*+#H0:`A#$&!P1Y!*4#H0 +M:%$,"$,08'!'C$J`.A!K40R(0Q!C<$="!],.B4J`.IL8&F@)!PD/``<`*`3: +M$`<`#PD!"$,#X/`@`D`*0Q`<&&!P1S"U@TT`)"E=(!S_]^/_`30D!B0.""SV +MTS"]\[6)!XD/@[`!D0`DR0$"D7I(I0`O&#AH>4@I&(X<,!Q4TF\0`0````#^ +M`````@``1T*^4`/PL/S_(8$QB$,"F0A#,1P#\+[X<DA`63A@`30.+.?3`9D` +M(&].8"1-`0$!B1F+>$IX`3`;`AI#HD,J0TIP$PJ+<(MY2GD;`AI#HD,J0TIQ +M$PJ+<8MZ2GH;`AI#HD-`,DIR$PJ+<HM[2GL;`AI#HD-`,A,*2G.+<R$HUM/_ +M(`$PA"$`(D]/#^`[:=L#_-13')(`LEAZ85H<FP#S6+MA`2.;`@-#.V$!,`$Y +M[=).3F`@<GBS>`.9&P(:0X)#`2D)T3"(P`6`#@0H!-E`,G)P$PJS<`3@*D,0 +M''!P`PJS</SW*?ZS>'%X&"<;`AE#@`?`#KE#"$-P<`,*/$BS<`!H-TF(8P.9 +M,DX!*0O1,(C`!8`.!"@&V7!XLW@;`AA#H$-`,`7@<'BS>!L"&$.@0RA#<'`# +M"K-P_/?^_;-X<7B`!QL"&4.Y0\`."$-P<`,*LW`P:"!)B&,%L/"]`2@!T04@ +M`.``("%*_R,1B/DS@`;`#9E#`4,1@!9)"HB:0Q!#"(!P1_"U&$X$)0`BP`=! +M#Y``@!E#>`1X`3(;`AQ#K$,,0R,*!'!#<$(J\=/_(`$PA"(`)`1/+>`4I0"` +MM"<`P#10`L!`I@"``*,`@/\!``!_+`#`&"<`P`"B`(`<)P#``*$`@(QJ`L!\ +M:`+`%"<`P!`G`,`[:=L#_-1C'*0`-%G0?6)W`0```/S_`````@``@Y=A>WQA +M7!R;`/-8NV$!(YL"`T,[80$P`3KMTO)(`HBJ0PI#`H#Q2`*(JD,10P&`\+UP +MM0T<!!P6'/_WI?\H'/_WCO\Q'"`<__?._G"]`"!P1P$H`=$*('!'`"!P1P`H +M`=#C20AQ`2!P1Q"UX4P`("%H(FB10OS0$1Q"'`,<$!P$*_;9$+W;2A%H$&B( +M0OS90!H"*/G9<$<(*0'<`"D"T0`@P$-P1]1+&&O42A*($@000P4B$$,88\]( +M`3F`.`%B`"!P1\Q*$&L&(8A#$&,`('!'!"``*070QTHP,A%H"$,08`/@Q$H1 +M:X%#$6,`('!'P4B`.$%B`"!P1[](@#A`:G!'``:\2@`.@#H`M5%K`QP!()A` +M`R,;`@%#&4-18[9*0#(1:`A#$&#\]U+_`"``O0`&L4H`#H`Z46L#'`$@F$"! +M0U%CK$I`,A%H@4,18``@<$>I20`@0#$*:``A$QS+0-L'`-4!,`$Q("GWVW!' +M^+4%'`@<H4D`)PR(%AP`+`/0`1P@'`+PIOZ:28AAF4@0(D`P`F``+!'03V$( +M(0%@:`>52T`/@#L9:Y5*@``26`I`,0<)#X%`$4,('!AC`"#XO7"U`"4`*@#0 +M"24()``J`-`@)(E(0#`":`$F\4(%T`@&``XQ'(%`$4`*T"@<!.`Q'(%`$4`$ +MT`$PH$+XTP`@P$,H]C@I`0```/@!`0```@``L?P&Q'"]][46'`P<2QR"L`+1 +MV$,%L/"]\/?;^P*9P",D!B0.`)!X2@`@64.-&`&40@$2&3%<JA@!,`8H$7'W +MVP,@$"P`VP0@!P9L2C\.0#H0:`8<""&(0Q!@!2``\/WZ`"`!(V5)NT!"`1(9 +M$@D2`:\8!#<$S\IA!,\*8@3/2F(Z:(IB&AP!)W\"`D,Z0\IB`3`&*.C;64I` +M.A9@5TA`,`%H`9P!(Z-`&4,!8`!H`IE32HD`!#)04`"8\/>/^P`@J.<0M4L< +M`='80Q"]P"-80PP&34LD#@`AP!A+`1L9PQ@;>5-4`3$&*??;`"`0O?^U#QP& +M'!P<@;#P]VO[!1Q"2;``"5C`($!*<$.`&"`I*=$`+S70`"0#F>1#`2D<T3=) +M0#$):'(`-DL!)II:!.`S')-`"T`#T`$R("KXVR(<%AQ3'`G0`"%+`9L9>ES# +M&`$Q!BD:<??;$^`H'/#W/_L@'`6P\+T`+PO0`YD`*0C12P$;&7I<PQ@!,08I +M&G'WVR8<'DH$'4`Z$&@''`@AB$,08`4@`/!B^@`@02(82=(`",S+80C,"V(( +MS$MB(QPD:`0SC&($'!1#S&(0R\QA'!P;:`0T"V((S$MB",R+8B$C&P$#0\MB +M`3`&*.';"$I`.A=@*!SP]_OZ,!RZYP``%"<`P!@G`,#\)@#``*8`@$"C`(!^ +M^6BW`0```/0#`0```@``D9AE1S10`L`\+`#`Q&H"P!"U`"3`(V-#`@8[3!(. +M`"`;&40!I!@<&21Y#%0!,``&``X&*/73`2`0O?>UA+`6'`0<,D@`(@`E`Y4" +MDH!J`"<`*`+0T$,'L/"](1PH'&I&__<Q_P8B,!QI1@+P\?L`*`31(!S_]TG^ +M(!SLYR1*4&H,(0A#4&(%F``H#=$C'``B,1PH'/_W+?\@'/_W3?X;26`<!#$( +M@!+@&$@$,`&(@QP8B!A+"A@:511+0AP&,QJ`1!@C'#$<`"(H'/_W$O\/20`@ +M!#$)B``I`=`!(@*2"TH&,A*(`"H`T`$G"TH#F]-C`IM]`)L`*T,-!"M#&$,0 +M8R`<__?-_;3GQ&H"P#!0`L!`(`"`7"P`P$"C`("PM00<#1S\]]K[]TB$8O9( +M(#`%<0$@L+VPM00<#1S\]\[[\4B$8O!((#`%<0$@L+WN2!"U`(L3*`'1[4P$ +MX!0H`='L3`#@[$S\]\K[`^"A>/_WXO\$-""(_RCXT:%X_RGUT>9,_/>Q^P/@ +MH7C_]\C_!#0@B/\H^-&A>/\I]='@2A!I!R%)`XA#$&'=2A`R$&@%(4D#"$,0 +M8!"]U$@0M0"+$R@!T==,!.`4*`'1UDP`X-9,_/>5^P/@H7C_]ZW_!#0@B/\H +M^-&A>/\I]='03/SW?/L#X*%X__>3_P0T((C_*/C1H7B3='I'`0```/`%`0`` +M`@``7.QF"?\I]='%2A!I!R%)`XA#$&'"2A`R$&@#(4D#"$,08!"]$+7\]VO[ +MN$@`BQ,H`='`3`G@%"@!T;],!>"_3`/@H7C_]WC_!#0@B/\H^-&A>/\I]=&Z +M2,!X`"@"T/_WK/\!X/_W=/^V2`)H@"$*0P)@`F@*0P)@`F@*0P)@L4HD.E!J +MB$-08@$@$+T!('!'`2!P1P$@<$=!`*M*"1@0M8P8ITD#/$AP__=2__SW'?NA +M>&(@__<T_V%X8R#_]S#_(7AD(/_W+/\!(!"]G$D0M4APQ"@`V3@@G$D(&!`X +MP'N;24,`&!A$&/_W9__\]_WZH7AB(/_W%/]A>&,@__<0_R%X9"#_]PS_`2`0 +MO;"U!!P-'/SW]?HA'&L@__<-_RD<;2#_]PG_L+VPM00<#1S\]];Z(`1T20`, +MB&)S2"`P`'DH<`$@L+T(M?SWVOII1HL@__?J_P"K&'@(O1"U!!S\]\_Z(1R+ +M(/_WY_X0O;"U!!P-'/SWQ?HA'(P@__?=_BD<D"#_]]G^L+T(M0"K`"`8</SW +MMOII1I$@__?&_PB]:$A`>'!'`2!P1V5(`'@'*`'1`R!P1P,H`=$"('!'`2@` +MT7!'!"@`T7!'`B@!T04@<$<&*`#1<$<%*`'1!R!P1P`@P$-P1P$@<$>PM00< +M#1S\]W;Z14B$8D1((#``>2AP`2!C+3!Y`0```.P'`0```@``T5"B0;"]`2!P +M1P$@<$<!('!'`2!P1P$@<$<!('!'$+5-3/#WT_H`*`/1\/?1^@`H$M!%2E`R +M$&@((0A#$&#\]U;Z`^"A>/_W;?X$-""(_RCXT:%X_RGUT0$@$+U](,```3C] +MTG!'%"-80T$>`AP('``J^M%P1W!'<$?^M0T<!!P*'`$<`"`!JP/P_/C`)@`H +M'=``JQAZ`"@.T!AZ_R@+T&E&'B#_]Z+_`*L8>!EZ,$`!0QX@__<V_@"KV'D` +M*`;0V'G_*`/0V7D?(/_W*_XJ'"$<`2`!JP/PU?@`*$;0`*L8>@`H#M`8>O\H +M"]!I1B`@__=\_P"K&'@9>C!``4,@(/_W$/X`J]AY`"@GX`#!`(`44`+`XBX` +MP+(N`,"*+@#`YBP`P,"B`(#F,`#`MC``P(HP`,"R+`#``BX`P((M`,`6+0#` +M?"P`P"2H`(!&,0#`ZS$`P'`Q`,""+`#`!M#8>?\H`]#9>2$@__?<_2H<(1P" +M(`&K`_"&^``H%=``JQAZ`"@&T!AZ_R@#T!EZ(B#_]\G]`*O8>0`H!M#8>?\H +M`]#9>2,@__>^_?Z]\+6%L`0D*"`$J?_W&O\#J2D@__<6_P*I*B#_]Q+_`:DK +M(/_W#O]I1BP@__<*_P"K&7P`((\&OP[O2X$`[DI`,\T80#.*&,L8&XC!!\D/ +M`-`;"IL&FP[(1,KG`0```.@)`0```@``;QW:Z9]"*=$3B``I`-`;"AX&`*L; +M>C8.GD(?T2N(`"D`T!L*'@8`JQM[-@Z>0A71$HP`*0#0$@H`JQMX$@82#II" +M"]$JC``I`-`2"A$&`*L:>0D.D4(!T00<!.`!,``&``X$*+_3(!P%L/"]\+4$ +M',U(#QP`BQ8<F[`3*`'1RTT$X!0H`='*30#@RDW*2`$O`=&Y'P'@`"')0X&! +MQTF(:@(BD$,9(A!#B&+$2B0R$&@;(8D!"$,08/SW)OD#X*EX__<^_00U*(C_ +M*/C1J7C_*?71`"PVT0(N!=`#+@/0#B\!T;=-#>"W2$!\@`:`#P$H`=&U307@ +MM4T#X*EX__<@_00U*(C_*/C1J7C_*?71ID@`BQ,H`=&N303@%"@!T:U-`."M +M30(N`=`#+@719"'X(/_W!OWV(23@9B'X(/_W`/U[(1[@F$@`BQ,H`=&C303@ +M%"@!T:)-`."B30(N`=`#+@K19B'X(/_WZ_R#(?L@__?G_"DA4B`%X&8A^"#_ +M]^#\PB'[(/_WW/P#X*EX__?8_`0U*(C_*/C1J7C_*?71@D@`BQ,H"=%((?\@ +M83#_]\C\,B'_(&,P__?#_`(N`=`#+A+1&:D((/_WL_T8JQAY""$!0P@@__>T +M_``L`=&%(0#@!2$,(/_WK/P&J@&2!ZD`D3(<(QPY'``@`O!._P`H*="KP._9 +M`0```.0+`0```@``3WFY:AFIF2#_]Y3]&*L8>0"K&7_`)2A`B0:)#@%#F2#_ +M]Y#\&:F;(/_WA/T8JQAY`*L9?BA`B0:)#@%#FR#_]X'\&:FF(/_W=?T8JQAY +M009)#J8@__=V_`:J`9('J0"1,APC'#D<`2`"\!C_`"@IT!FIFB#_]U[]&*L8 +M>0"K&7_`)2A`B0:)#@%#FB#_]UK\&:F<(/_W3OT8JQAY`*L9?BA`B0:)#@%# +MG"#_]TO\&:FG(/_W/_T8JQAY009)#J<@__=`_"(<,1PX'`*K`O#N_@`H9]`9 +MJ9T@__<L_1BK&'D`JUEZP"4H0(D&B0X!0YT@__<H_!FIGB#_]QS]&*L8>0"K +MV7HH0(D&B0X!0YX@__<9_!FIGR#_]PW]&*L8>0"K67LH0(D&B0X!0Y\@__<* +M_!FIH"#_]_[\&*L8>0"K&7HH0(D&B0X!0Z`@__?[^QFIH2#_]^_\&*L8>0"K +MF7HH0(D&B0X!0Z$@__?L^QFIHB#_]^#\&*L8>0"K&7LH0(D&B0X!0Z(@__?= +M^QFIJ2#_]]'\&*L8>?@A`4"I(/_WTOL!("3@(N````#%`(`44`+``BX`P((M +M`,`6+0#``"``@`"H`(!2+P#`Q#X`P+HO`,`B,`#`XBX`P+(N`,"*+@#`YC`` +MP+8P`,"*,`#``"`7D$0A@"#_]Z;[[$@`>$`'00Y9F=6S`0```.`-`0```@`` +M@@VZ)'\@__>?^QFI`2#_]Y/\Z$C_)0"+&C44*`'0$R@.T0`L`=$"(0#@!B%0 +M(/_WB_LP(?H@__>'^Q@A*!P1X!BK&'D#*`?1,"'Z(/_W?/L:(2@<__=X^P`L +M`=$`(0#@!"%0(/_W</M0(0D@__=L^]!(`(L/*`?1`BX%T`,N`]`\(0D@__=@ +M^\M(`'@`*`/0`"P!T`'P7OW(2(!H`2%)!HA#QDF(8,9)2&B`"(``2&`+'1AH +M`2(00QA@BFC(#8)#BF`!+`71ODH,,A%H"$,08`+@RFB"0\I@$"%+(/_W-/NX +M2`!H`B$(0[9)"&#[]P7_&*D!(/_W@/P8JQAXLDD`!P`/&'`H(/_W=_RI2*Y) +M`'D/.<EX"$--T:M)!CEY(/_W:_RI3:A)#SUH>H`&@`YH<@4Y>B#_]V#\J'JC +M28`&@`ZH<@0Y+2#_]U?\Z'J>28`&@`[H<@,Y+B#_]T[\*'N`!H`.*'-I>HH& +M`M3J>E$8`^!`(E$:ZGI1&NER"R/I5C\I`=T_(0+@`"D!V@`AZ7*I>HH&`=1` +M&`+@0")1&D`:*',,(^A6/R@!W3\A`N``*`':`"$I<WY-:'D`*%#1@DT//>AX +M`2A+T8!)!CEY(/_W%/QH>GU)@`:`#FAR!3EZ(/_W"_RH>GA)@`:`#JAR`CDM +M(/_W`OQH>W1)@`:`#FAS`3D&I]<_`0```-P/`0```@``4,$L32X@__?Y^ZA[ +M@`:`#JAS:7J*!@+4:GM1&`/@0")1&FI[41II<PTCZ58_*0'=/R$"X``I`=H` +M(6ESJ7J*!@'40!@"X$`B41I`&JAS#B/H5C\H`=T_(0+@`"@!V@`AJ7-;30/@ +MJ7C_]V3Z!#4HB/\H^-&I>/\I]=$"\"/]`1QM(/_W5_H`+$K1&*L8>%%-`2@& +MV.`A#"#_]TSZF2$-(`'@8"$,(/_W1?H8JQAX`R@#TW\A#R#_]SWZ&*L8>`$H +M`]$"(1$@__<U^ABK&'@#*`'3]R$`X/,A%2#_]ROZ&*L8>`$H`=B\(0#@JR$9 +M(/_W(?H8JQAX`B@!T3(A`.`B(28@__<7^ABK&'@"*`/3#"%T(/_W#_H8JQAX +M`2A$V#_@&*L8>"Q-`2@&V.`A#"#_]P'ZF2$-(`'@8"$,(/_W^OD8JQAX`R@# +MTW\A#R#_]_+Y&*L8>`$H`]$"(1$@__?J^1BK&'@#*`'3ER$`X),A%2#_]^#Y +M&*L8>`(H`=$V(0#@)B$F(/_WUOD8JQAX`B@#TPPA="#_]\[Y&*L8>`$H`]BD +M(74@__?&^2(A)B#_]\+Y&.```'PL`,`44`+`*#,`P$`@`(``T`"`**@`@"=S +M`L""+`#`YBP`P+(L`,"I>/_WJ?D$-2B(_RCXT:EX_RGUT0(N`=`#+@K1&:D* +M(/_W_OI>%J[J`0```-@1`0```@``"/ZC*1BK&'D$(0%#"B#_]Y/Y&:D,(/_W +M\_H8JQAY""$!0PP@__>(^3,<.APA'``@^_>'__OW9?W_]\'[%I`$*`71`"P! +MT0`@`.`!(!:0`"4X!@`.&I#J2``M0%<5D!+1`"P*T0`B`)(!DB(<$JL:F!69 +M`O`Q_``B$.`6F``H(-$!(!:0*.``(0$B`9(`D169(AP2JQJ8`O`?_`$B`2$` +MD0&2(AP5F0^K&I@"\!7\`2$H'`^J^_=#_@`A*!P2JOOW/OX6F(5"!]$2J0[) +M#*@.P`^I#LD)J`[``34M!BT.!"VYTQFI+R#_]XOZ&*L8>?PA"*L(0-E\R0E) +M``A#V7D8J\D)"$,8<1EY+R#_]Q7Y&:E?(/_W=?H8JQAY@"$(0!AQ"*O8?0`H +M$=`8JQAY0"(00QAQ"*N8?1BK&7D(0QAQ"*N8>ABK&7G```A#`^`8JQAY0"*0 +M0QBK&'$9>5\@__?L^``L,M&G32AY`"@<T`BKV'RE28`&@`[`,,AS("$M(/_W +MV_@(J]AYV7R`!HD&B0Z`#D`:("%(0`$&"0XN(/_WS/@`(2EQ2."83<`AZ'L( +MJPA`V7R)!HD."$/H<^AZ`08)#BT@__>Y^"A[,>".36AY`"@<T`BKV'R,28`& +M@`[`,,AS("$M(/_WJ/@(J]AYV7R`!HD&B0Z`#D`:("%(0`$&"0Y!7G=@`0`` +M`-03`0```@``*)K`JBX@__>9^``A:7$5X'Y-P"'H>PBK"$#9?(D&B0X(0^AS +M:'L!!@D.+2#_]X;XJ'L!!@D.+B#_]X#X<TTH(.E[__=[^`BK67PI(/_W=O@( +MJYE\*B#_]W'X"*M9>2L@__=L^`BKF7DL(/_W9_@(JUE]&2#_]V+X"*L8?1EZ +M"0$(0P$&"0XU(/_W6/@!+$31(APY'``@!:L"\"C[X"4`*!G0&:E[(/_WK?D8 +MJQAY_B$!0'L@__="^!FI,R#_]Z+Y&*L8>0"K&7TH0,D&R0X!0S,@__<S^"(< +M.1P!(`6K`O`%^P`H&=`9J7L@__>+^1BK&'G[(0%`>R#_]R#X&:DT(/_W@/D8 +MJQAY`*L9?2A`R0;)#@%#-"#_]Q'X%Y@!*!?1`*L8?%E\"0$(0P$&"0X<(/_W +M!/@9J1T@__=D^1BK&'GP(0"K"$"9?`%#'2#^]_;_(1PX'/_WG?D`+!?1>``L +M2<`90!@"+@71!R\!V`,P"^`8,0;@`RX&T04O`=,).`/@!C$-')/@`S@%')#@ +M`BX!T`,N9=$['*DA,R(L)LT@<"\(K670%-R?!V/1GQ`)/Q,O7]("H]M=6P"? +M1```.CI!05M;145;6UM;6UM;6U-36@"Q)X@K$M`=W'PK5-`*W'0K`=!X*T71 +MFB`(JYAP.2!8<+`@1>"`*T;0A"LZT0BKF'"KKF8'`0```-`5`0```@``Y>[# +MY%YP0^!X4`+`?"P`P!AS`L!&,0#`E2L=T)DK&]"=*P'0H2LFT0BKF'`\(%AP +MM"`GX)H@"*N8<#D@6'"H("#@"*N:<%IP*.!F(`BKF'`F(%APJB`5X`BKFG`C +M(%APLR`/X!W@9B`(JYAP)B!8<*X@!^``X`[@`"$(JYEP("%9<*\@"*L8<!/@ +M"*N:<%IP"*L?<`W@"*N8<%YP"*L9<`?@WTC?2<`9$#C`>T,`&!A%&*EX8B#^ +M]T/_:7AC(/[W/_\I>&0@_O<[_]=(!R$#'!`[&FE)`P`L!]&*0QIA`6@%(E(# +M$4,!8`S@BD,:80%H`R)2`Q%#`6`!+`/1.B$6(/[W'O\9J0P@__=^^!BK&'D@ +M(8A#&'$9>0P@_O<1_\--*"#I>_[W#/_!2`!X00=)#R`Q)R#^]P3_'R$"(/[W +M`/^\2A!H&"$(0Q!@['`;L`$@\+WPM86P`"3[]]?ZM$X`)P2I<B#_]U#X?27M +M``7@__>#^`2I<B#_]T?X`*L8?,`'`M0!/?/2`>``+5+0`JE2(/_W.O@`JQAZ +M@0:)#D`Q4R#^]\[^`JE4(/_W+O@`JQAZ@0:)#D`Q52#^]\+^`JE6(/_W(O@` +MJQAZ@0:)#D`Q5R#^][;^`JE8(/_W%O@`JQAZ@0:)#D`Q62#^]ZK^!*EO(/_W +M"O@#J7`@__<&^`*I5B"44E9E`0```,P7`0```@``:%('K/_W`O@!J5@@_O?^ +M_P"K&'P`!D`.?R@)T1E[]R"(0P71&'H**`+3&'D**!#2`"P&T0'@`"P#T3L< +M`B)Q>`+@<7BR>#L<\'C_]QWY`32(YP$L!=%Q>+)X\'@['/_W$_D@'`6P\+V` +MM?OW4/H?(0(@_O=G_G!*&#)0:P@AB$-08__W:/^`O8"U^_=`^ATA`B#^]U?^ +M@+UP1V5)"'!P1V-)`"`(<'!'`"!P1W!'@+7[]SCZ@+V`M?OW-/J`O8"U^_<P +M^H"]@+7[]RSZ@+W_M8.P6$\,G?AP!!PX'()P!)@6'#D<2'#S]W'Z!"`#+0'1 +M!R$6X`(M`=$#(1+@!2T!T0(A#N`$+0'1.'`+X`$M`=$!(0;@!BT!T08A`N`' +M+0'1!2$Y<$5*.7@2>!%`04H1<$%)*#F):@"1/TDH.4EJ/DH!D00Z$6@$(`A# +M$&`\2$%H20A)`$%@-4D$,0AH,"(00PA@[_=,^#)-!QQH>``H&M'[]P?Z@"#[ +M]XWZ`0[I<0$""0ZI<0$$"0YI<2AQA"#[]X'Z*'(H>?\H`]``*`'0`2"H<`$@ +M:'`R'"`<!)D&F__W:_C_]]#^($TH/:AJ`B&(0ZAB^_>O^0`L`=%0(0#@2B%+ +M(/[WP_W[]YKY`JD,(/[W%?\`JQAZ]R$!0`P@_O>J_3@<[_<)^`"9J6(!F6EB +M"TGG%P(]`0```,@9`0```@``UA]_!!`Y2&DP(I!#2&%H:@0AB$-H8@M)!#$( +M:`$B$$,(8`$@![#PO>LQ`,!P,0#`T*(`@!AS`L!\+`#`**@`@#--`,!`I@"` +M@``0M80<HD"!0!%#"B*"0!%#&TJ`&`-@&4H0,A!H"$,08`$@$+T62A%I@P`" +M()A`@4,180$@<$<12H``@!@!8`$@<$<.28``0!@`:'!'#$F``$`80&EP1PE( +MP6(!('!'!T@!8P$@<$<%2$!J<$<$28AB`2!P1P))"&$!('!'````D`"0_[4) +MG)Y&^4L6:(Q&%B$`)9Y@5FC>8)9H'F'2:%IA]$H6BV)&$@04+@'0$RX"T0$F +MM@(`X.].,D-:8.Q.<F@((YI#<F"">,-X&P(:0Q,'FP\"*P;1$@82#P@J`M$8 +M(24<`.``),)X`WD;`AI#TP<$U9('`M4&,0D&"0Y9(@"K&G!<<(*)_R8V`EJ` +MPHG_)S\"FH`"BMJ`0FH60#8"$P:;&18*/D";&1(.FA@"D@*,$P(2"AI#`*N: +M@6)&8T8;"A("&D,`J]J!R4H`FT`R$V`!FU-@`IN38`.;TV``)@"K'G!9<$&( +MQ$L90`"K68#!B)F``8G9@$&)&8&!B5F!P8F9@0&*V8$`F1%A`9E180*9D6$# +MF=%A08H9@(&*68#!BIF`0WX!?AL"&4,)!PD/`*N9<=YQ'8'&N%_-`0```,0; +M`0```@``]GL<AUZ!`Y;!>`-Y&P(90\L'"=6)!P?508L`JQF!@8M9@<&+F8'= +M@0"9$6*C2@&90#)18@*9D6(#F=%B`2$`JQEP7'""B?\F-@):@,*)FH`"BMJ` +M0FH4'#1`)`(3!AL9%`HT0!L9$@Z:&`*2`HP3`A(*&D,`JYJ!`)N02I-B`9O3 +M8@*;$V,#FU-C<T:38R@PT&,*F``H`=",2`%@C$P@:`$CVP,80R!@$&@(0Q!@ +M"I@`*`?1$&B`!?S5$&@#(0D"B$,08``@!+#PO8!*$+44:``A"!QY2P`L`M`8 +M:(0%_-418!EH`R(2`I%#&6`((0A`$+W_M0F=ED9P2AYHC$86(0`DEF!>:-9@ +MGF@68=MH4V%K2A.+<D82!!0K`=`3*P+1@2/;``'@9TL(,QI#8TM:8()XPW@; +M`AI#$P>;#P(K!=$2!A(/""H!T1@A+!S">`-Y&P(:0],'!-62!P+5!C$)!@D. +M62(`JQIP77""B?\F-@):@,*)_R<_`IJ``HK:@$)J%D`V`A,&FQD6"CY`FQD2 +M#IH8`I("C!,"$@H:0P"KFH%R1G-&&PH2`AI#`*O:@4-*`)M`,A-@`9M38`*; +MDV`#F]-@`"8`JQYP67!!B#Y+&4``JUF`P8B9@`&)V8!!B1F!@8E9@<&)F8$! +MBMF!`)D180&946$"F9%A`YG184&*&8#OZSGX`0```,`=`0```@``.P\?R8&* +M68#!BIF`0WX!?AL"&4,)!PD/`*N9<=YQ'(%>@0.6P7@#>1L"&4/+!PG5B0<' +MU4&+`*L9@8&+68'!BYF!W($`F1%B'4H!F4`R46("F9%B`YG18@$A`*L9<%UP +M@HG_)C8"6H#"B9J``HK:@$)J%!PT0"0"$P8;&10*-$`;&1(.FA@"D@*,$P(2 +M"AI#`*N:@0";"DJ38@&;TV("FQ-C`YM38R@PD&-@1M!C"4P@:,L#&$,@8!!H +M"$,,X````*``D!10`L``!0!`C\<``)0^`,``I0"0$&#W2`%@2$($L/"]\+6, +M1O1)ED8*:!$"#`X5!/))+0X6!@QP37`V#O!*CG#7:@`G%V#/>#8$/P8^0RT" +M-4,E0Y5AS7F.>2T&-@0U0TYY-@(U0PYY-4/580$K%='->HYZ+08V!#5#3GHV +M`C5##GHU0Q5BS7N.>RT&-@0U0TY[-@(U0PY[-4-58@1P3'A$<(EX@7`%F8D! +MP7#520`GCV%D1HQCTTP$,$`T(&!P1LACF`$%(0A#$&#*20$@B&#PO<M)`^`` +M(`$P'BC\TTAKP`?XU$AK!"$(0,5*`2%`,A%A<$?_M0F=P4D#X``D`30>+/S3 +M3&OD!_C4`"A5T,QJ`"0,8+E,YGBG>#8&/P0^0V=X/P(^0R=X/D..8>9YIWDV +M!C\$/D-G>3\"/D/"IZ9T`0```+P?`0```@``5R,MXB=Y/D/.80$K%='F>J=Z +M-@8_!#Y#9WH_`CY#)WH^0PYBYGNG>S8&/P0^0V=[)'L_`CY#-$-,8J1,`9ZF +M8Z).0#8P8.)CF`'L>"MY`2(00QL"'$-C!P'4"",80PA@`"`!,!XH_--(:\`' +M^-1(:P0C&$`"T,EJD$O98)))0#$*802P\+WPM0X<CTP`(0F=!9\A8)Q&(1S) +M:B$<B6HA'`EK`QR)22@SBV//8S,<ATY`-C-@QG@#>1L"'D/S!S+5LP<1U0,< +M$#->>P=_-@8_!#Y#QWX_`CY#AWX^0\YBWGN;>S8",T,.X,-[AGL;!C8$,T-& +M>S8",T,&>S-#RV)#?`9\&P(S0PMC0WT&?1L&-@0S0\9\-@(S0X9\,T-+8L-] +MAGT;`AW@0WT&?1L&-@0S0\9\-@(S0X9\,T/+8L-]AGT;`C-#"V-#>@9Z&P8V +M!#-#QGDV`C-#AGDS0TMBPWJ&>AL",T.+8L-[AGL;!C8$,T-&>S8",T,&>S-# +M"V%#?`9\&P(S0TMA!IM+8\9X`WD;`AY#<P<!U`@C(V`3!&9&,T/+81(,"F(' +MF@`J`]"J`2`C&D,`X"`BBF$A:$<B$4,A8`B;`2(`*SU)$M$`(P$S'BO\TV-K +MVP?XU`-YP'@;`AA#0`<"U#A(0#`"80`@2&#PO4I@\+TQ2$%H`"D"T8%H`"EF +M]:;``0```+@A`0```@``QSA0J0K0,4H`(0$Q'BG\TU%KR0?XU``A06"!8"M) +M`2!`,0AA<$=PM1X<`"0`*''0)DT`(RM@ZVJK:BMK`QPD3"`SHV/B8R)*0#(1 +M8,%X`WD;`AE#B@<]U<D'$=4!'!`Q2GL#?Q(&&P0:0\-^&P(:0X-^&D/B8LI[ +MB7L2`A%#..!!?0)]"082!!%#PGP2`A%#@GP10^%BP7V"?0D"$4,A8T%Z`GH) +M!A($$4/">1("$4.">1%#86+!>H)Z"0(HX```E#X`P`"F`(!\-@#``*4`D`"D +M`)#!>X)["082!!%#0GL2`A%#`GL10^%B07P"?`D"$4,A8T%]`GT)!A($$4/" +M?!("$4."?!%#86+!?8)]"0(10P#@,."A8L%[@GL)!A($$4-">Q("$4,">Q%# +M(6%!?`)\"0(10V%A9F/!>`-Y&P(90TD'`=0((2E@*6A#(A%#*6``(0$Q'BG\ +MTVEKR0?XU&QK`WG`>`XA&P(80PQ`0`<"U!])`2`(82`<<+T0M<IXBW@2!AL$ +M&D-+>!L"&D,+>!-#&$H38,MYC'D;!B0$(T-,>20"(T,,>2-#4V#+>HQZ&P8D +M!"-#3'HD`B-##'HC0Y-@RWN,>QL&)`0C0TQ["7LD`B-#&4/18`5)`FA`.4I@ +M0&B(8``@"&`0O0`@<$<``$"E`)``I`"0<+5;20EH!!S[J]@)`0```+0C`0`` +M`@``YUPS*@@T`2.C0!E#5TL98(,`5DD<'%9-61@-8RPC54U#0UT9(QQ13`$F +M[F$V!L`T&QD>8`$FKF)U!@4H`=%E80;@!B@!T:5A`N`=8$I("&,%*@#8`"(( +M*@322$F0``A8*$,88``J`]!`245(0#&(87"]/DL0M1EH!!P(-`$BHD"10QE@ +M+",[2EA#`"&`&(%B$+TV2$%H-4I&(4`RD6$0(8%@.$H$(5%@`6@!(A%#`6!P +M1RY(@#`!:`(BD4.!(A%#`6!P1RE(`6A)"$D``6`!:$D(20`!8'!'$+4F3``@ +M`2(`(2PC0T/A4!L966"98-E@&F%989IAV6$98EEB`3`(*)EB[ML0O2PC&DE8 +M0T`8@&IP1Q9(@#`!:(D(B0#`(A%#`6!P1SBU$4J@(4`R46$/2U`S&6@!)2P< +MA$`A0P@<408(0QA@56$020`@"&!(8/KW]?MI1CP@_O<%^0"K&'CP(0%`/"#^ +M]P;X.+T```"N`(#_9P``*',"P/^W``"T,@#`&`@``$"E`(!`H@"`[DH0M0`C +M$V#M3&!@[4B!8.Q))#&18`HAT6`385-A!2&18>E)$6`@(2%PZ$PA:!@B$4,A +M8,-A`V+Q]RC[`2#Q]S'[$+W=2("U`6A)"$D``6#>2@0Z4&@8(8A#4&#Q]S#[ +M@+U#&%L(`-$!(T`:`B@>W``J%]!]H:S/`0```+`E`0```@``*B@P9-)(0&A" +M*`?1T4F):#@I`]$N.SHK$=@.X#`H`]$0.Q,K"]@(X!`H"-$N.SHK!=@"X`$[ +M%RL!V`$@<$<`('!'PTDPM4EH$"D1T<1,Q4T`(4H`HUH@.X-"`]*J6B`R@D)4 +MV`$Q"08)#@,I\=-7X+A*,"F2:"/1@RH0T;I,`"%*`*):$QP@.X-"`M@@,H)" +M/=(!,0D&"0X-*?'30."S3``A2@"B6A,<(#N#0@+8(#*"0BS2`3$)!@D.`RGQ +MTR_@0BD3T3@J$=&I3*I-`"%*`*-:(#N#0@/8JEH@,H)"%M(!,0D&"0X&*?'3 +M&>!`*0/002D!T$(I$]&?3``A2@"B6A,<(#N#0@;8(#*"0@/3`3$(!@`.,+T! +M,0D&"0X"*>W3_R`PO?"UB[``(`B0_R`'D``@B$D&D`60!)`#D`*0`)``)P`F +M`"4D,8!(`9$":20Y"F``)'])`2((:)("D$.@0@?1`"P%T'E(`&D(8)!#H$)B +MT*`!`9D*D`A<P`=9U0J8`9E`&`F0@'@"*`C9")F(0@#3")`'F8A"!M@'D`3@ +M`Y@!,``&``X#D`F8P'G_*`'1`"<MX`$W/P8_#@$O"-%D20EX2$/_]SW_!I#_ +M*"#0">!?20EX2$/_]S3__R@8T`:9B$(#T0$V-@8V#A'@!9D`*031`34M!BT. +M!9`)X`69B$(C+U*5`0```*PG`0```@``IY3T+`/1`34M!BT.`N``(`60`"4* +MF`&9"%R!!PO50`<)U0F8`2*`>`$<__?D_@`H`=`!(`20`30*+(W3!)@`*`30 +M14D(:0$P"&$4X`.8!B@)T@*8!B@&T@`B")@'F?_WROX`*`K0!"X!T@0M!M,Z +M24AI`3!(80$@`)`"X`"8`2@(T35)2&@!,$A@/4A!:`$Q06`#X#!)B&D!,(AA +M`)@+L/"]^+4N2;PQ#6AI1O_W//\$'`$F`2@F3R=)"=`X:4`%!M3(:``H%]`N +M2B@:D$(3V0AJ#1P`*`/0\?>V^?'WNODX:$`(0``X8#AH,$,X8``@Z&$H8NA@ +M#>#(:0`H"M$82+PP`&C(8#AI#1SH804@\?>3^2YB(!SXO0]*$&H!*!;1#TB\ +M,`%HT&@`*!#0"!H528A"#-D&2`%H20A)``%@`6@!(QE#`6``(-!A$&+08'!' +M@*\`@-0R`,"(=`+``1@``$2E`(#F,@#`X#(`P`HS`,#L,@#`\C(`P/XR`,#< +M,@#`0`P`@$"<```0M?KWV_E*(8D@_??S_5A,8&A"*`W15TB`:#@H#=%)(80@ +M_??G_6,A22#]]^/]-2$,X$`H`=%#(0#@/2&$(/WWV?UF(4D@_??5_64A2B#] +M]]']12&$(/WWS?U@:#`H!='T(88@_??&_2HA!N`0*`?1"2&&(/WWOOUQ#*QR +M`0```*@I`0```@``&=F,A%(AB2#]][K]X2%K(/WWMOWA(6P@_?>R_0PA;2#] +M]Z[]#"%N(/WWJOTH(?\@63#]]Z7]*"'_(%HP_?>@_5XA_R!>,/WWF_TP(?\@ +M9##]]Y;]$+WPM0$GB[`#)0`D`BD`ETC9%"E&T@`C2AX(X,88=GC'7/8;!J_^ +M5`$S&P8;#I-"]-L`(`(Y"N`&JQL8!JXV7%MXFQL!KC-4`3``!@`.B$+RVP`@ +M&N`&JQX8`2/V5@`N`-IV0@&K&U8V!C8.`"L`VEM"&P8`GQL.OD($TZM"`M@! +M-"0&)`X!,``&``Z(0N+;9"%A0Q`<`/"Q^10H`MT!(`NP\+T`(/OGU#(`P(AT +M`L#XM00<!AP%',`P`">'82@<T#``D$=S`)A'(0D!!W,+(&D8"'$C($`!*!@! +M(<&";W1D(+(A2%.T(0(@2%5H<F^!*!P!(2ER,#"!<"]T@B'!<(0A`7&+(4%Q +MEB&!<<=Q!W+_-&$T)W!0(&!P0R"@<`(@X'`G<?`@8'$H'.`V""(2,!VA`/`' +M^0$@0`)@82!A'$@!(2"!#B"@<@0@X'(C(``"H('_(#4P1U4Q<`TB<!P5H0#P +M\/@"(+!S,!P-(@\P%:$`\.CX`R`P=S`<#2(=,!6A`/#@^`0@*B&(53`<#2(K +M,!2A`/#7^`"8_R$!<2@<`/`I^`$@^+T``$U25DS8NHOA`0```*0K`0```@`` +M.;WO!RU!4``K"0``04)#1$5&1TA)2DM,30```$Y/4%%24U155E=865H```!A +M8F-D969G:&EJ:VQM````;F]P<7)S='5V=WAY>@```/"UDR')`$,8#$X+30`A +MB@"2&11X2@#7&&Q4?'"95`$Q#BGTTT4B$@&`&``A@7`!(0%P`2#PO0``'R8` +MP`"A`(!P1W!'>$<`````4.,>_R\1\0``ZA"T!"H.TP,<"T.;!PK1",@0R:-" +M`M$$.@0J^-*C0@'0!#@$.0`J`M$`(!"\<$?3!P'5`3(%X`-X#'@!,0$PHT(' +MT0-X#'@!,0$PHT(!T0(Z\=$8&^GG``!X1P```""@XP0`4>,(```Z`\`0X@T` +M``H$P&SB`@!<XP$@P.0!(,"D`2#`Q`P00>`&``#J@<^PX0$@P"0!(,`D`2#` +M1![_+^%X1P```""@XP!`+>D",*#A`L"@X0+@H.$@$%'B#%"@*`Q0H"@@$%$B +M^___*@$>L.$,4*`H#`"@2`!`O>@!$;#A!""`)![_+P$!(,!$`2#`1$`$$>,! +M(,`4'O\OX7A'```#`%+CS```F@/`$.((```*`3#1Y`(`7.,,((+@`<#1E`$P +MP.0!,-$T!"!"X@'`P)0!,,`T`S`1XJP```H$(%+BO0``.@/`,><"`%/C"``` +M"@\``(HL-*#A!,"QY00@4N(K^Q:K`0```*`M`0```@``],GL20P\@^$$,(#D +M^?__*@$0@>*Q``#J+#B@X03`L>4$(%+B##B#X00P@.3Y__\J`A"!XJD``.HL +M/*#A!,"QY00@4N(,-(/A!#"`Y/G__RH#$('BH0``ZGA'``"`)!#B``!@0D$P +M,N``$&$BH<%PX"```#HAQ'#@#P``.@`$H.'_)(+C(<)PX!<``#HAQ'#@"0`` +M.@`$H.'_*(+C(<1PX``$H"'_+((C(<)PX`X``#H`P'#B;```*B`$H"&APW#@ +M@!-!(`(@HN`APW#@`!-!(`(@HN"APG#@@!)!(`(@HN`APG#@`!)!(`(@HN"A +MP7#@@!%!(`(@HN`AP7#@`!%!(`(@HN"AP'#@@!!!(`(@HN`!P'#@`!!!(`(@ +MLN#E__\JPP\RX*,/@.``$&$B'O\OX7A'````(*#CH<%PX"```#HAQ'#@#P`` +M.@`$H.'_)(+C(<)PX!<``#HAQ'#@"0``.@`$H.'_*(+C(<1PX``$H"'_+((C +M(<)PX`X``#H`P'#B.0``*B`$H"&APW#@@!-!(`(@HN`APW#@`!-!(`(@HN"A +MPG#@@!)!(`(@HN`APG#@`!)!(`(@HN"AP7#@@!%!(`(@HN`AP7#@`!%!(`(@ +MHN"AP'#@@!!!(`(@HN`!P'#@`!!!(`(@LN#E__\J`@"@X1[_+^%X1P````#! +MY2`DH.$&K'Q)`0```)PO`0```@``)@5Z(`$@P>4@**#A`B#!Y2`LH.$#(,'E +M'O\OX7@``.O#`@#K',"?Y0_`C.`!`!SC#>"/$@_@H`$<_R_A`<"/XAS_+^$` +M\%[XI=?^_WA'```!0"WIN0(`ZP%`O>@!``#J>$<`````X..H``#J>$<```(` +MH.,"$*#C60``ZGA'```00"WI4@``ZP0`@.(00+WH'O\OX7A'```00"WI("!2 +MX@4``#H84+$H&%"@*!A0L2@84*`H("!2(OG__RH"SK#A&%"Q*!A0H"@8`+%( +M&`"@2!!`O>@"S[#A!#"1)`0P@"0>_R\!@B^PX0$@T40!,-$D`<#1)`$@P$0! +M,,`D`<#`)![_+^$0M00<`/``^"`<__>=_Q"\"+P81_"U!!P-'(.P`/!0_P"4 +M`94`(`#P`/@"D"9(:49X1`#P`/@%'`X<`*D#R0#P`/@`\`#X`/``^``A`"`` +M\`#X!QP`\#KX!!P`(0=B`"``\`#X01QA8@`A`"``\`#X`"&@8@`@`/``^``A +MX&(`(`#P`/@@8P#P`/@`\`#X`/``^`#P`/@`\`#X`/``^`#P`/@`\`#X*!PQ +M'`.P\+P(O!A'"+4`\`#X`/``^`#P`/@!L`B\&$<``)0```!X1P````"?Y1[_ +M+^'03`#`>$<``!!`+>E7`@#K``!0XQ!`O0@>_R\!$$"]Z$,``.H;&*YA`0`` +M`)@Q`0```@``?CKU1'A'```.4*#A\?__ZP7@H.$`0*#A#1"@X0HPH.$'`,#C +M8-"`XA!`+>E*`@#K$$"]Z`?0P>,`8*#C`'"@XP"`H.,`L*#C!,"@X<`)K.C` +M":SHP`FLZ,`)K.@30"WI``"@XP`0H./____K0!"!X@$@@.!$+X+B'""$Y1@0 +MA.4!`*#C$`"$Y1-`O>@4`(3E`!"@X0[PH.%X1P``$$`MZ0,`+>G+___K`$"@ +MX0,`O>@<,)3E`S!-X!0@E.4`(('E`.""X`,`7N$+``"*#C"3X&,PH.$',,/C +M0!V.X@<0@>('$,'C`P!1X0,0H($"`$'@%!"$Y1!`O>@>_R_A`""@X0``H./_ +M___K$$"]Z![_+^%X1P``&`"@XP@0G^56-!+O'O\OX9$````F``(`<+4.3@T< +M`1P$''Y$,!P`\`#X`ARP0@;1*1P@'`#P./APO`B\&$<&2'A$@D(#T"D<(!P` +M\%O[`"#RYP``,<W^_Q'-_O\XM0>T@[`*G`&M*1P`(`6K`ZH`E`#P`/@`*`70 +M`:D#R0>P,+P(O!A''R"`!@1`(1P"(/_W'_\`(``A`\4!J0/)[N<``'A'```> +M_R_A.+5"'@TJ)*0FTA<B(TM"0WM$U1@7/0(H%-%(`0'5(*0;X"1("$`!T".D +M%N#(``'5):02X(@``=4FI`[@2``,U2>D"N`9U(R.`0```)0S`0```@``7EZ6 +MQP@H`=$,'`;@"2@$T0$I`M$FI0#@*Z4*(`#P7/@H>``H!M`H>`$U`/!5^"AX +M`"CXT2!X`"@&T"!X`30`\$OX('@`*/C1"B``\$7X.+P(O`$@&$<```````!] +M#P``26YV86QI9"!/<&5R871I;VX````"```(1&EV:61E($)Y(%IE<F\``$]V +M97)F;&]W`````%5N9&5R9FQO=P```$EN97AA8W0@4F5S=6QT```Z($AE87`@ +M;65M;W)Y(&-O<G)U<'1E9`!5;FMN;W=N('-I9VYA;```"+4`JQAP`R!I1JO? +M`;`(O!A'````H`!'J!&?Y8@AG^71`*#C`/`AX0(0@>`#$,'C!!!!X@'0H.$` +MH*#C`+"@XV@AG^72`*#C`/`AX0(0@>`#$,'C!!!!X@'0H.'3`*#C`/`AX3PQ +MG^4`$(/E-!&?Y0``D>4$`(#B/!&?Y3PQG^4L(9_E``"!Y0`@@^4"`(#@+!&? +MY0`@H.,`(('E%"&?Y0``@N4@(9_E"@"@XP``@N4/$*#C$!""Y0$PH.,D,(+E +M*#""Y00AG^40,*#C"#""Y1[_+^'^___J_O__ZO[__^K^___J_O__ZFD!`.KM +M]YS[.$B'1O[__^H0#Q#NU`"?Y1`/`>[0\)_ET!"?Y0``H..`*Z#C`2!"X"(A +MH.$$`('D`2!2XOS__QKI`]DY`0```)`U`0```@``DRJ5B;0`G^6T$)_E``"! +MY;``G^6P$)_E`!"`Y:P`G^6L$)_E`!"`Y:@`G^6H$)_E`!"`Y1[_+^$>0"WI +MG!"?Y0`PD>68()_E`T""X```Q.4!,(/B_T"@XP0P`^``,('E'H"]Z`)`+>D0 +M'A#N`A`1X@$``!H0#@'N`0"@XP``H!,"@+WH7/X!```!````!`````0``!3] +M`0`T_0$`./T!`#S]`0!8\0$``)``D`"``)`93/`#=!\``$`````D_`$`\/#P +M\'C\`0#8_`$`?30!`-S\`0#C!P``X/P!`%4U`0`(```$+```!,``G^4`$.#C +M`!"`Y;@`G^6X$)_E`""@XP`0@.4$$(#E"""`Y0P@@.4`,*#C%3\'[A8_!^X! +M,*#C$#\"[C`_`NZ,,)_E$#\&[H@PG^41/P;N(3"@XQ(_!NY\,)_E$S\&[G\Q +MH.,4/P;NAC&@XQ4_!NX`,*#C%C\&[A<_!NY<,)_E4#\%[E@PG^5P/P7N$#\1 +M[H`]P^-,()_E`C"#X1`_`>X>_R_A(!"?Y0P`@>4>_R_A-)"?Y1G_+^'\EZ#C +M&?\OX0"0H.,9_R_A#"``@`#0`(!550``(0#^_RT``-`A@`"0-C,S`&8&!@`% +M$`4``0#__WA'```#,!#B``"0!1[_+P%$((_B`Q#2YP'PC^```*#A!@"0Z"$$ +MH.%(441"`0```(PW`0```@``'I91P0(LH.$"`(#A'O\OX08`D.@A"*#A`BB@ +MX0(`@.$>_R_A!@"0Z"$,H.$")*#A`@"`X1[_+^$``!0H>$?`1@#`G^4<_R_A +M5R0`P'A'P$8`P)_E'/\OX0$4`,!X1\!&`,"?Y1S_+^'E$P#`>$?`1@#`G^4< +M_R_A!Q0`P'A'P$8`P)_E'/\OX0$2`,!X1\!&`,"?Y1S_+^$Y$@#`>$?`1E#_ +M_^IX1\!&`,"?Y1S_+^$]*_`#>$?`1@#`G^4<_R_AF4WP`WA'P$8`P)_E'/\O +MX0%_`L!X1\!&`,"?Y1S_+^'I)O`#>$?`1@#`G^4<_R_A12KP`WA'P$8`P)_E +M'/\OX<DI\`-X1\!&`,"?Y1S_+^%]*O`#>$?`1@#`G^4<_R_AK2?P`WA'P$8` +MP)_E'/\OX5E'\`-X1\!&`,"?Y1S_+^$=(_`#>$?`1@#`G^4<_R_AG2/P`WA' +MP$8`P)_E'/\OX>$D\`-X1\!&`,"?Y1S_+^$M)?`#>$?`1@#`G^4<_R_A<23P +M`WA'P$8`P)_E'/\OX?U.\`-X1\!&`,"?Y1S_+^%Y)?`#>$?`1@#`G^4<_R_A +MR27P`WA'P$8`P)_E'/\OX0TF\`-X1\!&`,"?Y1S_+^&5)O`#>$?`1@#`G^4< +M_R_AY1OP`WA'P$8`P)_E'/\OX1D=\`-X1\!&`,"?Y1S_+^&8#VI``0```(@Y +M`0```@``H-LI:7D<\`-X1\!&`,"?Y1S_+^$U'/`#>$?`1@#`G^4<_R_AU1SP +M`WA'P$8`P)_E'/\OX0U,\`-X1\!&`,"?Y1S_+^$1*?`#>$?`1@#`G^4<_R_A +MU2CP`WA'P$8`P)_E'/\OX<TF\`-X1\!&`,"?Y1S_+^%-*/`#>$?`1@#`G^4< +M_R_AD2?P`WA'P$8`P)_E'/\OX>5!\`-X1\!&`,"?Y1S_+^$/3/`#>$?`1N[^ +M_^IX1\!&`,"?Y1S_+^';$@#`>$?`1@#`G^4<_R_A"TSP`WA'P$8S___J>$?` +M1@#`G^4<_R_AFQ\`P'A'P$8`P)_E'/\OX4<@`,!X1\!&`,"?Y1S_+^$G'0#` +M>$?`1@#`G^4<_R_A"QX`P'A'P$8`P)_E'/\OX1D4`,!X1\!&`,"?Y1S_+^'C +M)`#`>$?`1@#`G^4<_R_AQQT`P`#`G^4<_R_AL3`!``#`G^4<_R_A3S$!``#` +MG^4<_R_AP3(!``#`G^4<_R_A'0$```3P'^4D3?`#`,"?Y1S_+^$%,P$`>$<` +M`!``'.,!(*`!#P`MZ0,`+>D,4"WI.OW_ZPQ0O>@`(*#A`P"]Z``0DN4!.<SA +MP#43XD#$C!.,,*#AHSV@X0,0@>%P-P'B`\",X0`0@N4/,`SB"``SXW`P#!*# +MP8P1@#N@`PPPTP'`R(P#@`4<XPT``!I1S;X&`0```(0[`0```@``@+]*ZD`% +M'.,'```:0`0<XQ0``!J`!ASC"P``&D`/$>,/`+WH(```"G<!`.I`#A'C#P"] +MZ!$```IS`0#J@`X1XP\`O>@+```*;P$`ZH`/$>,/`+WH;`$`&A``'.,#("$0 +M`B`@``@``.I`#1'C#P"]Z`````ID`0#J$``<XQ[_+^&``!SC@"0!$H`D``*` +M)`+B$``<XS@`CQ(#`)`8`A"!$3P`GP4"`(`!'O\OX8``'..`)`$2@"0``A`` +M'.,H`(\2`P"0&`(0@1$D`)\%`@"``28!`.H```````#P?________^]_``"` +M?___?W\```````#X?P``P']X1P``@#B@XX`@@^"!,(/@@`ARXX`(<Y.?__^* +M@`A2XP$`H"$3`0#J>$<``$`($..9__\*3R`,XD@`4N,-`0`:E?__Z@```+__ +M_W^_````@/__?[]X1P``P"NPX0`TH.&`-(,3`P``2IX@<N(&```Z,P*@X1[_ +M+^&`$*#A?P11XP$``"H``*#C'O\OX1S`G^6`$*#A_P11XT#+C(/B__^*??__ +M&O___^H`P)_E>O__ZF@``@1X1P``0"2@XP,``.IX1P``@"00X@``8!)`)(+C +M(!BPX8`F@A(`"+`!'O\O`?\$$.-`)H(2``2@`?`$$..`)X(2``*@`<`$$.-` +M)X(2``&P`8`H@D)P@IB<`0```(`]`0```@``3<M)I(``H%&`'+#A0`2BX`3! +ML!,!`,`C'O\OX7A'``#_R*#CH",<X*$S'!`,`#(1#``S$2H```H!`##A0"^" +M0X#$H.,`!(SA`12,X0,@@N"0P8/@@"A"X@``7.,!,(,30,\"XH`$4^.#,*`Q +M0BBBX",$L.&""Z#@@,"MX'\`$R/\"%(3'O\O,0$`P`/\"%+C'O\O,0P``%J` +M$*#A0!=!XL`'4>,>_R\Q8`2`XG\`$^/LP+`1;,'P`=`CH$/P(Z!3_P`3XP,@ +MH`-,``#J@,B`XHS`L.$>_R]!8`1`XL3#H.,L___JH3,,X`$`,.%`+X)##`!2 +MX0P`4S$%```J@,"PX8'`L!&""Z`!'O\O`<C`3^(-``#J0,>@XX``?.&!`'R1 +MP<.@XW7__XK____J@,"PX8'`L!'_(((3@@N@$1[_+Q'!PZ#C$___ZGA'```` +M!*#A`12@X?\($N,0```:0`M0XT`I0C(`"*`Q_P00XX`J0@(`!*`!\`00XT`J +M0@(``J`!P`00XX`K0@(``;`!@`"@44`K@D+_"!/C@!2!$PSPH!&`!(#C0`M1 +MXT`Y0S(!&*`Q_P01XX`Z0P(!%*`!\`01XT`Z0P(!$J`!P`01XX`[0P(!$;`! +M@1"@44`[@T(,\*#A>$<``!!`+>DK_/_K`!"@XP`0@.400+WH'O\OX7A'``"I +MB'"2`0```'P_`0```@``6.<NO`,`+>D,4"WI(_S_ZPQ0O>@`P*#A`P"]Z``0 +MG.5`!Q'C@`0`$A[_+Q&`#A'C30``&H`T`.*@.X/A0#_#X\$P8^*`%,/C%P!1 +MXQD``,H@,&/B$!.PX2`P8^+_!,#C@`B`XS`#H.&`-`/B`P"`X0P```H!,*#A +M`!"<Y0@0@>,`$(SE@Q"PX00``#H!`(`2`@``&L(_\.$!`("2`0#`,T#$@N.\ +M_O_J`L"@X4`$$N.Y_O\:'O\OX68!4>,`$)SE"!"!XP`0C.6`!`-B!```:H`$ +ML.&`!`/B`0"`XH`$$@,!`,`#0,2@XZO^_^IX1P```P`MZ0Q0+>GH^__K#%"] +MZ`#`H.$#`+WH`!"<Y4`'$>.`!``2'O\O$8`.$>,>_R\!@!0`XF`4@>/_"!#C +M``2@`4`600+_"!#C``2@`4`600+P"!#C``*@`8`700+`"!#C``&@`4`700*` +M"!#C@`"@`8`8@1*`",#C`0"`X8#%@N,G``#J>$<```\P#.()`%/C"`"@`RP` +M``H(`%/C'O\O$4``'`,6```*0`L<XPL``!H@`!SC#```&A``'.,`(*`!`2"@ +M$8`$X`.`%.`3``#@$X`$$N,``.`1`1#@$1[_+^$``*#C`!"@XQ[_+^$``.#C +M`!#@XX`$$N,``.`1`1#@$1[_+^$0`!SC"`"?!0@0GQ6<LM:V`0```'A!`0`` +M`@``7'2J'@``H!,>_R_A``#`?P``^']X1P```%`MZ0!0+>E:_O_K`%"]Z`!0 +MO>@/,`SB"0`SXQ[_+Q$```#J>$<``$`)'.,:```*\`H<XP8```H(`!#C`0`` +M&@(`<.(>_R_A\!H,XD`*4>,>_R_AX!2@XX`>$>#`%8$C`P`MZ0Q0+>F'^__K +M#%"]Z`#`H.$#`+WH`#"<Y?`TP^,!,(/A`#",Y0`@#^'P),+C`2""X0+P*.$> +M_R_A``@<X`$`H!,>_R_A$P-#25,@!=\"`(,!(0(&`2("`0<B"`0&``<1(C-$ +M(@4"0$(/`"(%`H"$'@`B!0)@[%,`(@4"P-BG`"("`P<B`@4!!`8#`0`````% +M#D&Q.;4>+4Y6,/__`ND`!P8!`````0`'!@0!`!```!4_!P!-87)V96QL(%-E +M;6EC;VYD=6-T;W(`.#A7.#,P,"`X,#(N,3%B($-A<F1B=7,@4$,@0V%R9``X +M,P`P,0#___]!8FYO<FUA;"!T97)M:6YA=&EO;@```$%R:71H;65T:6,@97AC +M97!T:6]N.B``26QL96=A;"!I;G-T<G5C=&EO;@````!);G1E<G)U<'0@<F5C +M96EV960``````$EL;&5G86P@861D<F5S<P``````````5&5R;6EN871I;VX@ +M<F5Q=65S=`````!3=&%C:R!O=F55BQ=-`0```'1#`0```@``?!#)G7)F;&]W +M````````````4F5D:7)E8W0Z(&-A;B=T(&]P96XZ(`!/=70@;V8@:&5A<"!M +M96UO<GD``````%5S97(M9&5F:6YE9"!S:6=N86P@,0``57-E<BUD969I;F5D +M('-I9VYA;"`R``!0=7)E('9I<G1U86P@9FX@8V%L;&5D`$,K*R!L:6)R87)Y +M(&5X8V5P=&EO;@``````````````````J$8!``````0$````K$8!```2`,`D +M(0``T&<!`````,``````````````````````J$8!`+"J```$```$2!H``"0S +M`,`(1@(`````P(P/``"P?P+``````*H````````````````````````````` +M``````````````````````````````````````!%;0``D6X``)-N``"K;@`` +M&6T``"5M`````````````,%J``#-:@`````````````````````````````` +M``````````````````````````````````````````````!UE0```@`!``0` +M`@`+``0`%@`(`"P`$``,`"``$@!``!@`@``D```!,````D@```1@```(;``` +M$/\`_P`R````2P````(`!``*`!8`+``,`!(`&``D`#``2`!@`&P`D``-`!L` +M&@`V`"<`40""I@HY`0```'!%`0```@``L63*TS8`;`!.`*(`:`#8`'4`\P"" +M``X!&@`V`#0`;`!.`*(`:`#8`)P`1`'0`+`!Z@#F`00!'`(>`/P`'@#\``8> +M`/@>`#SPC@,``-8&``!4$```)AL``+I$``""%```G!T```PF```J-0``5$(` +M`#18```8:@``<'$```````"<&```R#(``-0P``"H80``1$@``(B0``"(7@`` +M@+L``(")``"(#0$`I+4``*A;`0#TR0``N((!`-3?``#@I0$`U#```*AA``"( +M7@``@+L``.2)``"(#0$`I+4``*A;`0"`!@$`2.@!`.11`0!@80(`U'4!`!"8 +M`@!`E@$`V,H"``4`!?\H`PP/,@(+#3P!"@M&``G_XPG'$ZH=C2=4.QI/_5CA +M8L<3C2=4.QI/IW8TGONQP<4#`>$`_____P"``)!I21"U!2`(@&=*!"!`,A"" +M9D@!)$!Y9DL`*`O1#(`#(!""84H@,A"!B&B@0XA@`"`8<!"]''`0O5Y)\+4) +M>``I'=%:24IY6$D`*C;0!QQ62%=+`"(@,`0<FF`%(B`T!"4")@DO"]("H]M= +M6P"?1```&AH%"`\:&@\@`$U(AF#PO4M.`R.S8"6"!R,+@`S@)8(-(@J`4"$! +M@4-))#$(:#!#"&#PO26""H`"@0$@P$,"X`J``"#`0PB!\+T(*`;1.DJG4<DJ +M`0```&Q'`0```@``/-@.FP@R$&@!(0A#$&#PO8AH0`A````$``R(8/"]-DGP +MM0EX`"D;T3),H6@#*1?0`BD5T"Y)2GDA'$EH`"H0T0`I!=$`*`/0`2!@8`,@ +M!>``*070`"@#T6!@`2`C20B`\+TD2P4B'W@@30$FNP":0!($$@R^0"`U`"D- +MT0$H[]%@8!I)!"!`,0B"&$F(:+!#``0`#(A@'N``*`K0`B@2T`,H&M`$*-O1 +M!"G9T&!@`R`8X``@#4E@8`@Q"&@R!!(,$$,(8/"]`BG\T`(@8&`'20$@0#$( +M@BJ!\+T#*?S0`R!@8`0@F$`H@?"]````T`"`H$T`P'`V`,#T,P#`@+4('`'P +MLOB`O?BU#AP%'!\<`"0$X#@9`?"H^`'%!#2T0OC3`"#XO?=(<$?V22<BD@&) +M&(EH`"#*!P#5`2!)!P'5!"$(0W!'[T@`?'!'[DN:?!()"G"9?`D'"0\!<'!' +MZDCI20!X`"@#T/\Q`3'(>`'@0#'(>O\H`=``*`#13"!P1_"UX$L`)!0[GW@C +MX'0CW4UC0UL9*24M`5T9:WJ30A;1JWJ+0A/1KGOK>QL"'D.&0@W8:WLM>QL" +M'4.%0@?3="#136!#0!DI)2T!0!GPO0$T)`8D#KQ"V=,`(/"]#B@!T<M)$>`. +M*`32`"D"T<A)!SD*X,=+$"@!V`$I`="F*`'2&1PB"),%`0```&A)`0```@`` +M@I5V,P'@?2%)`8,`&!A`&!"``2!P1SBU%!P-'"$<``8`#FI&__?;_P`H"-`J +M!A(.`*L8B"$<__>D_P`H`=$`(#B](##\YS"U`"4!(P`B`>``*P70E``D&"`T +M)'B,0@'1D0`)X`$R%"KRW0`M`=$`*P71:1X#U(D`"!@@,#"]`"`PO5PCH$I9 +M0XD80#$)?0%P`2!P1Q"U1",3<:LC4W'3>(`D(T/3<`4DE'$!)-1Q$"@1VRD@ +M4'`H()!P`"D%T9@)@`$Q,-!PRR`5X)@)@`$P,-!P*R`/X)@)@`$J,!PC`"G0 +M<`314W`;()!P,"`#X!T@4'"3<$$@$'`0O?^U1GN<1@J;"9\`(A;@A!BE>XU" +M#]CE>XU"#-.`&(![`ID(<.![848(<#IP4!P8<`$@!+#PO0$R$@82#I9"YM@` +M(/;G,+4`(YP`)!BE?)5"!=/D?)1"`M@+<`$@,+T!,QL&&PX$*^_3`"`PO6A* +M$+4`(10Z$GAE3`[@7"-+0QL90#-;>H-"!-%<($A#`!E`,!"]`3$)!@D.D4+N +MTP`@$+TPM1,E6DKM`0`A%#J2>5=,$.`D(TM#&QE;&5MX@T(&T20@2$-420`9 +M"C%`&#"]`3$)!@D.D4+LTP`@,+TPM<TE2THM`0`A%#J2>TA,#^!<(TM#&QE; +M&5M[@T(%T5P@2$-'20`90!B=%I)%`0```&1+`0```@``HO$5L#"]`3$)!@D. +MD4+MTP`@,+W_M9.P`"`<GQ8<&AP"D!P<.1P4F/_W-O\W2A0Z$7B0>P%#9]"1 +M>``I9-`=F0$I$]$`*`/0.!S_]\;_`N`X'/_WBO\%'"Q(%#B`>0`H"-`X'/_W +MF_\"D`/@.!S_]WS_!1P`+4?0"*HQ'!.8__>P_@`H0-`Z'`"K&(PQ'/_W>OX' +M'#C0%)@/J0(&$@X2DC@<__=._P`H+M`+J@RI`)$!D@ZJ$YD-JR@<__<@_P`H +M(M`(JQA^!ZHQ'/_WBOX(JQA]!JHQ'/_WA/X`JQB,F8M`&@$$"10*D1F+FHL! +M()%"`M`8BYF+0!H`!``4"9`(JQA_#"-80PO@'.```,0^`,!_+`#`;@D``(@3 +M``#4#```"*L9?$`920!!&!PQ!9$9>TD`0!@<,`20.!P2F?_WA?X&'`+1`"`7 +ML/"]"*L8?X`B@`#`&4!\('$8?P`C@`#`&0!\8''P>.%XP`G``9%#"$/@<!&0 +M!)C`5@69R58/'$`:"IE!0PF8`/![_L`9`Y`$F`$CP%8%F<]6"IG`&T%#"9@` +M\&[^QQD(JQA[%2,H&,!6"*L9?!4C:1C)5A"10!H*F4%#"9@`\%S^$)E`&*!Q +M*'W@<7!XP!E@<+!XP!F@</%X`Y@(&.E^01@1F"`QB0:`"8`!B0X(0P4<Y7`= +MF0$I)M$M&7,O`0```&!-`0```@``;X46_N=*D'L`*"+1`I@`*!_0"*L8?P8C +M`IE80T`8"*L9?`PC01C.5@BK&7L,(T`8P%8*F8`;04,)F`#P(_Z`&2!R*!BI +M"8D!@`:`#@A#X'`!X``@('(!('?G_[44'(&P#1PA'&I&__>^_0`H,M#-3\Q) +M`"`4-XYX*N!T(4%#*2(2`<D9B1A*>JI"'M&*>J)"&]'+>XI[&P(:0P"K&XB: +M0A/82WL)>QL"&4,`JQJ(D4(+TW0C6$,I(A(!P!F`&,!Z!)D(<`$@!;#PO0$P +M``8`#K!"TM,`(/;G\+6'>P`DG$8'FP`E!^!&&?9[-!DD!@$U+08D#BT.KT(! +MV95"\]."&-=[`"4!/QG@`AD6?8Y"#]A6?8Y"#-,`&0!]848(<%!]!9D(<`:8 +M!'!@'!AP`2#PO0$T)`8!-2T&)`XM#J]"X]P`(/"]$+673)9*`"$4-%)X#^#, +M(TM#&QG_,P$S6WB#0@31S"!(0P`9^#`0O0$Q"08)#I%"[=,`(!"]_[63L!R> +M%1P:'!P<,1P4F/_WA_V%2$%X`"ECT(!X`"A@T#`<__?1_P<<6]`(JBD<$YC_ +M]QO]`"A4T#(<`*L8C"D<__?E_`8<3-`4F`^I`@82#A*2,!S_][G]`"A"T/-Z +M#:@`D`NJ`I(.J(1&&AP,J0&1$YEC1C@<__=Q_P`H,=`(JQA^!ZH*+\W2`0`` +M`%Q/`0```@``O4F`ERD<__?P_`BK&'T&JBD<__?J_`"K&(R9BT`:`00)%`J1 +M&8N:BP$@D4("T!B+F8M`&@`$`!0)D`BK&'\9?$`!P!E)`$$8)#$%D1E[20!` +M&"0P!)`P'!*9__?Z_`4<`M$`(!>P\+T$F``CP%8%F<E6$9%`&@J904,)F`#P +M!/T1F0$C0!@#D`28P%8%F<E6$)%`&@J904,)F`#P]?P0F8`B0!AI>`BK"1AA +M<*EX"!B@<.EX`Y@(&#E[0!CA>"`P@`:)"8D!@`X(0^!PZ7B00\D)R0$(0^!P +M&'^``(`90'P@<1A_@`"`&0!\8'$!(+GG$+4M3`(<('@"*`S27"-80RE+%#/` +M&$`P"QQ<(?_W`_P@>`$P('``(!"]$+4B3`(<H'L"*`W27"-80Q]+%#/`&!Y+ +MP!@+'%PA__?M^Z![`3"@<P`@$+T0M1=,`AQ@>`(H`=,`(!"]S"-80Q-+%#/` +M&/@P"QS,(?_WUOM@>`$P8'`!(!"]$+4,3`(<H'@.*`'3`"`0O70C6$,'2Q0S +MP!@I(QL!P!@+''0A__>]^Z!X`3"@<`$@$+T``+`^`,#4#```$+7G3`(<H'D" +M*`'3`"`0O20C6$/B2Q0SP!CB2\`8"QPD(?_WH/N@>0$PH'$!(!"]4@`0&`(< +M,+4='$`R4#`%*0.<"M(!HUM<6P"?1`(&"@HCZRV0`0```%A1`0```@``Y78/ +M\PX`07@I<`!X"N!0>RAP$'L&X$%Z*7``>@+@07DI<`!Y('`!(#"]D@82#E(` +M,+40&*`P'1P%*0.<&=("HUM<6P"?1````P<+"P\`P7DI<(!Y"N!!>2EP`'D& +MX$%Z*7``>@+@P7HI<(!Z('`!(#"]07DI<`!Y('``(#"]_[4?'`0<"!R"L#H< +M(1P`!@`.`:L+G0R>__?D_0`H%-``+PW1(!S_]V?\`"@-T`"6`*L:>029*QS_ +M]Y7_!K#PO2`<__=$_@`H`=$`(/;G`)8`JQIY!)DK'/_WJ/_NY_"U3R:<39Q+ +M=@$`(A0UW'D3X!@C4T-;&9L97WB/0@G1FWB#0@;1&""425!#0!E@,4`8\+T! +M,A(&$@ZB0NG3`"#PO?^U'QP&'!4<`"J!L`_0,1P!(/_WU/\$'`G0`I@J'#$< +M``8`#FM&__>-_0`H`M$`(`6P\+T`JQAX0```&0![.'`!(/7G_+4/'!P<``8` +M#@`A%1P&'`&K__=U_0`H;=`J'`$A,!QK1O_W;?T`*&70`*L8>1EXB$)@T6]( +M;DD4,``M#-$)>0`I6-`9>0(I5=(9>1PC64,(&&A)G#$,X$EY`"E+T`"K&7D% +M*4?2&7D<(UE#"!AA2>`Q0!@`(4(88Q@!,1)^"08)#@,I&G+VTP4O--("H]M= +M6P"?1````Q(A(3$``"'Z="V.`0```%13`0```@``Q1)L<,H`$QA*`%UY%AD! +M,75P&WD)!@D.`RFC5/+3+.``(<L`&AA5>$H`%AEU<,-<`3$)!@D.`RFC5/+3 +M'>``(<H`$QA*`-UX%AD!,75PFW@)!@D.`RFC5/+3#N`/X``AR@`3&$H`W7D6 +M&0$Q=7";>0D&"0X#*:-4\M,!(/R]`"#\Y_BU!!P('!X<%1PA'``&``YK1O_W +MY_P`*!S0+DAA'!0P`"TL2@_1$GJ*0A/3`*L9>`(I#](4(6%#"!@9>(D`0!BO +M(0D!#^!2>HI"`],`JQEX!2D!TP`@^+UA`0@8`*L9>(D`0!@>24$8!"(P'`#P +MU?H!(/#G$+4$'!=+$!P4,P`L%4H1T0(I`=`#*0;1T7H`*1_0)"*](0D!%N"1 +M>@`I&-`/23@B8#$/X`(I`=`#*07147L`*0W0+"(*207@$7L`*0?0!TF,(J`Y +M61@`\*;Z`2`0O0`@$+VP/@#`>`D``"P+``"H#```,+6/)>A*+0$`(10ZTGCF +M3`_@,"-+0QL96QE;>(-"!=$P($A#X4D`&4`8,+T!,0D&"0Z10NW3`"`PO?^U +M!AS:2!\<%#C`>!4<@;``*"'0,!S_]];_!!P<T*!Z`"@9T`*8*APQ'``&``YK +M1O_W2/P`*`_0`"T)T0"K&'@"*`G2&'B#`!@8`1D+,0S@`*L8>`4H`M,`(`6P +M\+VU^)!X`0```%!5`0```@``"&9O/@"K&'B#`!@8`1D5,04B.!P`\$3Z`2#Q +MYQ"UO4P"'!0\X'@#*`S2,"-80[E+P!BY2\`8"QPP(?_W<OG@>`$PX'``(!"] +M`ARR2"<CFP'`&`L<@+48(?_W8_D`(("]$+6L3`(<%#S@>0(H`=,`(!"]&"-8 +M0Z=+P!BG2_`SP!@+'!@A__=-^>!Y`3#@<0$@$+T"')](H4N`M<`8"QQ$(?_W +M/_F;210Y"'D!,`AQ`2"`O0(<F4N72$0SP!@+'("UF"'_]R[YDTD4.4AY`3!( +M<0$@@+T0M8],`AP4/"!Z`R@!TP`@$+T4(UA#BDO`&(M+W#/`&`L<%"'_]Q/Y +M('H!,"!R`2`0O1"U@DP"'!0\8'H#*`'3`"`0O7Y+0`'`&%DC6P'`&`L<("'_ +M]_OX8'H!,&!R`2`0O0(<=D@7(]L!P!@+'("U1"'_]^SX<DD4.8AZ`3"(<@$@ +M@+T"'&Y(<$N`M<`8"QPP(?_WW/AJ210YR'H!,,AR`2"`O0(<:4ME2#`SP!@+ +M'("UH"'_]\OX84D4.0A[`3`(<P$@@+T"'&!+74C0,\`8"QR`M4`A__>Z^%E) +M%#E(>P$P2',!(("]L+4-'!0<$!P`\%[Y``8`#@XX&"@\T@*C&UQ;`)]$```, +M$18;."`X)3@X.#@J.#@O-#D^0TA-4E<A'"@<__>`_";@(1QH"VI'`0```$Q7 +M`0```@``A=JK=B@<__>F_"'@(1PH'/_WN/P<X"$<*!S_]P'_%^`A'"@<__<W +M_Q+@(1PH'/_W0O\-X"$<*!S_]\+\".`A'"@<__<#_P/@(1PH'/_W"O\AX"$< +M*!S_]S__'.`A'"@<__=3_Q?@(1PH'/_W9O\2X"$<*!S_]W+_#>`A'"@<__=] +M_PC@(1PH'/_WB?\#X"$<*!S_]TK\(!T`\/;XL+WQM1U.@K`,(#!P-1Q,(`#P +M[/@''$@@`/#H^``$``P!D``D!.`X&0#PX/@!Q00T`9B$0O?3.!T`\-CX!.`" +M'#`<`IG_]V[_0QSXT0](`/#A^#%\,#`.3PL*`7!#<``DI0`H'`#PPOBI&<D9 +M`/#8^`$T("STTP`@_KT``,0^`,#H"```"`H``,0+``#"30#`C`T``/BU)^`& +M'`#PI_A,20`,B$(!T0$@^+T`)0`DAP@/X*``@!D`\)GX`0X"`A(.21E1&`($ +M$@Y1&$`8!08M#@$TIT+MW`$M`]`^20AI`3`(83`=`/""^$,<U=$`(-OG<+4% +M'`X<`"0`(`#P=_@V28A"`=``('"]#"T&T3`<__?!_P`H`=$!('"](!QPO1"U +M`/"$^`P@`/!A^`0<$"``\%WX*DDJ2F,<#-$E2X-#"=$H2`!H*$L`!``,DV+0 +M8@EH"V`#X)1BT&():`Q@B(``(`#P1/@"HOWF`0```$A9`0```@``.Y?3WAQ) +MB$(+T10@`/`^^!@@`/`[^$P@`/`X^`0<0QP!T0`@$+T@'`#P,/@`!@`.#"@( +MT2$<__>K_P$H`]$`(/_W+_\0O0$@$+V\M0T<%!P'F@:9!M$!DB(<`)$I'/_W +MY_B\O0"1(API'/_WDOKXY___``"P/@#`6#`S.,P3`L``I0"``*8`@`!00P)X +M1\!&`,"?Y1S_+^%K:0``>$?`1@3P'^7X+0$`>$?`1@3P'^5`+0$`>$?`1@3P +M'^5H-P$`>$?`1@3P'^64+P$`>$?`1@#`G^4<_R_A36D```$```"`?P+`0W\" +MP(5_`L`)"```BG\"P(4$``"/?P+`M6X``)1_`L"_;P``F7\"P#T[``">?P+` +M?4H``*-_`L#Q9@``J'\"P`E2`````````````*A_`L`Q4@``HW\"P!UG``"9 +M?P+`CSL``)Y_`L!-2P`````````````!````__________\"``0!"P(6`RP$ +M#`42!A@')`@P"4@*8`ML#)`-__\```$!`@($"Q8,$A@D,$A@;"R0````='A0 +M;V]L8G5F`````````*A4`,`5`!4`%0`(!ZA4`,!T>%!O;VQB=68R```````` +M6.H`P!``$``0``@'6.H`P'1X4&]O;&)U9C0```````!H7`'``0`!``$`"`=H +M7`'`='A0;V]L8G72:`Q]`0```$1;`0```@``&_.P768S````````F&,!P`$` +M`0`!`&0`F&,!P`($"Q8L#!(8)#!(8&R0````H```_P$`D$P`%S4(&0+`.!D" +MP.@C`L#_______\"``0`"P`6`"P`#``2`!@`)``P`$@`8`!L`)`````$!00% +M'BA8`,````!8`@``8`````<````H&```*%@```$```$"`@,#1*``@$R@`(!4 +MH`"`7*``@&2@`(!LH`"`=*``@'R@`(`TH`"`/*``@!R@`(`DH`"`+*``@!2@ +M`(!`H`"`2*``@%"@`(!8H`"`8*``@&B@`(!PH`"`>*``@#"@`(`XH`"`&*`` +M@""@`(`HH`"`$*``@&9R86=,:7-T4&]O;`````"(:P+`*``H`"@`%`"(:P+` +M<#@5"P83#0H'!00#`P(/#P\-$1`0#1(1$0T2$1$-$A$1#1(1$0T2$1$-$A$1 +M#1(1$0T1$!`-#P\/#0X.#@T.#@X-#`P,#`X-#0P0#@X,$@\/#!(0$`P2#P\, +M$`X.#`X-#0P.#0T,#@T-#"0-#0PH#0T,+`T-##`-#0PT#P\,.`\/##P/#PQ` +M#P\,9`\/#&@/#PQL#P\,<`\/#'0/#PQX#P\,?`\/#(`/#PR$#P\,B`\/#(P/ +M#PR5#@X,F0X.#)T.#@RA#@X,I0X.#`@.#@P,#@X,$`X.#"(.#@PF#@X,*@Y# +MLAFS`0```$!=`0```@``UH>S$PX,+@X.#+@.#@R\#@X,P`X.#,0.#@PD#0T, +M+`T-##0/#PP\#P\,9`\/#&P/#PQT#P\,?`\/#(0/#PR5#@X,G0X.#`4`!P`( +M``D`"@`+``P`#`````$``@`#``,`!0`%``<`!P`)``D`"0`)``D```"*`@`` +MT@(``$8%``#<!0``%`4``*0%``","@``N`L``)X'``!Z"```T@\``)01```H +M"@``2`L``!@5``!P%P``/`\``.T0``"D'P``*",``%`4``"1%@``,"H``.`N +M``#:%@``9!D``'8O``"\-```9!D``#8<``"\-```F#H``!0%``"D!0``C`H` +M`+@+```H"@``2`L``!@5``!P%P``/`\``.T0``"D'P``*",``%`4``"1%@`` +M,"H``.`N``!X'@``VB$``$@_``!01@``H"@``",M``!@5```P%T``+0M``#( +M,@``[%X``'AI``#(,@``;#@``'AI```P=0``5A43`#$3$`!\"1``B0@0`*L* +M$`";"1``O@00`$4$$``=!Q``7080`"D#$`#8`A``5@4@`,T$(`!?`B``(P(@ +M`(\#(``T`R``E0$@`&P!(`"K`C``9P(P`#`!,``1`3``7P(P`",",``.`3`` +M\P`P``D",`#5`3``Z``P`-``,`"K"A``FPD0`+X$$`#SQ%8.`0```#Q?`0`` +M`@``NJN!.$4$$`!6!1``S000`%\"$``C`A``CP,0`#0#$`"5`1``;`$0`*L" +M(`!G`B``,`$@`!(!(`#(`2``F@$@`,L`(`"V`"``5@$P`#0!,`"8`#``B0`P +M`#`!,``2`3``AP`P`'H`,``$`3``Z@`P`'0`,`!H`#`````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M```````````````````````````````````````````````````````````` +M`````````````````````````&0```#(````)@(``$P$``"8"```6`(``(0# +M``"P!```"`<``&`)```0#@``P!(``!@5```@'```\/___P______\/___P__ +M____\/___P______\/___P\``0(#!`4&!P@)"@L,#0X/$!$2$Q05%A<8&1H; +M'!T>'P,&`@8!`0(`'``"`!T`:`!Y/L!;`0```#AA`0```@``*K#\<SP`"0#0 +M`!<``@!M`#\`-0!F`"<`(P`H`,``.````#L```#_`/\`"@#+`!D`JP`:`!T` +M'@`1`!\`*0`@`!$`(0`K`"(`$0`C`"L`)0`#`'8`<`!W`'``_P#_``H`R0`: +M``T`'@`9`!\`)0`@`!D`(0`E`"(`&0`C`"4`)0`$`'8`=0!W`'4`_P#_``(` +M```$````+0`!`"\`0``_`(``1P#S`%L``@!=``(`7P`Y`&``0`!A`*``:P"Q +M`&P`L0!P`&``<0"$`(,`G`"'`(@`Y``*``(!H``+`6@`3P$,`%(!L`!3`3P` +M5`$8`%@!%`!9`10`_P#_``(````$````+0`!`"\`0``Z``4`/P```%```@!; +M``(`70`"`&$`H`!K`.$`;`#A`'``8`!Q`(0`@P"?`(<`H`"*`!P`Y``*``(! +MH``+`6@`2P&R`$T!!`!/`0P`4@&P`%,!/`!4`1@`5P$``%@!*`!9`2@`7P$& +M`&$!#0#_`/\``@````0````M``$`+P!``#H`!0`_`(``4``"`%(`+@!;``(` +M70`"`&$`H`!K`.$`;`#A`'``8`!Q`(0`AP"@`(H`'`#D``H``@&@``L!:``3 +M`8``2P&*`$T!%`!/`0P`4@&0`%,!2`!4`1@`50%_`%<!``!8`2@`60$H`%\! +M!@!A`0P`_P"I1LJ6`0```#1C`0```@``"M2?\/\`"``"`$,`%`!%`!X`1P#S +M`%$`P`!?`#D`8`!``/<``@`8`8``_P#_``@``@!#`!0`10`>`$<`\P!0``(` +M40#``%\`.0!@`$``]P`"`!@!@`!=`5X`_P#_``@``@!#`!0`10`>`$<`\0!0 +M``(`40#``%4`5`"$`#T`A@"I`(D`4@!*`3``5P```%L``@!=``(`7P`Y`&`` +M0`"#`!L`:P#A`&P`X0!M``P`;@`,`%@!*`!9`2@`8P$P`/<``@`8`8``70%> +M`/\`_P"O````L````+$```"R````LP```+0`$`"U`!X`M@`D`+<`&`"X```` +MN0#N`+H`W@"[`.L`O`#P`+T```"^`!@`OP`U`,``4`#!`&0`P@!L`,,```#$ +M````Q0```,8```#'````_P#_`*\```"P````L0```+(```"S`/X`M`#Z`+4` +M\P"V`.H`MP#C`+@`X@"Y`/4`N@```+L`"0"\`!8`O0`G`+X`.`"_`$D`P`!7 +M`,$`80#"`&0`PP`$`,0`0@#%`,``Q@"S`,<`]P#_`/\`KP```+````"Q```` +ML@```+,`_0"T`/<`M0#M`+8`X0"W`-@`N`#5`+D`\`"Z````NP`-`+P`(`"] +M`#<`O@!0`+\`:`#``'P`P0")`,(`C@##``0`Q`"K`,4`.`#&`$``QP!Q^FO' +M`0```#!E`0```@``QZ"<O@D`_P#_``@`(@!#`!P`10`D`$<`XP!0``0`40"` +M`%\`+0!@`#0`]P```!@!4`#_`/\`"``B`$,`'`!%`"0`1P#C`%``!@!1`(`` +M7P`M`&``-`#W````&`%0`%T!7@#_`/\`"``B`$,`'`!%`"(`1P#A`%``!@!1 +M`(``50!*`(0`/`"&`/D`B0!2`$H!)P!7``P`7P`Y`&``0`"#`)L`]P```!@! +M4`!L`,$`;@`<`%@!(`!9`2``8P$P`%T!7@#_`/\`H"]<H37#H3PIH2*/H2CV +MH2]<HC7#HCPIHB*/HBCVHB]<HS7#HSPIHRN%J#,SJ"``J"S-J3F:J29FJC,S +MJB``JBS-KB``KBS-KSF:KR9FL#,SL"``L"S-L3F:L29FLC,SLB``LSS-LP3- +MM!LSM`&:M3``I3F:I29FIC,SIRS-J#F:J"9FJ3,SHBS-HSF:HR9FI#,SI39F +MI2,SHBF:HS``HS9FHSW-````````(Q@``"09````&@`````````````````` +M````&P```!P<`0$='0("'AX#`P,#!`0$!`4%!04&!@8&!P<'!P`````````` +M````````````````````````````````"`@("`D)"0D*"@H*"PL+"PP,#`P- +M#0T-#@X.#@\/#P\0$!`0$1$1$1(2$A(``````!.:T4`#`0```"QG`0"H```` +M_$CB'A,3$Q04%!05%145%A86%A<7%Q<``````````````````"4?)@`G("@` +M`"$````B````````````%````"@```!0````H````$`!``"``@````4``!`` +M```0````E`4&#Y0%Y@#T`90%E@#(`)4%*P+6"VT%E`6@#Y8`^@```&T%E`6@ +M#^8`]`$``#4%B!,&#=`'X@3H`T(#FP)Q`K,!3@$>`?H`3MY&\P$`````?P+` +MM````-R49')PM0`E`"053@?@@!E`:`#P)_@`*`#0`24!-.``,5@`*?/1`"T, +MT0U-`"10-03@0!E`:`#P%?@!-.``*5@`*?;1<+T`(<E#@+4`(`#P$?@`*`'0 +M`2"`O0`@@+T``'@E`,!X1\!&`,"?Y1S_+^$)3/`#>$?`1@#`G^4<_R_AA74` +M`$)U9F8`3U,@5P!%86=L`%1I;64`5&EM0@!#0B!0`%1X($T`4G@@30!)9&QE +8``````XV%.($```````````````?VXP8 +` +end diff --git a/sys/contrib/dev/mwl/mwlboot.fw.uu b/sys/contrib/dev/mwl/mwlboot.fw.uu new file mode 100644 index 0000000..62acb2e --- /dev/null +++ b/sys/contrib/dev/mwl/mwlboot.fw.uu @@ -0,0 +1,97 @@ +# FIRMWARE LICENSE TERMS +# +# +# Copyright (c) Marvell International Ltd. +# +# All rights reserved. +# +# Redistribution. Redistribution and use in binary form, without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions must reproduce the above copyright notice and the +# following disclaimer in the documentation and/or other materials +# provided with the distribution. +# +# * Neither the name of Marvell International Ltd. nor the names of its +# suppliers may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# * No reverse engineering, decompilation, or disassembly of this software +# is permitted. +# +# Limited patent license. Marvell International Ltd. grants a world-wide, +# royalty-free, non-exclusive license under patents it now or hereafter +# owns or controls to make, have made, use, import, offer to sell and sell +# ("Utilize") this software, but solely to the extent that any such patent +# is necessary to Utilize the software alone, or in combination with an +# operating system licensed under an approved Open Source license as +# listed by the Open Source Initiative at http://opensource.org/licenses. +# The patent license shall not apply to any other combinations which +# include this software. No hardware per se is licensed hereunder. +# +# DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIPOSSIBILITY OF SUCH DAMAGE. +# +# +begin 644 mwlboot.fw +M`P``Z@4```````````````````!0`)_E$`\![@``X.-($)_E#`"!Y1@`D>4@ +M"*#A"P!0XP0```H;`%#C`@``"BP`G^4P`('E`0``ZB0`G^4P`('E(-"?Y2`` +MG^4`$)#E'""?Y0`0@N4&``#J=!\````@`(`2"0``YSH'%``@``0$````_/\` +M`,2`C^(#`)CH"`"`X`@0@>`!L$#B`0!0X1,```IP`+#H!0!4X?K__PH!`!3C +M"T"$$`$`%>,+4(40`@`5XPE0A1`#4,7C$&!6XHP0M"B,$*4H^___BH9NL.$, +M`+0H#`"E*`1PE$0$<(5$Z?__Z@@@F.4,,)CE""""X`@P@^`!P$+B`'"@XP`` +MH.,`8*#C`+"@XP,`4N$0```+,`"RZ`$`%.,,0(00`@`4XPE`A!`#0,3C$%!5 +MXL$(I"C\__^*A5ZPX4$`I"@$<(1$\/__ZA@````\````/````%0````$\!_E +M@`$`P````````````````*@!``````#`L`8``%@(````"`#`D````*@!```` +M````L`8`P`````"0"`#`<````'!'<$<`1PA'$$<81R!'*$<P1SA'>$<```,` +M4N.#``":`\`0X@@```H!,-'D`@!<XPP@@N`!P-&4`3#`Y`$PT30$($+B`<#` +ME`$PP#0#,!'B8P``"@0@4N)T```Z`\`QYP(`4^,(```*#P``BBPTH.$$P+'E +M!"!2X@P\@^$$,(#D^?__*@$0@>)H``#J+#B@X03`L>4$(%+B##B#X00P@.3Y +M__\J`A"!XF```.HL/*#A!,"QY00@4N(,-(/A!#"`Y/G__RH#$('B6```ZGA' +M```>_R_A>$<```Y0H.$\``#K!>"@X0!`H.$-$*#A"C"@X0<`P.-@T(#B$$`M +MZ?H``.L00+WH!]#!XP!@H.,`<*#C`("@XP"PH.,$P*#AP`FLZ,`)K.C`":SH +MP`FLZ!]`+>D``*#C`!"@X____^M`$('B`6"`X$1OAN(<8(3E&!"$Y0$`H.,4 +M`(3E'T"]Z`(0H.$>_R_A>$<``!!`+>D`(*#A``"@X____^L00+WH'O\OX=3_ +M_^LV``#Z',"?Y0_`C.`!`!SC#>"/$@_@H`$<_R_A`<"/XAS_+^$`\%+X[0,` +M`'A'```!0"WI4```^P%`O>@!``#J>$<`````X.-3``#J>$<`````G^4>_R_A +MH`@`P'A'```00"WI("!2X@4``#H84+$H&%"@*!A0L2@84*`H("!2(OG__RH" +MSK#A&%"Q*!A0H"@8`+%(&`"@2!!`O>@"S[#A!#"1)`0P@"0>_R\!@B^PX0$@ +MT40!,-$D`<#1)`$@P$0!,,`D`<#`)![_+^$0M00<`/``^"`<__>L[Q"\"+P8 +M1_"U!!P-'(.P`/`4Z@"4`94`(`#P`/@"D"9(:49X1`#P`/@%'`X<`*D#R0#P +M`/@`\`#X`/``^``A`"``\`#X!QS_]Y;O!!P`(0=B`"``\`#X01QA8@`A`"`` +M\`#X`"&@8@`@`/``^``AX&(`(`#P`/@@8P#P`/@`\`#X`/``^`#P`/@`\`#X +M`/``^`#P`/@`\`#X*!PQ'`.P\+P(O!A'"+4`\`#X`/``^`#P`/@!L`B\&$<` +M`)0```!X1P``&`"@XP@0G^56-!+O'O\OX0D````F``(`>$<``![_+^%X1P`` +M$$`MZ:'__^L$`(#B$$"]Z![_+^$`1P``_K4`(`*0`9!-2`$FP&L&*!G2`J,; +M7%L`GT0```,$!`0$!``F1TH`(1%@A`!&2%%@D6!&30%9*!S_]S?^1$@`6?_W +M,OX`X/[G`9@`*`G004B`:0`,``(0(0A#!1P`(`&0`.`0)3Q/*!PY6?_W'OX` +M+@30.D@`6?_W%_X`)@`@`)`W2`!9__<0_C!(+D\/R`_'$#\X:`$H#]`$*`W0 +M`2`N3P5#*!PY6?_W`/XL2`!9__?[_0"8`"CCT")(`&@!*!C0!"@,T21/`"`Y +M6?_W[OTC2`!9__?I_1M(0&C_]X__&4D`(`A@2&"(8,A@`I@`**O0_KT93Q-( +M.5F%:"@<__?5_19(`%G_]]#]`"``D!1(`%G_]\K]$T@`6?_WQOT`*`'1`2;< +MYP$@!4,H'#E9__>]_0I(`%G_][C]`)@`*,_1Y>?`_P``D`@`P!@(`,``"@#` +M``@`P``@`(!X"`#`2`@`P#`(`,!@"`#`"$L:8!AA&6(#(,`"!"H!T&<@@`$# +M24`Q"&`8:``H_-$(8'!'`.@`@`0`G^5`'8#B#O"@X0`0``0!204@2&%P1P`, +M`(!P1P```"!P1W!'``!P1P``<$<```-(!$D(@$`'08A!@'!'```$@```(``` +M@`$AR0=(B$`'_-5P1P))`T@!@(\AP8-P1P2````@``"``2')!XAB<$<!(<D' +MB&-P1P5*@+42'07*`DD$.O_W4NT`(("]``H`P)`(`,"`M?_WZ_X`(("]```$ +M(4@'06,`(4%B<$<!(<D'2&I`!_S5<$<!(<@'`6($(4%C<$<!(<D'B(%P1P$A +MR0?(8W!'<$<```=(`+4`:$((!4E2`$`YR&F`!_S5"&D#2?_W<O\`O0``0`P` +M@``*`,!P1P``!$D(8`-(!2%`.$%A`"'!87!'``!`#`"`<$<```)(`"$!@0TA +M`8!P1R`!`(`"20B)P`?\U7!'```@`0"``T@-(0&``"&!@0$A@8!P1R`!`(`! +M20AA<$<````!`(`!24AA<$<````!`(`!200@"(-P1T```(`#20B)0`?\U0`@ +M"(%P1P``0```@`%)!"`(@W!'0```@`$AR0>(8G!'`2')!TAA<$=X1P``$$`M +MZ2C__^L`$*#C`!"`Y1!`O>@>_R_A004`P!$%`,`M!@#`>08`P*4%`,#M!0#` +M704`P!D%`,!-!@#`C08`P+D%`,`)!@#`-04`P`D%`,`=!@#`908`P)D%`,#% +M!0#`'04`P/T$`,`-!@#`608`P(T%`,#!!0#`904`P`T%`,!E!0#`904`P&4% +>`,!E!0#`504`P!4%`,!!!@#`A08`P+$%`,#Q!0#` +` +end diff --git a/sys/dev/mwl/if_mwl.c b/sys/dev/mwl/if_mwl.c new file mode 100644 index 0000000..ae13c9d --- /dev/null +++ b/sys/dev/mwl/if_mwl.c @@ -0,0 +1,4990 @@ +/*- + * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2007-2008 Marvell Semiconductor, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * Driver for the Marvell 88W8363 Wireless LAN controller. + */ + +#include "opt_inet.h" +#include "opt_mwl.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/sysctl.h> +#include <sys/mbuf.h> +#include <sys/malloc.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/kernel.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/errno.h> +#include <sys/callout.h> +#include <sys/bus.h> +#include <sys/endian.h> +#include <sys/kthread.h> +#include <sys/taskqueue.h> + +#include <machine/bus.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_types.h> +#include <net/if_arp.h> +#include <net/ethernet.h> +#include <net/if_llc.h> + +#include <net/bpf.h> + +#include <net80211/ieee80211_var.h> +#include <net80211/ieee80211_regdomain.h> + +#ifdef INET +#include <netinet/in.h> +#include <netinet/if_ether.h> +#endif /* INET */ + +#include <dev/mwl/if_mwlvar.h> +#include <dev/mwl/mwldiag.h> + +/* idiomatic shorthands: MS = mask+shift, SM = shift+mask */ +#define MS(v,x) (((v) & x) >> x##_S) +#define SM(v,x) (((v) << x##_S) & x) + +static struct ieee80211vap *mwl_vap_create(struct ieee80211com *, + const char name[IFNAMSIZ], int unit, int opmode, + int flags, const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac[IEEE80211_ADDR_LEN]); +static void mwl_vap_delete(struct ieee80211vap *); +static int mwl_setupdma(struct mwl_softc *); +static int mwl_hal_reset(struct mwl_softc *sc); +static int mwl_init_locked(struct mwl_softc *); +static void mwl_init(void *); +static void mwl_stop_locked(struct ifnet *, int); +static int mwl_reset(struct ieee80211vap *, u_long); +static void mwl_stop(struct ifnet *, int); +static void mwl_start(struct ifnet *); +static int mwl_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); +static int mwl_media_change(struct ifnet *); +static void mwl_watchdog(struct ifnet *); +static int mwl_ioctl(struct ifnet *, u_long, caddr_t); +static void mwl_radar_proc(void *, int); +static void mwl_chanswitch_proc(void *, int); +static void mwl_bawatchdog_proc(void *, int); +static int mwl_key_alloc(struct ieee80211vap *, + struct ieee80211_key *, + ieee80211_keyix *, ieee80211_keyix *); +static int mwl_key_delete(struct ieee80211vap *, + const struct ieee80211_key *); +static int mwl_key_set(struct ieee80211vap *, const struct ieee80211_key *, + const uint8_t mac[IEEE80211_ADDR_LEN]); +static int mwl_mode_init(struct mwl_softc *); +static void mwl_update_mcast(struct ifnet *); +static void mwl_update_promisc(struct ifnet *); +static void mwl_updateslot(struct ifnet *); +static int mwl_beacon_setup(struct ieee80211vap *); +static void mwl_beacon_update(struct ieee80211vap *, int); +#ifdef MWL_HOST_PS_SUPPORT +static void mwl_update_ps(struct ieee80211vap *, int); +static int mwl_set_tim(struct ieee80211_node *, int); +#endif +static int mwl_dma_setup(struct mwl_softc *); +static void mwl_dma_cleanup(struct mwl_softc *); +static struct ieee80211_node *mwl_node_alloc(struct ieee80211vap *, + const uint8_t [IEEE80211_ADDR_LEN]); +static void mwl_node_cleanup(struct ieee80211_node *); +static void mwl_node_drain(struct ieee80211_node *); +static void mwl_node_getsignal(const struct ieee80211_node *, + int8_t *, int8_t *); +static void mwl_node_getmimoinfo(const struct ieee80211_node *, + struct ieee80211_mimo_info *); +static int mwl_rxbuf_init(struct mwl_softc *, struct mwl_rxbuf *); +static void mwl_rx_proc(void *, int); +static void mwl_txq_init(struct mwl_softc *sc, struct mwl_txq *, int); +static int mwl_tx_setup(struct mwl_softc *, int, int); +static int mwl_wme_update(struct ieee80211com *); +static void mwl_tx_cleanupq(struct mwl_softc *, struct mwl_txq *); +static void mwl_tx_cleanup(struct mwl_softc *); +static uint16_t mwl_calcformat(uint8_t rate, const struct ieee80211_node *); +static int mwl_tx_start(struct mwl_softc *, struct ieee80211_node *, + struct mwl_txbuf *, struct mbuf *); +static void mwl_tx_proc(void *, int); +static int mwl_chan_set(struct mwl_softc *, struct ieee80211_channel *); +static void mwl_draintxq(struct mwl_softc *); +static void mwl_cleartxq(struct mwl_softc *, struct ieee80211vap *); +static void mwl_recv_action(struct ieee80211_node *, + const uint8_t *, const uint8_t *); +static int mwl_addba_request(struct ieee80211_node *, + struct ieee80211_tx_ampdu *, int dialogtoken, + int baparamset, int batimeout); +static int mwl_addba_response(struct ieee80211_node *, + struct ieee80211_tx_ampdu *, int status, + int baparamset, int batimeout); +static void mwl_addba_stop(struct ieee80211_node *, + struct ieee80211_tx_ampdu *); +static int mwl_startrecv(struct mwl_softc *); +static MWL_HAL_APMODE mwl_getapmode(const struct ieee80211vap *, + struct ieee80211_channel *); +static int mwl_setapmode(struct ieee80211vap *, struct ieee80211_channel*); +static void mwl_scan_start(struct ieee80211com *); +static void mwl_scan_end(struct ieee80211com *); +static void mwl_set_channel(struct ieee80211com *); +static int mwl_peerstadb(struct ieee80211_node *, + int aid, int staid, MWL_HAL_PEERINFO *pi); +static int mwl_localstadb(struct ieee80211vap *); +static int mwl_newstate(struct ieee80211vap *, enum ieee80211_state, int); +static int allocstaid(struct mwl_softc *sc, int aid); +static void delstaid(struct mwl_softc *sc, int staid); +static void mwl_newassoc(struct ieee80211_node *, int); +static void mwl_agestations(void *); +static int mwl_setregdomain(struct ieee80211com *, + struct ieee80211_regdomain *, int, + struct ieee80211_channel []); +static void mwl_getradiocaps(struct ieee80211com *, int, int *, + struct ieee80211_channel []); +static int mwl_getchannels(struct mwl_softc *); + +static void mwl_sysctlattach(struct mwl_softc *); +static void mwl_announce(struct mwl_softc *); + +SYSCTL_NODE(_hw, OID_AUTO, mwl, CTLFLAG_RD, 0, "Marvell driver parameters"); + +static int mwl_rxdesc = MWL_RXDESC; /* # rx desc's to allocate */ +SYSCTL_INT(_hw_mwl, OID_AUTO, rxdesc, CTLFLAG_RW, &mwl_rxdesc, + 0, "rx descriptors allocated"); +static int mwl_rxbuf = MWL_RXBUF; /* # rx buffers to allocate */ +SYSCTL_INT(_hw_mwl, OID_AUTO, rxbuf, CTLFLAG_RW, &mwl_rxbuf, + 0, "rx buffers allocated"); +TUNABLE_INT("hw.mwl.rxbuf", &mwl_rxbuf); +static int mwl_txbuf = MWL_TXBUF; /* # tx buffers to allocate */ +SYSCTL_INT(_hw_mwl, OID_AUTO, txbuf, CTLFLAG_RW, &mwl_txbuf, + 0, "tx buffers allocated"); +TUNABLE_INT("hw.mwl.txbuf", &mwl_txbuf); +static int mwl_txcoalesce = 8; /* # tx packets to q before poking f/w*/ +SYSCTL_INT(_hw_mwl, OID_AUTO, txcoalesce, CTLFLAG_RW, &mwl_txcoalesce, + 0, "tx buffers to send at once"); +TUNABLE_INT("hw.mwl.txcoalesce", &mwl_txcoalesce); +static int mwl_rxquota = MWL_RXBUF; /* # max buffers to process */ +SYSCTL_INT(_hw_mwl, OID_AUTO, rxquota, CTLFLAG_RW, &mwl_rxquota, + 0, "max rx buffers to process per interrupt"); +TUNABLE_INT("hw.mwl.rxquota", &mwl_rxquota); +static int mwl_rxdmalow = 3; /* # min buffers for wakeup */ +SYSCTL_INT(_hw_mwl, OID_AUTO, rxdmalow, CTLFLAG_RW, &mwl_rxdmalow, + 0, "min free rx buffers before restarting traffic"); +TUNABLE_INT("hw.mwl.rxdmalow", &mwl_rxdmalow); + +#ifdef MWL_DEBUG +static int mwl_debug = 0; +SYSCTL_INT(_hw_mwl, OID_AUTO, debug, CTLFLAG_RW, &mwl_debug, + 0, "control debugging printfs"); +TUNABLE_INT("hw.mwl.debug", &mwl_debug); +enum { + MWL_DEBUG_XMIT = 0x00000001, /* basic xmit operation */ + MWL_DEBUG_XMIT_DESC = 0x00000002, /* xmit descriptors */ + MWL_DEBUG_RECV = 0x00000004, /* basic recv operation */ + MWL_DEBUG_RECV_DESC = 0x00000008, /* recv descriptors */ + MWL_DEBUG_RESET = 0x00000010, /* reset processing */ + MWL_DEBUG_BEACON = 0x00000020, /* beacon handling */ + MWL_DEBUG_INTR = 0x00000040, /* ISR */ + MWL_DEBUG_TX_PROC = 0x00000080, /* tx ISR proc */ + MWL_DEBUG_RX_PROC = 0x00000100, /* rx ISR proc */ + MWL_DEBUG_KEYCACHE = 0x00000200, /* key cache management */ + MWL_DEBUG_STATE = 0x00000400, /* 802.11 state transitions */ + MWL_DEBUG_NODE = 0x00000800, /* node management */ + MWL_DEBUG_RECV_ALL = 0x00001000, /* trace all frames (beacons) */ + MWL_DEBUG_TSO = 0x00002000, /* TSO processing */ + MWL_DEBUG_AMPDU = 0x00004000, /* BA stream handling */ + MWL_DEBUG_ANY = 0xffffffff +}; +#define IS_BEACON(wh) \ + ((wh->i_fc[0] & (IEEE80211_FC0_TYPE_MASK|IEEE80211_FC0_SUBTYPE_MASK)) == \ + (IEEE80211_FC0_TYPE_MGT|IEEE80211_FC0_SUBTYPE_BEACON)) +#define IFF_DUMPPKTS_RECV(sc, wh) \ + (((sc->sc_debug & MWL_DEBUG_RECV) && \ + ((sc->sc_debug & MWL_DEBUG_RECV_ALL) || !IS_BEACON(wh))) || \ + (sc->sc_ifp->if_flags & (IFF_DEBUG|IFF_LINK2)) == (IFF_DEBUG|IFF_LINK2)) +#define IFF_DUMPPKTS_XMIT(sc) \ + ((sc->sc_debug & MWL_DEBUG_XMIT) || \ + (sc->sc_ifp->if_flags & (IFF_DEBUG|IFF_LINK2)) == (IFF_DEBUG|IFF_LINK2)) +#define DPRINTF(sc, m, fmt, ...) do { \ + if (sc->sc_debug & (m)) \ + printf(fmt, __VA_ARGS__); \ +} while (0) +#define KEYPRINTF(sc, hk, mac) do { \ + if (sc->sc_debug & MWL_DEBUG_KEYCACHE) \ + mwl_keyprint(sc, __func__, hk, mac); \ +} while (0) +static void mwl_printrxbuf(const struct mwl_rxbuf *bf, u_int ix); +static void mwl_printtxbuf(const struct mwl_txbuf *bf, u_int qnum, u_int ix); +#else +#define IFF_DUMPPKTS_RECV(sc, wh) \ + ((sc->sc_ifp->if_flags & (IFF_DEBUG|IFF_LINK2)) == (IFF_DEBUG|IFF_LINK2)) +#define IFF_DUMPPKTS_XMIT(sc) \ + ((sc->sc_ifp->if_flags & (IFF_DEBUG|IFF_LINK2)) == (IFF_DEBUG|IFF_LINK2)) +#define DPRINTF(sc, m, fmt, ...) do { \ + (void) sc; \ +} while (0) +#define KEYPRINTF(sc, k, mac) do { \ + (void) sc; \ +} while (0) +#endif + +MALLOC_DEFINE(M_MWLDEV, "mwldev", "mwl driver dma buffers"); + +/* + * Each packet has fixed front matter: a 2-byte length + * of the payload, followed by a 4-address 802.11 header + * (regardless of the actual header and always w/o any + * QoS header). The payload then follows. + */ +struct mwltxrec { + uint16_t fwlen; + struct ieee80211_frame_addr4 wh; +} __packed; + +/* + * Read/Write shorthands for accesses to BAR 0. Note + * that all BAR 1 operations are done in the "hal" and + * there should be no reference to them here. + */ +static __inline uint32_t +RD4(struct mwl_softc *sc, bus_size_t off) +{ + return bus_space_read_4(sc->sc_io0t, sc->sc_io0h, off); +} + +static __inline void +WR4(struct mwl_softc *sc, bus_size_t off, uint32_t val) +{ + bus_space_write_4(sc->sc_io0t, sc->sc_io0h, off, val); +} + +int +mwl_attach(uint16_t devid, struct mwl_softc *sc) +{ + struct ifnet *ifp; + struct ieee80211com *ic; + struct mwl_hal *mh; + int error = 0; + + DPRINTF(sc, MWL_DEBUG_ANY, "%s: devid 0x%x\n", __func__, devid); + + ifp = sc->sc_ifp = if_alloc(IFT_IEEE80211); + if (ifp == NULL) { + device_printf(sc->sc_dev, "can not if_alloc()\n"); + return ENOSPC; + } + ic = ifp->if_l2com; + + /* set these up early for if_printf use */ + if_initname(ifp, device_get_name(sc->sc_dev), + device_get_unit(sc->sc_dev)); + + mh = mwl_hal_attach(sc->sc_dev, devid, + sc->sc_io1h, sc->sc_io1t, sc->sc_dmat); + if (mh == NULL) { + if_printf(ifp, "unable to attach HAL\n"); + error = EIO; + goto bad; + } + sc->sc_mh = mh; + /* + * Load firmware so we can get setup. We arbitrarily + * pick station firmware; we'll re-load firmware as + * needed so setting up the wrong mode isn't a big deal. + */ + if (mwl_hal_fwload(mh, NULL) != 0) { + if_printf(ifp, "unable to setup builtin firmware\n"); + error = EIO; + goto bad1; + } + if (mwl_hal_gethwspecs(mh, &sc->sc_hwspecs) != 0) { + if_printf(ifp, "unable to fetch h/w specs\n"); + error = EIO; + goto bad1; + } + error = mwl_getchannels(sc); + if (error != 0) + goto bad1; + + sc->sc_txantenna = 0; /* h/w default */ + sc->sc_rxantenna = 0; /* h/w default */ + sc->sc_invalid = 0; /* ready to go, enable int handling */ + sc->sc_ageinterval = MWL_AGEINTERVAL; + + /* + * Allocate tx+rx descriptors and populate the lists. + * We immediately push the information to the firmware + * as otherwise it gets upset. + */ + error = mwl_dma_setup(sc); + if (error != 0) { + if_printf(ifp, "failed to setup descriptors: %d\n", error); + goto bad1; + } + error = mwl_setupdma(sc); /* push to firmware */ + if (error != 0) /* NB: mwl_setupdma prints msg */ + goto bad1; + + callout_init(&sc->sc_timer, CALLOUT_MPSAFE); + + sc->sc_tq = taskqueue_create("mwl_taskq", M_NOWAIT, + taskqueue_thread_enqueue, &sc->sc_tq); + taskqueue_start_threads(&sc->sc_tq, 1, PI_NET, + "%s taskq", ifp->if_xname); + + TASK_INIT(&sc->sc_rxtask, 0, mwl_rx_proc, sc); + TASK_INIT(&sc->sc_radartask, 0, mwl_radar_proc, sc); + TASK_INIT(&sc->sc_chanswitchtask, 0, mwl_chanswitch_proc, sc); + TASK_INIT(&sc->sc_bawatchdogtask, 0, mwl_bawatchdog_proc, sc); + + /* NB: insure BK queue is the lowest priority h/w queue */ + if (!mwl_tx_setup(sc, WME_AC_BK, MWL_WME_AC_BK)) { + if_printf(ifp, "unable to setup xmit queue for %s traffic!\n", + ieee80211_wme_acnames[WME_AC_BK]); + error = EIO; + goto bad2; + } + if (!mwl_tx_setup(sc, WME_AC_BE, MWL_WME_AC_BE) || + !mwl_tx_setup(sc, WME_AC_VI, MWL_WME_AC_VI) || + !mwl_tx_setup(sc, WME_AC_VO, MWL_WME_AC_VO)) { + /* + * Not enough hardware tx queues to properly do WME; + * just punt and assign them all to the same h/w queue. + * We could do a better job of this if, for example, + * we allocate queues when we switch from station to + * AP mode. + */ + if (sc->sc_ac2q[WME_AC_VI] != NULL) + mwl_tx_cleanupq(sc, sc->sc_ac2q[WME_AC_VI]); + if (sc->sc_ac2q[WME_AC_BE] != NULL) + mwl_tx_cleanupq(sc, sc->sc_ac2q[WME_AC_BE]); + sc->sc_ac2q[WME_AC_BE] = sc->sc_ac2q[WME_AC_BK]; + sc->sc_ac2q[WME_AC_VI] = sc->sc_ac2q[WME_AC_BK]; + sc->sc_ac2q[WME_AC_VO] = sc->sc_ac2q[WME_AC_BK]; + } + TASK_INIT(&sc->sc_txtask, 0, mwl_tx_proc, sc); + + ifp->if_softc = sc; + ifp->if_flags = IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST; + ifp->if_start = mwl_start; + ifp->if_watchdog = mwl_watchdog; + ifp->if_ioctl = mwl_ioctl; + ifp->if_init = mwl_init; + IFQ_SET_MAXLEN(&ifp->if_snd, IFQ_MAXLEN); + ifp->if_snd.ifq_drv_maxlen = IFQ_MAXLEN; + IFQ_SET_READY(&ifp->if_snd); + + ic->ic_ifp = ifp; + /* XXX not right but it's not used anywhere important */ + ic->ic_phytype = IEEE80211_T_OFDM; + ic->ic_opmode = IEEE80211_M_STA; + ic->ic_caps = + IEEE80211_C_STA /* station mode supported */ + | IEEE80211_C_HOSTAP /* hostap mode */ + | IEEE80211_C_MONITOR /* monitor mode */ +#if 0 + | IEEE80211_C_IBSS /* ibss, nee adhoc, mode */ + | IEEE80211_C_AHDEMO /* adhoc demo mode */ +#endif + | IEEE80211_C_WDS /* WDS supported */ + | IEEE80211_C_SHPREAMBLE /* short preamble supported */ + | IEEE80211_C_SHSLOT /* short slot time supported */ + | IEEE80211_C_WME /* WME/WMM supported */ + | IEEE80211_C_BURST /* xmit bursting supported */ + | IEEE80211_C_WPA /* capable of WPA1+WPA2 */ + | IEEE80211_C_BGSCAN /* capable of bg scanning */ + | IEEE80211_C_TXFRAG /* handle tx frags */ + | IEEE80211_C_TXPMGT /* capable of txpow mgt */ + | IEEE80211_C_DFS /* DFS supported */ + ; + + ic->ic_htcaps = + IEEE80211_HTCAP_SMPS_ENA /* SM PS mode enabled */ + | IEEE80211_HTCAP_CHWIDTH40 /* 40MHz channel width */ + | IEEE80211_HTCAP_SHORTGI20 /* short GI in 20MHz */ + | IEEE80211_HTCAP_SHORTGI40 /* short GI in 40MHz */ + | IEEE80211_HTCAP_RXSTBC_2STREAM/* 1-2 spatial streams */ +#if MWL_AGGR_SIZE == 7935 + | IEEE80211_HTCAP_MAXAMSDU_7935 /* max A-MSDU length */ +#else + | IEEE80211_HTCAP_MAXAMSDU_3839 /* max A-MSDU length */ +#endif +#if 0 + | IEEE80211_HTCAP_PSMP /* PSMP supported */ + | IEEE80211_HTCAP_40INTOLERANT /* 40MHz intolerant */ +#endif + /* s/w capabilities */ + | IEEE80211_HTC_HT /* HT operation */ + | IEEE80211_HTC_AMPDU /* tx A-MPDU */ + | IEEE80211_HTC_AMSDU /* tx A-MSDU */ + | IEEE80211_HTC_SMPS /* SMPS available */ + ; + + /* + * Mark h/w crypto support. + * XXX no way to query h/w support. + */ + ic->ic_cryptocaps |= IEEE80211_CRYPTO_WEP + | IEEE80211_CRYPTO_AES_CCM + | IEEE80211_CRYPTO_TKIP + | IEEE80211_CRYPTO_TKIPMIC + ; + /* + * Transmit requires space in the packet for a special + * format transmit record and optional padding between + * this record and the payload. Ask the net80211 layer + * to arrange this when encapsulating packets so we can + * add it efficiently. + */ + ic->ic_headroom = sizeof(struct mwltxrec) - + sizeof(struct ieee80211_frame); + + /* call MI attach routine. */ + ieee80211_ifattach(ic, sc->sc_hwspecs.macAddr); + ic->ic_setregdomain = mwl_setregdomain; + ic->ic_getradiocaps = mwl_getradiocaps; + /* override default methods */ + ic->ic_raw_xmit = mwl_raw_xmit; + ic->ic_newassoc = mwl_newassoc; + ic->ic_updateslot = mwl_updateslot; + ic->ic_update_mcast = mwl_update_mcast; + ic->ic_update_promisc = mwl_update_promisc; + ic->ic_wme.wme_update = mwl_wme_update; + + ic->ic_node_alloc = mwl_node_alloc; + sc->sc_node_cleanup = ic->ic_node_cleanup; + ic->ic_node_cleanup = mwl_node_cleanup; + sc->sc_node_drain = ic->ic_node_drain; + ic->ic_node_drain = mwl_node_drain; + ic->ic_node_getsignal = mwl_node_getsignal; + ic->ic_node_getmimoinfo = mwl_node_getmimoinfo; + + ic->ic_scan_start = mwl_scan_start; + ic->ic_scan_end = mwl_scan_end; + ic->ic_set_channel = mwl_set_channel; + + sc->sc_recv_action = ic->ic_recv_action; + ic->ic_recv_action = mwl_recv_action; + sc->sc_addba_request = ic->ic_addba_request; + ic->ic_addba_request = mwl_addba_request; + sc->sc_addba_response = ic->ic_addba_response; + ic->ic_addba_response = mwl_addba_response; + sc->sc_addba_stop = ic->ic_addba_stop; + ic->ic_addba_stop = mwl_addba_stop; + + ic->ic_vap_create = mwl_vap_create; + ic->ic_vap_delete = mwl_vap_delete; + + ieee80211_radiotap_attach(ic, + &sc->sc_tx_th.wt_ihdr, sizeof(sc->sc_tx_th), + MWL_TX_RADIOTAP_PRESENT, + &sc->sc_rx_th.wr_ihdr, sizeof(sc->sc_rx_th), + MWL_RX_RADIOTAP_PRESENT); + /* + * Setup dynamic sysctl's now that country code and + * regdomain are available from the hal. + */ + mwl_sysctlattach(sc); + + if (bootverbose) + ieee80211_announce(ic); + mwl_announce(sc); + return 0; +bad2: + mwl_dma_cleanup(sc); +bad1: + mwl_hal_detach(mh); +bad: + if_free(ifp); + sc->sc_invalid = 1; + return error; +} + +int +mwl_detach(struct mwl_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + DPRINTF(sc, MWL_DEBUG_ANY, "%s: if_flags %x\n", + __func__, ifp->if_flags); + + mwl_stop(ifp, 1); + /* + * NB: the order of these is important: + * o call the 802.11 layer before detaching the hal to + * insure callbacks into the driver to delete global + * key cache entries can be handled + * o reclaim the tx queue data structures after calling + * the 802.11 layer as we'll get called back to reclaim + * node state and potentially want to use them + * o to cleanup the tx queues the hal is called, so detach + * it last + * Other than that, it's straightforward... + */ + ieee80211_ifdetach(ic); + mwl_dma_cleanup(sc); + mwl_tx_cleanup(sc); + mwl_hal_detach(sc->sc_mh); + if_free(ifp); + + return 0; +} + +/* + * MAC address handling for multiple BSS on the same radio. + * The first vap uses the MAC address from the EEPROM. For + * subsequent vap's we set the U/L bit (bit 1) in the MAC + * address and use the next six bits as an index. + */ +static void +assign_address(struct mwl_softc *sc, uint8_t mac[IEEE80211_ADDR_LEN], int clone) +{ + int i; + + if (clone && mwl_hal_ismbsscapable(sc->sc_mh)) { + /* NB: we only do this if h/w supports multiple bssid */ + for (i = 0; i < 32; i++) + if ((sc->sc_bssidmask & (1<<i)) == 0) + break; + if (i != 0) + mac[0] |= (i << 2)|0x2; + } else + i = 0; + sc->sc_bssidmask |= 1<<i; + if (i == 0) + sc->sc_nbssid0++; +} + +static void +reclaim_address(struct mwl_softc *sc, uint8_t mac[IEEE80211_ADDR_LEN]) +{ + int i = mac[0] >> 2; + if (i != 0 || --sc->sc_nbssid0 == 0) + sc->sc_bssidmask &= ~(1<<i); +} + +static struct ieee80211vap * +mwl_vap_create(struct ieee80211com *ic, + const char name[IFNAMSIZ], int unit, int opmode, int flags, + const uint8_t bssid[IEEE80211_ADDR_LEN], + const uint8_t mac0[IEEE80211_ADDR_LEN]) +{ + struct ifnet *ifp = ic->ic_ifp; + struct mwl_softc *sc = ifp->if_softc; + struct mwl_hal *mh = sc->sc_mh; + struct ieee80211vap *vap, *apvap; + struct mwl_hal_vap *hvap; + struct mwl_vap *mvp; + uint8_t mac[IEEE80211_ADDR_LEN]; + + IEEE80211_ADDR_COPY(mac, mac0); + switch (opmode) { + case IEEE80211_M_HOSTAP: + if ((flags & IEEE80211_CLONE_MACADDR) == 0) + assign_address(sc, mac, flags & IEEE80211_CLONE_BSSID); + hvap = mwl_hal_newvap(mh, MWL_HAL_AP, mac); + if (hvap == NULL) { + if ((flags & IEEE80211_CLONE_MACADDR) == 0) + reclaim_address(sc, mac); + return NULL; + } + break; + case IEEE80211_M_STA: + if ((flags & IEEE80211_CLONE_MACADDR) == 0) + assign_address(sc, mac, flags & IEEE80211_CLONE_BSSID); + hvap = mwl_hal_newvap(mh, MWL_HAL_STA, mac); + if (hvap == NULL) { + if ((flags & IEEE80211_CLONE_MACADDR) == 0) + reclaim_address(sc, mac); + return NULL; + } + /* no h/w beacon miss support; always use s/w */ + flags |= IEEE80211_CLONE_NOBEACONS; + break; + case IEEE80211_M_WDS: + hvap = NULL; /* NB: we use associated AP vap */ + if (sc->sc_napvaps == 0) + return NULL; /* no existing AP vap */ + break; + case IEEE80211_M_MONITOR: + hvap = NULL; + break; + case IEEE80211_M_IBSS: + case IEEE80211_M_AHDEMO: + default: + return NULL; + } + + mvp = (struct mwl_vap *) malloc(sizeof(struct mwl_vap), + M_80211_VAP, M_NOWAIT | M_ZERO); + if (mvp == NULL) { + if (hvap != NULL) { + mwl_hal_delvap(hvap); + if ((flags & IEEE80211_CLONE_MACADDR) == 0) + reclaim_address(sc, mac); + } + /* XXX msg */ + return NULL; + } + mvp->mv_hvap = hvap; + if (opmode == IEEE80211_M_WDS) { + /* + * WDS vaps must have an associated AP vap; find one. + * XXX not right. + */ + TAILQ_FOREACH(apvap, &ic->ic_vaps, iv_next) + if (apvap->iv_opmode == IEEE80211_M_HOSTAP) { + mvp->mv_ap_hvap = MWL_VAP(apvap)->mv_hvap; + break; + } + KASSERT(mvp->mv_ap_hvap != NULL, ("no ap vap")); + } + vap = &mvp->mv_vap; + ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid, mac); + if (hvap != NULL) + IEEE80211_ADDR_COPY(vap->iv_myaddr, mac); + /* override with driver methods */ + mvp->mv_newstate = vap->iv_newstate; + vap->iv_newstate = mwl_newstate; + vap->iv_max_keyix = 0; /* XXX */ + vap->iv_key_alloc = mwl_key_alloc; + vap->iv_key_delete = mwl_key_delete; + vap->iv_key_set = mwl_key_set; +#ifdef MWL_HOST_PS_SUPPORT + if (opmode == IEEE80211_M_HOSTAP) { + vap->iv_update_ps = mwl_update_ps; + mvp->mv_set_tim = vap->iv_set_tim; + vap->iv_set_tim = mwl_set_tim; + } +#endif + vap->iv_reset = mwl_reset; + vap->iv_update_beacon = mwl_beacon_update; + + /* override max aid so sta's cannot assoc when we're out of sta id's */ + vap->iv_max_aid = MWL_MAXSTAID; + /* override default A-MPDU rx parameters */ + vap->iv_ampdu_rxmax = IEEE80211_HTCAP_MAXRXAMPDU_64K; + vap->iv_ampdu_density = IEEE80211_HTCAP_MPDUDENSITY_4; + + /* complete setup */ + ieee80211_vap_attach(vap, mwl_media_change, ieee80211_media_status); + + switch (vap->iv_opmode) { + case IEEE80211_M_HOSTAP: + case IEEE80211_M_STA: + /* + * Setup sta db entry for local address. + */ + mwl_localstadb(vap); + if (vap->iv_opmode == IEEE80211_M_HOSTAP) + sc->sc_napvaps++; + else + sc->sc_nstavaps++; + break; + case IEEE80211_M_WDS: + sc->sc_nwdsvaps++; + break; + default: + break; + } + /* + * Setup overall operating mode. + */ + if (sc->sc_napvaps) + ic->ic_opmode = IEEE80211_M_HOSTAP; + else if (sc->sc_nstavaps) + ic->ic_opmode = IEEE80211_M_STA; + else + ic->ic_opmode = opmode; + + return vap; +} + +static void +mwl_vap_delete(struct ieee80211vap *vap) +{ + struct mwl_vap *mvp = MWL_VAP(vap); + struct ifnet *parent = vap->iv_ic->ic_ifp; + struct mwl_softc *sc = parent->if_softc; + struct mwl_hal *mh = sc->sc_mh; + struct mwl_hal_vap *hvap = mvp->mv_hvap; + enum ieee80211_opmode opmode = vap->iv_opmode; + + /* XXX disallow ap vap delete if WDS still present */ + if (parent->if_drv_flags & IFF_DRV_RUNNING) { + /* quiesce h/w while we remove the vap */ + mwl_hal_intrset(mh, 0); /* disable interrupts */ + } + ieee80211_vap_detach(vap); + switch (opmode) { + case IEEE80211_M_HOSTAP: + case IEEE80211_M_STA: + KASSERT(hvap != NULL, ("no hal vap handle")); + (void) mwl_hal_delstation(hvap, vap->iv_myaddr); + mwl_hal_delvap(hvap); + if (opmode == IEEE80211_M_HOSTAP) + sc->sc_napvaps--; + else + sc->sc_nstavaps--; + /* XXX don't do it for IEEE80211_CLONE_MACADDR */ + reclaim_address(sc, vap->iv_myaddr); + break; + case IEEE80211_M_WDS: + sc->sc_nwdsvaps--; + break; + default: + break; + } + mwl_cleartxq(sc, vap); + free(mvp, M_80211_VAP); + if (parent->if_drv_flags & IFF_DRV_RUNNING) + mwl_hal_intrset(mh, sc->sc_imask); +} + +void +mwl_suspend(struct mwl_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + + DPRINTF(sc, MWL_DEBUG_ANY, "%s: if_flags %x\n", + __func__, ifp->if_flags); + + mwl_stop(ifp, 1); +} + +void +mwl_resume(struct mwl_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + + DPRINTF(sc, MWL_DEBUG_ANY, "%s: if_flags %x\n", + __func__, ifp->if_flags); + + if (ifp->if_flags & IFF_UP) + mwl_init(sc); +} + +void +mwl_shutdown(void *arg) +{ + struct mwl_softc *sc = arg; + + mwl_stop(sc->sc_ifp, 1); +} + +/* + * Interrupt handler. Most of the actual processing is deferred. + */ +void +mwl_intr(void *arg) +{ + struct mwl_softc *sc = arg; + struct mwl_hal *mh = sc->sc_mh; + uint32_t status; + + if (sc->sc_invalid) { + /* + * The hardware is not ready/present, don't touch anything. + * Note this can happen early on if the IRQ is shared. + */ + DPRINTF(sc, MWL_DEBUG_ANY, "%s: invalid; ignored\n", __func__); + return; + } + /* + * Figure out the reason(s) for the interrupt. + */ + mwl_hal_getisr(mh, &status); /* NB: clears ISR too */ + if (status == 0) /* must be a shared irq */ + return; + + DPRINTF(sc, MWL_DEBUG_INTR, "%s: status 0x%x imask 0x%x\n", + __func__, status, sc->sc_imask); + if (status & MACREG_A2HRIC_BIT_RX_RDY) + taskqueue_enqueue(sc->sc_tq, &sc->sc_rxtask); + if (status & MACREG_A2HRIC_BIT_TX_DONE) + taskqueue_enqueue(sc->sc_tq, &sc->sc_txtask); + if (status & MACREG_A2HRIC_BIT_BA_WATCHDOG) + taskqueue_enqueue(sc->sc_tq, &sc->sc_bawatchdogtask); + if (status & MACREG_A2HRIC_BIT_OPC_DONE) + mwl_hal_cmddone(mh); + if (status & MACREG_A2HRIC_BIT_MAC_EVENT) { + ; + } + if (status & MACREG_A2HRIC_BIT_ICV_ERROR) { + /* TKIP ICV error */ + sc->sc_stats.mst_rx_badtkipicv++; + } + if (status & MACREG_A2HRIC_BIT_QUEUE_EMPTY) { + /* 11n aggregation queue is empty, re-fill */ + ; + } + if (status & MACREG_A2HRIC_BIT_QUEUE_FULL) { + ; + } + if (status & MACREG_A2HRIC_BIT_RADAR_DETECT) { + /* radar detected, process event */ + taskqueue_enqueue(sc->sc_tq, &sc->sc_radartask); + } + if (status & MACREG_A2HRIC_BIT_CHAN_SWITCH) { + /* DFS channel switch */ + taskqueue_enqueue(sc->sc_tq, &sc->sc_chanswitchtask); + } +} + +static void +mwl_radar_proc(void *arg, int pending) +{ + struct mwl_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + DPRINTF(sc, MWL_DEBUG_ANY, "%s: radar detected, pending %u\n", + __func__, pending); + + sc->sc_stats.mst_radardetect++; + + IEEE80211_LOCK(ic); + ieee80211_dfs_notify_radar(ic, ic->ic_curchan); + IEEE80211_UNLOCK(ic); +} + +static void +mwl_chanswitch_proc(void *arg, int pending) +{ + struct mwl_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + DPRINTF(sc, MWL_DEBUG_ANY, "%s: channel switch notice, pending %u\n", + __func__, pending); + + IEEE80211_LOCK(ic); + sc->sc_csapending = 0; + ieee80211_csa_completeswitch(ic); + IEEE80211_UNLOCK(ic); +} + +static void +mwl_bawatchdog(const MWL_HAL_BASTREAM *sp) +{ + struct ieee80211_node *ni = sp->data[0]; + + /* send DELBA and drop the stream */ + ieee80211_ampdu_stop(ni, sp->data[1], IEEE80211_REASON_UNSPECIFIED); +} + +static void +mwl_bawatchdog_proc(void *arg, int pending) +{ + struct mwl_softc *sc = arg; + struct mwl_hal *mh = sc->sc_mh; + const MWL_HAL_BASTREAM *sp; + uint8_t bitmap, n; + + sc->sc_stats.mst_bawatchdog++; + + if (mwl_hal_getwatchdogbitmap(mh, &bitmap) != 0) { + DPRINTF(sc, MWL_DEBUG_AMPDU, + "%s: could not get bitmap\n", __func__); + sc->sc_stats.mst_bawatchdog_failed++; + return; + } + DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: bitmap 0x%x\n", __func__, bitmap); + if (bitmap == 0xff) { + n = 0; + /* disable all ba streams */ + for (bitmap = 0; bitmap < 8; bitmap++) { + sp = mwl_hal_bastream_lookup(mh, bitmap); + if (sp != NULL) { + mwl_bawatchdog(sp); + n++; + } + } + if (n == 0) { + DPRINTF(sc, MWL_DEBUG_AMPDU, + "%s: no BA streams found\n", __func__); + sc->sc_stats.mst_bawatchdog_empty++; + } + } else if (bitmap != 0xaa) { + /* disable a single ba stream */ + sp = mwl_hal_bastream_lookup(mh, bitmap); + if (sp != NULL) { + mwl_bawatchdog(sp); + } else { + DPRINTF(sc, MWL_DEBUG_AMPDU, + "%s: no BA stream %d\n", __func__, bitmap); + sc->sc_stats.mst_bawatchdog_notfound++; + } + } +} + +/* + * Convert net80211 channel to a HAL channel. + */ +static void +mwl_mapchan(MWL_HAL_CHANNEL *hc, const struct ieee80211_channel *chan) +{ + hc->channel = chan->ic_ieee; + + *(uint32_t *)&hc->channelFlags = 0; + if (IEEE80211_IS_CHAN_2GHZ(chan)) + hc->channelFlags.FreqBand = MWL_FREQ_BAND_2DOT4GHZ; + else if (IEEE80211_IS_CHAN_5GHZ(chan)) + hc->channelFlags.FreqBand = MWL_FREQ_BAND_5GHZ; + if (IEEE80211_IS_CHAN_HT40(chan)) { + hc->channelFlags.ChnlWidth = MWL_CH_40_MHz_WIDTH; + if (IEEE80211_IS_CHAN_HT40U(chan)) + hc->channelFlags.ExtChnlOffset = MWL_EXT_CH_ABOVE_CTRL_CH; + else + hc->channelFlags.ExtChnlOffset = MWL_EXT_CH_BELOW_CTRL_CH; + } else + hc->channelFlags.ChnlWidth = MWL_CH_20_MHz_WIDTH; + /* XXX 10MHz channels */ +} + +/* + * Inform firmware of our tx/rx dma setup. The BAR 0 + * writes below are for compatibility with older firmware. + * For current firmware we send this information with a + * cmd block via mwl_hal_sethwdma. + */ +static int +mwl_setupdma(struct mwl_softc *sc) +{ + int error, i; + + sc->sc_hwdma.rxDescRead = sc->sc_rxdma.dd_desc_paddr; + WR4(sc, sc->sc_hwspecs.rxDescRead, sc->sc_hwdma.rxDescRead); + WR4(sc, sc->sc_hwspecs.rxDescWrite, sc->sc_hwdma.rxDescRead); + + for (i = 0; i < MWL_NUM_TX_QUEUES; i++) { + struct mwl_txq *txq = &sc->sc_txq[i]; + sc->sc_hwdma.wcbBase[i] = txq->dma.dd_desc_paddr; + WR4(sc, sc->sc_hwspecs.wcbBase[i], sc->sc_hwdma.wcbBase[i]); + } + sc->sc_hwdma.maxNumTxWcb = mwl_txbuf; + sc->sc_hwdma.maxNumWCB = MWL_NUM_TX_QUEUES; + + error = mwl_hal_sethwdma(sc->sc_mh, &sc->sc_hwdma); + if (error != 0) { + device_printf(sc->sc_dev, + "unable to setup tx/rx dma; hal status %u\n", error); + /* XXX */ + } + return error; +} + +/* + * Inform firmware of tx rate parameters. + * Called after a channel change. + */ +static int +mwl_setcurchanrates(struct mwl_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + const struct ieee80211_rateset *rs; + MWL_HAL_TXRATE rates; + + memset(&rates, 0, sizeof(rates)); + rs = ieee80211_get_suprates(ic, ic->ic_curchan); + /* rate used to send management frames */ + rates.MgtRate = rs->rs_rates[0] & IEEE80211_RATE_VAL; + /* rate used to send multicast frames */ + rates.McastRate = rates.MgtRate; + + return mwl_hal_settxrate_auto(sc->sc_mh, &rates); +} + +/* + * Inform firmware of tx rate parameters. Called whenever + * user-settable params change and after a channel change. + */ +static int +mwl_setrates(struct ieee80211vap *vap) +{ + struct mwl_vap *mvp = MWL_VAP(vap); + struct ieee80211_node *ni = vap->iv_bss; + const struct ieee80211_txparam *tp = ni->ni_txparms; + MWL_HAL_TXRATE rates; + + KASSERT(vap->iv_state == IEEE80211_S_RUN, ("state %d", vap->iv_state)); + + /* + * Update the h/w rate map. + * NB: 0x80 for MCS is passed through unchanged + */ + memset(&rates, 0, sizeof(rates)); + /* rate used to send management frames */ + rates.MgtRate = tp->mgmtrate; + /* rate used to send multicast frames */ + rates.McastRate = tp->mcastrate; + + /* while here calculate EAPOL fixed rate cookie */ + mvp->mv_eapolformat = htole16(mwl_calcformat(rates.MgtRate, ni)); + + return mwl_hal_settxrate(mvp->mv_hvap, RATE_AUTO, &rates); +} + +/* + * Setup a fixed xmit rate cookie for EAPOL frames. + */ +static void +mwl_seteapolformat(struct ieee80211vap *vap) +{ + struct mwl_vap *mvp = MWL_VAP(vap); + struct ieee80211_node *ni = vap->iv_bss; + enum ieee80211_phymode mode; + uint8_t rate; + + KASSERT(vap->iv_state == IEEE80211_S_RUN, ("state %d", vap->iv_state)); + + mode = ieee80211_chan2mode(ni->ni_chan); + /* + * Use legacy rates when operating a mixed HT+non-HT bss. + * NB: this may violate POLA for sta and wds vap's. + */ + if (mode == IEEE80211_MODE_11NA && + (vap->iv_flags_ext & IEEE80211_FEXT_PUREN) == 0) + rate = vap->iv_txparms[IEEE80211_MODE_11A].mgmtrate; + else if (mode == IEEE80211_MODE_11NG && + (vap->iv_flags_ext & IEEE80211_FEXT_PUREN) == 0) + rate = vap->iv_txparms[IEEE80211_MODE_11G].mgmtrate; + else + rate = vap->iv_txparms[mode].mgmtrate; + + mvp->mv_eapolformat = htole16(mwl_calcformat(rate, ni)); +} + +/* + * Map SKU+country code to region code for radar bin'ing. + */ +static int +mwl_map2regioncode(const struct ieee80211_regdomain *rd) +{ + switch (rd->regdomain) { + case SKU_FCC: + case SKU_FCC3: + return DOMAIN_CODE_FCC; + case SKU_CA: + return DOMAIN_CODE_IC; + case SKU_ETSI: + case SKU_ETSI2: + case SKU_ETSI3: + if (rd->country == CTRY_SPAIN) + return DOMAIN_CODE_SPAIN; + if (rd->country == CTRY_FRANCE || rd->country == CTRY_FRANCE2) + return DOMAIN_CODE_FRANCE; + /* XXX force 1.3.1 radar type */ + return DOMAIN_CODE_ETSI_131; + case SKU_JAPAN: + return DOMAIN_CODE_MKK; + case SKU_ROW: + return DOMAIN_CODE_DGT; /* Taiwan */ + case SKU_APAC: + case SKU_APAC2: + case SKU_APAC3: + return DOMAIN_CODE_AUS; /* Australia */ + } + /* XXX KOREA? */ + return DOMAIN_CODE_FCC; /* XXX? */ +} + +static int +mwl_hal_reset(struct mwl_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct mwl_hal *mh = sc->sc_mh; + + mwl_hal_setantenna(mh, WL_ANTENNATYPE_RX, sc->sc_rxantenna); + mwl_hal_setantenna(mh, WL_ANTENNATYPE_TX, sc->sc_txantenna); + mwl_hal_setradio(mh, 1, WL_AUTO_PREAMBLE); + mwl_hal_setwmm(sc->sc_mh, (ic->ic_flags & IEEE80211_F_WME) != 0); + mwl_chan_set(sc, ic->ic_curchan); + mwl_hal_setrateadaptmode(mh, ic->ic_regdomain.location == 'O'); + mwl_hal_setoptimizationlevel(mh, + (ic->ic_flags & IEEE80211_F_BURST) != 0); + + mwl_hal_setregioncode(mh, mwl_map2regioncode(&ic->ic_regdomain)); + + return 1; +} + +static int +mwl_init_locked(struct mwl_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct mwl_hal *mh = sc->sc_mh; + int error = 0; + + DPRINTF(sc, MWL_DEBUG_ANY, "%s: if_flags 0x%x\n", + __func__, ifp->if_flags); + + MWL_LOCK_ASSERT(sc); + + /* + * Stop anything previously setup. This is safe + * whether this is the first time through or not. + */ + mwl_stop_locked(ifp, 0); + + /* + * Push vap-independent state to the firmware. + */ + if (!mwl_hal_reset(sc)) { + if_printf(ifp, "unable to reset hardware\n"); + return EIO; + } + + /* + * Setup recv (once); transmit is already good to go. + */ + error = mwl_startrecv(sc); + if (error != 0) { + if_printf(ifp, "unable to start recv logic\n"); + return error; + } + + /* + * Enable interrupts. + */ + sc->sc_imask = MACREG_A2HRIC_BIT_RX_RDY + | MACREG_A2HRIC_BIT_TX_DONE + | MACREG_A2HRIC_BIT_OPC_DONE +#if 0 + | MACREG_A2HRIC_BIT_MAC_EVENT +#endif + | MACREG_A2HRIC_BIT_ICV_ERROR + | MACREG_A2HRIC_BIT_RADAR_DETECT + | MACREG_A2HRIC_BIT_CHAN_SWITCH +#if 0 + | MACREG_A2HRIC_BIT_QUEUE_EMPTY +#endif + | MACREG_A2HRIC_BIT_BA_WATCHDOG + ; + + ifp->if_drv_flags |= IFF_DRV_RUNNING; + mwl_hal_intrset(mh, sc->sc_imask); + + return 0; +} + +static void +mwl_init(void *arg) +{ + struct mwl_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + int error = 0; + + DPRINTF(sc, MWL_DEBUG_ANY, "%s: if_flags 0x%x\n", + __func__, ifp->if_flags); + + MWL_LOCK(sc); + error = mwl_init_locked(sc); + MWL_UNLOCK(sc); + + if (error == 0) + ieee80211_start_all(ic); /* start all vap's */ +} + +static void +mwl_stop_locked(struct ifnet *ifp, int disable) +{ + struct mwl_softc *sc = ifp->if_softc; + + DPRINTF(sc, MWL_DEBUG_ANY, "%s: invalid %u if_flags 0x%x\n", + __func__, sc->sc_invalid, ifp->if_flags); + + MWL_LOCK_ASSERT(sc); + if (ifp->if_drv_flags & IFF_DRV_RUNNING) { + /* + * Shutdown the hardware and driver. + */ + ifp->if_drv_flags &= ~IFF_DRV_RUNNING; + ifp->if_timer = 0; + mwl_draintxq(sc); + } +} + +static void +mwl_stop(struct ifnet *ifp, int disable) +{ + struct mwl_softc *sc = ifp->if_softc; + + MWL_LOCK(sc); + mwl_stop_locked(ifp, disable); + MWL_UNLOCK(sc); +} + +static int +mwl_reset_vap(struct ieee80211vap *vap, int state) +{ + struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; + struct ieee80211com *ic = vap->iv_ic; + + if (state == IEEE80211_S_RUN) + mwl_setrates(vap); + /* XXX off by 1? */ + mwl_hal_setrtsthreshold(hvap, vap->iv_rtsthreshold); + /* XXX auto? 20/40 split? */ + mwl_hal_sethtgi(hvap, (vap->iv_flags_ext & + (IEEE80211_FEXT_SHORTGI20|IEEE80211_FEXT_SHORTGI40)) ? 1 : 0); + mwl_hal_setnprot(hvap, ic->ic_htprotmode == IEEE80211_PROT_NONE ? + HTPROTECT_NONE : HTPROTECT_AUTO); + /* XXX txpower cap */ + + /* re-setup beacons */ + if (state == IEEE80211_S_RUN && + (vap->iv_opmode == IEEE80211_M_HOSTAP || + vap->iv_opmode == IEEE80211_M_IBSS)) { + mwl_setapmode(vap, vap->iv_bss->ni_chan); + mwl_hal_setnprotmode(hvap, + MS(ic->ic_curhtprotmode, IEEE80211_HTINFO_OPMODE)); + return mwl_beacon_setup(vap); + } + return 0; +} + +/* + * Reset the hardware w/o losing operational state. + * Used to to reset or reload hardware state for a vap. + */ +static int +mwl_reset(struct ieee80211vap *vap, u_long cmd) +{ + struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; + int error = 0; + + if (hvap != NULL) { /* WDS, MONITOR, etc. */ + struct ieee80211com *ic = vap->iv_ic; + struct ifnet *ifp = ic->ic_ifp; + struct mwl_softc *sc = ifp->if_softc; + struct mwl_hal *mh = sc->sc_mh; + + /* XXX do we need to disable interrupts? */ + mwl_hal_intrset(mh, 0); /* disable interrupts */ + error = mwl_reset_vap(vap, vap->iv_state); + mwl_hal_intrset(mh, sc->sc_imask); + } + return error; +} + +/* + * Allocate a tx buffer for sending a frame. The + * packet is assumed to have the WME AC stored so + * we can use it to select the appropriate h/w queue. + */ +static struct mwl_txbuf * +mwl_gettxbuf(struct mwl_softc *sc, struct mwl_txq *txq) +{ + struct mwl_txbuf *bf; + + /* + * Grab a TX buffer and associated resources. + */ + MWL_TXQ_LOCK(txq); + bf = STAILQ_FIRST(&txq->free); + if (bf != NULL) { + STAILQ_REMOVE_HEAD(&txq->free, bf_list); + txq->nfree--; + } + MWL_TXQ_UNLOCK(txq); + if (bf == NULL) + DPRINTF(sc, MWL_DEBUG_XMIT, + "%s: out of xmit buffers on q %d\n", __func__, txq->qnum); + return bf; +} + +/* + * Return a tx buffer to the queue it came from. Note there + * are two cases because we must preserve the order of buffers + * as it reflects the fixed order of descriptors in memory + * (the firmware pre-fetches descriptors so we cannot reorder). + */ +static void +mwl_puttxbuf_head(struct mwl_txq *txq, struct mwl_txbuf *bf) +{ + bf->bf_m = NULL; + bf->bf_node = NULL; + MWL_TXQ_LOCK(txq); + STAILQ_INSERT_HEAD(&txq->free, bf, bf_list); + txq->nfree++; + MWL_TXQ_UNLOCK(txq); +} + +static void +mwl_puttxbuf_tail(struct mwl_txq *txq, struct mwl_txbuf *bf) +{ + bf->bf_m = NULL; + bf->bf_node = NULL; + MWL_TXQ_LOCK(txq); + STAILQ_INSERT_TAIL(&txq->free, bf, bf_list); + txq->nfree++; + MWL_TXQ_UNLOCK(txq); +} + +static void +mwl_start(struct ifnet *ifp) +{ + struct mwl_softc *sc = ifp->if_softc; + struct ieee80211_node *ni; + struct mwl_txbuf *bf; + struct mbuf *m; + struct mwl_txq *txq = NULL; /* XXX silence gcc */ + int nqueued; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0 || sc->sc_invalid) + return; + nqueued = 0; + for (;;) { + bf = NULL; + IFQ_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + /* + * Grab the node for the destination. + */ + ni = (struct ieee80211_node *) m->m_pkthdr.rcvif; + KASSERT(ni != NULL, ("no node")); + m->m_pkthdr.rcvif = NULL; /* committed, clear ref */ + /* + * Grab a TX buffer and associated resources. + * We honor the classification by the 802.11 layer. + */ + txq = sc->sc_ac2q[M_WME_GETAC(m)]; + bf = mwl_gettxbuf(sc, txq); + if (bf == NULL) { + m_freem(m); + ieee80211_free_node(ni); +#ifdef MWL_TX_NODROP + sc->sc_stats.mst_tx_qstop++; + /* XXX blocks other traffic */ + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + break; +#else + DPRINTF(sc, MWL_DEBUG_XMIT, + "%s: tail drop on q %d\n", __func__, txq->qnum); + sc->sc_stats.mst_tx_qdrop++; + continue; +#endif /* MWL_TX_NODROP */ + } + + /* + * Pass the frame to the h/w for transmission. + */ + if (mwl_tx_start(sc, ni, bf, m)) { + ifp->if_oerrors++; + mwl_puttxbuf_head(txq, bf); + ieee80211_free_node(ni); + continue; + } + nqueued++; + if (nqueued >= mwl_txcoalesce) { + /* + * Poke the firmware to process queued frames; + * see below about (lack of) locking. + */ + nqueued = 0; + mwl_hal_txstart(sc->sc_mh, 0/*XXX*/); + } + } + if (nqueued) { + /* + * NB: We don't need to lock against tx done because + * this just prods the firmware to check the transmit + * descriptors. The firmware will also start fetching + * descriptors by itself if it notices new ones are + * present when it goes to deliver a tx done interrupt + * to the host. So if we race with tx done processing + * it's ok. Delivering the kick here rather than in + * mwl_tx_start is an optimization to avoid poking the + * firmware for each packet. + * + * NB: the queue id isn't used so 0 is ok. + */ + mwl_hal_txstart(sc->sc_mh, 0/*XXX*/); + } +} + +static int +mwl_raw_xmit(struct ieee80211_node *ni, struct mbuf *m, + const struct ieee80211_bpf_params *params) +{ + struct ieee80211com *ic = ni->ni_ic; + struct ifnet *ifp = ic->ic_ifp; + struct mwl_softc *sc = ifp->if_softc; + struct mwl_txbuf *bf; + struct mwl_txq *txq; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0 || sc->sc_invalid) { + ieee80211_free_node(ni); + m_freem(m); + return ENETDOWN; + } + /* + * Grab a TX buffer and associated resources. + * Note that we depend on the classification + * by the 802.11 layer to get to the right h/w + * queue. Management frames must ALWAYS go on + * queue 1 but we cannot just force that here + * because we may receive non-mgt frames. + */ + txq = sc->sc_ac2q[M_WME_GETAC(m)]; + bf = mwl_gettxbuf(sc, txq); + if (bf == NULL) { + sc->sc_stats.mst_tx_qstop++; + /* XXX blocks other traffic */ + ifp->if_drv_flags |= IFF_DRV_OACTIVE; + ieee80211_free_node(ni); + m_freem(m); + return ENOBUFS; + } + /* + * Pass the frame to the h/w for transmission. + */ + if (mwl_tx_start(sc, ni, bf, m)) { + ifp->if_oerrors++; + mwl_puttxbuf_head(txq, bf); + + ieee80211_free_node(ni); + return EIO; /* XXX */ + } + /* + * NB: We don't need to lock against tx done because + * this just prods the firmware to check the transmit + * descriptors. The firmware will also start fetching + * descriptors by itself if it notices new ones are + * present when it goes to deliver a tx done interrupt + * to the host. So if we race with tx done processing + * it's ok. Delivering the kick here rather than in + * mwl_tx_start is an optimization to avoid poking the + * firmware for each packet. + * + * NB: the queue id isn't used so 0 is ok. + */ + mwl_hal_txstart(sc->sc_mh, 0/*XXX*/); + return 0; +} + +static int +mwl_media_change(struct ifnet *ifp) +{ + struct ieee80211vap *vap = ifp->if_softc; + int error; + + error = ieee80211_media_change(ifp); + /* NB: only the fixed rate can change and that doesn't need a reset */ + if (error == ENETRESET) { + mwl_setrates(vap); + error = 0; + } + return error; +} + +#ifdef MWL_DEBUG +static void +mwl_keyprint(struct mwl_softc *sc, const char *tag, + const MWL_HAL_KEYVAL *hk, const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + static const char *ciphers[] = { + "WEP", + "TKIP", + "AES-CCM", + }; + int i, n; + + printf("%s: [%u] %-7s", tag, hk->keyIndex, ciphers[hk->keyTypeId]); + for (i = 0, n = hk->keyLen; i < n; i++) + printf(" %02x", hk->key.aes[i]); + printf(" mac %s", ether_sprintf(mac)); + if (hk->keyTypeId == KEY_TYPE_ID_TKIP) { + printf(" %s", "rxmic"); + for (i = 0; i < sizeof(hk->key.tkip.rxMic); i++) + printf(" %02x", hk->key.tkip.rxMic[i]); + printf(" txmic"); + for (i = 0; i < sizeof(hk->key.tkip.txMic); i++) + printf(" %02x", hk->key.tkip.txMic[i]); + } + printf(" flags 0x%x\n", hk->keyFlags); +} +#endif + +/* + * Allocate a key cache slot for a unicast key. The + * firmware handles key allocation and every station is + * guaranteed key space so we are always successful. + */ +static int +mwl_key_alloc(struct ieee80211vap *vap, struct ieee80211_key *k, + ieee80211_keyix *keyix, ieee80211_keyix *rxkeyix) +{ + struct mwl_softc *sc = vap->iv_ic->ic_ifp->if_softc; + + if (k->wk_keyix != IEEE80211_KEYIX_NONE || + (k->wk_flags & IEEE80211_KEY_GROUP)) { + if (!(&vap->iv_nw_keys[0] <= k && + k < &vap->iv_nw_keys[IEEE80211_WEP_NKID])) { + /* should not happen */ + DPRINTF(sc, MWL_DEBUG_KEYCACHE, + "%s: bogus group key\n", __func__); + return 0; + } + /* give the caller what they requested */ + *keyix = *rxkeyix = k - vap->iv_nw_keys; + } else { + /* + * Firmware handles key allocation. + */ + *keyix = *rxkeyix = 0; + } + return 1; +} + +/* + * Delete a key entry allocated by mwl_key_alloc. + */ +static int +mwl_key_delete(struct ieee80211vap *vap, const struct ieee80211_key *k) +{ + struct mwl_softc *sc = vap->iv_ic->ic_ifp->if_softc; + struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; + MWL_HAL_KEYVAL hk; + const uint8_t bcastaddr[IEEE80211_ADDR_LEN] = + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + if (hvap == NULL) { + if (vap->iv_opmode != IEEE80211_M_WDS) { + /* XXX monitor mode? */ + DPRINTF(sc, MWL_DEBUG_KEYCACHE, + "%s: no hvap for opmode %d\n", __func__, + vap->iv_opmode); + return 0; + } + hvap = MWL_VAP(vap)->mv_ap_hvap; + } + + DPRINTF(sc, MWL_DEBUG_KEYCACHE, "%s: delete key %u\n", + __func__, k->wk_keyix); + + memset(&hk, 0, sizeof(hk)); + hk.keyIndex = k->wk_keyix; + switch (k->wk_cipher->ic_cipher) { + case IEEE80211_CIPHER_WEP: + hk.keyTypeId = KEY_TYPE_ID_WEP; + break; + case IEEE80211_CIPHER_TKIP: + hk.keyTypeId = KEY_TYPE_ID_TKIP; + break; + case IEEE80211_CIPHER_AES_CCM: + hk.keyTypeId = KEY_TYPE_ID_AES; + break; + default: + /* XXX should not happen */ + DPRINTF(sc, MWL_DEBUG_KEYCACHE, "%s: unknown cipher %d\n", + __func__, k->wk_cipher->ic_cipher); + return 0; + } + return (mwl_hal_keyreset(hvap, &hk, bcastaddr) == 0); /*XXX*/ +} + +static __inline int +addgroupflags(MWL_HAL_KEYVAL *hk, const struct ieee80211_key *k) +{ + if (k->wk_flags & IEEE80211_KEY_GROUP) { + if (k->wk_flags & IEEE80211_KEY_XMIT) + hk->keyFlags |= KEY_FLAG_TXGROUPKEY; + if (k->wk_flags & IEEE80211_KEY_RECV) + hk->keyFlags |= KEY_FLAG_RXGROUPKEY; + return 1; + } else + return 0; +} + +/* + * Set the key cache contents for the specified key. Key cache + * slot(s) must already have been allocated by mwl_key_alloc. + */ +static int +mwl_key_set(struct ieee80211vap *vap, const struct ieee80211_key *k, + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ +#define GRPXMIT (IEEE80211_KEY_XMIT | IEEE80211_KEY_GROUP) +/* NB: static wep keys are marked GROUP+tx/rx; GTK will be tx or rx */ +#define IEEE80211_IS_STATICKEY(k) \ + (((k)->wk_flags & (GRPXMIT|IEEE80211_KEY_RECV)) == \ + (GRPXMIT|IEEE80211_KEY_RECV)) + struct mwl_softc *sc = vap->iv_ic->ic_ifp->if_softc; + struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; + const struct ieee80211_cipher *cip = k->wk_cipher; + const uint8_t *macaddr; + MWL_HAL_KEYVAL hk; + + KASSERT((k->wk_flags & IEEE80211_KEY_SWCRYPT) == 0, + ("s/w crypto set?")); + + if (hvap == NULL) { + if (vap->iv_opmode != IEEE80211_M_WDS) { + /* XXX monitor mode? */ + DPRINTF(sc, MWL_DEBUG_KEYCACHE, + "%s: no hvap for opmode %d\n", __func__, + vap->iv_opmode); + return 0; + } + hvap = MWL_VAP(vap)->mv_ap_hvap; + } + memset(&hk, 0, sizeof(hk)); + hk.keyIndex = k->wk_keyix; + switch (cip->ic_cipher) { + case IEEE80211_CIPHER_WEP: + hk.keyTypeId = KEY_TYPE_ID_WEP; + hk.keyLen = k->wk_keylen; + if (k->wk_keyix == vap->iv_def_txkey) + hk.keyFlags = KEY_FLAG_WEP_TXKEY; + if (!IEEE80211_IS_STATICKEY(k)) { + /* NB: WEP is never used for the PTK */ + (void) addgroupflags(&hk, k); + } + break; + case IEEE80211_CIPHER_TKIP: + hk.keyTypeId = KEY_TYPE_ID_TKIP; + hk.key.tkip.tsc.high = (uint32_t)(k->wk_keytsc >> 16); + hk.key.tkip.tsc.low = (uint16_t)k->wk_keytsc; + hk.keyFlags = KEY_FLAG_TSC_VALID | KEY_FLAG_MICKEY_VALID; + hk.keyLen = k->wk_keylen + IEEE80211_MICBUF_SIZE; + if (!addgroupflags(&hk, k)) + hk.keyFlags |= KEY_FLAG_PAIRWISE; + break; + case IEEE80211_CIPHER_AES_CCM: + hk.keyTypeId = KEY_TYPE_ID_AES; + hk.keyLen = k->wk_keylen; + if (!addgroupflags(&hk, k)) + hk.keyFlags |= KEY_FLAG_PAIRWISE; + break; + default: + /* XXX should not happen */ + DPRINTF(sc, MWL_DEBUG_KEYCACHE, "%s: unknown cipher %d\n", + __func__, k->wk_cipher->ic_cipher); + return 0; + } + /* + * NB: tkip mic keys get copied here too; the layout + * just happens to match that in ieee80211_key. + */ + memcpy(hk.key.aes, k->wk_key, hk.keyLen); + + /* + * Locate address of sta db entry for writing key; + * the convention unfortunately is somewhat different + * than how net80211, hostapd, and wpa_supplicant think. + */ + if (vap->iv_opmode == IEEE80211_M_STA) { + /* + * NB: keys plumbed before the sta reaches AUTH state + * will be discarded or written to the wrong sta db + * entry because iv_bss is meaningless. This is ok + * (right now) because we handle deferred plumbing of + * WEP keys when the sta reaches AUTH state. + */ + macaddr = vap->iv_bss->ni_bssid; + } else if (vap->iv_opmode == IEEE80211_M_WDS && + vap->iv_state != IEEE80211_S_RUN) { + /* + * Prior to RUN state a WDS vap will not it's BSS node + * setup so we will plumb the key to the wrong mac + * address (it'll be our local address). Workaround + * this for the moment by grabbing the correct address. + */ + macaddr = vap->iv_des_bssid; + } else if ((k->wk_flags & GRPXMIT) == GRPXMIT) + macaddr = vap->iv_myaddr; + else + macaddr = mac; + KEYPRINTF(sc, &hk, macaddr); + return (mwl_hal_keyset(hvap, &hk, macaddr) == 0); +#undef IEEE80211_IS_STATICKEY +#undef GRPXMIT +} + +/* unaligned little endian access */ +#define LE_READ_2(p) \ + ((uint16_t) \ + ((((const uint8_t *)(p))[0] ) | \ + (((const uint8_t *)(p))[1] << 8))) +#define LE_READ_4(p) \ + ((uint32_t) \ + ((((const uint8_t *)(p))[0] ) | \ + (((const uint8_t *)(p))[1] << 8) | \ + (((const uint8_t *)(p))[2] << 16) | \ + (((const uint8_t *)(p))[3] << 24))) + +/* + * Set the multicast filter contents into the hardware. + * XXX f/w has no support; just defer to the os. + */ +static void +mwl_setmcastfilter(struct mwl_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; +#if 0 + struct ether_multi *enm; + struct ether_multistep estep; + uint8_t macs[IEEE80211_ADDR_LEN*MWL_HAL_MCAST_MAX];/* XXX stack use */ + uint8_t *mp; + int nmc; + + mp = macs; + nmc = 0; + ETHER_FIRST_MULTI(estep, &sc->sc_ec, enm); + while (enm != NULL) { + /* XXX Punt on ranges. */ + if (nmc == MWL_HAL_MCAST_MAX || + !IEEE80211_ADDR_EQ(enm->enm_addrlo, enm->enm_addrhi)) { + ifp->if_flags |= IFF_ALLMULTI; + return; + } + IEEE80211_ADDR_COPY(mp, enm->enm_addrlo); + mp += IEEE80211_ADDR_LEN, nmc++; + ETHER_NEXT_MULTI(estep, enm); + } + ifp->if_flags &= ~IFF_ALLMULTI; + mwl_hal_setmcast(sc->sc_mh, nmc, macs); +#else + /* XXX no mcast filter support; we get everything */ + ifp->if_flags |= IFF_ALLMULTI; +#endif +} + +static int +mwl_mode_init(struct mwl_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct mwl_hal *mh = sc->sc_mh; + + /* + * NB: Ignore promisc in hostap mode; it's set by the + * bridge. This is wrong but we have no way to + * identify internal requests (from the bridge) + * versus external requests such as for tcpdump. + */ + mwl_hal_setpromisc(mh, (ifp->if_flags & IFF_PROMISC) && + ic->ic_opmode != IEEE80211_M_HOSTAP); + mwl_setmcastfilter(sc); + + return 0; +} + +/* + * Callback from the 802.11 layer after a multicast state change. + */ +static void +mwl_update_mcast(struct ifnet *ifp) +{ + struct mwl_softc *sc = ifp->if_softc; + + mwl_setmcastfilter(sc); +} + +/* + * Callback from the 802.11 layer after a promiscuous mode change. + * Note this interface does not check the operating mode as this + * is an internal callback and we are expected to honor the current + * state (e.g. this is used for setting the interface in promiscuous + * mode when operating in hostap mode to do ACS). + */ +static void +mwl_update_promisc(struct ifnet *ifp) +{ + struct mwl_softc *sc = ifp->if_softc; + + mwl_hal_setpromisc(sc->sc_mh, (ifp->if_flags & IFF_PROMISC) != 0); +} + +/* + * Callback from the 802.11 layer to update the slot time + * based on the current setting. We use it to notify the + * firmware of ERP changes and the f/w takes care of things + * like slot time and preamble. + */ +static void +mwl_updateslot(struct ifnet *ifp) +{ + struct mwl_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + struct mwl_hal *mh = sc->sc_mh; + int prot; + + /* NB: can be called early; suppress needless cmds */ + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) + return; + + /* + * Calculate the ERP flags. The firwmare will use + * this to carry out the appropriate measures. + */ + prot = 0; + if (IEEE80211_IS_CHAN_ANYG(ic->ic_curchan)) { + if ((ic->ic_flags & IEEE80211_F_SHSLOT) == 0) + prot |= IEEE80211_ERP_NON_ERP_PRESENT; + if (ic->ic_flags & IEEE80211_F_USEPROT) + prot |= IEEE80211_ERP_USE_PROTECTION; + if (ic->ic_flags & IEEE80211_F_USEBARKER) + prot |= IEEE80211_ERP_LONG_PREAMBLE; + } + + DPRINTF(sc, MWL_DEBUG_RESET, + "%s: chan %u MHz/flags 0x%x %s slot, (prot 0x%x ic_flags 0x%x)\n", + __func__, ic->ic_curchan->ic_freq, ic->ic_curchan->ic_flags, + ic->ic_flags & IEEE80211_F_SHSLOT ? "short" : "long", prot, + ic->ic_flags); + + mwl_hal_setgprot(mh, prot); +} + +/* + * Setup the beacon frame. + */ +static int +mwl_beacon_setup(struct ieee80211vap *vap) +{ + struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; + struct ieee80211_node *ni = vap->iv_bss; + struct ieee80211_beacon_offsets bo; + struct mbuf *m; + + m = ieee80211_beacon_alloc(ni, &bo); + if (m == NULL) + return ENOBUFS; + mwl_hal_setbeacon(hvap, mtod(m, const void *), m->m_len); + m_free(m); + + return 0; +} + +/* + * Update the beacon frame in response to a change. + */ +static void +mwl_beacon_update(struct ieee80211vap *vap, int item) +{ + struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; + struct ieee80211com *ic = vap->iv_ic; + + KASSERT(hvap != NULL, ("no beacon")); + switch (item) { + case IEEE80211_BEACON_ERP: + mwl_updateslot(ic->ic_ifp); + break; + case IEEE80211_BEACON_HTINFO: + mwl_hal_setnprotmode(hvap, + MS(ic->ic_curhtprotmode, IEEE80211_HTINFO_OPMODE)); + break; + case IEEE80211_BEACON_CAPS: + case IEEE80211_BEACON_WME: + case IEEE80211_BEACON_APPIE: + case IEEE80211_BEACON_CSA: + break; + case IEEE80211_BEACON_TIM: + /* NB: firmware always forms TIM */ + return; + } + /* XXX retain beacon frame and update */ + mwl_beacon_setup(vap); +} + +static void +mwl_load_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error) +{ + bus_addr_t *paddr = (bus_addr_t*) arg; + KASSERT(error == 0, ("error %u on bus_dma callback", error)); + *paddr = segs->ds_addr; +} + +#ifdef MWL_HOST_PS_SUPPORT +/* + * Handle power save station occupancy changes. + */ +static void +mwl_update_ps(struct ieee80211vap *vap, int nsta) +{ + struct mwl_vap *mvp = MWL_VAP(vap); + + if (nsta == 0 || mvp->mv_last_ps_sta == 0) + mwl_hal_setpowersave_bss(mvp->mv_hvap, nsta); + mvp->mv_last_ps_sta = nsta; +} + +/* + * Handle associated station power save state changes. + */ +static int +mwl_set_tim(struct ieee80211_node *ni, int set) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct mwl_vap *mvp = MWL_VAP(vap); + + if (mvp->mv_set_tim(ni, set)) { /* NB: state change */ + mwl_hal_setpowersave_sta(mvp->mv_hvap, + IEEE80211_AID(ni->ni_associd), set); + return 1; + } else + return 0; +} +#endif /* MWL_HOST_PS_SUPPORT */ + +static int +mwl_desc_setup(struct mwl_softc *sc, const char *name, + struct mwl_descdma *dd, + int nbuf, size_t bufsize, int ndesc, size_t descsize) +{ + struct ifnet *ifp = sc->sc_ifp; + uint8_t *ds; + int error; + + DPRINTF(sc, MWL_DEBUG_RESET, + "%s: %s DMA: %u bufs (%ju) %u desc/buf (%ju)\n", + __func__, name, nbuf, (uintmax_t) bufsize, + ndesc, (uintmax_t) descsize); + + dd->dd_name = name; + dd->dd_desc_len = nbuf * ndesc * descsize; + + /* + * Setup DMA descriptor area. + */ + error = bus_dma_tag_create(bus_get_dma_tag(sc->sc_dev), /* parent */ + PAGE_SIZE, 0, /* alignment, bounds */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + dd->dd_desc_len, /* maxsize */ + 1, /* nsegments */ + dd->dd_desc_len, /* maxsegsize */ + BUS_DMA_ALLOCNOW, /* flags */ + NULL, /* lockfunc */ + NULL, /* lockarg */ + &dd->dd_dmat); + if (error != 0) { + if_printf(ifp, "cannot allocate %s DMA tag\n", dd->dd_name); + return error; + } + + /* allocate descriptors */ + error = bus_dmamap_create(dd->dd_dmat, BUS_DMA_NOWAIT, &dd->dd_dmamap); + if (error != 0) { + if_printf(ifp, "unable to create dmamap for %s descriptors, " + "error %u\n", dd->dd_name, error); + goto fail0; + } + + error = bus_dmamem_alloc(dd->dd_dmat, (void**) &dd->dd_desc, + BUS_DMA_NOWAIT | BUS_DMA_COHERENT, + &dd->dd_dmamap); + if (error != 0) { + if_printf(ifp, "unable to alloc memory for %u %s descriptors, " + "error %u\n", nbuf * ndesc, dd->dd_name, error); + goto fail1; + } + + error = bus_dmamap_load(dd->dd_dmat, dd->dd_dmamap, + dd->dd_desc, dd->dd_desc_len, + mwl_load_cb, &dd->dd_desc_paddr, + BUS_DMA_NOWAIT); + if (error != 0) { + if_printf(ifp, "unable to map %s descriptors, error %u\n", + dd->dd_name, error); + goto fail2; + } + + ds = dd->dd_desc; + memset(ds, 0, dd->dd_desc_len); + DPRINTF(sc, MWL_DEBUG_RESET, "%s: %s DMA map: %p (%lu) -> %p (%lu)\n", + __func__, dd->dd_name, ds, (u_long) dd->dd_desc_len, + (caddr_t) dd->dd_desc_paddr, /*XXX*/ (u_long) dd->dd_desc_len); + + return 0; +fail2: + bus_dmamem_free(dd->dd_dmat, dd->dd_desc, dd->dd_dmamap); +fail1: + bus_dmamap_destroy(dd->dd_dmat, dd->dd_dmamap); +fail0: + bus_dma_tag_destroy(dd->dd_dmat); + memset(dd, 0, sizeof(*dd)); + return error; +#undef DS2PHYS +} + +static void +mwl_desc_cleanup(struct mwl_softc *sc, struct mwl_descdma *dd) +{ + bus_dmamap_unload(dd->dd_dmat, dd->dd_dmamap); + bus_dmamem_free(dd->dd_dmat, dd->dd_desc, dd->dd_dmamap); + bus_dmamap_destroy(dd->dd_dmat, dd->dd_dmamap); + bus_dma_tag_destroy(dd->dd_dmat); + + memset(dd, 0, sizeof(*dd)); +} + +/* + * Construct a tx q's free list. The order of entries on + * the list must reflect the physical layout of tx descriptors + * because the firmware pre-fetches descriptors. + * + * XXX might be better to use indices into the buffer array. + */ +static void +mwl_txq_reset(struct mwl_softc *sc, struct mwl_txq *txq) +{ + struct mwl_txbuf *bf; + int i; + + bf = txq->dma.dd_bufptr; + STAILQ_INIT(&txq->free); + for (i = 0; i < mwl_txbuf; i++, bf++) + STAILQ_INSERT_TAIL(&txq->free, bf, bf_list); + txq->nfree = i; +} + +#define DS2PHYS(_dd, _ds) \ + ((_dd)->dd_desc_paddr + ((caddr_t)(_ds) - (caddr_t)(_dd)->dd_desc)) + +static int +mwl_txdma_setup(struct mwl_softc *sc, struct mwl_txq *txq) +{ + struct ifnet *ifp = sc->sc_ifp; + int error, bsize, i; + struct mwl_txbuf *bf; + struct mwl_txdesc *ds; + + error = mwl_desc_setup(sc, "tx", &txq->dma, + mwl_txbuf, sizeof(struct mwl_txbuf), + MWL_TXDESC, sizeof(struct mwl_txdesc)); + if (error != 0) + return error; + + /* allocate and setup tx buffers */ + bsize = mwl_txbuf * sizeof(struct mwl_txbuf); + bf = malloc(bsize, M_MWLDEV, M_NOWAIT | M_ZERO); + if (bf == NULL) { + if_printf(ifp, "malloc of %u tx buffers failed\n", + mwl_txbuf); + return ENOMEM; + } + txq->dma.dd_bufptr = bf; + + ds = txq->dma.dd_desc; + for (i = 0; i < mwl_txbuf; i++, bf++, ds += MWL_TXDESC) { + bf->bf_desc = ds; + bf->bf_daddr = DS2PHYS(&txq->dma, ds); + error = bus_dmamap_create(sc->sc_dmat, BUS_DMA_NOWAIT, + &bf->bf_dmamap); + if (error != 0) { + if_printf(ifp, "unable to create dmamap for tx " + "buffer %u, error %u\n", i, error); + return error; + } + } + mwl_txq_reset(sc, txq); + return 0; +} + +static void +mwl_txdma_cleanup(struct mwl_softc *sc, struct mwl_txq *txq) +{ + struct mwl_txbuf *bf; + int i; + + bf = txq->dma.dd_bufptr; + for (i = 0; i < mwl_txbuf; i++, bf++) { + KASSERT(bf->bf_m == NULL, ("mbuf on free list")); + KASSERT(bf->bf_node == NULL, ("node on free list")); + if (bf->bf_dmamap != NULL) + bus_dmamap_destroy(sc->sc_dmat, bf->bf_dmamap); + } + STAILQ_INIT(&txq->free); + txq->nfree = 0; + if (txq->dma.dd_bufptr != NULL) { + free(txq->dma.dd_bufptr, M_MWLDEV); + txq->dma.dd_bufptr = NULL; + } + if (txq->dma.dd_desc_len != 0) + mwl_desc_cleanup(sc, &txq->dma); +} + +static int +mwl_rxdma_setup(struct mwl_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + int error, jumbosize, bsize, i; + struct mwl_rxbuf *bf; + struct mwl_jumbo *rbuf; + struct mwl_rxdesc *ds; + caddr_t data; + + error = mwl_desc_setup(sc, "rx", &sc->sc_rxdma, + mwl_rxdesc, sizeof(struct mwl_rxbuf), + 1, sizeof(struct mwl_rxdesc)); + if (error != 0) + return error; + + /* + * Receive is done to a private pool of jumbo buffers. + * This allows us to attach to mbuf's and avoid re-mapping + * memory on each rx we post. We allocate a large chunk + * of memory and manage it in the driver. The mbuf free + * callback method is used to reclaim frames after sending + * them up the stack. By default we allocate 2x the number of + * rx descriptors configured so we have some slop to hold + * us while frames are processed. + */ + if (mwl_rxbuf < 2*mwl_rxdesc) { + if_printf(ifp, + "too few rx dma buffers (%d); increasing to %d\n", + mwl_rxbuf, 2*mwl_rxdesc); + mwl_rxbuf = 2*mwl_rxdesc; + } + jumbosize = roundup(MWL_AGGR_SIZE, PAGE_SIZE); + sc->sc_rxmemsize = mwl_rxbuf*jumbosize; + + error = bus_dma_tag_create(sc->sc_dmat, /* parent */ + PAGE_SIZE, 0, /* alignment, bounds */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + sc->sc_rxmemsize, /* maxsize */ + 1, /* nsegments */ + sc->sc_rxmemsize, /* maxsegsize */ + BUS_DMA_ALLOCNOW, /* flags */ + NULL, /* lockfunc */ + NULL, /* lockarg */ + &sc->sc_rxdmat); + error = bus_dmamap_create(sc->sc_rxdmat, BUS_DMA_NOWAIT, &sc->sc_rxmap); + if (error != 0) { + if_printf(ifp, "could not create rx DMA map\n"); + return error; + } + + error = bus_dmamem_alloc(sc->sc_rxdmat, (void**) &sc->sc_rxmem, + BUS_DMA_NOWAIT | BUS_DMA_COHERENT, + &sc->sc_rxmap); + if (error != 0) { + if_printf(ifp, "could not alloc %ju bytes of rx DMA memory\n", + (uintmax_t) sc->sc_rxmemsize); + return error; + } + + error = bus_dmamap_load(sc->sc_rxdmat, sc->sc_rxmap, + sc->sc_rxmem, sc->sc_rxmemsize, + mwl_load_cb, &sc->sc_rxmem_paddr, + BUS_DMA_NOWAIT); + if (error != 0) { + if_printf(ifp, "could not load rx DMA map\n"); + return error; + } + + /* + * Allocate rx buffers and set them up. + */ + bsize = mwl_rxdesc * sizeof(struct mwl_rxbuf); + bf = malloc(bsize, M_MWLDEV, M_NOWAIT | M_ZERO); + if (bf == NULL) { + if_printf(ifp, "malloc of %u rx buffers failed\n", bsize); + return error; + } + sc->sc_rxdma.dd_bufptr = bf; + + STAILQ_INIT(&sc->sc_rxbuf); + ds = sc->sc_rxdma.dd_desc; + for (i = 0; i < mwl_rxdesc; i++, bf++, ds++) { + bf->bf_desc = ds; + bf->bf_daddr = DS2PHYS(&sc->sc_rxdma, ds); + /* pre-assign dma buffer */ + bf->bf_data = ((uint8_t *)sc->sc_rxmem) + (i*jumbosize); + /* NB: tail is intentional to preserve descriptor order */ + STAILQ_INSERT_TAIL(&sc->sc_rxbuf, bf, bf_list); + } + + /* + * Place remainder of dma memory buffers on the free list. + */ + SLIST_INIT(&sc->sc_rxfree); + for (; i < mwl_rxbuf; i++) { + data = ((uint8_t *)sc->sc_rxmem) + (i*jumbosize); + rbuf = MWL_JUMBO_DATA2BUF(data); + SLIST_INSERT_HEAD(&sc->sc_rxfree, rbuf, next); + sc->sc_nrxfree++; + } + MWL_RXFREE_INIT(sc); + return 0; +} +#undef DS2PHYS + +static void +mwl_rxdma_cleanup(struct mwl_softc *sc) +{ + if (sc->sc_rxmap != NULL) + bus_dmamap_unload(sc->sc_rxdmat, sc->sc_rxmap); + if (sc->sc_rxmem != NULL) { + bus_dmamem_free(sc->sc_rxdmat, sc->sc_rxmem, sc->sc_rxmap); + sc->sc_rxmem = NULL; + } + if (sc->sc_rxmap != NULL) { + bus_dmamap_destroy(sc->sc_rxdmat, sc->sc_rxmap); + sc->sc_rxmap = NULL; + } + if (sc->sc_rxdma.dd_bufptr != NULL) { + free(sc->sc_rxdma.dd_bufptr, M_MWLDEV); + sc->sc_rxdma.dd_bufptr = NULL; + } + if (sc->sc_rxdma.dd_desc_len != 0) + mwl_desc_cleanup(sc, &sc->sc_rxdma); + MWL_RXFREE_DESTROY(sc); +} + +static int +mwl_dma_setup(struct mwl_softc *sc) +{ + int error, i; + + error = mwl_rxdma_setup(sc); + if (error != 0) + return error; + + for (i = 0; i < MWL_NUM_TX_QUEUES; i++) { + error = mwl_txdma_setup(sc, &sc->sc_txq[i]); + if (error != 0) { + mwl_dma_cleanup(sc); + return error; + } + } + return 0; +} + +static void +mwl_dma_cleanup(struct mwl_softc *sc) +{ + int i; + + for (i = 0; i < MWL_NUM_TX_QUEUES; i++) + mwl_txdma_cleanup(sc, &sc->sc_txq[i]); + mwl_rxdma_cleanup(sc); +} + +static struct ieee80211_node * +mwl_node_alloc(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct ieee80211com *ic = vap->iv_ic; + struct mwl_softc *sc = ic->ic_ifp->if_softc; + const size_t space = sizeof(struct mwl_node); + struct mwl_node *mn; + + mn = malloc(space, M_80211_NODE, M_NOWAIT|M_ZERO); + if (mn == NULL) { + /* XXX stat+msg */ + return NULL; + } + DPRINTF(sc, MWL_DEBUG_NODE, "%s: mn %p\n", __func__, mn); + return &mn->mn_node; +} + +static void +mwl_node_cleanup(struct ieee80211_node *ni) +{ + struct ieee80211com *ic = ni->ni_ic; + struct mwl_softc *sc = ic->ic_ifp->if_softc; + struct mwl_node *mn = MWL_NODE(ni); + + DPRINTF(sc, MWL_DEBUG_NODE, "%s: ni %p ic %p staid %d\n", + __func__, ni, ni->ni_ic, mn->mn_staid); + + if (mn->mn_staid != 0) { + struct ieee80211vap *vap = ni->ni_vap; + + if (mn->mn_hvap != NULL) { + if (vap->iv_opmode == IEEE80211_M_STA) + mwl_hal_delstation(mn->mn_hvap, vap->iv_myaddr); + else + mwl_hal_delstation(mn->mn_hvap, ni->ni_macaddr); + } + /* + * NB: legacy WDS peer sta db entry is installed using + * the associate ap's hvap; use it again to delete it. + * XXX can vap be NULL? + */ + else if (vap->iv_opmode == IEEE80211_M_WDS && + MWL_VAP(vap)->mv_ap_hvap != NULL) + mwl_hal_delstation(MWL_VAP(vap)->mv_ap_hvap, + ni->ni_macaddr); + delstaid(sc, mn->mn_staid); + mn->mn_staid = 0; + } + sc->sc_node_cleanup(ni); +} + +/* + * Reclaim rx dma buffers from packets sitting on the ampdu + * reorder queue for a station. We replace buffers with a + * system cluster (if available). + */ +static void +mwl_ampdu_rxdma_reclaim(struct ieee80211_rx_ampdu *rap) +{ +#if 0 + int i, n, off; + struct mbuf *m; + void *cl; + + n = rap->rxa_qframes; + for (i = 0; i < rap->rxa_wnd && n > 0; i++) { + m = rap->rxa_m[i]; + if (m == NULL) + continue; + n--; + /* our dma buffers have a well-known free routine */ + if ((m->m_flags & M_EXT) == 0 || + m->m_ext.ext_free != mwl_ext_free) + continue; + /* + * Try to allocate a cluster and move the data. + */ + off = m->m_data - m->m_ext.ext_buf; + if (off + m->m_pkthdr.len > MCLBYTES) { + /* XXX no AMSDU for now */ + continue; + } + cl = pool_cache_get_paddr(&mclpool_cache, 0, + &m->m_ext.ext_paddr); + if (cl != NULL) { + /* + * Copy the existing data to the cluster, remove + * the rx dma buffer, and attach the cluster in + * its place. Note we preserve the offset to the + * data so frames being bridged can still prepend + * their headers without adding another mbuf. + */ + memcpy((caddr_t) cl + off, m->m_data, m->m_pkthdr.len); + MEXTREMOVE(m); + MEXTADD(m, cl, MCLBYTES, 0, NULL, &mclpool_cache); + /* setup mbuf like _MCLGET does */ + m->m_flags |= M_CLUSTER | M_EXT_RW; + _MOWNERREF(m, M_EXT | M_CLUSTER); + /* NB: m_data is clobbered by MEXTADDR, adjust */ + m->m_data += off; + } + } +#endif +} + +/* + * Callback to reclaim resources. We first let the + * net80211 layer do it's thing, then if we are still + * blocked by a lack of rx dma buffers we walk the ampdu + * reorder q's to reclaim buffers by copying to a system + * cluster. + */ +static void +mwl_node_drain(struct ieee80211_node *ni) +{ + struct ieee80211com *ic = ni->ni_ic; + struct mwl_softc *sc = ic->ic_ifp->if_softc; + struct mwl_node *mn = MWL_NODE(ni); + + DPRINTF(sc, MWL_DEBUG_NODE, "%s: ni %p vap %p staid %d\n", + __func__, ni, ni->ni_vap, mn->mn_staid); + + /* NB: call up first to age out ampdu q's */ + sc->sc_node_drain(ni); + + /* XXX better to not check low water mark? */ + if (sc->sc_rxblocked && mn->mn_staid != 0 && + (ni->ni_flags & IEEE80211_NODE_HT)) { + uint8_t tid; + /* + * Walk the reorder q and reclaim rx dma buffers by copying + * the packet contents into clusters. + */ + for (tid = 0; tid < WME_NUM_TID; tid++) { + struct ieee80211_rx_ampdu *rap; + + rap = &ni->ni_rx_ampdu[tid]; + if ((rap->rxa_flags & IEEE80211_AGGR_XCHGPEND) == 0) + continue; + if (rap->rxa_qframes) + mwl_ampdu_rxdma_reclaim(rap); + } + } +} + +static void +mwl_node_getsignal(const struct ieee80211_node *ni, int8_t *rssi, int8_t *noise) +{ + *rssi = ni->ni_ic->ic_node_getrssi(ni); +#ifdef MWL_ANT_INFO_SUPPORT +#if 0 + /* XXX need to smooth data */ + *noise = -MWL_NODE_CONST(ni)->mn_ai.nf; +#else + *noise = -95; /* XXX */ +#endif +#else + *noise = -95; /* XXX */ +#endif +} + +/* + * Convert Hardware per-antenna rssi info to common format: + * Let a1, a2, a3 represent the amplitudes per chain + * Let amax represent max[a1, a2, a3] + * Rssi1_dBm = RSSI_dBm + 20*log10(a1/amax) + * Rssi1_dBm = RSSI_dBm + 20*log10(a1) - 20*log10(amax) + * We store a table that is 4*20*log10(idx) - the extra 4 is to store or + * maintain some extra precision. + * + * Values are stored in .5 db format capped at 127. + */ +static void +mwl_node_getmimoinfo(const struct ieee80211_node *ni, + struct ieee80211_mimo_info *mi) +{ +#define CVT(_dst, _src) do { \ + (_dst) = rssi + ((logdbtbl[_src] - logdbtbl[rssi_max]) >> 2); \ + (_dst) = (_dst) > 64 ? 127 : ((_dst) << 1); \ +} while (0) + static const int8_t logdbtbl[32] = { + 0, 0, 24, 38, 48, 56, 62, 68, + 72, 76, 80, 83, 86, 89, 92, 94, + 96, 98, 100, 102, 104, 106, 107, 109, + 110, 112, 113, 115, 116, 117, 118, 119 + }; + const struct mwl_node *mn = MWL_NODE_CONST(ni); + uint8_t rssi = mn->mn_ai.rsvd1/2; /* XXX */ + uint32_t rssi_max; + + rssi_max = mn->mn_ai.rssi_a; + if (mn->mn_ai.rssi_b > rssi_max) + rssi_max = mn->mn_ai.rssi_b; + if (mn->mn_ai.rssi_c > rssi_max) + rssi_max = mn->mn_ai.rssi_c; + + CVT(mi->rssi[0], mn->mn_ai.rssi_a); + CVT(mi->rssi[1], mn->mn_ai.rssi_b); + CVT(mi->rssi[2], mn->mn_ai.rssi_c); + + mi->noise[0] = mn->mn_ai.nf_a; + mi->noise[1] = mn->mn_ai.nf_b; + mi->noise[2] = mn->mn_ai.nf_c; +#undef CVT +} + +static __inline void * +mwl_getrxdma(struct mwl_softc *sc) +{ + struct mwl_jumbo *buf; + void *data; + + /* + * Allocate from jumbo pool. + */ + MWL_RXFREE_LOCK(sc); + buf = SLIST_FIRST(&sc->sc_rxfree); + if (buf == NULL) { + DPRINTF(sc, MWL_DEBUG_ANY, + "%s: out of rx dma buffers\n", __func__); + sc->sc_stats.mst_rx_nodmabuf++; + data = NULL; + } else { + SLIST_REMOVE_HEAD(&sc->sc_rxfree, next); + sc->sc_nrxfree--; + data = MWL_JUMBO_BUF2DATA(buf); + } + MWL_RXFREE_UNLOCK(sc); + return data; +} + +static __inline void +mwl_putrxdma(struct mwl_softc *sc, void *data) +{ + struct mwl_jumbo *buf; + + /* XXX bounds check data */ + MWL_RXFREE_LOCK(sc); + buf = MWL_JUMBO_DATA2BUF(data); + SLIST_INSERT_HEAD(&sc->sc_rxfree, buf, next); + sc->sc_nrxfree++; + MWL_RXFREE_UNLOCK(sc); +} + +static int +mwl_rxbuf_init(struct mwl_softc *sc, struct mwl_rxbuf *bf) +{ + struct mwl_rxdesc *ds; + + ds = bf->bf_desc; + if (bf->bf_data == NULL) { + bf->bf_data = mwl_getrxdma(sc); + if (bf->bf_data == NULL) { + /* mark descriptor to be skipped */ + ds->RxControl = EAGLE_RXD_CTRL_OS_OWN; + /* NB: don't need PREREAD */ + MWL_RXDESC_SYNC(sc, ds, BUS_DMASYNC_PREWRITE); + sc->sc_stats.mst_rxbuf_failed++; + return ENOMEM; + } + } + /* + * NB: DMA buffer contents is known to be unmodified + * so there's no need to flush the data cache. + */ + + /* + * Setup descriptor. + */ + ds->QosCtrl = 0; + ds->RSSI = 0; + ds->Status = EAGLE_RXD_STATUS_IDLE; + ds->Channel = 0; + ds->PktLen = htole16(MWL_AGGR_SIZE); + ds->SQ2 = 0; + ds->pPhysBuffData = htole32(MWL_JUMBO_DMA_ADDR(sc, bf->bf_data)); + /* NB: don't touch pPhysNext, set once */ + ds->RxControl = EAGLE_RXD_CTRL_DRIVER_OWN; + MWL_RXDESC_SYNC(sc, ds, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); + + return 0; +} + +static void +mwl_ext_free(void *data, void *arg) +{ + struct mwl_softc *sc = arg; + + /* XXX bounds check data */ + mwl_putrxdma(sc, data); + /* + * If we were previously blocked by a lack of rx dma buffers + * check if we now have enough to restart rx interrupt handling. + * NB: we know we are called at splvm which is above splnet. + */ + if (sc->sc_rxblocked && sc->sc_nrxfree > mwl_rxdmalow) { + sc->sc_rxblocked = 0; + mwl_hal_intrset(sc->sc_mh, sc->sc_imask); + } +} + +struct mwl_frame_bar { + u_int8_t i_fc[2]; + u_int8_t i_dur[2]; + u_int8_t i_ra[IEEE80211_ADDR_LEN]; + u_int8_t i_ta[IEEE80211_ADDR_LEN]; + /* ctl, seq, FCS */ +} __packed; + +/* + * Like ieee80211_anyhdrsize, but handles BAR frames + * specially so the logic below to piece the 802.11 + * header together works. + */ +static __inline int +mwl_anyhdrsize(const void *data) +{ + const struct ieee80211_frame *wh = data; + + if ((wh->i_fc[0]&IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_CTL) { + switch (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) { + case IEEE80211_FC0_SUBTYPE_CTS: + case IEEE80211_FC0_SUBTYPE_ACK: + return sizeof(struct ieee80211_frame_ack); + case IEEE80211_FC0_SUBTYPE_BAR: + return sizeof(struct mwl_frame_bar); + } + return sizeof(struct ieee80211_frame_min); + } else + return ieee80211_hdrsize(data); +} + +static void +mwl_handlemicerror(struct ieee80211com *ic, const uint8_t *data) +{ + const struct ieee80211_frame *wh; + struct ieee80211_node *ni; + + wh = (const struct ieee80211_frame *)(data + sizeof(uint16_t)); + ni = ieee80211_find_rxnode(ic, (const struct ieee80211_frame_min *) wh); + if (ni != NULL) { + ieee80211_notify_michael_failure(ni->ni_vap, wh, 0); + ieee80211_free_node(ni); + } +} + +/* + * Convert hardware signal strength to rssi. The value + * provided by the device has the noise floor added in; + * we need to compensate for this but we don't have that + * so we use a fixed value. + * + * The offset of 8 is good for both 2.4 and 5GHz. The LNA + * offset is already set as part of the initial gain. This + * will give at least +/- 3dB for 2.4GHz and +/- 5dB for 5GHz. + */ +static __inline int +cvtrssi(uint8_t ssi) +{ + int rssi = (int) ssi + 8; + /* XXX hack guess until we have a real noise floor */ + rssi = 2*(87 - rssi); /* NB: .5 dBm units */ + return (rssi < 0 ? 0 : rssi > 127 ? 127 : rssi); +} + +static void +mwl_rx_proc(void *arg, int npending) +{ +#define IEEE80211_DIR_DSTODS(wh) \ + ((((const struct ieee80211_frame *)wh)->i_fc[1] & IEEE80211_FC1_DIR_MASK) == IEEE80211_FC1_DIR_DSTODS) + struct mwl_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct mwl_rxbuf *bf; + struct mwl_rxdesc *ds; + struct mbuf *m; + struct ieee80211_qosframe *wh; + struct ieee80211_qosframe_addr4 *wh4; + struct ieee80211_node *ni; + struct mwl_node *mn; + int off, len, hdrlen, pktlen, rssi, ntodo; + uint8_t *data, status; + void *newdata; + int16_t nf; + + DPRINTF(sc, MWL_DEBUG_RX_PROC, "%s: pending %u rdptr 0x%x wrptr 0x%x\n", + __func__, npending, RD4(sc, sc->sc_hwspecs.rxDescRead), + RD4(sc, sc->sc_hwspecs.rxDescWrite)); + nf = -96; /* XXX */ + bf = sc->sc_rxnext; + for (ntodo = mwl_rxquota; ntodo > 0; ntodo--) { + if (bf == NULL) + bf = STAILQ_FIRST(&sc->sc_rxbuf); + ds = bf->bf_desc; + data = bf->bf_data; + if (data == NULL) { + /* + * If data allocation failed previously there + * will be no buffer; try again to re-populate it. + * Note the firmware will not advance to the next + * descriptor with a dma buffer so we must mimic + * this or we'll get out of sync. + */ + DPRINTF(sc, MWL_DEBUG_ANY, + "%s: rx buf w/o dma memory\n", __func__); + (void) mwl_rxbuf_init(sc, bf); + sc->sc_stats.mst_rx_dmabufmissing++; + break; + } + MWL_RXDESC_SYNC(sc, ds, + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); + if (ds->RxControl != EAGLE_RXD_CTRL_DMA_OWN) + break; +#ifdef MWL_DEBUG + if (sc->sc_debug & MWL_DEBUG_RECV_DESC) + mwl_printrxbuf(bf, 0); +#endif + status = ds->Status; + if (status & EAGLE_RXD_STATUS_DECRYPT_ERR_MASK) { + ifp->if_ierrors++; + sc->sc_stats.mst_rx_crypto++; + /* + * NB: Check EAGLE_RXD_STATUS_GENERAL_DECRYPT_ERR + * for backwards compatibility. + */ + if (status != EAGLE_RXD_STATUS_GENERAL_DECRYPT_ERR && + (status & EAGLE_RXD_STATUS_TKIP_MIC_DECRYPT_ERR)) { + /* + * MIC error, notify upper layers. + */ + bus_dmamap_sync(sc->sc_rxdmat, sc->sc_rxmap, + BUS_DMASYNC_POSTREAD); + mwl_handlemicerror(ic, data); + sc->sc_stats.mst_rx_tkipmic++; + } + /* XXX too painful to tap packets */ + goto rx_next; + } + /* + * Sync the data buffer. + */ + len = le16toh(ds->PktLen); + bus_dmamap_sync(sc->sc_rxdmat, sc->sc_rxmap, BUS_DMASYNC_POSTREAD); + /* + * The 802.11 header is provided all or in part at the front; + * use it to calculate the true size of the header that we'll + * construct below. We use this to figure out where to copy + * payload prior to constructing the header. + */ + hdrlen = mwl_anyhdrsize(data + sizeof(uint16_t)); + off = sizeof(uint16_t) + sizeof(struct ieee80211_frame_addr4); + + /* calculate rssi early so we can re-use for each aggregate */ + rssi = cvtrssi(ds->RSSI); + + pktlen = hdrlen + (len - off); + /* + * NB: we know our frame is at least as large as + * IEEE80211_MIN_LEN because there is a 4-address + * frame at the front. Hence there's no need to + * vet the packet length. If the frame in fact + * is too small it should be discarded at the + * net80211 layer. + */ + + /* + * Attach dma buffer to an mbuf. We tried + * doing this based on the packet size (i.e. + * copying small packets) but it turns out to + * be a net loss. The tradeoff might be system + * dependent (cache architecture is important). + */ + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + DPRINTF(sc, MWL_DEBUG_ANY, + "%s: no rx mbuf\n", __func__); + sc->sc_stats.mst_rx_nombuf++; + goto rx_next; + } + /* + * Acquire the replacement dma buffer before + * processing the frame. If we're out of dma + * buffers we disable rx interrupts and wait + * for the free pool to reach mlw_rxdmalow buffers + * before starting to do work again. If the firmware + * runs out of descriptors then it will toss frames + * which is better than our doing it as that can + * starve our processing. It is also important that + * we always process rx'd frames in case they are + * A-MPDU as otherwise the host's view of the BA + * window may get out of sync with the firmware. + */ + newdata = mwl_getrxdma(sc); + if (newdata == NULL) { + /* NB: stat+msg in mwl_getrxdma */ + m_free(m); + /* disable RX interrupt and mark state */ + mwl_hal_intrset(sc->sc_mh, + sc->sc_imask &~ MACREG_A2HRIC_BIT_RX_RDY); + sc->sc_rxblocked = 1; + ieee80211_drain(ic); + /* XXX check rxblocked and immediately start again? */ + goto rx_stop; + } + bf->bf_data = newdata; + /* + * Attach the dma buffer to the mbuf; + * mwl_rxbuf_init will re-setup the rx + * descriptor using the replacement dma + * buffer we just installed above. + */ + MEXTADD(m, data, MWL_AGGR_SIZE, mwl_ext_free, + data, sc, 0, EXT_NET_DRV); + m->m_data += off - hdrlen; + m->m_pkthdr.len = m->m_len = pktlen; + m->m_pkthdr.rcvif = ifp; + /* NB: dma buffer assumed read-only */ + + /* + * Piece 802.11 header together. + */ + wh = mtod(m, struct ieee80211_qosframe *); + /* NB: don't need to do this sometimes but ... */ + /* XXX special case so we can memcpy after m_devget? */ + ovbcopy(data + sizeof(uint16_t), wh, hdrlen); + if (IEEE80211_QOS_HAS_SEQ(wh)) { + if (IEEE80211_DIR_DSTODS(wh)) { + wh4 = mtod(m, + struct ieee80211_qosframe_addr4*); + *(uint16_t *)wh4->i_qos = ds->QosCtrl; + } else { + *(uint16_t *)wh->i_qos = ds->QosCtrl; + } + } + /* + * The f/w strips WEP header but doesn't clear + * the WEP bit; mark the packet with M_WEP so + * net80211 will treat the data as decrypted. + * While here also clear the PWR_MGT bit since + * power save is handled by the firmware and + * passing this up will potentially cause the + * upper layer to put a station in power save + * (except when configured with MWL_HOST_PS_SUPPORT). + */ + if (wh->i_fc[1] & IEEE80211_FC1_WEP) + m->m_flags |= M_WEP; +#ifdef MWL_HOST_PS_SUPPORT + wh->i_fc[1] &= ~IEEE80211_FC1_WEP; +#else + wh->i_fc[1] &= ~(IEEE80211_FC1_WEP | IEEE80211_FC1_PWR_MGT); +#endif + + if (ieee80211_radiotap_active(ic)) { + struct mwl_rx_radiotap_header *tap = &sc->sc_rx_th; + + tap->wr_flags = 0; + tap->wr_rate = ds->Rate; + tap->wr_antsignal = rssi + nf; + tap->wr_antnoise = nf; + } + if (IFF_DUMPPKTS_RECV(sc, wh)) { + ieee80211_dump_pkt(ic, mtod(m, caddr_t), + len, ds->Rate, rssi); + } + ifp->if_ipackets++; + + /* dispatch */ + ni = ieee80211_find_rxnode(ic, + (const struct ieee80211_frame_min *) wh); + if (ni != NULL) { + mn = MWL_NODE(ni); +#ifdef MWL_ANT_INFO_SUPPORT + mn->mn_ai.rssi_a = ds->ai.rssi_a; + mn->mn_ai.rssi_b = ds->ai.rssi_b; + mn->mn_ai.rssi_c = ds->ai.rssi_c; + mn->mn_ai.rsvd1 = rssi; +#endif + /* tag AMPDU aggregates for reorder processing */ +#if 0 + if ((ds->Rate & 0x80) && (ds->HtSig2 & 0x8)) +#else + if (ni->ni_flags & IEEE80211_NODE_HT) +#endif + m->m_flags |= M_AMPDU; + (void) ieee80211_input(ni, m, rssi, nf); + ieee80211_free_node(ni); + } else + (void) ieee80211_input_all(ic, m, rssi, nf); +rx_next: + /* NB: ignore ENOMEM so we process more descriptors */ + (void) mwl_rxbuf_init(sc, bf); + bf = STAILQ_NEXT(bf, bf_list); + } +rx_stop: + sc->sc_rxnext = bf; + + if ((ifp->if_drv_flags & IFF_DRV_OACTIVE) == 0 && + !IFQ_IS_EMPTY(&ifp->if_snd)) { + /* NB: kick fw; the tx thread may have been preempted */ + mwl_hal_txstart(sc->sc_mh, 0); + mwl_start(ifp); + } +#undef IEEE80211_DIR_DSTODS +} + +static void +mwl_txq_init(struct mwl_softc *sc, struct mwl_txq *txq, int qnum) +{ + struct mwl_txbuf *bf, *bn; + struct mwl_txdesc *ds; + + MWL_TXQ_LOCK_INIT(sc, txq); + txq->qnum = qnum; + txq->txpri = 0; /* XXX */ +#if 0 + /* NB: q setup by mwl_txdma_setup XXX */ + STAILQ_INIT(&txq->free); +#endif + STAILQ_FOREACH(bf, &txq->free, bf_list) { + bf->bf_txq = txq; + + ds = bf->bf_desc; + bn = STAILQ_NEXT(bf, bf_list); + if (bn == NULL) + bn = STAILQ_FIRST(&txq->free); + ds->pPhysNext = htole32(bn->bf_daddr); + } + STAILQ_INIT(&txq->active); +} + +/* + * Setup a hardware data transmit queue for the specified + * access control. We record the mapping from ac's + * to h/w queues for use by mwl_tx_start. + */ +static int +mwl_tx_setup(struct mwl_softc *sc, int ac, int mvtype) +{ +#define N(a) (sizeof(a)/sizeof(a[0])) + struct mwl_txq *txq; + + if (ac >= N(sc->sc_ac2q)) { + device_printf(sc->sc_dev, "AC %u out of range, max %zu!\n", + ac, N(sc->sc_ac2q)); + return 0; + } + if (mvtype >= MWL_NUM_TX_QUEUES) { + device_printf(sc->sc_dev, "mvtype %u out of range, max %u!\n", + mvtype, MWL_NUM_TX_QUEUES); + return 0; + } + txq = &sc->sc_txq[mvtype]; + mwl_txq_init(sc, txq, mvtype); + sc->sc_ac2q[ac] = txq; + return 1; +#undef N +} + +/* + * Update WME parameters for a transmit queue. + */ +static int +mwl_txq_update(struct mwl_softc *sc, int ac) +{ +#define MWL_EXPONENT_TO_VALUE(v) ((1<<v)-1) + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct mwl_txq *txq = sc->sc_ac2q[ac]; + struct wmeParams *wmep = &ic->ic_wme.wme_chanParams.cap_wmeParams[ac]; + struct mwl_hal *mh = sc->sc_mh; + int aifs, cwmin, cwmax, txoplim; + + aifs = wmep->wmep_aifsn; + /* XXX in sta mode need to pass log values for cwmin/max */ + cwmin = MWL_EXPONENT_TO_VALUE(wmep->wmep_logcwmin); + cwmax = MWL_EXPONENT_TO_VALUE(wmep->wmep_logcwmax); + txoplim = wmep->wmep_txopLimit; /* NB: units of 32us */ + + if (mwl_hal_setedcaparams(mh, txq->qnum, cwmin, cwmax, aifs, txoplim)) { + device_printf(sc->sc_dev, "unable to update hardware queue " + "parameters for %s traffic!\n", + ieee80211_wme_acnames[ac]); + return 0; + } + return 1; +#undef MWL_EXPONENT_TO_VALUE +} + +/* + * Callback from the 802.11 layer to update WME parameters. + */ +static int +mwl_wme_update(struct ieee80211com *ic) +{ + struct mwl_softc *sc = ic->ic_ifp->if_softc; + + return !mwl_txq_update(sc, WME_AC_BE) || + !mwl_txq_update(sc, WME_AC_BK) || + !mwl_txq_update(sc, WME_AC_VI) || + !mwl_txq_update(sc, WME_AC_VO) ? EIO : 0; +} + +/* + * Reclaim resources for a setup queue. + */ +static void +mwl_tx_cleanupq(struct mwl_softc *sc, struct mwl_txq *txq) +{ + /* XXX hal work? */ + MWL_TXQ_LOCK_DESTROY(txq); +} + +/* + * Reclaim all tx queue resources. + */ +static void +mwl_tx_cleanup(struct mwl_softc *sc) +{ + int i; + + for (i = 0; i < MWL_NUM_TX_QUEUES; i++) + mwl_tx_cleanupq(sc, &sc->sc_txq[i]); +} + +static int +mwl_tx_dmasetup(struct mwl_softc *sc, struct mwl_txbuf *bf, struct mbuf *m0) +{ + struct mbuf *m; + int error; + + /* + * Load the DMA map so any coalescing is done. This + * also calculates the number of descriptors we need. + */ + error = bus_dmamap_load_mbuf_sg(sc->sc_dmat, bf->bf_dmamap, m0, + bf->bf_segs, &bf->bf_nseg, + BUS_DMA_NOWAIT); + if (error == EFBIG) { + /* XXX packet requires too many descriptors */ + bf->bf_nseg = MWL_TXDESC+1; + } else if (error != 0) { + sc->sc_stats.mst_tx_busdma++; + m_freem(m0); + return error; + } + /* + * Discard null packets and check for packets that + * require too many TX descriptors. We try to convert + * the latter to a cluster. + */ + if (error == EFBIG) { /* too many desc's, linearize */ + sc->sc_stats.mst_tx_linear++; +#if MWL_TXDESC > 1 + m = m_collapse(m0, M_DONTWAIT, MWL_TXDESC); +#else + m = m_defrag(m0, M_DONTWAIT); +#endif + if (m == NULL) { + m_freem(m0); + sc->sc_stats.mst_tx_nombuf++; + return ENOMEM; + } + m0 = m; + error = bus_dmamap_load_mbuf_sg(sc->sc_dmat, bf->bf_dmamap, m0, + bf->bf_segs, &bf->bf_nseg, + BUS_DMA_NOWAIT); + if (error != 0) { + sc->sc_stats.mst_tx_busdma++; + m_freem(m0); + return error; + } + KASSERT(bf->bf_nseg <= MWL_TXDESC, + ("too many segments after defrag; nseg %u", bf->bf_nseg)); + } else if (bf->bf_nseg == 0) { /* null packet, discard */ + sc->sc_stats.mst_tx_nodata++; + m_freem(m0); + return EIO; + } + DPRINTF(sc, MWL_DEBUG_XMIT, "%s: m %p len %u\n", + __func__, m0, m0->m_pkthdr.len); + bus_dmamap_sync(sc->sc_dmat, bf->bf_dmamap, BUS_DMASYNC_PREWRITE); + bf->bf_m = m0; + + return 0; +} + +static __inline int +mwl_cvtlegacyrate(int rate) +{ + switch (rate) { + case 2: return 0; + case 4: return 1; + case 11: return 2; + case 22: return 3; + case 44: return 4; + case 12: return 5; + case 18: return 6; + case 24: return 7; + case 36: return 8; + case 48: return 9; + case 72: return 10; + case 96: return 11; + case 108:return 12; + } + return 0; +} + +/* + * Calculate fixed tx rate information per client state; + * this value is suitable for writing to the Format field + * of a tx descriptor. + */ +static uint16_t +mwl_calcformat(uint8_t rate, const struct ieee80211_node *ni) +{ + uint16_t fmt; + + fmt = SM(3, EAGLE_TXD_ANTENNA) + | (IEEE80211_IS_CHAN_HT40D(ni->ni_chan) ? + EAGLE_TXD_EXTCHAN_LO : EAGLE_TXD_EXTCHAN_HI); + if (rate & 0x80) { /* HT MCS */ + fmt |= EAGLE_TXD_FORMAT_HT + /* NB: 0x80 implicitly stripped from ucastrate */ + | SM(rate, EAGLE_TXD_RATE); + /* XXX short/long GI may be wrong; re-check */ + if (IEEE80211_IS_CHAN_HT40(ni->ni_chan)) { + fmt |= EAGLE_TXD_CHW_40 + | (ni->ni_htcap & IEEE80211_HTCAP_SHORTGI40 ? + EAGLE_TXD_GI_SHORT : EAGLE_TXD_GI_LONG); + } else { + fmt |= EAGLE_TXD_CHW_20 + | (ni->ni_htcap & IEEE80211_HTCAP_SHORTGI20 ? + EAGLE_TXD_GI_SHORT : EAGLE_TXD_GI_LONG); + } + } else { /* legacy rate */ + fmt |= EAGLE_TXD_FORMAT_LEGACY + | SM(mwl_cvtlegacyrate(rate), EAGLE_TXD_RATE) + | EAGLE_TXD_CHW_20 + /* XXX iv_flags & IEEE80211_F_SHPREAMBLE? */ + | (ni->ni_capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE ? + EAGLE_TXD_PREAMBLE_SHORT : EAGLE_TXD_PREAMBLE_LONG); + } + return fmt; +} + +static int +mwl_tx_start(struct mwl_softc *sc, struct ieee80211_node *ni, struct mwl_txbuf *bf, + struct mbuf *m0) +{ +#define IEEE80211_DIR_DSTODS(wh) \ + ((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) == IEEE80211_FC1_DIR_DSTODS) + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct ieee80211vap *vap = ni->ni_vap; + int error, iswep, ismcast; + int hdrlen, copyhdrlen, pktlen; + struct mwl_txdesc *ds; + struct mwl_txq *txq; + struct ieee80211_frame *wh; + struct mwltxrec *tr; + struct mwl_node *mn; + uint16_t qos; +#if MWL_TXDESC > 1 + int i; +#endif + + wh = mtod(m0, struct ieee80211_frame *); + iswep = wh->i_fc[1] & IEEE80211_FC1_WEP; + ismcast = IEEE80211_IS_MULTICAST(wh->i_addr1); + hdrlen = ieee80211_anyhdrsize(wh); + copyhdrlen = hdrlen; + pktlen = m0->m_pkthdr.len; + if (IEEE80211_QOS_HAS_SEQ(wh)) { + if (IEEE80211_DIR_DSTODS(wh)) { + qos = *(uint16_t *) + (((struct ieee80211_qosframe_addr4 *) wh)->i_qos); + copyhdrlen -= sizeof(qos); + } else + qos = *(uint16_t *) + (((struct ieee80211_qosframe *) wh)->i_qos); + } else + qos = 0; + + if (iswep) { + const struct ieee80211_cipher *cip; + struct ieee80211_key *k; + + /* + * Construct the 802.11 header+trailer for an encrypted + * frame. The only reason this can fail is because of an + * unknown or unsupported cipher/key type. + * + * NB: we do this even though the firmware will ignore + * what we've done for WEP and TKIP as we need the + * ExtIV filled in for CCMP and this also adjusts + * the headers which simplifies our work below. + */ + k = ieee80211_crypto_encap(ni, m0); + if (k == NULL) { + /* + * This can happen when the key is yanked after the + * frame was queued. Just discard the frame; the + * 802.11 layer counts failures and provides + * debugging/diagnostics. + */ + m_freem(m0); + return EIO; + } + /* + * Adjust the packet length for the crypto additions + * done during encap and any other bits that the f/w + * will add later on. + */ + cip = k->wk_cipher; + pktlen += cip->ic_header + cip->ic_miclen + cip->ic_trailer; + + /* packet header may have moved, reset our local pointer */ + wh = mtod(m0, struct ieee80211_frame *); + } + + if (ieee80211_radiotap_active_vap(vap)) { + sc->sc_tx_th.wt_flags = 0; /* XXX */ + if (iswep) + sc->sc_tx_th.wt_flags |= IEEE80211_RADIOTAP_F_WEP; +#if 0 + sc->sc_tx_th.wt_rate = ds->DataRate; +#endif + sc->sc_tx_th.wt_txpower = ni->ni_txpower; + sc->sc_tx_th.wt_antenna = sc->sc_txantenna; + + ieee80211_radiotap_tx(vap, m0); + } + /* + * Copy up/down the 802.11 header; the firmware requires + * we present a 2-byte payload length followed by a + * 4-address header (w/o QoS), followed (optionally) by + * any WEP/ExtIV header (but only filled in for CCMP). + * We are assured the mbuf has sufficient headroom to + * prepend in-place by the setup of ic_headroom in + * mwl_attach. + */ + if (hdrlen < sizeof(struct mwltxrec)) { + const int space = sizeof(struct mwltxrec) - hdrlen; + if (M_LEADINGSPACE(m0) < space) { + /* NB: should never happen */ + device_printf(sc->sc_dev, + "not enough headroom, need %d found %zd, " + "m_flags 0x%x m_len %d\n", + space, M_LEADINGSPACE(m0), m0->m_flags, m0->m_len); + ieee80211_dump_pkt(ic, + mtod(m0, const uint8_t *), m0->m_len, 0, -1); + m_freem(m0); + sc->sc_stats.mst_tx_noheadroom++; + return EIO; + } + M_PREPEND(m0, space, M_NOWAIT); + } + tr = mtod(m0, struct mwltxrec *); + if (wh != (struct ieee80211_frame *) &tr->wh) + ovbcopy(wh, &tr->wh, hdrlen); + /* + * Note: the "firmware length" is actually the length + * of the fully formed "802.11 payload". That is, it's + * everything except for the 802.11 header. In particular + * this includes all crypto material including the MIC! + */ + tr->fwlen = htole16(pktlen - hdrlen); + + /* + * Load the DMA map so any coalescing is done. This + * also calculates the number of descriptors we need. + */ + error = mwl_tx_dmasetup(sc, bf, m0); + if (error != 0) { + /* NB: stat collected in mwl_tx_dmasetup */ + DPRINTF(sc, MWL_DEBUG_XMIT, + "%s: unable to setup dma\n", __func__); + return error; + } + bf->bf_node = ni; /* NB: held reference */ + m0 = bf->bf_m; /* NB: may have changed */ + tr = mtod(m0, struct mwltxrec *); + wh = (struct ieee80211_frame *)&tr->wh; + + /* + * Formulate tx descriptor. + */ + ds = bf->bf_desc; + txq = bf->bf_txq; + + ds->QosCtrl = qos; /* NB: already little-endian */ +#if MWL_TXDESC == 1 + /* + * NB: multiframes should be zero because the descriptors + * are initialized to zero. This should handle the case + * where the driver is built with MWL_TXDESC=1 but we are + * using firmware with multi-segment support. + */ + ds->PktPtr = htole32(bf->bf_segs[0].ds_addr); + ds->PktLen = htole16(bf->bf_segs[0].ds_len); +#else + ds->multiframes = htole32(bf->bf_nseg); + ds->PktLen = htole16(m0->m_pkthdr.len); + for (i = 0; i < bf->bf_nseg; i++) { + ds->PktPtrArray[i] = htole32(bf->bf_segs[i].ds_addr); + ds->PktLenArray[i] = htole16(bf->bf_segs[i].ds_len); + } +#endif + /* NB: pPhysNext, DataRate, and SapPktInfo setup once, don't touch */ + ds->Format = 0; + ds->pad = 0; + + mn = MWL_NODE(ni); + /* + * Select transmit rate. + */ + switch (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) { + case IEEE80211_FC0_TYPE_MGT: + sc->sc_stats.mst_tx_mgmt++; + /* fall thru... */ + case IEEE80211_FC0_TYPE_CTL: + /* NB: assign to BE q to avoid bursting */ + ds->TxPriority = MWL_WME_AC_BE; + break; + case IEEE80211_FC0_TYPE_DATA: + if (!ismcast) { + const struct ieee80211_txparam *tp = ni->ni_txparms; + /* + * EAPOL frames get forced to a fixed rate and w/o + * aggregation; otherwise check for any fixed rate + * for the client (may depend on association state). + */ + if (m0->m_flags & M_EAPOL) { + const struct mwl_vap *mvp = MWL_VAP_CONST(vap); + ds->Format = mvp->mv_eapolformat; + ds->pad = htole16( + EAGLE_TXD_FIXED_RATE | EAGLE_TXD_DONT_AGGR); + } else if (tp != NULL && /* XXX temp dwds WAR */ + tp->ucastrate != IEEE80211_FIXED_RATE_NONE) { + /* XXX pre-calculate per node */ + ds->Format = htole16( + mwl_calcformat(tp->ucastrate, ni)); + ds->pad = htole16(EAGLE_TXD_FIXED_RATE); + } + /* NB: EAPOL frames will never have qos set */ + if (qos == 0) + ds->TxPriority = txq->qnum; +#if MWL_MAXBA > 3 + else if (mwl_bastream_match(&mn->mn_ba[3], qos)) + ds->TxPriority = mn->mn_ba[3].txq; +#endif +#if MWL_MAXBA > 2 + else if (mwl_bastream_match(&mn->mn_ba[2], qos)) + ds->TxPriority = mn->mn_ba[2].txq; +#endif +#if MWL_MAXBA > 1 + else if (mwl_bastream_match(&mn->mn_ba[1], qos)) + ds->TxPriority = mn->mn_ba[1].txq; +#endif +#if MWL_MAXBA > 0 + else if (mwl_bastream_match(&mn->mn_ba[0], qos)) + ds->TxPriority = mn->mn_ba[0].txq; +#endif + else + ds->TxPriority = txq->qnum; + } else + ds->TxPriority = txq->qnum; + break; + default: + if_printf(ifp, "bogus frame type 0x%x (%s)\n", + wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK, __func__); + sc->sc_stats.mst_tx_badframetype++; + m_freem(m0); + return EIO; + } + + if (IFF_DUMPPKTS_XMIT(sc)) + ieee80211_dump_pkt(ic, + mtod(m0, const uint8_t *)+sizeof(uint16_t), + m0->m_len - sizeof(uint16_t), ds->DataRate, -1); + + MWL_TXQ_LOCK(txq); + ds->Status = htole32(EAGLE_TXD_STATUS_FW_OWNED); + STAILQ_INSERT_TAIL(&txq->active, bf, bf_list); + MWL_TXDESC_SYNC(txq, ds, BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); + + ifp->if_opackets++; + ifp->if_timer = 5; + MWL_TXQ_UNLOCK(txq); + + return 0; +#undef IEEE80211_DIR_DSTODS +} + +static __inline int +mwl_cvtlegacyrix(int rix) +{ +#define N(x) (sizeof(x)/sizeof(x[0])) + static const int ieeerates[] = + { 2, 4, 11, 22, 44, 12, 18, 24, 36, 48, 72, 96, 108 }; + return (rix < N(ieeerates) ? ieeerates[rix] : 0); +#undef N +} + +/* + * Process completed xmit descriptors from the specified queue. + */ +static int +mwl_tx_processq(struct mwl_softc *sc, struct mwl_txq *txq) +{ +#define EAGLE_TXD_STATUS_MCAST \ + (EAGLE_TXD_STATUS_MULTICAST_TX | EAGLE_TXD_STATUS_BROADCAST_TX) + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + struct mwl_txbuf *bf; + struct mwl_txdesc *ds; + struct ieee80211_node *ni; + struct mwl_node *an; + int nreaped; + uint32_t status; + + DPRINTF(sc, MWL_DEBUG_TX_PROC, "%s: tx queue %u\n", __func__, txq->qnum); + for (nreaped = 0;; nreaped++) { + MWL_TXQ_LOCK(txq); + bf = STAILQ_FIRST(&txq->active); + if (bf == NULL) { + MWL_TXQ_UNLOCK(txq); + break; + } + ds = bf->bf_desc; + MWL_TXDESC_SYNC(txq, ds, + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); + if (ds->Status & htole32(EAGLE_TXD_STATUS_FW_OWNED)) { + MWL_TXQ_UNLOCK(txq); + break; + } + STAILQ_REMOVE_HEAD(&txq->active, bf_list); + MWL_TXQ_UNLOCK(txq); + +#ifdef MWL_DEBUG + if (sc->sc_debug & MWL_DEBUG_XMIT_DESC) + mwl_printtxbuf(bf, txq->qnum, nreaped); +#endif + ni = bf->bf_node; + if (ni != NULL) { + an = MWL_NODE(ni); + status = le32toh(ds->Status); + if (status & EAGLE_TXD_STATUS_OK) { + uint16_t Format = le16toh(ds->Format); + uint8_t txant = MS(Format, EAGLE_TXD_ANTENNA); + + sc->sc_stats.mst_ant_tx[txant]++; + if (status & EAGLE_TXD_STATUS_OK_RETRY) + sc->sc_stats.mst_tx_retries++; + if (status & EAGLE_TXD_STATUS_OK_MORE_RETRY) + sc->sc_stats.mst_tx_mretries++; + if (txq->qnum >= MWL_WME_AC_VO) + ic->ic_wme.wme_hipri_traffic++; + ni->ni_txrate = MS(Format, EAGLE_TXD_RATE); + if ((Format & EAGLE_TXD_FORMAT_HT) == 0) { + ni->ni_txrate = mwl_cvtlegacyrix( + ni->ni_txrate); + } else + ni->ni_txrate |= IEEE80211_RATE_MCS; + sc->sc_stats.mst_tx_rate = ni->ni_txrate; + } else { + if (status & EAGLE_TXD_STATUS_FAILED_LINK_ERROR) + sc->sc_stats.mst_tx_linkerror++; + if (status & EAGLE_TXD_STATUS_FAILED_XRETRY) + sc->sc_stats.mst_tx_xretries++; + if (status & EAGLE_TXD_STATUS_FAILED_AGING) + sc->sc_stats.mst_tx_aging++; + if (bf->bf_m->m_flags & M_FF) + sc->sc_stats.mst_ff_txerr++; + } + /* + * Do any tx complete callback. Note this must + * be done before releasing the node reference. + * XXX no way to figure out if frame was ACK'd + */ + if (bf->bf_m->m_flags & M_TXCB) { + /* XXX strip fw len in case header inspected */ + m_adj(bf->bf_m, sizeof(uint16_t)); + ieee80211_process_callback(ni, bf->bf_m, + (status & EAGLE_TXD_STATUS_OK) == 0); + } + /* + * Reclaim reference to node. + * + * NB: the node may be reclaimed here if, for example + * this is a DEAUTH message that was sent and the + * node was timed out due to inactivity. + */ + ieee80211_free_node(ni); + } + ds->Status = htole32(EAGLE_TXD_STATUS_IDLE); + + bus_dmamap_sync(sc->sc_dmat, bf->bf_dmamap, + BUS_DMASYNC_POSTWRITE); + bus_dmamap_unload(sc->sc_dmat, bf->bf_dmamap); + m_freem(bf->bf_m); + + mwl_puttxbuf_tail(txq, bf); + } + return nreaped; +#undef EAGLE_TXD_STATUS_MCAST +} + +/* + * Deferred processing of transmit interrupt; special-cased + * for four hardware queues, 0-3. + */ +static void +mwl_tx_proc(void *arg, int npending) +{ + struct mwl_softc *sc = arg; + struct ifnet *ifp = sc->sc_ifp; + int nreaped; + + /* + * Process each active queue. + */ + nreaped = 0; + if (!STAILQ_EMPTY(&sc->sc_txq[0].active)) + nreaped += mwl_tx_processq(sc, &sc->sc_txq[0]); + if (!STAILQ_EMPTY(&sc->sc_txq[1].active)) + nreaped += mwl_tx_processq(sc, &sc->sc_txq[1]); + if (!STAILQ_EMPTY(&sc->sc_txq[2].active)) + nreaped += mwl_tx_processq(sc, &sc->sc_txq[2]); + if (!STAILQ_EMPTY(&sc->sc_txq[3].active)) + nreaped += mwl_tx_processq(sc, &sc->sc_txq[3]); + + if (nreaped != 0) { + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_timer = 0; + if (!IFQ_IS_EMPTY(&ifp->if_snd)) { + /* NB: kick fw; the tx thread may have been preempted */ + mwl_hal_txstart(sc->sc_mh, 0); + mwl_start(ifp); + } + } +} + +static void +mwl_tx_draintxq(struct mwl_softc *sc, struct mwl_txq *txq) +{ + struct ieee80211_node *ni; + struct mwl_txbuf *bf; + u_int ix; + + /* + * NB: this assumes output has been stopped and + * we do not need to block mwl_tx_tasklet + */ + for (ix = 0;; ix++) { + MWL_TXQ_LOCK(txq); + bf = STAILQ_FIRST(&txq->active); + if (bf == NULL) { + MWL_TXQ_UNLOCK(txq); + break; + } + STAILQ_REMOVE_HEAD(&txq->active, bf_list); + MWL_TXQ_UNLOCK(txq); +#ifdef MWL_DEBUG + if (sc->sc_debug & MWL_DEBUG_RESET) { + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + const struct mwltxrec *tr = + mtod(bf->bf_m, const struct mwltxrec *); + mwl_printtxbuf(bf, txq->qnum, ix); + ieee80211_dump_pkt(ic, (const uint8_t *)&tr->wh, + bf->bf_m->m_len - sizeof(tr->fwlen), 0, -1); + } +#endif /* MWL_DEBUG */ + bus_dmamap_unload(sc->sc_dmat, bf->bf_dmamap); + ni = bf->bf_node; + if (ni != NULL) { + /* + * Reclaim node reference. + */ + ieee80211_free_node(ni); + } + m_freem(bf->bf_m); + + mwl_puttxbuf_tail(txq, bf); + } +} + +/* + * Drain the transmit queues and reclaim resources. + */ +static void +mwl_draintxq(struct mwl_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + int i; + + for (i = 0; i < MWL_NUM_TX_QUEUES; i++) + mwl_tx_draintxq(sc, &sc->sc_txq[i]); + ifp->if_drv_flags &= ~IFF_DRV_OACTIVE; + ifp->if_timer = 0; +} + +#ifdef MWL_DIAGAPI +/* + * Reset the transmit queues to a pristine state after a fw download. + */ +static void +mwl_resettxq(struct mwl_softc *sc) +{ + int i; + + for (i = 0; i < MWL_NUM_TX_QUEUES; i++) + mwl_txq_reset(sc, &sc->sc_txq[i]); +} +#endif /* MWL_DIAGAPI */ + +/* + * Clear the transmit queues of any frames submitted for the + * specified vap. This is done when the vap is deleted so we + * don't potentially reference the vap after it is gone. + * Note we cannot remove the frames; we only reclaim the node + * reference. + */ +static void +mwl_cleartxq(struct mwl_softc *sc, struct ieee80211vap *vap) +{ + struct mwl_txq *txq; + struct mwl_txbuf *bf; + int i; + + for (i = 0; i < MWL_NUM_TX_QUEUES; i++) { + txq = &sc->sc_txq[i]; + MWL_TXQ_LOCK(txq); + STAILQ_FOREACH(bf, &txq->active, bf_list) { + struct ieee80211_node *ni = bf->bf_node; + if (ni != NULL && ni->ni_vap == vap) { + bf->bf_node = NULL; + ieee80211_free_node(ni); + } + } + MWL_TXQ_UNLOCK(txq); + } +} + +static void +mwl_recv_action(struct ieee80211_node *ni, const uint8_t *frm, const uint8_t *efrm) +{ + struct mwl_softc *sc = ni->ni_ic->ic_ifp->if_softc; + const struct ieee80211_action *ia; + + ia = (const struct ieee80211_action *) frm; + if (ia->ia_category == IEEE80211_ACTION_CAT_HT && + ia->ia_action == IEEE80211_ACTION_HT_MIMOPWRSAVE) { + const struct ieee80211_action_ht_mimopowersave *mps = + (const struct ieee80211_action_ht_mimopowersave *) ia; + + mwl_hal_setmimops(sc->sc_mh, ni->ni_macaddr, + mps->am_control & IEEE80211_A_HT_MIMOPWRSAVE_ENA, + MS(mps->am_control, IEEE80211_A_HT_MIMOPWRSAVE_MODE)); + } else + sc->sc_recv_action(ni, frm, efrm); +} + +static int +mwl_addba_request(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, + int dialogtoken, int baparamset, int batimeout) +{ + struct mwl_softc *sc = ni->ni_ic->ic_ifp->if_softc; + struct mwl_node *mn = MWL_NODE(ni); + struct mwl_bastate *bas; + + bas = tap->txa_private; + if (bas == NULL) { + const MWL_HAL_BASTREAM *sp; + /* + * Check for a free BA stream slot. + */ +#if MWL_MAXBA > 3 + if (mn->mn_ba[3].bastream == NULL) + bas = &mn->mn_ba[3]; + else +#endif +#if MWL_MAXBA > 2 + if (mn->mn_ba[2].bastream == NULL) + bas = &mn->mn_ba[2]; + else +#endif +#if MWL_MAXBA > 1 + if (mn->mn_ba[1].bastream == NULL) + bas = &mn->mn_ba[1]; + else +#endif +#if MWL_MAXBA > 0 + if (mn->mn_ba[0].bastream == NULL) + bas = &mn->mn_ba[0]; + else +#endif + { + /* sta already has max BA streams */ + /* XXX assign BA stream to highest priority tid */ + DPRINTF(sc, MWL_DEBUG_AMPDU, + "%s: already has max bastreams\n", __func__); + sc->sc_stats.mst_ampdu_reject++; + return 0; + } + /* NB: no held reference to ni */ + sp = mwl_hal_bastream_alloc(sc->sc_mh, + 1/* XXX immediate*/, ni->ni_macaddr, tap->txa_ac, + ni->ni_htparam, ni, tap); + if (sp == NULL) { + /* + * No available stream, return 0 so no + * a-mpdu aggregation will be done. + */ + DPRINTF(sc, MWL_DEBUG_AMPDU, + "%s: no bastream available\n", __func__); + sc->sc_stats.mst_ampdu_nostream++; + return 0; + } + DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: alloc bastream %p\n", + __func__, sp); + /* NB: qos is left zero so we won't match in mwl_tx_start */ + bas->bastream = sp; + tap->txa_private = bas; + } + /* fetch current seq# from the firmware; if available */ + if (mwl_hal_bastream_get_seqno(sc->sc_mh, bas->bastream, + &tap->txa_start) != 0) + tap->txa_start = 0; + return sc->sc_addba_request(ni, tap, dialogtoken, baparamset, batimeout); +} + +static int +mwl_addba_response(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap, + int code, int baparamset, int batimeout) +{ + struct mwl_softc *sc = ni->ni_ic->ic_ifp->if_softc; + struct mwl_bastate *bas; + + bas = tap->txa_private; + if (bas == NULL) { + /* XXX should not happen */ + DPRINTF(sc, MWL_DEBUG_AMPDU, + "%s: no BA stream allocated, AC %d\n", + __func__, tap->txa_ac); + sc->sc_stats.mst_addba_nostream++; + return 0; + } + if (code == IEEE80211_STATUS_SUCCESS) { + int bufsiz, error; + + /* + * Tell the firmware to setup the BA stream; + * we know resources are available because we + * pre-allocated one before forming the request. + */ + bufsiz = MS(baparamset, IEEE80211_BAPS_BUFSIZ); + if (bufsiz == 0) + bufsiz = IEEE80211_AGGR_BAWMAX; + error = mwl_hal_bastream_create(sc->sc_mh, bas->bastream, + bufsiz, bufsiz-1, tap->txa_start); + if (error != 0) { + /* + * Setup failed, return immediately so no a-mpdu + * aggregation will be done. + */ + mwl_hal_bastream_destroy(sc->sc_mh, bas->bastream); + mwl_bastream_free(bas); + tap->txa_private = NULL; + + DPRINTF(sc, MWL_DEBUG_AMPDU, + "%s: create failed, error %d, bufsiz %d AC %d " + "htparam 0x%x\n", __func__, error, bufsiz, + tap->txa_ac, ni->ni_htparam); + sc->sc_stats.mst_bacreate_failed++; + return 0; + } + /* NB: cache txq to avoid ptr indirect */ + mwl_bastream_setup(bas, tap->txa_ac, bas->bastream->txq); + DPRINTF(sc, MWL_DEBUG_AMPDU, + "%s: bastream %p assigned to txq %d AC %d bufsiz %d " + "htparam 0x%x\n", __func__, bas->bastream, + bas->txq, tap->txa_ac, bufsiz, ni->ni_htparam); + } else { + /* + * Other side NAK'd us; return the resources. + */ + DPRINTF(sc, MWL_DEBUG_AMPDU, + "%s: request failed with code %d, destroy bastream %p\n", + __func__, code, bas->bastream); + mwl_hal_bastream_destroy(sc->sc_mh, bas->bastream); + mwl_bastream_free(bas); + tap->txa_private = NULL; + } + /* NB: firmware sends BAR so we don't need to */ + return sc->sc_addba_response(ni, tap, code, baparamset, batimeout); +} + +static void +mwl_addba_stop(struct ieee80211_node *ni, struct ieee80211_tx_ampdu *tap) +{ + struct mwl_softc *sc = ni->ni_ic->ic_ifp->if_softc; + struct mwl_bastate *bas; + + bas = tap->txa_private; + if (bas != NULL) { + DPRINTF(sc, MWL_DEBUG_AMPDU, "%s: destroy bastream %p\n", + __func__, bas->bastream); + mwl_hal_bastream_destroy(sc->sc_mh, bas->bastream); + mwl_bastream_free(bas); + tap->txa_private = NULL; + } + sc->sc_addba_stop(ni, tap); +} + +/* + * Setup the rx data structures. This should only be + * done once or we may get out of sync with the firmware. + */ +static int +mwl_startrecv(struct mwl_softc *sc) +{ + if (!sc->sc_recvsetup) { + struct mwl_rxbuf *bf, *prev; + struct mwl_rxdesc *ds; + + prev = NULL; + STAILQ_FOREACH(bf, &sc->sc_rxbuf, bf_list) { + int error = mwl_rxbuf_init(sc, bf); + if (error != 0) { + DPRINTF(sc, MWL_DEBUG_RECV, + "%s: mwl_rxbuf_init failed %d\n", + __func__, error); + return error; + } + if (prev != NULL) { + ds = prev->bf_desc; + ds->pPhysNext = htole32(bf->bf_daddr); + } + prev = bf; + } + if (prev != NULL) { + ds = prev->bf_desc; + ds->pPhysNext = + htole32(STAILQ_FIRST(&sc->sc_rxbuf)->bf_daddr); + } + sc->sc_recvsetup = 1; + } + mwl_mode_init(sc); /* set filters, etc. */ + return 0; +} + +static MWL_HAL_APMODE +mwl_getapmode(const struct ieee80211vap *vap, struct ieee80211_channel *chan) +{ + MWL_HAL_APMODE mode; + + if (IEEE80211_IS_CHAN_HT(chan)) { + if (vap->iv_flags_ext & IEEE80211_FEXT_PUREN) + mode = AP_MODE_N_ONLY; + else if (IEEE80211_IS_CHAN_5GHZ(chan)) + mode = AP_MODE_AandN; + else if (vap->iv_flags & IEEE80211_F_PUREG) + mode = AP_MODE_GandN; + else + mode = AP_MODE_BandGandN; + } else if (IEEE80211_IS_CHAN_ANYG(chan)) { + if (vap->iv_flags & IEEE80211_F_PUREG) + mode = AP_MODE_G_ONLY; + else + mode = AP_MODE_MIXED; + } else if (IEEE80211_IS_CHAN_B(chan)) + mode = AP_MODE_B_ONLY; + else if (IEEE80211_IS_CHAN_A(chan)) + mode = AP_MODE_A_ONLY; + else + mode = AP_MODE_MIXED; /* XXX should not happen? */ + return mode; +} + +static int +mwl_setapmode(struct ieee80211vap *vap, struct ieee80211_channel *chan) +{ + struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; + return mwl_hal_setapmode(hvap, mwl_getapmode(vap, chan)); +} + +/* + * Set/change channels. + */ +static int +mwl_chan_set(struct mwl_softc *sc, struct ieee80211_channel *chan) +{ + struct mwl_hal *mh = sc->sc_mh; + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + MWL_HAL_CHANNEL hchan; + int maxtxpow; + + DPRINTF(sc, MWL_DEBUG_RESET, "%s: chan %u MHz/flags 0x%x\n", + __func__, chan->ic_freq, chan->ic_flags); + + /* + * Convert to a HAL channel description with + * the flags constrained to reflect the current + * operating mode. + */ + mwl_mapchan(&hchan, chan); + mwl_hal_intrset(mh, 0); /* disable interrupts */ +#if 0 + mwl_draintxq(sc); /* clear pending tx frames */ +#endif + mwl_hal_setchannel(mh, &hchan); + /* + * Tx power is cap'd by the regulatory setting and + * possibly a user-set limit. We pass the min of + * these to the hal to apply them to the cal data + * for this channel. + * XXX min bound? + */ + maxtxpow = 2*chan->ic_maxregpower; + if (maxtxpow > ic->ic_txpowlimit) + maxtxpow = ic->ic_txpowlimit; + mwl_hal_settxpower(mh, &hchan, maxtxpow / 2); + /* NB: potentially change mcast/mgt rates */ + mwl_setcurchanrates(sc); + + /* + * Update internal state. + */ + sc->sc_tx_th.wt_chan_freq = htole16(chan->ic_freq); + sc->sc_rx_th.wr_chan_freq = htole16(chan->ic_freq); + if (IEEE80211_IS_CHAN_A(chan)) { + sc->sc_tx_th.wt_chan_flags = htole16(IEEE80211_CHAN_A); + sc->sc_rx_th.wr_chan_flags = htole16(IEEE80211_CHAN_A); + } else if (IEEE80211_IS_CHAN_ANYG(chan)) { + sc->sc_tx_th.wt_chan_flags = htole16(IEEE80211_CHAN_G); + sc->sc_rx_th.wr_chan_flags = htole16(IEEE80211_CHAN_G); + } else { + sc->sc_tx_th.wt_chan_flags = htole16(IEEE80211_CHAN_B); + sc->sc_rx_th.wr_chan_flags = htole16(IEEE80211_CHAN_B); + } + sc->sc_curchan = hchan; + mwl_hal_intrset(mh, sc->sc_imask); + + return 0; +} + +static void +mwl_scan_start(struct ieee80211com *ic) +{ + struct ifnet *ifp = ic->ic_ifp; + struct mwl_softc *sc = ifp->if_softc; + + DPRINTF(sc, MWL_DEBUG_STATE, "%s\n", __func__); +} + +static void +mwl_scan_end(struct ieee80211com *ic) +{ + struct ifnet *ifp = ic->ic_ifp; + struct mwl_softc *sc = ifp->if_softc; + + DPRINTF(sc, MWL_DEBUG_STATE, "%s\n", __func__); +} + +static void +mwl_set_channel(struct ieee80211com *ic) +{ + struct ifnet *ifp = ic->ic_ifp; + struct mwl_softc *sc = ifp->if_softc; + + (void) mwl_chan_set(sc, ic->ic_curchan); +} + +/* + * Handle a channel switch request. We inform the firmware + * and mark the global state to suppress various actions. + * NB: we issue only one request to the fw; we may be called + * multiple times if there are multiple vap's. + */ +static void +mwl_startcsa(struct ieee80211vap *vap) +{ + struct ieee80211com *ic = vap->iv_ic; + struct mwl_softc *sc = ic->ic_ifp->if_softc; + MWL_HAL_CHANNEL hchan; + + if (sc->sc_csapending) + return; + + mwl_mapchan(&hchan, ic->ic_csa_newchan); + /* 1 =>'s quiet channel */ + mwl_hal_setchannelswitchie(sc->sc_mh, &hchan, 1, ic->ic_csa_count); + sc->sc_csapending = 1; +} + +/* + * Plumb any static WEP key for the station. This is + * necessary as we must propagate the key from the + * global key table of the vap to each sta db entry. + */ +static void +mwl_setanywepkey(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + if ((vap->iv_flags & (IEEE80211_F_PRIVACY|IEEE80211_F_WPA)) == + IEEE80211_F_PRIVACY && + vap->iv_def_txkey != IEEE80211_KEYIX_NONE && + vap->iv_nw_keys[vap->iv_def_txkey].wk_keyix != IEEE80211_KEYIX_NONE) + (void) mwl_key_set(vap, &vap->iv_nw_keys[vap->iv_def_txkey], mac); +} + +static int +mwl_peerstadb(struct ieee80211_node *ni, int aid, int staid, MWL_HAL_PEERINFO *pi) +{ +#define WME(ie) ((const struct ieee80211_wme_info *) ie) + struct ieee80211vap *vap = ni->ni_vap; + struct mwl_hal_vap *hvap; + int error; + + if (vap->iv_opmode == IEEE80211_M_WDS) { + /* + * WDS vap's do not have a f/w vap; instead they piggyback + * on an AP vap and we must install the sta db entry and + * crypto state using that AP's handle (the WDS vap has none). + */ + hvap = MWL_VAP(vap)->mv_ap_hvap; + } else + hvap = MWL_VAP(vap)->mv_hvap; + error = mwl_hal_newstation(hvap, ni->ni_macaddr, + aid, staid, pi, + ni->ni_flags & (IEEE80211_NODE_QOS | IEEE80211_NODE_HT), + ni->ni_ies.wme_ie != NULL ? WME(ni->ni_ies.wme_ie)->wme_info : 0); + if (error == 0) { + /* + * Setup security for this station. For sta mode this is + * needed even though do the same thing on transition to + * AUTH state because the call to mwl_hal_newstation + * clobbers the crypto state we setup. + */ + mwl_setanywepkey(vap, ni->ni_macaddr); + } + return error; +#undef WME +} + +static void +mwl_setglobalkeys(struct ieee80211vap *vap) +{ + struct ieee80211_key *wk; + + wk = &vap->iv_nw_keys[0]; + for (; wk < &vap->iv_nw_keys[IEEE80211_WEP_NKID]; wk++) + if (wk->wk_keyix != IEEE80211_KEYIX_NONE) + (void) mwl_key_set(vap, wk, vap->iv_myaddr); +} + +/* + * Re-create the local sta db entry for a vap to ensure + * up to date WME state is pushed to the firmware. Because + * this resets crypto state this must be followed by a + * reload of any keys in the global key table. + */ +static int +mwl_localstadb(struct ieee80211vap *vap) +{ +#define WME(ie) ((const struct ieee80211_wme_info *) ie) + struct mwl_hal_vap *hvap = MWL_VAP(vap)->mv_hvap; + struct ieee80211_node *bss; + int error; + + switch (vap->iv_opmode) { + case IEEE80211_M_STA: + bss = vap->iv_bss; + error = mwl_hal_newstation(hvap, vap->iv_myaddr, + 0, 0, NULL, bss->ni_flags & IEEE80211_NODE_QOS, + bss->ni_ies.wme_ie != NULL ? + WME(bss->ni_ies.wme_ie)->wme_info : 0); + if (error == 0) + mwl_setglobalkeys(vap); + break; + case IEEE80211_M_HOSTAP: + error = mwl_hal_newstation(hvap, vap->iv_myaddr, + 0, 0, NULL, vap->iv_flags & IEEE80211_F_WME, 0); + if (error == 0) + mwl_setglobalkeys(vap); + break; + default: + error = 0; + break; + } + return error; +#undef WME +} + +static int +mwl_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg) +{ + struct mwl_vap *mvp = MWL_VAP(vap); + struct mwl_hal_vap *hvap = mvp->mv_hvap; + struct ieee80211com *ic = vap->iv_ic; + struct ieee80211_node *ni = NULL; + struct ifnet *ifp = ic->ic_ifp; + struct mwl_softc *sc = ifp->if_softc; + struct mwl_hal *mh = sc->sc_mh; + enum ieee80211_state ostate = vap->iv_state; + int error; + + DPRINTF(sc, MWL_DEBUG_STATE, "%s: %s: %s -> %s\n", + vap->iv_ifp->if_xname, __func__, + ieee80211_state_name[ostate], ieee80211_state_name[nstate]); + + callout_stop(&sc->sc_timer); + /* + * Clear current radar detection state. + */ + if (ostate == IEEE80211_S_CAC) { + /* stop quiet mode radar detection */ + mwl_hal_setradardetection(mh, DR_CHK_CHANNEL_AVAILABLE_STOP); + } else if (sc->sc_radarena) { + /* stop in-service radar detection */ + mwl_hal_setradardetection(mh, DR_DFS_DISABLE); + sc->sc_radarena = 0; + } + /* + * Carry out per-state actions before doing net80211 work. + */ + if (nstate == IEEE80211_S_INIT) { + /* NB: only ap+sta vap's have a fw entity */ + if (hvap != NULL) + mwl_hal_stop(hvap); + } else if (nstate == IEEE80211_S_SCAN) { + mwl_hal_start(hvap); + /* NB: this disables beacon frames */ + mwl_hal_setinframode(hvap); + } else if (nstate == IEEE80211_S_AUTH) { + /* + * Must create a sta db entry in case a WEP key needs to + * be plumbed. This entry will be overwritten if we + * associate; otherwise it will be reclaimed on node free. + */ + ni = vap->iv_bss; + MWL_NODE(ni)->mn_hvap = hvap; + (void) mwl_peerstadb(ni, 0, 0, NULL); + } else if (nstate == IEEE80211_S_CSA) { + /* XXX move to below? */ + if (vap->iv_opmode == IEEE80211_M_HOSTAP) + mwl_startcsa(vap); + } else if (nstate == IEEE80211_S_CAC) { + /* XXX move to below? */ + /* stop ap xmit and enable quiet mode radar detection */ + mwl_hal_setradardetection(mh, DR_CHK_CHANNEL_AVAILABLE_START); + } + + /* + * Invoke the parent method to do net80211 work. + */ + error = mvp->mv_newstate(vap, nstate, arg); + + /* + * Carry out work that must be done after net80211 runs; + * this work requires up to date state (e.g. iv_bss). + */ + if (error == 0 && nstate == IEEE80211_S_RUN) { + /* NB: collect bss node again, it may have changed */ + ni = vap->iv_bss; + + DPRINTF(sc, MWL_DEBUG_STATE, + "%s: %s(RUN): iv_flags 0x%08x bintvl %d bssid %s " + "capinfo 0x%04x chan %d\n", + vap->iv_ifp->if_xname, __func__, vap->iv_flags, + ni->ni_intval, ether_sprintf(ni->ni_bssid), ni->ni_capinfo, + ieee80211_chan2ieee(ic, ic->ic_curchan)); + + /* + * Recreate local sta db entry to update WME state. + */ + mwl_localstadb(vap); + switch (vap->iv_opmode) { + case IEEE80211_M_HOSTAP: + if (ostate == IEEE80211_S_CAC) { + /* enable in-service radar detection */ + mwl_hal_setradardetection(mh, + DR_IN_SERVICE_MONITOR_START); + sc->sc_radarena = 1; + } + /* + * Allocate and setup the beacon frame + * (and related state). + */ + error = mwl_reset_vap(vap, IEEE80211_S_RUN); + if (error != 0) { + DPRINTF(sc, MWL_DEBUG_STATE, + "%s: beacon setup failed, error %d\n", + __func__, error); + goto bad; + } + /* NB: must be after setting up beacon */ + mwl_hal_start(hvap); + break; + case IEEE80211_M_STA: + DPRINTF(sc, MWL_DEBUG_STATE, "%s: %s: aid 0x%x\n", + vap->iv_ifp->if_xname, __func__, ni->ni_associd); + /* + * Set state now that we're associated. + */ + mwl_hal_setassocid(hvap, ni->ni_bssid, ni->ni_associd); + mwl_setrates(vap); + mwl_hal_setrtsthreshold(hvap, vap->iv_rtsthreshold); + break; + case IEEE80211_M_WDS: + DPRINTF(sc, MWL_DEBUG_STATE, "%s: %s: bssid %s\n", + vap->iv_ifp->if_xname, __func__, + ether_sprintf(ni->ni_bssid)); + mwl_seteapolformat(vap); + break; + default: + break; + } + /* + * Set CS mode according to operating channel; + * this mostly an optimization for 5GHz. + * + * NB: must follow mwl_hal_start which resets csmode + */ + if (IEEE80211_IS_CHAN_5GHZ(ic->ic_bsschan)) + mwl_hal_setcsmode(mh, CSMODE_AGGRESSIVE); + else + mwl_hal_setcsmode(mh, CSMODE_AUTO_ENA); + /* + * Start timer to prod firmware. + */ + if (sc->sc_ageinterval != 0) + callout_reset(&sc->sc_timer, sc->sc_ageinterval*hz, + mwl_agestations, sc); + } else if (nstate == IEEE80211_S_SLEEP) { + /* XXX set chip in power save */ + } +bad: + return error; +} + +/* + * Convert a legacy rate set to a firmware bitmask. + */ +static uint32_t +get_rate_bitmap(const struct ieee80211_rateset *rs) +{ + uint32_t rates; + int i; + + rates = 0; + for (i = 0; i < rs->rs_nrates; i++) + switch (rs->rs_rates[i] & IEEE80211_RATE_VAL) { + case 2: rates |= 0x001; break; + case 4: rates |= 0x002; break; + case 11: rates |= 0x004; break; + case 22: rates |= 0x008; break; + case 44: rates |= 0x010; break; + case 12: rates |= 0x020; break; + case 18: rates |= 0x040; break; + case 24: rates |= 0x080; break; + case 36: rates |= 0x100; break; + case 48: rates |= 0x200; break; + case 72: rates |= 0x400; break; + case 96: rates |= 0x800; break; + case 108: rates |= 0x1000; break; + } + return rates; +} + +/* + * Construct an HT firmware bitmask from an HT rate set. + */ +static uint32_t +get_htrate_bitmap(const struct ieee80211_htrateset *rs) +{ + uint32_t rates; + int i; + + rates = 0; + for (i = 0; i < rs->rs_nrates; i++) { + if (rs->rs_rates[i] < 16) + rates |= 1<<rs->rs_rates[i]; + } + return rates; +} + +/* + * Manage station id's; these are separate from AID's + * as AID's may have values out of the range of possible + * station id's acceptable to the firmware. + */ +static int +allocstaid(struct mwl_softc *sc, int aid) +{ + int staid; + + if (!(0 < aid && aid < MWL_MAXSTAID) || isset(sc->sc_staid, aid)) { + /* NB: don't use 0 */ + for (staid = 1; staid < MWL_MAXSTAID; staid++) + if (isclr(sc->sc_staid, staid)) + break; + } else + staid = aid; + setbit(sc->sc_staid, staid); + return staid; +} + +static void +delstaid(struct mwl_softc *sc, int staid) +{ + clrbit(sc->sc_staid, staid); +} + +/* + * Setup driver-specific state for a newly associated node. + * Note that we're called also on a re-associate, the isnew + * param tells us if this is the first time or not. + */ +static void +mwl_newassoc(struct ieee80211_node *ni, int isnew) +{ + struct ieee80211vap *vap = ni->ni_vap; + struct mwl_softc *sc = vap->iv_ic->ic_ifp->if_softc; + struct mwl_node *mn = MWL_NODE(ni); + MWL_HAL_PEERINFO pi; + uint16_t aid; + int error; + + aid = IEEE80211_AID(ni->ni_associd); + if (isnew) { + mn->mn_staid = allocstaid(sc, aid); + mn->mn_hvap = MWL_VAP(vap)->mv_hvap; + } else { + mn = MWL_NODE(ni); + /* XXX reset BA stream? */ + } + DPRINTF(sc, MWL_DEBUG_NODE, "%s: mac %s isnew %d aid %d staid %d\n", + __func__, ether_sprintf(ni->ni_macaddr), isnew, aid, mn->mn_staid); + /* + * Craft station database entry for station. + * NB: use host byte order here, the hal handles byte swapping. + */ + memset(&pi, 0, sizeof(pi)); + pi.LegacyRateBitMap = get_rate_bitmap(&ni->ni_rates); + pi.CapInfo = ni->ni_capinfo; + if (ni->ni_flags & IEEE80211_NODE_HT) { + /* HT capabilities, etc */ + pi.HTCapabilitiesInfo = ni->ni_htcap; + /* XXX pi.HTCapabilitiesInfo */ + pi.MacHTParamInfo = ni->ni_htparam; + pi.HTRateBitMap = get_htrate_bitmap(&ni->ni_htrates); + pi.AddHtInfo.ControlChan = ni->ni_htctlchan; + pi.AddHtInfo.AddChan = ni->ni_ht2ndchan; + pi.AddHtInfo.OpMode = ni->ni_htopmode; + pi.AddHtInfo.stbc = ni->ni_htstbc; + + /* constrain according to local configuration */ + if ((vap->iv_flags_ext & IEEE80211_FEXT_SHORTGI40) == 0) + pi.HTCapabilitiesInfo &= ~IEEE80211_HTCAP_SHORTGI40; + if ((vap->iv_flags_ext & IEEE80211_FEXT_SHORTGI20) == 0) + pi.HTCapabilitiesInfo &= ~IEEE80211_HTCAP_SHORTGI20; + if (ni->ni_chw != 40) + pi.HTCapabilitiesInfo &= ~IEEE80211_HTCAP_CHWIDTH40; + } + error = mwl_peerstadb(ni, aid, mn->mn_staid, &pi); + if (error != 0) { + DPRINTF(sc, MWL_DEBUG_NODE, + "%s: error %d creating sta db entry\n", + __func__, error); + /* XXX how to deal with error? */ + } +} + +/* + * Periodically poke the firmware to age out station state + * (power save queues, pending tx aggregates). + */ +static void +mwl_agestations(void *arg) +{ + struct mwl_softc *sc = arg; + + mwl_hal_setkeepalive(sc->sc_mh); + if (sc->sc_ageinterval != 0) /* NB: catch dynamic changes */ + callout_reset(&sc->sc_timer, sc->sc_ageinterval*hz, + mwl_agestations, sc); +} + +static const struct mwl_hal_channel * +findhalchannel(const MWL_HAL_CHANNELINFO *ci, int ieee) +{ + int i; + + for (i = 0; i < ci->nchannels; i++) { + const struct mwl_hal_channel *hc = &ci->channels[i]; + if (hc->ieee == ieee) + return hc; + } + return NULL; +} + +static int +mwl_setregdomain(struct ieee80211com *ic, struct ieee80211_regdomain *rd, + int nchan, struct ieee80211_channel chans[]) +{ + struct mwl_softc *sc = ic->ic_ifp->if_softc; + struct mwl_hal *mh = sc->sc_mh; + const MWL_HAL_CHANNELINFO *ci; + int i; + + for (i = 0; i < nchan; i++) { + struct ieee80211_channel *c = &chans[i]; + const struct mwl_hal_channel *hc; + + if (IEEE80211_IS_CHAN_2GHZ(c)) { + mwl_hal_getchannelinfo(mh, MWL_FREQ_BAND_2DOT4GHZ, + IEEE80211_IS_CHAN_HT40(c) ? + MWL_CH_40_MHz_WIDTH : MWL_CH_20_MHz_WIDTH, &ci); + } else if (IEEE80211_IS_CHAN_5GHZ(c)) { + mwl_hal_getchannelinfo(mh, MWL_FREQ_BAND_5GHZ, + IEEE80211_IS_CHAN_HT40(c) ? + MWL_CH_40_MHz_WIDTH : MWL_CH_20_MHz_WIDTH, &ci); + } else { + if_printf(ic->ic_ifp, + "%s: channel %u freq %u/0x%x not 2.4/5GHz\n", + __func__, c->ic_ieee, c->ic_freq, c->ic_flags); + return EINVAL; + } + /* + * Verify channel has cal data and cap tx power. + */ + hc = findhalchannel(ci, c->ic_ieee); + if (hc != NULL) { + if (c->ic_maxpower > 2*hc->maxTxPow) + c->ic_maxpower = 2*hc->maxTxPow; + goto next; + } + if (IEEE80211_IS_CHAN_HT40(c)) { + /* + * Look for the extension channel since the + * hal table only has the primary channel. + */ + hc = findhalchannel(ci, c->ic_extieee); + if (hc != NULL) { + if (c->ic_maxpower > 2*hc->maxTxPow) + c->ic_maxpower = 2*hc->maxTxPow; + goto next; + } + } + if_printf(ic->ic_ifp, + "%s: no cal data for channel %u ext %u freq %u/0x%x\n", + __func__, c->ic_ieee, c->ic_extieee, + c->ic_freq, c->ic_flags); + return EINVAL; + next: + ; + } + return 0; +} + +#define IEEE80211_CHAN_HTG (IEEE80211_CHAN_HT|IEEE80211_CHAN_G) +#define IEEE80211_CHAN_HTA (IEEE80211_CHAN_HT|IEEE80211_CHAN_A) + +static void +addchan(struct ieee80211_channel *c, int freq, int flags, int ieee, int txpow) +{ + c->ic_freq = freq; + c->ic_flags = flags; + c->ic_ieee = ieee; + c->ic_minpower = 0; + c->ic_maxpower = 2*txpow; + c->ic_maxregpower = txpow; +} + +static const struct ieee80211_channel * +findchannel(const struct ieee80211_channel chans[], int nchans, + int freq, int flags) +{ + const struct ieee80211_channel *c; + int i; + + for (i = 0; i < nchans; i++) { + c = &chans[i]; + if (c->ic_freq == freq && c->ic_flags == flags) + return c; + } + return NULL; +} + +static void +addht40channels(struct ieee80211_channel chans[], int maxchans, int *nchans, + const MWL_HAL_CHANNELINFO *ci, int flags) +{ + struct ieee80211_channel *c; + const struct ieee80211_channel *extc; + const struct mwl_hal_channel *hc; + int i; + + c = &chans[*nchans]; + + flags &= ~IEEE80211_CHAN_HT; + for (i = 0; i < ci->nchannels; i++) { + /* + * Each entry defines an HT40 channel pair; find the + * extension channel above and the insert the pair. + */ + hc = &ci->channels[i]; + extc = findchannel(chans, *nchans, hc->freq+20, + flags | IEEE80211_CHAN_HT20); + if (extc != NULL) { + if (*nchans >= maxchans) + break; + addchan(c, hc->freq, flags | IEEE80211_CHAN_HT40U, + hc->ieee, hc->maxTxPow); + c->ic_extieee = extc->ic_ieee; + c++, (*nchans)++; + if (*nchans >= maxchans) + break; + addchan(c, extc->ic_freq, flags | IEEE80211_CHAN_HT40D, + extc->ic_ieee, hc->maxTxPow); + c->ic_extieee = hc->ieee; + c++, (*nchans)++; + } + } +} + +static void +addchannels(struct ieee80211_channel chans[], int maxchans, int *nchans, + const MWL_HAL_CHANNELINFO *ci, int flags) +{ + struct ieee80211_channel *c; + int i; + + c = &chans[*nchans]; + + for (i = 0; i < ci->nchannels; i++) { + const struct mwl_hal_channel *hc; + + hc = &ci->channels[i]; + if (*nchans >= maxchans) + break; + addchan(c, hc->freq, flags, hc->ieee, hc->maxTxPow); + c++, (*nchans)++; + if (flags == IEEE80211_CHAN_G || flags == IEEE80211_CHAN_HTG) { + /* g channel have a separate b-only entry */ + if (*nchans >= maxchans) + break; + c[0] = c[-1]; + c[-1].ic_flags = IEEE80211_CHAN_B; + c++, (*nchans)++; + } + if (flags == IEEE80211_CHAN_HTG) { + /* HT g channel have a separate g-only entry */ + if (*nchans >= maxchans) + break; + c[-1].ic_flags = IEEE80211_CHAN_G; + c[0] = c[-1]; + c[0].ic_flags &= ~IEEE80211_CHAN_HT; + c[0].ic_flags |= IEEE80211_CHAN_HT20; /* HT20 */ + c++, (*nchans)++; + } + if (flags == IEEE80211_CHAN_HTA) { + /* HT a channel have a separate a-only entry */ + if (*nchans >= maxchans) + break; + c[-1].ic_flags = IEEE80211_CHAN_A; + c[0] = c[-1]; + c[0].ic_flags &= ~IEEE80211_CHAN_HT; + c[0].ic_flags |= IEEE80211_CHAN_HT20; /* HT20 */ + c++, (*nchans)++; + } + } +} + +static void +getchannels(struct mwl_softc *sc, int maxchans, int *nchans, + struct ieee80211_channel chans[]) +{ + const MWL_HAL_CHANNELINFO *ci; + + /* + * Use the channel info from the hal to craft the + * channel list. Note that we pass back an unsorted + * list; the caller is required to sort it for us + * (if desired). + */ + *nchans = 0; + if (mwl_hal_getchannelinfo(sc->sc_mh, + MWL_FREQ_BAND_2DOT4GHZ, MWL_CH_20_MHz_WIDTH, &ci) == 0) + addchannels(chans, maxchans, nchans, ci, IEEE80211_CHAN_HTG); + if (mwl_hal_getchannelinfo(sc->sc_mh, + MWL_FREQ_BAND_5GHZ, MWL_CH_20_MHz_WIDTH, &ci) == 0) + addchannels(chans, maxchans, nchans, ci, IEEE80211_CHAN_HTA); + if (mwl_hal_getchannelinfo(sc->sc_mh, + MWL_FREQ_BAND_2DOT4GHZ, MWL_CH_40_MHz_WIDTH, &ci) == 0) + addht40channels(chans, maxchans, nchans, ci, IEEE80211_CHAN_HTG); + if (mwl_hal_getchannelinfo(sc->sc_mh, + MWL_FREQ_BAND_5GHZ, MWL_CH_40_MHz_WIDTH, &ci) == 0) + addht40channels(chans, maxchans, nchans, ci, IEEE80211_CHAN_HTA); +} + +static void +mwl_getradiocaps(struct ieee80211com *ic, + int maxchans, int *nchans, struct ieee80211_channel chans[]) +{ + struct mwl_softc *sc = ic->ic_ifp->if_softc; + + getchannels(sc, maxchans, nchans, chans); +} + +static int +mwl_getchannels(struct mwl_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + struct ieee80211com *ic = ifp->if_l2com; + + /* + * Use the channel info from the hal to craft the + * channel list for net80211. Note that we pass up + * an unsorted list; net80211 will sort it for us. + */ + memset(ic->ic_channels, 0, sizeof(ic->ic_channels)); + ic->ic_nchans = 0; + getchannels(sc, IEEE80211_CHAN_MAX, &ic->ic_nchans, ic->ic_channels); + + ic->ic_regdomain.regdomain = SKU_DEBUG; + ic->ic_regdomain.country = CTRY_DEFAULT; + ic->ic_regdomain.location = 'I'; + ic->ic_regdomain.isocc[0] = ' '; /* XXX? */ + ic->ic_regdomain.isocc[1] = ' '; + return (ic->ic_nchans == 0 ? EIO : 0); +} +#undef IEEE80211_CHAN_HTA +#undef IEEE80211_CHAN_HTG + +#ifdef MWL_DEBUG +static void +mwl_printrxbuf(const struct mwl_rxbuf *bf, u_int ix) +{ + const struct mwl_rxdesc *ds = bf->bf_desc; + uint32_t status = le32toh(ds->Status); + + printf("R[%2u] (DS.V:%p DS.P:%p) NEXT:%08x DATA:%08x RC:%02x%s\n" + " STAT:%02x LEN:%04x RSSI:%02x CHAN:%02x RATE:%02x QOS:%04x HT:%04x\n", + ix, ds, (const struct mwl_desc *)bf->bf_daddr, + le32toh(ds->pPhysNext), le32toh(ds->pPhysBuffData), + ds->RxControl, + ds->RxControl != EAGLE_RXD_CTRL_DRIVER_OWN ? + "" : (status & EAGLE_RXD_STATUS_OK) ? " *" : " !", + ds->Status, le16toh(ds->PktLen), ds->RSSI, ds->Channel, + ds->Rate, le16toh(ds->QosCtrl), le16toh(ds->HtSig2)); +} + +static void +mwl_printtxbuf(const struct mwl_txbuf *bf, u_int qnum, u_int ix) +{ + const struct mwl_txdesc *ds = bf->bf_desc; + uint32_t status = le32toh(ds->Status); + + printf("Q%u[%3u]", qnum, ix); + printf(" (DS.V:%p DS.P:%p)\n", + ds, (const struct mwl_txdesc *)bf->bf_daddr); + printf(" NEXT:%08x DATA:%08x LEN:%04x STAT:%08x%s\n", + le32toh(ds->pPhysNext), + le32toh(ds->PktPtr), le16toh(ds->PktLen), status, + status & EAGLE_TXD_STATUS_USED ? + "" : (status & 3) != 0 ? " *" : " !"); + printf(" RATE:%02x PRI:%x QOS:%04x SAP:%08x FORMAT:%04x\n", + ds->DataRate, ds->TxPriority, le16toh(ds->QosCtrl), + le32toh(ds->SapPktInfo), le16toh(ds->Format)); +#if MWL_TXDESC > 1 + printf(" MULTIFRAMES:%u LEN:%04x %04x %04x %04x %04x %04x\n" + , le32toh(ds->multiframes) + , le16toh(ds->PktLenArray[0]), le16toh(ds->PktLenArray[1]) + , le16toh(ds->PktLenArray[2]), le16toh(ds->PktLenArray[3]) + , le16toh(ds->PktLenArray[4]), le16toh(ds->PktLenArray[5]) + ); + printf(" DATA:%08x %08x %08x %08x %08x %08x\n" + , le32toh(ds->PktPtrArray[0]), le32toh(ds->PktPtrArray[1]) + , le32toh(ds->PktPtrArray[2]), le32toh(ds->PktPtrArray[3]) + , le32toh(ds->PktPtrArray[4]), le32toh(ds->PktPtrArray[5]) + ); +#endif +#if 0 +{ const uint8_t *cp = (const uint8_t *) ds; + int i; + for (i = 0; i < sizeof(struct mwl_txdesc); i++) { + printf("%02x ", cp[i]); + if (((i+1) % 16) == 0) + printf("\n"); + } + printf("\n"); +} +#endif +} +#endif /* MWL_DEBUG */ + +#if 0 +static void +mwl_txq_dump(struct mwl_txq *txq) +{ + struct mwl_txbuf *bf; + int i = 0; + + MWL_TXQ_LOCK(txq); + STAILQ_FOREACH(bf, &txq->active, bf_list) { + struct mwl_txdesc *ds = bf->bf_desc; + MWL_TXDESC_SYNC(txq, ds, + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); +#ifdef MWL_DEBUG + mwl_printtxbuf(bf, txq->qnum, i); +#endif + i++; + } + MWL_TXQ_UNLOCK(txq); +} +#endif + +static void +mwl_watchdog(struct ifnet *ifp) +{ + struct mwl_softc *sc = ifp->if_softc; + + if ((ifp->if_drv_flags & IFF_DRV_RUNNING) && !sc->sc_invalid) { + if (mwl_hal_setkeepalive(sc->sc_mh)) + if_printf(ifp, "transmit timeout (firmware hung?)\n"); + else + if_printf(ifp, "transmit timeout\n"); +#if 0 + mwl_reset(ifp); +mwl_txq_dump(&sc->sc_txq[0]);/*XXX*/ +#endif + ifp->if_oerrors++; + sc->sc_stats.mst_watchdog++; + } +} + +#ifdef MWL_DIAGAPI +/* + * Diagnostic interface to the HAL. This is used by various + * tools to do things like retrieve register contents for + * debugging. The mechanism is intentionally opaque so that + * it can change frequently w/o concern for compatiblity. + */ +static int +mwl_ioctl_diag(struct mwl_softc *sc, struct mwl_diag *md) +{ + struct mwl_hal *mh = sc->sc_mh; + u_int id = md->md_id & MWL_DIAG_ID; + void *indata = NULL; + void *outdata = NULL; + u_int32_t insize = md->md_in_size; + u_int32_t outsize = md->md_out_size; + int error = 0; + + if (md->md_id & MWL_DIAG_IN) { + /* + * Copy in data. + */ + indata = malloc(insize, M_TEMP, M_NOWAIT); + if (indata == NULL) { + error = ENOMEM; + goto bad; + } + error = copyin(md->md_in_data, indata, insize); + if (error) + goto bad; + } + if (md->md_id & MWL_DIAG_DYN) { + /* + * Allocate a buffer for the results (otherwise the HAL + * returns a pointer to a buffer where we can read the + * results). Note that we depend on the HAL leaving this + * pointer for us to use below in reclaiming the buffer; + * may want to be more defensive. + */ + outdata = malloc(outsize, M_TEMP, M_NOWAIT); + if (outdata == NULL) { + error = ENOMEM; + goto bad; + } + } + if (mwl_hal_getdiagstate(mh, id, indata, insize, &outdata, &outsize)) { + if (outsize < md->md_out_size) + md->md_out_size = outsize; + if (outdata != NULL) + error = copyout(outdata, md->md_out_data, + md->md_out_size); + } else { + error = EINVAL; + } +bad: + if ((md->md_id & MWL_DIAG_IN) && indata != NULL) + free(indata, M_TEMP); + if ((md->md_id & MWL_DIAG_DYN) && outdata != NULL) + free(outdata, M_TEMP); + return error; +} + +static int +mwl_ioctl_reset(struct mwl_softc *sc, struct mwl_diag *md) +{ + struct mwl_hal *mh = sc->sc_mh; + int error; + + MWL_LOCK_ASSERT(sc); + + if (md->md_id == 0 && mwl_hal_fwload(mh, NULL) != 0) { + device_printf(sc->sc_dev, "unable to load firmware\n"); + return EIO; + } + if (mwl_hal_gethwspecs(mh, &sc->sc_hwspecs) != 0) { + device_printf(sc->sc_dev, "unable to fetch h/w specs\n"); + return EIO; + } + error = mwl_setupdma(sc); + if (error != 0) { + /* NB: mwl_setupdma prints a msg */ + return error; + } + /* + * Reset tx/rx data structures; after reload we must + * re-start the driver's notion of the next xmit/recv. + */ + mwl_draintxq(sc); /* clear pending frames */ + mwl_resettxq(sc); /* rebuild tx q lists */ + sc->sc_rxnext = NULL; /* force rx to start at the list head */ + return 0; +} +#endif /* MWL_DIAGAPI */ + +static int +mwl_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) +{ +#define IS_RUNNING(ifp) \ + ((ifp->if_flags & IFF_UP) && (ifp->if_drv_flags & IFF_DRV_RUNNING)) + struct mwl_softc *sc = ifp->if_softc; + struct ieee80211com *ic = ifp->if_l2com; + struct ifreq *ifr = (struct ifreq *)data; + int error = 0, startall; + + switch (cmd) { + case SIOCSIFFLAGS: + MWL_LOCK(sc); + startall = 0; + if (IS_RUNNING(ifp)) { + /* + * To avoid rescanning another access point, + * do not call mwl_init() here. Instead, + * only reflect promisc mode settings. + */ + mwl_mode_init(sc); + } else if (ifp->if_flags & IFF_UP) { + /* + * Beware of being called during attach/detach + * to reset promiscuous mode. In that case we + * will still be marked UP but not RUNNING. + * However trying to re-init the interface + * is the wrong thing to do as we've already + * torn down much of our state. There's + * probably a better way to deal with this. + */ + if (!sc->sc_invalid) { + mwl_init_locked(sc); /* XXX lose error */ + startall = 1; + } + } else + mwl_stop_locked(ifp, 1); + MWL_UNLOCK(sc); + if (startall) + ieee80211_start_all(ic); + break; + case SIOCGMVSTATS: + mwl_hal_gethwstats(sc->sc_mh, &sc->sc_stats.hw_stats); + /* NB: embed these numbers to get a consistent view */ + sc->sc_stats.mst_tx_packets = ifp->if_opackets; + sc->sc_stats.mst_rx_packets = ifp->if_ipackets; + /* + * NB: Drop the softc lock in case of a page fault; + * we'll accept any potential inconsisentcy in the + * statistics. The alternative is to copy the data + * to a local structure. + */ + return copyout(&sc->sc_stats, + ifr->ifr_data, sizeof (sc->sc_stats)); +#ifdef MWL_DIAGAPI + case SIOCGMVDIAG: + /* XXX check privs */ + return mwl_ioctl_diag(sc, (struct mwl_diag *) ifr); + case SIOCGMVRESET: + /* XXX check privs */ + MWL_LOCK(sc); + error = mwl_ioctl_reset(sc,(struct mwl_diag *) ifr); + MWL_UNLOCK(sc); + break; +#endif /* MWL_DIAGAPI */ + case SIOCGIFMEDIA: + error = ifmedia_ioctl(ifp, ifr, &ic->ic_media, cmd); + break; + case SIOCGIFADDR: + error = ether_ioctl(ifp, cmd, data); + break; + default: + error = EINVAL; + break; + } + return error; +#undef IS_RUNNING +} + +#ifdef MWL_DEBUG +static int +mwl_sysctl_debug(SYSCTL_HANDLER_ARGS) +{ + struct mwl_softc *sc = arg1; + int debug, error; + + debug = sc->sc_debug | (mwl_hal_getdebug(sc->sc_mh) << 24); + error = sysctl_handle_int(oidp, &debug, 0, req); + if (error || !req->newptr) + return error; + mwl_hal_setdebug(sc->sc_mh, debug >> 24); + sc->sc_debug = debug & 0x00ffffff; + return 0; +} +#endif /* MWL_DEBUG */ + +static void +mwl_sysctlattach(struct mwl_softc *sc) +{ +#ifdef MWL_DEBUG + struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(sc->sc_dev); + struct sysctl_oid *tree = device_get_sysctl_tree(sc->sc_dev); + + sc->sc_debug = mwl_debug; + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "debug", CTLTYPE_INT | CTLFLAG_RW, sc, 0, + mwl_sysctl_debug, "I", "control debugging printfs"); +#endif +} + +/* + * Announce various information on device/driver attach. + */ +static void +mwl_announce(struct mwl_softc *sc) +{ + struct ifnet *ifp = sc->sc_ifp; + + if_printf(ifp, "Rev A%d hardware, v%d.%d.%d.%d firmware (regioncode %d)\n", + sc->sc_hwspecs.hwVersion, + (sc->sc_hwspecs.fwReleaseNumber>>24) & 0xff, + (sc->sc_hwspecs.fwReleaseNumber>>16) & 0xff, + (sc->sc_hwspecs.fwReleaseNumber>>8) & 0xff, + (sc->sc_hwspecs.fwReleaseNumber>>0) & 0xff, + sc->sc_hwspecs.regionCode); + sc->sc_fwrelease = sc->sc_hwspecs.fwReleaseNumber; + + if (bootverbose) { + int i; + for (i = 0; i <= WME_AC_VO; i++) { + struct mwl_txq *txq = sc->sc_ac2q[i]; + if_printf(ifp, "Use hw queue %u for %s traffic\n", + txq->qnum, ieee80211_wme_acnames[i]); + } + } + if (bootverbose || mwl_rxdesc != MWL_RXDESC) + if_printf(ifp, "using %u rx descriptors\n", mwl_rxdesc); + if (bootverbose || mwl_rxbuf != MWL_RXBUF) + if_printf(ifp, "using %u rx buffers\n", mwl_rxbuf); + if (bootverbose || mwl_txbuf != MWL_TXBUF) + if_printf(ifp, "using %u tx buffers\n", mwl_txbuf); + if (bootverbose && mwl_hal_ismbsscapable(sc->sc_mh)) + if_printf(ifp, "multi-bss support\n"); +#ifdef MWL_TX_NODROP + if (bootverbose) + if_printf(ifp, "no tx drop\n"); +#endif +} diff --git a/sys/dev/mwl/if_mwl_pci.c b/sys/dev/mwl/if_mwl_pci.c new file mode 100644 index 0000000..2fb0922 --- /dev/null +++ b/sys/dev/mwl/if_mwl_pci.c @@ -0,0 +1,316 @@ +/*- + * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2007-2009 Marvell Semiconductor, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + */ + +#include <sys/cdefs.h> +#ifdef __FreeBSD__ +__FBSDID("$FreeBSD$"); +#endif + +/* + * PCI front-end for the Marvell Wireless LAN controller driver. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/errno.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/bus.h> +#include <sys/rman.h> + +#include <sys/socket.h> + +#include <net/if.h> +#include <net/if_media.h> +#include <net/if_arp.h> + +#include <net80211/ieee80211_var.h> + +#include <dev/mwl/if_mwlvar.h> + +#include <dev/pci/pcivar.h> +#include <dev/pci/pcireg.h> + +/* + * PCI glue. + */ + +struct mwl_pci_softc { + struct mwl_softc sc_sc; + struct resource *sc_sr0; /* BAR0 memory resource */ + struct resource *sc_sr1; /* BAR1 memory resource */ + struct resource *sc_irq; /* irq resource */ + void *sc_ih; /* interrupt handler */ +}; + +#define BS_BAR0 0x10 +#define BS_BAR1 0x14 + +struct mwl_pci_ident { + uint16_t vendor; + uint16_t device; + const char *name; +}; + +static const struct mwl_pci_ident mwl_pci_ids[] = { + { 0x11ab, 0x2a02, "Marvell 88W8363" }, + { 0x11ab, 0x2a03, "Marvell 88W8363" }, + { 0x11ab, 0x2a0a, "Marvell 88W8363" }, + { 0x11ab, 0x2a0b, "Marvell 88W8363" }, + { 0x11ab, 0x2a0c, "Marvell 88W8363" }, + { 0x11ab, 0x2a21, "Marvell 88W8363" }, + { 0x11ab, 0x2a24, "Marvell 88W8363" }, + + { 0, 0, NULL } +}; + +const static struct mwl_pci_ident * +mwl_pci_lookup(int vendor, int device) +{ + const struct mwl_pci_ident *ident; + + for (ident = mwl_pci_ids; ident->name != NULL; ident++) + if (vendor == ident->vendor && device == ident->device) + return ident; + return NULL; +} + +static int +mwl_pci_probe(device_t dev) +{ + const struct mwl_pci_ident *ident; + + ident = mwl_pci_lookup(pci_get_vendor(dev), pci_get_device(dev)); + if (ident != NULL) { + device_set_desc(dev, ident->name); + return BUS_PROBE_DEFAULT; + } + return ENXIO; +} + +static u_int32_t +mwl_pci_setup(device_t dev) +{ + u_int32_t cmd; + + /* + * Enable memory mapping and bus mastering. + */ + cmd = pci_read_config(dev, PCIR_COMMAND, 4); + cmd |= PCIM_CMD_MEMEN | PCIM_CMD_BUSMASTEREN; + pci_write_config(dev, PCIR_COMMAND, cmd, 4); + cmd = pci_read_config(dev, PCIR_COMMAND, 4); + if ((cmd & PCIM_CMD_MEMEN) == 0) { + device_printf(dev, "failed to enable memory mapping\n"); + return 0; + } + if ((cmd & PCIM_CMD_BUSMASTEREN) == 0) { + device_printf(dev, "failed to enable bus mastering\n"); + return 0; + } + return 1; +} + +static int +mwl_pci_attach(device_t dev) +{ + struct mwl_pci_softc *psc = device_get_softc(dev); + struct mwl_softc *sc = &psc->sc_sc; + int rid, error = ENXIO; + + sc->sc_dev = dev; + + /* + * Enable memory mapping and bus mastering. + */ + if (!mwl_pci_setup(dev)) + return 0; + /* + * Setup memory-mapping of PCI registers. + */ + rid = BS_BAR0; + psc->sc_sr0 = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (psc->sc_sr0 == NULL) { + device_printf(dev, "cannot map BAR0 register space\n"); + goto bad; + } + rid = BS_BAR1; + psc->sc_sr1 = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, + RF_ACTIVE); + if (psc->sc_sr1 == NULL) { + device_printf(dev, "cannot map BAR1 register space\n"); + goto bad1; + } + sc->sc_invalid = 1; + + /* + * Arrange interrupt line. + */ + rid = 0; + psc->sc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, + RF_SHAREABLE|RF_ACTIVE); + if (psc->sc_irq == NULL) { + device_printf(dev, "could not map interrupt\n"); + goto bad2; + } + if (bus_setup_intr(dev, psc->sc_irq, + INTR_TYPE_NET | INTR_MPSAFE, + NULL, mwl_intr, sc, &psc->sc_ih)) { + device_printf(dev, "could not establish interrupt\n"); + goto bad3; + } + + /* + * Setup DMA descriptor area. + */ + if (bus_dma_tag_create(bus_get_dma_tag(dev), /* parent */ + 1, 0, /* alignment, bounds */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + BUS_SPACE_MAXADDR, /* maxsize */ + MWL_TXDESC, /* nsegments */ + BUS_SPACE_MAXADDR, /* maxsegsize */ + BUS_DMA_ALLOCNOW, /* flags */ + NULL, /* lockfunc */ + NULL, /* lockarg */ + &sc->sc_dmat)) { + device_printf(dev, "cannot allocate DMA tag\n"); + goto bad4; + } + + /* + * Finish off the attach. + */ + MWL_LOCK_INIT(sc); + sc->sc_io0t = rman_get_bustag(psc->sc_sr0); + sc->sc_io0h = rman_get_bushandle(psc->sc_sr0); + sc->sc_io1t = rman_get_bustag(psc->sc_sr1); + sc->sc_io1h = rman_get_bushandle(psc->sc_sr1); + if (mwl_attach(pci_get_device(dev), sc) == 0) + return (0); + + MWL_LOCK_DESTROY(sc); + bus_dma_tag_destroy(sc->sc_dmat); +bad4: + bus_teardown_intr(dev, psc->sc_irq, psc->sc_ih); +bad3: + bus_release_resource(dev, SYS_RES_IRQ, 0, psc->sc_irq); +bad2: + bus_release_resource(dev, SYS_RES_MEMORY, BS_BAR1, psc->sc_sr1); +bad1: + bus_release_resource(dev, SYS_RES_MEMORY, BS_BAR0, psc->sc_sr0); +bad: + return (error); +} + +static int +mwl_pci_detach(device_t dev) +{ + struct mwl_pci_softc *psc = device_get_softc(dev); + struct mwl_softc *sc = &psc->sc_sc; + + /* check if device was removed */ + sc->sc_invalid = !bus_child_present(dev); + + mwl_detach(sc); + + bus_generic_detach(dev); + bus_teardown_intr(dev, psc->sc_irq, psc->sc_ih); + bus_release_resource(dev, SYS_RES_IRQ, 0, psc->sc_irq); + + bus_dma_tag_destroy(sc->sc_dmat); + bus_release_resource(dev, SYS_RES_MEMORY, BS_BAR1, psc->sc_sr1); + bus_release_resource(dev, SYS_RES_MEMORY, BS_BAR0, psc->sc_sr0); + + MWL_LOCK_DESTROY(sc); + + return (0); +} + +static int +mwl_pci_shutdown(device_t dev) +{ + struct mwl_pci_softc *psc = device_get_softc(dev); + + mwl_shutdown(&psc->sc_sc); + return (0); +} + +static int +mwl_pci_suspend(device_t dev) +{ + struct mwl_pci_softc *psc = device_get_softc(dev); + + mwl_suspend(&psc->sc_sc); + + return (0); +} + +static int +mwl_pci_resume(device_t dev) +{ + struct mwl_pci_softc *psc = device_get_softc(dev); + + if (!mwl_pci_setup(dev)) + return ENXIO; + + mwl_resume(&psc->sc_sc); + + return (0); +} + +static device_method_t mwl_pci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, mwl_pci_probe), + DEVMETHOD(device_attach, mwl_pci_attach), + DEVMETHOD(device_detach, mwl_pci_detach), + DEVMETHOD(device_shutdown, mwl_pci_shutdown), + DEVMETHOD(device_suspend, mwl_pci_suspend), + DEVMETHOD(device_resume, mwl_pci_resume), + + { 0,0 } +}; +static driver_t mwl_pci_driver = { + "mwl", + mwl_pci_methods, + sizeof (struct mwl_pci_softc) +}; +static devclass_t mwl_devclass; +DRIVER_MODULE(mwl, pci, mwl_pci_driver, mwl_devclass, 0, 0); +MODULE_VERSION(mwl, 1); +MODULE_DEPEND(mwl, wlan, 1, 1, 1); /* 802.11 media layer */ +MODULE_DEPEND(mwl, mwlfw_fw, 1, 1, 1); /* firmware */ diff --git a/sys/dev/mwl/if_mwlioctl.h b/sys/dev/mwl/if_mwlioctl.h new file mode 100644 index 0000000..e076019 --- /dev/null +++ b/sys/dev/mwl/if_mwlioctl.h @@ -0,0 +1,136 @@ +/*- + * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2007-2009 Marvell Semiconductor, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + * + * $FreeBSD$ + */ + +/* + * Ioctl-related defintions for the Marvel Wireless LAN controller driver. + */ +#ifndef _DEV_MWL_MVIOCTL_H +#define _DEV_MWL_MVIOCTL_H + +struct mwl_stats { + struct mwl_hal_hwstats hw_stats; /* XXX tied to h/w defs */ + uint32_t mst_failure; /* generic hardware failure */ + uint32_t mst_rx_badtkipicv; + uint32_t mst_tx_discard; + uint32_t mst_tx_qstop; + uint32_t mst_tx_encap; + uint32_t mst_tx_mgmt; + uint32_t mst_rx_nombuf; + uint32_t mst_rx_busdma; + uint32_t mst_rx_tooshort; + uint32_t mst_tx_busdma; + uint32_t mst_tx_linear; + uint32_t mst_tx_nombuf; + uint32_t mst_tx_nodata; + uint32_t mst_tx_shortpre; + uint32_t mst_tx_retries; + uint32_t mst_tx_mretries; + uint32_t mst_tx_linkerror; + uint32_t mst_tx_xretries; + uint32_t mst_tx_aging; + uint32_t mst_tx_qdrop; + uint32_t mst_ff_txerr; + uint32_t mst_watchdog; + uint32_t mst_tx_packets; + uint32_t mst_rx_packets; + int8_t mst_rx_rssi; + int8_t mst_rx_noise; + uint8_t mst_tx_rate; + uint32_t mst_ant_tx[4]; + uint32_t mst_ant_rx[4]; + uint32_t mst_tx_tso; + uint32_t mst_tso_badeth; + uint32_t mst_tso_nohdr; + uint32_t mst_tso_badsplit; + uint32_t mst_rx_crypto; + uint32_t mst_rx_tkipmic; + uint32_t mst_rx_nodmabuf; + uint32_t mst_tx_noheadroom; + uint32_t mst_tx_badframetype; + uint32_t mst_ampdu_nostream; + uint32_t mst_ampdu_reject; + uint32_t mst_addba_nostream; + uint32_t mst_bacreate_failed; + uint32_t mst_bawatchdog; + uint32_t mst_radardetect; + uint32_t mst_rx_dmabufmissing; + uint32_t mst_bawatchdog_notfound; + uint32_t mst_bawatchdog_empty; + uint32_t mst_bawatchdog_failed; + uint32_t mst_rxbuf_failed; + uint32_t mst_pad[31]; +}; + +#define SIOCGMVSTATS _IOWR('i', 137, struct ifreq) + +/* + * Radio capture format. + */ +#define MWL_RX_RADIOTAP_PRESENT ( \ + (1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL) | \ + (1 << IEEE80211_RADIOTAP_DBM_ANTNOISE) | \ + 0) + +struct mwl_rx_radiotap_header { + struct ieee80211_radiotap_header wr_ihdr; + u_int8_t wr_flags; + u_int8_t wr_rate; + u_int16_t wr_chan_freq; + u_int16_t wr_chan_flags; + int8_t wr_antsignal; + int8_t wr_antnoise; + u_int8_t wr_antenna; +}; + +#define MWL_TX_RADIOTAP_PRESENT ( \ + (1 << IEEE80211_RADIOTAP_FLAGS) | \ + (1 << IEEE80211_RADIOTAP_RATE) | \ + (1 << IEEE80211_RADIOTAP_CHANNEL) | \ + (1 << IEEE80211_RADIOTAP_DBM_TX_POWER) | \ + (1 << IEEE80211_RADIOTAP_ANTENNA) | \ + 0) + +struct mwl_tx_radiotap_header { + struct ieee80211_radiotap_header wt_ihdr; + u_int8_t wt_flags; + u_int8_t wt_rate; + u_int16_t wt_chan_freq; + u_int16_t wt_chan_flags; + u_int8_t wt_txpower; + u_int8_t wt_antenna; +}; + +#endif /* _DEV_MWL_MVIOCTL_H */ diff --git a/sys/dev/mwl/if_mwlvar.h b/sys/dev/mwl/if_mwlvar.h new file mode 100644 index 0000000..6730ef6 --- /dev/null +++ b/sys/dev/mwl/if_mwlvar.h @@ -0,0 +1,354 @@ +/*- + * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2007-2009 Marvell Semiconductor, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + * + * $FreeBSD$ + */ + +/* + * Definitions for the Marvell 88W8363 Wireless LAN controller. + */ +#ifndef _DEV_MWL_MVVAR_H +#define _DEV_MWL_MVVAR_H + +#include <sys/endian.h> +#include <net80211/ieee80211_radiotap.h> +#include <dev/mwl/mwlhal.h> +#include <dev/mwl/mwlreg.h> +#include <dev/mwl/if_mwlioctl.h> + +#ifndef MWL_TXBUF +#define MWL_TXBUF 256 /* number of TX descriptors/buffers */ +#endif +#ifndef MWL_RXDESC +#define MWL_RXDESC 256 /* number of RX descriptors */ +#endif +#ifndef MWL_RXBUF +#define MWL_RXBUF ((5*MWL_RXDESC)/2)/* number of RX dma buffers */ +#endif +#ifndef MWL_MAXBA +#define MWL_MAXBA 2 /* max BA streams/sta */ +#endif + +#ifdef MWL_SGDMA_SUPPORT +#define MWL_TXDESC 6 /* max tx descriptors/segments */ +#else +#define MWL_TXDESC 1 /* max tx descriptors/segments */ +#endif +#ifndef MWL_AGGR_SIZE +#define MWL_AGGR_SIZE 3839 /* max tx agregation size */ +#endif +#define MWL_AGEINTERVAL 1 /* poke f/w every sec to age q's */ +#define MWL_MAXSTAID 64 /* max of 64 stations */ + +/* + * DMA state for tx/rx descriptors. + */ + +/* + * Software backed version of tx/rx descriptors. We keep + * the software state out of the h/w descriptor structure + * so that may be allocated in uncached memory w/o paying + * performance hit. + */ +struct mwl_txbuf { + STAILQ_ENTRY(mwl_txbuf) bf_list; + void *bf_desc; /* h/w descriptor */ + bus_addr_t bf_daddr; /* physical addr of desc */ + bus_dmamap_t bf_dmamap; /* DMA map for descriptors */ + int bf_nseg; + bus_dma_segment_t bf_segs[MWL_TXDESC]; + struct mbuf *bf_m; + struct ieee80211_node *bf_node; + struct mwl_txq *bf_txq; /* backpointer to tx q/ring */ +}; +typedef STAILQ_HEAD(, mwl_txbuf) mwl_txbufhead; + +/* + * Common "base class" for tx/rx descriptor resources + * allocated using the bus dma api. + */ +struct mwl_descdma { + const char* dd_name; + void *dd_desc; /* descriptors */ + bus_addr_t dd_desc_paddr; /* physical addr of dd_desc */ + bus_size_t dd_desc_len; /* size of dd_desc */ + bus_dma_segment_t dd_dseg; + int dd_dnseg; /* number of segments */ + bus_dma_tag_t dd_dmat; /* bus DMA tag */ + bus_dmamap_t dd_dmamap; /* DMA map for descriptors */ + void *dd_bufptr; /* associated buffers */ +}; + +/* + * TX/RX ring definitions. There are 4 tx rings, one + * per AC, and 1 rx ring. Note carefully that transmit + * descriptors are treated as a contiguous chunk and the + * firmware pre-fetches descriptors. This means that we + * must preserve order when moving descriptors between + * the active+free lists; otherwise we may stall transmit. + */ +struct mwl_txq { + struct mwl_descdma dma; /* bus dma resources */ + struct mtx lock; /* tx q lock */ + char name[12]; /* e.g. "mwl0_txq4" */ + int qnum; /* f/w q number */ + int txpri; /* f/w tx priority */ + int nfree; /* # buffers on free list */ + mwl_txbufhead free; /* queue of free buffers */ + mwl_txbufhead active; /* queue of active buffers */ +}; + +#define MWL_TXQ_LOCK_INIT(_sc, _tq) do { \ + snprintf((_tq)->name, sizeof((_tq)->name), "%s_txq%u", \ + device_get_nameunit((_sc)->sc_dev), (_tq)->qnum); \ + mtx_init(&(_tq)->lock, (_tq)->name, NULL, MTX_DEF); \ +} while (0) +#define MWL_TXQ_LOCK_DESTROY(_tq) mtx_destroy(&(_tq)->lock) +#define MWL_TXQ_LOCK(_tq) mtx_lock(&(_tq)->lock) +#define MWL_TXQ_UNLOCK(_tq) mtx_unlock(&(_tq)->lock) +#define MWL_TXQ_LOCK_ASSERT(_tq) mtx_assert(&(_tq)->lock, MA_OWNED) + +#define MWL_TXDESC_SYNC(txq, ds, how) do { \ + bus_dmamap_sync((txq)->dma.dd_dmat, (txq)->dma.dd_dmamap, how); \ +} while(0) + +/* + * RX dma buffers that are not in use are kept on a list. + */ +struct mwl_jumbo { + SLIST_ENTRY(mwl_jumbo) next; +}; +typedef SLIST_HEAD(, mwl_jumbo) mwl_jumbohead; + +#define MWL_JUMBO_DATA2BUF(_data) ((struct mwl_jumbo *)(_data)) +#define MWL_JUMBO_BUF2DATA(_buf) ((uint8_t *)(_buf)) +#define MWL_JUMBO_OFFSET(_sc, _data) \ + (((const uint8_t *)(_data)) - (const uint8_t *)((_sc)->sc_rxmem)) +#define MWL_JUMBO_DMA_ADDR(_sc, _data) \ + ((_sc)->sc_rxmem_paddr + MWL_JUMBO_OFFSET(_sc, _data)) + +struct mwl_rxbuf { + STAILQ_ENTRY(mwl_rxbuf) bf_list; + void *bf_desc; /* h/w descriptor */ + bus_addr_t bf_daddr; /* physical addr of desc */ + uint8_t *bf_data; /* rx data area */ +}; +typedef STAILQ_HEAD(, mwl_rxbuf) mwl_rxbufhead; + +#define MWL_RXDESC_SYNC(sc, ds, how) do { \ + bus_dmamap_sync((sc)->sc_rxdma.dd_dmat, (sc)->sc_rxdma.dd_dmamap, how);\ +} while (0) + +/* + * BA stream state. One of these is setup for each stream + * allocated/created for use. We pre-allocate the h/w stream + * before sending ADDBA request then complete the setup when + * get ADDBA response (success). The completed state is setup + * to optimize the fast path in mwl_txstart--we precalculate + * the QoS control bits in the outbound frame and use those + * to identify which BA stream to use (assigning the h/w q to + * the TxPriority field of the descriptor). + * + * NB: Each station may have at most MWL_MAXBA streams at one time. + */ +struct mwl_bastate { + uint16_t qos; /* QoS ctl for BA stream */ + uint8_t txq; /* h/w q for BA stream */ + const MWL_HAL_BASTREAM *bastream; /* A-MPDU BA stream */ +}; + +static __inline__ void +mwl_bastream_setup(struct mwl_bastate *bas, int ac, int txq) +{ + bas->txq = txq; + bas->qos = htole16(WME_AC_TO_TID(ac) | IEEE80211_QOS_ACKPOLICY_BA); +} + +static __inline__ void +mwl_bastream_free(struct mwl_bastate *bas) +{ + bas->qos = 0; + bas->bastream = NULL; + /* NB: don't need to clear txq */ +} + +/* + * Check the QoS control bits from an outbound frame against the + * value calculated when a BA stream is setup (above). We need + * to match the TID and also the ACK policy so we only match AMPDU + * frames. The bits from the frame are assumed in network byte + * order, hence the potential byte swap. + */ +static __inline__ int +mwl_bastream_match(const struct mwl_bastate *bas, uint16_t qos) +{ + return (qos & htole16(IEEE80211_QOS_TID|IEEE80211_QOS_ACKPOLICY)) == + bas->qos; +} + +/* driver-specific node state */ +struct mwl_node { + struct ieee80211_node mn_node; /* base class */ + struct mwl_ant_info mn_ai; /* antenna info */ + uint32_t mn_avgrssi; /* average rssi over all rx frames */ + uint16_t mn_staid; /* firmware station id */ + struct mwl_bastate mn_ba[MWL_MAXBA]; + struct mwl_hal_vap *mn_hvap; /* hal vap handle */ +}; +#define MWL_NODE(ni) ((struct mwl_node *)(ni)) +#define MWL_NODE_CONST(ni) ((const struct mwl_node *)(ni)) + +/* + * Driver-specific vap state. + */ +struct mwl_vap { + struct ieee80211vap mv_vap; /* base class */ + struct mwl_hal_vap *mv_hvap; /* hal vap handle */ + struct mwl_hal_vap *mv_ap_hvap; /* ap hal vap handle for wds */ + uint16_t mv_last_ps_sta; /* last count of ps sta's */ + uint16_t mv_eapolformat; /* fixed tx rate for EAPOL */ + int (*mv_newstate)(struct ieee80211vap *, + enum ieee80211_state, int); + int (*mv_set_tim)(struct ieee80211_node *, int); +}; +#define MWL_VAP(vap) ((struct mwl_vap *)(vap)) +#define MWL_VAP_CONST(vap) ((const struct mwl_vap *)(vap)) + +struct mwl_softc { + struct ifnet *sc_ifp; /* interface common */ + struct mwl_stats sc_stats; /* interface statistics */ + int sc_debug; + device_t sc_dev; + bus_dma_tag_t sc_dmat; /* bus DMA tag */ + bus_space_handle_t sc_io0h; /* BAR 0 */ + bus_space_tag_t sc_io0t; + bus_space_handle_t sc_io1h; /* BAR 1 */ + bus_space_tag_t sc_io1t; + struct mtx sc_mtx; /* master lock (recursive) */ + struct taskqueue *sc_tq; /* private task queue */ + unsigned int sc_invalid : 1, /* disable hardware accesses */ + sc_recvsetup:1, /* recv setup */ + sc_csapending:1,/* 11h channel switch pending */ + sc_radarena : 1,/* radar detection enabled */ + sc_rxblocked: 1;/* rx waiting for dma buffers */ + + struct mwl_hal *sc_mh; /* h/w access layer */ + struct mwl_hal_vap *sc_hvap; /* hal vap handle */ + struct mwl_hal_hwspec sc_hwspecs; /* h/w capabilities */ + uint32_t sc_fwrelease; /* release # of loaded f/w */ + struct mwl_hal_txrxdma sc_hwdma; /* h/w dma setup */ + uint32_t sc_imask; /* interrupt mask copy */ + enum ieee80211_phymode sc_curmode; + u_int16_t sc_curaid; /* current association id */ + u_int8_t sc_curbssid[IEEE80211_ADDR_LEN]; + MWL_HAL_CHANNEL sc_curchan; + MWL_HAL_TXRATE_HANDLING sc_txratehandling; + u_int16_t sc_rxantenna; /* rx antenna */ + u_int16_t sc_txantenna; /* tx antenna */ + uint8_t sc_napvaps; /* # ap mode vaps */ + uint8_t sc_nwdsvaps; /* # wds mode vaps */ + uint8_t sc_nstavaps; /* # sta mode vaps */ + uint8_t sc_nbssid0; /* # vap's using base mac */ + uint32_t sc_bssidmask; /* bssid mask */ + + void (*sc_recv_mgmt)(struct ieee80211com *, + struct mbuf *, + struct ieee80211_node *, + int, int, int, u_int32_t); + int (*sc_newstate)(struct ieee80211com *, + enum ieee80211_state, int); + void (*sc_node_cleanup)(struct ieee80211_node *); + void (*sc_node_drain)(struct ieee80211_node *); + void (*sc_recv_action)(struct ieee80211_node *, + const uint8_t *, const uint8_t *); + int (*sc_addba_request)(struct ieee80211_node *, + struct ieee80211_tx_ampdu *, + int dialogtoken, int baparamset, + int batimeout); + int (*sc_addba_response)(struct ieee80211_node *, + struct ieee80211_tx_ampdu *, + int status, int baparamset, + int batimeout); + void (*sc_addba_stop)(struct ieee80211_node *, + struct ieee80211_tx_ampdu *); + + struct mwl_descdma sc_rxdma; /* rx bus dma resources */ + mwl_rxbufhead sc_rxbuf; /* rx buffers */ + struct mwl_rxbuf *sc_rxnext; /* next rx buffer to process */ + struct task sc_rxtask; /* rx int processing */ + void *sc_rxmem; /* rx dma buffer pool */ + bus_dma_tag_t sc_rxdmat; /* rx bus DMA tag */ + bus_size_t sc_rxmemsize; /* rx dma buffer pool size */ + bus_dmamap_t sc_rxmap; /* map for rx dma buffers */ + bus_addr_t sc_rxmem_paddr; /* physical addr of sc_rxmem */ + mwl_jumbohead sc_rxfree; /* list of free dma buffers */ + int sc_nrxfree; /* # buffers on rx free list */ + struct mtx sc_rxlock; /* lock on sc_rxfree */ + + struct mwl_txq sc_txq[MWL_NUM_TX_QUEUES]; + struct mwl_txq *sc_ac2q[5]; /* WME AC -> h/w q map */ + struct mbuf *sc_aggrq; /* aggregation q */ + struct task sc_txtask; /* tx int processing */ + struct task sc_bawatchdogtask;/* BA watchdog processing */ + + struct task sc_radartask; /* radar detect processing */ + struct task sc_chanswitchtask;/* chan switch processing */ + + uint8_t sc_staid[MWL_MAXSTAID/NBBY]; + int sc_ageinterval; + struct callout sc_timer; /* periodic work */ + + struct mwl_tx_radiotap_header sc_tx_th; + struct mwl_rx_radiotap_header sc_rx_th; +}; + +#define MWL_LOCK_INIT(_sc) \ + mtx_init(&(_sc)->sc_mtx, device_get_nameunit((_sc)->sc_dev), \ + NULL, MTX_DEF | MTX_RECURSE) +#define MWL_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx) +#define MWL_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) +#define MWL_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) +#define MWL_LOCK_ASSERT(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) + +#define MWL_RXFREE_INIT(_sc) \ + mtx_init(&(_sc)->sc_rxlock, device_get_nameunit((_sc)->sc_dev), \ + NULL, MTX_DEF) +#define MWL_RXFREE_DESTROY(_sc) mtx_destroy(&(_sc)->sc_rxlock) +#define MWL_RXFREE_LOCK(_sc) mtx_lock(&(_sc)->sc_rxlock) +#define MWL_RXFREE_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_rxlock) +#define MWL_RXFREE_ASSERT(_sc) mtx_assert(&(_sc)->sc_rxlock, MA_OWNED) + +int mwl_attach(u_int16_t, struct mwl_softc *); +int mwl_detach(struct mwl_softc *); +void mwl_resume(struct mwl_softc *); +void mwl_suspend(struct mwl_softc *); +void mwl_shutdown(void *); +void mwl_intr(void *); + +#endif /* _DEV_MWL_MVVAR_H */ diff --git a/sys/dev/mwl/mwldiag.h b/sys/dev/mwl/mwldiag.h new file mode 100644 index 0000000..65c8eb5 --- /dev/null +++ b/sys/dev/mwl/mwldiag.h @@ -0,0 +1,108 @@ +/*- + * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2007-2009 Marvell Semiconductor, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + * + * $FreeBSD$ + */ + +#ifndef _MWL_DIAG_H_ +#define _MWL_DIAG_H_ +/* + * Diagnostic interface. This is an open-ended interface that + * is opaque to applications. Diagnostic programs use this to + * retrieve internal data structures, etc. There is no guarantee + * that calling conventions for calls other than MWL_DIAG_REVS + * are stable between HAL releases; a diagnostic application must + * use the HAL revision information to deal with ABI/API differences. + * + * NB: do not renumber these, certain codes are publicly used. + */ +enum { + MWL_DIAG_CMD_REVS = 0, /* MAC/PHY/Radio revs */ + MWL_DIAG_CMD_REGS = 1, /* Registers */ + MWL_DIAG_CMD_HOSTCMD = 2, /* issue arbitrary cmd */ + MWL_DIAG_CMD_FWLOAD = 3, /* load firmware */ +}; + +/* + * Device revision information. + */ +typedef struct { + uint16_t mh_devid; /* PCI device ID */ + uint16_t mh_subvendorid; /* PCI subvendor ID */ + uint16_t mh_macRev; /* MAC revision */ + uint16_t mh_phyRev; /* PHY revision */ +} MWL_DIAG_REVS; + +typedef struct { + uint16_t start; /* first register */ + uint16_t end; /* ending register or zero */ +} MWL_DIAG_REGRANGE; + +/* + * Registers are mapped into virtual banks; the hal converts + * r/w operations through the diag api to host cmds as required. + * + * NB: register offsets are 16-bits and we need to avoid real + * register mappings in BAR1. + */ +#define MWL_DIAG_BASE_MAC 0xa000 +#define MWL_DIAG_ISMAC(r) \ + (MWL_DIAG_BASE_MAC <= (r) && (r) < (MWL_DIAG_BASE_MAC+0x1000)) +#define MWL_DIAG_BASE_BB 0xe000 +#define MWL_DIAG_ISBB(r) \ + (MWL_DIAG_BASE_BB <= (r) && (r) < (MWL_DIAG_BASE_BB+0x1000)) +#define MWL_DIAG_BASE_RF 0xf000 +#define MWL_DIAG_ISRF(r) \ + (MWL_DIAG_BASE_RF <= (r) && (r) < (MWL_DIAG_BASE_RF+0x1000)) + +/* + * Firmware download + */ +typedef struct { + uint32_t opmode; /* operating mode */ + uint32_t signature; /* f/w ready signature */ + char name[1]; /* variable length pathname */ +} MWL_DIAG_FWLOAD; + +struct mwl_diag { + char md_name[IFNAMSIZ]; /* if name, e.g. "mv0" */ + uint16_t md_id; +#define MWL_DIAG_DYN 0x8000 /* allocate buffer in caller */ +#define MWL_DIAG_IN 0x4000 /* copy in parameters */ +#define MWL_DIAG_OUT 0x0000 /* copy out results (always) */ +#define MWL_DIAG_ID 0x0fff + uint16_t md_in_size; /* pack to fit, yech */ + void * md_in_data; + void * md_out_data; + u_int md_out_size; + +}; +#define SIOCGMVDIAG _IOWR('i', 138, struct mwl_diag) +#define SIOCGMVRESET _IOW('i', 139, struct mwl_diag) +#endif /* _MWL_DIAG_H_ */ diff --git a/sys/dev/mwl/mwlhal.c b/sys/dev/mwl/mwlhal.c new file mode 100644 index 0000000..0ffa0b1 --- /dev/null +++ b/sys/dev/mwl/mwlhal.c @@ -0,0 +1,2703 @@ +/*- + * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2007-2009 Marvell Semiconductor, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/sysctl.h> +#include <sys/malloc.h> +#include <sys/lock.h> +#include <sys/mutex.h> +#include <sys/kernel.h> +#include <sys/errno.h> +#include <sys/bus.h> +#include <sys/endian.h> + +#include <sys/linker.h> +#include <sys/firmware.h> + +#include <machine/bus.h> + +#include <dev/mwl/mwlhal.h> +#include <dev/mwl/mwlreg.h> + +#include <sys/socket.h> +#include <sys/sockio.h> +#include <net/if.h> +#include <dev/mwl/mwldiag.h> + +#define MWLHAL_DEBUG /* debug msgs */ + +typedef enum { + WL_ANTENNAMODE_RX = 0xffff, + WL_ANTENNAMODE_TX = 2, +} wlantennamode_e; + +typedef enum { + WL_TX_POWERLEVEL_LOW = 5, + WL_TX_POWERLEVEL_MEDIUM = 10, + WL_TX_POWERLEVEL_HIGH = 15, +} wltxpowerlevel_e; + +#define MWL_CMDBUF_SIZE 0x4000 /* size of f/w command buffer */ +#define MWL_BASTREAMS_MAX 7 /* max BA streams (NB: fw >3.3.5.9) */ +#define MWL_BAQID_MAX 8 /* max BA Q id's (NB: fw >3.3.5.9) */ +#define MWL_MBSS_AP_MAX 8 /* max ap vap's */ +#define MWL_MBSS_STA_MAX 24 /* max station/client vap's */ +#define MWL_MBSS_MAX (MWL_MBSS_AP_MAX+MWL_MBSS_STA_MAX) + +/* + * BA stream -> queue ID mapping + * + * The first 2 streams map to h/w; the remaining streams are + * implemented in firmware. + */ +static const int ba2qid[MWL_BASTREAMS_MAX] = { + 5, 6 /* h/w supported */ +#if MWL_BASTREAMS_MAX == 7 + , 7, 0, 1, 2, 3 /* f/w supported */ +#endif +}; +static int qid2ba[MWL_BAQID_MAX]; + +#define IEEE80211_ADDR_LEN 6 /* XXX */ +#define IEEE80211_ADDR_COPY(_dst, _src) \ + memcpy(_dst, _src, IEEE80211_ADDR_LEN) +#define IEEE80211_ADDR_EQ(_dst, _src) \ + (memcmp(_dst, _src, IEEE80211_ADDR_LEN) == 0) + +#define _CMD_SETUP(pCmd, type, cmd) do { \ + pCmd = (type *)&mh->mh_cmdbuf[0]; \ + memset(pCmd, 0, sizeof(type)); \ + pCmd->CmdHdr.Cmd = htole16(cmd); \ + pCmd->CmdHdr.Length = htole16(sizeof(type)); \ +} while (0) + +#define _VCMD_SETUP(vap, pCmd, type, cmd) do { \ + _CMD_SETUP(pCmd, type, cmd); \ + pCmd->CmdHdr.MacId = vap->macid; \ +} while (0) + +#define PWTAGETRATETABLE20M 14*4 +#define PWTAGETRATETABLE40M 9*4 +#define PWTAGETRATETABLE20M_5G 35*4 +#define PWTAGETRATETABLE40M_5G 16*4 + +struct mwl_hal_bastream { + MWL_HAL_BASTREAM public; /* public state */ + uint8_t stream; /* stream # */ + uint8_t setup; /* f/w cmd sent */ + uint8_t ba_type; + uint8_t tid; + uint8_t paraminfo; + uint8_t macaddr[IEEE80211_ADDR_LEN]; +}; + +struct mwl_hal_priv; + +struct mwl_hal_vap { + struct mwl_hal_priv *mh; /* back pointer */ + uint16_t bss_type; /* f/w type */ + uint8_t vap_type; /* MWL_HAL_BSSTYPE */ + uint8_t macid; /* for passing to f/w */ + uint8_t flags; +#define MVF_RUNNING 0x01 /* BSS_START issued */ +#define MVF_STATION 0x02 /* sta db entry created */ + uint8_t mac[IEEE80211_ADDR_LEN];/* mac address */ +}; +#define MWLVAP(_vap) ((_vap)->mh) + +/* + * Per-device state. We allocate a single cmd buffer for + * submitting operations to the firmware. Access to this + * buffer (and the f/w) are single-threaded. At present + * we spin waiting for cmds to complete which is bad. Not + * sure if it's possible to submit multiple requests or + * control when we get cmd done interrupts. There's no + * documentation and no example code to indicate what can + * or cannot be done so all we can do right now is follow the + * linux driver logic. This falls apart when the f/w fails; + * the system comes to a crawl as we spin waiting for operations + * to finish. + */ +struct mwl_hal_priv { + struct mwl_hal public; /* public area */ + device_t mh_dev; + char mh_mtxname[12]; + struct mtx mh_mtx; + bus_dma_tag_t mh_dmat; /* bus DMA tag for cmd buffer */ + bus_dma_segment_t mh_seg; /* segment for cmd buffer */ + bus_dmamap_t mh_dmamap; /* DMA map for cmd buffer */ + uint16_t *mh_cmdbuf; /* f/w cmd buffer */ + bus_addr_t mh_cmdaddr; /* physaddr of cmd buffer */ + int mh_flags; +#define MHF_CALDATA 0x0001 /* cal data retrieved */ +#define MHF_FWHANG 0x0002 /* fw appears hung */ +#define MHF_MBSS 0x0004 /* mbss enabled */ + struct mwl_hal_vap mh_vaps[MWL_MBSS_MAX+1]; + int mh_bastreams; /* bit mask of available BA streams */ + int mh_regioncode; /* XXX last region code sent to fw */ + struct mwl_hal_bastream mh_streams[MWL_BASTREAMS_MAX]; + int mh_debug; + MWL_HAL_CHANNELINFO mh_20M; + MWL_HAL_CHANNELINFO mh_40M; + MWL_HAL_CHANNELINFO mh_20M_5G; + MWL_HAL_CHANNELINFO mh_40M_5G; + int mh_SDRAMSIZE_Addr; + uint32_t mh_RTSSuccesses;/* cumulative stats for read-on-clear */ + uint32_t mh_RTSFailures; + uint32_t mh_RxDuplicateFrames; + uint32_t mh_FCSErrorCount; + MWL_DIAG_REVS mh_revs; +}; +#define MWLPRIV(_mh) ((struct mwl_hal_priv *)(_mh)) + +static int mwl_hal_setmac_locked(struct mwl_hal_vap *, + const uint8_t addr[IEEE80211_ADDR_LEN]); +static int mwlExecuteCmd(struct mwl_hal_priv *, unsigned short cmd); +static int mwlGetPwrCalTable(struct mwl_hal_priv *); +#ifdef MWLHAL_DEBUG +static const char *mwlcmdname(int cmd); +static void dumpresult(struct mwl_hal_priv *, int showresult); +#endif /* MWLHAL_DEBUG */ + +SYSCTL_DECL(_hw_mwl); +SYSCTL_NODE(_hw_mwl, OID_AUTO, hal, CTLFLAG_RD, 0, "Marvell HAL parameters"); + +static __inline void +MWL_HAL_LOCK(struct mwl_hal_priv *mh) +{ + mtx_lock(&mh->mh_mtx); +} + +static __inline void +MWL_HAL_LOCK_ASSERT(struct mwl_hal_priv *mh) +{ + mtx_assert(&mh->mh_mtx, MA_OWNED); +} + +static __inline void +MWL_HAL_UNLOCK(struct mwl_hal_priv *mh) +{ + mtx_unlock(&mh->mh_mtx); +} + +static __inline uint32_t +RD4(struct mwl_hal_priv *mh, bus_size_t off) +{ + return bus_space_read_4(mh->public.mh_iot, mh->public.mh_ioh, off); +} + +static __inline void +WR4(struct mwl_hal_priv *mh, bus_size_t off, uint32_t val) +{ + bus_space_write_4(mh->public.mh_iot, mh->public.mh_ioh, off, val); +} + +static void +mwl_hal_load_cb(void *arg, bus_dma_segment_t *segs, int nsegs, int error) +{ + bus_addr_t *paddr = (bus_addr_t*) arg; + KASSERT(error == 0, ("error %u on bus_dma callback", error)); + *paddr = segs->ds_addr; +} + +/* + * Setup for communication with the device. We allocate + * a command buffer and map it for bus dma use. The pci + * device id is used to identify whether the device has + * SRAM on it (in which case f/w download must include a + * memory controller reset). All bus i/o operations happen + * in BAR 1; the driver passes in the tag and handle we need. + */ +struct mwl_hal * +mwl_hal_attach(device_t dev, uint16_t devid, + bus_space_handle_t ioh, bus_space_tag_t iot, bus_dma_tag_t tag) +{ + struct mwl_hal_priv *mh; + struct mwl_hal_vap *hvap; + int error, i; + + mh = malloc(sizeof(struct mwl_hal_priv), M_DEVBUF, M_NOWAIT | M_ZERO); + if (mh == NULL) + return NULL; + mh->mh_dev = dev; + mh->public.mh_ioh = ioh; + mh->public.mh_iot = iot; + for (i = 0; i < MWL_BASTREAMS_MAX; i++) { + mh->mh_streams[i].public.txq = ba2qid[i]; + mh->mh_streams[i].stream = i; + /* construct back-mapping while we're at it */ + if (mh->mh_streams[i].public.txq < MWL_BAQID_MAX) + qid2ba[mh->mh_streams[i].public.txq] = i; + else + device_printf(dev, "unexpected BA tx qid %d for " + "stream %d\n", mh->mh_streams[i].public.txq, i); + } + /* setup constant portion of vap state */ + /* XXX should get max ap/client vap's from f/w */ + i = 0; + hvap = &mh->mh_vaps[i]; + hvap->vap_type = MWL_HAL_AP; + hvap->bss_type = htole16(WL_MAC_TYPE_PRIMARY_AP); + hvap->macid = 0; + for (i++; i < MWL_MBSS_AP_MAX; i++) { + hvap = &mh->mh_vaps[i]; + hvap->vap_type = MWL_HAL_AP; + hvap->bss_type = htole16(WL_MAC_TYPE_SECONDARY_AP); + hvap->macid = i; + } + hvap = &mh->mh_vaps[i]; + hvap->vap_type = MWL_HAL_STA; + hvap->bss_type = htole16(WL_MAC_TYPE_PRIMARY_CLIENT); + hvap->macid = i; + for (i++; i < MWL_MBSS_STA_MAX; i++) { + hvap = &mh->mh_vaps[i]; + hvap->vap_type = MWL_HAL_STA; + hvap->bss_type = htole16(WL_MAC_TYPE_SECONDARY_CLIENT); + hvap->macid = i; + } + mh->mh_revs.mh_devid = devid; + snprintf(mh->mh_mtxname, sizeof(mh->mh_mtxname), + "%s_hal", device_get_nameunit(dev)); + mtx_init(&mh->mh_mtx, mh->mh_mtxname, NULL, MTX_DEF); + + /* + * Allocate the command buffer and map into the address + * space of the h/w. We request "coherent" memory which + * will be uncached on some architectures. + */ + error = bus_dma_tag_create(tag, /* parent */ + PAGE_SIZE, 0, /* alignment, bounds */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + MWL_CMDBUF_SIZE, /* maxsize */ + 1, /* nsegments */ + MWL_CMDBUF_SIZE, /* maxsegsize */ + BUS_DMA_ALLOCNOW, /* flags */ + NULL, /* lockfunc */ + NULL, /* lockarg */ + &mh->mh_dmat); + if (error != 0) { + device_printf(dev, "unable to allocate memory for cmd buffer, " + "error %u\n", error); + goto fail0; + } + + /* allocate descriptors */ + error = bus_dmamap_create(mh->mh_dmat, BUS_DMA_NOWAIT, &mh->mh_dmamap); + if (error != 0) { + device_printf(dev, "unable to create dmamap for cmd buffers, " + "error %u\n", error); + goto fail0; + } + + error = bus_dmamem_alloc(mh->mh_dmat, (void**) &mh->mh_cmdbuf, + BUS_DMA_NOWAIT | BUS_DMA_COHERENT, + &mh->mh_dmamap); + if (error != 0) { + device_printf(dev, "unable to allocate memory for cmd buffer, " + "error %u\n", error); + goto fail1; + } + + error = bus_dmamap_load(mh->mh_dmat, mh->mh_dmamap, + mh->mh_cmdbuf, MWL_CMDBUF_SIZE, + mwl_hal_load_cb, &mh->mh_cmdaddr, + BUS_DMA_NOWAIT); + if (error != 0) { + device_printf(dev, "unable to load cmd buffer, error %u\n", + error); + goto fail2; + } + + /* + * Some cards have SDRAM. When loading firmware we need + * to reset the SDRAM controller prior to doing this. + * When the SDRAMSIZE is non-zero we do that work in + * mwl_hal_fwload. + */ + switch (devid) { + case 0x2a02: /* CB82 */ + case 0x2a03: /* CB85 */ + case 0x2a08: /* MC85_B1 */ + case 0x2a0b: /* CB85AP */ + case 0x2a24: + mh->mh_SDRAMSIZE_Addr = 0x40fe70b7; /* 8M SDRAM */ + break; + case 0x2a04: /* MC85 */ + mh->mh_SDRAMSIZE_Addr = 0x40fc70b7; /* 16M SDRAM */ + break; + default: + break; + } + return &mh->public; +fail2: + bus_dmamem_free(mh->mh_dmat, mh->mh_cmdbuf, mh->mh_dmamap); +fail1: + bus_dmamap_destroy(mh->mh_dmat, mh->mh_dmamap); +fail0: + bus_dma_tag_destroy(mh->mh_dmat); + mtx_destroy(&mh->mh_mtx); + free(mh, M_DEVBUF); + return NULL; +} + +void +mwl_hal_detach(struct mwl_hal *mh0) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + + bus_dmamem_free(mh->mh_dmat, mh->mh_cmdbuf, mh->mh_dmamap); + bus_dmamap_destroy(mh->mh_dmat, mh->mh_dmamap); + bus_dma_tag_destroy(mh->mh_dmat); + mtx_destroy(&mh->mh_mtx); + free(mh, M_DEVBUF); +} + +/* + * Reset internal state after a firmware download. + */ +static int +mwlResetHalState(struct mwl_hal_priv *mh) +{ + int i; + + /* XXX get from f/w */ + mh->mh_bastreams = (1<<MWL_BASTREAMS_MAX)-1; + for (i = 0; i < MWL_MBSS_MAX; i++) + mh->mh_vaps[i].mh = NULL; + /* + * Clear cumulative stats. + */ + mh->mh_RTSSuccesses = 0; + mh->mh_RTSFailures = 0; + mh->mh_RxDuplicateFrames = 0; + mh->mh_FCSErrorCount = 0; + /* + * Fetch cal data for later use. + * XXX may want to fetch other stuff too. + */ + /* XXX check return */ + if ((mh->mh_flags & MHF_CALDATA) == 0) + mwlGetPwrCalTable(mh); + return 0; +} + +struct mwl_hal_vap * +mwl_hal_newvap(struct mwl_hal *mh0, MWL_HAL_BSSTYPE type, + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + struct mwl_hal_vap *vap; + int i; + + MWL_HAL_LOCK(mh); + /* NB: could optimize but not worth it w/ max 32 bss */ + for (i = 0; i < MWL_MBSS_MAX; i++) { + vap = &mh->mh_vaps[i]; + if (vap->vap_type == type && vap->mh == NULL) { + vap->mh = mh; + mwl_hal_setmac_locked(vap, mac); + break; + } + } + MWL_HAL_UNLOCK(mh); + return (i < MWL_MBSS_MAX) ? vap : NULL; +} + +void +mwl_hal_delvap(struct mwl_hal_vap *vap) +{ + /* NB: locking not needed for single write */ + vap->mh = NULL; +} + +/* + * Manipulate the debug mask. Note debug + * msgs are only provided when this code is + * compiled with MWLHAL_DEBUG defined. + */ + +void +mwl_hal_setdebug(struct mwl_hal *mh, int debug) +{ + MWLPRIV(mh)->mh_debug = debug; +} + +int +mwl_hal_getdebug(struct mwl_hal *mh) +{ + return MWLPRIV(mh)->mh_debug; +} + +void +mwl_hal_setbastreams(struct mwl_hal *mh, int mask) +{ + MWLPRIV(mh)->mh_bastreams = mask & ((1<<MWL_BASTREAMS_MAX)-1); +} + +int +mwl_hal_getbastreams(struct mwl_hal *mh) +{ + return MWLPRIV(mh)->mh_bastreams; +} + +int +mwl_hal_ismbsscapable(struct mwl_hal *mh) +{ + return (MWLPRIV(mh)->mh_flags & MHF_MBSS) != 0; +} + +#if 0 +/* XXX inlined */ +/* + * Return the current ISR setting and clear the cause. + * XXX maybe make inline + */ +void +mwl_hal_getisr(struct mwl_hal *mh0, uint32_t *status) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + uint32_t cause; + + cause = RD4(mh, MACREG_REG_A2H_INTERRUPT_CAUSE); + if (cause == 0xffffffff) { /* card removed */ +device_printf(mh->mh_dev, "%s: cause 0x%x\n", __func__, cause); + cause = 0; + } else if (cause != 0) { + /* clear cause bits */ + WR4(mh, MACREG_REG_A2H_INTERRUPT_CAUSE, + cause &~ mh->public.mh_imask); + RD4(mh, MACREG_REG_INT_CODE); /* XXX flush write? */ + } + *status = cause; +} +#endif + +/* + * Set the interrupt mask. + */ +void +mwl_hal_intrset(struct mwl_hal *mh0, uint32_t mask) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + + WR4(mh, MACREG_REG_A2H_INTERRUPT_MASK, 0); + RD4(mh, MACREG_REG_INT_CODE); + + mh->public.mh_imask = mask; + WR4(mh, MACREG_REG_A2H_INTERRUPT_MASK, mask); + RD4(mh, MACREG_REG_INT_CODE); +} + +#if 0 +/* XXX inlined */ +/* + * Kick the firmware to tell it there are new tx descriptors + * for processing. The driver says what h/w q has work in + * case the f/w ever gets smarter. + */ +void +mwl_hal_txstart(struct mwl_hal *mh0, int qnum) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + uint32_t dummy; + + WR4(mh, MACREG_REG_H2A_INTERRUPT_EVENTS, MACREG_H2ARIC_BIT_PPA_READY); + dummy = RD4(mh, MACREG_REG_INT_CODE); +} +#endif + +/* + * Callback from the driver on a cmd done interrupt. + * Nothing to do right now as we spin waiting for + * cmd completion. + */ +void +mwl_hal_cmddone(struct mwl_hal *mh0) +{ +#if 0 + struct mwl_hal_priv *mh = MWLPRIV(mh0); + + if (mh->mh_debug & MWL_HAL_DEBUG_CMDDONE) { + device_printf(mh->mh_dev, "cmd done interrupt:\n"); + dumpresult(mh, 1); + } +#endif +} + +/* + * Return "hw specs". Note this must be the first + * cmd MUST be done after a firmware download or the + * f/w will lockup. + * XXX move into the hal so driver doesn't need to be responsible + */ +int +mwl_hal_gethwspecs(struct mwl_hal *mh0, struct mwl_hal_hwspec *hw) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_GET_HW_SPEC *pCmd; + int retval, minrev; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_GET_HW_SPEC, HostCmd_CMD_GET_HW_SPEC); + memset(&pCmd->PermanentAddr[0], 0xff, IEEE80211_ADDR_LEN); + pCmd->ulFwAwakeCookie = htole32((unsigned int)mh->mh_cmdaddr+2048); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_GET_HW_SPEC); + if (retval == 0) { + IEEE80211_ADDR_COPY(hw->macAddr, pCmd->PermanentAddr); + hw->wcbBase[0] = le32toh(pCmd->WcbBase0) & 0x0000ffff; + hw->wcbBase[1] = le32toh(pCmd->WcbBase1) & 0x0000ffff; + hw->wcbBase[2] = le32toh(pCmd->WcbBase2) & 0x0000ffff; + hw->wcbBase[3] = le32toh(pCmd->WcbBase3) & 0x0000ffff; + hw->rxDescRead = le32toh(pCmd->RxPdRdPtr)& 0x0000ffff; + hw->rxDescWrite = le32toh(pCmd->RxPdWrPtr)& 0x0000ffff; + hw->regionCode = le16toh(pCmd->RegionCode) & 0x00ff; + hw->fwReleaseNumber = le32toh(pCmd->FWReleaseNumber); + hw->maxNumWCB = le16toh(pCmd->NumOfWCB); + hw->maxNumMCAddr = le16toh(pCmd->NumOfMCastAddr); + hw->numAntennas = le16toh(pCmd->NumberOfAntenna); + hw->hwVersion = pCmd->Version; + hw->hostInterface = pCmd->HostIf; + + mh->mh_revs.mh_macRev = hw->hwVersion; /* XXX */ + mh->mh_revs.mh_phyRev = hw->hostInterface; /* XXX */ + + minrev = ((hw->fwReleaseNumber) >> 16) & 0xff; + if (minrev >= 4) { + /* starting with 3.4.x.x s/w BA streams supported */ + mh->mh_bastreams &= (1<<MWL_BASTREAMS_MAX)-1; + } else + mh->mh_bastreams &= (1<<2)-1; + } + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Inform the f/w about location of the tx/rx dma data structures + * and related state. This cmd must be done immediately after a + * mwl_hal_gethwspecs call or the f/w will lockup. + */ +int +mwl_hal_sethwdma(struct mwl_hal *mh0, const struct mwl_hal_txrxdma *dma) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_SET_HW_SPEC *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_SET_HW_SPEC, HostCmd_CMD_SET_HW_SPEC); + pCmd->WcbBase[0] = htole32(dma->wcbBase[0]); + pCmd->WcbBase[1] = htole32(dma->wcbBase[1]); + pCmd->WcbBase[2] = htole32(dma->wcbBase[2]); + pCmd->WcbBase[3] = htole32(dma->wcbBase[3]); + pCmd->TxWcbNumPerQueue = htole32(dma->maxNumTxWcb); + pCmd->NumTxQueues = htole32(dma->maxNumWCB); + pCmd->TotalRxWcb = htole32(1); /* XXX */ + pCmd->RxPdWrPtr = htole32(dma->rxDescRead); + pCmd->Flags = htole32(SET_HW_SPEC_HOSTFORM_BEACON +#ifdef MWL_HOST_PS_SUPPORT + | SET_HW_SPEC_HOST_POWERSAVE +#endif + | SET_HW_SPEC_HOSTFORM_PROBERESP); + /* disable multi-bss operation for A1-A4 parts */ + if (mh->mh_revs.mh_macRev < 5) + pCmd->Flags |= htole32(SET_HW_SPEC_DISABLEMBSS); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_HW_SPEC); + if (retval == 0) { + if (pCmd->Flags & htole32(SET_HW_SPEC_DISABLEMBSS)) + mh->mh_flags &= ~MHF_MBSS; + else + mh->mh_flags |= MHF_MBSS; + } + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Retrieve statistics from the f/w. + * XXX should be in memory shared w/ driver + */ +int +mwl_hal_gethwstats(struct mwl_hal *mh0, struct mwl_hal_hwstats *stats) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_802_11_GET_STAT *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_802_11_GET_STAT, + HostCmd_CMD_802_11_GET_STAT); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11_GET_STAT); + if (retval == 0) { + const uint32_t *sp = (const uint32_t *)&pCmd->TxRetrySuccesses; + uint32_t *dp = (uint32_t *)&stats->TxRetrySuccesses; + int i; + + for (i = 0; i < sizeof(*stats)/sizeof(uint32_t); i++) + dp[i] = le32toh(sp[i]); + /* + * Update stats not returned by f/w but available + * through public registers. Note these registers + * are "clear on read" so we maintain cumulative data. + * XXX register defines + */ + mh->mh_RTSSuccesses += RD4(mh, 0xa834); + mh->mh_RTSFailures += RD4(mh, 0xa830); + mh->mh_RxDuplicateFrames += RD4(mh, 0xa84c); + mh->mh_FCSErrorCount += RD4(mh, 0xa840); + } + MWL_HAL_UNLOCK(mh); + + stats->RTSSuccesses = mh->mh_RTSSuccesses; + stats->RTSFailures = mh->mh_RTSFailures; + stats->RxDuplicateFrames = mh->mh_RxDuplicateFrames; + stats->FCSErrorCount = mh->mh_FCSErrorCount; + return retval; +} + +/* + * Set HT guard interval handling. + * Takes effect immediately. + */ +int +mwl_hal_sethtgi(struct mwl_hal_vap *vap, int GIType) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_HT_GUARD_INTERVAL *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_HT_GUARD_INTERVAL, + HostCmd_CMD_HT_GUARD_INTERVAL); + pCmd->Action = htole32(HostCmd_ACT_GEN_SET); + + if (GIType == 0) { + pCmd->GIType = htole32(GI_TYPE_LONG); + } else if (GIType == 1) { + pCmd->GIType = htole32(GI_TYPE_LONG | GI_TYPE_SHORT); + } else { + pCmd->GIType = htole32(GI_TYPE_LONG); + } + + retval = mwlExecuteCmd(mh, HostCmd_CMD_HT_GUARD_INTERVAL); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Configure radio. + * Takes effect immediately. + * XXX preamble installed after set fixed rate cmd + */ +int +mwl_hal_setradio(struct mwl_hal *mh0, int onoff, MWL_HAL_PREAMBLE preamble) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_802_11_RADIO_CONTROL *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_802_11_RADIO_CONTROL, + HostCmd_CMD_802_11_RADIO_CONTROL); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET); + if (onoff == 0) + pCmd->Control = 0; + else + pCmd->Control = htole16(preamble); + pCmd->RadioOn = htole16(onoff); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11_RADIO_CONTROL); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Configure antenna use. + * Takes effect immediately. + * XXX tx antenna setting ignored + * XXX rx antenna setting should always be 3 (for now) + */ +int +mwl_hal_setantenna(struct mwl_hal *mh0, MWL_HAL_ANTENNA dirSet, int ant) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_802_11_RF_ANTENNA *pCmd; + int retval; + + if (!(dirSet == WL_ANTENNATYPE_RX || dirSet == WL_ANTENNATYPE_TX)) + return EINVAL; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_802_11_RF_ANTENNA, + HostCmd_CMD_802_11_RF_ANTENNA); + pCmd->Action = htole16(dirSet); + if (ant == 0) /* default to all/both antennae */ + ant = 3; + pCmd->AntennaMode = htole16(ant); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11_RF_ANTENNA); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Set packet size threshold for implicit use of RTS. + * Takes effect immediately. + * XXX packet length > threshold =>'s RTS + */ +int +mwl_hal_setrtsthreshold(struct mwl_hal_vap *vap, int threshold) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_DS_802_11_RTS_THSD *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_DS_802_11_RTS_THSD, + HostCmd_CMD_802_11_RTS_THSD); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET); + pCmd->Threshold = htole16(threshold); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11_RTS_THSD); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Enable sta-mode operation (disables beacon frame xmit). + */ +int +mwl_hal_setinframode(struct mwl_hal_vap *vap) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_SET_INFRA_MODE *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_SET_INFRA_MODE, + HostCmd_CMD_SET_INFRA_MODE); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_INFRA_MODE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Configure radar detection in support of 802.11h. + */ +int +mwl_hal_setradardetection(struct mwl_hal *mh0, MWL_HAL_RADAR action) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_802_11h_Detect_Radar *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_802_11h_Detect_Radar, + HostCmd_CMD_802_11H_DETECT_RADAR); + pCmd->CmdHdr.Length = htole16(sizeof(HostCmd_802_11h_Detect_Radar)); + pCmd->Action = htole16(action); + if (mh->mh_regioncode == DOMAIN_CODE_ETSI_131) + pCmd->RadarTypeCode = htole16(131); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11H_DETECT_RADAR); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Convert public channel flags definition to a + * value suitable for feeding to the firmware. + * Note this includes byte swapping. + */ +static uint32_t +cvtChannelFlags(const MWL_HAL_CHANNEL *chan) +{ + uint32_t w; + + /* + * NB: f/w only understands FREQ_BAND_5GHZ, supplying the more + * precise band info causes it to lockup (sometimes). + */ + w = (chan->channelFlags.FreqBand == MWL_FREQ_BAND_2DOT4GHZ) ? + FREQ_BAND_2DOT4GHZ : FREQ_BAND_5GHZ; + switch (chan->channelFlags.ChnlWidth) { + case MWL_CH_10_MHz_WIDTH: + w |= CH_10_MHz_WIDTH; + break; + case MWL_CH_20_MHz_WIDTH: + w |= CH_20_MHz_WIDTH; + break; + case MWL_CH_40_MHz_WIDTH: + default: + w |= CH_40_MHz_WIDTH; + break; + } + switch (chan->channelFlags.ExtChnlOffset) { + case MWL_EXT_CH_NONE: + w |= EXT_CH_NONE; + break; + case MWL_EXT_CH_ABOVE_CTRL_CH: + w |= EXT_CH_ABOVE_CTRL_CH; + break; + case MWL_EXT_CH_BELOW_CTRL_CH: + w |= EXT_CH_BELOW_CTRL_CH; + break; + } + return htole32(w); +} + +/* + * Start a channel switch announcement countdown. The IE + * in the beacon frame is allowed to go out and the firmware + * counts down and notifies the host when it's time to switch + * channels. + */ +int +mwl_hal_setchannelswitchie(struct mwl_hal *mh0, + const MWL_HAL_CHANNEL *nextchan, uint32_t mode, uint32_t count) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_SET_SWITCH_CHANNEL *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_SET_SWITCH_CHANNEL, + HostCmd_CMD_SET_SWITCH_CHANNEL); + pCmd->Next11hChannel = htole32(nextchan->channel); + pCmd->Mode = htole32(mode); + pCmd->InitialCount = htole32(count+1); + pCmd->ChannelFlags = cvtChannelFlags(nextchan); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_SWITCH_CHANNEL); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Set the region code that selects the radar bin'ing agorithm. + */ +int +mwl_hal_setregioncode(struct mwl_hal *mh0, int regionCode) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_SET_REGIONCODE_INFO *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_SET_REGIONCODE_INFO, + HostCmd_CMD_SET_REGION_CODE); + /* XXX map pseudo-codes to fw codes */ + switch (regionCode) { + case DOMAIN_CODE_ETSI_131: + pCmd->regionCode = htole16(DOMAIN_CODE_ETSI); + break; + default: + pCmd->regionCode = htole16(regionCode); + break; + } + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_REGION_CODE); + if (retval == 0) + mh->mh_regioncode = regionCode; + MWL_HAL_UNLOCK(mh); + return retval; +} + +#define RATEVAL(r) ((r) &~ RATE_MCS) +#define RATETYPE(r) (((r) & RATE_MCS) ? HT_RATE_TYPE : LEGACY_RATE_TYPE) + +int +mwl_hal_settxrate(struct mwl_hal_vap *vap, MWL_HAL_TXRATE_HANDLING handling, + const MWL_HAL_TXRATE *rate) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_USE_FIXED_RATE *pCmd; + FIXED_RATE_ENTRY *fp; + int retval, i, n; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_USE_FIXED_RATE, + HostCmd_CMD_SET_FIXED_RATE); + + pCmd->MulticastRate = RATEVAL(rate->McastRate); + pCmd->MultiRateTxType = RATETYPE(rate->McastRate); + /* NB: no rate type field */ + pCmd->ManagementRate = RATEVAL(rate->MgtRate); + memset(pCmd->FixedRateTable, 0, sizeof(pCmd->FixedRateTable)); + if (handling == RATE_FIXED) { + pCmd->Action = htole32(HostCmd_ACT_GEN_SET); + pCmd->AllowRateDrop = htole32(FIXED_RATE_WITHOUT_AUTORATE_DROP); + fp = pCmd->FixedRateTable; + fp->FixedRate = + htole32(RATEVAL(rate->RateSeries[0].Rate)); + fp->FixRateTypeFlags.FixRateType = + htole32(RATETYPE(rate->RateSeries[0].Rate)); + pCmd->EntryCount = htole32(1); + } else if (handling == RATE_FIXED_DROP) { + pCmd->Action = htole32(HostCmd_ACT_GEN_SET); + pCmd->AllowRateDrop = htole32(FIXED_RATE_WITH_AUTO_RATE_DROP); + n = 0; + fp = pCmd->FixedRateTable; + for (i = 0; i < 4; i++) { + if (rate->RateSeries[0].TryCount == 0) + break; + fp->FixRateTypeFlags.FixRateType = + htole32(RATETYPE(rate->RateSeries[i].Rate)); + fp->FixedRate = + htole32(RATEVAL(rate->RateSeries[i].Rate)); + fp->FixRateTypeFlags.RetryCountValid = + htole32(RETRY_COUNT_VALID); + fp->RetryCount = + htole32(rate->RateSeries[i].TryCount-1); + n++; + } + pCmd->EntryCount = htole32(n); + } else + pCmd->Action = htole32(HostCmd_ACT_NOT_USE_FIXED_RATE); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_FIXED_RATE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_settxrate_auto(struct mwl_hal *mh0, const MWL_HAL_TXRATE *rate) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_USE_FIXED_RATE *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_USE_FIXED_RATE, + HostCmd_CMD_SET_FIXED_RATE); + + pCmd->MulticastRate = RATEVAL(rate->McastRate); + pCmd->MultiRateTxType = RATETYPE(rate->McastRate); + /* NB: no rate type field */ + pCmd->ManagementRate = RATEVAL(rate->MgtRate); + memset(pCmd->FixedRateTable, 0, sizeof(pCmd->FixedRateTable)); + pCmd->Action = htole32(HostCmd_ACT_NOT_USE_FIXED_RATE); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_FIXED_RATE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +#undef RATEVAL +#undef RATETYPE + +int +mwl_hal_setslottime(struct mwl_hal *mh0, int usecs) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_SLOT *pCmd; + int retval; + + if (usecs != 9 && usecs != 20) + return EINVAL; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_SLOT, + HostCmd_CMD_802_11_SET_SLOT); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET); + pCmd->Slot = (usecs == 9 ? 1 : 0); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11_SET_SLOT); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_adjusttxpower(struct mwl_hal *mh0, uint32_t level) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_802_11_RF_TX_POWER *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_802_11_RF_TX_POWER, + HostCmd_CMD_802_11_RF_TX_POWER); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET); + + if (level < 30) { + pCmd->SupportTxPowerLevel = htole16(WL_TX_POWERLEVEL_LOW); + } else if (level >= 30 && level < 60) { + pCmd->SupportTxPowerLevel = htole16(WL_TX_POWERLEVEL_MEDIUM); + } else { + pCmd->SupportTxPowerLevel = htole16(WL_TX_POWERLEVEL_HIGH); + } + + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11_RF_TX_POWER); + MWL_HAL_UNLOCK(mh); + return retval; +} + +static const struct mwl_hal_channel * +findchannel(const struct mwl_hal_priv *mh, const MWL_HAL_CHANNEL *c) +{ + const struct mwl_hal_channel *hc; + const MWL_HAL_CHANNELINFO *ci; + int chan = c->channel, i; + + if (c->channelFlags.FreqBand == MWL_FREQ_BAND_2DOT4GHZ) { + i = chan - 1; + if (c->channelFlags.ChnlWidth == MWL_CH_40_MHz_WIDTH) { + ci = &mh->mh_40M; + if (c->channelFlags.ExtChnlOffset == MWL_EXT_CH_BELOW_CTRL_CH) + i -= 4; + } else + ci = &mh->mh_20M; + /* 2.4G channel table is directly indexed */ + hc = ((unsigned)i < ci->nchannels) ? &ci->channels[i] : NULL; + } else if (c->channelFlags.FreqBand == MWL_FREQ_BAND_5GHZ) { + if (c->channelFlags.ChnlWidth == MWL_CH_40_MHz_WIDTH) { + ci = &mh->mh_40M_5G; + if (c->channelFlags.ExtChnlOffset == MWL_EXT_CH_BELOW_CTRL_CH) + chan -= 4; + } else + ci = &mh->mh_20M_5G; + /* 5GHz channel table is sparse and must be searched */ + for (i = 0; i < ci->nchannels; i++) + if (ci->channels[i].ieee == chan) + break; + hc = (i < ci->nchannels) ? &ci->channels[i] : NULL; + } else + hc = NULL; + return hc; +} + +int +mwl_hal_settxpower(struct mwl_hal *mh0, const MWL_HAL_CHANNEL *c, uint8_t maxtxpow) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_802_11_RF_TX_POWER *pCmd; + const struct mwl_hal_channel *hc; + int i, retval; + + hc = findchannel(mh, c); + if (hc == NULL) { + /* XXX temp while testing */ + device_printf(mh->mh_dev, + "%s: no cal data for channel %u band %u width %u ext %u\n", + __func__, c->channel, c->channelFlags.FreqBand, + c->channelFlags.ChnlWidth, c->channelFlags.ExtChnlOffset); + return EINVAL; + } + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_802_11_RF_TX_POWER, + HostCmd_CMD_802_11_RF_TX_POWER); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET_LIST); + i = 0; + /* NB: 5Ghz cal data have the channel # in [0]; don't truncate */ + if (c->channelFlags.FreqBand == MWL_FREQ_BAND_5GHZ) + pCmd->PowerLevelList[i++] = htole16(hc->targetPowers[0]); + for (; i < 4; i++) { + uint16_t pow = hc->targetPowers[i]; + if (pow > maxtxpow) + pow = maxtxpow; + pCmd->PowerLevelList[i] = htole16(pow); + } + retval = mwlExecuteCmd(mh, HostCmd_CMD_802_11_RF_TX_POWER); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_getchannelinfo(struct mwl_hal *mh0, int band, int chw, + const MWL_HAL_CHANNELINFO **ci) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + + switch (band) { + case MWL_FREQ_BAND_2DOT4GHZ: + *ci = (chw == MWL_CH_20_MHz_WIDTH) ? &mh->mh_20M : &mh->mh_40M; + break; + case MWL_FREQ_BAND_5GHZ: + *ci = (chw == MWL_CH_20_MHz_WIDTH) ? + &mh->mh_20M_5G : &mh->mh_40M_5G; + break; + default: + return EINVAL; + } + return ((*ci)->freqLow == (*ci)->freqHigh) ? EINVAL : 0; +} + +int +mwl_hal_setmcast(struct mwl_hal *mh0, int nmc, const uint8_t macs[]) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_MAC_MULTICAST_ADR *pCmd; + int retval; + + if (nmc > MWL_HAL_MCAST_MAX) + return EINVAL; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_MAC_MULTICAST_ADR, + HostCmd_CMD_MAC_MULTICAST_ADR); + memcpy(pCmd->MACList, macs, nmc*IEEE80211_ADDR_LEN); + pCmd->NumOfAdrs = htole16(nmc); + pCmd->Action = htole16(0xffff); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_MAC_MULTICAST_ADR); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_keyset(struct mwl_hal_vap *vap, const MWL_HAL_KEYVAL *kv, + const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_UPDATE_ENCRYPTION_SET_KEY *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_UPDATE_ENCRYPTION_SET_KEY, + HostCmd_CMD_UPDATE_ENCRYPTION); + if (kv->keyFlags & (KEY_FLAG_TXGROUPKEY|KEY_FLAG_RXGROUPKEY)) + pCmd->ActionType = htole32(EncrActionTypeSetGroupKey); + else + pCmd->ActionType = htole32(EncrActionTypeSetKey); + pCmd->KeyParam.Length = htole16(sizeof(pCmd->KeyParam)); + pCmd->KeyParam.KeyTypeId = htole16(kv->keyTypeId); + pCmd->KeyParam.KeyInfo = htole32(kv->keyFlags); + pCmd->KeyParam.KeyIndex = htole32(kv->keyIndex); + /* NB: includes TKIP MIC keys */ + memcpy(&pCmd->KeyParam.Key, &kv->key, kv->keyLen); + switch (kv->keyTypeId) { + case KEY_TYPE_ID_WEP: + pCmd->KeyParam.KeyLen = htole16(kv->keyLen); + break; + case KEY_TYPE_ID_TKIP: + pCmd->KeyParam.KeyLen = htole16(sizeof(TKIP_TYPE_KEY)); + pCmd->KeyParam.Key.TkipKey.TkipRsc.low = + htole16(kv->key.tkip.rsc.low); + pCmd->KeyParam.Key.TkipKey.TkipRsc.high = + htole32(kv->key.tkip.rsc.high); + pCmd->KeyParam.Key.TkipKey.TkipTsc.low = + htole16(kv->key.tkip.tsc.low); + pCmd->KeyParam.Key.TkipKey.TkipTsc.high = + htole32(kv->key.tkip.tsc.high); + break; + case KEY_TYPE_ID_AES: + pCmd->KeyParam.KeyLen = htole16(sizeof(AES_TYPE_KEY)); + break; + } +#ifdef MWL_MBSS_SUPPORT + IEEE80211_ADDR_COPY(pCmd->KeyParam.Macaddr, mac); +#else + IEEE80211_ADDR_COPY(pCmd->Macaddr, mac); +#endif + retval = mwlExecuteCmd(mh, HostCmd_CMD_UPDATE_ENCRYPTION); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_keyreset(struct mwl_hal_vap *vap, const MWL_HAL_KEYVAL *kv, const uint8_t mac[IEEE80211_ADDR_LEN]) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_UPDATE_ENCRYPTION_SET_KEY *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_UPDATE_ENCRYPTION_SET_KEY, + HostCmd_CMD_UPDATE_ENCRYPTION); + pCmd->ActionType = htole16(EncrActionTypeRemoveKey); + pCmd->KeyParam.Length = htole16(sizeof(pCmd->KeyParam)); + pCmd->KeyParam.KeyTypeId = htole16(kv->keyTypeId); + pCmd->KeyParam.KeyInfo = htole32(kv->keyFlags); + pCmd->KeyParam.KeyIndex = htole32(kv->keyIndex); +#ifdef MWL_MBSS_SUPPORT + IEEE80211_ADDR_COPY(pCmd->KeyParam.Macaddr, mac); +#else + IEEE80211_ADDR_COPY(pCmd->Macaddr, mac); +#endif + retval = mwlExecuteCmd(mh, HostCmd_CMD_UPDATE_ENCRYPTION); + MWL_HAL_UNLOCK(mh); + return retval; +} + +static int +mwl_hal_setmac_locked(struct mwl_hal_vap *vap, + const uint8_t addr[IEEE80211_ADDR_LEN]) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_DS_SET_MAC *pCmd; + + _VCMD_SETUP(vap, pCmd, HostCmd_DS_SET_MAC, HostCmd_CMD_SET_MAC_ADDR); + IEEE80211_ADDR_COPY(&pCmd->MacAddr[0], addr); +#ifdef MWL_MBSS_SUPPORT + pCmd->MacType = vap->bss_type; /* NB: already byte swapped */ + IEEE80211_ADDR_COPY(vap->mac, addr); /* XXX do only if success */ +#endif + return mwlExecuteCmd(mh, HostCmd_CMD_SET_MAC_ADDR); +} + +int +mwl_hal_setmac(struct mwl_hal_vap *vap, const uint8_t addr[IEEE80211_ADDR_LEN]) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + int retval; + + MWL_HAL_LOCK(mh); + retval = mwl_hal_setmac_locked(vap, addr); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setbeacon(struct mwl_hal_vap *vap, const void *frame, size_t frameLen) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_DS_SET_BEACON *pCmd; + int retval; + + /* XXX verify frameLen fits */ + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_DS_SET_BEACON, HostCmd_CMD_SET_BEACON); + /* XXX override _VCMD_SETUP */ + pCmd->CmdHdr.Length = htole16(sizeof(HostCmd_DS_SET_BEACON)-1+frameLen); + pCmd->FrmBodyLen = htole16(frameLen); + memcpy(pCmd->FrmBody, frame, frameLen); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_BEACON); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setpowersave_bss(struct mwl_hal_vap *vap, uint8_t nsta) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_SET_POWERSAVESTATION *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_SET_POWERSAVESTATION, + HostCmd_CMD_SET_POWERSAVESTATION); + pCmd->NumberOfPowersave = nsta; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_POWERSAVESTATION); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setpowersave_sta(struct mwl_hal_vap *vap, uint16_t aid, int ena) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_SET_TIM *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_SET_TIM, HostCmd_CMD_SET_TIM); + pCmd->Aid = htole16(aid); + pCmd->Set = htole32(ena); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_TIM); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setassocid(struct mwl_hal_vap *vap, + const uint8_t bssId[IEEE80211_ADDR_LEN], uint16_t assocId) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_SET_AID *pCmd = (HostCmd_FW_SET_AID *) &mh->mh_cmdbuf[0]; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_SET_AID, HostCmd_CMD_SET_AID); + pCmd->AssocID = htole16(assocId); + IEEE80211_ADDR_COPY(&pCmd->MacAddr[0], bssId); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_AID); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setchannel(struct mwl_hal *mh0, const MWL_HAL_CHANNEL *chan) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_RF_CHANNEL *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_RF_CHANNEL, HostCmd_CMD_SET_RF_CHANNEL); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET); + pCmd->CurrentChannel = chan->channel; + pCmd->ChannelFlags = cvtChannelFlags(chan); /* NB: byte-swapped */ + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_RF_CHANNEL); + MWL_HAL_UNLOCK(mh); + return retval; +} + +static int +bastream_check_available(struct mwl_hal_priv *mh, int qid, + const uint8_t Macaddr[IEEE80211_ADDR_LEN], + uint8_t Tid, uint8_t ParamInfo) +{ + HostCmd_FW_BASTREAM *pCmd; + int retval; + + MWL_HAL_LOCK_ASSERT(mh); + + _CMD_SETUP(pCmd, HostCmd_FW_BASTREAM, HostCmd_CMD_BASTREAM); + pCmd->ActionType = htole32(BaCheckCreateStream); + pCmd->BaInfo.CreateParams.BarThrs = htole32(63); + pCmd->BaInfo.CreateParams.WindowSize = htole32(64); + pCmd->BaInfo.CreateParams.IdleThrs = htole32(0x22000); + IEEE80211_ADDR_COPY(&pCmd->BaInfo.CreateParams.PeerMacAddr[0], Macaddr); + pCmd->BaInfo.CreateParams.DialogToken = 10; + pCmd->BaInfo.CreateParams.Tid = Tid; + pCmd->BaInfo.CreateParams.QueueId = qid; + pCmd->BaInfo.CreateParams.ParamInfo = (uint8_t) ParamInfo; +#if 0 + cvtBAFlags(&pCmd->BaInfo.CreateParams.Flags, sp->ba_type, 0); +#else + pCmd->BaInfo.CreateParams.Flags = + htole32(BASTREAM_FLAG_IMMEDIATE_TYPE) + | htole32(BASTREAM_FLAG_DIRECTION_UPSTREAM) + ; +#endif + + retval = mwlExecuteCmd(mh, HostCmd_CMD_BASTREAM); + if (retval == 0) { + /* + * NB: BA stream create may fail when the stream is + * h/w backed under some (as yet not understood) conditions. + * Check the result code to catch this. + */ + if (le16toh(pCmd->CmdHdr.Result) != HostCmd_RESULT_OK) + retval = EIO; + } + return retval; +} + +const MWL_HAL_BASTREAM * +mwl_hal_bastream_alloc(struct mwl_hal *mh0, int ba_type, + const uint8_t Macaddr[IEEE80211_ADDR_LEN], + uint8_t Tid, uint8_t ParamInfo, void *a1, void *a2) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + struct mwl_hal_bastream *sp; + int s; + + MWL_HAL_LOCK(mh); + if (mh->mh_bastreams == 0) { + /* no streams available */ + MWL_HAL_UNLOCK(mh); + return NULL; + } + for (s = 0; (mh->mh_bastreams & (1<<s)) == 0; s++) + ; + if (bastream_check_available(mh, s, Macaddr, Tid, ParamInfo)) { + MWL_HAL_UNLOCK(mh); + return NULL; + } + sp = &mh->mh_streams[s]; + mh->mh_bastreams &= ~(1<<s); + sp->public.data[0] = a1; + sp->public.data[1] = a2; + IEEE80211_ADDR_COPY(sp->macaddr, Macaddr); + sp->tid = Tid; + sp->paraminfo = ParamInfo; + sp->setup = 0; + sp->ba_type = ba_type; + MWL_HAL_UNLOCK(mh); + return sp != NULL ? &sp->public : NULL; +} + +const MWL_HAL_BASTREAM * +mwl_hal_bastream_lookup(struct mwl_hal *mh0, int s) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + + if (!(0 <= s && s < MWL_BASTREAMS_MAX)) + return NULL; + if (mh->mh_bastreams & (1<<s)) + return NULL; + return &mh->mh_streams[s].public; +} + +#ifndef __DECONST +#define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var)) +#endif + +int +mwl_hal_bastream_create(struct mwl_hal *mh0, + const MWL_HAL_BASTREAM *s, int BarThrs, int WindowSize, uint16_t seqno) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + struct mwl_hal_bastream *sp = __DECONST(struct mwl_hal_bastream *, s); + HostCmd_FW_BASTREAM *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_BASTREAM, HostCmd_CMD_BASTREAM); + pCmd->ActionType = htole32(BaCreateStream); + pCmd->BaInfo.CreateParams.BarThrs = htole32(63);//BarThrs; + pCmd->BaInfo.CreateParams.WindowSize = htole32(64); /*WindowSize;*/ + pCmd->BaInfo.CreateParams.IdleThrs = htole32(0x22000); + IEEE80211_ADDR_COPY(&pCmd->BaInfo.CreateParams.PeerMacAddr[0], + sp->macaddr); +#if 0 + pCmd->BaInfo.CreateParams.DialogToken = DialogToken; +#else + pCmd->BaInfo.CreateParams.DialogToken = 10; +#endif + pCmd->BaInfo.CreateParams.Tid = sp->tid; + pCmd->BaInfo.CreateParams.QueueId = sp->stream; + pCmd->BaInfo.CreateParams.ParamInfo = sp->paraminfo; + /* NB: ResetSeqNo known to be zero */ + pCmd->BaInfo.CreateParams.StartSeqNo = htole16(seqno); +#if 0 + cvtBAFlags(&pCmd->BaInfo.CreateParams.Flags, sp->ba_type, 0); +#else + pCmd->BaInfo.CreateParams.Flags = + htole32(BASTREAM_FLAG_IMMEDIATE_TYPE) + | htole32(BASTREAM_FLAG_DIRECTION_UPSTREAM) + ; +#endif + + retval = mwlExecuteCmd(mh, HostCmd_CMD_BASTREAM); + if (retval == 0) { + /* + * NB: BA stream create may fail when the stream is + * h/w backed under some (as yet not understood) conditions. + * Check the result code to catch this. + */ + if (le16toh(pCmd->CmdHdr.Result) != HostCmd_RESULT_OK) + retval = EIO; + else + sp->setup = 1; + } + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_bastream_destroy(struct mwl_hal *mh0, const MWL_HAL_BASTREAM *s) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + struct mwl_hal_bastream *sp = __DECONST(struct mwl_hal_bastream *, s); + HostCmd_FW_BASTREAM *pCmd; + int retval; + + if (sp->stream >= MWL_BASTREAMS_MAX) { + /* XXX */ + return EINVAL; + } + MWL_HAL_LOCK(mh); + if (sp->setup) { + _CMD_SETUP(pCmd, HostCmd_FW_BASTREAM, HostCmd_CMD_BASTREAM); + pCmd->ActionType = htole32(BaDestroyStream); + pCmd->BaInfo.DestroyParams.FwBaContext.Context = + htole32(sp->stream); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_BASTREAM); + } else + retval = 0; + /* NB: always reclaim stream */ + mh->mh_bastreams |= 1<<sp->stream; + sp->public.data[0] = NULL; + sp->public.data[1] = NULL; + sp->setup = 0; + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_bastream_get_seqno(struct mwl_hal *mh0, + const MWL_HAL_BASTREAM *s, uint16_t *pseqno) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + struct mwl_hal_bastream *sp = __DECONST(struct mwl_hal_bastream *, s); + HostCmd_GET_SEQNO *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_GET_SEQNO, HostCmd_CMD_GET_SEQNO); + IEEE80211_ADDR_COPY(pCmd->MacAddr, sp->macaddr); + pCmd->TID = sp->tid; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_GET_SEQNO); + if (retval == 0) + *pseqno = le16toh(pCmd->SeqNo); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_getwatchdogbitmap(struct mwl_hal *mh0, uint8_t bitmap[1]) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_GET_WATCHDOG_BITMAP *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_GET_WATCHDOG_BITMAP, + HostCmd_CMD_GET_WATCHDOG_BITMAP); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_GET_WATCHDOG_BITMAP); + if (retval == 0) { + bitmap[0] = pCmd->Watchdogbitmap; + /* fw returns qid, map it to BA stream */ + if (bitmap[0] < MWL_BAQID_MAX) + bitmap[0] = qid2ba[bitmap[0]]; + } + MWL_HAL_UNLOCK(mh); + return retval; +} + +static void +cvtPeerInfo(PeerInfo_t *to, const MWL_HAL_PEERINFO *from) +{ + to->LegacyRateBitMap = htole32(from->LegacyRateBitMap); + to->HTRateBitMap = htole32(from->HTRateBitMap); + to->CapInfo = htole16(from->CapInfo); + to->HTCapabilitiesInfo = htole16(from->HTCapabilitiesInfo); + to->MacHTParamInfo = from->MacHTParamInfo; + to->AddHtInfo.ControlChan = from->AddHtInfo.ControlChan; + to->AddHtInfo.AddChan = from->AddHtInfo.AddChan; + to->AddHtInfo.OpMode = htole16(from->AddHtInfo.OpMode); + to->AddHtInfo.stbc = htole16(from->AddHtInfo.stbc); +} + +/* XXX station id must be in [0..63] */ +int +mwl_hal_newstation(struct mwl_hal_vap *vap, + const uint8_t addr[IEEE80211_ADDR_LEN], uint16_t aid, uint16_t sid, + const MWL_HAL_PEERINFO *peer, int isQosSta, int wmeInfo) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_SET_NEW_STN *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_SET_NEW_STN, HostCmd_CMD_SET_NEW_STN); + pCmd->AID = htole16(aid); + pCmd->StnId = htole16(sid); + pCmd->Action = htole16(0); /* SET */ + if (peer != NULL) { + /* NB: must fix up byte order */ + cvtPeerInfo(&pCmd->PeerInfo, peer); + } + IEEE80211_ADDR_COPY(&pCmd->MacAddr[0], addr); + pCmd->Qosinfo = wmeInfo; + pCmd->isQosSta = (isQosSta != 0); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_NEW_STN); + if (retval == 0 && IEEE80211_ADDR_EQ(vap->mac, addr)) + vap->flags |= MVF_STATION; + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_delstation(struct mwl_hal_vap *vap, + const uint8_t addr[IEEE80211_ADDR_LEN]) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_SET_NEW_STN *pCmd; + int retval, islocal; + + MWL_HAL_LOCK(mh); + islocal = IEEE80211_ADDR_EQ(vap->mac, addr); + if (!islocal || (vap->flags & MVF_STATION)) { + _VCMD_SETUP(vap, pCmd, HostCmd_FW_SET_NEW_STN, + HostCmd_CMD_SET_NEW_STN); + pCmd->Action = htole16(2); /* REMOVE */ + IEEE80211_ADDR_COPY(&pCmd->MacAddr[0], addr); + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_NEW_STN); + if (islocal) + vap->flags &= ~MVF_STATION; + } else + retval = 0; + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Prod the firmware to age packets on station power + * save queues and reap frames on the tx aggregation q's. + */ +int +mwl_hal_setkeepalive(struct mwl_hal *mh0) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_KEEP_ALIVE_TICK *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_KEEP_ALIVE_TICK, + HostCmd_CMD_SET_KEEP_ALIVE); + /* + * NB: tick must be 0 to prod the f/w; + * a non-zero value is a noop. + */ + pCmd->tick = 0; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_KEEP_ALIVE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setapmode(struct mwl_hal_vap *vap, MWL_HAL_APMODE ApMode) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_SET_APMODE *pCmd; + int retval; + + /* XXX validate ApMode? */ + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_SET_APMODE, HostCmd_CMD_SET_APMODE); + pCmd->ApMode = ApMode; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_APMODE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_stop(struct mwl_hal_vap *vap) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_DS_BSS_START *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + if (vap->flags & MVF_RUNNING) { + _VCMD_SETUP(vap, pCmd, HostCmd_DS_BSS_START, + HostCmd_CMD_BSS_START); + pCmd->Enable = htole32(HostCmd_ACT_GEN_OFF); + retval = mwlExecuteCmd(mh, HostCmd_CMD_BSS_START); + } else + retval = 0; + /* NB: mark !running regardless */ + vap->flags &= ~MVF_RUNNING; + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_start(struct mwl_hal_vap *vap) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_DS_BSS_START *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_DS_BSS_START, HostCmd_CMD_BSS_START); + pCmd->Enable = htole32(HostCmd_ACT_GEN_ON); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_BSS_START); + if (retval == 0) + vap->flags |= MVF_RUNNING; + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setgprot(struct mwl_hal *mh0, int prot) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_G_PROTECT_FLAG *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_G_PROTECT_FLAG, + HostCmd_CMD_SET_G_PROTECT_FLAG); + pCmd->GProtectFlag = htole32(prot); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_G_PROTECT_FLAG); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setwmm(struct mwl_hal *mh0, int onoff) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SetWMMMode *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SetWMMMode, + HostCmd_CMD_SET_WMM_MODE); + pCmd->Action = htole16(onoff); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_WMM_MODE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setedcaparams(struct mwl_hal *mh0, uint8_t qnum, + uint32_t CWmin, uint32_t CWmax, uint8_t AIFSN, uint16_t TXOPLimit) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_EDCA_PARAMS *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_EDCA_PARAMS, + HostCmd_CMD_SET_EDCA_PARAMS); + /* + * NB: CWmin and CWmax are always set. + * TxOpLimit is set if bit 0x2 is marked in Action + * AIFSN is set if bit 0x4 is marked in Action + */ + pCmd->Action = htole16(0xffff); /* NB: set everything */ + pCmd->TxOP = htole16(TXOPLimit); + pCmd->CWMax = htole32(CWmax); + pCmd->CWMin = htole32(CWmin); + pCmd->AIFSN = AIFSN; + pCmd->TxQNum = qnum; /* XXX check */ + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_EDCA_PARAMS); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* XXX 0 = indoor, 1 = outdoor */ +int +mwl_hal_setrateadaptmode(struct mwl_hal *mh0, uint16_t mode) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_SET_RATE_ADAPT_MODE *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_SET_RATE_ADAPT_MODE, + HostCmd_CMD_SET_RATE_ADAPT_MODE); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET); + pCmd->RateAdaptMode = htole16(mode); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_RATE_ADAPT_MODE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setcsmode(struct mwl_hal *mh0, MWL_HAL_CSMODE csmode) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_DS_SET_LINKADAPT_CS_MODE *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_SET_LINKADAPT_CS_MODE, + HostCmd_CMD_SET_LINKADAPT_CS_MODE); + pCmd->Action = htole16(HostCmd_ACT_GEN_SET); + pCmd->CSMode = htole16(csmode); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_LINKADAPT_CS_MODE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setnprot(struct mwl_hal_vap *vap, MWL_HAL_HTPROTECT mode) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_SET_N_PROTECT_FLAG *pCmd; + int retval; + + /* XXX validate mode */ + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_SET_N_PROTECT_FLAG, + HostCmd_CMD_SET_N_PROTECT_FLAG); + pCmd->NProtectFlag = htole32(mode); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_N_PROTECT_FLAG); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setnprotmode(struct mwl_hal_vap *vap, uint8_t mode) +{ + struct mwl_hal_priv *mh = MWLVAP(vap); + HostCmd_FW_SET_N_PROTECT_OPMODE *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _VCMD_SETUP(vap, pCmd, HostCmd_FW_SET_N_PROTECT_OPMODE, + HostCmd_CMD_SET_N_PROTECT_OPMODE); + pCmd->NProtectOpMode = mode; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_N_PROTECT_OPMODE); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setoptimizationlevel(struct mwl_hal *mh0, int level) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_OPTIMIZATION_LEVEL *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_OPTIMIZATION_LEVEL, + HostCmd_CMD_SET_OPTIMIZATION_LEVEL); + pCmd->OptLevel = level; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_OPTIMIZATION_LEVEL); + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setmimops(struct mwl_hal *mh0, const uint8_t addr[IEEE80211_ADDR_LEN], + uint8_t enable, uint8_t mode) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_MIMOPSHT *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_MIMOPSHT, HostCmd_CMD_SET_MIMOPSHT); + IEEE80211_ADDR_COPY(pCmd->Addr, addr); + pCmd->Enable = enable; + pCmd->Mode = mode; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_MIMOPSHT); + MWL_HAL_UNLOCK(mh); + return retval; +} + +static int +mwlGetCalTable(struct mwl_hal_priv *mh, uint8_t annex, uint8_t index) +{ + HostCmd_FW_GET_CALTABLE *pCmd; + int retval; + + MWL_HAL_LOCK_ASSERT(mh); + + _CMD_SETUP(pCmd, HostCmd_FW_GET_CALTABLE, HostCmd_CMD_GET_CALTABLE); + pCmd->annex = annex; + pCmd->index = index; + memset(pCmd->calTbl, 0, sizeof(pCmd->calTbl)); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_GET_CALTABLE); + if (retval == 0 && + pCmd->calTbl[0] != annex && annex != 0 && annex != 255) + retval = EIO; + return retval; +} + +/* + * Calculate the max tx power from the channel's cal data. + */ +static void +setmaxtxpow(struct mwl_hal_channel *hc, int i, int maxix) +{ + hc->maxTxPow = hc->targetPowers[i]; + for (i++; i < maxix; i++) + if (hc->targetPowers[i] > hc->maxTxPow) + hc->maxTxPow = hc->targetPowers[i]; +} + +/* + * Construct channel info for 5GHz channels from cal data. + */ +static void +get5Ghz(MWL_HAL_CHANNELINFO *ci, const uint8_t table[], int len) +{ + int i, j, f, l, h; + + l = 32000; + h = 0; + j = 0; + for (i = 0; i < len; i += 4) { + struct mwl_hal_channel *hc; + + if (table[i] == 0) + continue; + f = 5000 + 5*table[i]; + if (f < l) + l = f; + if (f > h) + h = f; + hc = &ci->channels[j]; + hc->freq = f; + hc->ieee = table[i]; + memcpy(hc->targetPowers, &table[i], 4); + setmaxtxpow(hc, 1, 4); /* NB: col 1 is the freq, skip*/ + j++; + } + ci->nchannels = j; + ci->freqLow = (l == 32000) ? 0 : l; + ci->freqHigh = h; +} + +static uint16_t +ieee2mhz(int chan) +{ + if (chan == 14) + return 2484; + if (chan < 14) + return 2407 + chan*5; + return 2512 + (chan-15)*20; +} + +/* + * Construct channel info for 2.4GHz channels from cal data. + */ +static void +get2Ghz(MWL_HAL_CHANNELINFO *ci, const uint8_t table[], int len) +{ + int i, j; + + j = 0; + for (i = 0; i < len; i += 4) { + struct mwl_hal_channel *hc = &ci->channels[j]; + hc->ieee = 1+j; + hc->freq = ieee2mhz(1+j); + memcpy(hc->targetPowers, &table[i], 4); + setmaxtxpow(hc, 0, 4); + j++; + } + ci->nchannels = j; + ci->freqLow = ieee2mhz(1); + ci->freqHigh = ieee2mhz(j); +} + +#undef DUMPCALDATA +#ifdef DUMPCALDATA +static void +dumpcaldata(const char *name, const uint8_t *table, int n) +{ + int i; + printf("\n%s:\n", name); + for (i = 0; i < n; i += 4) + printf("[%2d] %3d %3d %3d %3d\n", i/4, table[i+0], table[i+1], table[i+2], table[i+3]); +} +#endif + +static int +mwlGetPwrCalTable(struct mwl_hal_priv *mh) +{ + const uint8_t *data; + MWL_HAL_CHANNELINFO *ci; + int len; + + MWL_HAL_LOCK(mh); + /* NB: we hold the lock so it's ok to use cmdbuf */ + data = ((const HostCmd_FW_GET_CALTABLE *) mh->mh_cmdbuf)->calTbl; + if (mwlGetCalTable(mh, 33, 0) == 0) { + len = (data[2] | (data[3] << 8)) - 12; + if (len > PWTAGETRATETABLE20M) + len = PWTAGETRATETABLE20M; +#ifdef DUMPCALDATA +dumpcaldata("2.4G 20M", &data[12], len);/*XXX*/ +#endif + get2Ghz(&mh->mh_20M, &data[12], len); + } + if (mwlGetCalTable(mh, 34, 0) == 0) { + len = (data[2] | (data[3] << 8)) - 12; + if (len > PWTAGETRATETABLE40M) + len = PWTAGETRATETABLE40M; +#ifdef DUMPCALDATA +dumpcaldata("2.4G 40M", &data[12], len);/*XXX*/ +#endif + ci = &mh->mh_40M; + get2Ghz(ci, &data[12], len); + } + if (mwlGetCalTable(mh, 35, 0) == 0) { + len = (data[2] | (data[3] << 8)) - 20; + if (len > PWTAGETRATETABLE20M_5G) + len = PWTAGETRATETABLE20M_5G; +#ifdef DUMPCALDATA +dumpcaldata("5G 20M", &data[20], len);/*XXX*/ +#endif + get5Ghz(&mh->mh_20M_5G, &data[20], len); + } + if (mwlGetCalTable(mh, 36, 0) == 0) { + len = (data[2] | (data[3] << 8)) - 20; + if (len > PWTAGETRATETABLE40M_5G) + len = PWTAGETRATETABLE40M_5G; +#ifdef DUMPCALDATA +dumpcaldata("5G 40M", &data[20], len);/*XXX*/ +#endif + ci = &mh->mh_40M_5G; + get5Ghz(ci, &data[20], len); + } + mh->mh_flags |= MHF_CALDATA; + MWL_HAL_UNLOCK(mh); + return 0; +} + +int +mwl_hal_getregioncode(struct mwl_hal *mh0, uint8_t *countryCode) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + int retval; + + MWL_HAL_LOCK(mh); + retval = mwlGetCalTable(mh, 0, 0); + if (retval == 0) { + const HostCmd_FW_GET_CALTABLE *pCmd = + (const HostCmd_FW_GET_CALTABLE *) mh->mh_cmdbuf; + *countryCode = pCmd->calTbl[16]; + } + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_setpromisc(struct mwl_hal *mh0, int ena) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + uint32_t v; + + MWL_HAL_LOCK(mh); + v = RD4(mh, MACREG_REG_PROMISCUOUS); + WR4(mh, MACREG_REG_PROMISCUOUS, ena ? v | 1 : v &~ 1); + MWL_HAL_UNLOCK(mh); + return 0; +} + +int +mwl_hal_getpromisc(struct mwl_hal *mh0) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + uint32_t v; + + MWL_HAL_LOCK(mh); + v = RD4(mh, MACREG_REG_PROMISCUOUS); + MWL_HAL_UNLOCK(mh); + return (v & 1) != 0; +} + +int +mwl_hal_GetBeacon(struct mwl_hal *mh0, uint8_t *pBcn, uint16_t *pLen) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_GET_BEACON *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_GET_BEACON, HostCmd_CMD_GET_BEACON); + pCmd->Bcnlen = htole16(0); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_GET_BEACON); + if (retval == 0) { + /* XXX bounds check */ + memcpy(pBcn, &pCmd->Bcn, pCmd->Bcnlen); + *pLen = pCmd->Bcnlen; + } + MWL_HAL_UNLOCK(mh); + return retval; +} + +int +mwl_hal_SetRifs(struct mwl_hal *mh0, uint8_t QNum) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + HostCmd_FW_SET_RIFS *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_FW_SET_RIFS, HostCmd_CMD_SET_RIFS); + pCmd->QNum = QNum; + + retval = mwlExecuteCmd(mh, HostCmd_CMD_SET_RIFS); + MWL_HAL_UNLOCK(mh); + return retval; +} + +/* + * Diagnostic api's for set/get registers. + */ + +static int +getRFReg(struct mwl_hal_priv *mh, int flag, uint32_t reg, uint32_t *val) +{ + HostCmd_DS_RF_REG_ACCESS *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_RF_REG_ACCESS, HostCmd_CMD_RF_REG_ACCESS); + pCmd->Offset = htole16(reg); + pCmd->Action = htole16(flag); + pCmd->Value = htole32(*val); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_RF_REG_ACCESS); + if (retval == 0) + *val = pCmd->Value; + MWL_HAL_UNLOCK(mh); + return retval; +} + +static int +getBBReg(struct mwl_hal_priv *mh, int flag, uint32_t reg, uint32_t *val) +{ + HostCmd_DS_BBP_REG_ACCESS *pCmd; + int retval; + + MWL_HAL_LOCK(mh); + _CMD_SETUP(pCmd, HostCmd_DS_BBP_REG_ACCESS, HostCmd_CMD_BBP_REG_ACCESS); + pCmd->Offset = htole16(reg); + pCmd->Action = htole16(flag); + pCmd->Value = htole32(*val); + + retval = mwlExecuteCmd(mh, HostCmd_CMD_BBP_REG_ACCESS); + if (retval == 0) + *val = pCmd->Value; + MWL_HAL_UNLOCK(mh); + return retval; +} + +static u_int +mwl_hal_getregdump(struct mwl_hal_priv *mh, const MWL_DIAG_REGRANGE *regs, + void *dstbuf, int space) +{ + uint32_t *dp = dstbuf; + int i; + + for (i = 0; space >= 2*sizeof(uint32_t); i++) { + u_int r = regs[i].start; + u_int e = regs[i].end; + *dp++ = (r<<16) | e; + space -= sizeof(uint32_t); + do { + if (MWL_DIAG_ISMAC(r)) + *dp = RD4(mh, r); + else if (MWL_DIAG_ISBB(r)) + getBBReg(mh, HostCmd_ACT_GEN_READ, + r - MWL_DIAG_BASE_BB, dp); + else if (MWL_DIAG_ISRF(r)) + getRFReg(mh, HostCmd_ACT_GEN_READ, + r - MWL_DIAG_BASE_RF, dp); + else if (r < 0x1000 || r == MACREG_REG_FW_PRESENT) + *dp = RD4(mh, r); + else + *dp = 0xffffffff; + dp++; + r += sizeof(uint32_t); + space -= sizeof(uint32_t); + } while (r <= e && space >= sizeof(uint32_t)); + } + return (char *) dp - (char *) dstbuf; +} + +int +mwl_hal_getdiagstate(struct mwl_hal *mh0, int request, + const void *args, uint32_t argsize, + void **result, uint32_t *resultsize) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + + switch (request) { + case MWL_DIAG_CMD_REVS: + *result = &mh->mh_revs; + *resultsize = sizeof(mh->mh_revs); + return 1; + case MWL_DIAG_CMD_REGS: + *resultsize = mwl_hal_getregdump(mh, args, *result, *resultsize); + return 1; + case MWL_DIAG_CMD_HOSTCMD: { + FWCmdHdr *pCmd = (FWCmdHdr *) &mh->mh_cmdbuf[0]; + int retval; + + MWL_HAL_LOCK(mh); + memcpy(pCmd, args, argsize); + retval = mwlExecuteCmd(mh, le16toh(pCmd->Cmd)); + *result = (*resultsize != 0) ? pCmd : NULL; + MWL_HAL_UNLOCK(mh); + return (retval == 0); + } + case MWL_DIAG_CMD_FWLOAD: + if (mwl_hal_fwload(mh0, __DECONST(void *, args))) { + device_printf(mh->mh_dev, "problem loading fw image\n"); + return 0; + } + return 1; + } + return 0; +} + +/* + * Low level firmware cmd block handshake support. + */ + +static void +mwlSendCmd(struct mwl_hal_priv *mh) +{ + uint32_t dummy; + + bus_dmamap_sync(mh->mh_dmat, mh->mh_dmamap, + BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE); + + WR4(mh, MACREG_REG_GEN_PTR, mh->mh_cmdaddr); + dummy = RD4(mh, MACREG_REG_INT_CODE); + + WR4(mh, MACREG_REG_H2A_INTERRUPT_EVENTS, MACREG_H2ARIC_BIT_DOOR_BELL); +} + +static int +mwlWaitForCmdComplete(struct mwl_hal_priv *mh, uint16_t cmdCode) +{ +#define MAX_WAIT_FW_COMPLETE_ITERATIONS 10000 + int i; + + for (i = 0; i < MAX_WAIT_FW_COMPLETE_ITERATIONS; i++) { + if (mh->mh_cmdbuf[0] == le16toh(cmdCode)) + return 1; + DELAY(1*1000); + } + return 0; +#undef MAX_WAIT_FW_COMPLETE_ITERATIONS +} + +static int +mwlExecuteCmd(struct mwl_hal_priv *mh, unsigned short cmd) +{ + + MWL_HAL_LOCK_ASSERT(mh); + + if ((mh->mh_flags & MHF_FWHANG) && + (mh->mh_debug & MWL_HAL_DEBUG_IGNHANG) == 0) { +#ifdef MWLHAL_DEBUG + device_printf(mh->mh_dev, "firmware hung, skipping cmd %s\n", + mwlcmdname(cmd)); +#else + device_printf(mh->mh_dev, "firmware hung, skipping cmd 0x%x\n", + cmd); +#endif + return ENXIO; + } + if (RD4(mh, MACREG_REG_INT_CODE) == 0xffffffff) { + device_printf(mh->mh_dev, "%s: device not present!\n", + __func__); + return EIO; + } +#ifdef MWLHAL_DEBUG + if (mh->mh_debug & MWL_HAL_DEBUG_SENDCMD) + dumpresult(mh, 0); +#endif + mwlSendCmd(mh); + if (!mwlWaitForCmdComplete(mh, 0x8000 | cmd)) { +#ifdef MWLHAL_DEBUG + device_printf(mh->mh_dev, + "timeout waiting for f/w cmd %s\n", mwlcmdname(cmd)); +#else + device_printf(mh->mh_dev, + "timeout waiting for f/w cmd 0x%x\n", cmd); +#endif + mh->mh_flags |= MHF_FWHANG; + return ETIMEDOUT; + } + bus_dmamap_sync(mh->mh_dmat, mh->mh_dmamap, + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); +#ifdef MWLHAL_DEBUG + if (mh->mh_debug & MWL_HAL_DEBUG_CMDDONE) + dumpresult(mh, 1); +#endif + return 0; +} + +/* + * Firmware download support. + */ +#define FW_DOWNLOAD_BLOCK_SIZE 256 +#define FW_CHECK_USECS (5*1000) /* 5ms */ +#define FW_MAX_NUM_CHECKS 200 + +#if 0 +/* XXX read f/w from file */ +#include <dev/mwl/mwlbootfw.h> +#include <dev/mwl/mwl88W8363fw.h> +#endif + +static void +mwlFwReset(struct mwl_hal_priv *mh) +{ + if (RD4(mh, MACREG_REG_INT_CODE) == 0xffffffff) { + device_printf(mh->mh_dev, "%s: device not present!\n", + __func__); + return; + } + WR4(mh, MACREG_REG_H2A_INTERRUPT_EVENTS, ISR_RESET); + mh->mh_flags &= ~MHF_FWHANG; +} + +static void +mwlTriggerPciCmd(struct mwl_hal_priv *mh) +{ + uint32_t dummy; + + bus_dmamap_sync(mh->mh_dmat, mh->mh_dmamap, BUS_DMASYNC_PREWRITE); + + WR4(mh, MACREG_REG_GEN_PTR, mh->mh_cmdaddr); + dummy = RD4(mh, MACREG_REG_INT_CODE); + + WR4(mh, MACREG_REG_INT_CODE, 0x00); + dummy = RD4(mh, MACREG_REG_INT_CODE); + + WR4(mh, MACREG_REG_H2A_INTERRUPT_EVENTS, MACREG_H2ARIC_BIT_DOOR_BELL); + dummy = RD4(mh, MACREG_REG_INT_CODE); +} + +static int +mwlWaitFor(struct mwl_hal_priv *mh, uint32_t val) +{ + int i; + + for (i = 0; i < FW_MAX_NUM_CHECKS; i++) { + DELAY(FW_CHECK_USECS); + if (RD4(mh, MACREG_REG_INT_CODE) == val) + return 1; + } + return 0; +} + +/* + * Firmware block xmit when talking to the boot-rom. + */ +static int +mwlSendBlock(struct mwl_hal_priv *mh, int bsize, const void *data, size_t dsize) +{ + mh->mh_cmdbuf[0] = htole16(HostCmd_CMD_CODE_DNLD); + mh->mh_cmdbuf[1] = htole16(bsize); + memcpy(&mh->mh_cmdbuf[4], data , dsize); + mwlTriggerPciCmd(mh); + /* XXX 2000 vs 200 */ + if (mwlWaitFor(mh, MACREG_INT_CODE_CMD_FINISHED)) { + WR4(mh, MACREG_REG_INT_CODE, 0); + return 1; + } + device_printf(mh->mh_dev, + "%s: timeout waiting for CMD_FINISHED, INT_CODE 0x%x\n", + __func__, RD4(mh, MACREG_REG_INT_CODE)); + return 0; +} + +/* + * Firmware block xmit when talking to the 1st-stage loader. + */ +static int +mwlSendBlock2(struct mwl_hal_priv *mh, const void *data, size_t dsize) +{ + memcpy(&mh->mh_cmdbuf[0], data, dsize); + mwlTriggerPciCmd(mh); + if (mwlWaitFor(mh, MACREG_INT_CODE_CMD_FINISHED)) { + WR4(mh, MACREG_REG_INT_CODE, 0); + return 1; + } + device_printf(mh->mh_dev, + "%s: timeout waiting for CMD_FINISHED, INT_CODE 0x%x\n", + __func__, RD4(mh, MACREG_REG_INT_CODE)); + return 0; +} + +static void +mwlPokeSdramController(struct mwl_hal_priv *mh, int SDRAMSIZE_Addr) +{ + /** Set up sdram controller for superflyv2 **/ + WR4(mh, 0x00006014, 0x33); + WR4(mh, 0x00006018, 0xa3a2632); + WR4(mh, 0x00006010, SDRAMSIZE_Addr); +} + +int +mwl_hal_fwload(struct mwl_hal *mh0, void *fwargs) +{ + struct mwl_hal_priv *mh = MWLPRIV(mh0); + const char *fwname = "mw88W8363fw"; + const char *fwbootname = "mwlboot"; + const struct firmware *fwboot = NULL; + const struct firmware *fw; + /* XXX get from firmware header */ + uint32_t FwReadySignature = HostCmd_SOFTAP_FWRDY_SIGNATURE; + uint32_t OpMode = HostCmd_SOFTAP_MODE; + const uint8_t *fp, *ep; + const uint8_t *fmdata; + uint32_t blocksize, nbytes, fmsize; + int i, error, ntries; + + fw = firmware_get(fwname); + if (fw == NULL) { + device_printf(mh->mh_dev, + "could not load firmware image %s\n", fwname); + return ENXIO; + } + fmdata = fw->data; + fmsize = fw->datasize; + if (fmsize < 4) { + device_printf(mh->mh_dev, "firmware image %s too small\n", + fwname); + error = ENXIO; + goto bad2; + } + if (fmdata[0] == 0x01 && fmdata[1] == 0x00 && + fmdata[2] == 0x00 && fmdata[3] == 0x00) { + /* + * 2-stage load, get the boot firmware. + */ + fwboot = firmware_get(fwbootname); + if (fwboot == NULL) { + device_printf(mh->mh_dev, + "could not load firmware image %s\n", fwbootname); + error = ENXIO; + goto bad2; + } + } else + fwboot = NULL; + + mwlFwReset(mh); + + WR4(mh, MACREG_REG_A2H_INTERRUPT_CLEAR_SEL, MACREG_A2HRIC_BIT_MASK); + WR4(mh, MACREG_REG_A2H_INTERRUPT_CAUSE, 0x00); + WR4(mh, MACREG_REG_A2H_INTERRUPT_MASK, 0x00); + WR4(mh, MACREG_REG_A2H_INTERRUPT_STATUS_MASK, MACREG_A2HRIC_BIT_MASK); + if (mh->mh_SDRAMSIZE_Addr != 0) { + /** Set up sdram controller for superflyv2 **/ + mwlPokeSdramController(mh, mh->mh_SDRAMSIZE_Addr); + } + device_printf(mh->mh_dev, "load %s firmware image (%u bytes)\n", + fwname, fmsize); + if (fwboot != NULL) { + /* + * Do 2-stage load. The 1st stage loader is setup + * with the bootrom loader then we load the real + * image using a different handshake. With this + * mechanism the firmware is segmented into chunks + * that have a CRC. If a chunk is incorrect we'll + * be told to retransmit. + */ + /* XXX assumes hlpimage fits in a block */ + /* NB: zero size block indicates download is finished */ + if (!mwlSendBlock(mh, fwboot->datasize, fwboot->data, fwboot->datasize) || + !mwlSendBlock(mh, 0, NULL, 0)) { + error = ETIMEDOUT; + goto bad; + } + DELAY(200*FW_CHECK_USECS); + if (mh->mh_SDRAMSIZE_Addr != 0) { + /** Set up sdram controller for superflyv2 **/ + mwlPokeSdramController(mh, mh->mh_SDRAMSIZE_Addr); + } + nbytes = ntries = 0; /* NB: silence compiler */ + for (fp = fmdata, ep = fp + fmsize; fp < ep; ) { + WR4(mh, MACREG_REG_INT_CODE, 0); + blocksize = RD4(mh, MACREG_REG_SCRATCH); + if (blocksize == 0) /* download complete */ + break; + if (blocksize > 0x00000c00) { + error = EINVAL; + goto bad; + } + if ((blocksize & 0x1) == 0) { + /* block successfully downloaded, advance */ + fp += nbytes; + ntries = 0; + } else { + if (++ntries > 2) { + /* + * Guard against f/w telling us to + * retry infinitely. + */ + error = ELOOP; + goto bad; + } + /* clear NAK bit/flag */ + blocksize &= ~0x1; + } + if (blocksize > ep - fp) { + /* XXX this should not happen, what to do? */ + blocksize = ep - fp; + } + nbytes = blocksize; + if (!mwlSendBlock2(mh, fp, nbytes)) { + error = ETIMEDOUT; + goto bad; + } + } + } else { + for (fp = fmdata, ep = fp + fmsize; fp < ep;) { + nbytes = ep - fp; + if (nbytes > FW_DOWNLOAD_BLOCK_SIZE) + nbytes = FW_DOWNLOAD_BLOCK_SIZE; + if (!mwlSendBlock(mh, FW_DOWNLOAD_BLOCK_SIZE, fp, nbytes)) { + error = EIO; + goto bad; + } + fp += nbytes; + } + } + /* done with firmware... */ + if (fwboot != NULL) + firmware_put(fwboot, FIRMWARE_UNLOAD); + firmware_put(fw, FIRMWARE_UNLOAD); + /* + * Wait for firmware to startup; we monitor the + * INT_CODE register waiting for a signature to + * written back indicating it's ready to go. + */ + mh->mh_cmdbuf[1] = 0; + /* + * XXX WAR for mfg fw download + */ + if (OpMode != HostCmd_STA_MODE) + mwlTriggerPciCmd(mh); + for (i = 0; i < FW_MAX_NUM_CHECKS; i++) { + WR4(mh, MACREG_REG_GEN_PTR, OpMode); + DELAY(FW_CHECK_USECS); + if (RD4(mh, MACREG_REG_INT_CODE) == FwReadySignature) { + WR4(mh, MACREG_REG_INT_CODE, 0x00); + return mwlResetHalState(mh); + } + } + return ETIMEDOUT; +bad: + mwlFwReset(mh); +bad2: + /* done with firmware... */ + if (fwboot != NULL) + firmware_put(fwboot, FIRMWARE_UNLOAD); + firmware_put(fw, FIRMWARE_UNLOAD); + return error; +} + +#ifdef MWLHAL_DEBUG +static const char * +mwlcmdname(int cmd) +{ + static char buf[12]; +#define CMD(x) case HostCmd_CMD_##x: return #x + switch (cmd) { + CMD(CODE_DNLD); + CMD(GET_HW_SPEC); + CMD(SET_HW_SPEC); + CMD(MAC_MULTICAST_ADR); + CMD(802_11_GET_STAT); + CMD(MAC_REG_ACCESS); + CMD(BBP_REG_ACCESS); + CMD(RF_REG_ACCESS); + CMD(802_11_RADIO_CONTROL); + CMD(802_11_RF_TX_POWER); + CMD(802_11_RF_ANTENNA); + CMD(SET_BEACON); + CMD(SET_RF_CHANNEL); + CMD(SET_AID); + CMD(SET_INFRA_MODE); + CMD(SET_G_PROTECT_FLAG); + CMD(802_11_RTS_THSD); + CMD(802_11_SET_SLOT); + CMD(SET_EDCA_PARAMS); + CMD(802_11H_DETECT_RADAR); + CMD(SET_WMM_MODE); + CMD(HT_GUARD_INTERVAL); + CMD(SET_FIXED_RATE); + CMD(SET_LINKADAPT_CS_MODE); + CMD(SET_MAC_ADDR); + CMD(SET_RATE_ADAPT_MODE); + CMD(BSS_START); + CMD(SET_NEW_STN); + CMD(SET_KEEP_ALIVE); + CMD(SET_APMODE); + CMD(SET_SWITCH_CHANNEL); + CMD(UPDATE_ENCRYPTION); + CMD(BASTREAM); + CMD(SET_RIFS); + CMD(SET_N_PROTECT_FLAG); + CMD(SET_N_PROTECT_OPMODE); + CMD(SET_OPTIMIZATION_LEVEL); + CMD(GET_CALTABLE); + CMD(SET_MIMOPSHT); + CMD(GET_BEACON); + CMD(SET_REGION_CODE); + CMD(SET_POWERSAVESTATION); + CMD(SET_TIM); + CMD(GET_TIM); + CMD(GET_SEQNO); + } + snprintf(buf, sizeof(buf), "0x%x", cmd); + return buf; +#undef CMD +} + +static void +dumpresult(struct mwl_hal_priv *mh, int showresult) +{ + const FWCmdHdr *h = (const FWCmdHdr *)mh->mh_cmdbuf; + const uint8_t *cp; + int len, i; + + len = le16toh(h->Length); +#ifdef MWL_MBSS_SUPPORT + device_printf(mh->mh_dev, "Cmd %s Length %d SeqNum %d MacId %d", + mwlcmdname(le16toh(h->Cmd) &~ 0x8000), len, h->SeqNum, h->MacId); +#else + device_printf(mh->mh_dev, "Cmd %s Length %d SeqNum %d", + mwlcmdname(le16toh(h->Cmd) &~ 0x8000), len, le16toh(h->SeqNum)); +#endif + if (showresult) { + const char *results[] = + { "OK", "ERROR", "NOT_SUPPORT", "PENDING", "BUSY", + "PARTIAL_DATA" }; + int result = le16toh(h->Result); + + if (result <= HostCmd_RESULT_PARTIAL_DATA) + printf(" Result %s", results[result]); + else + printf(" Result %d", result); + } + cp = (const uint8_t *)h; + for (i = 0; i < len; i++) { + if ((i % 16) == 0) + printf("\n%02x", cp[i]); + else + printf(" %02x", cp[i]); + } + printf("\n"); +} +#endif /* MWLHAL_DEBUG */ diff --git a/sys/dev/mwl/mwlhal.h b/sys/dev/mwl/mwlhal.h new file mode 100644 index 0000000..c82b427 --- /dev/null +++ b/sys/dev/mwl/mwlhal.h @@ -0,0 +1,666 @@ +/*- + * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2007-2009 Marvell Semiconductor, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + * + * $FreeBSD$ + */ + +#ifndef _MWL_HAL_H_ +#define _MWL_HAL_H_ +/* + * Hardware Access Layer for Marvell Wireless Devices. + */ + +#define MWL_MBSS_SUPPORT /* enable multi-bss support */ + +/* + * Define total number of TX queues in the shared memory. + * This count includes the EDCA queues, Block Ack queues, and HCCA queues + * In addition to this, there could be a management packet queue some + * time in the future + */ +#define MWL_NUM_EDCA_QUEUES 4 +#define MWL_NUM_HCCA_QUEUES 0 +#define MWL_NUM_BA_QUEUES 0 +#define MWL_NUM_MGMT_QUEUES 0 +#define MWL_NUM_TX_QUEUES \ + (MWL_NUM_EDCA_QUEUES + MWL_NUM_HCCA_QUEUES + MWL_NUM_BA_QUEUES + \ + MWL_NUM_MGMT_QUEUES) +#define MWL_MAX_RXWCB_QUEUES 1 + +#define MWL_MAX_SUPPORTED_RATES 12 +#define MWL_MAX_SUPPORTED_MCS 32 + +typedef enum { + MWL_HAL_OK +} MWL_HAL_STATUS; + +/* + * Transmit queue assignment. + */ +enum { + MWL_WME_AC_BK = 0, /* background access category */ + MWL_WME_AC_BE = 1, /* best effort access category*/ + MWL_WME_AC_VI = 2, /* video access category */ + MWL_WME_AC_VO = 3, /* voice access category */ +}; + +struct device; + +struct mwl_hal { + bus_space_handle_t mh_ioh; /* BAR 1 copied from softc */ + bus_space_tag_t mh_iot; + uint32_t mh_imask; /* interrupt mask */ + /* remainder is opaque to driver */ +}; +struct mwl_hal *mwl_hal_attach(struct device *dev, uint16_t devid, + bus_space_handle_t ioh, bus_space_tag_t iot, bus_dma_tag_t tag); +void mwl_hal_detach(struct mwl_hal *); + +/* + * Query whether multi-bss support is available/enabled. + */ +int mwl_hal_ismbsscapable(struct mwl_hal *); + +typedef enum { + MWL_HAL_AP, + MWL_HAL_STA, /* infrastructure mode */ + MWL_HAL_IBSS /* ibss/adhoc mode */ +} MWL_HAL_BSSTYPE; +struct mwl_hal_vap; + +struct mwl_hal_vap *mwl_hal_newvap(struct mwl_hal *, MWL_HAL_BSSTYPE, + const uint8_t mac[6]); +void mwl_hal_delvap(struct mwl_hal_vap *); + +enum { + MWL_HAL_DEBUG_SENDCMD = 0x00000001, + MWL_HAL_DEBUG_CMDDONE = 0x00000002, + MWL_HAL_DEBUG_IGNHANG = 0x00000004, +}; +void mwl_hal_setdebug(struct mwl_hal *, int); +int mwl_hal_getdebug(struct mwl_hal *); + +typedef struct { + uint16_t freqLow, freqHigh; + int nchannels; + struct mwl_hal_channel { + uint16_t freq; /* channel center */ + uint8_t ieee; /* channel number */ + int8_t maxTxPow; /* max tx power (dBm) */ + uint8_t targetPowers[4];/* target powers (dBm) */ +#define MWL_HAL_MAXCHAN 40 + } channels[MWL_HAL_MAXCHAN]; +} MWL_HAL_CHANNELINFO; +int mwl_hal_getchannelinfo(struct mwl_hal *, int band, int chw, + const MWL_HAL_CHANNELINFO **); + +/* + * Return the current ISR setting and clear the cause. + */ +static __inline void +mwl_hal_getisr(struct mwl_hal *mh, uint32_t *status) +{ +#define MACREG_REG_A2H_INTERRUPT_CAUSE 0x00000C30 // (From ARM to host) +#define MACREG_REG_INT_CODE 0x00000C14 + uint32_t cause; + + cause = bus_space_read_4(mh->mh_iot, mh->mh_ioh, + MACREG_REG_A2H_INTERRUPT_CAUSE); + if (cause == 0xffffffff) { /* card removed */ + cause = 0; + } else if (cause != 0) { + /* clear cause bits */ + bus_space_write_4(mh->mh_iot, mh->mh_ioh, + MACREG_REG_A2H_INTERRUPT_CAUSE, cause &~ mh->mh_imask); + (void) bus_space_read_4(mh->mh_iot, mh->mh_ioh, + MACREG_REG_INT_CODE); + cause &= mh->mh_imask; + } + *status = cause; +#undef MACREG_REG_INT_CODE +#undef MACREG_REG_A2H_INTERRUPT_CAUSE +} + +void mwl_hal_intrset(struct mwl_hal *mh, uint32_t mask); + +/* + * Kick the firmware to tell it there are new tx descriptors + * for processing. The driver says what h/w q has work in + * case the f/w ever gets smarter. + */ +static __inline void +mwl_hal_txstart(struct mwl_hal *mh, int qnum) +{ +#define MACREG_REG_H2A_INTERRUPT_EVENTS 0x00000C18 // (From host to ARM) +#define MACREG_H2ARIC_BIT_PPA_READY 0x00000001 // bit 0 +#define MACREG_REG_INT_CODE 0x00000C14 + + bus_space_write_4(mh->mh_iot, mh->mh_ioh, + MACREG_REG_H2A_INTERRUPT_EVENTS, MACREG_H2ARIC_BIT_PPA_READY); + (void) bus_space_read_4(mh->mh_iot, mh->mh_ioh, MACREG_REG_INT_CODE); +#undef MACREG_REG_INT_CODE +#undef MACREG_H2ARIC_BIT_PPA_READY +#undef MACREG_REG_H2A_INTERRUPT_EVENTS +} + +void mwl_hal_cmddone(struct mwl_hal *mh); + +typedef struct { + uint32_t FreqBand : 6, +#define MWL_FREQ_BAND_2DOT4GHZ 0x1 +#define MWL_FREQ_BAND_5GHZ 0x4 + ChnlWidth: 5, +#define MWL_CH_10_MHz_WIDTH 0x1 +#define MWL_CH_20_MHz_WIDTH 0x2 +#define MWL_CH_40_MHz_WIDTH 0x4 + ExtChnlOffset: 2, +#define MWL_EXT_CH_NONE 0x0 +#define MWL_EXT_CH_ABOVE_CTRL_CH 0x1 +#define MWL_EXT_CH_BELOW_CTRL_CH 0x3 + : 19; /* reserved */ +} MWL_HAL_CHANNEL_FLAGS; + +typedef struct { + uint32_t channel; + MWL_HAL_CHANNEL_FLAGS channelFlags; +} MWL_HAL_CHANNEL; + +/* + * Get Hardware/Firmware capabilities. + */ +struct mwl_hal_hwspec { + uint8_t hwVersion; /* version of the HW */ + uint8_t hostInterface; /* host interface */ + uint16_t maxNumWCB; /* max # of WCB FW handles */ + uint16_t maxNumMCAddr; /* max # of mcast addresses FW handles*/ + uint16_t maxNumTxWcb; /* max # of tx descs per WCB */ + uint8_t macAddr[6]; /* MAC address programmed in HW */ + uint16_t regionCode; /* EEPROM region code */ + uint16_t numAntennas; /* Number of antenna used */ + uint32_t fwReleaseNumber; /* firmware release number */ + uint32_t wcbBase0; + uint32_t rxDescRead; + uint32_t rxDescWrite; + uint32_t ulFwAwakeCookie; + uint32_t wcbBase[4]; +}; +int mwl_hal_gethwspecs(struct mwl_hal *mh, struct mwl_hal_hwspec *); + +/* + * Supply tx/rx dma-related settings to the firmware. + */ +struct mwl_hal_txrxdma { + uint32_t maxNumWCB; /* max # of WCB FW handles */ + uint32_t maxNumTxWcb; /* max # of tx descs per WCB */ + uint32_t rxDescRead; + uint32_t rxDescWrite; + uint32_t wcbBase[4]; +}; +int mwl_hal_sethwdma(struct mwl_hal *mh, const struct mwl_hal_txrxdma *); + +/* + * Get Hardware Statistics. + * + * Items marked with ! are deprecated and not ever updated. In + * some cases this is because work has been moved to the host (e.g. + * rx defragmentation). + */ +struct mwl_hal_hwstats { + uint32_t TxRetrySuccesses; /* tx success w/ 1 retry */ + uint32_t TxMultipleRetrySuccesses;/* tx success w/ >1 retry */ + uint32_t TxFailures; /* tx fail due to no ACK */ + uint32_t RTSSuccesses; /* CTS rx'd for RTS */ + uint32_t RTSFailures; /* CTS not rx'd for RTS */ + uint32_t AckFailures; /* same as TxFailures */ + uint32_t RxDuplicateFrames; /* rx discard for dup seqno */ + uint32_t FCSErrorCount; /* rx discard for bad FCS */ + uint32_t TxWatchDogTimeouts; /* MAC tx hang (f/w recovery) */ + uint32_t RxOverflows; /* no f/w buffer for rx data */ + uint32_t RxFragErrors; /* !rx fail due to defrag */ + uint32_t RxMemErrors; /* out of mem or desc corrupted + in some way */ + uint32_t RxPointerErrors; /* MAC internal ptr problem */ + uint32_t TxUnderflows; /* !tx underflow on dma */ + uint32_t TxDone; /* MAC tx ops completed + (possibly w/ error) */ + uint32_t TxDoneBufTryPut; /* ! */ + uint32_t TxDoneBufPut; /* same as TxDone */ + uint32_t Wait4TxBuf; /* !no f/w buf avail when + supplied a tx descriptor */ + uint32_t TxAttempts; /* tx descriptors processed */ + uint32_t TxSuccesses; /* tx attempts successful */ + uint32_t TxFragments; /* tx with fragmentation */ + uint32_t TxMulticasts; /* tx multicast frames */ + uint32_t RxNonCtlPkts; /* rx non-control frames */ + uint32_t RxMulticasts; /* rx multicast frames */ + uint32_t RxUndecryptableFrames; /* rx failed due to crypto */ + uint32_t RxICVErrors; /* rx failed due to ICV check */ + uint32_t RxExcludedFrames; /* rx discarded, e.g. bssid */ +}; +int mwl_hal_gethwstats(struct mwl_hal *mh, struct mwl_hal_hwstats *); + +/* + * Set HT Guard Interval. + * + * GIType = 0: enable long and short GI + * GIType = 1: enable short GI + * GIType = 2: enable long GI + */ +int mwl_hal_sethtgi(struct mwl_hal_vap *, int GIType); + +/* + * Set Radio Configuration. + * + * onoff != 0 turns radio on; otherwise off. + * if radio is enabled, the preamble is set too. + */ +typedef enum { + WL_LONG_PREAMBLE = 1, + WL_SHORT_PREAMBLE = 3, + WL_AUTO_PREAMBLE = 5, +} MWL_HAL_PREAMBLE; +int mwl_hal_setradio(struct mwl_hal *mh, int onoff, MWL_HAL_PREAMBLE preamble); + +/* + * Set Antenna Configuration (legacy operation). + * + * The RX antenna can be selected using the the bitmask + * ant (bit 0 = antenna 1, bit 1 = antenna 2, etc.) + * (diversity?XXX) + */ +typedef enum { + WL_ANTENNATYPE_RX = 1, + WL_ANTENNATYPE_TX = 2, +} MWL_HAL_ANTENNA; +int mwl_hal_setantenna(struct mwl_hal *mh, MWL_HAL_ANTENNA dirSet, int ant); + +/* + * Set the threshold for using RTS on TX. + */ +int mwl_hal_setrtsthreshold(struct mwl_hal_vap *, int threshold); + +/* + * Set the adapter to operate in infrastructure mode. + */ +int mwl_hal_setinframode(struct mwl_hal_vap *); + +/* + * Set Radar Detection Configuration. + */ +typedef enum { + DR_DFS_DISABLE = 0, + DR_CHK_CHANNEL_AVAILABLE_START = 1, + DR_CHK_CHANNEL_AVAILABLE_STOP = 2, + DR_IN_SERVICE_MONITOR_START = 3 +} MWL_HAL_RADAR; +int mwl_hal_setradardetection(struct mwl_hal *mh, MWL_HAL_RADAR action); +/* + * Set the region code that selects the radar bin'ing agorithm. + */ +int mwl_hal_setregioncode(struct mwl_hal *mh, int regionCode); + +/* + * Initiate an 802.11h-based channel switch. The CSA ie + * is included in the next beacon(s) using the specified + * information and the firmware counts down until switch + * time after which it notifies the driver by delivering + * an interrupt with MACREG_A2HRIC_BIT_CHAN_SWITCH set in + * the cause register. + */ +int mwl_hal_setchannelswitchie(struct mwl_hal *, + const MWL_HAL_CHANNEL *nextchan, uint32_t mode, uint32_t count); + +/* + * Set regdomain code (IEEE SKU). + */ +enum { + DOMAIN_CODE_FCC = 0x10, /* USA */ + DOMAIN_CODE_IC = 0x20, /* Canda */ + DOMAIN_CODE_ETSI = 0x30, /* Europe */ + DOMAIN_CODE_SPAIN = 0x31, /* Spain */ + DOMAIN_CODE_FRANCE = 0x32, /* France */ + DOMAIN_CODE_ETSI_131 = 0x130,/* ETSI w/ 1.3.1 radar type */ + DOMAIN_CODE_MKK = 0x40, /* Japan */ + DOMAIN_CODE_MKK2 = 0x41, /* Japan w/ 10MHz chan spacing */ + DOMAIN_CODE_DGT = 0x80, /* Taiwan */ + DOMAIN_CODE_AUS = 0x81, /* Australia */ +}; + +/* + * Transmit rate control. Rate codes with bit 0x80 set are + * interpreted as MCS codes (this limits us to 0-127). The + * transmit rate can be set to a single fixed rate or can + * be configured to start at an initial rate and drop based + * on retry counts. + */ +typedef enum { + RATE_AUTO = 0, /* rate selected by firmware */ + RATE_FIXED = 2, /* rate fixed */ + RATE_FIXED_DROP = 1, /* rate starts fixed but may drop */ +} MWL_HAL_TXRATE_HANDLING; + +typedef struct { + uint8_t McastRate; /* rate for multicast frames */ +#define RATE_MCS 0x80 /* rate is an MCS index */ + uint8_t MgtRate; /* rate for management frames */ + struct { + uint8_t TryCount; /* try this many times */ + uint8_t Rate; /* use this tx rate */ + } RateSeries[4]; /* rate series */ +} MWL_HAL_TXRATE; + +int mwl_hal_settxrate(struct mwl_hal_vap *, + MWL_HAL_TXRATE_HANDLING handling, const MWL_HAL_TXRATE *rate); +/* NB: hack for setting rates while scanning */ +int mwl_hal_settxrate_auto(struct mwl_hal *, const MWL_HAL_TXRATE *rate); + +/* + * Set the Slot Time Configuration. + * NB: usecs must either be 9 or 20 for now. + */ +int mwl_hal_setslottime(struct mwl_hal *mh, int usecs); + +/* + * Adjust current transmit power settings according to powerLevel. + * This translates to low/medium/high use of the current tx power rate tables. + */ +int mwl_hal_adjusttxpower(struct mwl_hal *, uint32_t powerLevel); +/* + * Set the transmit power for the specified channel; the power + * is taken from the calibration data and capped according to + * the specified max tx power (in dBm). + */ +int mwl_hal_settxpower(struct mwl_hal *, const MWL_HAL_CHANNEL *, + uint8_t maxtxpow); + +/* + * Set the Multicast Address Filter. + * A packed array addresses is specified. + */ +#define MWL_HAL_MCAST_MAX 32 +int mwl_hal_setmcast(struct mwl_hal *mh, int nmc, const uint8_t macs[]); + +/* + * Crypto Configuration. + */ +typedef struct { + uint16_t pad; + uint16_t keyTypeId; +#define KEY_TYPE_ID_WEP 0 +#define KEY_TYPE_ID_TKIP 1 +#define KEY_TYPE_ID_AES 2 /* AES-CCMP */ + uint32_t keyFlags; +#define KEY_FLAG_INUSE 0x00000001 /* indicate key is in use */ +#define KEY_FLAG_RXGROUPKEY 0x00000002 /* Group key for RX only */ +#define KEY_FLAG_TXGROUPKEY 0x00000004 /* Group key for TX */ +#define KEY_FLAG_PAIRWISE 0x00000008 /* pairwise */ +#define KEY_FLAG_RXONLY 0x00000010 /* only used for RX */ +#define KEY_FLAG_AUTHENTICATOR 0x00000020 /* Key is for Authenticator */ +#define KEY_FLAG_TSC_VALID 0x00000040 /* Sequence counters valid */ +#define KEY_FLAG_WEP_TXKEY 0x01000000 /* Tx key for WEP */ +#define KEY_FLAG_MICKEY_VALID 0x02000000 /* Tx/Rx MIC keys are valid */ + uint32_t keyIndex; /* for WEP only; actual key index */ + uint16_t keyLen; /* key size in bytes */ + union { /* key material, keyLen gives size */ + uint8_t wep[16]; /* enough for 128 bits */ + uint8_t aes[16]; + struct { + /* NB: group or pairwise key is determined by keyFlags */ + uint8_t keyMaterial[16]; + uint8_t txMic[8]; + uint8_t rxMic[8]; + struct { + uint16_t low; + uint32_t high; + } rsc; + struct { + uint16_t low; + uint32_t high; + } tsc; + } __packed tkip; + }__packed key; +} __packed MWL_HAL_KEYVAL; + +/* + * Plumb a unicast/group key. The mac address identifies + * the station, use the broadcast address for group keys. + */ +int mwl_hal_keyset(struct mwl_hal_vap *, const MWL_HAL_KEYVAL *kv, + const uint8_t mac[6]); + +/* + * Plumb a unicast/group key. The mac address identifies + * the station, use the broadcast address for group keys. + */ +int mwl_hal_keyreset(struct mwl_hal_vap *, const MWL_HAL_KEYVAL *kv, + const uint8_t mac[6]); + +/* + * Set the MAC address. + */ +int mwl_hal_setmac(struct mwl_hal_vap *, const uint8_t addr[6]); + +/* + * Set the beacon frame contents. The firmware will modify the + * frame only to add CSA and WME ie's and to fill in dynamic fields + * such as the sequence #.. + */ +int mwl_hal_setbeacon(struct mwl_hal_vap *, const void *, size_t); + +/* + * Handle power save operation for AP operation when offloaded to + * the host (SET_HW_SPEC_HOST_POWERSAVE). mwl_hal_setbss_powersave + * informs the firmware whether 1+ associated stations are in power + * save mode (it will then buffer mcast traffic). mwl_hal_setsta_powersave + * specifies a change in power save state for an associated station. + */ +int mwl_hal_setpowersave_bss(struct mwl_hal_vap *, uint8_t nsta); +int mwl_hal_setpowersave_sta(struct mwl_hal_vap *, uint16_t aid, int ena); + +/* + * Set Association Configuration for station operation. + */ +int mwl_hal_setassocid(struct mwl_hal_vap *, const uint8_t bssId[6], + uint16_t assocId); + +/* + * Set the current channel. + */ +int mwl_hal_setchannel(struct mwl_hal *mh, const MWL_HAL_CHANNEL *c); + +/* + * A-MPDU Block Ack (BA) stream support. There are several + * streams that the driver must multiplex. Once assigned + * to a station the driver queues frames to a corresponding + * transmit queue and the firmware handles all the work. + * + * XXX no way to find out how many streams are supported + */ +typedef struct { + void *data[2]; /* opaque data */ + int txq; +} MWL_HAL_BASTREAM; + +const MWL_HAL_BASTREAM *mwl_hal_bastream_alloc(struct mwl_hal *mh, + int ba_type, const uint8_t Macaddr[16], uint8_t Tid, + uint8_t ParamInfo, void *, void *); +const MWL_HAL_BASTREAM *mwl_hal_bastream_lookup(struct mwl_hal *mh, int s); +int mwl_hal_bastream_create(struct mwl_hal *mh, const MWL_HAL_BASTREAM *, + int BarThrs, int WindowSize, uint16_t seqno); +int mwl_hal_bastream_destroy(struct mwl_hal *mh, const MWL_HAL_BASTREAM *); +int mwl_hal_bastream_get_seqno(struct mwl_hal *mh, const MWL_HAL_BASTREAM *, + uint16_t *pseqno); +int mwl_hal_getwatchdogbitmap(struct mwl_hal *mh, uint8_t bitmap[1]); +/* for sysctl hookup for debugging */ +void mwl_hal_setbastreams(struct mwl_hal *mh, int mask); +int mwl_hal_getbastreams(struct mwl_hal *mh); + +/* + * Inform the firmware of a new association station. + * The address is the MAC address of the peer station. + * The AID is supplied sans the 0xc000 bits. The station + * ID is defined by the caller. The peer information must + * be supplied. + * + * NB: All values are in host byte order; any byte swapping + * is handled by the hal. + */ +typedef struct { + uint32_t LegacyRateBitMap; + uint32_t HTRateBitMap; + uint16_t CapInfo; + uint16_t HTCapabilitiesInfo; + uint8_t MacHTParamInfo; + uint8_t Rev; + struct { + uint8_t ControlChan; + uint8_t AddChan; + uint8_t OpMode; + uint8_t stbc; + } __packed AddHtInfo; +} __packed MWL_HAL_PEERINFO; +int mwl_hal_newstation(struct mwl_hal_vap *, const uint8_t addr[6], + uint16_t aid, uint16_t sid, const MWL_HAL_PEERINFO *, + int isQosSta, int wmeInfo); +int mwl_hal_delstation(struct mwl_hal_vap *, const uint8_t addr[6]); + +/* + * Prod the firmware to age packets on station power + * save queues and reap frames on the tx aggregation q's. + */ +int mwl_hal_setkeepalive(struct mwl_hal *mh); + +typedef enum { + AP_MODE_B_ONLY = 1, + AP_MODE_G_ONLY = 2, + AP_MODE_MIXED = 3, + AP_MODE_N_ONLY = 4, + AP_MODE_BandN = 5, + AP_MODE_GandN = 6, + AP_MODE_BandGandN = 7, + AP_MODE_A_ONLY = 8, + AP_MODE_AandG = 10, + AP_MODE_AandN = 12, +} MWL_HAL_APMODE; +int mwl_hal_setapmode(struct mwl_hal_vap *, MWL_HAL_APMODE); + +/* + * Enable/disable firmware operation. mwl_hal_start is + * also used to sync state updates, e.g. beacon frame + * reconstruction after content changes. + */ +int mwl_hal_stop(struct mwl_hal_vap *); +int mwl_hal_start(struct mwl_hal_vap *); + +/* + * Enable/disable 11g protection use. This call specifies + * the ERP information element flags to use. + */ +int mwl_hal_setgprot(struct mwl_hal *, int); + +/* + * Enable/disable WMM support. + */ +int mwl_hal_setwmm(struct mwl_hal *mh, int onoff); + +/* + * Configure WMM EDCA parameters for the specified h/w ring. + */ +int mwl_hal_setedcaparams(struct mwl_hal *mh, uint8_t qnum, + uint32_t CWmin, uint32_t CWmax, uint8_t AIFSN, uint16_t TXOPLimit); + +/* + * Configure rate adaptation for indooor/outdoor operation. + * XXX wtf? + */ +int mwl_hal_setrateadaptmode(struct mwl_hal *mh, uint16_t mode); + +typedef enum { + CSMODE_CONSERVATIVE = 0, + CSMODE_AGGRESSIVE = 1, + CSMODE_AUTO_ENA = 2, + CSMODE_AUTO_DIS = 3, +} MWL_HAL_CSMODE; +int mwl_hal_setcsmode(struct mwl_hal *mh, MWL_HAL_CSMODE csmode); + +/* + * Configure 11n protection on/off. + */ +typedef enum { + HTPROTECT_NONE = 0, /* disable */ + HTPROTECT_OPT = 1, /* optional */ + HTPROTECT_HT20 = 2, /* protect only HT20 */ + HTPROTECT_HT2040 = 3, /* protect HT20/40 */ + HTPROTECT_AUTO = 4, /* automatic */ +} MWL_HAL_HTPROTECT; +int mwl_hal_setnprot(struct mwl_hal_vap *, MWL_HAL_HTPROTECT mode); +/* + * Configure 11n protection mechanism for when protection is enabled. + */ +int mwl_hal_setnprotmode(struct mwl_hal_vap *, uint8_t mode); + +/* + * Enable/disable Marvell "turbo mode"". + */ +int mwl_hal_setoptimizationlevel(struct mwl_hal *mh, int onoff); + +/* + * Set MIMO Power Save handling for a station; the enable and mode + * values come directly from the Action frame. + */ +int mwl_hal_setmimops(struct mwl_hal *mh, const uint8_t addr[6], + uint8_t enable, uint8_t mode); + +/* + * Retrieve the region/country code from the EEPROM. + */ +int mwl_hal_getregioncode(struct mwl_hal *mh, uint8_t *countryCode); +int mwl_hal_GetBeacon(struct mwl_hal *mh, uint8_t *pBcn, uint16_t *pLen); +int mwl_hal_SetRifs(struct mwl_hal *mh, uint8_t QNum); + +/* + * Set/get promiscuous mode. + */ +int mwl_hal_setpromisc(struct mwl_hal *, int ena); +int mwl_hal_getpromisc(struct mwl_hal *); + +/* + * Diagnostic interface. This is an open-ended interface that + * is opaque to applications. Diagnostic programs use this to + * retrieve internal data structures, etc. There is no guarantee + * that calling conventions for calls other than MWL_DIAG_REVS + * are stable between HAL releases; a diagnostic application must + * use the HAL revision information to deal with ABI/API differences. + */ +int mwl_hal_getdiagstate(struct mwl_hal *mh, int request, + const void *args, uint32_t argsize, + void **result, uint32_t *resultsize); + +int mwl_hal_fwload(struct mwl_hal *mh, void *fwargs); +#endif /* _MWL_HAL_H_ */ diff --git a/sys/dev/mwl/mwlreg.h b/sys/dev/mwl/mwlreg.h new file mode 100644 index 0000000..17ebd61 --- /dev/null +++ b/sys/dev/mwl/mwlreg.h @@ -0,0 +1,1352 @@ +/*- + * Copyright (c) 2007-2009 Sam Leffler, Errno Consulting + * Copyright (c) 2007-2009 Marvell Semiconductor, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any + * redistribution must be conditioned upon including a substantially + * similar Disclaimer requirement for further binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGES. + * + * $FreeBSD$ + */ + +/* + * Definitions for the Marvell Wireless LAN controller Hardware Access Layer. + */ +#ifndef _MWL_HALREG_H_ +#define _MWL_HALREG_H_ + +#define MWL_ANT_INFO_SUPPORT /* per-antenna data in rx descriptor */ + +#define MACREG_REG_TSF_LOW 0xa600 /* TSF lo */ +#define MACREG_REG_TSF_HIGH 0xa604 /* TSF hi */ +#define MACREG_REG_CHIP_REV 0xa814 /* chip rev */ + +// Map to 0x80000000 (Bus control) on BAR0 +#define MACREG_REG_H2A_INTERRUPT_EVENTS 0x00000C18 // (From host to ARM) +#define MACREG_REG_H2A_INTERRUPT_CAUSE 0x00000C1C // (From host to ARM) +#define MACREG_REG_H2A_INTERRUPT_MASK 0x00000C20 // (From host to ARM) +#define MACREG_REG_H2A_INTERRUPT_CLEAR_SEL 0x00000C24 // (From host to ARM) +#define MACREG_REG_H2A_INTERRUPT_STATUS_MASK 0x00000C28 // (From host to ARM) + +#define MACREG_REG_A2H_INTERRUPT_EVENTS 0x00000C2C // (From ARM to host) +#define MACREG_REG_A2H_INTERRUPT_CAUSE 0x00000C30 // (From ARM to host) +#define MACREG_REG_A2H_INTERRUPT_MASK 0x00000C34 // (From ARM to host) +#define MACREG_REG_A2H_INTERRUPT_CLEAR_SEL 0x00000C38 // (From ARM to host) +#define MACREG_REG_A2H_INTERRUPT_STATUS_MASK 0x00000C3C // (From ARM to host) + + +// Map to 0x80000000 on BAR1 +#define MACREG_REG_GEN_PTR 0x00000C10 +#define MACREG_REG_INT_CODE 0x00000C14 +#define MACREG_REG_SCRATCH 0x00000C40 +#define MACREG_REG_FW_PRESENT 0x0000BFFC + +#define MACREG_REG_PROMISCUOUS 0xA300 + +// Bit definitio for MACREG_REG_A2H_INTERRUPT_CAUSE (A2HRIC) +#define MACREG_A2HRIC_BIT_TX_DONE 0x00000001 // bit 0 +#define MACREG_A2HRIC_BIT_RX_RDY 0x00000002 // bit 1 +#define MACREG_A2HRIC_BIT_OPC_DONE 0x00000004 // bit 2 +#define MACREG_A2HRIC_BIT_MAC_EVENT 0x00000008 // bit 3 +#define MACREG_A2HRIC_BIT_RX_PROBLEM 0x00000010 // bit 4 + +#define MACREG_A2HRIC_BIT_RADIO_OFF 0x00000020 // bit 5 +#define MACREG_A2HRIC_BIT_RADIO_ON 0x00000040 // bit 6 + +#define MACREG_A2HRIC_BIT_RADAR_DETECT 0x00000080 // bit 7 + +#define MACREG_A2HRIC_BIT_ICV_ERROR 0x00000100 // bit 8 +#define MACREG_A2HRIC_BIT_MIC_ERROR 0x00000200 // bit 9 +#define MACREG_A2HRIC_BIT_QUEUE_EMPTY 0x00004000 +#define MACREG_A2HRIC_BIT_QUEUE_FULL 0x00000800 +#define MACREG_A2HRIC_BIT_CHAN_SWITCH 0x00001000 +#define MACREG_A2HRIC_BIT_TX_WATCHDOG 0x00002000 +#define MACREG_A2HRIC_BIT_BA_WATCHDOG 0x00000400 +#define ISR_SRC_BITS ((MACREG_A2HRIC_BIT_RX_RDY) | \ + (MACREG_A2HRIC_BIT_TX_DONE) | \ + (MACREG_A2HRIC_BIT_OPC_DONE) | \ + (MACREG_A2HRIC_BIT_MAC_EVENT)| \ + (MACREG_A2HRIC_BIT_MIC_ERROR)| \ + (MACREG_A2HRIC_BIT_ICV_ERROR)| \ + (MACREG_A2HRIC_BIT_RADAR_DETECT)| \ + (MACREG_A2HRIC_BIT_CHAN_SWITCH)| \ + (MACREG_A2HRIC_BIT_TX_WATCHDOG)| \ + (MACREG_A2HRIC_BIT_QUEUE_EMPTY)| \ + (MACREG_A2HRIC_BIT_BA_WATCHDOG)) + +#define MACREG_A2HRIC_BIT_MASK ISR_SRC_BITS + + +// Bit definitio for MACREG_REG_H2A_INTERRUPT_CAUSE (H2ARIC) +#define MACREG_H2ARIC_BIT_PPA_READY 0x00000001 // bit 0 +#define MACREG_H2ARIC_BIT_DOOR_BELL 0x00000002 // bit 1 +#define ISR_RESET (1<<15) + +// INT code register event definition +#define MACREG_INT_CODE_CMD_FINISHED 0x00000005 + +/* + * Host/Firmware Interface definitions. + */ + +/** + * Define total number of TX queues in the shared memory. + * This count includes the EDCA queues, Block Ack queues, and HCCA queues + * In addition to this, there could be a management packet queue some + * time in the future + */ +#define NUM_EDCA_QUEUES 4 +#define NUM_HCCA_QUEUES 0 +#define NUM_BA_QUEUES 0 +#define NUM_MGMT_QUEUES 0 +#define TOTAL_TX_QUEUES \ + (NUM_EDCA_QUEUES + NUM_HCCA_QUEUES + NUM_BA_QUEUES + NUM_MGMT_QUEUES) +#define MAX_TXWCB_QUEUES TOTAL_TX_QUEUES +#define MAX_RXWCB_QUEUES 1 + +//============================================================================= +// PUBLIC DEFINITIONS +//============================================================================= + +#define RATE_INDEX_MAX_ARRAY 14 +#define WOW_MAX_STATION 32 + +/* + * Hardware tx/rx descriptors. + * + * NB: tx descriptor size must match f/w expected size + * because f/w prefetch's the next descriptor linearly + * and doesn't chase the next pointer. + */ +struct mwl_txdesc { + uint32_t Status; +#define EAGLE_TXD_STATUS_IDLE 0x00000000 +#define EAGLE_TXD_STATUS_USED 0x00000001 +#define EAGLE_TXD_STATUS_OK 0x00000001 +#define EAGLE_TXD_STATUS_OK_RETRY 0x00000002 +#define EAGLE_TXD_STATUS_OK_MORE_RETRY 0x00000004 +#define EAGLE_TXD_STATUS_MULTICAST_TX 0x00000008 +#define EAGLE_TXD_STATUS_BROADCAST_TX 0x00000010 +#define EAGLE_TXD_STATUS_FAILED_LINK_ERROR 0x00000020 +#define EAGLE_TXD_STATUS_FAILED_EXCEED_LIMIT 0x00000040 +#define EAGLE_TXD_STATUS_FAILED_XRETRY EAGLE_TXD_STATUS_FAILED_EXCEED_LIMIT +#define EAGLE_TXD_STATUS_FAILED_AGING 0x00000080 +#define EAGLE_TXD_STATUS_FW_OWNED 0x80000000 + uint8_t DataRate; + uint8_t TxPriority; + uint16_t QosCtrl; + uint32_t PktPtr; + uint16_t PktLen; + uint8_t DestAddr[6]; + uint32_t pPhysNext; + uint32_t SapPktInfo; +#define EAGLE_TXD_MODE_BONLY 1 +#define EAGLE_TXD_MODE_GONLY 2 +#define EAGLE_TXD_MODE_BG 3 +#define EAGLE_TXD_MODE_NONLY 4 +#define EAGLE_TXD_MODE_BN 5 +#define EAGLE_TXD_MODE_GN 6 +#define EAGLE_TXD_MODE_BGN 7 +#define EAGLE_TXD_MODE_AONLY 8 +#define EAGLE_TXD_MODE_AG 10 +#define EAGLE_TXD_MODE_AN 12 + uint16_t Format; +#define EAGLE_TXD_FORMAT 0x0001 /* frame format/rate */ +#define EAGLE_TXD_FORMAT_LEGACY 0x0000 /* legacy rate frame */ +#define EAGLE_TXD_FORMAT_HT 0x0001 /* HT rate frame */ +#define EAGLE_TXD_GI 0x0002 /* guard interval */ +#define EAGLE_TXD_GI_SHORT 0x0002 /* short guard interval */ +#define EAGLE_TXD_GI_LONG 0x0000 /* long guard interval */ +#define EAGLE_TXD_CHW 0x0004 /* channel width */ +#define EAGLE_TXD_CHW_20 0x0000 /* 20MHz channel width */ +#define EAGLE_TXD_CHW_40 0x0004 /* 40MHz channel width */ +#define EAGLE_TXD_RATE 0x01f8 /* tx rate (legacy)/ MCS */ +#define EAGLE_TXD_RATE_S 3 +#define EAGLE_TXD_ADV 0x0600 /* advanced coding */ +#define EAGLE_TXD_ADV_S 9 +#define EAGLE_TXD_ADV_NONE 0x0000 +#define EAGLE_TXD_ADV_LDPC 0x0200 +#define EAGLE_TXD_ADV_RS 0x0400 +/* NB: 3 is reserved */ +#define EAGLE_TXD_ANTENNA 0x1800 /* antenna select */ +#define EAGLE_TXD_ANTENNA_S 11 +#define EAGLE_TXD_EXTCHAN 0x6000 /* extension channel */ +#define EAGLE_TXD_EXTCHAN_S 13 +#define EAGLE_TXD_EXTCHAN_HI 0x0000 /* above */ +#define EAGLE_TXD_EXTCHAN_LO 0x2000 /* below */ +#define EAGLE_TXD_PREAMBLE 0x8000 +#define EAGLE_TXD_PREAMBLE_SHORT 0x8000 /* short preamble */ +#define EAGLE_TXD_PREAMBLE_LONG 0x0000 /* long preamble */ + uint16_t pad; /* align to 4-byte boundary */ +#define EAGLE_TXD_FIXED_RATE 0x0100 /* get tx rate from Format */ +#define EAGLE_TXD_DONT_AGGR 0x0200 /* don't aggregate frame */ +} __packed; + +struct mwl_ant_info { + uint8_t rssi_a; /* RSSI for antenna A */ + uint8_t rssi_b; /* RSSI for antenna B */ + uint8_t rssi_c; /* RSSI for antenna C */ + uint8_t rsvd1; /* Reserved */ + uint8_t nf_a; /* Noise floor for antenna A */ + uint8_t nf_b; /* Noise floor for antenna B */ + uint8_t nf_c; /* Noise floor for antenna C */ + uint8_t rsvd2; /* Reserved */ + uint8_t nf; /* Noise floor */ +} __packed; + +struct mwl_rxdesc { + uint8_t RxControl; /* control element */ +#define EAGLE_RXD_CTRL_DRIVER_OWN 0x00 +#define EAGLE_RXD_CTRL_OS_OWN 0x04 +#define EAGLE_RXD_CTRL_DMA_OWN 0x80 + uint8_t RSSI; /* received signal strengt indication */ + uint8_t Status; /* status field w/ USED bit */ +#define EAGLE_RXD_STATUS_IDLE 0x00 +#define EAGLE_RXD_STATUS_OK 0x01 +#define EAGLE_RXD_STATUS_MULTICAST_RX 0x02 +#define EAGLE_RXD_STATUS_BROADCAST_RX 0x04 +#define EAGLE_RXD_STATUS_FRAGMENT_RX 0x08 +#define EAGLE_RXD_STATUS_GENERAL_DECRYPT_ERR 0xff +#define EAGLE_RXD_STATUS_DECRYPT_ERR_MASK 0x80 +#define EAGLE_RXD_STATUS_TKIP_MIC_DECRYPT_ERR 0x02 +#define EAGLE_RXD_STATUS_WEP_ICV_DECRYPT_ERR 0x04 +#define EAGLE_RXD_STATUS_TKIP_ICV_DECRYPT_ERR 0x08 + uint8_t Channel; /* channel # pkt received on */ + uint16_t PktLen; /* total length of received data */ + uint8_t SQ2; /* not used */ + uint8_t Rate; /* received data rate */ + uint32_t pPhysBuffData; /* physical address of payload data */ + uint32_t pPhysNext; /* physical address of next RX desc */ + uint16_t QosCtrl; /* received QosCtrl field variable */ + uint16_t HtSig2; /* like name states */ +#ifdef MWL_ANT_INFO_SUPPORT + struct mwl_ant_info ai; /* antenna info */ +#endif +} __packed; + +/* +// Define OpMode for SoftAP/Station mode +// +// The following mode signature has to be written to PCI scratch register#0 +// right after successfully downloading the last block of firmware and +// before waiting for firmware ready signature + */ +#define HostCmd_STA_MODE 0x5A +#define HostCmd_SOFTAP_MODE 0xA5 + +#define HostCmd_STA_FWRDY_SIGNATURE 0xF0F1F2F4 +#define HostCmd_SOFTAP_FWRDY_SIGNATURE 0xF1F2F4A5 + +//*************************************************************************** +//*************************************************************************** + +//*************************************************************************** + +#define HostCmd_CMD_CODE_DNLD 0x0001 +#define HostCmd_CMD_GET_HW_SPEC 0x0003 +#define HostCmd_CMD_SET_HW_SPEC 0x0004 +#define HostCmd_CMD_MAC_MULTICAST_ADR 0x0010 +#define HostCmd_CMD_802_11_GET_STAT 0x0014 +#define HostCmd_CMD_MAC_REG_ACCESS 0x0019 +#define HostCmd_CMD_BBP_REG_ACCESS 0x001a +#define HostCmd_CMD_RF_REG_ACCESS 0x001b +#define HostCmd_CMD_802_11_RADIO_CONTROL 0x001c +#define HostCmd_CMD_802_11_RF_TX_POWER 0x001e +#define HostCmd_CMD_802_11_RF_ANTENNA 0x0020 +#define HostCmd_CMD_SET_BEACON 0x0100 +#define HostCmd_CMD_SET_AID 0x010d +#define HostCmd_CMD_SET_RF_CHANNEL 0x010a +#define HostCmd_CMD_SET_INFRA_MODE 0x010e +#define HostCmd_CMD_SET_G_PROTECT_FLAG 0x010f +#define HostCmd_CMD_802_11_RTS_THSD 0x0113 +#define HostCmd_CMD_802_11_SET_SLOT 0x0114 + +#define HostCmd_CMD_802_11H_DETECT_RADAR 0x0120 +#define HostCmd_CMD_SET_WMM_MODE 0x0123 +#define HostCmd_CMD_HT_GUARD_INTERVAL 0x0124 +#define HostCmd_CMD_SET_FIXED_RATE 0x0126 +#define HostCmd_CMD_SET_LINKADAPT_CS_MODE 0x0129 +#define HostCmd_CMD_SET_MAC_ADDR 0x0202 +#define HostCmd_CMD_SET_RATE_ADAPT_MODE 0x0203 +#define HostCmd_CMD_GET_WATCHDOG_BITMAP 0x0205 + +//SoftAP command code +#define HostCmd_CMD_BSS_START 0x1100 +#define HostCmd_CMD_SET_NEW_STN 0x1111 +#define HostCmd_CMD_SET_KEEP_ALIVE 0x1112 +#define HostCmd_CMD_SET_APMODE 0x1114 +#define HostCmd_CMD_SET_SWITCH_CHANNEL 0x1121 + +/* + @HWENCR@ + Command to update firmware encryption keys. +*/ +#define HostCmd_CMD_UPDATE_ENCRYPTION 0x1122 +/* + @11E-BA@ + Command to create/destroy block ACK +*/ +#define HostCmd_CMD_BASTREAM 0x1125 +#define HostCmd_CMD_SET_RIFS 0x1126 +#define HostCmd_CMD_SET_N_PROTECT_FLAG 0x1131 +#define HostCmd_CMD_SET_N_PROTECT_OPMODE 0x1132 +#define HostCmd_CMD_SET_OPTIMIZATION_LEVEL 0x1133 +#define HostCmd_CMD_GET_CALTABLE 0x1134 +#define HostCmd_CMD_SET_MIMOPSHT 0x1135 +#define HostCmd_CMD_GET_BEACON 0x1138 +#define HostCmd_CMD_SET_REGION_CODE 0x1139 +#define HostCmd_CMD_SET_POWERSAVESTATION 0x1140 +#define HostCmd_CMD_SET_TIM 0x1141 +#define HostCmd_CMD_GET_TIM 0x1142 +#define HostCmd_CMD_GET_SEQNO 0x1143 + +/* +// Define general result code for each command + */ +#define HostCmd_RESULT_OK 0x0000 // OK +#define HostCmd_RESULT_ERROR 0x0001 // Genenral error +#define HostCmd_RESULT_NOT_SUPPORT 0x0002 // Command is not valid +#define HostCmd_RESULT_PENDING 0x0003 // Command is pending (will be processed) +#define HostCmd_RESULT_BUSY 0x0004 // System is busy (command ignored) +#define HostCmd_RESULT_PARTIAL_DATA 0x0005 // Data buffer is not big enough + + +/* +// Definition of action or option for each command +// +// Define general purpose action + */ +#define HostCmd_ACT_GEN_READ 0x0000 +#define HostCmd_ACT_GEN_WRITE 0x0001 +#define HostCmd_ACT_GEN_GET 0x0000 +#define HostCmd_ACT_GEN_SET 0x0001 +#define HostCmd_ACT_GEN_OFF 0x0000 +#define HostCmd_ACT_GEN_ON 0x0001 + +#define HostCmd_ACT_DIFF_CHANNEL 0x0002 +#define HostCmd_ACT_GEN_SET_LIST 0x0002 + +// Define action or option for HostCmd_FW_USE_FIXED_RATE +#define HostCmd_ACT_USE_FIXED_RATE 0x0001 +#define HostCmd_ACT_NOT_USE_FIXED_RATE 0x0002 + +// Define action or option for HostCmd_CMD_802_11_SET_WEP +//#define HostCmd_ACT_ENABLE 0x0001 // Use MAC control for WEP on/off +//#define HostCmd_ACT_DISABLE 0x0000 +#define HostCmd_ACT_ADD 0x0002 +#define HostCmd_ACT_REMOVE 0x0004 +#define HostCmd_ACT_USE_DEFAULT 0x0008 + +#define HostCmd_TYPE_WEP_40_BIT 0x0001 // 40 bit +#define HostCmd_TYPE_WEP_104_BIT 0x0002 // 104 bit +#define HostCmd_TYPE_WEP_128_BIT 0x0003 // 128 bit +#define HostCmd_TYPE_WEP_TX_KEY 0x0004 // TX WEP + +#define HostCmd_NUM_OF_WEP_KEYS 4 + +#define HostCmd_WEP_KEY_INDEX_MASK 0x3fffffff + + +// Define action or option for HostCmd_CMD_802_11_RESET +#define HostCmd_ACT_HALT 0x0001 +#define HostCmd_ACT_RESTART 0x0002 + +// Define action or option for HostCmd_CMD_802_11_RADIO_CONTROL +#define HostCmd_TYPE_AUTO_PREAMBLE 0x0001 +#define HostCmd_TYPE_SHORT_PREAMBLE 0x0002 +#define HostCmd_TYPE_LONG_PREAMBLE 0x0003 + +// Define action or option for CMD_802_11_RF_CHANNEL +#define HostCmd_TYPE_802_11A 0x0001 +#define HostCmd_TYPE_802_11B 0x0002 + +// Define action or option for HostCmd_CMD_802_11_RF_TX_POWER +#define HostCmd_ACT_TX_POWER_OPT_SET_HIGH 0x0003 +#define HostCmd_ACT_TX_POWER_OPT_SET_MID 0x0002 +#define HostCmd_ACT_TX_POWER_OPT_SET_LOW 0x0001 +#define HostCmd_ACT_TX_POWER_OPT_SET_AUTO 0x0000 + +#define HostCmd_ACT_TX_POWER_LEVEL_MIN 0x000e // in dbm +#define HostCmd_ACT_TX_POWER_LEVEL_GAP 0x0001 // in dbm +// Define action or option for HostCmd_CMD_802_11_DATA_RATE +#define HostCmd_ACT_SET_TX_AUTO 0x0000 +#define HostCmd_ACT_SET_TX_FIX_RATE 0x0001 +#define HostCmd_ACT_GET_TX_RATE 0x0002 + +#define HostCmd_ACT_SET_RX 0x0001 +#define HostCmd_ACT_SET_TX 0x0002 +#define HostCmd_ACT_SET_BOTH 0x0003 +#define HostCmd_ACT_GET_RX 0x0004 +#define HostCmd_ACT_GET_TX 0x0008 +#define HostCmd_ACT_GET_BOTH 0x000c + +#define TYPE_ANTENNA_DIVERSITY 0xffff + +// Define action or option for HostCmd_CMD_802_11_PS_MODE +#define HostCmd_TYPE_CAM 0x0000 +#define HostCmd_TYPE_MAX_PSP 0x0001 +#define HostCmd_TYPE_FAST_PSP 0x0002 + +#define HostCmd_CMD_SET_EDCA_PARAMS 0x0115 + +//============================================================================= +// HOST COMMAND DEFINITIONS +//============================================================================= + +// +// Definition of data structure for each command +// +// Define general data structure +typedef struct { + uint16_t Cmd; + uint16_t Length; +#ifdef MWL_MBSS_SUPPORT + uint8_t SeqNum; + uint8_t MacId; +#else + uint16_t SeqNum; +#endif + uint16_t Result; +} __packed FWCmdHdr; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t Version; // HW revision + uint8_t HostIf; // Host interface + uint16_t NumOfMCastAdr; // Max. number of Multicast address FW can handle + uint8_t PermanentAddr[6]; // MAC address + uint16_t RegionCode; // Region Code + uint32_t FWReleaseNumber; // 4 byte of FW release number + uint32_t ulFwAwakeCookie; // Firmware awake cookie + uint32_t DeviceCaps; // Device capabilities (see above) + uint32_t RxPdWrPtr; // Rx shared memory queue + uint32_t NumTxQueues; // # TX queues in WcbBase array + uint32_t WcbBase[MAX_TXWCB_QUEUES]; // TX WCB Rings + uint32_t Flags; +#define SET_HW_SPEC_DISABLEMBSS 0x08 +#define SET_HW_SPEC_HOSTFORM_BEACON 0x10 +#define SET_HW_SPEC_HOSTFORM_PROBERESP 0x20 +#define SET_HW_SPEC_HOST_POWERSAVE 0x40 +#define SET_HW_SPEC_HOSTENCRDECR_MGMT 0x80 + uint32_t TxWcbNumPerQueue; + uint32_t TotalRxWcb; +} __packed HostCmd_DS_SET_HW_SPEC; + +typedef struct { + FWCmdHdr CmdHdr; + u_int8_t Version; /* version of the HW */ + u_int8_t HostIf; /* host interface */ + u_int16_t NumOfWCB; /* Max. number of WCB FW can handle */ + u_int16_t NumOfMCastAddr; /* MaxNbr of MC addresses FW can handle */ + u_int8_t PermanentAddr[6]; /* MAC address programmed in HW */ + u_int16_t RegionCode; + u_int16_t NumberOfAntenna; /* Number of antenna used */ + u_int32_t FWReleaseNumber; /* 4 byte of FW release number */ + u_int32_t WcbBase0; + u_int32_t RxPdWrPtr; + u_int32_t RxPdRdPtr; + u_int32_t ulFwAwakeCookie; + u_int32_t WcbBase1; + u_int32_t WcbBase2; + u_int32_t WcbBase3; +} __packed HostCmd_DS_GET_HW_SPEC; + +typedef struct { + FWCmdHdr CmdHdr; + u_int32_t Enable; /* FALSE: Disable or TRUE: Enable */ +} __packed HostCmd_DS_BSS_START; + + +typedef struct { + u_int8_t ElemId; + u_int8_t Len; + u_int8_t OuiType[4]; /* 00:50:f2:01 */ + u_int8_t Ver[2]; + u_int8_t GrpKeyCipher[4]; + u_int8_t PwsKeyCnt[2]; + u_int8_t PwsKeyCipherList[4]; + u_int8_t AuthKeyCnt[2]; + u_int8_t AuthKeyList[4]; +} __packed RsnIE_t; + +typedef struct { + u_int8_t ElemId; + u_int8_t Len; + u_int8_t Ver[2]; + u_int8_t GrpKeyCipher[4]; + u_int8_t PwsKeyCnt[2]; + u_int8_t PwsKeyCipherList[4]; + u_int8_t AuthKeyCnt[2]; + u_int8_t AuthKeyList[4]; + u_int8_t RsnCap[2]; +} __packed Rsn48IE_t; + +typedef struct { + u_int8_t ElementId; + u_int8_t Len; + u_int8_t CfpCnt; + u_int8_t CfpPeriod; + u_int16_t CfpMaxDuration; + u_int16_t CfpDurationRemaining; +} __packed CfParams_t; + +typedef struct { + u_int8_t ElementId; + u_int8_t Len; + u_int16_t AtimWindow; +} __packed IbssParams_t; + +typedef union { + CfParams_t CfParamSet; + IbssParams_t IbssParamSet; +} __packed SsParams_t; + +typedef struct { + u_int8_t ElementId; + u_int8_t Len; + u_int16_t DwellTime; + u_int8_t HopSet; + u_int8_t HopPattern; + u_int8_t HopIndex; +} __packed FhParams_t; + +typedef struct { + u_int8_t ElementId; + u_int8_t Len; + u_int8_t CurrentChan; +} __packed DsParams_t; + +typedef union { + FhParams_t FhParamSet; + DsParams_t DsParamSet; +} __packed PhyParams_t; + +typedef struct { + u_int8_t FirstChannelNum; + u_int8_t NumOfChannels; + u_int8_t MaxTxPwrLevel; +} __packed ChannelInfo_t; + +typedef struct { + u_int8_t ElementId; + u_int8_t Len; + u_int8_t CountryStr[3]; + ChannelInfo_t ChannelInfo[40]; +} __packed Country_t; + +typedef struct { + u_int8_t AIFSN : 4; + u_int8_t ACM : 1; + u_int8_t ACI : 2; + u_int8_t rsvd : 1; + +}__packed ACIAIFSN_field_t; + +typedef struct { + u_int8_t ECW_min : 4; + u_int8_t ECW_max : 4; +}__packed ECWmin_max_field_t; + +typedef struct { + ACIAIFSN_field_t ACI_AIFSN; + ECWmin_max_field_t ECW_min_max; + u_int16_t TXOP_lim; +}__packed ACparam_rcd_t; + +typedef struct { + u_int8_t ElementId; + u_int8_t Len; + u_int8_t OUI[3]; + u_int8_t Type; + u_int8_t Subtype; + u_int8_t version; + u_int8_t rsvd; + ACparam_rcd_t AC_BE; + ACparam_rcd_t AC_BK; + ACparam_rcd_t AC_VI; + ACparam_rcd_t AC_VO; +} __packed WMM_param_elem_t ; + +typedef struct { +#ifdef MWL_MBSS_SUPPORT + u_int8_t StaMacAddr[6]; +#endif + u_int8_t SsId[32]; + u_int8_t BssType; + u_int16_t BcnPeriod; + u_int8_t DtimPeriod; + SsParams_t SsParamSet; + PhyParams_t PhyParamSet; + u_int16_t ProbeDelay; + u_int16_t CapInfo; /* see below */ + u_int8_t BssBasicRateSet[14]; + u_int8_t OpRateSet[14]; + RsnIE_t RsnIE; + Rsn48IE_t Rsn48IE; + WMM_param_elem_t WMMParam; + Country_t Country; + u_int32_t ApRFType; /* 0->B, 1->G, 2->Mixed, 3->A, 4->11J */ +} __packed StartCmd_t; + +#define HostCmd_CAPINFO_DEFAULT 0x0000 +#define HostCmd_CAPINFO_ESS 0x0001 +#define HostCmd_CAPINFO_IBSS 0x0002 +#define HostCmd_CAPINFO_CF_POLLABLE 0x0004 +#define HostCmd_CAPINFO_CF_REQUEST 0x0008 +#define HostCmd_CAPINFO_PRIVACY 0x0010 +#define HostCmd_CAPINFO_SHORT_PREAMBLE 0x0020 +#define HostCmd_CAPINFO_PBCC 0x0040 +#define HostCmd_CAPINFO_CHANNEL_AGILITY 0x0080 +#define HostCmd_CAPINFO_SHORT_SLOT 0x0400 +#define HostCmd_CAPINFO_DSSS_OFDM 0x2000 + +typedef struct { + FWCmdHdr CmdHdr; + StartCmd_t StartCmd; +} __packed HostCmd_DS_AP_BEACON; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t FrmBodyLen; + uint8_t FrmBody[1]; /* NB: variable length */ +} __packed HostCmd_DS_SET_BEACON; + +// Define data structure for HostCmd_CMD_MAC_MULTICAST_ADR +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t NumOfAdrs; +#define MWL_HAL_MCAST_MAX 32 + uint8_t MACList[6*32]; +} __packed HostCmd_DS_MAC_MULTICAST_ADR; + +// Indicate to FW the current state of AP ERP info +typedef struct { + FWCmdHdr CmdHdr; + uint32_t GProtectFlag; +} __packed HostCmd_FW_SET_G_PROTECT_FLAG; + +typedef struct { + FWCmdHdr CmdHdr; +} __packed HostCmd_FW_SET_INFRA_MODE; + +// Define data structure for HostCmd_CMD_802_11_RF_CHANNEL +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint8_t CurrentChannel; /* channel # */ + uint32_t ChannelFlags; /* see below */ +} __packed HostCmd_FW_SET_RF_CHANNEL; + +/* bits 0-5 specify frequency band */ +#define FREQ_BAND_2DOT4GHZ 0x0001 +#define FREQ_BAND_4DOT9GHZ 0x0002 /* XXX not implemented */ +#define FREQ_BAND_5GHZ 0x0004 +#define FREQ_BAND_5DOT2GHZ 0x0008 /* XXX not implemented */ +/* bits 6-10 specify channel width */ +#define CH_AUTO_WIDTH 0x0000 /* XXX not used? */ +#define CH_10_MHz_WIDTH 0x0040 +#define CH_20_MHz_WIDTH 0x0080 +#define CH_40_MHz_WIDTH 0x0100 +/* bits 11-12 specify extension channel */ +#define EXT_CH_NONE 0x0000 /* no extension channel */ +#define EXT_CH_ABOVE_CTRL_CH 0x0800 /* extension channel above */ +#define EXT_CH_AUTO 0x1000 /* XXX not used? */ +#define EXT_CH_BELOW_CTRL_CH 0x1800 /* extension channel below */ +/* bits 13-31 are reserved */ + +#define FIXED_RATE_WITH_AUTO_RATE_DROP 0 +#define FIXED_RATE_WITHOUT_AUTORATE_DROP 1 + +#define LEGACY_RATE_TYPE 0 +#define HT_RATE_TYPE 1 + +#define RETRY_COUNT_VALID 0 +#define RETRY_COUNT_INVALID 1 + +typedef struct { + // lower rate after the retry count + uint32_t FixRateType; //0: legacy, 1: HT + uint32_t RetryCountValid; //0: retry count is not valid, 1: use retry count specified +} __packed FIX_RATE_FLAG; + +typedef struct { + FIX_RATE_FLAG FixRateTypeFlags; + uint32_t FixedRate; // legacy rate(not index) or an MCS code. + uint32_t RetryCount; +} __packed FIXED_RATE_ENTRY; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t Action; //HostCmd_ACT_GEN_GET 0x0000 + //HostCmd_ACT_GEN_SET 0x0001 + //HostCmd_ACT_NOT_USE_FIXED_RATE 0x0002 + uint32_t AllowRateDrop; // use fixed rate specified but firmware can drop to + uint32_t EntryCount; + FIXED_RATE_ENTRY FixedRateTable[4]; + uint8_t MulticastRate; + uint8_t MultiRateTxType; + uint8_t ManagementRate; +} __packed HostCmd_FW_USE_FIXED_RATE; + +typedef struct { + uint32_t AllowRateDrop; + uint32_t EntryCount; + FIXED_RATE_ENTRY FixedRateTable[4]; +} __packed USE_FIXED_RATE_INFO; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t Action; + uint32_t GIType; +#define GI_TYPE_LONG 0x0001 +#define GI_TYPE_SHORT 0x0002 +} __packed HostCmd_FW_HT_GUARD_INTERVAL; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t Action; + uint8_t RxAntennaMap; + uint8_t TxAntennaMap; +} __packed HostCmd_FW_HT_MIMO_CONFIG; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint8_t Slot; // Slot=0 if regular, Slot=1 if short. +} __packed HostCmd_FW_SET_SLOT; + + +// Define data structure for HostCmd_CMD_802_11_GET_STAT +typedef struct { + FWCmdHdr CmdHdr; + uint32_t TxRetrySuccesses; + uint32_t TxMultipleRetrySuccesses; + uint32_t TxFailures; + uint32_t RTSSuccesses; + uint32_t RTSFailures; + uint32_t AckFailures; + uint32_t RxDuplicateFrames; + uint32_t FCSErrorCount; + uint32_t TxWatchDogTimeouts; + uint32_t RxOverflows; //used + uint32_t RxFragErrors; //used + uint32_t RxMemErrors; //used + uint32_t PointerErrors; //used + uint32_t TxUnderflows; //used + uint32_t TxDone; + uint32_t TxDoneBufTryPut; + uint32_t TxDoneBufPut; + uint32_t Wait4TxBuf; // Put size of requested buffer in here + uint32_t TxAttempts; + uint32_t TxSuccesses; + uint32_t TxFragments; + uint32_t TxMulticasts; + uint32_t RxNonCtlPkts; + uint32_t RxMulticasts; + uint32_t RxUndecryptableFrames; + uint32_t RxICVErrors; + uint32_t RxExcludedFrames; +} __packed HostCmd_DS_802_11_GET_STAT; + + +// Define data structure for HostCmd_CMD_MAC_REG_ACCESS +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t Offset; + uint32_t Value; + uint16_t Reserved; +} __packed HostCmd_DS_MAC_REG_ACCESS; + +// Define data structure for HostCmd_CMD_BBP_REG_ACCESS +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t Offset; + uint8_t Value; + uint8_t Reserverd[3]; +} __packed HostCmd_DS_BBP_REG_ACCESS; + +// Define data structure for HostCmd_CMD_RF_REG_ACCESS +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t Offset; + uint8_t Value; + uint8_t Reserverd[3]; +} __packed HostCmd_DS_RF_REG_ACCESS; + + +// Define data structure for HostCmd_CMD_802_11_RADIO_CONTROL +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t Control; // @bit0: 1/0,on/off, @bit1: 1/0, long/short @bit2: 1/0,auto/fix + uint16_t RadioOn; +} __packed HostCmd_DS_802_11_RADIO_CONTROL; + + +#define TX_POWER_LEVEL_TOTAL 8 +// Define data structure for HostCmd_CMD_802_11_RF_TX_POWER +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t SupportTxPowerLevel; + uint16_t CurrentTxPowerLevel; + uint16_t Reserved; + uint16_t PowerLevelList[TX_POWER_LEVEL_TOTAL]; +} __packed HostCmd_DS_802_11_RF_TX_POWER; + +// Define data structure for HostCmd_CMD_802_11_RF_ANTENNA +typedef struct _HostCmd_DS_802_11_RF_ANTENNA { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t AntennaMode; // Number of antennas or 0xffff(diversity) +} __packed HostCmd_DS_802_11_RF_ANTENNA; + +// Define data structure for HostCmd_CMD_802_11_PS_MODE +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t PowerMode; // CAM, Max.PSP or Fast PSP +} __packed HostCmd_DS_802_11_PS_MODE; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t Threshold; +} __packed HostCmd_DS_802_11_RTS_THSD; + +// used for stand alone bssid sets/clears +typedef struct { + FWCmdHdr CmdHdr; +#ifdef MWL_MBSS_SUPPORT + uint16_t MacType; +#define WL_MAC_TYPE_PRIMARY_CLIENT 0 +#define WL_MAC_TYPE_SECONDARY_CLIENT 1 +#define WL_MAC_TYPE_PRIMARY_AP 2 +#define WL_MAC_TYPE_SECONDARY_AP 3 +#endif + uint8_t MacAddr[6]; +} __packed HostCmd_DS_SET_MAC, + HostCmd_FW_SET_BSSID, + HostCmd_FW_SET_MAC; + +// Indicate to FW to send out PS Poll +typedef struct { + FWCmdHdr CmdHdr; + uint32_t PSPoll; +} __packed HostCmd_FW_TX_POLL; + +// used for AID sets/clears +typedef struct { + FWCmdHdr CmdHdr; + uint16_t AssocID; + uint8_t MacAddr[6]; //AP's Mac Address(BSSID) + uint32_t GProtection; + uint8_t ApRates[ RATE_INDEX_MAX_ARRAY]; +} __packed HostCmd_FW_SET_AID; + +typedef struct { + uint32_t LegacyRateBitMap; + uint32_t HTRateBitMap; + uint16_t CapInfo; + uint16_t HTCapabilitiesInfo; + uint8_t MacHTParamInfo; + uint8_t Rev; + struct { + uint8_t ControlChan; + uint8_t AddChan; + uint16_t OpMode; + uint16_t stbc; + } __packed AddHtInfo; +} __packed PeerInfo_t; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t AID; + uint8_t MacAddr[6]; + uint16_t StnId; + uint16_t Action; + uint16_t Reserved; + PeerInfo_t PeerInfo; + uint8_t Qosinfo; + uint8_t isQosSta; +} __packed HostCmd_FW_SET_NEW_STN; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t tick; +} __packed HostCmd_FW_SET_KEEP_ALIVE_TICK; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t QNum; +} __packed HostCmd_FW_SET_RIFS; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t ApMode; +} __packed HostCmd_FW_SET_APMODE; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; // see following + uint16_t RadarTypeCode; +} __packed HostCmd_802_11h_Detect_Radar; + +#define DR_DFS_DISABLE 0 +#define DR_CHK_CHANNEL_AVAILABLE_START 1 +#define DR_CHK_CHANNEL_AVAILABLE_STOP 2 +#define DR_IN_SERVICE_MONITOR_START 3 + +//New Structure for Update Tim 30/9/2003 +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Aid; + uint32_t Set; +} __packed HostCmd_UpdateTIM; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t SsidBroadcastEnable; +} __packed HostCmd_SSID_BROADCAST; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t WdsEnable; +} __packed HostCmd_WDS; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t Next11hChannel; + uint32_t Mode; + uint32_t InitialCount; + uint32_t ChannelFlags ; +} __packed HostCmd_SET_SWITCH_CHANNEL; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t SpectrumMgmt; +} __packed HostCmd_SET_SPECTRUM_MGMT; + +typedef struct { + FWCmdHdr CmdHdr; + int32_t PowerConstraint; +} __packed HostCmd_SET_POWER_CONSTRAINT; + +typedef struct { + uint8_t FirstChannelNo; + uint8_t NoofChannel; + uint8_t MaxTransmitPw; +} __packed DomainChannelEntry; + +typedef struct { + uint8_t CountryString[3]; + uint8_t GChannelLen; + DomainChannelEntry DomainEntryG[1]; /** Assume only 1 G zone **/ + uint8_t AChannelLen; + DomainChannelEntry DomainEntryA[20]; /** Assume max of 5 A zone **/ +} __packed DomainCountryInfo; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t Action ; // 0 -> unset, 1 ->set + DomainCountryInfo DomainInfo ; +} __packed HostCmd_SET_COUNTRY_INFO; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t regionCode ; +} __packed HostCmd_SET_REGIONCODE_INFO; + +// for HostCmd_CMD_SET_WMM_MODE +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; // 0->unset, 1->set +} __packed HostCmd_FW_SetWMMMode; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; // 0->unset, 1->set + uint16_t IeListLen; + uint8_t IeList[200]; +} __packed HostCmd_FW_SetIEs; + +#define EDCA_PARAM_SIZE 18 +#define BA_PARAM_SIZE 2 + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; //0 = get all, 0x1 =set CWMin/Max, 0x2 = set TXOP , 0x4 =set AIFSN + uint16_t TxOP; // in unit of 32 us + uint32_t CWMax; // 0~15 + uint32_t CWMin; // 0~15 + uint8_t AIFSN; + uint8_t TxQNum; // Tx Queue number. +} __packed HostCmd_FW_SET_EDCA_PARAMS; + +/****************************************************************************** + @HWENCR@ + Hardware Encryption related data structures and constant definitions. + Note that all related changes are marked with the @HWENCR@ tag. +*******************************************************************************/ + +#define MAX_ENCR_KEY_LENGTH 16 /* max 128 bits - depends on type */ +#define MIC_KEY_LENGTH 8 /* size of Tx/Rx MIC key - 8 bytes*/ + +#define ENCR_KEY_TYPE_ID_WEP 0x00 /* Key type is WEP */ +#define ENCR_KEY_TYPE_ID_TKIP 0x01 /* Key type is TKIP */ +#define ENCR_KEY_TYPE_ID_AES 0x02 /* Key type is AES-CCMP */ + +/* flags used in structure - same as driver EKF_XXX flags */ +#define ENCR_KEY_FLAG_INUSE 0x00000001 /* indicate key is in use */ +#define ENCR_KEY_FLAG_RXGROUPKEY 0x00000002 /* Group key for RX only */ +#define ENCR_KEY_FLAG_TXGROUPKEY 0x00000004 /* Group key for TX */ +#define ENCR_KEY_FLAG_PAIRWISE 0x00000008 /* pairwise */ +#define ENCR_KEY_FLAG_RXONLY 0x00000010 /* only used for RX */ +// These flags are new additions - for hardware encryption commands only. +#define ENCR_KEY_FLAG_AUTHENTICATOR 0x00000020 /* Key is for Authenticator */ +#define ENCR_KEY_FLAG_TSC_VALID 0x00000040 /* Sequence counters valid */ +#define ENCR_KEY_FLAG_WEP_TXKEY 0x01000000 /* Tx key for WEP */ +#define ENCR_KEY_FLAG_MICKEY_VALID 0x02000000 /* Tx/Rx MIC keys are valid */ + +/* + UPDATE_ENCRYPTION command action type. +*/ +typedef enum { + // request to enable/disable HW encryption + EncrActionEnableHWEncryption, + // request to set encryption key + EncrActionTypeSetKey, + // request to remove one or more keys + EncrActionTypeRemoveKey, + EncrActionTypeSetGroupKey +} ENCR_ACTION_TYPE; + +/* + Key material definitions (for WEP, TKIP, & AES-CCMP) +*/ + +/* + WEP Key material definition + ---------------------------- + WEPKey --> An array of 'MAX_ENCR_KEY_LENGTH' bytes. + Note that we do not support 152bit WEP keys +*/ +typedef struct { + // WEP key material (max 128bit) + uint8_t KeyMaterial[ MAX_ENCR_KEY_LENGTH ]; +} __packed WEP_TYPE_KEY; + +/* + TKIP Key material definition + ---------------------------- + This structure defines TKIP key material. Note that + the TxMicKey and RxMicKey may or may not be valid. +*/ +/* TKIP Sequence counter - 24 bits */ +/* Incremented on each fragment MPDU */ +typedef struct { + uint16_t low; + uint32_t high; +} __packed ENCR_TKIPSEQCNT; + +typedef struct { + // TKIP Key material. Key type (group or pairwise key) is + // determined by flags in KEY_PARAM_SET structure. + uint8_t KeyMaterial[ MAX_ENCR_KEY_LENGTH ]; + uint8_t TkipTxMicKey[ MIC_KEY_LENGTH ]; + uint8_t TkipRxMicKey[ MIC_KEY_LENGTH ]; + ENCR_TKIPSEQCNT TkipRsc; + ENCR_TKIPSEQCNT TkipTsc; +} __packed TKIP_TYPE_KEY; + +/* + AES-CCMP Key material definition + -------------------------------- + This structure defines AES-CCMP key material. +*/ +typedef struct { + // AES Key material + uint8_t KeyMaterial[ MAX_ENCR_KEY_LENGTH ]; +} __packed AES_TYPE_KEY; + +/* + Encryption key definition. + -------------------------- + This structure provides all required/essential + information about the key being set/removed. +*/ +typedef struct { + uint16_t Length; // Total length of this structure + uint16_t KeyTypeId; // Key type - WEP, TKIP or AES-CCMP. + uint32_t KeyInfo; // key flags (ENCR_KEY_FLAG_XXX_ + uint32_t KeyIndex; // For WEP only - actual key index + uint16_t KeyLen; // Size of the key + union { // Key material (variable size array) + WEP_TYPE_KEY WepKey; + TKIP_TYPE_KEY TkipKey; + AES_TYPE_KEY AesKey; + }__packed Key; +#ifdef MWL_MBSS_SUPPORT + uint8_t Macaddr[6]; +#endif +} __packed KEY_PARAM_SET; + +/* + HostCmd_FW_UPDATE_ENCRYPTION + ---------------------------- + Define data structure for updating firmware encryption keys. + +*/ +typedef struct { + FWCmdHdr CmdHdr; + uint32_t ActionType; // ENCR_ACTION_TYPE + uint32_t DataLength; // size of the data buffer attached. +#ifdef MWL_MBSS_SUPPORT + uint8_t macaddr[6]; +#endif + uint8_t ActionData[1]; +} __packed HostCmd_FW_UPDATE_ENCRYPTION; + + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t ActionType; // ENCR_ACTION_TYPE + uint32_t DataLength; // size of the data buffer attached. + KEY_PARAM_SET KeyParam; +#ifndef MWL_MBSS_SUPPORT + uint8_t Macaddr[8]; /* XXX? */ +#endif +} __packed HostCmd_FW_UPDATE_ENCRYPTION_SET_KEY; + +typedef struct { + // Rate flags - see above. + uint32_t Flags; + // Rate in 500Kbps units. + uint8_t RateKbps; + // 802.11 rate to conversion table index value. + // This is the value required by the firmware/hardware. + uint16_t RateCodeToIndex; +}__packed RATE_INFO; + +/* + UPDATE_STADB command action type. +*/ +typedef enum { + // request to add entry to stainfo db + StaInfoDbActionAddEntry, + // request to modify peer entry + StaInfoDbActionModifyEntry, + // request to remove peer from stainfo db + StaInfoDbActionRemoveEntry +}__packed STADB_ACTION_TYPE; + +/* + @11E-BA@ + 802.11e/WMM Related command(s)/data structures +*/ + +// Flag to indicate if the stream is an immediate block ack stream. +// if this bit is not set, the stream is delayed block ack stream. +#define BASTREAM_FLAG_DELAYED_TYPE 0x00 +#define BASTREAM_FLAG_IMMEDIATE_TYPE 0x01 + +// Flag to indicate the direction of the stream (upstream/downstream). +// If this bit is not set, the direction is downstream. +#define BASTREAM_FLAG_DIRECTION_UPSTREAM 0x00 +#define BASTREAM_FLAG_DIRECTION_DOWNSTREAM 0x02 +#define BASTREAM_FLAG_DIRECTION_DLP 0x04 +#define BASTREAM_FLAG_DIRECTION_BOTH 0x06 + +typedef enum { + BaCreateStream, + BaUpdateStream, + BaDestroyStream, + BaFlushStream, + BaCheckCreateStream +} BASTREAM_ACTION_TYPE; + +typedef struct { + uint32_t Context; +} __packed BASTREAM_CONTEXT; + +// parameters for block ack creation +typedef struct { + // BA Creation flags - see above + uint32_t Flags; + // idle threshold + uint32_t IdleThrs; + // block ack transmit threshold (after how many pkts should we send BAR?) + uint32_t BarThrs; + // receiver window size + uint32_t WindowSize; + // MAC Address of the BA partner + uint8_t PeerMacAddr[6]; + // Dialog Token + uint8_t DialogToken; + //TID for the traffic stream in this BA + uint8_t Tid; + // shared memory queue ID (not sure if this is required) + uint8_t QueueId; + uint8_t ParamInfo; + // returned by firmware - firmware context pointer. + // this context pointer will be passed to firmware for all future commands. + BASTREAM_CONTEXT FwBaContext; + uint8_t ResetSeqNo; /** 0 or 1**/ + uint16_t StartSeqNo; +}__packed BASTREAM_CREATE_STREAM; + +// new transmit sequence number information +typedef struct { + // BA flags - see above + uint32_t Flags; + // returned by firmware in the create ba stream response + BASTREAM_CONTEXT FwBaContext; + // new sequence number for this block ack stream + uint16_t BaSeqNum; +}__packed BASTREAM_UPDATE_STREAM; + +typedef struct { + // BA Stream flags + uint32_t Flags; + // returned by firmware in the create ba stream response + BASTREAM_CONTEXT FwBaContext; +}__packed BASTREAM_STREAM_INFO; + +//Command to create/destroy block ACK +typedef struct { + FWCmdHdr CmdHdr; + uint32_t ActionType; + union + { + // information required to create BA Stream... + BASTREAM_CREATE_STREAM CreateParams; + // update starting/new sequence number etc. + BASTREAM_UPDATE_STREAM UpdtSeqNum; + // destroy an existing stream... + BASTREAM_STREAM_INFO DestroyParams; + // destroy an existing stream... + BASTREAM_STREAM_INFO FlushParams; + }__packed BaInfo; +}__packed HostCmd_FW_BASTREAM; + +// Define data structure for HostCmd_CMD_GET_WATCHDOG_BITMAP +typedef struct { + FWCmdHdr CmdHdr; + uint8_t Watchdogbitmap; // for SW/BA +} __packed HostCmd_FW_GET_WATCHDOG_BITMAP; + + + +// Define data structure for HostCmd_CMD_SET_REGION_POWER +typedef struct { + FWCmdHdr CmdHdr; + uint16_t MaxPowerLevel; + uint16_t Reserved; +} __packed HostCmd_DS_SET_REGION_POWER; + +// Define data structure for HostCmd_CMD_SET_RATE_ADAPT_MODE +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t RateAdaptMode; +} __packed HostCmd_DS_SET_RATE_ADAPT_MODE; + +// Define data structure for HostCmd_CMD_SET_LINKADAPT_CS_MODE +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Action; + uint16_t CSMode; +} __packed HostCmd_DS_SET_LINKADAPT_CS_MODE; + +typedef struct { + FWCmdHdr CmdHdr; + uint32_t NProtectFlag; +} __packed HostCmd_FW_SET_N_PROTECT_FLAG; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t NProtectOpMode; +} __packed HostCmd_FW_SET_N_PROTECT_OPMODE; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t OptLevel; +} __packed HostCmd_FW_SET_OPTIMIZATION_LEVEL; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t annex; + uint8_t index; + uint8_t len; + uint8_t Reserverd; +#define CAL_TBL_SIZE 160 + uint8_t calTbl[CAL_TBL_SIZE]; +} __packed HostCmd_FW_GET_CALTABLE; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t Addr[6]; + uint8_t Enable; + uint8_t Mode; +} __packed HostCmd_FW_SET_MIMOPSHT; + +#define MAX_BEACON_SIZE 1024 +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Bcnlen; + uint8_t Reserverd[2]; + uint8_t Bcn[MAX_BEACON_SIZE]; +} __packed HostCmd_FW_GET_BEACON; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t NumberOfPowersave; + uint8_t reserved; +} __packed HostCmd_SET_POWERSAVESTATION; + +typedef struct { + FWCmdHdr CmdHdr; + uint16_t Aid; + uint32_t Set; + uint8_t reserved; +} __packed HostCmd_SET_TIM; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t TrafficMap[251]; + uint8_t reserved; +} __packed HostCmd_GET_TIM; + +typedef struct { + FWCmdHdr CmdHdr; + uint8_t MacAddr[6]; + uint8_t TID; + uint16_t SeqNo; + uint8_t reserved; +} __packed HostCmd_GET_SEQNO; +#endif /* _MWL_HALREG_H_ */ diff --git a/sys/modules/Makefile b/sys/modules/Makefile index 6a04d5e..51ce6fc 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -178,6 +178,7 @@ SUBDIR= ${_3dfx} \ msdosfs_iconv \ ${_mse} \ msk \ + mwl \ mxge \ my \ ${_ncp} \ diff --git a/sys/modules/mwl/Makefile b/sys/modules/mwl/Makefile new file mode 100644 index 0000000..8971eb1 --- /dev/null +++ b/sys/modules/mwl/Makefile @@ -0,0 +1,41 @@ +# +# Copyright (c) 2007-2009 Sam Leffler, Errno Consulting +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer, +# without modification. +# 2. Redistributions in binary form must reproduce at minimum a disclaimer +# similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any +# redistribution must be conditioned upon including a substantially +# similar Disclaimer requirement for further binary redistribution. +# +# NO WARRANTY +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY +# AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +# THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, +# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGES. +# +# $FreeBSD$ +# + +.PATH: ${.CURDIR}/../../dev/mwl + +KMOD= if_mwl +SRCS= if_mwl.c if_mwl_pci.c mwlhal.c +SRCS+= device_if.h bus_if.h pci_if.h opt_inet.h opt_mwl.h + +opt_mwl.h: + echo '#define MWL_DEBUG 1'> $@ + +.include <bsd.kmod.mk> diff --git a/sys/modules/mwlfw/Makefile b/sys/modules/mwlfw/Makefile new file mode 100644 index 0000000..9d7b226 --- /dev/null +++ b/sys/modules/mwlfw/Makefile @@ -0,0 +1,14 @@ +# $FreeBSD$ + +KMOD= mw88W8363fw +FIRMWS= mw88W8363.fw:mw88W8363fw mwlboot.fw:mwlboot + +CLEANFILES+= mw88W8363.fw mwlboot.fw + +mw88W8363.fw: ${.CURDIR}/../../contrib/dev/mwl/mw88W8363.fw.uu + uudecode -p $? > ${.TARGET} + +mwlboot.fw: ${.CURDIR}/../../contrib/dev/mwl/mwlboot.fw.uu + uudecode -p $? > ${.TARGET} + +.include <bsd.kmod.mk> |