From 61a57106648723c309a09769f8f9fcbb32c19c49 Mon Sep 17 00:00:00 2001 From: sakumisu <1203593632@qq.com> Date: Tue, 1 Jul 2025 10:01:49 +0800 Subject: [PATCH] update(cherryusb): update framework and bugfix * update cdc_acm device framework * add host serial device framework * fix dfs read write return type * fix webusb desc len * fix dwc2 h7rs gccfg Signed-off-by: sakumisu <1203593632@qq.com> --- components/drivers/usb/cherryusb/Kconfig | 9 +- components/drivers/usb/cherryusb/Kconfig.rtt | 14 + .../drivers/usb/cherryusb/Kconfig.rttpkg | 14 + components/drivers/usb/cherryusb/SConscript | 21 +- .../cherryusb/class/vendor/serial/usbh_ftdi.c | 1 + .../usb/cherryusb/demo/adb/cherryadb.png | Bin 42508 -> 0 bytes .../cherryusb/demo/adb/usbd_adb_template.c | 13 +- .../cherryusb/demo/cdc_acm_mavlink_template.c | 337 +++++++ .../demo/cdc_acm_rttchardev_template.c | 208 ++++ .../usb/cherryusb/demo/webusb_hid_template.c | 2 +- .../drivers/usb/cherryusb/platform/README.md | 3 +- .../cherryusb/platform/rtthread/usb_check.c | 5 + .../platform/rtthread/usbd_adb_shell.c | 156 +++ .../cherryusb/platform/rtthread/usbd_serial.c | 272 ++++++ .../cherryusb/platform/rtthread/usbh_dfs.c | 16 +- .../cherryusb/platform/rtthread/usbh_serial.c | 914 ++++++++++++++++++ .../usb/cherryusb/port/dwc2/usb_glue_st.c | 6 +- .../usb/cherryusb/port/dwc2/usb_hc_dwc2.c | 11 +- 18 files changed, 1972 insertions(+), 30 deletions(-) delete mode 100644 components/drivers/usb/cherryusb/demo/adb/cherryadb.png create mode 100644 components/drivers/usb/cherryusb/demo/cdc_acm_mavlink_template.c create mode 100644 components/drivers/usb/cherryusb/demo/cdc_acm_rttchardev_template.c create mode 100644 components/drivers/usb/cherryusb/platform/rtthread/usbd_adb_shell.c create mode 100644 components/drivers/usb/cherryusb/platform/rtthread/usbd_serial.c create mode 100644 components/drivers/usb/cherryusb/platform/rtthread/usbh_serial.c diff --git a/components/drivers/usb/cherryusb/Kconfig b/components/drivers/usb/cherryusb/Kconfig index ce39f3e713..98e3315107 100644 --- a/components/drivers/usb/cherryusb/Kconfig +++ b/components/drivers/usb/cherryusb/Kconfig @@ -123,16 +123,17 @@ if CHERRYUSB prompt "Enable usb mtp device, it is commercial charge" default n - config CHERRYUSB_DEVICE_DFU - bool - prompt "Enable usb dfu device" - default n config CHERRYUSB_DEVICE_ADB bool prompt "Enable usb adb device" default n + config CHERRYUSB_DEVICE_DFU + bool + prompt "Enable usb dfu device" + default n + choice prompt "Select usb device template" default CHERRYUSB_DEVICE_TEMPLATE_NONE diff --git a/components/drivers/usb/cherryusb/Kconfig.rtt b/components/drivers/usb/cherryusb/Kconfig.rtt index 209332f5e6..2d35b4cae6 100644 --- a/components/drivers/usb/cherryusb/Kconfig.rtt +++ b/components/drivers/usb/cherryusb/Kconfig.rtt @@ -125,11 +125,21 @@ if RT_USING_CHERRYUSB prompt "Enable usb mtp device, it is commercial charge" default n + config RT_CHERRYUSB_DEVICE_ADB + bool + prompt "Enable usb adb device" + default n + config RT_CHERRYUSB_DEVICE_DFU bool prompt "Enable usb dfu device" default n + config RT_CHERRYUSB_DEVICE_CDC_ACM_CHARDEV + bool + prompt "Enable chardev for cdc acm device" + default n + choice prompt "Select usb device template" default RT_CHERRYUSB_DEVICE_TEMPLATE_NONE @@ -169,6 +179,10 @@ if RT_USING_CHERRYUSB bool "winusbv2_cdc" config RT_CHERRYUSB_DEVICE_TEMPLATE_WINUSBV2_HID bool "winusbv2_hid" + config RT_CHERRYUSB_DEVICE_TEMPLATE_ADB + bool "adb" + config RT_CHERRYUSB_DEVICE_TEMPLATE_CDC_ACM_CHARDEV + bool "cdc_acm_chardev" endchoice config CONFIG_USBDEV_MSC_BLOCK_DEV_NAME diff --git a/components/drivers/usb/cherryusb/Kconfig.rttpkg b/components/drivers/usb/cherryusb/Kconfig.rttpkg index 73d5f3b2b4..63614327dd 100644 --- a/components/drivers/usb/cherryusb/Kconfig.rttpkg +++ b/components/drivers/usb/cherryusb/Kconfig.rttpkg @@ -124,11 +124,21 @@ if PKG_USING_CHERRYUSB prompt "Enable usb mtp device, it is commercial charge" default n + config PKG_CHERRYUSB_DEVICE_ADB + bool + prompt "Enable usb adb device" + default n + config PKG_CHERRYUSB_DEVICE_DFU bool prompt "Enable usb dfu device" default n + config PKG_CHERRYUSB_DEVICE_CDC_ACM_CHARDEV + bool + prompt "Enable chardev for cdc acm device" + default n + choice prompt "Select usb device template" default PKG_CHERRYUSB_DEVICE_TEMPLATE_NONE @@ -168,6 +178,10 @@ if PKG_USING_CHERRYUSB bool "winusbv2_cdc" config PKG_CHERRYUSB_DEVICE_TEMPLATE_WINUSBV2_HID bool "winusbv2_hid" + config PKG_CHERRYUSB_DEVICE_TEMPLATE_ADB + bool "adb" + config PKG_CHERRYUSB_DEVICE_TEMPLATE_CDC_ACM_CHARDEV + bool "cdc_acm_chardev" endchoice config CONFIG_USBDEV_MSC_BLOCK_DEV_NAME diff --git a/components/drivers/usb/cherryusb/SConscript b/components/drivers/usb/cherryusb/SConscript index acbddefb4f..eba0a3e182 100644 --- a/components/drivers/usb/cherryusb/SConscript +++ b/components/drivers/usb/cherryusb/SConscript @@ -127,13 +127,17 @@ if GetDepend(['RT_CHERRYUSB_DEVICE']): src += Glob('class/cdc/usbd_cdc_ncm.c') if GetDepend(['RT_CHERRYUSB_DEVICE_DFU']): src += Glob('class/dfu/usbd_dfu.c') + if GetDepend(['RT_CHERRYUSB_DEVICE_ADB']): + src += Glob('class/adb/usbd_adb.c') + src += Glob('platform/rtthread/usbd_adb_shell.c') + + if GetDepend(['RT_CHERRYUSB_DEVICE_CDC_ACM_CHARDEV']): + src += Glob('platform/rtthread/usbd_serial.c') if GetDepend(['RT_CHERRYUSB_DEVICE_TEMPLATE_CDC_ACM']): src += Glob('demo/cdc_acm_template.c') - if GetDepend(['RT_CHERRYUSB_DEVICE_TEMPLATE_MSC']): + if GetDepend(['RT_CHERRYUSB_DEVICE_TEMPLATE_MSC']) or GetDepend(['RT_CHERRYUSB_DEVICE_TEMPLATE_MSC_BLKDEV']): src += Glob('demo/msc_ram_template.c') - if GetDepend(['RT_CHERRYUSB_DEVICE_TEMPLATE_MSC_BLKDEV']): - src += Glob('platform/rtthread/usbd_msc_blkdev.c') if GetDepend(['RT_CHERRYUSB_DEVICE_TEMPLATE_HID_MOUSE']): src += Glob('demo/hid_mouse_template.c') if GetDepend(['RT_CHERRYUSB_DEVICE_TEMPLATE_HID_KEYBOARD']): @@ -162,6 +166,10 @@ if GetDepend(['RT_CHERRYUSB_DEVICE']): src += Glob('demo/winusb2.0_cdc_template.c') if GetDepend(['RT_CHERRYUSB_DEVICE_TEMPLATE_WINUSBV2_HID']): src += Glob('demo/winusb2.0_hid_template.c') + if GetDepend(['RT_CHERRYUSB_DEVICE_TEMPLATE_ADB']): + src += Glob('demo/adb/usbd_adb_template.c') + if GetDepend(['RT_CHERRYUSB_DEVICE_TEMPLATE_CDC_ACM_CHARDEV']): + src += Glob('demo/cdc_acm_rttchardev_template.c') # USB HOST if GetDepend(['RT_CHERRYUSB_HOST']): @@ -285,6 +293,13 @@ if GetDepend(['RT_CHERRYUSB_HOST']): CPPDEFINES+=['TEST_USBH_MSC=0'] src += Glob('demo/usb_host.c') + if GetDepend(['RT_CHERRYUSB_HOST_CDC_ACM']) \ + or GetDepend(['RT_CHERRYUSB_HOST_FTDI']) \ + or GetDepend(['RT_CHERRYUSB_HOST_CH34X']) \ + or GetDepend(['RT_CHERRYUSB_HOST_CP210X']) \ + or GetDepend(['RT_CHERRYUSB_HOST_PL2303']): + src += Glob('platform/rtthread/usbh_serial.c') + if GetDepend('RT_USING_DFS') and GetDepend(['RT_CHERRYUSB_HOST_MSC']): src += Glob('platform/rtthread/usbh_dfs.c') diff --git a/components/drivers/usb/cherryusb/class/vendor/serial/usbh_ftdi.c b/components/drivers/usb/cherryusb/class/vendor/serial/usbh_ftdi.c index 6bc19b2045..4bc7f81b01 100644 --- a/components/drivers/usb/cherryusb/class/vendor/serial/usbh_ftdi.c +++ b/components/drivers/usb/cherryusb/class/vendor/serial/usbh_ftdi.c @@ -350,6 +350,7 @@ static int usbh_ftdi_connect(struct usbh_hubport *hport, uint8_t intf) switch (version) { case 0x400: ftdi_class->chip_type = FT232B; + break; case 0x500: ftdi_class->chip_type = FT2232C; break; diff --git a/components/drivers/usb/cherryusb/demo/adb/cherryadb.png b/components/drivers/usb/cherryusb/demo/adb/cherryadb.png deleted file mode 100644 index 512586b954e80be18f05f84d734cbe204792ce64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42508 zcmd43WmJ^^+b^nuF!TUY(jlNADP4l3lt>6j4c#d)w7}3^(x9kxNY_w9HzFzBIWTm? zx%vIi@0`8PI%_@8+Izp)FIYDYGkC}MdtINp2z#d@hll+X`@w?;cnb2;?;kvP#0-4# zfG~hp?!q4SKX^d*KtWnk1Ddhhgq5N(o_gokknWT85;y8mpE0;2>KBumqdY^}Ln#Em zL_5!AHoasZIxnLHCKMXk&cn+Ee%Q{*D2dA$fu+Xs)tt&77m^A#(?sOtF9y7R_f02l z&(BBYq@dZgvG7vMozJ!L!WX(-Q2KEr%WJ49*|X)$XTe29Wv0e1q6HPinWC7?K|v1# zONH9Tf7H{XakY859Ih(3Tpn-LA%%p5bgbjRIr;fL86xgO&9~PZ66xlHX{kk}4R7x_ zgTR_nmM|;{U%40>J~B>&0XKCR__?6#cA}8iX|dIx+^F8c!9h0{7?>~e-F2a$YM68w zcpME;3{_41&J62$^$pBi7bF9jKj=mb1CucAAYd$|ycR>7 zy^5jM7Vxzw*0$W(wGWh6+f3GE#l%S5PF-(*XsHNul5E8*_IL{e$xa1tl9H14nJ~b5 zSV@SOn>epb#-Hdtk!-`ICj$bZOuk^&o68ek<+NAa zgXsdbGrrd=v@FHMoc&(=O=NX#gCclbcAnndeA#mHSN7RVjGVU~5=RZ6%3F7D*3Qsq z-+%}+Y)b39Je7ER10Lh-UcDg@)A)8nk+hPXuLe>Rlb}m43t9 z3XhAm;9HVySx3nTZf~coIbs;>}Tr+_`AQo2~p~frY4@aBDg~ltoO2H zzq-3DyR+_!q0O$qQ~bitEphofX4JYCY|wm^ng982jnhx(+kOz3fznj$f{*%e`_(Ju znfZYIDeoh56B-J={#lPVDq-OICnSugVz;jA320W-76;3nr_^n2*Dv5;VwKNzcA-SD z{sz~bT{m=`CAKqxtY;l~0ZfFU1brTcy?vKD)z+G+^@f%NGbnALdC#4fjhS}4D>odh zW4+DEf%|@USLH|j5vP?%=XQ@}xXvaL7 zMI@WYYd>Hal;k%6yLuTe0x@6fOVav5E#fvXkiz|4Dxi>u{bS9*Jf^DX0nDy@qU57p zyu0fo)t=Sfg#J)smbd47Z_mC!o3f=s9p9C}6cTD`Q{aD3iZ=zXmt>A&?haCW2a8P1 zW30?d?UHs5(h@!2IUic5OSCk;L~-Y;WLkG(LY^RaYUe$Rd~}(oCu#pGJZ1>IehT|+ zX#1{qL4DaTiL8sB2?p9)Xlc2LEVml@>VL5qNMJmZ@fwo&1S8b5D1u%}>36r&O4qb+ zeX|}%om-G7o@CIRVXmCv>?mdOgOfKm7mE=7W zQKKE`$zRmf!kswB@d|=a;%a($*XjJKlgu!3Tyd%|hzmZW?^RW!tWhut1(XV`{iGoYZ&E^m%s4k)V|Jsu!0=R8S=Yb=Gt?Z_`37rRNLW1M>Qz zOp!d-2Yr=>X7nvT6mFHB>KUO5x2KfKyJuJUen!~hH(!+m^A=Y{a#4Dh(-$!7cn{w5 zA^&eo(vQf$YnA9eF3fg`FZM+brqyyjsk9i*nmXN^Yy4=^U^i2vefaWGP~NTSuVG~E zg1w6=7b29_0G0wG`Td1J;+o5zHaWRfs5QN<9OMN*9#zz8try-NFS5YHc_-k*@cc4P zj;7o$$6hut3Db3j;&RZzml&r^DTS+zhPG|zZ=n1e_6PgBf!b)N-(o;t#Cdc{2c|ntza7jHn>mf~eG$Vri}E|Yz1oin16J~s zEV&xU=|aIq13wrQEwU%S^B_~$l_#Cw?yXI>^sC;M>D6(2FfnzY=LDbM)rqAybf`kv z9J#NWCOas6PGpEPXhFNDvLi^&giB5nrva54L}^4lY$q4u3_nt-b5waj6m{x^GELvz zBIV|?9%Hq6Lin&hnX?vyY9*BH!SBOam*Hn(9BYI)q2RxMf0P9}5w9~a&u3b0K6>GP zRk;jY>cl}~l#Tp#H6k0yMZCwYk#J#?vL9Si&qYr6ZfeM5d24cfB?LvriH;!!b90Uc zKicyi@$GYr1d4t7QhUe@G8F$3hRPWnlO)F=z(T6J*$KJNFYRZbb#%?vyC8+coR+0` zMM_)FX5;)2^{5&#c4)i6V#j-JICEGq`UkfAOIpXHurgRWPis_4Raz z8i^@MS*v%^7b-m5y}AiS4wV-Sc-DqI1U^OOTYr9*dhBGbm|v<2q)gbA@)1PtT8BqO zkiW5A@T)yCIXdmG9>|WdEkSY5q2@n1Vo*D-nS}QDE4D3abriuM5Ol&vVWPN`TFkYS zyylT}!3WKHbr~6#Cp$AM%l@r4{;w}DhW+gO9q;I_n@_9DQh`3JyD!7wBz4G!V9P&X ziosAN$ih3c?IBp49m#6)I_s*AuyeYI118hRl0x&FtB2JHDa9vu~hsSbsj zrAr3(dNbr`Q&$xR!li~4(#a`{+ci93d z5*u!OO%1gI-!ZFrOqwVDMqYSQ@}5%KDJM@@AVFCP4tJ%9?$rVT9enwUr7Vu*_Vnv* zYG+*Xu2ba9OpCr)qU(ugi)xa|BXWKCvw768KN{vPKls^y9AmK@Lcw0>je+|V~xeBV8jM#(y`au0%=fv!7QBg5&TPe zq3sO^QPD&b%I>xEv9;4YAC*v)iUhG_3!%_F&zds&=ANtjkmYv?xee!dr9>vm62?dX zDbdr}K@d*`ucwSf6T*mVj>OgiN>kNoqV96iNtj!FR(Ox*D9Tc@OYb_ytmfOf`gaZ& zXn2WlPu$I~cNfwQDh;^(#7-U`%?6(h)~oMpv9@&hcQdKs%k%7GAllc=p$Ue>QB_Wl zFy3q(>Il+gH!*a@v38?#HIPFE{IFv!5Wv+p(6Mf!Em8j66X+s;CCUJMgOZPxJ7`5C znA#_A3ut9nKtmbV{eFp`4DD;CVxf+CBJ>i(btt7LxZZrV7va<5vZ3N7D*E!Zj`-aw zi$r-g;Y+H7rS2+!E`PT4P}=K7OsxzCdk}q+z>6>BkE8AJuUW{HM{Y*hZ zsnB0>6N$$Lp(_MWU-gtGt@so&C;I21t1Y{JV`1D(Jl4k?gdZqAdss_HMpVEm>OQoppz#G;+`F-B!A;zDIdy} z^L#4^ocaTSK-bn!!9CTjww`8r6HldM_o*|S#9!>$R%sF=3`C|$uk<1UfO+G|*d-jh zuf5rIt9pX%$se}mXKIu>{R6!J1#QFC6 z!j@kWq^58#p{Jpdh%^V$TZ426nL$LpJt^%>>b&jP^c85tn9CQ5z&$e!m~w1(YE6*$GNdY|h|YV&4~N62PT5G9GoMIlL{8n&3^u=j<+Fyys)}DQ%?yI2 z#vZ(SA$QLmzDXja|Cf&RWdJJReadHTI@y?S^$GcReAe&o#tkp#Y{5@rX0Vr>ui6l} zQPNSTDO@scExtDc`D))Q<`N^{t6z~|+%6!l2E)E98fW{RZ6DVSr>+96h?|n%Hb1_B z8VwWw6@mDU zHc@8iVBop+v+8Mxh0*R*c`iU)awP*X6k?JiSGr@;qbLQ^8uy!LCQg8&%FG_(G}YqQ zLLuSnZA&S!sh+FE4RltO#yL&p5#NKk&kxa*_UD@h`6l($b7NZhJkOUxOg7GUrz_2` zPLbxnb7W2hqX`*hQ-J?q+EXu;g4aB;AkU;b+G+CUpqn;kd}XL0TN+7}3R&(5J8hs5 zg>rp5D+fz4?ObCr%s1Etf#m*j>Z(ACQfn(BBAAUqE9q92FLO}cF5oW=Fv?o5xE4toW+KIYuAx($@f8H1qvb9!XShzw(PA#^N{zcswOJNf9?WhMP$BPg$62w} zPW>~WOANx#mm~PrJF|$_|N0 z)1t2HZbkfCE#SCNhWue4w_vfJ^1hu9;D;L$EuTX=DJ$Anf|A=m(i-6R&AQFlozkFa zBW_n9-|oT|)BGjizr4=u(K#7IK+~pX8aW zlMUPR_kXzN&|2TF85|@&>Pi!7zU;L8XfRFk+=~O^!+U!mvff;NqtqVkIw-R5YfQ@S z{k*jF(D54w{B5hx;%ymJ!S(K*u@0p1_3!O84d2WV2MvJZd|A88GoY*Fe&_A9%X>c$ z_bfopW!Uu6?_RuAx<;+^#BeTAAJRdsRlD0CB4=uA_a)^r0A@Rrw+pcUXV+OPCfdDT z1*}ZqLo{XbKvZCRc8I^E8Kv3wWSIjXD40F&-u(UDj-tlUea>|vpMZj=gG$VEj7s=Z z&r^J9Yz)Dka-Q)r=~oW(vlBauvsuEfeNS0%NxnUl;{U966pkbPEckFpWWVYWCgXZY zvk)h408v>KvApQg5yH}01qE`FaNt zGR)yOGK=c0F<$m3lak9Bb-0ys4>9rmG zWU$SUN2f`LSgXhB`}v#prZx|hXJu;;zoGAi*_H2VPu!Hx${wGD3Otr`RhnoyUmbOMNaX=B?LPLWRg3&ocxW?O zS}oB*J|S%oaM)_|AcBljj?*^+?(TVjvg=5)m>kwq!;ao4<9rYCVkAQhYX&0+hA z^k--k?ugdeab)>QoiF$q)baDtF97D(?bma14M1h;v_Nm+3>mb3f?-#oWUk_uo8M2Z zGNk9fBGB6wN${X@ZuZ$`dzZQdY)a?5(!EGDnusK?qbNTG?S*s9tnhJx_4%0+u8YR2 zJzORw!EBX8Xo5FZst136o?RpjYaIc`+(rFYNe=aiEj0aQ!$Bm@bfrI;ZONKhh|m5UVYf%cUGb#Yd5U# z30(Y8fqH~!%^pYQSB#PURE(dKO%|GcoPtN|D~Q)zUfKPv8sHZtz%OgVb}m*r>Bs59 zknH8YaXqLS;FDE?&LAI~Y#yp`S(Cq$j@(Iau~&tb`En^EY|AMb6BZ>f>D9Cf_tBRb5!lp80v-Q1hZF#rnoJNa#S68$MW%mN;6~ zm(75>?nM8E?u=ma^HHC#o>wWw22Po}a2+b+&J%jq^4LmM_@9UVO$yN=W(VP#Ma@Ys zJv|#!oK2^Ff0n38QbPZB?vW~-YW zv6R?~3dW zdh)Xi`|{0v&4Ob)-YNB)ZAXmBB{Drx?!)8F%*O9IiSEU8H`oFFl#;g2H01L;4kGQ< z^`!HJY6DS^$bXuiyZH972m$3<&+-8phAk#Kc%Cq8l}=qCQGxUj~CS{aHdbSM82 z;dO+D%-T$dvoz*$rs3LUI&q?!GAOAw&Z=OhIdQP;d z;ixaIXdzzF?bOOq~T4@g;Wu<8~SiV@;qparfkIE zmyy3RmD}0pWZ)@>5a)z8NMF33-`R^^DQ#@bg$n6|Fz7dMhYA@VIw|)QFn!0!4SLay$2Jghp^vwZ5d>m0vji3|&ebdgUz{}18cD0P2jd|( zNuyVspTZg6W@tOT!|-24CKDTsExp_c9xzVPlW0vkkT}2)(u(z68b>jU8yC+W%yCnU z;zYs^Y6fRm3}7`}u)XW^kYWZ|LJIp}1A+MVD<&k7kI-WR~`IIE4^A zX7KUF`FWoCcv{JNzmG^dJ|R8NUZcA<R`sG;KBCVa`i z>TzD0Ao}-l3EA0(vkk5`S0>IsTmOhjeSk2W}XUX zKHi1brM_pYAeEOP=P?cSIpcgx>-NGct6esB$G3hT54gbOkQXG(^Cz~2Xi8;BecHT+ zcXLCPz!k(rM&c0b?b?@+%H@m@9JtnwqzC@OVjDipbhjheKANC-mAGm|qP1;SLXLP< z%!?2f3`4xSb%l=lU+OG$mrySwBzUn8HUwrY1!c7Wd5*E3mZqY>W(Z z)1w-=3;FRpU~{l#0w?$d>Ry!DplNv%o8KEFIFGg8m%k@Rbq06$+uzRH3Sdp;f=Jn_ z32Cu(3I0kHVViEydu()~@5ojzB`kzHrr^b`$37x^1+0(LUqlr)F&`nxNl^eRu#2MJ z2wK~1nsuGjKfPH?Hmuo~9I7PdoX?=>c)T(HQq$1D>nnUG3E$oiuFgKRDg8KrzQJh?q1|BFZ{}CjB&^?$6ExX4kp5Wnq=O^cPV*A^0 z!~K**d7FyM=(A6@xQ}PCK1Wz`p7mXL%i;~ys}O${V+$x)G#Y@z9Z4oZ1}%C1d3Lyz z282W1HFh(*e}sbnoKSvS#^ZeqY$w{=3<`%PV0F)*V29TZwhFCW8*R?BzS?=0G<#KiM9q zkodw2GGOc^W}BI*b#Tx>9kPotdRTS<>a(kzBQ2WgpKtP7kz(g~5^tGe`6pKyc~z)c zRLljDz-8*fYrh20{LgXbA#R_u2&}T-c%xZL`ka?s&a3h+D^UVnb`HQNbWcpTwIzpQ z+P&_mMBLa_QWD+q1LyD0s)9+0%d)KLG|gvpcl2k(2?h#n&|>2<7%+uQzk6un8<0E5 zcPz<{qmGqRX~aCIhB+dSwu{Qle~L+#eeeMPwfvnEG~Mju#oodYm&elM%2}o~VqzAq zu`z;03@rV%1QN)jTXJ!%OmY!2@dtW~uMOm7mcpij>}J(D4}9oU8P)3=uI)}X!WW$u z6*4mdHH4m$3upE98h(uq$BlTllpqY>7*icNMXxs~pc4(S@OO+os#+XJh5V)iR)Z$< zW_dtHHr@YoIWJh1;Jx4QaalMVjd$!#1Bf4#!r9s_h|uQ}*s+43nDSD`PeEDy)3Vy% z^&8#F4*}^D+g2XML#IUJwaYE?_t8o$D_mVCKSG%M(<ff#y5q(1AJv)P zk{nJ6M@!T}fuOGo&}V|#w|NJ28yca49o@XNJ5D^TxD!z_S?t>*$u%436TvkACIIROZ!-|8GjxD-u`?x|k=>RnN` zj&(~JGmy0vx4a82>!k|N0mV z6px8?g(~wq& z_UbnP)P3ERpd+m#(Is+_`muUSQcNkn%hcX}l*8P{=N}m$hNpO=0$6=@VNEIFS<`P* zb?1@}hHxpXXmBgs1NbkO4A*LMc>%rGD>|jCIj2YihaOfZ`s0}r_!+s5?v#*UJbcJ-QtPzWk@BCDY5d4I(zyY)tP^QxkSn1=?g^T)E zUcUPD`Y|kmXfX%^HWm4^6nEhWTbF>V|M}$hTl2XFSGR4CTGnqs`9#Hk;YtN)IbX2S z8S>-_l&q(y^j9n;hTmWs--M$7R}@Y{0*h)IfZsaAEBCP0t{J*MnxF1Yms|A_J{=Z2 z%MkluK-kUQgws%O)t9H5O(RkUH38F9aa)R2;eca5@Yol60Z(L!G*jnsEp@(7^Q!k}ACd!;3ATr@4Ud5>u$=6(U!(*BT6n`T>Fvi7t z9`>>zZ-*KezvQxry6lwHV(4C+~)2?u#~wckRUFB&mmH(U4nHq?Zj5D)*51&sST|B;DQDY`MD%`K9* z%cv)oK87~84p;h z?Wwi_Z1t__N(;TzL*QzjUx@iGK>>ql+TC4Q2a<9#Cl(K4F#rcpg;W4yQ6&R9H_;C< zppx<^{_QPe0%Je`EwP-%SBl0U@a=mRj7$*Zd(xSMs2L0e|AF$J+I*B^Udr(Yau=NKN2v9gFLs z+(fBEmLS>X_)%f`mx83aZ{PO*s*FJu4HQ~GU>f*InYm=(0l#|t&JQX;8@ zc-AlTUd}(;+J_&hTt#o7F=CK^0?w_DklO%Gk0#rR(ooUvr1fq41&De>Pc`n`nWjeF zPT4r0Box!Ipd&uxBvFfd3))}Zcdwxjv0i~k0bEY>uaaps0DKaIlX}q^batz92G9$T zn$RNv39T$1lD@#{n%IB#St*1XEKgPRq0HcvH|z2F3hf;^+Zz?Ex>-!Q!e3ZMxZHXL zFto5_SUl822CeH8AUv&igb|w`uJ$@z(S1U>jhu$ic=p)4d<&{w@D(Ywt?-0HSXpYQ zVBYzhYFTf|S@-8#10^Y>UMwO6%bG8(wf-^!CC&w%l$l&1i-UV2iO*Ve`l#ydP}>@a z*LEH*G+`fnW_%8u5A_udTF8MD!viqaY5{8R}rGc@1 z)6AG_YQwTC1FFLsgCXuzDO@z7L+8dDGiGzsY&s9=>l^u!4R)zUn zNVNE$d9vyW$$CC`6yV070kRQP1-&DKpl1y|KbkU$nZDU%Tn=yy;Ei4zf0Is(RcNdH zYn`NKm5Oj4SXhc4XTgCkDuN+YvC=}q83Y31UDVzBWbRq1Pr$$1ENKL2alxZ5jGh5C zfXXdxF0Yt^(g;iboN6fIwo9%^vsy5kZ4rk})^6eFaNe=zV+7ZmTkL~ohX*De1o*qJ zHOcM*=49x}AVF}VuQ1oAdGUllvGS1~Go2oixOE+RaF);HhhJPA?V)^Dqa9E6`|5<@ z!CdlU5H#*JbP~=>SpH#63*4(6N6MdPKGlvo^j%9{j~`X3P*~Y_*{JaJ-l-i5g}nPc z`$AR4*VoqxkXuW-zvr$5?=uvyejx*XfaoTvY~U*O$%3YSl*zf*=s_%21Z1Tuu_01> z3P2zTxk-WGoTm^Ax@RIB)>YjwQ4A@8Rk~OL9?#=VTLFdpp832k$N4{8#U6OaKT7{N z&VRFe{&&0V|E~81!@Hs=hb_nQ1W2of_Pb)_W~?zUmqprt^TSU@O34GB)Om z1zZ^4UlX8bWT*JvT&Ajhmo;mAPVqDMlnwW~CFm@jST4Ck{BCc2%PG4k{+Z&J6g*0P zjlyWa?)xh|8zId09>5b7G9{m|kI>w1PA0qUX6jw?bn2adPFI*I+RiY>h#GsI?pgsJ z5eZwR?~Uits3PajaY0cuK-uRL^E~Fld-_V=6K+Z+;X7*!^o?1cv$>j5fdy|~h>&l_ zePjrT$^a+6tCzw3i;lao4^Z5Fjg=}tlC)zF(3 zjBDCrOCS)|LrQX(g8JTN1FWN5myIEFpd3wr5Ws0!;H?85!g8Ndh9I~3uYlJ+z)$&n zE|0l^(hxfC3RvK&je8Ajj$)i}RDh4%;uurFu?=dm&3x(?M4|nYUQAxL@87Hy0IE*A zT+e&$r~XfS3i;%rV*t>Y`cSb$^CJ?7ZbejyGUNM>Q0+a30>bOWJ4mDvP(908%y5nR zzr0+@YV9`}{h@G*<$H0sX1+E0vm9955g?p0_MyRX;yr~V07g& zEM~ED==pV`SjPm25RqO*j=;A52 zIb1HivMFmaH}F2_eCpB!KtuHJfZ&G%!G|dT(0q3zAmGu}JWx$j)Rx4k(E`uuOkqnW z;Y9LGZ^3d<)9ahJiSn>qIcIv{!;D+p7WlW9v7W*y9*XW83GPBTPYWvmFa_r zBrOq1^$m6|M;-9;_2@ER?!R9?xXJWoDQYi7`dvrYJJ?G6`32EG8fwf=d5mKiUc?5H zEPd`Mwk7f?>ZsXvx}xqj**yjeP$zOn5gi1UqU$j?nM;y#8VI zIpAIae=B{oK4{m3KZFGCb}@YiD^V`JDH>E7zWhjSy4JzaWh2vF*FUiB_{~BF8HQxo zWEa*CzySJ7h6r*AkPE#EbMePC16rW})j@yt4AQGeOL>5-Y*^s#baQ<>9?c3J{VYsC>8>{avw zzmTic=Ekc>0kb9mPRJ~%;#vwJ5U(YqZUb)bm>_Sa2e*#{@mJ*hfgkC!?&*gp?j&_=vskVVE+*SD1#`DugUERi%wU}V|ewRzV zgg-)utiU-X6*kg5#Ui1(a0JtwmlB|SPyzrxkn-<-~vZ7+w>K>n2Ki_F+3|5Ca_r9t-}UWRcw z)p>P}zd3Y~i8spj+k^Zmlrf?7b@E_@%JZ~|p*g5{Wq&HOdcos#zxA{WV!51x83_wy zo6&u8T4&gL`r#il5<=%tddX-#<($o6xk!Qi+?5|LmUG+fao zyjIqO>1;ZxRc*=3PLG2j>cDenh@iU?3}pA!KKMXb733&hZ!VgtzFyuq8hT`jG&utR#ThdI*9$E>0QJg&ve#FcDJB^sSok41KtbjM}XmTW+MD|bYtg!n~ z)B2K}*3@q2+i-^rFt$D5d|5Mx0F=V8d{8 zuKGA-&R(pi+!Crt_T6i>Oe+Xez-!{q?(oorFJ*mN42esXPJJDDsdo-4f1PO%+Fm($ zBcB(*q80r8eOY?YlQ^I{zY%E&TB*yx*bgOQ(V|L}r1Q91gORyH`y1#h$=xRj9L)qJ z3?M}Jw;(0EkI-0>cPtBOi?J;JzKM&ZxcF^>PPKW_h4aEt?i=aQ$g4eSt(5DNlpyo1 z_?F6L7@2Wk2636iT7s&0<-R?o@p|6ft@QvgabmdMAL&H*7!UfTj1}hvr)HsU-!ijU z9x0HMP;c+>FehN|SKq|J-!E=u(VpCgbgc-?@Ol4L*R7AY5xquxv2eKP(Qi50@%(WQ z8|xOJ4eJT%#nIdgcv7$g?3c?dz&9W@68V&Wl%QAt=O06@C9fQ=XRG#3{Q7MTD@?;E z1Rae?b(w$)`=Y0gtDv?g1*6QgVPd;2Y3ximVdY9U$ww|$Z`>@Ux9E@H_Y($de%q;c z1;SvNu_c4C=fKC7$NJpR5K{9Wq6%&%34cxqUDjJRa&Itp)tm?J)+my@Xm&fKA_7*{ zr|v|b&Yboz#-sp^BMnH7rao8CO}P)f6dSh8-{(J*tL^TC(cO>lNoS6;e2R2W)9;i9 zKUzZi$6Zg1C%0u(l=gdS3c519=w?b9E391qpye7wP!9C^^c&CYHSUYENdsU|%;owV zu$fjB{?xlzT>ULK37K=NhaL?IAaPb-0>Q36MhMZjiLaYi^|VhD{_q@HKP7xF7ggZn zFqYuFu}Cb#$ZSBtk;TIh?79N?=;-k+|Xr+sbj{yW3F@j z!1bXA1<(;*0rI&3kou6nr^*~?pSa9AABLCdaNqJ1M)mI45+J37|EgfWNt>`PC`$?xG)a?Ir; zyX~@9SV9tI$J#Q_j7ujbS7ORPZM{!Vb^^FkN0M~@OmW1?7w_9!*`w*kvMwLFfE|E* zl&HB&EeBKDb48H8ER3Brz1QVL;~wLXc1X!^S&qCt$_*VXgu1F2 ziQb<&$Y#5Px7!t|zJ=ac9 z?!NVau;BN*PHwUb5fW@K6$P1UnfB2F^)+EX_g{m0J;L?oLyzh-NJU?h{Oqk>c(tLj z%bPdvSuWXEZ--zCrL-|fLlQ9I3w#~JdigV; z!*GL--cJT7N}^U+2@y6?1(e){u6c6p4_Emg7zDeh|H-#-9qTk~VIpfEe}j0=D8rRf zum7D<(U!4==tl2+X7rt8EKm80Or(9uzBDnvi*Di#jmcm$nU+kNqsgm z<~JVKS4hDQmHgA+r#e_M<rWKrMK!bnbrgv_75AbC&@k10J+MV8{$KN{uH6W2H0%r4}>fj|~(qY0MR*peLg2z6AK~L31M}%@&pwM20|EH!0&|dXMmQ={b69BY*l+vhfaD$U|_-pRcYDNKFIxuPbTHZ zw%1gh<0bXcR@{e2VMOIgGxL@V!2NVGU#wLT%rD4+>KTH*u$}>YNu@HcNYqCAvDj7V9U&)t^NgOP8jcRvW=Do#`)*mUDy;m&dKc*P5r`8jFSEqoypZ1*U zMs}}irr!H3r#JVhvo5j##pF*z!}rr*o*dJ98us&WkBNS1uRBzTM9THG_e=;N69mt% z0Dy`?RAc6yie~GlW%j>c?9}2pAfTmz452@IuH%|c8+)l1S>?28d&)4$g+!1f4d&u>LsWXZ&7=FPf$BTBs}@tqhkR_-;HKua?gn+(H~^ z+r|W)UL&6XPa>Xh$|N9yrnGTSxN#jbBITJb4DXE~5X`kX2NTsD>s&|bq2WSDJX_ad z7kla4+bHUk*466t@Qr@zzNQi!9rUNq_zj4Ml};m|gnTLE zv6#d>73GHsRgXyq%k)1FB^64CGTrB`RI)^^WMc1C;>1=2I(IA1_b9@Sd}bmL8V2sK z{XcoCG5rrAw2@jPo^TZuIi*=+G>yneUWQ{q4_I{IVM#5i); z1n_r!e|q`q=}4^U<2bauuNGo8;3PA)1VY$WlfaT$?qgmNVkQ@pdT8+7{Tu~pZ1TA} z^^X#jG6jUbb-*D5QhK?{>5(Meo@~=) zbW~HmvW&&UTN?cB$q(GZfcR5%8$j;r?eXs~3Q?mun{|n~A5|;nUL)b}>CS(__Y}7-t2SLNEKA4tq&0zx=ag74jnoenbWPI+;^QX9$#gn0!dp8=-+D$z^8yOE=`- z=~d;aDysxM)%(=l1i3g63oE~1CklGsWw20?V z_>uR5&sirkkBf2pGh{s38%x&B>K^pfpPzRKu$n-%g@Rvzijb!G7lAHtqw0G|EMnK_ z4t;?^49wx#El}jV&0*;xLEELxj4PzUojzRaN8Xa#2o%DkH4VsZ%D~!y^vt*G9kTS( z8%S&XLF&G>lt>72!i7DyqrGn#n75}t9;rz#?*Qq2r5GA9V*~QL-Tbvo3)$c2P15r} z^kE7kHgQ-$nB4*h2a`7fx1OYH1WnkXm36z$()P%e3E0+sr!qB}ZuoZtGa}DVF zafE8`CsURQBI~P41ouRw6;(odPLZ9t=SbTs4pFZ3(vSVQg(-qgIhmGGRwqTqPEx>! z`I4WmaMxEAOg%wz@+adzNxtC7dn27O8)xF&@=i?jg=Y08br!C9#9W<|Nz&N9_I>z@ zG~4O~C}yFb^0{de5QfPlOyfxa%+-l$|5sAx`7{3ENp6jW?j<0lnf+)sMbFFv5G)Lp zzz2zU{}6p*ysAfXqgU0R5@8b_DMWFjXW?0Tvxgv-DMu+H!-Sa6E~#7&dKuKK@|em@ zmEdHn*Jv{3Em-@Gky9Jo2Ww4c?H#}npz{4(MGCjcuCORDm|1w71B2ej=}lZ@uBAdu zwQD!C0(pwdSAv4jtO#<#9+P}1)+WRmUyM57kOIx~`F_?TVX4y-V?XSEF`BC9s0M7A zlV|_Tb;!d+Jq}mbL@ZlEU!S{4(`7_}hz`ZYBW-tR2nofU2YyDW4a@y*?EF0&>lWAI z=U{7WV^G#}*7A}+nLkNGgXQqWtK5{%P{zhJJ(3Gw{elgqJhkZO;`*1=%OQ>gYaS%X zCR=@#2wyU4P zam$MYfe>Fytp`0E)Cs@6+^NB36REYG9xeR=w-}~50r)60E#d^9O)CvgSCJ9>rCBqs+=OBenDg4fMAjpsX zMEi-~6RUwms)U(2Z3mD+ypP|A^S@aD#72a2387H#DOB{Cn%WZH^c0VSoW#uPB>q8n zxx%lRw=Ha*-|Li&ch{KWKD6N!9WYQ)K^dV-NGLF%Tq1|&w5)}PkIhC`dC19zQ|Vku zhK|8*0I$<4>jW$dldHyNV48Hw-i5OjZN>0ns{i`p$ixi5Z+2orzjggg}HT~hm)w3U7DR6EwIZz(31evH+0P+cBkC?;_&UWw)iqB4mpMx&?8s| zf%Lm%4t=Hj!RYb}QNW@6;R;OU)k@hB0+;bVjszk##a#O)8aU%o(@AOYA^k86g|lDX zLOH#`vgM_mGSlI#+4rQ*Tme=mQk{Wu;bA6O12!Wl;lg?A{Stt4| zR9UGbfKha*(C}Cii1XH(v50W)-k6?xRW_G$*i^oa($eAPg3zSwQvs&#Cr#>9bEj6) z`QDk+`idbKycOM1+w=;gw&Dt~W%su%y6rgXJKm;-eZ;aa4!%A?zO1=mhnQTSA5auU zyaOW`bqugC=$NKRftizGAXQV@TLX)}&Vf~oR9dLAxq($mBZ*(X4k)S>rGKGZ#SDNI z&@>_QvjF;9_tkMUdO{N7$TX*mXVr3YN2=J_5x%mKbmCwZ8O-ki0kg!SoCdR~MMdVc zb88`rX+KS;Pod8Hqlch`G+eC0)jTlvTMVg=bnkFFA;-nffPZObo8FHGF$IH35ojwU zBETFbM*?cQFA$h#lM1E#_wN?`eafg-sePX+wgpDeZM&GQ?o+LW@lSb=idg%BMAU9R zdo|zc-4m~iMVDcW~EL9%-E^ku>i#N^J3lmUhqS)nwmrJ*r~i;gNM@dQ#?N*x)#f+ zs${kmvr2{~8J;K<^_5u*IKMd3asEX0YqwAQ9}LQ$BW*x{BUuvfd0@&VvIE%0ML&GI z@1f$WW5D3gA6>aWR}YPkF$&7&fQLA-(puP(q@y`SfOEvXb`4|~dEOiH01#LE=LrzX z%C9%oO!?)8hqF@-rHwez*QIUA&(fb+j~)%W)K=VBP8(w*x}ra0%JcmcWbDqaCD2?| zKGlrq`5y}Jz%NMq05Enna5OBD{psfd-C__#nacT}@;e2*Z^qS?Dv?6m8>%Nny8T@& zG#3ce4Hs*dGmsXxQSzGgCDt8+=s8yl1>4rPA#<647Nl9|0NinFd>s;Ic0`C#PyLJT z^{`cMk#4;yb<>vq9Wug~fi6l1*^FlVBp)T?++Cszj*Lp{QQB&{-4GcF`p{2-w_4Al z$4owO_|{GZ!w?uWm83un< ztO57Aa=UT|=EaJ^LV(lkaj)q$&SZ}V-QZjbfhE|_lVrBop<{yWbNAB z0gSfB!KMcUN<5H9M;pU2Ntt3^ZnN86z)b_fHZPRm^Vl5$2U9$7s#r^HCzUtoN}d6T zFruro{!Xow#i05OXcKlA9c0Wug&aVbC-3Rxd+;Q+!J`Uf@MkcfB=$4-g(m1!n(LJH z0>kbDfT=|kPFt2Q$@P;+rvNqK0!+)S;|kd6r`-K`+qDIF3bDvfk^ zPrAFLOAxSVkZ$P)3F+?c&ON5C=j`V^d%y2F`**&a^~Dve$(;AR?=h}%{jVSo`NXVU z3-i5A8XQAUHil%kcK~k~I0ZvRpQ*}Q5U}K|gTcO!-yEy`G_5DRCBSmPLM)k~uiR{; z!%bS|P2r9p1x>+{CwPAf!|ZoZg>+Ui4;r-n2qLbYMw_Yf+}FFXXSzP%UGju&Z!(FB zi+R!0DI2->! z$9Z5O4jd@CG}AczN8vauq!@*>n@%<3i!@BlnPsC0#uXZU3u)?>r%9H z^!+GXy#{3#z44|g+qt2Y{-v^Id^TJ-d7w;1|0n)xis) z0m=kcM%AJSD+ee=#2j3Un5%BRe((qGy8u;HvOkJ@1WI}lk)~>=?XfX}-x3GekTt&h z7jEJa77xK7vm~?-$Ta#VXso~$ka&xu5gqzn5EvQDa;$0Ozy*GTPa|*6I>)CB{yt>u zMTK^o5?C!@2k?EinhO@1pFs{Mk>IpMx%A<$U8pw2-% zf8q^k`aYk4kdJ%)?b9G9;Ec<|5rHxE6>dJjurlGl+~o88UZ#cZw)p71M<@YSvbV2U z0Pr;Vci^~JU=8crveUKVuvzV&7-%PS$rTQlgn{0DV1X;qiV zZ39`6vO{+&D`i2;6gOYt%c|0#{y^`DO#Y1abMx6J7|mcWv}7)1iPB6ju6MK4=}x|= z$GJZ_v7Wk;aP1w&tD3_su^w#aYJMl87(83&k_Hpsuo-mxp|QbwoU)$jExak$B>EGr zHI;WI>>?1ey9E4P)%F%I1Bb`OLA5>8sI)soVZY%#L%mdAlIe{OTM8%JorST`IPqwwRBYZzLyP(Y#vSP>vIr(U@(mfHB-xV1!(AO zU#G!Mof3wmj)8A@%B4yqAMG0d#DT&~?I7AVR`Z2zO8^QEjBjCrnYIxaZr&=VpHpK8 z4%WQ&30K$+Ro<83KP|CYoK_yvi+e8+|J-5INGaqSRKh{NpKGn24L|eH_2t!S{NVPN||6P^pV~NbytzI=WlTVL*@#+05-LcA46zr6h=OT78&E!%b*>!)ZEoBc_}%lU z6@QNGl4nw@=@CJ%m=j!yWM0uKbD65q*AxSzExpU zSk#uN$BJW!$G&XLbFC*&1VhLrurBE(uVqV~AI6SGhNZ}}15z*IwCvssN)me#G6FOHa6Rr?U{E z-pp?jUQnDKG7-n_79K@ zA|_mZ5ktaj`?El+hP|?X)tA)`2^nqC_b`l9YU%=4M;jW$3ZOc6z`s~hO#>Qpy#IB* zwU_L_arFp`D1 zgAf9mX$3jrs{=qhy)%7(^|DFjxAyE{G}uoVqct}7e46WEPJKX;>&|%`_oI?m|2QS# zvu}e=RO&o=)pXJGB( zaQUql7zjbQ{L2GT^q0U8mRrDOuOOZ^*HUO_`7FO^&IfFWDX=&J0P79w_j;kgM64uo zTmFOwfG93tLu7}g6#WT#@&dxbw{Cx4F1S1}w2K4{pSjN&m;E8x2UzkPY8Gq~Sy`-J z*5SB4_B{MKKoiQd$V~EN&=u_MMO*n4_Vz}Sy-fnJC+Y#$ymjVhyUqQnie50X949LI zbPCIXfLZL%Y_@p8*#twukbh+eEC(E5aYzeZ^!#ydgEdT+bSx^e-k)YdC!Y+U>ssoB zNdz|~lZ|(Sr}O8L^Xt93L3Cp7J~u|#sJTqPJ*<2X+sRg;TE}O+RlE2TlXh)+UnR`H zuwC3FTv!Qq=IS3PO!CMz518E|A{{2LMdNj2*vtt=09_1dPV(c6<><7-{(HM-mq0XH3=+sSv>TK0r29XD#V|JK$B7rT7l zWe)nSf7J)<36u6D)TXWx+g!$d$rGn}K=4+GM9Bq?w=bM%azN0S@%`OxLLSVtmmsOD z=O?-n*a>eC=0fq|1;M|(U|Lb|*9$1f8qqw`4_#B~Ieogi%f}6Y!gp-jho|*-Buq~K zOCkoQV5A>oX~WBGxIG_V?s@lhABKe!oaU07{gIIH=wM57HUd@>JQk|{qv8)doN<`Q zWK+8cfENUhZNz%At|?URjHshE7;AbL9KEqDS=r3NrpL)M=V~x>#%(oaSl&=#{`(oD z{DGzJ1zQ(MW_@T_WhXS~fs!)+X9uSJ%g?)atjbV`xu1l1G*CyTzSG7Wn6I{5E+~2V zcnNlpkNr@|3UdQXVQT3J1>k6uo9eYb2FTG_^X)3*0^182*3lur)9QW&RFaI^1z(IJQubP7b&Q0?Y2B=C&^K(=!YUb)93 zY3F9)w{90Z-;0D4n*i=w8jU?)z-yS_!R9Y!apT}~5JnPl{h(&>v%NYoJ682RRb2zq z+Y-b=m;Jf5{P(wcPoF(w-*38aBqcq9W#w5*$MA|rm%_r4$i8Pp3tx44jenNKRZ&wj zwQe|lCqOs})U)PPyIjD;6h-Ut5I4kW<5#nAp##$q-9gu{j8A`ftHO|nVsypzM`5#w zfbe3j5~6YQS{Ilv^|YS+@g?_deqzmT<@65pb|p?pzq8rZdzpB?#M8-WGIMY=&=qai zs8DGigV)kv5AFwNrD5(M0^_YuR-)S~me@mwgat;eOPU|YcH=4!kNbJPXBQ2>!; zK!^;_SmQKq>*nCvz*DWnW*`WmkJTlXY>hSms$(?nPzF-b?2UrU=+Daz0vj=qr&X z>ZrWeZG&rIHU==z4M7A5Y9j6`wbn(%qgyIqoNG*Q%z?Rco6jeT?AL zc2m@yQic*IU2f4(5ud`Qbz02qjkUIrnbcxsZ@$hw>$%It?M5TLaDR=w z&;X`C5?19p;n4a!>>?RhnCOx{VBcCEpZwe^VY%WVw)T}dNxvoFeBPRI7GKYg;c!en4!RhK-#7f_ z<(>je1O^0fRE#YFfy+Kl_9O%yNAGeZO)2E3DxYB-I@ayQdd~k~96Z^GH))`sY$0^M zVgu1M9Dr5W4z$e#WUjsX^zu9DBFlf?X2Oc9$ z?6}1UKK=95$J6PL&$rwSuAE}1#iJ0R9Dplk18(06&s7d87#@V=c87HaAdG_@Aq^I2 z_|}GPsvxzur#iQuZhnhg!BEIeFz=fN56cfIEi=~Y=`D0^&bPy(Mp{n`Qw{>*%*8SC zXEhlhNgI8c2>`8;2)uaqWJk#c&{)yTlIty>!Pdbd2>@_?N)TBhoPnxY0c-*aV6W*1 z&~&+HvEe~deNR`~{EgC^kds}zr)M7-JI_grqsaSl0}ONQpP_bt#^mV<#AXeNrnrW^ zV^u13TsoZ$x2>nJZH!S7CKgEwU#urU#^lkOfQq|0hN0pjgeRr(o3IShJXvvI_y#jj z$|$}IK;gsh7UKqatC80?{j!mB=?ua1HoAe8cN)+jh1oiJ*4e4Nz6_P|64vBw|KctG)1Uu?S)L%Fm4xCyo^uc(>pTRsavjE`M(iwj z)egrgH{elsUR{vr^R6tq+^w8?+FP=803L|c`M&%5J{Rl$kA-zF*OThJyPzZciDGp< ztlLLBVe#rm`TT1+aa@{pr6mg>J!(h7VqD81aOy zP7}0w%_~MTmSG)!KL}qUa6W(Oi{97d_%>W^)J(g5b- zbbe%n$0+eRK>3fbR1daR{a;IiI(5`g<6;nTU$$g;kE_Cg=?qje3u7_nAQf4VSO-F= zqc@x_YbRe{O@fP~b${$q(H~OWzOP9Ln^VQPZH@qNv16hu!#PcWCj=gsac5#Ef=r12T@YkoqplZg(Zk_&PJCy}^wJZS(W;9i<$bhE z`nPcZ^|C-bC7KQ*pm^|(ex~_;znCn>4Yu_=`sU)$kF_o-JuZKLKQM{S^Gzfg6(`B& zsQpY5Y&~-4qK}(%l{k2`l07h29bxNCcXtmBsr6p01W4u?Z9E4Hz8hb%!^$`@PO$B> zmc-d1$lw8-G8XD0WeflN+ALN&sh@$an2qnt*4bdoM~lQeK{^n5zzuVyhGqrW1!0n! zJFpI92ob00S{;xo>)9TSq}#`7-p<2A=|b&aPdE>%GHVorrJH5aje9R7>gQ!r=p*Mw zT%PFeYw}e@vS;#l$gQ7by&lzzco_3qCpXaaJzahhriFE6NU>uDepmIXn##@Lz53Hs!Lxm+a}=)B2J11S z)1v5P<+sF-ms|`X5V~g$kkp4)PmGrbB8o%Akf_>EssHtBrs7X9W8bIf_JcTS7~ELR zMgS@_a4QBCxc{ARlN|k*;^hCE7}(?uzC#zd+}{ZewUS3Q4dwk+*|jZB7v#3wKXxB_ z7WOn!%M@*~_pSUk)8aysfolo=UBZDPw$+S8-e>v|d}bBob`3r~iv9eK(Yx)C*V`mu zfIORgjBTKKBkDX&8&Q+DTqUp9Z0E4!O%`co;5-GEwkecFwbB&XMKy|2{=Q(CD}CgX zD7*upip+;}!1A2_pBiBL|6T)}EJaRv1oMNzGWB3*FtBAzj{}|HP9?UzHhxa^sJ%AfO(RIQ>&q^xVj_XthI?B05>OJP7sA9CPQi}9aFgW2b=!j^0B568f z?PKQXByzkdu-$*D;vHti!oX0n5Atrqti)s9NW?>MU}iCJ?zLFjBKk8Ms^wyu9f`xJIkCw*5>|+L(w5u4@)z{hV!(p;|m_WEnBZQqYfFT+d6I(uj70IPLz`H7m zu36}dP3=fE_h3yjLT#}lgw2AC%amZi4`*4+-F4JdU!ox^&J9~!{eWkP-LkVf_E_0n zceG18jNnq^_T~1os=j%NNE)k9i)q#D{UmP5d2D3KV(A{tNUz3=vaZTlt+@0vuDbm_ z*B9;%{0S!`@u;X`8YD;`-a0b4&7|YW?tQpZ{$O&yM_QP_aoc6#VwM7q;1&yD#-CUn zi=%NyU^6c=SwUkh(EXh$me*>E^Sb7bJXjVH*k$1({9E|u0>`Tuy%Qh01K2!-4n0|)$J z`^8B>_jvaH`aSI&ywA{?$dmUMX1Q!jp)*iI%S-p{pc>sbo0UtYhgrDwkcgWS#6!be zRjMj0Ge7efb^_T5+D@0%Q?F(%|@lkk#d7~V31MhopNY${^{w30pKo1i!|hqOxU-$xAhxn zGn<-*JkIv=?dCtQx`D$dOz&*5fQ`a4W9tuGp~@OM@JE8WATHf*E?S~5Q$(b4_z2{~ z>o0!(bXrn8O};NjrPl(rTfl&V#E*Uu9ncRG4@@Ir5WVTh@ke@8It=(w`)jaZ>Cr#e zi=BRI{^^Df->VgUj(#OOM;PuG_f>|etC^x7c9x_~;@w9kLN4``7W|x!6hRyllESPx zoul*Ljm`=2htVCmp`KNp5mAVdH}NJf znrwTqfu+015hX& zmu_ohiT_NFGXOUKf5M;f2LjMG>Hq99I&j#G>?04x*9%0WBa$oa zoFw#Qxhp7VT8x>At;a%lev6&h2v13~{?r~yJ+S;*{oujdc)D?~k#vF5!F0FnN&8;m z(@_=fdOu#-P75Df*WHTo0{m={;Pw@;W|bg`jM|p{Ef%1Qfef8d4(HSe>~40H)WK4h z;AHvmzY!rPZ~pfpWH~QnUaT^+xxx9N2c@&ex zX@E@q{S)SQ)@Q!*qH_Xr>2Y#>z(2@>_0Q&%Gosnt=Br;YC{ zVBE_d*A}m={HUoOuF-{(~3Qz)d$Lq7~Xzg8zfmiuLFFVc}}fK?QdXX{0>zv%z+W&JK4 z|CBa%=x3XpTl(K^Ki_niP0tgN-mA?78s)fnjK8_R*`?lVU*{|6z&+N`rZi#0uD?{u z6mC!?o4txY#Le@3%O+w%*k->o-!$)%@+*?Vc}I=BOjJ;c5;qZhTlC<2P#>Y-qW8h!H=!7OpK^|2vHd0mHS8c9o2uHBD_I4-=r4)RQl@wP1%5TMu;c+YP^Tk@Eu@WE? zC0EiHHw7&5PULmj;96)`zkg&2+xHQGtD|LwBerA_>)B&FUnHlyna_BK@S+5h`lI0D z>W~m8cE^CE9fn6!CqTP!6|bUrh&BBBTkFh336*e*_2Xm2g{MC^-g%R{W7!k_PLyk` z#3we1^!`0~ay1mzTeimBjnrH$U-C6~7RahOO}RLla14A+e3?@}>z!rz(=Rb&)!Ie8 z#-=;1Xqe!wUROEquS`<;BRTmZE$c3SBVU-*0hi(RZnS4gw(m&P7HFUe3rAwVW!uP4 z!(=^cUq1`c3BNeG;q5v;Xlgse!5E5dmyt1;Vg`VWlZSTq4;+=~&9-Wlg@HB7D6GHC z4+BwDE~{Ry%R3#bXUQ?o#)&Aups(A4V^HI)huRBf)zq(ogW0Ps4Z7#33|W7H!*Lp;ta{p7bSDA{&=8(kOPnqYtedJcKsa6yz(XkOquc%fgkjYhqOjw$fJ)V znhofNfu7Cq`7_lmR?Tg&$lb^nr5hAUg1^S(I?{=YRAOOTH-!wr%jz)~!1bm$IRdoo zHhOvJo#tUVrjgdgeB%xCOJ6$qCO;k+h&Il8BF35MC$&mrk|Q6R-r)|kYF^q{&zZ+~ z0hhnrbo_kHm<0T422l_FWQ}mvW&(~=0l$Ehw<)=an=4iwlb;VILx8J@>$r|@?8oBN z#3`SKO=yGduOe7J2}i3H@F>n+93FVFmXsXA19-e@cwezpPQ{K+SDnjqMFO2NYb31 zW5w9U)6cyC49#T|pBJ$I6T<5^y|+*k@4xQ>SbQP>%0-|)|0>qhG;2qui z_u!G1HM$^QS3{S3&L6D%cD5LUz=q!H5H_*1p9bnKQC#P|(9&2-WUP%}krCl39=v$1 z;(d>F(hy9cKfKlB;E7D|a#UX5r!`Ro@vIeQ8oerVx-;@SK%x73!;yfgf)Va78}%3+0Uv znoHDbp(9s-Mt}LzSV`^NmGnCuTl({=e+V~k;$E%y0}(PWID6e&Y z^%6=`Vsb|R?eyM>$HQk=KRI-b>^%EabfI6K=KtD`YNjX00z@2+lCYGxaw>?@IJ?Yq14S zEp%~ZcPl4axJP13yvRB+2$?)PtMun1bUg~nk{Qns!Z}uQ0mQ*5p&9Jg1mUVW(DtU? z)VkSvI5XBHr{S&usoJcofE5dYAcFk|ef)*nQaT!~MqY$<-Ivd*-xe~uYqjOh^!#!vO82f?$!n?Z7w@?v zIc##^{u&~RIL9CeO;@e81H`SV%yan`B>WZxm*>;2Dsp=)Qm&tlzK#PcLm#($86HL% zY;I8`mSdW?9mS9$|z`7O>p)}uIXs14F3&6SWJWT%OX^=r&`|&`E zGXASl=AI;>KBVAU6`2w>>l4bDD=VekMlEHBFU5=FRJA61l^1imSiIDHIDS0R4tm7g zVqHfR7*2GgSW0uwh0`>J;iPIWLj+?HzyG=-VHwV7Elb1a3L{TM=LCH=x%I!QNGKIic2{pe z8NfNUA!VUntH^pQLK$vL-cmk`jEpX!<#&`XD*OvJFK#(nE6-Fv&UL2NAi`_(Zbwff%RBd$N;g;EhT7-sw#%J`qtYf7@)hJ zLSekb7;;L1p-l|VamSr7f!`{j^_BZhFH6(Ob%D+bEC}2LrR-|Tnu8Yvlj54SAzm$TszAaJ01xc)k{ML*U6 zVKw((_tSO-S)rdskgO-z@Kkl32DtLFg-yvtL&32@+!*f_GI^uf*2tUBe1m2KdK#wd z;#fi}VAdMLB|)I~P+{oIaG`isvs-pH^{SS7s*75cl~Y^v=jnjc9Pc3Br(X12X9dfk zQLzXIm3f0c#c+bf`l)+Osb^oT1kMKlvDc9Fe3H42tel($m5eH1i8s;*BKBA=Z6h-O z4`N_eSH4;hYJT)p3|yCxzKzK?)^Xwx6nrc zlF8WC;@_5abu|`rZWt+B{3j$Ujh8F)9Mc4dkq|vPeJNtLG{)mH3e=;*OZ$2?plr<~ z{rzqkvoytuxDK@hB36#L!WPO9KCbC-y&uvh3q9Ff({lRauuI>T5l`J@HFs5ET$^E6 zk1zAic0+1Ybk zZ);CTR~4`BWX}7)vDCuXiS$@jJwiZ2Cy1F0qA-hKVA~IeYFN$^_F4b{R|G~BF#B=J z9)8-`JrMt&Y)}6p(=;4J{&w?i`l*&laJin_b1aGgNX6sP1b;T6#i)%>aHV!f9RfeF zjdBe99}hFL>j#gwOC?ofpnn?zeGr_KJN%oh#oQQwJkw`62Gr90UYa@>Uk(wW@3oQ& zYPE^H$wS98k2mwZ$XD68?>sj`H!?A~uCEfDr#Vh3nlMVMvpyO%yE!?UQOYqyJ6c_j z_K!2vQQ<*TYG;%gSJ20T(TR>S*=God{!qnZb=0djK7BxqP{zK@(H-(UyFvbNCqW zvC1!j@TUHe?Skqg*@FnRqr!^?gVHScxi0bLH)nAd{a*7J71Me2MlK&00(DoAf*Q7;6<_npcWy9gJ zVhQo8C%8W|lE^l^ADwa}&2-}9D=Dcg$RlD?Ju{g!f{7vB$&n%bouNVlXLGSOBJbDl zPC8Y8oMFGI1vtP3WKdncwfQEWvIH!HE2<;U+bmkbsy~~2(Sa!$j4loqbzEO5eLeUF zGAZf?(eMMLCXB?1j=wID$`)P8dtOcmBfN4z+62`GrLSXHn>BrWN}71 z$l^dS#-vN|$^QJjLOO5hoQL>9r4LkB45-o*FWteYYp(^3#W6|CJ`|t5tpcocyxg3c z2f!t*7DOXGXLV(N5wA@`XoBzZ<-&e0yea?wO(bEHAeIuiw)F9hEpXxgH(WaEOQ)KX8fr|iF?YWdp zG`%Lo&@7SnHnVenO_U9pz#2JxlJB*$3;9F8qI4~7mIjuz^&b8+djW324;=3wzCCuD z49JKIy4WcjykRvEq&Rhg!w&b`XfA63%_j4^090hulC;(qg_MH3^Ftcg>9W?-1=!g0 z=m$dU;6tn))T3XcB_I2LAgaC)7;Dp46Glj9e~sWj>8vaGqfPZ__~Lx={`>ATnK(KX z9Ye=nFFYhmc2(K`pbP~6&nW{t5?4lh!VBx$&I^>^jd{tLWf4GI;*Qk;CS^Nyb{$cq z+Xg(K?WoRApC^&!{hoBEAfSw_|3v|~1GY9V+?1(P3z|47U!%=@W6F?Zanqffs16y}@{CYHJzO$8vlnj1tBPF$o8q~* z`+80#)#xH8&T54Zy4pO^`@AfwINM$)>nX(1sLd(cx+8?7)TT|w-H99@#+qBa+uqSElv!(2*^n+54?_|{a#IFN zCgu&CmP z7v<&kc!G+Ge(jHRId7(c30y3`ywece9@Z;eANGKj$duO(E&I*%TlAadY|WX0o~hpv z-p4)Z1@oOaLBi`ARo@^|(PX>FC0I~yIv_&l#DDat>FB{rMH(Yq%|H%S_U%VAY`VP>xK#jeuifVJUXW zDyIQ%wF8?2m_$9~Hq_6B`To4d2_KRpkI@b@w^p{+sd$f zp7GA!12=n@^CAvBN<^FdF2X~eSDmDL&5|res|i7zq~IKY3w=OyT)Detdm1>HK_bTMi)(OCbUCkXcK z-08#Z^`!)C_tUK{FIz>MbFBMymRlRpKP2c`M||QhY(0XMFjP|zu{GD3aK01EQEYKH z9=OM2)3&~H^m6bJY84g*y>6=fn)h$ zz3&Jw(ftepxR;Z{c;ExnWD!-q>H$AQ?y%k{>+tCplPn%V0fI3t9lxNzy+Q-mXNtC_ z%vh*F2nPLjuH3E;QpvhI{Ug|jc;BAp=T0RAY==HTiKKs}>aHTKrfiJ^<|!l#g7x=z z*Xq|bED*uJwL*gM>6wNuIOo{pM}~p#>(LR8zCNKt4tmA8Kng9^Z>-yr_HSTatO||o zm7YzLJr8#APT*+jR>|yJaaj)NZD76JPAiswE{|ICwZPC){|+{(-okw8086ra`E{r1 z{rWaY3Tzyro$uJr$wFekWIaa;s$fIB ziR76(=r95l!CfIp6$BE_ALw6dHXPa=fF!wa0Q3e)@jjzfP*?a}1VJbEi)D|49vTmT z-k^X}%;sc`nZKcerInMO*hiog)h+rbFe!qS!nK~68#QDPwbpUi@1WXX>bLd8uRHZ= zVDi{(x?g{?J~mnd7CGn<(~Sf-$u-s=q^VB~xrDODra z2Qmdq7VBziEp`f%sG|@(^SSnq+!bVqtz! zxWRmsgb}F-E5Q0(WHR-GXr#!))@s8G#?N4BVJ`CHinnoP2z%DfcWCl^fS_BaqoqiY zDF_tQ?`_c3zIwSTw7B~?Q^}*(sWOrKWfJ2eT;FV|7pV?1H(s|dNgc#+J`y3kZS{+O zdXhRO4G2qc94O$FOHN+wlJum0KioK^zJp*BW>&|*THuH^YVxxyxXQJjHw*3?)6%n+ zT38dKw}A`>N43;z+e5lOO*>u}X#=zG1)RHb{_T+z+%3k?X`d_jQqV|2J@0J%b&Xy`LxFEI;MD zJ=vk$!Pl{M^S>Od7Q&pl9rtFGM}BOtaICx`l$h@F9k$Obxel!~7oTKt1fvw0OKk+q z3I!4nNl_gjM$lHf5fS;@Q$~e4UY5lfwh)%> zpfYxBs!`v1!VZfJ$byG`zpDU!KWOybo&&sr`~mY)a|AW+kZ;ALRAxX9v>>W&mY4k1rR708fEtZGTGp6<9NsKUfNRNV|J5xY4OHtfMn;!WnTe&!(q>G|gV3y~$ zp4nKI?aRk;K7afWOUf9>=poxt*&cP)^^2k(-dPH3XJQKz`{bM?_9=Q?bdZNW?R;u=%Dt1ur(?%HR zgf`t4T=cx2*crG9vM41YsMwiwx}_om$R47dtO{y2?97?=WEHjf3)Wv4x!hj7^kC{5 zpKh{aeAlpf*LhGjv-ts1v&kaus&{luj9)_=eP4l3?*SohOAp9MAQiY58ni-dLn*qCFixk*yPNFmwpOmD!9JpYWTk&&R2Y9%|mw$&)I@HiDc_a7TQ z!!%FZ@eE4dW}O^18m7Gm+@{DfPqs zJk)heiH^#v-nGIc+amY-Cfne1E0^~-XGMLL?{Ajytv7yU6#-zhm(+Cgr4A&Z%!acx zZotYnjSF%eK#Yh3(7MVBpDrbR>MkfR|!L^!ioQ2hI>=l#^9A!Vq z8cr|Jt{Vf7rTZSpy2&lWQWk5t26k0+E?mF``U=uv8;!Tk|G#F<^niBH8WjD)8bFT4 zKGoOHpe3vsf}YQQjYdGjIiljx8L3KVlDCsR`abuBa1UG|=03%7uh%D+W+&3p>v_PF zvyoy6Kj?L2?qGi-FzKX!h`Qp<>Nb{X`!c>#Y1ih+oIzpwuC$uBT<}WpttPkAehI}{ z1W7u6XmsCyKGj9kpsP>47(*4 zJe{?-%=CKG*e=2EjMpDInk~$J|Nh+;B;qLTMNs{(+}V7UP3AortL_V9V!zL9*=_YK zYi2NC_bkwnutS00r|)!Uas+l97`Z(H&L%fIM=)iooxk04X%up#57e@2f0EL)>70%3 zU#@q>S_m$OGwN1Pb8?2QwBs+I30~+j>14={p;A#)Gkn6*)@}Wqmotb#Bc>w4+^G=f zkB-TzX+gULP7yBGL!}?6Nt>P*2MxRW*RvG8d^gJ+*%Vz*<{j6#b*@Zi0HgsKA$Oav z%8K8cRSBtJ9Zf^RiKo=xSx9>(;bjLL$&6Pj&3Z)sVATJRl-o;MAvUlvTUQm`3iO=P zd-CQCDV`t!e(eDkM%F`d6r_73gnf7!oE_%DDZeZ4RIr7eCNu|pLO#ZDG zTQt7#+30xa0T<6=6N;r|JSd)=3dfnKv`Q1nz?zkl-Izi-r}SbHA_phHipMK#&6*F8 zyb^ZdeumJ_hhYih5%VuZ3UVqMiGh;=@ZMA`_C`Wj-B?Dygg_w$VI;Zdxag( ztYe(Odb-05I77{R67`<}x%RJP##d4;iVinBqscQTgMcuI9aO8b2!3A&s(nvFSOYCS zGje1}ZxH92LbHI_$Jo0B;Z9%YsLwrW)@PV`M)f0m3yqX-oS8@?4O@k8qKFuu;)|+_j3}6BE&^W3{cIn zQV^QHE%_P|U3N~cPQI@FjahV>j|VwUE4hl2e7SAtrys8QqHKbL*4lmoqiQYi+x^nE@21u1f{cTudTHX9WpgnBt3F{Y z3AAdncTvUJ&M;kjCSj=;%5qn9r(&g1D?T;W=BZl}{iJ=^`4d{Vpd=oTK3P)EBdk{j zn{FMMb+-aMHM8V`Bf3+ti1%>BwooYlu>LHQY;2A>0i0tw5VW6vKL)$P>u(f#V!AXo zGtvUD+tGaBl*zokJ=Re8j*PWz^hFC8{r8BPSWKmzjHt9AG6n?t36Ua>8E^zB-z4uBIRtO2VOoZ+z7*7ZGumwpeEq7v?a zeTPMN087ezVQF6tH(c)$V*5u9H0^uXh4a6a+SLbR9WfnN zrfrzINYcbMMxJH9!G%{L{AT5C;doO zuWL=ybyxXyc6}Tj|Bkl~)7nL$&b;yxTd=9PhdbCQgHqWa>-za-)jK{Sj7VhzeGC~^XJCkaFdu?l50cpf5dD^x z!dZ%N4~Q_aD(w$?1W$g6TLZ(nx_AIK%<`cB7aVEKfs#Xq@zvxJJ!?xfY;rxGFetob zm(}Tb={ZG`vQ_Sig?p*p*oEq?d@*@pwV=gbtOw+#2f>196+AFsMDtTXb~l zQE10xHV%aI?zN7au?`!?FN66QtZP>BRNuZ`g~d>=6)DCk=k4Nv{|7L0KMXoCc|gyc z3&=p{Os{?f{>wAOJG-Ih)+R=~<{eE?BgL_O0~GO0hr($UWI!pLK_`L*iD5W8HQdkd zJ=BMmKK4gwVM_{Q%BekoGbxj-Rzk62>#09ESj~oMnzAi`ZT2WJ35%{=10;TB?u6A_ zKANBrOi!5vxyRipR)X~@PvV~#g3g#SX0ckQIFSZ02~dUig~-GNCz#J^arPLrh(}^^ z7_y_S0T*xsHp18S4qbL5*JN0=2cdrg;*lmu@X7q(0Ql6D`XF?|rJ~xcywOV_HR|xD zwv`Lz+^s#EvbaV6Qs=yyk&(FF?6{L|@YQggq|6JwELQQOMx8vHpFRj*C;}EAbu%%& zAGV8PnOJ4Dv53ko75dsKG|1R>!58(XW11u)Ea3v zhO1Y>Egj?{64u;tIcdYOW7cE-UE2&n>*(g8&5&LaDH%&WiJq4$pNzwhB>ts4g*x7D zXDd#T0B#GHzah_L5C*Zw4SM*^;y*3m`1M`0zt9F9@Am^;DH|Hxv8u6zuUc-8Ic%!G zCEe4-tzCO6I%BbDX1Tg zSxbF59#={Ii+3L=R{PDp#m|!gFf&Z`@Z&;fVfL(b3GrVjY=B@PootT}dVeFeYY}7; zY;!-^_~0P2dh8>Tdgu5quASglq~7guOhoI?Pd_3#3tkIYPO!4@f(WLKk&=&3Oxd=8 zqTMbbA;Bv_s~#)lG5V|2AE|lTHM;FOM_jyB5mPM?{l}YMvw9H^LIOZt2du8$Wi>(7 z4b*kWi%s{T`G0D&FnAa$d2~Uc#Y^uRP@Tz(@A~Rpkyc7AX_FVT4q>TkanT;q`iV1O zm+yw}xkV5AMyl#WMK+eXJzo)i>;;XVo?6gBX1?RvHE-<=uVX0>MdqdxlFae18_8Ao zjI%c)X2nk-xNY~OdnQxX=Z`-B>9U}87;OnaqV>2msz*kXG6z7=E7z6dMM#1ns}WJ9 z=|wO9AnWV*Fm>N?s3puuvfzQ_d6z{m1ix`wQ-CXRs&fo#v)kK!u6-Xfic!} z$*z&}i>G#F+SQZusUZ#rFC1;LdniCLWZo<*)vFf+kq z(0Vhn^q0>t+VF{=p7^WR#_TL}u$qq0k~vBOm2iP(A1Zzb+ZcBpi&*3BndUw=0h_*r zaVBXrn3}^4X^SQ)YCy+~*sGZqM)Qmu6D=RgHJ=i|4(!r0!j9Ki-JJ z`IWHkztjM9VOB8q;2Q>c)CL$Q{AGwiPv6=VJqTmI|F^b*O#=*@0ji2E9h=op0Y&G* z922%Y21*9V*)qQE@PWjHHVa}qx7>5Xi(pin8;5Umf}94R!j+rmPxjMR3!FLla$G(Ai3a^k$jwYL z#l*Vmpgm|5Kb&R317+KvpI&rtkmGsCXN{K&r!J9!G1gzMXv+fguhjj&a*$2>uw(;cz6d2%yXtY0{f-dtE~jR4V0aicG0 zszglIe>*yCe(b?IIs&M(L;N2^SP93niL}K5h{=;r{BIo{kx$eeE7IqxW@ZKM`N%(` zy1w$_c2=Y7|4O9Pz4^@qORwp`Z-ltdyJOdGVXx^(H!!sPh<|7A`jEwDg$H%;w~>i;Inr z4U53}A@bACtq5#n>F!@2^>U(Q#C7R*e{h0I&+|jFh(tZkP=(_`I|52UL;}}Zo(DN? z`-VQYsXvCx!;f)bT^&EHc^Ppj#Ynr-+)>HpE*l}AIp|9xAQAEG8kxLL~5CHoc{iN=y5U0a$#U9yBMF_uC05T&t1 z64zE&_GK9B7$LHZHDf1=#+qF`pOK#5?YZ~d=Q-y&zdwHG{N~SD&iQ`7XTG2B`}0~Q zhqF_i*6Z^(DNP+aMSR!=fZb~?=Au6h_uR8@lYe(nW8IX|#o~sD$|&Y3?>Ocg!j$Cu zOFPJtL#saUGQ!V4zc5zcte39G5BKXs2b=PDuy2jOkGHAj8!-l7M%t*)iJ@L?9vg<1;@P|d$d0a6r2Yw9lfpd53_h@byb@iA}u`nV`>Ra zfxgvWdCG70CeQn`sI{h?V0xUq>lzFdR;zV+Y`(l<;p2j5C{%CL`DpgOa8iGlR3>0^ zV!Jl~S5;{{yYFXBJ%edQH*HSyid4tj5rG+TDdV!2mzO*;JZm|zgbsz9N%JE)mUF0L zDiTlkdH~AG_8bP3bZml>4v=_TL7}rUjAzaqd{mIRf@?#=LZt?KLD4 z3C0NLa*&iVofo-3d7eG^^v^)wZ?2k=FGgebT2f>_w=0vtX2nGjBzxH8JO(?yTk21I z^psU6Ze#=7b$`6NgZ(y>Y%$4OoD|Lf$!|@me?C~EDJwvj?y>texm?jx9xQN;hSSC! zZu!7AFtlpxF^HJHv7M@=OU~W!GzxKJ5WHpn)q17xl2U*5o&~mHy9_7k=VM|+lJr|* zR&@Vp(Eq!McE)f9|6y_VD7VeVOUkqgdUjaNZ6@UV+03c4!OXhTI;B9pRK+Zgy z|AvNauK@rlc^VV{kCHT}noS!?&9#Nmg)-;SdDzaPhG(DIZLg~V26k6K_2(QVi)9_~ z7CrC7MAJrq1i~RS3D7*PFDURK3kCu4U#JawfJoO@oOvad;-Dt5mchH}xYCy|-@O|3 zJEC`dVW_{>U#XatmJP(0xPv?B9;Ij&O7r2v2V{<%2&hW1?^_1Gfy`?KQm1jsTRWm_ zqUTrhn(vw@dET}1s9bxJ-l6}SnzFJ<>H7+ej$e6DYfns)pG=On;~6j8XMC-bj`dREPCk^cBt_7)L50aFH7{UpV$@P^i$@`Tf&z!;emCC zyhbXPeAOCnJL(Tlg*wF8x!JoTv*31NjkRa{$ew4JNYPl85*ZcA7nf)k;1@53-~bcs zdiz*c=7Ojvab2s@8HVoqY2fQsn{Du1*;xnvZy6H%sfR~|rqR;+t;xNLPB{E6ADt#;M+@uUBLx&84Xo~a(9|?ZdH+Dq95{p+Hmxe z$GqVI-9(6@Sr5A%^wjD`@)x7pm}M@WJ=hn`FZy9g=&!ms@>B0eqoJllo$W=}Rc3J| z>_Ql=$};rqv;zilB(h2mF`vWXN-xU_2?I!5%+S*w0~o4kZ=`T{DVI#C@ZJMX(=ruu zNjF9BKKD+9j}kb917pY6Q9|~kg|Yge;lI?dl|qFIKuWK_Dqm$>Y-cHCUSr49n_`u= zKizyQu$=1f&d(&;fFqY`IQV?U72NB1ylkJn^*dVoegT)}VZNI2Id9*$S65&{ezmG= zdF;%2`ODmtrjO;ztpAjr!HX-4%_p`A)uI&K>w14!W+RenYg2PfE+GZyvw$&E+6N+y`WYEYsXzd1eQ@+KPsf0HDEQK^`qD_>y)89w)mNI&)HTIp8?9T zMkT|4Z`Wyp@L+YCC$Rib8!dzgVr<$*K{TT~6p2d1O~qcSdL)nsGBpT=Y3~Cqq%8?D zr_L@rbtg1rf1YSo!ePZ46IPHrh_Bg$CGKG*uODpJS@MZRWRlNAW4$88od~KDTFfV2 zydkbesd;XDI=SN1P+9dQ`moImC~G)m#XY03(k%9rR<9JzM_XX>Ubs2~7W2$2>iFO~ z?GbE@M@9sciZ)$!cXy)=;yU#XJ32EwS7$q6a}M=jt(C30DzE+oKloL|`{sgR>_-=Lu@Tj9Vpvamkv+n7i2-n8*C! z?eCHmZ!c4_-f*+8em9%}xp3Tj59>|~5liL<9r9OYG4q3QKHk^!7`h9AP%bvbt!+!X zO;`DzJz1uaF@mz&>5)lUbRm(`FJ1n700On{!g~n(R&r7u>Jb2 z`!?*R*e+k^OD zK{rTqMm_eJpfEe1`*ACj##Ne%Wp&=y2%N$kD~v9m>dj&2_OA0#jyIeqo%y0rjMfG3 z*19>4c}L(U8Ih$spFIVv<$EOBjWj}2R+2w+HZ&aVJ01f?MlTK}CrqYCO1#BzOgAi{ zO!4<_U%=Y<#@-nysOM`E^e>57$>z79>^kJUfIRwG0&G}?puX6L3>Uo*@@6*|KtZUg zk$2#E=}yqZ++2~mES*cJmg58{mfLwn`e8P>%wgJ~w+h4pT`~ye_El^Gwd`o_vSOMu zcn5G%^NNay`LU7f_vepB`TPK5iDSbC&eL^W)<|Fl&ih+;+2a@=h~-xinRzQGV>=aA z@STle{cq?*Bd3QI7?rur{FJBCX(0qkNemT%=i!LE>zauhebvgV8U7kC0BRQ^+l4lR<_v8gbT`j<~HoG_dCtcS~OC>d( z80G_ASD+KqR2&HL3@2t?#nUzX10{+|wmy(!NlW>n<^a!<>%U@M66(cDjP>Qoj|pW_ z+N20uN4~>HNU44JsCx1MJ|dtl{3U#Zy08x)RjJ*$MbI=|U?+q$rtlNle+uVvNFIg? zjQVZAUO)DbOIonFBf1GN#Q`6WdIRBX%quilYa&wNOc1w*TT*v6obu+meddJLXoIYg zL7@~nc5fbGY2c(Wc4&;uTi^3&?9*VWL!YxIHT6@jHb9J#7{Z9ceqn*Fp>Po|z+W5o z%Q~7pv@?NW72IpdLlxZ&5@B~!9tccu1CRKyb1^oaGHzT!qAsy3fRdWk)v^HV*|7do zRSdQwctU|sI`tvh)cRU~tDkcFjS4Tw*OJILp|dtWoZQ&&hG0M-QlLIG z$^W2(;)z}y2Rf+5=10?D7kJOP_N#|53c6$|7}_Ok=?DW|8!}HS&Cmx@D26b^{r!B} z>x%zVu|%J^-+;Q4yA2vpp9@5>TO}WF+^WOXxzA5I6h9JlRR)%It#X6tM!DdVx_Y@> z8S4BYWSQ}cC#T?*!vdT{Z(6U6Yf~UP2$={9yi+<=r9pNbnDerSZi( z1W~~Tn~$ZBb<+BqXwYXnj3sf3Jw^ZrqdAZiY0C4zHJ)srg-RWaO=u!!S*AJ0pHh`1I5ud#N=YopWdJpIe`1)kCLPRA5!w& z7C%_&;#qOzBwWt(95^OMfrDdQ>dXRjkUrYbak^lez diff --git a/components/drivers/usb/cherryusb/demo/adb/usbd_adb_template.c b/components/drivers/usb/cherryusb/demo/adb/usbd_adb_template.c index c5a2d9ef9b..24f6d7acb5 100644 --- a/components/drivers/usb/cherryusb/demo/adb/usbd_adb_template.c +++ b/components/drivers/usb/cherryusb/demo/adb/usbd_adb_template.c @@ -164,7 +164,7 @@ static const char *string_descriptor_callback(uint8_t speed, uint8_t index) return string_descriptors[index]; } -const struct usb_descriptor msc_bootuf2_descriptor = { +const struct usb_descriptor adb_descriptor = { .device_descriptor_callback = device_descriptor_callback, .config_descriptor_callback = config_descriptor_callback, .device_quality_descriptor_callback = device_quality_descriptor_callback, @@ -274,9 +274,16 @@ static void usbd_event_handler(uint8_t busid, uint8_t event) static struct usbd_interface intf0; +#ifdef RT_USING_MSH +extern void usbd_adb_shell_init(uint8_t in_ep, uint8_t out_ep); +#else extern int shell_init(bool need_login); +#endif void cherryadb_init(uint8_t busid, uint32_t reg_base) { +#ifdef RT_USING_MSH + usbd_adb_shell_init(WINUSB_IN_EP, WINUSB_OUT_EP); +#else /* default password is : 12345678 */ /* shell_init() must be called in-task */ if (0 != shell_init(false)) { @@ -286,7 +293,7 @@ void cherryadb_init(uint8_t busid, uint32_t reg_base) ; } } - +#endif #ifdef CONFIG_USBDEV_ADVANCE_DESC usbd_desc_register(busid, &adb_descriptor); #else @@ -297,4 +304,4 @@ void cherryadb_init(uint8_t busid, uint32_t reg_base) #endif usbd_add_interface(busid, usbd_adb_init_intf(busid, &intf0, WINUSB_IN_EP, WINUSB_OUT_EP)); usbd_initialize(busid, reg_base, usbd_event_handler); -} \ No newline at end of file +} diff --git a/components/drivers/usb/cherryusb/demo/cdc_acm_mavlink_template.c b/components/drivers/usb/cherryusb/demo/cdc_acm_mavlink_template.c new file mode 100644 index 0000000000..0a8bc3b66c --- /dev/null +++ b/components/drivers/usb/cherryusb/demo/cdc_acm_mavlink_template.c @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2025, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "usbd_core.h" +#include "usbd_cdc_acm.h" +#include "chry_ringbuffer.h" +#include + +/*!< endpoint address */ +#define CDC_IN_EP 0x81 +#define CDC_OUT_EP 0x02 +#define CDC_INT_EP 0x83 + +#define USBD_VID 0xFFFF +#define USBD_PID 0xFFFF +#define USBD_MAX_POWER 100 +#define USBD_LANGID_STRING 1033 + +/*!< config descriptor size */ +#define USB_CONFIG_SIZE (9 + CDC_ACM_DESCRIPTOR_LEN) + +#ifdef CONFIG_USB_HS +#define CDC_MAX_MPS 512 +#else +#define CDC_MAX_MPS 64 +#endif + +#ifdef CONFIG_USBDEV_ADVANCE_DESC +static const uint8_t device_descriptor[] = { + USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, USBD_VID, USBD_PID, 0x0100, 0x01) +}; + +static const uint8_t config_descriptor[] = { + USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, CDC_MAX_MPS, 0x02) +}; + +static const uint8_t device_quality_descriptor[] = { + /////////////////////////////////////// + /// device qualifier descriptor + /////////////////////////////////////// + 0x0a, + USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x40, + 0x00, + 0x00, +}; + +static const char *string_descriptors[] = { + (const char[]){ 0x09, 0x04 }, /* Langid */ + "CherryUSB", /* Manufacturer */ + "CherryUSB CDC DEMO", /* Product */ + "2022123456", /* Serial Number */ +}; + +static const uint8_t *device_descriptor_callback(uint8_t speed) +{ + return device_descriptor; +} + +static const uint8_t *config_descriptor_callback(uint8_t speed) +{ + return config_descriptor; +} + +static const uint8_t *device_quality_descriptor_callback(uint8_t speed) +{ + return device_quality_descriptor; +} + +static const char *string_descriptor_callback(uint8_t speed, uint8_t index) +{ + if (index > 3) { + return NULL; + } + return string_descriptors[index]; +} + +const struct usb_descriptor cdc_descriptor = { + .device_descriptor_callback = device_descriptor_callback, + .config_descriptor_callback = config_descriptor_callback, + .device_quality_descriptor_callback = device_quality_descriptor_callback, + .string_descriptor_callback = string_descriptor_callback +}; +#else +/*!< global descriptor */ +static const uint8_t cdc_descriptor[] = { + USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, USBD_VID, USBD_PID, 0x0100, 0x01), + USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, CDC_MAX_MPS, 0x02), + /////////////////////////////////////// + /// string0 descriptor + /////////////////////////////////////// + USB_LANGID_INIT(USBD_LANGID_STRING), + /////////////////////////////////////// + /// string1 descriptor + /////////////////////////////////////// + 0x14, /* bLength */ + USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ + 'C', 0x00, /* wcChar0 */ + 'h', 0x00, /* wcChar1 */ + 'e', 0x00, /* wcChar2 */ + 'r', 0x00, /* wcChar3 */ + 'r', 0x00, /* wcChar4 */ + 'y', 0x00, /* wcChar5 */ + 'U', 0x00, /* wcChar6 */ + 'S', 0x00, /* wcChar7 */ + 'B', 0x00, /* wcChar8 */ + /////////////////////////////////////// + /// string2 descriptor + /////////////////////////////////////// + 0x26, /* bLength */ + USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ + 'C', 0x00, /* wcChar0 */ + 'h', 0x00, /* wcChar1 */ + 'e', 0x00, /* wcChar2 */ + 'r', 0x00, /* wcChar3 */ + 'r', 0x00, /* wcChar4 */ + 'y', 0x00, /* wcChar5 */ + 'U', 0x00, /* wcChar6 */ + 'S', 0x00, /* wcChar7 */ + 'B', 0x00, /* wcChar8 */ + ' ', 0x00, /* wcChar9 */ + 'C', 0x00, /* wcChar10 */ + 'D', 0x00, /* wcChar11 */ + 'C', 0x00, /* wcChar12 */ + ' ', 0x00, /* wcChar13 */ + 'D', 0x00, /* wcChar14 */ + 'E', 0x00, /* wcChar15 */ + 'M', 0x00, /* wcChar16 */ + 'O', 0x00, /* wcChar17 */ + /////////////////////////////////////// + /// string3 descriptor + /////////////////////////////////////// + 0x16, /* bLength */ + USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ + '2', 0x00, /* wcChar0 */ + '0', 0x00, /* wcChar1 */ + '2', 0x00, /* wcChar2 */ + '2', 0x00, /* wcChar3 */ + '1', 0x00, /* wcChar4 */ + '2', 0x00, /* wcChar5 */ + '3', 0x00, /* wcChar6 */ + '4', 0x00, /* wcChar7 */ + '5', 0x00, /* wcChar8 */ + '6', 0x00, /* wcChar9 */ +#ifdef CONFIG_USB_HS + /////////////////////////////////////// + /// device qualifier descriptor + /////////////////////////////////////// + 0x0a, + USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x40, + 0x00, + 0x00, +#endif + 0x00 +}; +#endif + +chry_ringbuffer_t usb_rx_rb; +uint8_t usb_rx_buffer[2048]; + +USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t temp_rx_buffer[512]; +USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t usb_tx_buffer[MAVLINK_MAX_PACKET_LEN]; + +volatile bool ep_tx_busy_flag = false; + +static void usbd_event_handler(uint8_t busid, uint8_t event) +{ + switch (event) { + case USBD_EVENT_RESET: + break; + case USBD_EVENT_CONNECTED: + break; + case USBD_EVENT_DISCONNECTED: + break; + case USBD_EVENT_RESUME: + break; + case USBD_EVENT_SUSPEND: + break; + case USBD_EVENT_CONFIGURED: + ep_tx_busy_flag = false; + /* setup first out ep read transfer */ + usbd_ep_start_read(busid, CDC_OUT_EP, temp_rx_buffer, usbd_get_ep_mps(busid, CDC_OUT_EP)); + break; + case USBD_EVENT_SET_REMOTE_WAKEUP: + break; + case USBD_EVENT_CLR_REMOTE_WAKEUP: + break; + + default: + break; + } +} + +void usbd_cdc_acm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + USB_LOG_RAW("actual out len:%d\r\n", (unsigned int)nbytes); + + chry_ringbuffer_write(&usb_rx_rb, temp_rx_buffer, nbytes); + usbd_ep_start_read(busid, CDC_OUT_EP, temp_rx_buffer, usbd_get_ep_mps(busid, CDC_OUT_EP)); +} + +void usbd_cdc_acm_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + USB_LOG_RAW("actual in len:%d\r\n", (unsigned int)nbytes); + + if ((nbytes % usbd_get_ep_mps(busid, ep)) == 0 && nbytes) { + /* send zlp */ + usbd_ep_start_write(busid, CDC_IN_EP, NULL, 0); + } else { + ep_tx_busy_flag = false; + } +} + +/*!< endpoint call back */ +struct usbd_endpoint cdc_out_ep = { + .ep_addr = CDC_OUT_EP, + .ep_cb = usbd_cdc_acm_bulk_out +}; + +struct usbd_endpoint cdc_in_ep = { + .ep_addr = CDC_IN_EP, + .ep_cb = usbd_cdc_acm_bulk_in +}; + +static struct usbd_interface intf0; +static struct usbd_interface intf1; + +void cdc_acm_mavlink_init(uint8_t busid, uintptr_t reg_base) +{ + chry_ringbuffer_init(&usb_rx_rb, usb_rx_buffer, sizeof(usb_rx_buffer)); +#ifdef CONFIG_USBDEV_ADVANCE_DESC + usbd_desc_register(busid, &cdc_descriptor); +#else + usbd_desc_register(busid, cdc_descriptor); +#endif + usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf0)); + usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf1)); + usbd_add_endpoint(busid, &cdc_out_ep); + usbd_add_endpoint(busid, &cdc_in_ep); + usbd_initialize(busid, reg_base, usbd_event_handler); +} + +void cdc_acm_mavlink_write(uint8_t *data, uint32_t len) +{ + if (!usb_device_is_configured(0)) { + return; + } + ep_tx_busy_flag = true; + usbd_ep_start_write(0, CDC_IN_EP, data, len); + while (ep_tx_busy_flag) { + } +} + +void send_heartbeat(void) +{ + mavlink_message_t message; + + const uint8_t system_id = 42; + const uint8_t base_mode = 0; + const uint8_t custom_mode = 0; + mavlink_msg_heartbeat_pack_chan( + system_id, + MAV_COMP_ID_PERIPHERAL, + MAVLINK_COMM_0, + &message, + MAV_TYPE_GENERIC, + MAV_AUTOPILOT_GENERIC, + base_mode, + custom_mode, + MAV_STATE_STANDBY); + + const int len = mavlink_msg_to_send_buffer(usb_tx_buffer, &message); + cdc_acm_mavlink_write(usb_tx_buffer, len); +} + +void handle_heartbeat(const mavlink_message_t *message) +{ + mavlink_heartbeat_t heartbeat; + mavlink_msg_heartbeat_decode(message, &heartbeat); + + USB_LOG_RAW("Got heartbeat from "); + switch (heartbeat.autopilot) { + case MAV_AUTOPILOT_GENERIC: + USB_LOG_RAW("generic"); + break; + case MAV_AUTOPILOT_ARDUPILOTMEGA: + USB_LOG_RAW("ArduPilot"); + break; + case MAV_AUTOPILOT_PX4: + USB_LOG_RAW("PX4"); + break; + default: + USB_LOG_RAW("other"); + break; + } + USB_LOG_RAW(" autopilot\n"); + + send_heartbeat(); +} + +void mavlink_polling(void) +{ + uint8_t ch; + bool ret; + mavlink_message_t message; + mavlink_status_t status; + + ret = chry_ringbuffer_read_byte(&usb_rx_rb, &ch); + if (ret) { + if (mavlink_parse_char(MAVLINK_COMM_0, ch, &message, &status) == 1) { + USB_LOG_INFO( + "Received message %d from %d/%d\n", + message.msgid, message.sysid, message.compid); + + switch (message.msgid) { + case MAVLINK_MSG_ID_HEARTBEAT: + handle_heartbeat(&message); + break; + } + } + } +} \ No newline at end of file diff --git a/components/drivers/usb/cherryusb/demo/cdc_acm_rttchardev_template.c b/components/drivers/usb/cherryusb/demo/cdc_acm_rttchardev_template.c new file mode 100644 index 0000000000..f5fec3f0ea --- /dev/null +++ b/components/drivers/usb/cherryusb/demo/cdc_acm_rttchardev_template.c @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2025, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "usbd_core.h" +#include "usbd_cdc_acm.h" + +/*!< endpoint address */ +#define CDC_IN_EP 0x81 +#define CDC_OUT_EP 0x02 +#define CDC_INT_EP 0x83 + +#define USBD_VID 0xFFFF +#define USBD_PID 0xFFFF +#define USBD_MAX_POWER 100 +#define USBD_LANGID_STRING 1033 + +/*!< config descriptor size */ +#define USB_CONFIG_SIZE (9 + CDC_ACM_DESCRIPTOR_LEN) + +#ifdef CONFIG_USB_HS +#define CDC_MAX_MPS 512 +#else +#define CDC_MAX_MPS 64 +#endif + +#ifdef CONFIG_USBDEV_ADVANCE_DESC +static const uint8_t device_descriptor[] = { + USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, USBD_VID, USBD_PID, 0x0100, 0x01) +}; + +static const uint8_t config_descriptor[] = { + USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, CDC_MAX_MPS, 0x02) +}; + +static const uint8_t device_quality_descriptor[] = { + /////////////////////////////////////// + /// device qualifier descriptor + /////////////////////////////////////// + 0x0a, + USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x40, + 0x00, + 0x00, +}; + +static const char *string_descriptors[] = { + (const char[]){ 0x09, 0x04 }, /* Langid */ + "CherryUSB", /* Manufacturer */ + "CherryUSB CDC DEMO", /* Product */ + "2022123456", /* Serial Number */ +}; + +static const uint8_t *device_descriptor_callback(uint8_t speed) +{ + return device_descriptor; +} + +static const uint8_t *config_descriptor_callback(uint8_t speed) +{ + return config_descriptor; +} + +static const uint8_t *device_quality_descriptor_callback(uint8_t speed) +{ + return device_quality_descriptor; +} + +static const char *string_descriptor_callback(uint8_t speed, uint8_t index) +{ + if (index > 3) { + return NULL; + } + return string_descriptors[index]; +} + +const struct usb_descriptor cdc_descriptor = { + .device_descriptor_callback = device_descriptor_callback, + .config_descriptor_callback = config_descriptor_callback, + .device_quality_descriptor_callback = device_quality_descriptor_callback, + .string_descriptor_callback = string_descriptor_callback +}; +#else +/*!< global descriptor */ +static const uint8_t cdc_descriptor[] = { + USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, USBD_VID, USBD_PID, 0x0100, 0x01), + USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, CDC_MAX_MPS, 0x02), + /////////////////////////////////////// + /// string0 descriptor + /////////////////////////////////////// + USB_LANGID_INIT(USBD_LANGID_STRING), + /////////////////////////////////////// + /// string1 descriptor + /////////////////////////////////////// + 0x14, /* bLength */ + USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ + 'C', 0x00, /* wcChar0 */ + 'h', 0x00, /* wcChar1 */ + 'e', 0x00, /* wcChar2 */ + 'r', 0x00, /* wcChar3 */ + 'r', 0x00, /* wcChar4 */ + 'y', 0x00, /* wcChar5 */ + 'U', 0x00, /* wcChar6 */ + 'S', 0x00, /* wcChar7 */ + 'B', 0x00, /* wcChar8 */ + /////////////////////////////////////// + /// string2 descriptor + /////////////////////////////////////// + 0x26, /* bLength */ + USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ + 'C', 0x00, /* wcChar0 */ + 'h', 0x00, /* wcChar1 */ + 'e', 0x00, /* wcChar2 */ + 'r', 0x00, /* wcChar3 */ + 'r', 0x00, /* wcChar4 */ + 'y', 0x00, /* wcChar5 */ + 'U', 0x00, /* wcChar6 */ + 'S', 0x00, /* wcChar7 */ + 'B', 0x00, /* wcChar8 */ + ' ', 0x00, /* wcChar9 */ + 'C', 0x00, /* wcChar10 */ + 'D', 0x00, /* wcChar11 */ + 'C', 0x00, /* wcChar12 */ + ' ', 0x00, /* wcChar13 */ + 'D', 0x00, /* wcChar14 */ + 'E', 0x00, /* wcChar15 */ + 'M', 0x00, /* wcChar16 */ + 'O', 0x00, /* wcChar17 */ + /////////////////////////////////////// + /// string3 descriptor + /////////////////////////////////////// + 0x16, /* bLength */ + USB_DESCRIPTOR_TYPE_STRING, /* bDescriptorType */ + '2', 0x00, /* wcChar0 */ + '0', 0x00, /* wcChar1 */ + '2', 0x00, /* wcChar2 */ + '2', 0x00, /* wcChar3 */ + '1', 0x00, /* wcChar4 */ + '2', 0x00, /* wcChar5 */ + '3', 0x00, /* wcChar6 */ + '4', 0x00, /* wcChar7 */ + '5', 0x00, /* wcChar8 */ + '6', 0x00, /* wcChar9 */ +#ifdef CONFIG_USB_HS + /////////////////////////////////////// + /// device qualifier descriptor + /////////////////////////////////////// + 0x0a, + USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER, + 0x00, + 0x02, + 0x00, + 0x00, + 0x00, + 0x40, + 0x00, + 0x00, +#endif + 0x00 +}; +#endif + +static void usbd_event_handler(uint8_t busid, uint8_t event) +{ + switch (event) { + case USBD_EVENT_RESET: + break; + case USBD_EVENT_CONNECTED: + break; + case USBD_EVENT_DISCONNECTED: + break; + case USBD_EVENT_RESUME: + break; + case USBD_EVENT_SUSPEND: + break; + case USBD_EVENT_CONFIGURED: + + break; + case USBD_EVENT_SET_REMOTE_WAKEUP: + break; + case USBD_EVENT_CLR_REMOTE_WAKEUP: + break; + + default: + break; + } +} + +extern void usbd_cdc_acm_serial_init(uint8_t busid, uint8_t in_ep, uint8_t out_ep); + +void cdc_acm_chardev_init(uint8_t busid, uintptr_t reg_base) +{ +#ifdef CONFIG_USBDEV_ADVANCE_DESC + usbd_desc_register(busid, &cdc_descriptor); +#else + usbd_desc_register(busid, cdc_descriptor); +#endif + usbd_cdc_acm_serial_init(busid, CDC_IN_EP, CDC_OUT_EP); + usbd_initialize(busid, reg_base, usbd_event_handler); +} \ No newline at end of file diff --git a/components/drivers/usb/cherryusb/demo/webusb_hid_template.c b/components/drivers/usb/cherryusb/demo/webusb_hid_template.c index f446391f18..1019e0541c 100644 --- a/components/drivers/usb/cherryusb/demo/webusb_hid_template.c +++ b/components/drivers/usb/cherryusb/demo/webusb_hid_template.c @@ -135,7 +135,7 @@ uint8_t USBD_BinaryObjectStoreDescriptor[USBD_BOS_WTOTALLENGTH] = { struct usb_webusb_descriptor webusb_url_desc = { .vendor_code = USBD_WEBUSB_VENDOR_CODE, .string = USBD_WebUSBURLDescriptor, - .string_len = USBD_WINUSB_DESC_SET_LEN + .string_len = URL_DESCRIPTOR_LENGTH }; struct usb_msosv2_descriptor msosv2_desc = { diff --git a/components/drivers/usb/cherryusb/platform/README.md b/components/drivers/usb/cherryusb/platform/README.md index 35f0cdd795..3d7d8808f7 100644 --- a/components/drivers/usb/cherryusb/platform/README.md +++ b/components/drivers/usb/cherryusb/platform/README.md @@ -18,7 +18,8 @@ lwip support with usb host net class(cdc_ecm/cdc_ncm/cdc_rndis/asix/rtl8152/bl61 - DFS support with usb host msc. - lwip support with usb host net class(cdc_ecm/cdc_ncm/cdc_rndis/asix/rtl8152/bl616_wifi). - msh support with lsusb - +- device char support with host cdc_acm/ftdi/ch34x/cp210x/pl2303 +- shell support with adb ## Nuttx diff --git a/components/drivers/usb/cherryusb/platform/rtthread/usb_check.c b/components/drivers/usb/cherryusb/platform/rtthread/usb_check.c index 4a598fe6c0..b37b93f115 100644 --- a/components/drivers/usb/cherryusb/platform/rtthread/usb_check.c +++ b/components/drivers/usb/cherryusb/platform/rtthread/usb_check.c @@ -1,3 +1,8 @@ +/* + * Copyright (c) 2022 ~ 2025, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ #include "rtthread.h" #include "usb_config.h" diff --git a/components/drivers/usb/cherryusb/platform/rtthread/usbd_adb_shell.c b/components/drivers/usb/cherryusb/platform/rtthread/usbd_adb_shell.c new file mode 100644 index 0000000000..4ab7f59af3 --- /dev/null +++ b/components/drivers/usb/cherryusb/platform/rtthread/usbd_adb_shell.c @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2025, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include "usbd_core.h" +#include "usbd_adb.h" + +#ifndef CONFIG_USBDEV_SHELL_RX_BUFSIZE +#define CONFIG_USBDEV_SHELL_RX_BUFSIZE (2048) +#endif + +struct usbd_adb_shell { + struct rt_device parent; + usb_osal_sem_t tx_done; + struct rt_ringbuffer rx_rb; + rt_uint8_t rx_rb_buffer[CONFIG_USBDEV_SHELL_RX_BUFSIZE]; +} g_usbd_adb_shell; + +void usbd_adb_notify_shell_read(uint8_t *data, uint32_t len) +{ + rt_ringbuffer_put(&g_usbd_adb_shell.rx_rb, data, len); + + if (g_usbd_adb_shell.parent.rx_indicate) { + g_usbd_adb_shell.parent.rx_indicate(&g_usbd_adb_shell.parent, len); + } +} + +void usbd_adb_notify_write_done(void) +{ + if (g_usbd_adb_shell.tx_done) { + usb_osal_sem_give(g_usbd_adb_shell.tx_done); + } +} + +static rt_err_t usbd_adb_shell_open(struct rt_device *dev, rt_uint16_t oflag) +{ + while (!usb_device_is_configured(0)) { + rt_thread_mdelay(10); + } + return RT_EOK; +} + +static rt_err_t usbd_adb_shell_close(struct rt_device *dev) +{ + if (g_usbd_adb_shell.tx_done) { + usb_osal_sem_give(g_usbd_adb_shell.tx_done); + } + + return RT_EOK; +} + +static rt_ssize_t usbd_adb_shell_read(struct rt_device *dev, + rt_off_t pos, + void *buffer, + rt_size_t size) +{ + return rt_ringbuffer_get(&g_usbd_adb_shell.rx_rb, (rt_uint8_t *)buffer, size); +} + +static rt_ssize_t usbd_adb_shell_write(struct rt_device *dev, + rt_off_t pos, + const void *buffer, + rt_size_t size) +{ + int ret = 0; + + RT_ASSERT(dev != RT_NULL); + + if (!usb_device_is_configured(0)) { + return size; + } + + if (usbd_adb_can_write() && size) { + usb_osal_sem_reset(g_usbd_adb_shell.tx_done); + usbd_abd_write(ADB_SHELL_LOALID, buffer, size); + usb_osal_sem_take(g_usbd_adb_shell.tx_done, 0xffffffff); + } + + return size; +} + +#ifdef RT_USING_DEVICE_OPS +const static struct rt_device_ops usbd_adb_shell_ops = { + NULL, + usbd_adb_shell_open, + usbd_adb_shell_close, + usbd_adb_shell_read, + usbd_adb_shell_write, + NULL +}; +#endif + +void usbd_adb_shell_init(uint8_t in_ep, uint8_t out_ep) +{ + rt_err_t ret; + struct rt_device *device; + + device = &(g_usbd_adb_shell.parent); + + device->type = RT_Device_Class_Char; + device->rx_indicate = RT_NULL; + device->tx_complete = RT_NULL; + +#ifdef RT_USING_DEVICE_OPS + device->ops = &usbd_adb_shell_ops; +#else + device->init = NULL; + device->open = usbd_adb_shell_open; + device->close = usbd_adb_shell_close; + device->read = usbd_adb_shell_read; + device->write = usbd_adb_shell_write; + device->control = NULL; +#endif + device->user_data = NULL; + + /* register a character device */ + ret = rt_device_register(device, "adb-sh", RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_REMOVABLE); + +#ifdef RT_USING_POSIX_DEVIO + /* set fops */ + device->fops = NULL; +#endif + + g_usbd_adb_shell.tx_done = usb_osal_sem_create(0); + rt_ringbuffer_init(&g_usbd_adb_shell.rx_rb, g_usbd_adb_shell.rx_rb_buffer, sizeof(g_usbd_adb_shell.rx_rb_buffer)); +} + +static int adb_enter(int argc, char **argv) +{ + (void)argc; + (void)argv; + + finsh_set_device("adb-sh"); + rt_console_set_device("adb-sh"); + + return 0; +} +MSH_CMD_EXPORT(adb_enter, adb_enter); + +static int adb_exit(int argc, char **argv) +{ + (void)argc; + (void)argv; + + usbd_adb_close(ADB_SHELL_LOALID); + + finsh_set_device(RT_CONSOLE_DEVICE_NAME); + rt_console_set_device(RT_CONSOLE_DEVICE_NAME); + + return 0; +} +MSH_CMD_EXPORT(adb_exit, adb_exit); diff --git a/components/drivers/usb/cherryusb/platform/rtthread/usbd_serial.c b/components/drivers/usb/cherryusb/platform/rtthread/usbd_serial.c new file mode 100644 index 0000000000..5ce622091b --- /dev/null +++ b/components/drivers/usb/cherryusb/platform/rtthread/usbd_serial.c @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2025, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include "usbd_core.h" +#include "usbd_cdc_acm.h" + +#define DEV_FORMAT_CDC_ACM "usb-acm%d" + +#ifndef CONFIG_USBDEV_MAX_CDC_ACM_CLASS +#define CONFIG_USBDEV_MAX_CDC_ACM_CLASS (4) +#endif + +#ifndef CONFIG_USBDEV_SERIAL_RX_BUFSIZE +#define CONFIG_USBDEV_SERIAL_RX_BUFSIZE (2048) +#endif + +struct usbd_serial { + struct rt_device parent; + uint8_t busid; + uint8_t in_ep; + uint8_t out_ep; + struct usbd_interface intf_ctrl; + struct usbd_interface intf_data; + usb_osal_sem_t tx_done; + uint8_t minor; + char name[32]; + struct rt_ringbuffer rx_rb; + rt_uint8_t rx_rb_buffer[CONFIG_USBDEV_SERIAL_RX_BUFSIZE]; +}; + +static uint32_t g_devinuse = 0; + +static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_usbd_serial_cdc_acm_rx_buf[CONFIG_USBDEV_MAX_CDC_ACM_CLASS][USB_ALIGN_UP(512, CONFIG_USB_ALIGN_SIZE)]; + +static struct usbd_serial g_usbd_serial_cdc_acm[CONFIG_USBDEV_MAX_CDC_ACM_CLASS]; + +static struct usbd_serial *usbd_serial_alloc(void) +{ + uint8_t devno; + struct usbd_serial *serial; + + for (devno = 0; devno < CONFIG_USBDEV_MAX_CDC_ACM_CLASS; devno++) { + if ((g_devinuse & (1U << devno)) == 0) { + g_devinuse |= (1U << devno); + + serial = &g_usbd_serial_cdc_acm[devno]; + memset(serial, 0, sizeof(struct usbd_serial)); + serial->minor = devno; + snprintf(serial->name, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT_CDC_ACM, serial->minor); + return serial; + } + } + return NULL; +} + +static void usbd_serial_free(struct usbd_serial *serial) +{ + uint8_t devno = serial->minor; + + if (devno < 32) { + g_devinuse &= ~(1U << devno); + } + memset(serial, 0, sizeof(struct usbd_serial)); +} + +static rt_err_t usbd_serial_open(struct rt_device *dev, rt_uint16_t oflag) +{ + struct usbd_serial *serial; + + RT_ASSERT(dev != RT_NULL); + + serial = (struct usbd_serial *)dev; + + if (!usb_device_is_configured(serial->busid)) { + USB_LOG_ERR("USB device is not configured\n"); + return -RT_EPERM; + } + + usbd_ep_start_read(serial->busid, serial->out_ep, + g_usbd_serial_cdc_acm_rx_buf[serial->minor], + usbd_get_ep_mps(serial->busid, serial->out_ep)); + return RT_EOK; +} + +static rt_ssize_t usbd_serial_read(struct rt_device *dev, + rt_off_t pos, + void *buffer, + rt_size_t size) +{ + struct usbd_serial *serial; + + RT_ASSERT(dev != RT_NULL); + + serial = (struct usbd_serial *)dev; + + if (!usb_device_is_configured(serial->busid)) { + return -RT_EPERM; + } + + return rt_ringbuffer_get(&serial->rx_rb, (rt_uint8_t *)buffer, size); +} + +static rt_ssize_t usbd_serial_write(struct rt_device *dev, + rt_off_t pos, + const void *buffer, + rt_size_t size) +{ + struct usbd_serial *serial; + int ret = 0; + rt_uint8_t *align_buf; + + RT_ASSERT(dev != RT_NULL); + + serial = (struct usbd_serial *)dev; + + if (!usb_device_is_configured(serial->busid)) { + return -RT_EPERM; + } + align_buf = (rt_uint8_t *)buffer; + +#ifdef RT_USING_CACHE + if ((uint32_t)buffer & (CONFIG_USB_ALIGN_SIZE - 1)) { + align_buf = rt_malloc_align(size, CONFIG_USB_ALIGN_SIZE); + if (!align_buf) { + USB_LOG_ERR("serial get align buf failed\n"); + return 0; + } + + usb_memcpy(align_buf, buffer, size); + } +#endif + usb_osal_sem_reset(serial->tx_done); + usbd_ep_start_write(serial->busid, serial->in_ep, align_buf, size); + ret = usb_osal_sem_take(serial->tx_done, 3000); + if (ret < 0) { + USB_LOG_ERR("serial write timeout\n"); + ret = -RT_ETIMEOUT; + } else { + ret = size; + } + +#ifdef CONFIG_USB_DCACHE_ENABLE + if ((uint32_t)buffer & (CONFIG_USB_ALIGN_SIZE - 1)) { + rt_free_align(align_buf); + } +#endif + + return ret; +} + +#ifdef RT_USING_DEVICE_OPS +const static struct rt_device_ops usbd_serial_ops = { + NULL, + usbd_serial_open, + NULL, + usbd_serial_read, + usbd_serial_write, + NULL +}; +#endif + +rt_err_t usbd_serial_register(struct usbd_serial *serial, + void *data) +{ + rt_err_t ret; + struct rt_device *device; + RT_ASSERT(serial != RT_NULL); + + device = &(serial->parent); + + device->type = RT_Device_Class_Char; + device->rx_indicate = RT_NULL; + device->tx_complete = RT_NULL; + +#ifdef RT_USING_DEVICE_OPS + device->ops = &usbd_serial_ops; +#else + device->init = NULL; + device->open = usbd_serial_open; + device->close = NULL; + device->read = usbd_serial_read; + device->write = usbd_serial_write; + device->control = NULL; +#endif + device->user_data = data; + + /* register a character device */ + ret = rt_device_register(device, serial->name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_REMOVABLE); + +#ifdef RT_USING_POSIX_DEVIO + /* set fops */ + device->fops = NULL; +#endif + rt_ringbuffer_init(&serial->rx_rb, serial->rx_rb_buffer, sizeof(serial->rx_rb_buffer)); + + return ret; +} + +void usbd_cdc_acm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + struct usbd_serial *serial; + + for (uint8_t devno = 0; devno < CONFIG_USBDEV_MAX_CDC_ACM_CLASS; devno++) { + serial = &g_usbd_serial_cdc_acm[devno]; + if (serial->out_ep == ep) { + rt_ringbuffer_put(&serial->rx_rb, g_usbd_serial_cdc_acm_rx_buf[serial->minor], nbytes); + break; + } + } +} + +void usbd_cdc_acm_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + struct usbd_serial *serial; + + if ((nbytes % usbd_get_ep_mps(busid, ep)) == 0 && nbytes) { + /* send zlp */ + usbd_ep_start_write(busid, ep, NULL, 0); + } else { + for (uint8_t devno = 0; devno < CONFIG_USBDEV_MAX_CDC_ACM_CLASS; devno++) { + serial = &g_usbd_serial_cdc_acm[devno]; + if ((serial->in_ep == ep) && serial->tx_done) { + usb_osal_sem_give(serial->tx_done); + break; + } + } + } +} + +void usbd_cdc_acm_serial_init(uint8_t busid, uint8_t in_ep, uint8_t out_ep) +{ + struct usbd_serial *serial; + + struct usbd_endpoint cdc_out_ep = { + .ep_addr = out_ep, + .ep_cb = usbd_cdc_acm_bulk_out + }; + + struct usbd_endpoint cdc_in_ep = { + .ep_addr = in_ep, + .ep_cb = usbd_cdc_acm_bulk_in + }; + + serial = usbd_serial_alloc(); + if (serial == NULL) { + USB_LOG_ERR("No more serial device available\n"); + return; + } + + serial->busid = busid; + serial->in_ep = in_ep; + serial->out_ep = out_ep; + serial->tx_done = usb_osal_sem_create(0); + + usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &serial->intf_ctrl)); + usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &serial->intf_data)); + usbd_add_endpoint(busid, &cdc_out_ep); + usbd_add_endpoint(busid, &cdc_in_ep); + + if (usbd_serial_register(serial, NULL) != RT_EOK) { + USB_LOG_ERR("Failed to register serial device\n"); + usbd_serial_free(serial); + return; + } + + USB_LOG_INFO("USB CDC ACM Serial Device %s initialized\n", serial->name); +} \ No newline at end of file diff --git a/components/drivers/usb/cherryusb/platform/rtthread/usbh_dfs.c b/components/drivers/usb/cherryusb/platform/rtthread/usbh_dfs.c index 7cdfe1165c..1344f53926 100644 --- a/components/drivers/usb/cherryusb/platform/rtthread/usbh_dfs.c +++ b/components/drivers/usb/cherryusb/platform/rtthread/usbh_dfs.c @@ -30,15 +30,15 @@ static rt_err_t rt_udisk_init(rt_device_t dev) return RT_EOK; } -static ssize_t rt_udisk_read(rt_device_t dev, rt_off_t pos, void *buffer, - rt_size_t size) +static rt_ssize_t rt_udisk_read(rt_device_t dev, rt_off_t pos, void *buffer, + rt_size_t size) { struct usbh_msc *msc_class = (struct usbh_msc *)dev->user_data; int ret; rt_uint8_t *align_buf; align_buf = (rt_uint8_t *)buffer; -#ifdef RT_USING_CACHE +#ifdef CONFIG_USB_DCACHE_ENABLE if ((uint32_t)buffer & (CONFIG_USB_ALIGN_SIZE - 1)) { align_buf = rt_malloc_align(size * msc_class->blocksize, CONFIG_USB_ALIGN_SIZE); if (!align_buf) { @@ -53,7 +53,7 @@ static ssize_t rt_udisk_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_kprintf("usb mass_storage read failed\n"); return 0; } -#ifdef RT_USING_CACHE +#ifdef CONFIG_USB_DCACHE_ENABLE if ((uint32_t)buffer & (CONFIG_USB_ALIGN_SIZE - 1)) { usb_memcpy(buffer, align_buf, size * msc_class->blocksize); rt_free_align(align_buf); @@ -62,15 +62,15 @@ static ssize_t rt_udisk_read(rt_device_t dev, rt_off_t pos, void *buffer, return size; } -static ssize_t rt_udisk_write(rt_device_t dev, rt_off_t pos, const void *buffer, - rt_size_t size) +static rt_ssize_t rt_udisk_write(rt_device_t dev, rt_off_t pos, const void *buffer, + rt_size_t size) { struct usbh_msc *msc_class = (struct usbh_msc *)dev->user_data; int ret; rt_uint8_t *align_buf; align_buf = (rt_uint8_t *)buffer; -#ifdef RT_USING_CACHE +#ifdef CONFIG_USB_DCACHE_ENABLE if ((uint32_t)buffer & (CONFIG_USB_ALIGN_SIZE - 1)) { align_buf = rt_malloc_align(size * msc_class->blocksize, CONFIG_USB_ALIGN_SIZE); if (!align_buf) { @@ -86,7 +86,7 @@ static ssize_t rt_udisk_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_kprintf("usb mass_storage write failed\n"); return 0; } -#ifdef RT_USING_CACHE +#ifdef CONFIG_USB_DCACHE_ENABLE if ((uint32_t)buffer & (CONFIG_USB_ALIGN_SIZE - 1)) { rt_free_align(align_buf); } diff --git a/components/drivers/usb/cherryusb/platform/rtthread/usbh_serial.c b/components/drivers/usb/cherryusb/platform/rtthread/usbh_serial.c new file mode 100644 index 0000000000..b2888ec8e6 --- /dev/null +++ b/components/drivers/usb/cherryusb/platform/rtthread/usbh_serial.c @@ -0,0 +1,914 @@ +/* + * Copyright (c) 2025, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include "usbh_core.h" +#include "usbh_cdc_acm.h" +#include "usbh_ftdi.h" +#include "usbh_cp210x.h" +#include "usbh_ch34x.h" +#include "usbh_pl2303.h" + +#define DEV_FORMAT_VENDOR "ttyUSB%d" +#define DEV_FORMAT_CDC_ACM "ttyACM%d" + +#define USBH_RX_MAX_SIZE 2048 + +#ifndef CONFIG_USBHOST_MAX_VENDOR_SERIAL_CLASS +#define CONFIG_USBHOST_MAX_VENDOR_SERIAL_CLASS (4) +#endif + +#ifndef CONFIG_USBHOST_SERIAL_RX_BUFSIZE +#define CONFIG_USBHOST_SERIAL_RX_BUFSIZE (USBH_RX_MAX_SIZE * 2) +#endif + +enum usbh_serial_type { + USBH_SERIAL_TYPE_CDC_ACM = 0, + USBH_SERIAL_TYPE_FTDI, + USBH_SERIAL_TYPE_CP210X, + USBH_SERIAL_TYPE_CH34X, + USBH_SERIAL_TYPE_PL2303, +}; + +struct usbh_serial { + struct rt_device parent; + enum usbh_serial_type type; + uint8_t minor; + char name[CONFIG_USBHOST_DEV_NAMELEN]; + struct rt_ringbuffer rx_rb; + rt_uint8_t rx_rb_buffer[CONFIG_USBHOST_SERIAL_RX_BUFSIZE]; +}; + +static uint32_t g_devinuse_vendor = 0; +static uint32_t g_devinuse_cdc_acm = 0; + +static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_usbh_serial_vendor_rx_buf[CONFIG_USBHOST_MAX_VENDOR_SERIAL_CLASS][USB_ALIGN_UP(USBH_RX_MAX_SIZE, CONFIG_USB_ALIGN_SIZE)]; +static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_usbh_serial_cdc_acm_rx_buf[CONFIG_USBHOST_MAX_CDC_ACM_CLASS][USB_ALIGN_UP(USBH_RX_MAX_SIZE, CONFIG_USB_ALIGN_SIZE)]; + +static struct usbh_serial *usbh_serial_alloc(uint8_t type) +{ + uint8_t devno; + struct usbh_serial *serial; + + for (devno = 0; devno < CONFIG_USBHOST_MAX_VENDOR_SERIAL_CLASS; devno++) { + if ((g_devinuse_vendor & (1U << devno)) == 0) { + g_devinuse_vendor |= (1U << devno); + + serial = rt_malloc(sizeof(struct usbh_serial)); + memset(serial, 0, sizeof(struct usbh_serial)); + serial->type = type; + serial->minor = devno; + snprintf(serial->name, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT_VENDOR, serial->minor); + return serial; + } + } + return NULL; +} + +static void usbh_serial_free(struct usbh_serial *serial) +{ + uint8_t devno = serial->minor; + + if (devno < 32) { + g_devinuse_vendor &= ~(1U << devno); + } + memset(serial, 0, sizeof(struct usbh_serial)); + rt_free(serial); +} + +static struct usbh_serial *usbh_serial_cdc_acm_alloc(uint8_t type) +{ + uint8_t devno; + struct usbh_serial *serial; + + for (devno = 0; devno < CONFIG_USBHOST_MAX_CDC_ACM_CLASS; devno++) { + if ((g_devinuse_cdc_acm & (1U << devno)) == 0) { + g_devinuse_cdc_acm |= (1U << devno); + + serial = rt_malloc(sizeof(struct usbh_serial)); + memset(serial, 0, sizeof(struct usbh_serial)); + serial->type = type; + serial->minor = devno; + snprintf(serial->name, CONFIG_USBHOST_DEV_NAMELEN, DEV_FORMAT_CDC_ACM, serial->minor); + return serial; + } + } + return NULL; +} + +static void usbh_serial_cdc_acm_free(struct usbh_serial *serial) +{ + uint8_t devno = serial->minor; + + if (devno < 32) { + g_devinuse_cdc_acm &= ~(1U << devno); + } + memset(serial, 0, sizeof(struct usbh_serial)); + rt_free(serial); +} + +static rt_err_t usbh_serial_open(struct rt_device *dev, rt_uint16_t oflag) +{ + struct usbh_serial *serial; + + RT_ASSERT(dev != RT_NULL); + + serial = (struct usbh_serial *)dev; + + switch (serial->type) { + case USBH_SERIAL_TYPE_CDC_ACM: + break; + case USBH_SERIAL_TYPE_FTDI: + break; + case USBH_SERIAL_TYPE_CP210X: + break; + case USBH_SERIAL_TYPE_CH34X: + break; + case USBH_SERIAL_TYPE_PL2303: + break; + + default: + break; + } + + return RT_EOK; +} + +static rt_err_t usbh_serial_close(struct rt_device *dev) +{ + struct usbh_serial *serial; + + RT_ASSERT(dev != RT_NULL); + + serial = (struct usbh_serial *)dev; + + switch (serial->type) { + case USBH_SERIAL_TYPE_CDC_ACM: + break; + case USBH_SERIAL_TYPE_FTDI: + break; + case USBH_SERIAL_TYPE_CP210X: + break; + case USBH_SERIAL_TYPE_CH34X: + break; + case USBH_SERIAL_TYPE_PL2303: + break; + + default: + break; + } + + return RT_EOK; +} + +static rt_ssize_t usbh_serial_read(struct rt_device *dev, + rt_off_t pos, + void *buffer, + rt_size_t size) +{ + struct usbh_serial *serial; + + RT_ASSERT(dev != RT_NULL); + + serial = (struct usbh_serial *)dev; + + return rt_ringbuffer_get(&serial->rx_rb, (rt_uint8_t *)buffer, size); +} + +static rt_ssize_t usbh_serial_write(struct rt_device *dev, + rt_off_t pos, + const void *buffer, + rt_size_t size) +{ + struct usbh_serial *serial; + int ret = 0; + rt_uint8_t *align_buf; + + RT_ASSERT(dev != RT_NULL); + + serial = (struct usbh_serial *)dev; + + align_buf = (rt_uint8_t *)buffer; +#ifdef CONFIG_USB_DCACHE_ENABLE + if ((uint32_t)buffer & (CONFIG_USB_ALIGN_SIZE - 1)) { + align_buf = rt_malloc_align(size, CONFIG_USB_ALIGN_SIZE); + if (!align_buf) { + USB_LOG_ERR("serial get align buf failed\n"); + return 0; + } + + usb_memcpy(align_buf, buffer, size); + } +#endif + + switch (serial->type) { +#if defined(PKG_CHERRYUSB_HOST_CDC_ACM) || defined(RT_CHERRYUSB_HOST_CDC_ACM) + case USBH_SERIAL_TYPE_CDC_ACM: + ret = usbh_cdc_acm_bulk_out_transfer((struct usbh_cdc_acm *)dev->user_data, (uint8_t *)align_buf, size, RT_WAITING_FOREVER); + if (ret < 0) { + USB_LOG_ERR("usbh_cdc_acm_bulk_out_transfer failed: %d\n", ret); +#ifdef CONFIG_USB_DCACHE_ENABLE + rt_free_align(align_buf); +#endif + return 0; + } + break; +#endif +#if defined(PKG_CHERRYUSB_HOST_FTDI) || defined(RT_CHERRYUSB_HOST_FTDI) + case USBH_SERIAL_TYPE_FTDI: + ret = usbh_ftdi_bulk_out_transfer((struct usbh_ftdi *)dev->user_data, (uint8_t *)align_buf, size, RT_WAITING_FOREVER); + if (ret < 0) { + USB_LOG_ERR("usbh_ftdi_bulk_out_transfer failed: %d\n", ret); +#ifdef CONFIG_USB_DCACHE_ENABLE + rt_free_align(align_buf); +#endif + return 0; + } + break; +#endif +#if defined(PKG_CHERRYUSB_HOST_CH34X) || defined(RT_CHERRYUSB_HOST_CH34X) + case USBH_SERIAL_TYPE_CH34X: + ret = usbh_ch34x_bulk_out_transfer((struct usbh_ch34x *)dev->user_data, (uint8_t *)align_buf, size, RT_WAITING_FOREVER); + if (ret < 0) { + USB_LOG_ERR("usbh_ch34x_bulk_out_transfer failed: %d\n", ret); +#ifdef CONFIG_USB_DCACHE_ENABLE + rt_free_align(align_buf); +#endif + return 0; + } + break; +#endif +#if defined(PKG_CHERRYUSB_HOST_PL2303) || defined(RT_CHERRYUSB_HOST_PL2303) + case USBH_SERIAL_TYPE_PL2303: + ret = usbh_pl2303_bulk_out_transfer((struct usbh_pl2303 *)dev->user_data, (uint8_t *)align_buf, size, RT_WAITING_FOREVER); + if (ret < 0) { + USB_LOG_ERR("usbh_pl2303_bulk_out_transfer failed: %d\n", ret); +#ifdef CONFIG_USB_DCACHE_ENABLE + rt_free_align(align_buf); +#endif + return 0; + } + break; +#endif + default: + break; + } + +#ifdef CONFIG_USB_DCACHE_ENABLE + if ((uint32_t)buffer & (CONFIG_USB_ALIGN_SIZE - 1)) { + rt_free_align(align_buf); + } +#endif + + return ret; +} + +static rt_err_t usbh_serial_control(struct rt_device *dev, + int cmd, + void *args) +{ + struct usbh_serial *serial; + struct serial_configure *config; + struct cdc_line_coding line_coding; + int ret = -RT_EINVAL; + + RT_ASSERT(dev != RT_NULL); + + serial = (struct usbh_serial *)dev; + + switch (serial->type) { +#if defined(PKG_CHERRYUSB_HOST_CDC_ACM) || defined(RT_CHERRYUSB_HOST_CDC_ACM) + case USBH_SERIAL_TYPE_CDC_ACM: + if (cmd == RT_DEVICE_CTRL_CONFIG) { + struct usbh_cdc_acm *cdc_acm_class; + cdc_acm_class = (struct usbh_cdc_acm *)dev->user_data; + + config = (struct serial_configure *)args; + + line_coding.dwDTERate = config->baud_rate; + line_coding.bDataBits = config->data_bits; + line_coding.bCharFormat = 0; // STOP_BITS_1 + line_coding.bParityType = config->parity; + + usbh_cdc_acm_set_line_coding(cdc_acm_class, &line_coding); + } + + ret = RT_EOK; + break; +#endif +#if defined(PKG_CHERRYUSB_HOST_FTDI) || defined(RT_CHERRYUSB_HOST_FTDI) + case USBH_SERIAL_TYPE_FTDI: + if (cmd == RT_DEVICE_CTRL_CONFIG) { + struct usbh_ftdi *ftdi_class; + ftdi_class = (struct usbh_ftdi *)dev->user_data; + + config = (struct serial_configure *)args; + + line_coding.dwDTERate = config->baud_rate; + line_coding.bDataBits = config->data_bits; + line_coding.bCharFormat = 0; // STOP_BITS_1 + line_coding.bParityType = config->parity; + + usbh_ftdi_set_line_coding(ftdi_class, &line_coding); + } + + ret = RT_EOK; + break; +#endif +#if defined(PKG_CHERRYUSB_HOST_CP210X) || defined(RT_CHERRYUSB_HOST_CP210X) + case USBH_SERIAL_TYPE_CP210X: + if (cmd == RT_DEVICE_CTRL_CONFIG) { + struct usbh_cp210x *cp210x_class; + cp210x_class = (struct usbh_cp210x *)dev->user_data; + + config = (struct serial_configure *)args; + + line_coding.dwDTERate = config->baud_rate; + line_coding.bDataBits = config->data_bits; + line_coding.bCharFormat = 0; // STOP_BITS_1 + line_coding.bParityType = config->parity; + + usbh_cp210x_set_line_coding(cp210x_class, &line_coding); + } + + ret = RT_EOK; + break; +#endif +#if defined(PKG_CHERRYUSB_HOST_CH34X) || defined(RT_CHERRYUSB_HOST_CH34X) + case USBH_SERIAL_TYPE_CH34X: + if (cmd == RT_DEVICE_CTRL_CONFIG) { + struct usbh_ch34x *ch34x_class; + ch34x_class = (struct usbh_ch34x *)dev->user_data; + + config = (struct serial_configure *)args; + + line_coding.dwDTERate = config->baud_rate; + line_coding.bDataBits = config->data_bits; + line_coding.bCharFormat = 0; // STOP_BITS_1 + line_coding.bParityType = config->parity; + + usbh_ch34x_set_line_coding(ch34x_class, &line_coding); + } + + ret = RT_EOK; + break; +#endif +#if defined(PKG_CHERRYUSB_HOST_PL2303) || defined(RT_CHERRYUSB_HOST_PL2303) + case USBH_SERIAL_TYPE_PL2303: + if (cmd == RT_DEVICE_CTRL_CONFIG) { + struct usbh_pl2303 *pl2303_class; + pl2303_class = (struct usbh_pl2303 *)dev->user_data; + + config = (struct serial_configure *)args; + + line_coding.dwDTERate = config->baud_rate; + line_coding.bDataBits = config->data_bits; + line_coding.bCharFormat = 0; // STOP_BITS_1 + line_coding.bParityType = config->parity; + + usbh_pl2303_set_line_coding(pl2303_class, &line_coding); + } + + ret = RT_EOK; + break; +#endif + default: + break; + } + + return ret; +} + +#ifdef RT_USING_DEVICE_OPS +const static struct rt_device_ops usbh_serial_ops = { + NULL, + usbh_serial_open, + usbh_serial_close, + usbh_serial_read, + usbh_serial_write, + usbh_serial_control +}; +#endif + +#ifdef RT_USING_POSIX_DEVIO +#include +#include +#include +#include +#include + +#ifdef RT_USING_POSIX_TERMIOS +#include +#endif + +static rt_err_t usbh_serial_fops_rx_ind(rt_device_t dev, rt_size_t size) +{ + rt_wqueue_wakeup(&(dev->wait_queue), (void*)POLLIN); + + return RT_EOK; +} + +/* fops for serial */ +static int usbh_serial_fops_open(struct dfs_file *fd) +{ + rt_err_t ret = 0; + rt_uint16_t flags = 0; + rt_device_t device; + + device = (rt_device_t)fd->vnode->data; + RT_ASSERT(device != RT_NULL); + + switch (fd->flags & O_ACCMODE) + { + case O_RDONLY: + USB_LOG_DBG("fops open: O_RDONLY!"); + flags = RT_DEVICE_FLAG_RDONLY; + break; + case O_WRONLY: + USB_LOG_DBG("fops open: O_WRONLY!"); + flags = RT_DEVICE_FLAG_WRONLY; + break; + case O_RDWR: + USB_LOG_DBG("fops open: O_RDWR!"); + flags = RT_DEVICE_FLAG_RDWR; + break; + default: + USB_LOG_ERR("fops open: unknown mode - %d!", fd->flags & O_ACCMODE); + break; + } + + if ((fd->flags & O_ACCMODE) != O_WRONLY) + rt_device_set_rx_indicate(device, usbh_serial_fops_rx_ind); + ret = rt_device_open(device, flags); + if (ret == RT_EOK) return 0; + + return ret; +} + +static int usbh_serial_fops_close(struct dfs_file *fd) +{ + rt_device_t device; + + device = (rt_device_t)fd->vnode->data; + + rt_device_set_rx_indicate(device, RT_NULL); + rt_device_close(device); + + return 0; +} + +static int usbh_serial_fops_ioctl(struct dfs_file *fd, int cmd, void *args) +{ + rt_device_t device; + int flags = (int)(rt_base_t)args; + int mask = O_NONBLOCK | O_APPEND; + + device = (rt_device_t)fd->vnode->data; + switch (cmd) + { + case FIONREAD: + break; + case FIONWRITE: + break; + case F_SETFL: + flags &= mask; + fd->flags &= ~mask; + fd->flags |= flags; + break; + } + + return rt_device_control(device, cmd, args); +} + +static int usbh_serial_fops_read(struct dfs_file *fd, void *buf, size_t count) +{ + int size = 0; + rt_device_t device; + + device = (rt_device_t)fd->vnode->data; + + do + { + size = rt_device_read(device, -1, buf, count); + if (size <= 0) + { + if (fd->flags & O_NONBLOCK) + { + size = -EAGAIN; + break; + } + + rt_wqueue_wait(&(device->wait_queue), 0, RT_WAITING_FOREVER); + } + }while (size <= 0); + + return size; +} + +static int usbh_serial_fops_write(struct dfs_file *fd, const void *buf, size_t count) +{ + rt_device_t device; + + device = (rt_device_t)fd->vnode->data; + return rt_device_write(device, -1, buf, count); +} + +static int usbh_serial_fops_poll(struct dfs_file *fd, struct rt_pollreq *req) +{ + int mask = 0; + int flags = 0; + rt_device_t device; + struct usbh_serial *serial; + + device = (rt_device_t)fd->vnode->data; + RT_ASSERT(device != RT_NULL); + + serial = (struct usbh_serial *)device; + + /* only support POLLIN */ + flags = fd->flags & O_ACCMODE; + if (flags == O_RDONLY || flags == O_RDWR) + { + rt_base_t level; + + rt_poll_add(&(device->wait_queue), req); + + level = rt_hw_interrupt_disable(); + + if (rt_ringbuffer_data_len(&serial->rx_rb)) + mask |= POLLIN; + rt_hw_interrupt_enable(level); + } + // mask|=POLLOUT; + return mask; +} + +const static struct dfs_file_ops usbh_serial_fops = +{ + usbh_serial_fops_open, + usbh_serial_fops_close, + usbh_serial_fops_ioctl, + usbh_serial_fops_read, + usbh_serial_fops_write, + RT_NULL, /* flush */ + RT_NULL, /* lseek */ + RT_NULL, /* getdents */ + usbh_serial_fops_poll, +}; +#endif /* RT_USING_POSIX_DEVIO */ + +rt_err_t usbh_serial_register(struct usbh_serial *serial, + void *data) +{ + rt_err_t ret; + struct rt_device *device; + RT_ASSERT(serial != RT_NULL); + + device = &(serial->parent); + + device->type = RT_Device_Class_Char; + device->rx_indicate = RT_NULL; + device->tx_complete = RT_NULL; + +#ifdef RT_USING_DEVICE_OPS + device->ops = &usbh_serial_ops; +#else + device->init = NULL; + device->open = usbh_serial_open; + device->close = usbh_serial_close; + device->read = usbh_serial_read; + device->write = usbh_serial_write; + device->control = usbh_serial_control; +#endif + device->user_data = data; + + /* register a character device */ + ret = rt_device_register(device, serial->name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_REMOVABLE); + +#ifdef RT_USING_POSIX_DEVIO + /* set fops */ + device->fops = &usbh_serial_fops; +#endif + rt_ringbuffer_init(&serial->rx_rb, serial->rx_rb_buffer, sizeof(serial->rx_rb_buffer)); + + return ret; +} + +void usbh_serial_unregister(struct usbh_serial *serial) +{ + RT_ASSERT(serial != NULL); + + rt_device_unregister(&serial->parent); + + if (serial->type == USBH_SERIAL_TYPE_CDC_ACM) { + usbh_serial_cdc_acm_free(serial); + } else { + usbh_serial_free(serial); + } +} + +#if defined(PKG_CHERRYUSB_HOST_CDC_ACM) || defined(RT_CHERRYUSB_HOST_CDC_ACM) +void usbh_cdc_acm_callback(void *arg, int nbytes) +{ + struct usbh_cdc_acm *cdc_acm_class = (struct usbh_cdc_acm *)arg; + struct usbh_serial *serial; + int ret; + struct usbh_urb *urb = &cdc_acm_class->bulkin_urb; + + if (nbytes > 0) { + serial = (struct usbh_serial *)cdc_acm_class->user_data; + rt_ringbuffer_put(&serial->rx_rb, g_usbh_serial_cdc_acm_rx_buf[serial->minor], nbytes); + + if (serial->parent.rx_indicate) { + serial->parent.rx_indicate(&serial->parent, nbytes); + } + + usbh_bulk_urb_fill(urb, cdc_acm_class->hport, cdc_acm_class->bulkin, g_usbh_serial_cdc_acm_rx_buf[serial->minor], sizeof(g_usbh_serial_cdc_acm_rx_buf[serial->minor]), 0, usbh_cdc_acm_callback, cdc_acm_class); + ret = usbh_submit_urb(urb); + if (ret < 0) { + USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret); + } + } +} + +void usbh_cdc_acm_run(struct usbh_cdc_acm *cdc_acm_class) +{ + struct usbh_serial *serial; + int ret; + struct usbh_urb *urb = &cdc_acm_class->bulkin_urb; + + serial = usbh_serial_cdc_acm_alloc(USBH_SERIAL_TYPE_CDC_ACM); + cdc_acm_class->user_data = serial; + + usbh_serial_register(serial, cdc_acm_class); + + struct cdc_line_coding linecoding; + linecoding.dwDTERate = 115200; + linecoding.bDataBits = 8; + linecoding.bParityType = 0; + linecoding.bCharFormat = 0; + usbh_cdc_acm_set_line_coding(cdc_acm_class, &linecoding); + + usbh_bulk_urb_fill(urb, cdc_acm_class->hport, cdc_acm_class->bulkin, g_usbh_serial_cdc_acm_rx_buf[serial->minor], sizeof(g_usbh_serial_cdc_acm_rx_buf[serial->minor]), 0, usbh_cdc_acm_callback, cdc_acm_class); + ret = usbh_submit_urb(urb); + if (ret < 0) { + USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret); + usbh_serial_unregister(serial); + return; + } +} + +void usbh_cdc_acm_stop(struct usbh_cdc_acm *cdc_acm_class) +{ + struct usbh_serial *serial; + + serial = (struct usbh_serial *)cdc_acm_class->user_data; + usbh_serial_unregister(serial); +} +#endif + +#if defined(PKG_CHERRYUSB_HOST_FTDI) || defined(RT_CHERRYUSB_HOST_FTDI) +void usbh_ftdi_callback(void *arg, int nbytes) +{ + struct usbh_ftdi *ftdi_class = (struct usbh_ftdi *)arg; + struct usbh_serial *serial; + int ret; + struct usbh_urb *urb = &ftdi_class->bulkin_urb; + + if (nbytes >= 2) { + serial = (struct usbh_serial *)ftdi_class->user_data; + + nbytes -= 2; // Skip the first two bytes (header) + rt_ringbuffer_put(&serial->rx_rb, &g_usbh_serial_vendor_rx_buf[serial->minor][2], nbytes); + + if (serial->parent.rx_indicate && nbytes) { + serial->parent.rx_indicate(&serial->parent, nbytes); + } + + usbh_bulk_urb_fill(urb, ftdi_class->hport, ftdi_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_ftdi_callback, ftdi_class); + ret = usbh_submit_urb(urb); + if (ret < 0) { + USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret); + } + } +} + +void usbh_ftdi_run(struct usbh_ftdi *ftdi_class) +{ + struct usbh_serial *serial; + int ret; + struct usbh_urb *urb = &ftdi_class->bulkin_urb; + + serial = usbh_serial_alloc(USBH_SERIAL_TYPE_FTDI); + ftdi_class->user_data = serial; + + usbh_serial_register(serial, ftdi_class); + + struct cdc_line_coding linecoding; + linecoding.dwDTERate = 115200; + linecoding.bDataBits = 8; + linecoding.bParityType = 0; + linecoding.bCharFormat = 0; + usbh_ftdi_set_line_coding(ftdi_class, &linecoding); + + usbh_bulk_urb_fill(urb, ftdi_class->hport, ftdi_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_ftdi_callback, ftdi_class); + ret = usbh_submit_urb(urb); + if (ret < 0) { + USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret); + usbh_serial_unregister(serial); + return; + } +} + +void usbh_ftdi_stop(struct usbh_ftdi *ftdi_class) +{ + struct usbh_serial *serial; + + serial = (struct usbh_serial *)ftdi_class->user_data; + usbh_serial_unregister(serial); +} +#endif + +#if defined(PKG_CHERRYUSB_HOST_CH34X) || defined(RT_CHERRYUSB_HOST_CH34X) +void usbh_ch34x_callback(void *arg, int nbytes) +{ + struct usbh_ch34x *ch34x_class = (struct usbh_ch34x *)arg; + struct usbh_serial *serial; + int ret; + struct usbh_urb *urb = &ch34x_class->bulkin_urb; + + if (nbytes > 0) { + serial = (struct usbh_serial *)ch34x_class->user_data; + rt_ringbuffer_put(&serial->rx_rb, g_usbh_serial_vendor_rx_buf[serial->minor], nbytes); + + if (serial->parent.rx_indicate) { + serial->parent.rx_indicate(&serial->parent, nbytes); + } + + usbh_bulk_urb_fill(urb, ch34x_class->hport, ch34x_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_ch34x_callback, ch34x_class); + ret = usbh_submit_urb(urb); + if (ret < 0) { + USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret); + } + } +} + +void usbh_ch34x_run(struct usbh_ch34x *ch34x_class) +{ + struct usbh_serial *serial; + int ret; + struct usbh_urb *urb = &ch34x_class->bulkin_urb; + + serial = usbh_serial_alloc(USBH_SERIAL_TYPE_CH34X); + ch34x_class->user_data = serial; + + usbh_serial_register(serial, ch34x_class); + + struct cdc_line_coding linecoding; + linecoding.dwDTERate = 115200; + linecoding.bDataBits = 8; + linecoding.bParityType = 0; + linecoding.bCharFormat = 0; + usbh_ch34x_set_line_coding(ch34x_class, &linecoding); + + usbh_bulk_urb_fill(urb, ch34x_class->hport, ch34x_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_ch34x_callback, ch34x_class); + ret = usbh_submit_urb(urb); + if (ret < 0) { + USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret); + usbh_serial_unregister(serial); + return; + } +} + +void usbh_ch34x_stop(struct usbh_ch34x *ch34x_class) +{ + struct usbh_serial *serial; + + serial = (struct usbh_serial *)ch34x_class->user_data; + usbh_serial_unregister(serial); +} +#endif + +#if defined(PKG_CHERRYUSB_HOST_CP210X) || defined(RT_CHERRYUSB_HOST_CP210X) +void usbh_cp210x_callback(void *arg, int nbytes) +{ + struct usbh_cp210x *cp210x_class = (struct usbh_cp210x *)arg; + struct usbh_serial *serial; + int ret; + struct usbh_urb *urb = &cp210x_class->bulkin_urb; + + if (nbytes > 0) { + serial = (struct usbh_serial *)cp210x_class->user_data; + rt_ringbuffer_put(&serial->rx_rb, g_usbh_serial_vendor_rx_buf[serial->minor], nbytes); + + if (serial->parent.rx_indicate) { + serial->parent.rx_indicate(&serial->parent, nbytes); + } + + usbh_bulk_urb_fill(urb, cp210x_class->hport, cp210x_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_cp210x_callback, cp210x_class); + ret = usbh_submit_urb(urb); + if (ret < 0) { + USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret); + } + } +} + +void usbh_cp210x_run(struct usbh_cp210x *cp210x_class) +{ + struct usbh_serial *serial; + int ret; + struct usbh_urb *urb = &cp210x_class->bulkin_urb; + + serial = usbh_serial_alloc(USBH_SERIAL_TYPE_CP210X); + cp210x_class->user_data = serial; + + usbh_serial_register(serial, cp210x_class); + + struct cdc_line_coding linecoding; + linecoding.dwDTERate = 115200; + linecoding.bDataBits = 8; + linecoding.bParityType = 0; + linecoding.bCharFormat = 0; + usbh_cp210x_set_line_coding(cp210x_class, &linecoding); + + usbh_bulk_urb_fill(urb, cp210x_class->hport, cp210x_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_cp210x_callback, cp210x_class); + ret = usbh_submit_urb(urb); + if (ret < 0) { + USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret); + usbh_serial_unregister(serial); + return; + } +} + +void usbh_cp210x_stop(struct usbh_cp210x *cp210x_class) +{ + struct usbh_serial *serial; + + serial = (struct usbh_serial *)cp210x_class->user_data; + usbh_serial_unregister(serial); +} +#endif + +#if defined(PKG_CHERRYUSB_HOST_PL2303) || defined(RT_CHERRYUSB_HOST_PL2303) +void usbh_pl2303_callback(void *arg, int nbytes) +{ + struct usbh_pl2303 *pl2303_class = (struct usbh_pl2303 *)arg; + struct usbh_serial *serial; + int ret; + struct usbh_urb *urb = &pl2303_class->bulkin_urb; + + if (nbytes > 0) { + serial = (struct usbh_serial *)pl2303_class->user_data; + rt_ringbuffer_put(&serial->rx_rb, g_usbh_serial_vendor_rx_buf[serial->minor], nbytes); + + if (serial->parent.rx_indicate) { + serial->parent.rx_indicate(&serial->parent, nbytes); + } + + usbh_bulk_urb_fill(urb, pl2303_class->hport, pl2303_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_pl2303_callback, pl2303_class); + ret = usbh_submit_urb(urb); + if (ret < 0) { + USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret); + } + } +} + +void usbh_pl2303_run(struct usbh_pl2303 *pl2303_class) +{ + struct usbh_serial *serial; + int ret; + struct usbh_urb *urb = &pl2303_class->bulkin_urb; + + serial = usbh_serial_alloc(USBH_SERIAL_TYPE_PL2303); + pl2303_class->user_data = serial; + + usbh_serial_register(serial, pl2303_class); + + struct cdc_line_coding linecoding; + linecoding.dwDTERate = 115200; + linecoding.bDataBits = 8; + linecoding.bParityType = 0; + linecoding.bCharFormat = 0; + usbh_pl2303_set_line_coding(pl2303_class, &linecoding); + + usbh_bulk_urb_fill(urb, pl2303_class->hport, pl2303_class->bulkin, g_usbh_serial_vendor_rx_buf[serial->minor], sizeof(g_usbh_serial_vendor_rx_buf[serial->minor]), 0, usbh_pl2303_callback, pl2303_class); + ret = usbh_submit_urb(urb); + if (ret < 0) { + USB_LOG_ERR("usbh_submit_urb failed: %d\n", ret); + usbh_serial_unregister(serial); + return; + } +} + +void usbh_pl2303_stop(struct usbh_pl2303 *pl2303_class) +{ + struct usbh_serial *serial; + + serial = (struct usbh_serial *)pl2303_class->user_data; + usbh_serial_unregister(serial); +} +#endif \ No newline at end of file diff --git a/components/drivers/usb/cherryusb/port/dwc2/usb_glue_st.c b/components/drivers/usb/cherryusb/port/dwc2/usb_glue_st.c index 9925f5e98b..ccaa743f91 100644 --- a/components/drivers/usb/cherryusb/port/dwc2/usb_glue_st.c +++ b/components/drivers/usb/cherryusb/port/dwc2/usb_glue_st.c @@ -178,7 +178,7 @@ uint32_t usbd_get_dwc2_gccfg_conf(uint32_t reg_base) usb_hsphy_init(25000000U); return (1 << 23); /* Enable USB HS PHY USBx->GCCFG |= USB_OTG_GCCFG_PHYHSEN;*/ #elif __has_include("stm32h7rsxx.h") - return (1 << 21); + return ((1 << 23) | (1 << 24)); #else return 0; #endif @@ -205,6 +205,8 @@ uint32_t usbh_get_dwc2_gccfg_conf(uint32_t reg_base) USB_OTG_GLB->GCCFG = (1 << 23); usb_hsphy_init(25000000U); return (1 << 23); /* Enable USB HS PHY USBx->GCCFG |= USB_OTG_GCCFG_PHYHSEN;*/ +#elif __has_include("stm32h7rsxx.h") + return (1 << 25); #else return 0; #endif @@ -252,4 +254,4 @@ void usb_dcache_flush(uintptr_t addr, size_t size) { SCB_CleanInvalidateDCache_by_Addr((void *)addr, size); } -#endif \ No newline at end of file +#endif diff --git a/components/drivers/usb/cherryusb/port/dwc2/usb_hc_dwc2.c b/components/drivers/usb/cherryusb/port/dwc2/usb_hc_dwc2.c index 46effd5655..09fd628f0b 100644 --- a/components/drivers/usb/cherryusb/port/dwc2/usb_hc_dwc2.c +++ b/components/drivers/usb/cherryusb/port/dwc2/usb_hc_dwc2.c @@ -324,16 +324,11 @@ static inline void dwc2_chan_transfer(struct usbh_bus *bus, uint8_t ch_num, uint static void dwc2_halt(struct usbh_bus *bus, uint8_t ch_num) { volatile uint32_t ChannelEna = (USB_OTG_HC(ch_num)->HCCHAR & USB_OTG_HCCHAR_CHENA) >> 31; - volatile uint32_t HcEpType = (USB_OTG_HC(ch_num)->HCCHAR & USB_OTG_HCCHAR_EPTYP) >> 18; - volatile uint32_t SplitEna = (USB_OTG_HC(ch_num)->HCSPLT & USB_OTG_HCSPLT_SPLITEN) >> 31; volatile uint32_t count = 0U; volatile uint32_t value; - /* In buffer DMA, Channel disable must not be programmed for non-split periodic channels. - At the end of the next uframe/frame (in the worst case), the core generates a channel halted - and disables the channel automatically. */ - if ((((USB_OTG_GLB->GAHBCFG & USB_OTG_GAHBCFG_DMAEN) == USB_OTG_GAHBCFG_DMAEN) && (SplitEna == 0U)) && - ((ChannelEna == 0U) || (((HcEpType == HCCHAR_ISOC) || (HcEpType == HCCHAR_INTR))))) { + if (((USB_OTG_GLB->GAHBCFG & USB_OTG_GAHBCFG_DMAEN) == USB_OTG_GAHBCFG_DMAEN) && + (ChannelEna == 0U)) { return; } @@ -1397,4 +1392,4 @@ void USBH_IRQHandler(uint8_t busid) USB_OTG_GLB->GINTSTS = USB_OTG_GINTSTS_HCINT; } } -} \ No newline at end of file +}