From 2a9f557adc7124524398bf5bf4502ba872e945f8 Mon Sep 17 00:00:00 2001 From: shefty Date: Wed, 24 Jun 2009 18:19:31 +0000 Subject: [PATCH] winverbs branch: update to trunk svn 2267 git-svn-id: svn://openib.tc.cornell.edu/gen1@2268 ad392aa1-c5ef-ae45-8dd8-e69d62a5ef86 --- branches/winverbs/WinOF/BuildRelease.bat | 16 +- branches/winverbs/WinOF/WIX/CustomActions.vbs | 664 +- .../winverbs/WinOF/WIX/README_release.txt | 69 +- branches/winverbs/WinOF/WIX/Release_notes.htm | 372 +- branches/winverbs/WinOF/WIX/common/IBcore.inc | 85 +- .../winverbs/WinOF/WIX/common/hca_filters.inc | 78 + branches/winverbs/WinOF/WIX/common/iou.inc | 27 + branches/winverbs/WinOF/WIX/common/ipoib.inc | 23 +- .../WinOF/WIX/common/mlnx_drivers.inc | 133 +- .../winverbs/WinOF/WIX/common/qlgc_vnic.inc | 18 +- branches/winverbs/WinOF/WIX/common/srp.inc | 35 +- .../WinOF/WIX/common/winverbs_drivers.inc | 39 +- branches/winverbs/WinOF/WIX/dat.conf | 13 +- branches/winverbs/WinOF/WIX/ia64/DPInst.exe | Bin 1115704 -> 0 bytes .../winverbs/WinOF/WIX/win7/ia64/Makefile | 19 +- branches/winverbs/WinOF/WIX/win7/ia64/wof.wxs | 36 +- branches/winverbs/WinOF/WIX/win7/x64/Makefile | 23 +- branches/winverbs/WinOF/WIX/win7/x64/wof.wxs | 45 +- branches/winverbs/WinOF/WIX/win7/x86/Makefile | 21 +- branches/winverbs/WinOF/WIX/win7/x86/wof.wxs | 32 +- branches/winverbs/WinOF/WIX/wlh/ia64/Makefile | 19 +- branches/winverbs/WinOF/WIX/wlh/ia64/wof.wxs | 28 +- branches/winverbs/WinOF/WIX/wlh/x64/Makefile | 23 +- branches/winverbs/WinOF/WIX/wlh/x64/wof.wxs | 24 +- branches/winverbs/WinOF/WIX/wlh/x86/Makefile | 21 +- branches/winverbs/WinOF/WIX/wlh/x86/wof.wxs | 24 +- .../winverbs/WinOF/WIX/wnet/ia64/Makefile | 19 +- branches/winverbs/WinOF/WIX/wnet/ia64/wof.wxs | 42 +- branches/winverbs/WinOF/WIX/wnet/x64/Makefile | 23 +- branches/winverbs/WinOF/WIX/wnet/x64/wof.wxs | 41 +- branches/winverbs/WinOF/WIX/wnet/x86/Makefile | 19 +- branches/winverbs/WinOF/WIX/wnet/x86/wof.wxs | 43 +- branches/winverbs/WinOF/WIX/wxp/x86/Makefile | 19 +- branches/winverbs/WinOF/WIX/wxp/x86/wof.wxs | 49 +- branches/winverbs/WinOF/WIX/x64/DPInst.exe | Bin 684600 -> 0 bytes branches/winverbs/WinOF/WIX/x86/DPInst.exe | Bin 549944 -> 0 bytes .../winverbs/core/complib/user/cl_timer.c | 2 +- branches/winverbs/docs/Manual.htm | 2113 ++++- branches/winverbs/etc/makebin.bat | 53 +- .../winverbs/hw/mlx4/kernel/bus/drv/drv.c | 73 +- .../winverbs/hw/mlx4/kernel/bus/drv/drv.h | 10 +- .../winverbs/hw/mlx4/kernel/bus/drv/pci.c | 167 +- .../winverbs/hw/mlx4/kernel/bus/drv/pdo.c | 10 +- branches/winverbs/hw/mlx4/kernel/bus/ib/cq.c | 4 +- .../winverbs/hw/mlx4/kernel/bus/net/mlx4.h | 3 +- branches/winverbs/hw/mlx4/kernel/inc/l2w.h | 5 + .../inc/kernel/complib/cl_timer_osd.h | 4 +- .../dapl2/test/dapltest/scripts/dt-cli.bat | 120 +- branches/winverbs/ulp/ipoib/kernel6/SOURCES | 59 + branches/winverbs/ulp/ipoib/kernel6/ipoib.cdf | 13 + branches/winverbs/ulp/ipoib/kernel6/ipoib.rc | 48 + .../winverbs/ulp/ipoib/kernel6/ipoib32-xp.cdf | 10 + .../winverbs/ulp/ipoib/kernel6/ipoib32.cdf | 11 + .../ulp/ipoib/kernel6/ipoib_adapter.c | 1632 ++++ .../ulp/ipoib/kernel6/ipoib_adapter.h | 483 + .../winverbs/ulp/ipoib/kernel6/ipoib_cm.c | 0 .../winverbs/ulp/ipoib/kernel6/ipoib_debug.h | 303 + .../winverbs/ulp/ipoib/kernel6/ipoib_driver.c | 4057 ++++++++ .../winverbs/ulp/ipoib/kernel6/ipoib_driver.h | 162 + .../ulp/ipoib/kernel6/ipoib_endpoint.c | 1170 +++ .../ulp/ipoib/kernel6/ipoib_endpoint.h | 257 + .../winverbs/ulp/ipoib/kernel6/ipoib_ibat.c | 666 ++ .../winverbs/ulp/ipoib/kernel6/ipoib_ibat.h | 45 + .../winverbs/ulp/ipoib/kernel6/ipoib_log.mc | 334 + .../winverbs/ulp/ipoib/kernel6/ipoib_port.c | 8098 ++++++++++++++++ .../winverbs/ulp/ipoib/kernel6/ipoib_port.h | 827 ++ .../ulp/ipoib/kernel6/ipoib_xfr_mgr.c | 74 + .../ulp/ipoib/kernel6/ipoib_xfr_mgr.h | 513 ++ branches/winverbs/ulp/ipoib/kernel6/makefile | 7 + .../winverbs/ulp/ipoib/kernel6/makefile.inc | 17 + .../ulp/ipoib/kernel6/netipoib-xp32.inf | 280 + .../winverbs/ulp/ipoib/kernel6/netipoib.inx | 295 + branches/winverbs/ulp/ipoib/kernel6/offload.h | 47 + branches/winverbs/ulp/ipoib_NDIS6_CM/dirs | 2 + .../winverbs/ulp/ipoib_NDIS6_CM/ip_stats.h | 150 + .../ulp/ipoib_NDIS6_CM/kernel/SOURCES | 59 + .../ulp/ipoib_NDIS6_CM/kernel/ipoib.cdf | 13 + .../ulp/ipoib_NDIS6_CM/kernel/ipoib.rc | 48 + .../ulp/ipoib_NDIS6_CM/kernel/ipoib32.cdf | 11 + .../ulp/ipoib_NDIS6_CM/kernel/ipoib_adapter.c | 1642 ++++ .../ulp/ipoib_NDIS6_CM/kernel/ipoib_adapter.h | 483 + .../ulp/ipoib_NDIS6_CM/kernel/ipoib_debug.h | 303 + .../ulp/ipoib_NDIS6_CM/kernel/ipoib_driver.c | 4708 ++++++++++ .../ulp/ipoib_NDIS6_CM/kernel/ipoib_driver.h | 166 + .../ipoib_NDIS6_CM/kernel/ipoib_endpoint.c | 1170 +++ .../ipoib_NDIS6_CM/kernel/ipoib_endpoint.h | 257 + .../ulp/ipoib_NDIS6_CM/kernel/ipoib_ibat.c | 693 ++ .../ulp/ipoib_NDIS6_CM/kernel/ipoib_ibat.h | 45 + .../ulp/ipoib_NDIS6_CM/kernel/ipoib_log.mc | 334 + .../ulp/ipoib_NDIS6_CM/kernel/ipoib_port.c | 8123 +++++++++++++++++ .../ulp/ipoib_NDIS6_CM/kernel/ipoib_port.h | 827 ++ .../ulp/ipoib_NDIS6_CM/kernel/ipoib_xfr_mgr.c | 74 + .../ulp/ipoib_NDIS6_CM/kernel/ipoib_xfr_mgr.h | 513 ++ .../ulp/ipoib_NDIS6_CM/kernel/makefile | 7 + .../ulp/ipoib_NDIS6_CM/kernel/makefile.inc | 17 + .../ulp/ipoib_NDIS6_CM/kernel/netipoib.inx | 295 + .../ulp/ipoib_NDIS6_CM/kernel/offload.h | 47 + 97 files changed, 42733 insertions(+), 1368 deletions(-) create mode 100644 branches/winverbs/WinOF/WIX/common/hca_filters.inc create mode 100644 branches/winverbs/WinOF/WIX/common/iou.inc delete mode 100644 branches/winverbs/WinOF/WIX/ia64/DPInst.exe delete mode 100644 branches/winverbs/WinOF/WIX/x64/DPInst.exe delete mode 100644 branches/winverbs/WinOF/WIX/x86/DPInst.exe create mode 100644 branches/winverbs/ulp/ipoib/kernel6/SOURCES create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib.cdf create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib.rc create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib32-xp.cdf create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib32.cdf create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib_adapter.c create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib_adapter.h create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib_cm.c create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib_debug.h create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib_driver.c create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib_driver.h create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib_endpoint.c create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib_endpoint.h create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib_ibat.c create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib_ibat.h create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib_log.mc create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib_port.c create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib_port.h create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib_xfr_mgr.c create mode 100644 branches/winverbs/ulp/ipoib/kernel6/ipoib_xfr_mgr.h create mode 100644 branches/winverbs/ulp/ipoib/kernel6/makefile create mode 100644 branches/winverbs/ulp/ipoib/kernel6/makefile.inc create mode 100644 branches/winverbs/ulp/ipoib/kernel6/netipoib-xp32.inf create mode 100644 branches/winverbs/ulp/ipoib/kernel6/netipoib.inx create mode 100644 branches/winverbs/ulp/ipoib/kernel6/offload.h create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/dirs create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/ip_stats.h create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/SOURCES create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib.cdf create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib.rc create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib32.cdf create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_adapter.c create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_adapter.h create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_debug.h create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_driver.c create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_driver.h create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_endpoint.c create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_endpoint.h create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_ibat.c create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_ibat.h create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_log.mc create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_port.c create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_port.h create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_xfr_mgr.c create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_xfr_mgr.h create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/makefile create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/makefile.inc create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/netipoib.inx create mode 100644 branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/offload.h diff --git a/branches/winverbs/WinOF/BuildRelease.bat b/branches/winverbs/WinOF/BuildRelease.bat index ead390fd..1b97fee1 100644 --- a/branches/winverbs/WinOF/BuildRelease.bat +++ b/branches/winverbs/WinOF/BuildRelease.bat @@ -533,25 +533,25 @@ mkdir %RBIN_WXP% if EXIST "%BSE%\etc\makebin.bat" ( if "%WIN7%" == "yes" ( - call %BSE%\etc\makebin.bat %BSE% %RBIN_W7% win7 %_DDK_VER% %_COIN_VER% + call %BSE%\etc\makebin.bat %BSE% %RBIN_W7% win7 %_DDK% %_COIN_VER% if ERRORLEVEL 1 ( - echo %0: Err in makebin.bat %BSE% %RBIN_W7% wlh + echo %0: Err in makebin.bat %BSE% %RBIN_W7% wlh %_DDK% %_COIN_VER% exit /B 1 ) ) - call %BSE%\etc\makebin.bat %BSE% %RBIN_WLH% wlh %_DDK_VER% %_COIN_VER% + call %BSE%\etc\makebin.bat %BSE% %RBIN_WLH% wlh %_DDK% %_COIN_VER% if ERRORLEVEL 1 ( - echo %0: Err in makebin.bat %BSE% %RBIN_WLH% wlh + echo %0: Err in makebin.bat %BSE% %RBIN_WLH% wlh %_DDK% %_COIN_VER% exit /B 1 ) - call %BSE%\etc\makebin.bat %BSE% %RBIN_WNET% wnet %_DDK_VER% %_COIN_VER% + call %BSE%\etc\makebin.bat %BSE% %RBIN_WNET% wnet %_DDK% %_COIN_VER% if ERRORLEVEL 1 ( - echo %0: Err in makebin.bat %BSE% %RBIN_WNET% wnet + echo %0: Err in makebin.bat %BSE% %RBIN_WNET% wnet %_DDK% %_COIN_VER% exit /B 1 ) - call %BSE%\etc\makebin.bat %BSE% %RBIN_WXP% wxp %_DDK_VER% %_COIN_VER% + call %BSE%\etc\makebin.bat %BSE% %RBIN_WXP% wxp %_DDK% %_COIN_VER% if ERRORLEVEL 1 ( - echo %0: Err in makebin.bat %BSE% %RBIN_WXP% wxp + echo %0: Err in makebin.bat %BSE% %RBIN_WXP% wxp %_DDK% %_COIN_VER% exit /B 1 ) ) diff --git a/branches/winverbs/WinOF/WIX/CustomActions.vbs b/branches/winverbs/WinOF/WIX/CustomActions.vbs index 7baa4037..c973131b 100644 --- a/branches/winverbs/WinOF/WIX/CustomActions.vbs +++ b/branches/winverbs/WinOF/WIX/CustomActions.vbs @@ -41,7 +41,7 @@ Const WindowsVista ="600" Const WindowsSvr2008 ="600" Const Windows7 ="601" -Const UseDPinst = "600" ' use DPinst.exe to install drivers for +Const UseDPinst = "501" ' use DPinst.exe to install drivers for ' Windows VersionNT >= this value. ' Global debug flag: Session.Property from msiexec.exe cmd line DBG=1 @@ -89,40 +89,14 @@ Sub WinOF_setup AddLocal = Session.Property("ADDLOCAL") ' The WIX UI (UserInterface) sets up ADDLOCAL. When cmd-line msiexec.exe is - ' run with a deprecited UI, then ADDLOCAL is not setup; default it's value - ' here. + ' run with a minimal UI (/passive), then ADDLOCAL is not setup correctly; + ' default it's value here. + If AddLocal = "" AND Installed = "" Then ' Enable default features. AddLocal = "IBcore,hca_mthca,fIPoIB,fWSD,fDAPL,fDatBASIC1,fDatBASIC2" End If - ' Process msiexec cmd line arg HCA=cx or HCA=+cx - ' Pre WLH OS only (Server 2003/XP) - ' Replace default InfiniHost HCA driver with ConnectX HCA driver if - ' requested 'HCA=cx' or add ConnectX '+cx'. - - If VersionNT < UseDPinst Then - use_this_HCA = Session.Property("HCA") - If use_this_HCA <> "" Then - ' down-case if required. - If Instr(use_this_HCA,"CX") <> 0 Then - use_this_HCA = Replace(use_this_HCA,"CX","cx") - End if - If Instr(use_this_HCA,"+cx") <> 0 Then - ' Add ConnectX HCA drivers - AddLocal = AddLocal & ",hca_connectX" - Else - If Instr(use_this_HCA,"cx") <> 0 Then - If Instr(AddLocal,"hca_mthca") <> 0 Then - AddLocal = Replace(AddLocal,"hca_mthca","hca_connectX") - Else - AddLocal = AddLocal & ",hca_connectX" - End If - End If - End If - End If - End If - If Session.Property("OSM") = "1" OR Session.Property("OSMS") = "1" Then AddLocal = AddLocal & ",fOSMS" End If @@ -224,10 +198,8 @@ Sub DriverFileDelete(fso,WshShell,filename,VersionNT) ' allow continuation after 'permission denied' error On Error Resume Next ' unlock the driver file by deleting PnPLocked reg entry. - If VersionNT >= UseDPinst Then - base = "reg delete HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\PnpLockdownFiles /v " - Return = WshShell.Run (base & filename & " /f", 0, true) - End If + base = "reg delete HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\PnpLockdownFiles /v " + Return = WshShell.Run (base & filename & " /f", 0, true) fso.DeleteFile(filename),True If (Err And Err.Number <> 70) Then ' tolerate protection errors ErrMsg ("Could not delete: " & filename) @@ -299,8 +271,7 @@ End Sub ''''''''''' Remove Driver Files '''''''''''' ' Attempt to clean out driver installed files which fail to be uninstalled -' when the driver is uninstalled. Win2K3/x64 files persist, XP & Win2K3/x86 -' the driver files are correctly remove? +' when the driver is uninstalled. Sub RemoveDriverFiles(fso,WshShell,VersionNT) @@ -650,61 +621,17 @@ Function Find_Dev_by_Tag(WshShell,exe,sFindHow,tag) End Function -Function IsInfiniHost(ID) - Dim dID - HCAs = Array("5A44","5A45","5E8C","5E8D","6264","6274","6275","6278",_ - "6279","6282") - - For each dID in HCAs - If dID = ID Then - IsInfiniHost = 1 -'msgbox "match ID " & ID & " dID " & dID - Exit Function - End if - Next -'msgbox "NO match ID " & ID - IsInfiniHost = 0 - -End Function - - -' Install Qlogic VNIC Driver using devman.exe (aka devcon) - -Sub devman_Install_VNIC(WshShell,sInstalldir) - - Dim devID, rc - - devmanQL = "cmd.exe /c cd /d " & sInstalldir & "qlgcvnic & " & _ - "..\IBcore\devman.exe " - - Err.clear - devID = "IBA\V00066AP00000030" - rc = WshShell.Run (devmanQL & "disable " & devID, 0, true) - rc = WshShell.Run (devmanQL & "update netvnic.inf " & devID,0,true) - ' Display error number and description if applicable - If rc <> 0 Then - msgbox "Install_VNIC Err(" & rc & ") - devman update qlgcvnic.sys " _ - & devID,,"devman_install_VNIC" - Else - rc = WshShell.Run (devmanQL & "enable " & devID, 0, true) - If sDBG >= "1" Then - msgbox "VNIC Install OK",,"devman_Install_VNIC" - End If - End if - -End Sub - Function dpinst_Install_VNIC(WshShell,sInstalldir) - Dim dpinstSRP,cmd,rc + Dim dpinstVNIC,cmd,rc dpinst_Install_VNIC = 0 dpinstVNIC = "cmd.exe /c cd /d " & sInstalldir & _ - "qlgcvnic & ..\ibcore\dpinst.exe " + "qlgcvnic & ..\dpinst.exe " - cmd = dpinstVNIC & "/S /F /SA /PATH """ & sInstalldir & "qlgcvnic""" & _ - " /SE /SW" + cmd = dpinstVNIC & "/S /F /SA /PATH """ & sInstalldir & _ + "Drivers\qlgcvnic"" /SE /SW" rc = WshShell.Run (cmd,0,true) If (rc AND DPINST_INSTALLED) = 0 Then dpinst_status "qlgcvnic Install failed",cmd,rc,"dpinst_Install_VNIC" @@ -716,75 +643,14 @@ Function dpinst_Install_VNIC(WshShell,sInstalldir) End Function -' Install SRP (SCSI RDMA Protocol) Driver - -' QLogic Virtual FC I/O controller or -' InfiniBand SRP Miniport: IBA\C0100C609EP0108 or IBA\CFF00C609EP0108 -' OFED SRP target: IBA\V000002P00005A44 -' one driver handles all three. Definition also used for SRP uninstall. - -SRP_IDS = Array(_ - "IBA\V000002P00005A44",_ - "IBA\C0100C609EP0108",_ - "IBA\CFF00C609EP0108",_ - "IBA\V00066AP00000038",_ - "IBA\V000006P00006282") - -Function devman_Install_SRP(WshShell,sInstalldir) - - Dim Devices,devID,rc,found - - devmanSRP = "cmd.exe /c cd /d " & sInstalldir & "SRP & " & _ - "..\IBcore\devman.exe " - Err.clear - found = 0 - - On Error Resume Next - - Devices = Find_IBA_Devices(WshShell,sInstalldir) - If IsNull(Devices) Then - msgbox "missing SRP [IBA\*] devices?",,"devman_Install_SRP" - devman_Install_SRP = -1 - Exit Function - End If - - For each ID in SRP_IDS - For each devID in Devices - If Instr(1,devID,ID) = 1 Then - found = 1 - rc = WshShell.Run (devmanSRP & "update ib_srp.inf " & ID,0,true) - ' Display error number and description if applicable - If rc <> 0 Then - msgbox "Err(" & rc & ") devman update ib_srp.inf " & _ - ID,,"devman_Install_SRP" - ElseIf sDBG >= "1" Then - msgbox "SRP install OK.",,"devman_Install_SRP" - End If - Exit For - End If - Next -' One driver handles all SRP devices - install once? -' If found = 1 Then -' Exit For -' End If - Next - - If found = 0 Then - devman_Install_SRP = -1 - Else - devman_Install_SRP = 0 - End If - -End Function - Function dpinst_Install_SRP(WshShell,sInstalldir) Dim dpinstSRP,cmd,rc dpinst_Install_SRP = 0 dpinstSRP = "cmd.exe /c cd /d " & sInstalldir _ - & "SRP & ..\ibcore\dpinst.exe " - cmd = dpinstSRP & "/S /F /SA /PATH """ & sInstalldir & "SRP""" & " /SE /SW" + & "SRP & ..\dpinst.exe " + cmd = dpinstSRP & "/S /F /SA /PATH """ & sInstalldir & "Drivers\SRP""" & " /SE /SW" rc = WshShell.Run (cmd,0,true) If (rc AND DPINST_INSTALLED) = 0 Then dpinst_status "SRP Install failed",cmd,rc,"dpinst_Install_SRP" @@ -796,29 +662,6 @@ Function dpinst_Install_SRP(WshShell,sInstalldir) End Function -Sub devman_Install_IOU(WshShell,sInstalldir) - - Dim rc,devman - - devman = "cmd.exe /c cd /d " & sInstalldir & "IBcore & devman.exe " - - On Error Resume Next - - dev_list = Find_Dev_by_Tag(WshShell,devman,"find","InfiniBand I/O Unit") - - ' if no IB_IOU device found, install ibiou.sys driver - If IsNull(dev_list) Then - rc = WshShell.Run (devman & "update ib_iou.inf IBA\IB_IOU",0,true) - If rc <> 0 Then - msgbox "Install_IOU Err(" & rc & ")" & _ - "devman update ib_iou.inf IBA\IB_IOU" - End If -' Else -' msgbox "IOU driver already loaded [" & dev_list(0) & "]" - End If - -End Sub - ' For installer error codes see ' http://msdn2.microsoft.com/en-us/library/aa368542(VS.85).aspx @@ -873,267 +716,13 @@ Sub dpinst_status(umsg,cmd,err,title) End Sub -Function dpinst_install(WshShell,sInstalldir,localSM,IPOIB,VNIC,SRP) - - Dim dpinst,dpinstNET,cmd,rc - - err.clear - On Error Resume Next - - cmdspec = "cmd.exe /c cd /d " & sInstalldir - dpinst = cmdspec & "IBcore & dpinst.exe " - dpinstNET = cmdspec & "net & ..\ibcore\dpinst.exe " - - cmd = dpinst & "/F /SA /SE /SW" - - ' HCA driver install - mlx4 or mthca, dpinst does all .inf files in - ' the current folder. - - rc = WshShell.Run (cmd,0,true) - - If (rc AND DPINST_INSTALLED) = 0 Then - dpinst_status "HCA driver Install failed",cmd,rc,"dpinst_install" - dpinst_install=ERROR_INSTALL_FAILURE - Exit Function - ElseIf sDBG >= "1" Then - dpinst_status "IB Core Drivers [HCA] Install OK.",cmd,rc,_ - "dpinst_install" - End If - - ' Check/install IPoIB driver - If IPOIB Then - cmd = dpinstNET & "/S /F /SA /PATH """ & sInstalldir & "net""" & _ - " /SE /SW" - rc = WshShell.Run (cmd,0,true) - If (rc AND DPINST_INSTALLED) = 0 Then - dpinst_status "IPoIB Install failed",cmd,rc,"dpinst_install" - dpinst_install=ERROR_INSTALL_FAILURE - Exit Function - - ElseIf sDBG >= "1" Then - dpinst_status "IPoIB Install OK.",cmd,rc,"dpinst_install" - End If - End If - - ' Start the Local OpenSM Subnet Manager service? - If localSM Then - OpenSM_StartMeUp WshShell,sInstalldir - If sDBG >= "1" Then - msgbox "Local Subnet Management Service [OpenSM] started.",,_ - "dpinst_install" - End If - End If - - ' IOU driver is installed (loaded into driver store) by default as the - ' driver (ibiou.sys) is located in IBcore; side-effect of dpinst.exe. - - If VNIC Then - rc = dpinst_Install_VNIC(WshShell,sInstalldir) - End If - - If SRP Then - rc = dpinst_Install_SRP(WshShell,sInstalldir) - End If - - dpinst_install = 0 - -End Function - - -Function devman_install(WshShell,sInstalldir,have_mthca,have_mlx4,localSM,IPOIB,VNIC,SRP) - - Dim devman,cmd,Return,rc - - ' an HCA driver is 'required' for devman/devcon installs. - If have_mlx4 = 0 AND have_mthca = 0 Then - msgbox "devman_install - No HCA Driver Selected to Install?" & _ - " Aborting Installation." - ' Force installer cleanup (real magic). - devman_install=ERROR_INSTALL_SOURCE_ABSENT - Exit Function - End If - - devman = "cmd.exe /c cd /d " & sInstalldir & "IBcore & devman.exe " - - dev_list = Find_Dev_by_Tag(WshShell,devman,"find","VEN_15B3") - - If IsNull(dev_list) Then - msgbox "HCA driver install - No HCA devices to install?" - devman_install=ERROR_INSTALL_FAILURE - Exit Function - End If - - ' Install ConnectX (mlx4) HCA driver? - - If have_mlx4 Then - - ' install ConnectX/mlx4 bus driver - cmd = "" - For each Dev in dev_list - If Dev <> "" Then - mlxdev = Left(Dev,(Instr(dev,"SUBSYS")-2)) - devID = Right(mlxdev,4) - If IsInfiniHost( devID ) = 0 Then - ' Mixed HCA types? if not, then Vendor_ID install - If have_mthca = 0 Then - mlxdev = "PCI\VEN_15B3" - End If - cmd = devman & "update mlx4_bus.inf """ & mlxdev & """" - Return = WshShell.Run (cmd,0,true) - If Return = 0 Then - DrvInstalled = true - Exit For - End If - ' one time install attempt if not mixed HCA types present. - ' Otherwise, continue with next mlx4 DEV_ID - If have_mthca = 0 Then - Exit For - End If - End if - End if - Next - - If DrvInstalled <> true Then - msgbox "mlx4_bus Install failed(" & Return & ") " & cmd - devman_install=ERROR_INSTALL_FAILURE - Exit Function - End if - - ' Install ConnectX HCA - cmd = devman & "update mlx4_hca.inf MLX4\CONNECTX_HCA" - Return = WshShell.Run (cmd,0,true) - - ' Display error number and description if applicable - If Return <> 0 Then - msgbox "mlx4_hca Install Err(" & Return & ") " & cmd - devman_install=ERROR_INSTALL_FAILURE - Exit Function - End if - End If - - ' InfiniHost (mthca) HCA driver? - - If have_mthca Then - DrvInstalled = false - - cmd = "" - For each dev in dev_list - If dev <> "" Then - idev = Left(dev,(Instr(dev,"SUBSYS")-2)) - devID = Right(idev,4) - If IsInfiniHost( devID ) = 1 Then - ' Mixed HCA types? if not, then Vendor_ID install - If have_mlx4 = 0 Then - idev = "PCI\VEN_15B3" - End If - cmd = devman & "update mthca.inf """ & idev & """" - Return = WshShell.Run (cmd,0,true) - If Return = 0 Then - DrvInstalled = true - Exit For - End If - ' one time install attempt if no mlx4 present - ' otherwise, continue with next DEV_ID - If have_mlx4 = 0 Then - Exit For - End If - End if - End if - Next - - If DrvInstalled <> true Then - msgbox "InfiniHost(mthca) Install Err(" & Return & ") " & cmd - devman_install=ERROR_INSTALL_FAILURE - Exit Function - End if - End if - - ' an HCA driver is 'required'. - - If DrvInstalled = false Then - msgbox "No HCA Driver Selected to Install? Aborting Installation.",,_ - "devman_install" - ' Force installer cleanup (real magic). - devman_install=ERROR_INSTALL_SOURCE_ABSENT - Exit Function - ElseIf sDBG >= "1" Then - msgbox "IB Core Drivers [HCA] installed OK.",,"devman_install" - End if - - ' Start a Local OpenSM Subnet Manager service? - If localSM Then - OpenSM_StartMeUp WshShell,sInstalldir - If sDBG >= "1" Then - msgbox "Local Subnet Management Service [OpenSM] started.",,_ - "devman_install" - End If - End If - - ' Check/install IPoIB driver - If IPOIB Then - devmanNET = "cmd.exe /c cd /d " & sInstalldir & _ - "net & ..\IBcore\devman.exe " - cmd = devmanNET & "update netipoib.inf IBA\IPoIB" - rc = WshShell.Run (cmd,0,true) - If rc <> 0 Then - msgbox "IPoIB Install Err(" & rc & ") " & cmd - devman_install=ERROR_INSTALL_FAILURE - Exit Function - ElseIf sDBG >= "1" Then - msgbox "IPoIB Install OK.",,"devman_install" - End If - End If - - ' IB I/O Unit driver installed only if required by VNIC or SRP. - - If VNIC OR SRP Then - rc = WshShell.Run (devman & "update ib_iou.inf IBA\IB_IOU",0,true) - If rc <> 0 Then - msgbox "IOU Install Err(" & rc & ") update ib_iou.inf IBA\IB_IOU" - devman_install=ERROR_INSTALL_FAILURE - Exit Function - ElseIf sDBG >= "1" Then - msgbox "IOU driver Install OK.",,"devman_install" - End If - End If - - ' Install Qlogic VNIC Driver? - If VNIC Then - devman_Install_VNIC WshShell,sInstalldir - End if - - ' Install SRP (SCSI RDMA Protocol) Driver? - If SRP Then - For cnt=1 To 3 - rc = devman_Install_SRP(WshShell,sInstalldir) - If rc <> -1 Then - Exit For - End If - WshShell.popup "Waiting 10 seconds for SRP device(s) to appear",10,_ - "WinOF - SRP Install" - Next - If rc = -1 Then - msgbox "WinOF: Missing SRP device?" & vbCrLf _ - & "cd /d " & sInstalldir & "SRP" & vbCrLf _ - & "..\IBcore\devman find IBA\*" & vbCrLf _ - & "..\IBcore\devman update ib_srp.inf found-SRP-DevID" & vbCrLf - End If - End If - - devman_install = 0 - -End Function - - ''''''''''' Device Driver Install '''''''''''' Function DriverInstall() - Dim PropArray - Dim devman, devmanEXE - Dim rc, cmd, CheckMode, sInstalldir, fso - Dim DrvInstalled, idev, mlxdev, dev, dev_list - Dim VersionNT,IPOIB,SRP,VNIC,localSM + Dim CheckMode, PropArray + Dim VersionNT, InstallThis, localSM + Dim rc, cmd, sInstalldir, fso On Error Resume Next @@ -1168,112 +757,43 @@ Function DriverInstall() VersionNT = PropArray(4) InstallThis = PropArray(5) sDBG = PropArray(8) ' set global debug flag. - - devman = "cmd.exe /c cd /d " & sInstalldir & "IBcore & devman.exe " + localSM = instr(InstallThis,"fOSMS") Set WshShell = CreateObject("WScript.Shell") Set fso = CreateObject("Scripting.FileSystemObject") - If Not fso.FileExists(sInstalldir & "IBcore\devman.exe") Then - msgbox "DriverInstall: missing " & sInstalldir & "IBcore\devman.exe" - DriverInstall=ERROR_FUNCTION_FAILED - Exit Function - End if - - If Not fso.FileExists(sInstalldir & "IBcore\dpinst.exe") Then - msgbox "DriverInstall: missing " & sInstalldir & "IBcore\dpinst.exe" - DriverInstall=ERROR_FUNCTION_FAILED - Exit Function - End if - err.clear - - ' rescan system buses - Return = WshShell.Run (devman & "rescan", 0, true) - ' Install HCA (Host Channel Adapter) Driver(s) - DrvInstalled = false - - ' Which HCAs are selected to install? - ' Both InfiniHost(mthca) and ConnectX(mlx4) have the same - ' Vendor ID VEN_15B3, so a generic by 'Vendor ID' install will not suceed. - ' In the case of both InfiniHost and ConnectX (mixed) HCAs present, install - ' using VENDOR_ID and DEV_ID. - - have_mlx4 = 0 - have_mthca = 0 - - ' msgbox "InstallThis " & InstallThis + ' DIFxApp (Driver Install Frameworks for Applications) + ' http://www.microsoft.com/whdc/driver/install/DIFxFAQ.mspx#E4AAC + ' DIFxApp.wixlib has already installed driver to Driver Store; PNP will + ' handle the actual device driver install/rollback on failure. - have_mlx4 = instr(InstallThis,"hca_connectX") - have_mthca = instr(InstallThis,"hca_mthca") - IPOIB = instr(InstallThis,"fIPoIB") - localSM = instr(InstallThis,"fOSMS") - - SRP = instr(InstallThis,"fSRP") _ - AND fso.FileExists(sInstalldir & "SRP\ib_srp.inf") - - VNIC = instr(InstallThis,"fVNIC") _ - AND fso.FileExists(sInstalldir & "qlgcvnic\netvnic.inf") - - ' Flag Windows LongHorn Install (aka Vista | Server 2008) - - ' Use DPINST.EXE for Svr 2008 & Vista Driver Install (Windows LongHorn) - ' otherwise use devman. - - If VersionNT >= UseDPinst Then - DriverInstall = dpinst_install(WshShell,sInstalldir,localSM,IPOIB,_ - VNIC,SRP) - Else - DriverInstall = devman_install(WshShell,sInstalldir,have_mthca,_ - have_mlx4,localSM,IPOIB,VNIC,SRP) - End If - - If DriverInstall <> 0 Then - DriverUninstall - If VersionNT >= UseDPinst Then - ' dpinst.exe removes from the 'Driver Store' - dpinst = "cmd.exe /c cd /d " & sInstalldir & "IBcore & dpinst.exe " - If VNIC then - find_remove_INF_file WshShell,dpinst,"netvnic.cat" - End If - - If SRP then - find_remove_INF_file WshShell,dpinst,"ib_srp.cat" - End If - find_remove_INF_file WshShell,dpinst,"ib_iou.cat" - find_remove_INF_file WshShell,dpinst,"ipoib.cat" - find_remove_INF_file WshShell,dpinst,"mthca.cat" - find_remove_INF_file WshShell,dpinst,"mlx4" - End If - - If VNIC then - find_remove_INF_file WshShell,devman,"netvnic.cat" - End If + ' OpenSM Subnet Manager service was already created in the disabled state. + ' Should the Local OpenSM Subnet Manager service be started/enabled? - If SRP then - find_remove_INF_file WshShell,devman,"ib_srp.cat" + If localSM Then + OpenSM_StartMeUp WshShell,sInstalldir + If sDBG >= "1" Then + msgbox "Local Subnet Management Service [OpenSM] started.",,_ + "DriverInstall" End If - find_remove_INF_file WshShell,devman,"ib_iou.cat" - find_remove_INF_file WshShell,devman,"ipoib.cat" - find_remove_INF_file WshShell,devman,"mthca.cat" - find_remove_INF_file WshShell,devman,"mlx4" End If + DriverInstall = 0 + End Function ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' -' Find IBA devices using Devcon +' Find IBA devices using devman.exe Function Find_IBA_Devices(WshShell,sInstalldir) Dim dev - Set ibaDevicesExec = WshShell.Exec ("cmd.exe /c cd " & sInstalldir & "IBcore & devman.exe findall * | FIND ""IBA""") - - ' Set ibaDevicesExec = WshShell.Exec ("cmd.exe /c cd " & sInstalldir & "IBcore & devman.exe find IBA\*") + Set ibaDevicesExec = WshShell.Exec ("cmd.exe /c cd " & sInstalldir & "Drivers & devman.exe findall * | FIND ""IBA""") ibaDevices = split(ibaDevicesExec.StdOut.ReadAll, vbCrLF) @@ -1541,7 +1061,7 @@ Function GetDeviceInstallInfo(WshShell,sInstalldir,Dev) Dim devman,tmp,s,StrStart,StrEnd,FileCnt,INF devman = "cmd.exe /c cd /d " & sInstalldir & _ - "IBcore & devman.exe driverfiles ""@" + "Drivers & devman.exe driverfiles ""@" Set connExec = WshShell.Exec(devman & Dev & """") cmdout = split(connExec.StdOut.ReadAll, vbCrLF) @@ -1594,21 +1114,14 @@ End Function ' remove IB I/O Unit driver Sub Uninstall_IOU(fso,WshShell,devList,sInstalldir,VersionNT) - - Dim devman,tool + Dim tool RemoveDevice fso,WshShell,sInstalldir,devList,"InfiniBand I/O Unit",VersionNT - devman = "cmd.exe /c cd /d " & sInstalldir & "IBcore & devman.exe " + tool = "cmd.exe /c cd /d " & sInstalldir & "Drivers & dpinst.exe " - If VersionNT >= UseDPinst Then - ' use dpinst.exe instead of devman.exe for Windows LongHorn++ - tool = replace(devman,"devman","dpinst") - Else - tool = devman - End If + ' dpinst (loads the driver store) not load the driver (no srp/vnic) - ' dpinst can install and not load the driver (no srp/vnic) find_remove_INF_file WshShell,tool,"ib_iou.cat" End Sub @@ -1621,7 +1134,7 @@ Sub Uninstall_VNIC(fso,WshShell,devices,sInstalldir,VersionNT) Dim devman,Return,device,dt,sDRIVERS,tool,devInfo - devman = "cmd.exe /c cd /d " & sInstalldir & "IBcore & devman.exe " + devman = "cmd.exe /c cd /d " & sInstalldir & "Drivers & devman.exe " If IsNull(devices) Then ' create a list of IBA\* devices via "devcon find" @@ -1646,12 +1159,8 @@ Sub Uninstall_VNIC(fso,WshShell,devices,sInstalldir,VersionNT) End if Next - If VersionNT >= UseDPinst Then - ' use dpinst.exe instead of devman.exe for Windows LongHorn++ - tool = replace(devman,"devman","dpinst") - Else - tool = devman - End If + ' use dpinst.exe instead of devman.exe for Windows LongHorn++ + tool = replace(devman,"devman","dpinst") If IsNull(devInfo) Then find_remove_INF_file WshShell,tool,"netvnic.cat" @@ -1663,6 +1172,19 @@ Sub Uninstall_VNIC(fso,WshShell,devices,sInstalldir,VersionNT) End Sub +' QLogic Virtual FC I/O controller or +' InfiniBand SRP Miniport: IBA\C0100C609EP0108 or IBA\CFF00C609EP0108 +' OFED SRP target: IBA\V000002P00005A44 +' one driver handles all three. + +SRP_IDS = Array(_ + "IBA\V000002P00005A44",_ + "IBA\C0100C609EP0108",_ + "IBA\CFF00C609EP0108",_ + "IBA\V00066AP00000038",_ + "IBA\V000006P00006282") + + Sub Uninstall_SRP(fso,WshShell,devices,sInstalldir,VersionNT) Dim devman,devmanRMAT,devmanDAAT,Return,device,sDRIVERS,tool,devInfo @@ -1672,7 +1194,7 @@ Sub Uninstall_SRP(fso,WshShell,devices,sInstalldir,VersionNT) ' one driver handles all three. ' See previous SRP_IDS definition @ Install_SRP. - devman = "cmd.exe /c cd /d " & sInstalldir & "IBcore & devman.exe " + devman = "cmd.exe /c cd /d " & sInstalldir & "Drivers & devman.exe " devmanRMAT = devman & "remove @" devmanDAAT = devman & "disable @" devInfo = Null @@ -1706,12 +1228,8 @@ Sub Uninstall_SRP(fso,WshShell,devices,sInstalldir,VersionNT) Next Next - If VersionNT >= UseDPinst Then - ' use dpinst.exe instead of devman.exe for Windows LongHorn++ - tool = replace(devman,"devman","dpinst") - Else - tool = devman - End If + ' use dpinst.exe instead of devman.exe for Windows LongHorn++ + tool = replace(devman,"devman","dpinst") 'No SRP device - check/clear to be safe. If IsNull(devInfo) Then @@ -1728,7 +1246,7 @@ Sub RemoveDevice(fso,WshShell,sInstalldir,devList,DeviceTag,VersionNT) dim device,devman,devmanRMAT,devTarget,dt,Return,devInfo - devman = "cmd.exe /c cd /d " & sInstalldir & "IBcore & devman.exe " + devman = "cmd.exe /c cd /d " & sInstalldir & "Drivers & devman.exe " devmanRMAT = devman & "remove ""@" devmanDAAT = devman & "disable ""@" devInfo = Null @@ -1760,12 +1278,8 @@ Sub RemoveDevice(fso,WshShell,sInstalldir,devList,DeviceTag,VersionNT) Exit Sub End If - If VersionNT >= UseDPinst Then - ' use dpinst.exe instead of devman.exe for Windows LongHorn++ - tool = replace(devman,"devman","dpinst") - Else - tool = devman - End If + ' use dpinst.exe instead of devman.exe for Windows LongHorn++ + tool = replace(devman,"devman","dpinst") cleanup_driver_files fso,WshShell,sInstalldir,tool,VersionNT,devInfo @@ -1777,7 +1291,7 @@ Sub remove_all_HCA_devices(fso,WshShell,sInstalldir,VersionNT) Dim devman,tool - devman = "cmd.exe /c cd /d " & sInstalldir & "IBcore & devman.exe " + devman = "cmd.exe /c cd /d " & sInstalldir & "Drivers & devman.exe " ' Old (CoInstaller version) ibbus GUID - just in case. Return = WshShell.Run (devman & "remove {94F41CED-78EB-407C-B5DF-958040AF0FD8",0,true) @@ -1787,12 +1301,8 @@ Sub remove_all_HCA_devices(fso,WshShell,sInstalldir,VersionNT) ' VEN_15B3 covers devices: mthca & mlx4_bus RemoveDevice fso,WshShell,sInstalldir,Null,"PCI\VEN_15B3",VersionNT - If VersionNT >= UseDPinst Then - ' use dpinst.exe instead of devman.exe for Windows LongHorn++ - tool = "cmd.exe /c cd /d " & sInstalldir & "IBcore & dpinst.exe " - Else - tool = devman - End If + ' use dpinst.exe instead of devman.exe for Windows LongHorn++ + tool = "cmd.exe /c cd /d " & sInstalldir & "Drivers & dpinst.exe " find_remove_INF_file WshShell,tool,"mthca" find_remove_INF_file WshShell,tool,"mlx4_hca" @@ -1809,11 +1319,11 @@ Sub Uninstall_IB_Devices(fso,WshShell,sInstalldir,VersionNT) Dim devList - If (fso.FileExists(sInstalldir & "IBcore\dpinst.exe") = False) Then + If (fso.FileExists(sInstalldir & "Drivers\dpinst.exe") = False) Then Exit Sub ' no reason to continue without the tool. End if - If (fso.FileExists(sInstalldir & "IBcore\devman.exe") = False) Then + If (fso.FileExists(sInstalldir & "Drivers\devman.exe") = False) Then Exit Sub ' no reason to continue without the tool. End if @@ -1867,9 +1377,9 @@ Sub DriverUninstall() sRemove = "ALL" End If - If fso.FileExists(sInstalldir & "net\ndinstall.exe") Then + If fso.FileExists(sInstalldir & "Drivers\net\ndinstall.exe") Then Return = WshShell.Run ("cmd.exe /c cd /d " & sInstalldir & _ - "net & ndinstall.exe -r", 0, true) + "Drivers\net & ndinstall.exe -r", 0, true) End If If sVersionNT <> WindowsXP AND fso.FileExists(sInstalldir & "installsp.exe") Then @@ -2116,7 +1626,7 @@ Sub RunOnceCleanup(fso,sInstalldir) End if script = "RunOnceWinOFcleanup.bat" - src = sInstalldir & "IBcore\" & script + src = sInstalldir & "Drivers\" & script If Not fso.FileExists(src) Then msgbox "Missing " & src @@ -2232,7 +1742,7 @@ End Function Function InstallChanged - Dim rc, sInstalldir, sAddLocal, sRemove, sDRIVERS, NeedReboot, WLH + Dim rc, sInstalldir, sRemove, sDRIVERS, NeedReboot Err.clear sRemove = Session.Property("REMOVE") @@ -2248,50 +1758,14 @@ Function InstallChanged NeedReboot = 0 sInstalldir = Session.Property("INSTALLDIR") - sAddLocal = Session.Property("ADDLOCAL") - ' Flag Windows LongHorn Install (aka Vista | Server 2008) VersionNT = Session.Property("VersionNT") - If VersionNT >= UseDPinst Then - WLH = 1 - Else - WLH = 0 - End if On Error Resume Next - If (Not IsNull(sAddLocal)) AND (sAddLocal <> "") Then - If Instr(sAddLocal,"fSRP") OR Instr(sAddLocal,"fVNIC") Then - ' IOU driver loaded into driver store when HCA driver installed. - If WLH = 0 Then - devman_Install_IOU WshShell,sInstalldir - End If - End If - - If Instr(sAddLocal,"fSRP") Then - If fso.FileExists(sInstalldir & "SRP\ib_srp.inf") Then - If WLH Then - rc = dpinst_Install_SRP(WshShell,sInstalldir) - Else - rc = devman_Install_SRP(WshShell,sInstalldir) - End If - Else - msgbox "ERR: missing " & sInstalldir & "SRP\ib_srp.inf" - End If - End If + ' Nothing to do for ADD as DIFxAPP has loaded drivers into Driver Store. - If Instr(sAddLocal,"fVNIC") Then - If fso.FileExists(sInstalldir & "qlgcvnic\netvnic.inf") Then - If WLH Then - rc = dpinst_Install_VNIC(WshShell,sInstalldir) - Else - devman_Install_VNIC WshShell,sInstalldir - End If - Else - msgbox "Err: missing " & sInstalldir & "qlgcvnic\netvnic.inf" - End If - End If - End If + ' For REMOVE - cleanup If (Not IsNull(sRemove)) AND (sRemove <> "") Then diff --git a/branches/winverbs/WinOF/WIX/README_release.txt b/branches/winverbs/WinOF/WIX/README_release.txt index adf1d899..d6458823 100644 --- a/branches/winverbs/WinOF/WIX/README_release.txt +++ b/branches/winverbs/WinOF/WIX/README_release.txt @@ -1,5 +1,5 @@ -[4-17-09] WinOF 2.1 (RC0) release +[6-04-09] WinOF 2.1 (pre-RC0) release Downloads available at http://www.openfabrics.org/downloads/WinOF/v2.1_rc0 @@ -8,9 +8,9 @@ WinOF 2.1 Summary Changes ------------------------- 1) The WinOF 2.1 release is based on openib-windows source svn revision - (branches\WOF2-1 svn.xxxx). + (branches\WOF2-1 svn.2250). - Last WinOF release (2.0.1) based on svn.1932. + Last WinOF release (2.0.2) based on svn.1975. 2) Bug fixes in @@ -20,10 +20,11 @@ WinOF 2.1 Summary Changes SRP DAT/DAPL WinVerbs + WinMAD OFED (Open Fabrics Enterprise Distribution [Linux]) verbs API - OFED Diagnostics + OFED Diagnostic utilities -3) New Functionality +3) Integrated Functionality - OFED Compatibility layers allow for easy porting of OFED applications into the WinOF environment. @@ -32,27 +33,38 @@ WinOF 2.1 Summary Changes libumad - IB MAD exported user-mode interface library. librdmacm - OFED RDMA CM (Comunications Manager). - - OFED fabric diagnostic utilities are available. - ibstat - display HCA information. + - OFED Fabric Diagnostics available ( for usage info, see --help ). + ibaddr - query InfiniBand address(es) ibnetdiscover - generate a fabric topology. - portinfo - display InfiniBand port specific information. + iblinkinfo - report link info for all links in the fabric + ibping - ping an InfiniBand address + ibportstate - manage port (physical) state and link speed of an InfiniBand port + ibqueryerrors - query and report non-zero IB port counters + ibroute - query InfiniBand switch forwarding tables + ibstat - display HCA information. + ibsysstat - system status for an InfiniBand address + ibtracert - trace InfiniBand path saquery - SA (Subnet Administrator) query test. + sminfo - query InfiniBand SMInfo attributes + smpdump - dump InfiniBand subnet management attributes + smpquery - query InfiniBand subnet management attributes + vendstat - query InfiniBand vendor specific functions - Connected mode IPoIB ensures higher performance IPoIB transfers in addition to OFED (Linux) IPoIB compatibility. - Windows Server 2008/Vista WinOF installs now utilize Windows Plug-n-Play (PNP) to install the correct HCA driver(s). Selection of a specific HCA - device type is no longer required for Server 2008/Vista/HPC. + device driver type is no longer required for Server 2008/Vista/HPC. Windows Server 2003 & XP installs require an explicit HCA device - type selection; the default HCA device type is Mellanox InfiniHost. + driver type selection; the default HCA device type is Mellanox InfiniHost. - Windows Server 2003/XP WinOF unattended installs can override the install - default HCA device 'InfiniHost' via the msiexe.exe command line override + Windows Server 2003/XP WinOF unattended installs can override the default + HCA device type 'InfiniHost' via the msiexe.exe command line override 'HCA=cx'; where 'cx' implies Mellanox ConnectX HCA device(s). Specifing 'HCA=+cx' will install the ConnectX HCA driver in addition to - the InfiniHost HCA driver; mixed HCA environment. + the InfiniHost HCA driver; a mixed HCA environment. Example unattended installs: Server 2003 with ConnectX HCA @@ -68,17 +80,19 @@ WinOF 2.1 Summary Changes - Server 2008-HPC install support has been enhanced to provide a no-drivers - installed mode. Device driver '.inf' files are not processed during the - WinOF install. - The base assumption is that a WDM node provisioning template will install - WinOF drivers. All other WinOF files are installed to the standard WinOF - location '%ProgramFiles(x86)%\WinOF'. + installed mode to ease WinOF installation using WDM. Device driver '.inf' + files are not processed during the WinOF install. + The base assumption is that a WDM node provisioning template (see cluster + Manager) will install WinOF drivers. All other WinOF files are installed + to the standard WinOF location '%ProgramFiles(x86)%\WinOF'. Examples - unattended install: start/wait msiexec /I WOF.msi /quiet NODRV=1 + unattended install for use with clusrun.bat + start/wait msiexec /I WOF.msi /quiet NODRV=1 - fast attended install: start/wait msiexec /I WOF.msi /passive NODRV=1 + console based non-interactive install: + start/wait msiexec /I WOF.msi /passive NODRV=1 install selectable features: start/wait msiexec /I WOF.msi NODRV=1 @@ -87,17 +101,20 @@ WinOF 2.1 Summary Changes The folder %TEMP%\PFiles\WinOF will be created. - unattended uninstall with auto-reboot: + console based unattended uninstall with auto-reboot: start/wait msiexec /X WOF.msi /passive - - SRP and VNIC drivers are command line selectable: + clusrun unattended uninstall with auto-reboot + start/wait msiexec /X WOF.msi /quiet /forcereboot + + - SRP and VNIC drivers are command line install selectable: start/wait msiexec /I WOF.msi /passive SRP=1 start/wait msiexec /I WOF.msi /passive VNIC=1 - Subnet Management started as a local Windows Service from a command line: start/wait msiexec /I WOF.msi /passive OSMS=1 - - HCA drivers load WinVerbs and WinMad filter drivers by default. + - HCA drivers loads filter drivers WinVerbs and WinMad by default. @@ -106,7 +123,7 @@ Note on Vista installs Only: Vista installs must be performed from an Administrator priviledged command window. Right-clicking the .msi installer file for a Vista installation will fail due to insufficent privileges to install the HCA driver! - From the Administrator privileged cmd-window say + From the Administrator privileged cmd-window (Interactive install) say start/wait msiexec /I WinOF_wlh_xxx.msi -or- @@ -137,5 +154,7 @@ Please: make 'sure' your HCA firmware is recent; vstat.exe displays HCA firmware version. -thank you. +thank you, + +WinOF Developers. diff --git a/branches/winverbs/WinOF/WIX/Release_notes.htm b/branches/winverbs/WinOF/WIX/Release_notes.htm index c8ceb7d1..cf495e94 100644 --- a/branches/winverbs/WinOF/WIX/Release_notes.htm +++ b/branches/winverbs/WinOF/WIX/Release_notes.htm @@ -92,7 +92,7 @@ src=openfabrics.gif>

2.1 Release Notes

-04/10/2009

+06/08/2009
@@ -110,8 +110,8 @@ How to Install
-

-Unattended Install

+

+Unattended Install

HCA Device Driver Installation

@@ -140,6 +140,7 @@ Known Issues


 

+

Overview

The @@ -150,7 +151,7 @@ InfiniBand fabric.

 Binary files generated from the OpenIB-windows developers subversion (svn) source tree 'svn://openib.tc.cornell.edu' -(branches\WOF2-0 svn revision 1835) +(branches\WOF2-1 svn revision 1835) are packaged into a WIX 2.0 (Windows Installer Xml) single file install package referred to as the Windows OpenFabrics (WinOF) release 2.1.
@@ -163,7 +164,7 @@ to numerous components along with full Winverbs and OFED integration modules:

  • Windows Server - 2008 and Vista are supported.

  • + 2008, HPC and Vista are supported.

  • Network Direct supported on Server 2008/HPC.

  • @@ -171,8 +172,21 @@ to numerous components along with full Winverbs and OFED integration modules:Mellanox ConnectX(mlx4) drivers enhanced for increased performance and stability.

  • +

    WInVerbs and WinMad HCA + filter drivers are automatically loaded in support of OFED verbs and + diagnostics.

  • +
  • +

    OFED verbs library + enables easy porting of Linux OFED applications into the WinOF environment.

    +
  • +
  • QLogic has - enhanced VNIC & SRP for increased performance and stability.

  • + enhanced VNIC & SRP for increased performance and stability.

    + +
  • +

    uDAT/DAPL is now a + common code base with OFED uDAT/DAPL; supported DAPL providers: IBAL, + socket-cm and rdma-cm.

  • Bug fixes for stability in IBcore, WSD, VNIC, SRP, IPoIB, DAT/DAPL

  • @@ -197,7 +211,8 @@ supported devices below
  • - Infiniband Core components

  • + Infiniband Core components: IBAL, + WInverbs, Winmad, OFED verbs and rdma_cm.
  • Upper Layer Protocols: IPoIB, WSD, ND, VNIC, SRP Initiator and DAT/DAPL

  • @@ -213,7 +228,8 @@ supported devices below

    Performance tests

  • -

    Diagnostic tools

    +

    + OFED Diagnostic tools

  • @@ -231,6 +247,7 @@ supported devices below

    <Return-to-Top>

     

    +

    Supported Platforms, Operating Systems and Infiniband Hardware

    CPU architectures

    @@ -259,22 +276,18 @@ supported devices below Server 2008 R2

  • -

    Windows 7 - (Server/Ultimate/Professional)

    -
  • +

    + Vista

  • -

    Vista

    -
  • +

    + Windows Server 2008 HPC Edition

  • -

    Windows Server 2008 HPC Edition

    -
  • -
  • -

    Windows Server 2003 - / XP64

    -
  • +

    + Windows Server 2003 + / XP64

  • -

    Windows XP/32 (SP2)

    -
  • +

    + Windows XP32 (SP2)

    Supported HCAs (Host Channel Adapters)

    @@ -300,11 +313,11 @@ tab-stops:list .5in'>
  • MT25408 - ConnectX DDR Channel Adapter, not +tab-stops:list .5in'>MT25408 - ConnectX Channel Adapters, not yet supported on IA64 platforms.

  • -

    Both SDR and DDR mode of the InfiniHost III family are supported.

    For official Firmware (FW) versions and update tools please see:
    +

    Both SDR, DDR and QDR modes of the InfiniHost III models are supported.

    For official Firmware (FW) versions and update tools please see:
    http://www.mellanox.com/support/firmware_table.php
    Current HCA firmware version can be viewed from the 'vstat' command.

    @@ -333,6 +346,7 @@ Current HCA firmware version can be viewed from the 'vstat' command.

    <Return-to-Top>

     

    +

    Installation Notes

    @@ -375,8 +389,15 @@ OpenFabrics manual.

    <Return-to-Top>

     

    +

    How to Install

    +

    Summary

    +

    Requirements

    Install HCA hardware prior to installing the Windows OpenFabrics Release @@ -487,11 +508,13 @@ hardware vendors who participate in the open source WinOF development process.InfiniBand Core:

  • Optional:
        IPoIB - Internet Protocols over @@ -503,7 +526,6 @@ hardware vendors who participate in the open source WinOF development process.       - DAT/DAPL v2.0 runtime libraries
           - DAT v2.0 application build environment
        OpenSM_service_started - InfiniBand Subnet Management enabled and started as a Windows Service.
                                                    By default OpenSM is installed as a disabled Windows Service.
    -    WinVerbs - OFED verbs API for Windows.
        Checked versions of driver files.
     
  • The 'default' installation includes IB core, IPoIB, WSD (Win2K3 only), @@ -514,7 +536,8 @@ hardware vendors who participate in the open source WinOF development process.<Return-to-Top>

     

    -

    HCA Driver Installation

    +
    +

    HCA Driver Installation

    WinOF Server 2008 / Vista device driver installation operates somewhat differently than Server 2003/XP device driver installation.
    Server 2008 uses the device driver installation program dpinst.exe (Driver @@ -548,62 +571,77 @@ default HCA device type (case of ConnectX HCA hardware):

    The result is the installation of HCA drivers for ConnectX and InfiniHost.
     
  • -

    Unattended Install

    +
    +

    Unattended Install

    - To perform a silent unattended installation for Server - 2003/XP for Mellanox InfiniHost HCA hardware, invoke the following command - from a command window.
    -

    start/wait msiexec.exe /i WinOF_2-0_wnet_x64.msi  /qn  /quiet  /log - %TEMP%\WOF-install.log

    -

    '/log dev:path\logFilename.log' is optional.

    -

    msiexec.exe /? for all options.

    -
    -

    An unattended Server 2003/XP install will install the following 'default' options

    -
      -
    • Infiniband Core modules - Mellanox InfiniHost HCA driver, IB core stack and libraries, - documentation
      - default HCA driver selection is Mellanox InfiniHost, otherwise override - default InfiniHost HCA type with 'HCA=cx'.
    • + +
      + An unattended WinOF install will install the following 'default' options
        +
      • Infiniband Core modules - Mellanox HCA driver + (see HCA driver selection note), IB core stack, libraries, utilities and + documentation.
      • IPoIB - Internet Protocols over InfiniBand
      • WSD - (Win2K8/3 only, not installed on XP) Win Sock Direct
      • -
      • ND - Network Direct [started automatically (via ndinstall.exe) only for Server +
      • ND - NetworkDirect [started automatically (via ndinstall.exe) only for Server 2008/HPC and Vista].
      • DAT & DAPL (v1.1 & v2.0) runtime libraries + dapltest.exe (dt-svr & dt-cli).
      -

      Should WSD not be a desirable unattended install option (Win2008/3 only), +

      HCA Driver Selection for Server 2003/XP

      +
      +

      Default HCA driver selection is Mellanox InfiniHost, otherwise override + default InfiniHost HCA type with msiexec command option 'HCA=cx'.

      +
      +

      HCA selection for all other Windows variants + (Server 2008, HPC & Vista)

      +
      +

      Default HCA driver selection is driven by the + Windows PNP subsystem; no user selection required or supported.

      +
      +

      Should WSD not be a desirable unattended install option (Win2008/3 only), currently you would install unattended then execute the
      command 'installsp -r' on each node to remove WSD.

      +

      To perform a silent unattended installation for Server + 2003/XP for Mellanox InfiniHost HCA hardware, invoke the following command + from a command window.

      +
      +

      start/wait msiexec.exe /i WinOF_2-1_wnet_x64.msi  /qn  /quiet  /log + %TEMP%\WOF-install.log

      +

      '/log dev:path\logFilename.log' is optional.

      +

      msiexec.exe /? for all options.

      +
      +

    Examples:

    -

    Server 2003 - Mellanox HCA Hardware

    +

    Server 2003 - Mellanox HCA Hardware

    • InfiniHost HCA
      - start/wait msiexec.exe /i WinOF_2-0_wnet_x64.msi /qb /quiet
      + start/wait msiexec.exe /i WinOF_2-1_wnet_x64.msi /qb /quiet
       
    • ConnectX HCA
      - start/wait msiexec.exe /i WinOF_2-0_wnet_x64.msi /qb /quiet HCA=cx
      + start/wait msiexec.exe /i WinOF_2-1_wnet_x64.msi /qb /quiet HCA=cx
       
    • InfiniHost and ConnectX
      - start/wait msiexec.exe /i WinOF_2-0_wnet_x64.msi /qb /quiet HCA=+cx
    • + start/wait msiexec.exe /i WinOF_2-1_wnet_x64.msi /qb /quiet HCA=+cx
    -

    Windows XP - Mellanox HCA Hardware - same as above with .msi +

    Windows XP - Mellanox HCA Hardware - same as above with .msi name change (wnet --> wxp).

    • ConnectX HCA
      - start/wait msiexec.exe /i WinOF_2-0_wxp_x86.msi /qb /quiet HCA=cx
      + start/wait msiexec.exe /i WinOF_2-1_wxp_x86.msi /qb /quiet HCA=cx
       
    -

    Server 2008 / Vista - HCA Hardware

    +

    Server 2008 / Vista - any Mellanox HCA Hardware

    • No need to specify HCA type as PNP (Plug-n-Play)  figures out the correct HCA to install.
      - start/wait msiexec.exe /i WinOF_2-0_wlh_x64.msi /qb /quiet
    • + start/wait msiexec.exe /i WinOF_2-1_wlh_x64.msi /qb /quiet

     

    -

    Non-HCA Install Features added to above examples:

    +

    Non-HCA Install Features added to above msiexec command +line examples:

    <Return-to-Top>

     

    +

    QLogic VNIC Configuration

    The QLogic VNIC @@ -1070,6 +1191,7 @@ for the setting to take effect.

    <Return-to-Top>

     

    +

    DAT & uDAPL Configuration

    In order for DAT/DAPL programs to execute correctly, the 'dat.dll' file must be present in the current directory, @@ -1104,6 +1226,7 @@ manual for further details.

    <Return-to-Top>

     

    +

    SRP (SCSI RDMA Protocol) Driver Installation

    The WinOF installer does not install the SRP driver as part of a default installation.  @@ -1148,40 +1271,28 @@ when the WinOF uninstall attempts to delete the files the operation fails. 

    -

    WinVerbs technology preview

    +
    +

    WinVerbs

    WinVerbs is a userspace verbs and communication management interface -optimized
    -for the Windows operating system. Its lower interface is designed to support
    -any RDMA based device, including Infiniband and iWarp. Its upper interface is
    -capable of providing a low latency verbs interface, plus supports Microsoft's
    -Network Direct Interface, DAPL, and OFED libibverbs interfaces. It consists of
    +optimized +for the Windows operating system. Its lower interface is designed to support +any RDMA based device, including Infiniband and iWarp. Its upper interface is +capable of providing a low latency verbs interface, plus supports Microsoft's +NetworkDirect Interface, DAPL, and OFED libibverbs interfaces. It consists of
    a userspace library and a kernel filter driver.

    -The WinVerbs driver loads as an upper filter driver for Infiniband HCA.
    -(Open source iWarp drivers for Windows are not yet available.) A corresponding
    -userspace library installs as part of the Winverbs driver installation package.
    -Additionally, a Windows port of the OFED libibverbs library and several test
    -programs are also included.

    -

    To install WinVerbs and it's test applications, select the 'WinVerbs' feature -from the WinOF installer (.msi).
    -WinVerbs drivers are installed to %ProgramFiles(x86)%\WinOF\WinVerbs, although -the drivers are not automatically loaded.
    -In order to make WinVerbs test applications usable, WinVerbs drivers must be -manually loaded.
    -cd %ProgramFiles(x86)%\WinOF\WinVerbs
    -right-click on winverbs.inf, select 'install'.
    -Once WinVerbs is right-click installed, the HCA driver must be disabled and then -enabled in order for the winverbs upper filter driver to be integrated into the -HCA driver stack; see HCA driver properties->details, look for winverbs.
    -
    -Check if winverbs filter driver is active
    -    %ProgramFiles(x86)%\WinOF\IBcore\devman classfilter -InfiniBandController upper

    -
    -Remove winverbs filter driver
    -    %ProgramFiles(x86)%\WinOF\IBcore\devman classfilter -InfiniBandController upper !winverbs

    +The WinVerbs and WinMAD drivers load as upper filter drivers for the Infiniband HCA +device.
    +(Open source iWarp drivers for Windows are not yet available.)  A corresponding
    +WinVerbs.dll and libibmad.dll +userspace libraries install as part of the Winverbs driver installation package.
    +Additionally, a Windows port of the OFED libibverbs library, several test +programs and OFED InfiniBand diagnostic utilities are also included.

    +

    As of WinOF 2.1, Winverbs components are now integral +components of a default WinOF installation.
    +Although WinVerbs, WinMAD drivers, OFED libraries and utilities are install +selectable features, they are automatically included as part of the default +WinOF install.

    @@ -1189,18 +1300,10 @@ InfiniBandController upper !winverbs

    <Return-to-Top>
     

    -

    Known Issues

    +
    +

    Known Issues
     

    <Return-to-Top>
    diff --git a/branches/winverbs/WinOF/WIX/common/IBcore.inc b/branches/winverbs/WinOF/WIX/common/IBcore.inc index 6a0da213..5b93f015 100644 --- a/branches/winverbs/WinOF/WIX/common/IBcore.inc +++ b/branches/winverbs/WinOF/WIX/common/IBcore.inc @@ -1,94 +1,21 @@ + + LongName="DPInst.exe" Name="DPInst.exe" + Source="..\bin\misc\$(var.ARCH)\DPInst.exe" /> - - - - - - - - - - - - - - - - - - + - - - - - + - - - - - + - diff --git a/branches/winverbs/WinOF/WIX/common/hca_filters.inc b/branches/winverbs/WinOF/WIX/common/hca_filters.inc new file mode 100644 index 00000000..eeaa49ed --- /dev/null +++ b/branches/winverbs/WinOF/WIX/common/hca_filters.inc @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/branches/winverbs/WinOF/WIX/common/iou.inc b/branches/winverbs/WinOF/WIX/common/iou.inc new file mode 100644 index 00000000..21c4f56f --- /dev/null +++ b/branches/winverbs/WinOF/WIX/common/iou.inc @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/branches/winverbs/WinOF/WIX/common/ipoib.inc b/branches/winverbs/WinOF/WIX/common/ipoib.inc index 72335bd6..7d11e9ef 100644 --- a/branches/winverbs/WinOF/WIX/common/ipoib.inc +++ b/branches/winverbs/WinOF/WIX/common/ipoib.inc @@ -1,6 +1,16 @@ - + + + + + @@ -35,9 +45,6 @@ Name="ibwsd32.dll" LongName='ibwsd32.dll' /> - @@ -64,6 +71,12 @@ + + @@ -72,4 +85,6 @@ Name="ndinstal.exe" LongName='ndinstall.exe' /> + + diff --git a/branches/winverbs/WinOF/WIX/common/mlnx_drivers.inc b/branches/winverbs/WinOF/WIX/common/mlnx_drivers.inc index cd3b5942..9335a608 100644 --- a/branches/winverbs/WinOF/WIX/common/mlnx_drivers.inc +++ b/branches/winverbs/WinOF/WIX/common/mlnx_drivers.inc @@ -1,17 +1,19 @@ + + + + Guid="BE4ED061-C852-40a2-A240-F06DBBE1159F" + DriverDeleteFiles="yes" + DriverForceInstall="yes" + DriverLegacy="no" + DriverPlugAndPlayPrompt="no" + DriverAddRemovePrograms="no" + DriverSequence="1" > - - - - + - + + + + + - + - - + - + + + + + + + + + - - - - - @@ -83,6 +102,9 @@ + @@ -97,12 +119,65 @@ + + + - + + + + + + + + + + + + + + + + + + + + + diff --git a/branches/winverbs/WinOF/WIX/common/qlgc_vnic.inc b/branches/winverbs/WinOF/WIX/common/qlgc_vnic.inc index d810dcd2..f8a92399 100644 --- a/branches/winverbs/WinOF/WIX/common/qlgc_vnic.inc +++ b/branches/winverbs/WinOF/WIX/common/qlgc_vnic.inc @@ -1,12 +1,16 @@ - - + - - + + @@ -20,5 +24,5 @@ LongName="netvnic.cat" Name="NETVNIC.cat" Source="..\bin\net\$(var.ARCH)\netvnic.cat" /> - + diff --git a/branches/winverbs/WinOF/WIX/common/srp.inc b/branches/winverbs/WinOF/WIX/common/srp.inc index 42c2aae3..2c3ac4d8 100644 --- a/branches/winverbs/WinOF/WIX/common/srp.inc +++ b/branches/winverbs/WinOF/WIX/common/srp.inc @@ -1,26 +1,33 @@ - - - - - - + + + + + - - - + + diff --git a/branches/winverbs/WinOF/WIX/common/winverbs_drivers.inc b/branches/winverbs/WinOF/WIX/common/winverbs_drivers.inc index 1b147a76..7f58e5cc 100644 --- a/branches/winverbs/WinOF/WIX/common/winverbs_drivers.inc +++ b/branches/winverbs/WinOF/WIX/common/winverbs_drivers.inc @@ -1,30 +1,47 @@ - - - - - - - - - - + + + + + + + + diff --git a/branches/winverbs/WinOF/WIX/dat.conf b/branches/winverbs/WinOF/WIX/dat.conf index 1e627692..3dfce3a9 100644 --- a/branches/winverbs/WinOF/WIX/dat.conf +++ b/branches/winverbs/WinOF/WIX/dat.conf @@ -1,12 +1,13 @@ # -# DAT (DAPL) configuration file +# DAT (DAPL Provider) configuration file # -# Entries scanned sequentially - first entry to open is used. +# Entries scanned sequentially - first entry to open is used unless explicit +# DAPL interface name specified. # # Each entry requires the following fields: # -# \ -# +# \ +# # # DAT v1.1 dapl provider configuration for HCA0 port 1 ibnic0 u1.1 threadsafe default C:\Windows\dapl.dll ri.1.1 "IbalHca0 1" "" @@ -32,7 +33,7 @@ ibnic0v2-scmd u2.0 nonthreadsafe default "C:\\Program Files (x86)\\WinOF\\dapl2- # # DAT 2.0 RDMA-CM # connection; supports DAT Windows <==> Linux over IB connections. -ibnic0v2-cma u2.0 nonthreadsafe default C:\Windows\dapl2-ofa-cma.dll ri.2.0 "ibv_device0 1" "" +ibnic0v2-cma u2.0 nonthreadsafe default C:\Windows\dapl2-ofa-cma.dll ri.2.0 "rdma_dev0 1" "" # # DAT 2.0 RDMA-CM (debug) -ibnic0v2-cmad u2.0 nonthreadsafe default "C:\\Program Files (x86)\\WinOF\\dapl2-ofa-cmad.dll" ri.2.0 "ibv_device0 1" "" +ibnic0v2-cmad u2.0 nonthreadsafe default "C:\\Program Files (x86)\\WinOF\\dapl2-ofa-cmad.dll" ri.2.0 "rdma_dev0 1" "" diff --git a/branches/winverbs/WinOF/WIX/ia64/DPInst.exe b/branches/winverbs/WinOF/WIX/ia64/DPInst.exe deleted file mode 100644 index d2295f9047bda34b67dd38b3453cec30893d3633..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1115704 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

    sS2V`998LtTMe8=jRAK`TT4t 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

    X7a_DJM3Fd7syzB3X*D&Y*67r|< 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 abFIMCV(Xlp)*c>-ajmD6gqb)z{Qm&44Sw+e diff --git a/branches/winverbs/WinOF/WIX/win7/ia64/Makefile b/branches/winverbs/WinOF/WIX/win7/ia64/Makefile index 77e75f2b..2ce9b004 100644 --- a/branches/winverbs/WinOF/WIX/win7/ia64/Makefile +++ b/branches/winverbs/WinOF/WIX/win7/ia64/Makefile @@ -12,26 +12,39 @@ L=..\..\WIX_tools\wix-2.0.5805.0-binaries WIX_UI="$(L)\wixui.wixlib" -loc "$(L)\WixUI_en-us.wxl" +DFX=DIFxApp.wixlib +# Since makebin.bat knows correct WDK version, it copies the DIFX APP files +# to the bin\ tree; eliminates Makefiles having to know about WDK versions. +DFXP=..\bin\Misc\ia64 + full: clean $(P).msi clean: - @del /q $(P).msi 2>nul @del /q $(S).wixobj 2>nul + @del /q $(P).msi 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul MySetup: $(P).msi license.rtf: ..\..\license.rtf @copy ..\..\license.rtf .\license.rtf +# .dlls need to be in the current folder +$(DFX) : $(DFXP)\$(DFX) $(DFXP)\DIFxApp.dll $(DFXP)\DIFxAppA.dll + @copy /B/Y $(DFXP)\DIFxApp.dll . + @copy /B/Y $(DFXP)\DIFxAppA.dll . + @copy /B/Y $(DFXP)\$(DFX) . + $(S).wixobj: $(S).wxs -$(P).msi: $(S).wixobj license.rtf +$(P).msi: $(S).wixobj license.rtf $(DFX) @echo -- @echo Building $(P).msi - $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(WIX_UI) + $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(DFX) $(WIX_UI) @del /q $(S).wixobj 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul ################### diff --git a/branches/winverbs/WinOF/WIX/win7/ia64/wof.wxs b/branches/winverbs/WinOF/WIX/win7/ia64/wof.wxs index 986308f9..df8faa4d 100644 --- a/branches/winverbs/WinOF/WIX/win7/ia64/wof.wxs +++ b/branches/winverbs/WinOF/WIX/win7/ia64/wof.wxs @@ -56,19 +56,16 @@ - - - - + + + + + - - - - --> - - @@ -116,7 +111,8 @@ Display="expand" ConfigurableDirectory="INSTALLDIR" AllowAdvertise='no' InstallDefault='local' Absent='allow'> - + + - + - - - + + Display="expand" Level="1" ConfigurableDirectory="INSTALLDIR" + AllowAdvertise='no' InstallDefault='local' Absent='allow'> IPOIB_EXISTS @@ -162,14 +156,14 @@ Display="expand" Level="2" ConfigurableDirectory="INSTALLDIR" AllowAdvertise="no" InstallDefault="local" Absent="allow"> - IPOIB_EXISTS + IPOIB_EXISTS --> - + + Display="expand" ConfigurableDirectory="INSTALLDIR" + AllowAdvertise="no" InstallDefault="local" Absent="allow"> diff --git a/branches/winverbs/WinOF/WIX/win7/x64/Makefile b/branches/winverbs/WinOF/WIX/win7/x64/Makefile index e0527ea9..acba40e1 100644 --- a/branches/winverbs/WinOF/WIX/win7/x64/Makefile +++ b/branches/winverbs/WinOF/WIX/win7/x64/Makefile @@ -12,26 +12,39 @@ L=..\..\WIX_tools\wix-2.0.5805.0-binaries WIX_UI="$(L)\wixui.wixlib" -loc "$(L)\WixUI_en-us.wxl" +DFX=DIFxApp.wixlib +# Since makebin.bat knows correct WDK version, it copies the DIFX APP files +# to the bin\ tree; eliminates Makefiles having to know about WDK versions. +DFXP=..\bin\Misc\amd64 + full: clean $(P).msi clean: - @del /q $(P).msi 2>nul - @del /q $(S).wixobj 2>nul - @del /q license.rtf 2>nul + @del /q $(S).wixobj 2>nul + @del /q $(P).msi 2>nul + @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul MySetup: $(P).msi license.rtf: ..\..\license.rtf @copy ..\..\license.rtf .\license.rtf +# .dlls need to be in the current folder +$(DFX) : $(DFXP)\$(DFX) $(DFXP)\DIFxApp.dll $(DFXP)\DIFxAppA.dll + @copy /B/Y $(DFXP)\DIFxApp.dll . + @copy /B/Y $(DFXP)\DIFxAppA.dll . + @copy /B/Y $(DFXP)\$(DFX) . + $(S).wixobj: $(S).wxs -$(P).msi: $(S).wixobj license.rtf +$(P).msi: $(S).wixobj license.rtf $(DFX) @echo -- @echo Building $(P).msi - $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(WIX_UI) + $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(DFX) $(WIX_UI) @del /q $(S).wixobj 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul ################### diff --git a/branches/winverbs/WinOF/WIX/win7/x64/wof.wxs b/branches/winverbs/WinOF/WIX/win7/x64/wof.wxs index 14b3909a..52eebf11 100644 --- a/branches/winverbs/WinOF/WIX/win7/x64/wof.wxs +++ b/branches/winverbs/WinOF/WIX/win7/x64/wof.wxs @@ -56,19 +56,16 @@ - - - - + + + + + - - - - - - + + + + + @@ -116,7 +121,8 @@ Display="expand" ConfigurableDirectory="INSTALLDIR" AllowAdvertise='no' InstallDefault='local' Absent='allow'> - + + - + - - + - + + Display="expand" Level="1" ConfigurableDirectory="INSTALLDIR" + AllowAdvertise='no' InstallDefault='local' Absent='allow'> IPOIB_EXISTS @@ -168,7 +173,7 @@ Description="Open Subnet Management started as a local Windows Service" Display="expand" ConfigurableDirectory="INSTALLDIR" AllowAdvertise="no" InstallDefault="local" Absent="allow"> - + + Display="expand" ConfigurableDirectory="INSTALLDIR" + AllowAdvertise="no" InstallDefault="local" Absent="allow"> diff --git a/branches/winverbs/WinOF/WIX/win7/x86/Makefile b/branches/winverbs/WinOF/WIX/win7/x86/Makefile index db2cf5c5..f53d575d 100644 --- a/branches/winverbs/WinOF/WIX/win7/x86/Makefile +++ b/branches/winverbs/WinOF/WIX/win7/x86/Makefile @@ -3,31 +3,48 @@ # S=WOF P=$(S)_win7_x86 + +#WIX 2.0 # L=..\..\WIX_tools\wix-2.0.5325.0-binaries L=..\..\WIX_tools\wix-2.0.5805.0-binaries +# WIX 3.0 L=..\WIX_tools\wix-3.0.2925.0-binaries + WIX_UI="$(L)\wixui.wixlib" -loc "$(L)\WixUI_en-us.wxl" +DFX=DIFxApp.wixlib +# Since makebin.bat knows correct WDK version, it copies the DIFX APP files +# to the bin\ tree; eliminates Makefiles having to know about WDK versions. +DFXP=..\bin\Misc\x86 + full: clean $(P).msi clean: @del /q $(S).wixobj 2>nul @del /q $(P).msi 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul MySetup: $(P).msi license.rtf: ..\..\license.rtf @copy ..\..\license.rtf .\license.rtf +# .dlls need to be in the current folder +$(DFX) : $(DFXP)\$(DFX) $(DFXP)\DIFxApp.dll $(DFXP)\DIFxAppA.dll + @copy /B/Y $(DFXP)\DIFxApp.dll . + @copy /B/Y $(DFXP)\DIFxAppA.dll . + @copy /B/Y $(DFXP)\$(DFX) . + $(S).wixobj: $(S).wxs -$(P).msi: $(S).wixobj license.rtf +$(P).msi: $(S).wixobj license.rtf $(DFX) @echo -- @echo Building $(P).msi - $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(WIX_UI) + $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(DFX) $(WIX_UI) @del /q $(S).wixobj 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul ################### diff --git a/branches/winverbs/WinOF/WIX/win7/x86/wof.wxs b/branches/winverbs/WinOF/WIX/win7/x86/wof.wxs index a4d1297f..23ac09b1 100644 --- a/branches/winverbs/WinOF/WIX/win7/x86/wof.wxs +++ b/branches/winverbs/WinOF/WIX/win7/x86/wof.wxs @@ -50,19 +50,16 @@ - - - - + + + + + - - - - - - @@ -110,7 +105,8 @@ Display="expand" ConfigurableDirectory="INSTALLDIR" AllowAdvertise='no' InstallDefault='local' Absent='allow'> - + + - + - - - + - + + Display="expand" ConfigurableDirectory="INSTALLDIR" + AllowAdvertise="no" InstallDefault="local" Absent="allow"> - + nul @del /q $(S).wixobj 2>nul + @del /q $(P).msi 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul MySetup: $(P).msi license.rtf: ..\..\license.rtf @copy ..\..\license.rtf .\license.rtf +# .dlls need to be in the current folder +$(DFX) : $(DFXP)\$(DFX) $(DFXP)\DIFxApp.dll $(DFXP)\DIFxAppA.dll + @copy /B/Y $(DFXP)\DIFxApp.dll . + @copy /B/Y $(DFXP)\DIFxAppA.dll . + @copy /B/Y $(DFXP)\$(DFX) . + $(S).wixobj: $(S).wxs -$(P).msi: $(S).wixobj license.rtf +$(P).msi: $(S).wixobj license.rtf $(DFX) @echo -- @echo Building $(P).msi - $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(WIX_UI) + $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(DFX) $(WIX_UI) @del /q $(S).wixobj 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul ################### diff --git a/branches/winverbs/WinOF/WIX/wlh/ia64/wof.wxs b/branches/winverbs/WinOF/WIX/wlh/ia64/wof.wxs index a18a044b..b90ae9f1 100644 --- a/branches/winverbs/WinOF/WIX/wlh/ia64/wof.wxs +++ b/branches/winverbs/WinOF/WIX/wlh/ia64/wof.wxs @@ -57,19 +57,16 @@ - - - - + + + + + - - - - --> - - @@ -117,7 +112,8 @@ Display="expand" ConfigurableDirectory="INSTALLDIR" AllowAdvertise='no' InstallDefault='local' Absent='allow'> - + + - + - - - + + Display="expand" Level="1" ConfigurableDirectory="INSTALLDIR" + AllowAdvertise='no' InstallDefault='local' Absent='allow'> IPOIB_EXISTS diff --git a/branches/winverbs/WinOF/WIX/wlh/x64/Makefile b/branches/winverbs/WinOF/WIX/wlh/x64/Makefile index 8a943d89..2d4d3082 100644 --- a/branches/winverbs/WinOF/WIX/wlh/x64/Makefile +++ b/branches/winverbs/WinOF/WIX/wlh/x64/Makefile @@ -12,26 +12,39 @@ L=..\..\WIX_tools\wix-2.0.5805.0-binaries WIX_UI="$(L)\wixui.wixlib" -loc "$(L)\WixUI_en-us.wxl" +DFX=DIFxApp.wixlib +# Since makebin.bat knows correct WDK version, it copies the DIFX APP files +# to the bin\ tree; eliminates Makefiles having to know about WDK versions. +DFXP=..\bin\Misc\amd64 + full: clean $(P).msi clean: - @del /q $(P).msi 2>nul - @del /q $(S).wixobj 2>nul - @del /q license.rtf 2>nul + @del /q $(S).wixobj 2>nul + @del /q $(P).msi 2>nul + @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul MySetup: $(P).msi license.rtf: ..\..\license.rtf @copy ..\..\license.rtf .\license.rtf +# .dlls need to be in the current folder +$(DFX) : $(DFXP)\$(DFX) $(DFXP)\DIFxApp.dll $(DFXP)\DIFxAppA.dll + @copy /B/Y $(DFXP)\DIFxApp.dll . + @copy /B/Y $(DFXP)\DIFxAppA.dll . + @copy /B/Y $(DFXP)\$(DFX) . + $(S).wixobj: $(S).wxs -$(P).msi: $(S).wixobj license.rtf +$(P).msi: $(S).wixobj license.rtf $(DFX) @echo -- @echo Building $(P).msi - $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(WIX_UI) + $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(DFX) $(WIX_UI) @del /q $(S).wixobj 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul ################### diff --git a/branches/winverbs/WinOF/WIX/wlh/x64/wof.wxs b/branches/winverbs/WinOF/WIX/wlh/x64/wof.wxs index 5af2ef09..e5e5cf47 100644 --- a/branches/winverbs/WinOF/WIX/wlh/x64/wof.wxs +++ b/branches/winverbs/WinOF/WIX/wlh/x64/wof.wxs @@ -57,19 +57,16 @@ - - - - + + + + + - - - - - - @@ -127,7 +122,8 @@ Display="expand" ConfigurableDirectory="INSTALLDIR" AllowAdvertise='no' InstallDefault='local' Absent='allow'> - + + - + - - @@ -156,7 +150,7 @@ Description="Internet Protocols over InfiniBand" Display="expand" ConfigurableDirectory="INSTALLDIR" AllowAdvertise="no" InstallDefault="local" Absent="allow"> - + nul @del /q $(P).msi 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul MySetup: $(P).msi license.rtf: ..\..\license.rtf @copy ..\..\license.rtf .\license.rtf +# .dlls need to be in the current folder +$(DFX) : $(DFXP)\$(DFX) $(DFXP)\DIFxApp.dll $(DFXP)\DIFxAppA.dll + @copy /B/Y $(DFXP)\DIFxApp.dll . + @copy /B/Y $(DFXP)\DIFxAppA.dll . + @copy /B/Y $(DFXP)\$(DFX) . + $(S).wixobj: $(S).wxs -$(P).msi: $(S).wixobj license.rtf +$(P).msi: $(S).wixobj license.rtf $(DFX) @echo -- @echo Building $(P).msi - $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(WIX_UI) + $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(DFX) $(WIX_UI) @del /q $(S).wixobj 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul ################### diff --git a/branches/winverbs/WinOF/WIX/wlh/x86/wof.wxs b/branches/winverbs/WinOF/WIX/wlh/x86/wof.wxs index 7dcceedb..2dcbfed9 100644 --- a/branches/winverbs/WinOF/WIX/wlh/x86/wof.wxs +++ b/branches/winverbs/WinOF/WIX/wlh/x86/wof.wxs @@ -53,19 +53,16 @@ - - - - + + + + + - - - - - - @@ -113,7 +108,8 @@ Display="expand" ConfigurableDirectory="INSTALLDIR" AllowAdvertise='no' InstallDefault='local' Absent='allow'> - + + - + - - - + nul @del /q $(S).wixobj 2>nul + @del /q $(P).msi 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul MySetup: $(P).msi license.rtf: ..\..\license.rtf @copy ..\..\license.rtf .\license.rtf +# .dlls need to be in the current folder +$(DFX) : $(DFXP)\$(DFX) $(DFXP)\DIFxApp.dll $(DFXP)\DIFxAppA.dll + @copy /B/Y $(DFXP)\DIFxApp.dll . + @copy /B/Y $(DFXP)\DIFxAppA.dll . + @copy /B/Y $(DFXP)\$(DFX) . + $(S).wixobj: $(S).wxs -$(P).msi: $(S).wixobj license.rtf +$(P).msi: $(S).wixobj license.rtf $(DFX) @echo -- @echo Building $(P).msi - $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(WIX_UI) + $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(DFX) $(WIX_UI) @del /q $(S).wixobj 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul ################### diff --git a/branches/winverbs/WinOF/WIX/wnet/ia64/wof.wxs b/branches/winverbs/WinOF/WIX/wnet/ia64/wof.wxs index 295a2361..291a6efa 100644 --- a/branches/winverbs/WinOF/WIX/wnet/ia64/wof.wxs +++ b/branches/winverbs/WinOF/WIX/wnet/ia64/wof.wxs @@ -1,6 +1,6 @@ @@ -56,19 +56,16 @@ - - - - + + + + + - - - - --> - - @@ -114,21 +109,10 @@ - - - - - - - - + AllowAdvertise='no' InstallDefault='local' Absent='allow'> + + + - + - - - + nul - @del /q $(S).wixobj 2>nul - @del /q license.rtf 2>nul + @del /q $(S).wixobj 2>nul + @del /q $(P).msi 2>nul + @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul MySetup: $(P).msi license.rtf: ..\..\license.rtf @copy ..\..\license.rtf .\license.rtf +# .dlls need to be in the current folder +$(DFX) : $(DFXP)\$(DFX) $(DFXP)\DIFxApp.dll $(DFXP)\DIFxAppA.dll + @copy /B/Y $(DFXP)\DIFxApp.dll . + @copy /B/Y $(DFXP)\DIFxAppA.dll . + @copy /B/Y $(DFXP)\$(DFX) . + $(S).wixobj: $(S).wxs -$(P).msi: $(S).wixobj license.rtf +$(P).msi: $(S).wixobj license.rtf $(DFX) @echo -- @echo Building $(P).msi - $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(WIX_UI) + $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(DFX) $(WIX_UI) @del /q $(S).wixobj 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul ################### diff --git a/branches/winverbs/WinOF/WIX/wnet/x64/wof.wxs b/branches/winverbs/WinOF/WIX/wnet/x64/wof.wxs index 2d39ee4b..ac674665 100644 --- a/branches/winverbs/WinOF/WIX/wnet/x64/wof.wxs +++ b/branches/winverbs/WinOF/WIX/wnet/x64/wof.wxs @@ -56,19 +56,16 @@ - - - - + + + + + - - - - - - @@ -114,22 +109,10 @@ - - - - - - - - - + AllowAdvertise='no' InstallDefault='local' Absent='allow'> + + + - + - - - + nul @del /q $(P).msi 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul MySetup: $(P).msi license.rtf: ..\..\license.rtf @copy ..\..\license.rtf .\license.rtf +# .dlls need to be in the current folder +$(DFX) : $(DFXP)\$(DFX) $(DFXP)\DIFxApp.dll $(DFXP)\DIFxAppA.dll + @copy /B/Y $(DFXP)\DIFxApp.dll . + @copy /B/Y $(DFXP)\DIFxAppA.dll . + @copy /B/Y $(DFXP)\$(DFX) . + $(S).wixobj: $(S).wxs -$(P).msi: $(S).wixobj license.rtf +$(P).msi: $(S).wixobj license.rtf $(DFX) @echo -- @echo Building $(P).msi - $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(WIX_UI) + $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(DFX) $(WIX_UI) @del /q $(S).wixobj 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul ################### diff --git a/branches/winverbs/WinOF/WIX/wnet/x86/wof.wxs b/branches/winverbs/WinOF/WIX/wnet/x86/wof.wxs index eb64183d..f05d9599 100644 --- a/branches/winverbs/WinOF/WIX/wnet/x86/wof.wxs +++ b/branches/winverbs/WinOF/WIX/wnet/x86/wof.wxs @@ -50,19 +50,16 @@ - - - - + + + + + - - - - - - @@ -108,22 +103,10 @@ - - - - - - - - - + AllowAdvertise='no' InstallDefault='local' Absent='allow'> + + + - + - - - + - + nul @del /q $(P).msi 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul MySetup: $(P).msi license.rtf: ..\..\license.rtf @copy ..\..\license.rtf .\license.rtf +# .dlls need to be in the current folder +$(DFX) : $(DFXP)\$(DFX) $(DFXP)\DIFxApp.dll $(DFXP)\DIFxAppA.dll + @copy /B/Y $(DFXP)\DIFxApp.dll . + @copy /B/Y $(DFXP)\DIFxAppA.dll . + @copy /B/Y $(DFXP)\$(DFX) . + $(S).wixobj: $(S).wxs -$(P).msi: $(S).wixobj license.rtf +$(P).msi: $(S).wixobj license.rtf $(DFX) @echo -- @echo Building $(P).msi - $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(WIX_UI) + $(L)\light.exe /nologo -out $(P).msi $(S).wixobj $(DFX) $(WIX_UI) @del /q $(S).wixobj 2>nul @del /q license.rtf 2>nul + @del /q/f DIFxA*.* 2>nul ################### diff --git a/branches/winverbs/WinOF/WIX/wxp/x86/wof.wxs b/branches/winverbs/WinOF/WIX/wxp/x86/wof.wxs index 2d6d2730..05b2ef3a 100644 --- a/branches/winverbs/WinOF/WIX/wxp/x86/wof.wxs +++ b/branches/winverbs/WinOF/WIX/wxp/x86/wof.wxs @@ -50,19 +50,18 @@ - - - - + + + + + - - - - @@ -111,21 +106,10 @@ + AllowAdvertise='no' InstallDefault='local' Absent='allow'> - - - - - - - + + - + - - + diff --git a/branches/winverbs/WinOF/WIX/x64/DPInst.exe b/branches/winverbs/WinOF/WIX/x64/DPInst.exe deleted file mode 100644 index 2491b563624c6a7e35625ef5368cf2885973f378..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 684600 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�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 diff --git a/branches/winverbs/WinOF/WIX/x86/DPInst.exe b/branches/winverbs/WinOF/WIX/x86/DPInst.exe deleted file mode 100644 index e1ba21c88e65cb8e57e2488e48142c5b45816946..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 549944 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�huuADH5XcV*v1%y$(|G-&_Dp;Yuya?(V*_7Wg&QY5#5w7x*$@>uM?h9fp5Hh z^y;U$6rKIL2uYrVLv9FjUr4{@_hCWuYHZ-v!G$~(TjuzhUlYIP7tuIB42~_cj7bT{ zUG0OCM;GAD0etcCc)f^%o|-~04Q%)sc$7q6FwIR)(2s~=S2y(mIbXv|lRj{Qo^~m`5`)5LOm2Ci4t3A)+S!!Sj>vNyDiFMd=c+^p6D1p2{zO|ue~x%ryrmv^9K#@93}!y_=`-Vcq8 zL9N?$Iq2Nf4qO9TmMOL2VF0!=fU`1y?M&ddxyW%y>N*Et@AWT!79CqW-RqApr&8!! zWqHfUD^uA{ZZ?D%U+!}roZ!iX z%mW8BW{Ti6No^YrfphiK(VySa5emhUxVBb(jD-E{cDE8WaHZ5IS>YSHqN<5 z`xb9%%85AdPDOHKg$ysJ*@siU%Ma=A@Z~NPwE?CNOS2QRn>ZHcqQ0 z@->KmZAALX_B+_1uVJdOGrGKam%=cp-%S-4Jp~)^XQrKbgMfxz2aNp~+$770(C32Y znOKcNII9DNbs>Vr>A=ld?lOAi@;|9!SyjI~(%aEh^DxFxA_})uKz2bHAmBr-S+WHY zy^sAS4p*xFc>ya*>Llwe9=CiPF=+VI73`6}0xdX2z;Eh6g`R*YOM{XD9LB~oG85ia zY=DyXD-q=MdJ4!?y%6>_JWp19Hi=@w0u;_$DNniUX=_x4q!Y_XV{CLp&$Dx_1&|zA zo-?uTu}9-nM{`Dg%H+k-vK)D>nY?dX0r-^3tNzVM^~;MeJ%gXLOlk9O9HGj)UEY5W zHRF5mHwWT3`|!RaY!X`W+9SQ>KUb>)Neq_`gkTmK`=diXMij)`ZhioQb__l~b2Uc| zk8rxA2!zzmYKION7oYIM5}%kb$xHBHO@(l$ohZD$f1suSxd9kWlTF`87#T z37#MJ$iE>Bn)idRNk*VDj`%gn)X5oqgt(v)7v}D7>{{oojIzhD7x> zRb_4PIjPCrESt0C#}Hq~RqBxo5Wyk8+D$MB^^+1gSSUm;=FN0W>;2Er9upZ3=CK>G zgutmZuHAt=SJqOgl#BX>a&XM>Z@cKt;zJS9SMW((zFpibe;!H6^4AZAg2WApZzI?# zDw?*_zogObbX42&n~6s)4;FuQi!KeWAwiQs5ME}lTSgIwt4QwA678~^zm9972Bq~h2@IsT z0o*Fv;Pwp}h(=le9XcJ~yoMhGfXT$g=_i=)G?nGEvX9SZWzkb0$9%{*F*^P9@-+sk zgitRlROAeXpVzRz{%oL6j6pd*avaVsz)t3aoNIlFlM0fFA;=g*g#U!37vP!b^!vvO z6dOOJ>S~bR40l#AFL2g&9&owf@0^f2qBtT6VHR9vkg>1#;n zkYVo-s6!X^s^R=~3lt7d2N?zT>8Xx_JGc{|(RTXG)cIQDK1t#q%>rxRdG6J1JRzXFG z3_%2eJz4|12@#`La!h2WwyP8TLk>5sJd!XGQk5egnh4*~&XqS2L7;~`e@Qk}Lge~! zFM@%QpE~}#M}JA0RSW!9gSy_mOM^Q(vE8mg_1@m7!97f!twHq;UadjCrYa!>vC?iq z+aMGcKla83Rl}ar$Dcy-FPx*OZ{*WSf~!}bK-Ak%CMnGb@$k%`<`Fp=^?vs!V?O+BR`=1hpWlm4JVZPI-j)Fv&`pf;(R!A)($K@=sE3->CSTu0}FzivM`X+4AqmxJO%EXT}q z_N?eA+C;)T%x!T__N}Q z>Q<)odQcJfp@qO#h4Qa~cB*ai70u|xi#y;&829dQ32AFcF8?Rhv%aj`TMpbo)~9PL zL2Y>WfgAp}K~#6(+$1F73K&127v%Zmb?%F5SCeKT&3$BbE!*#4@yUPYQX=q<)DG-7 zZRJMStxzOb-?qeNZ8k?M`pR`kQjD)W(2aSops?C6AcAL1z49oek(SyvOeI|_xXJqQ zN#{7)-8ZlhWH12z91k8sf8wFe|0I)JoK5Ue4R>%5_r}V|eox(31^eN%!n+tdWhP7v zggdBYuP|XVlqZ--3&_lp^G!tGx|cIeg!?`o8EPUN*Lc((5!Gf!HZc2RTbB2?{sTGE zvtC?j@2HS`*v^=R;fCDgulqnVx$D&-*VcgLPvOI}jGPaZTvVuj;Xc;0D!3hTG7_y7 zPGle=cD9cH1=lGFS#Enju_|45NzwAD)_c{a$a*xn3kWj zpLEE+qd_zNHK?O^y#{r7uhO6@x~Da0#y^9>opL5tucmLUMh~9CTyZtJ_m3B2ta3x8 zV0TqKemgkNjmQO1ScqPMDB944Ph{yu*{~5K$C&A`zcmt#=KFsyqt$IWfUtD> z(r%ZRA@I%&HVlVGbOzHLOk#tfq%xPUe6T#XbgPs zBin1Bs1y*5(4OdrhBy#19Z%-!$+guiVR=lKOpK$+IEp(9)y3<{9H7)Drpny{tWFMO zz)Vv-8J%2BFsorIy4EcogU12vCKK`(uupv7P#z-vthuJZUM%_AIys-$?}XmOr98!# zRO*uX6V-V2*S&!}2O=IPdGh6LDyL-(mqHh($z=+N*EXQ_edQ4L>g_m_!H+gf0oVo5 zsPsiSgvmib`sMpykk242&D@kU2c!@SY&_0PG=S|VvtTmWapoH?2jrPBQXVBhyuJ>9 z(U$xWr#Bp9uP30t?%!b8e$5O-uZ`EQBvd>;9zSv{Y~plhG9{x=`^&f51}3L!8;9B) z_!OHhtY!+!lebI;n-_bW z5@#~z>@FPR6iC_U=w3EkYcTmfUktMu>*Yh(iNgXF=WKML zOQ*&zoLs}vM4a~3y%o3atrQi~CEB(7T}XGQ?tXiV3h5=)FpmttEr#oKn_}hoN)9%O z@zny@6FYT=c;Fak>HF*^5Qm+|33+BA9Sa8VCc}(@vFzA{LGhct$5N#d>qkVr>X9226rG8-|zI-uSFnN)ML)YqogVBGQ+qC3@nVgunhAWYx~{ zx#Bm%$A^W%WW7=Z!Pp4F(4K`v`Rf;>H3eIW)Ccdc$JFgA>O=RwzYbG3!t~Jl>t-XI z+9TgAE@)${FLr>8Z28902p3Zx`Im`oL?lPvLxcyvt!>8Nohutoq%2mMqs*<9aa>vd z2y=x@Kwsi{lxZ7zrh9A1+D>=)0{}(`&;9?1d;j<-t1Iz;l9_}FOqf9fL=74xwrJF% z5haenKuCy6Ffo!4A%HEW(`Z|Sc>pVc&?jRV9!Kp~+uE;oWzk)`wOiX2p;j~@Gz0zw zbQP6qY^l9*QjImjASv^GpZm-t6WZPVe7^sD^Lk~T=ea-6J@?*o&pr2?bJ+pnJ$HYt zdh@Sf8#&n0BvrrFQ>*T#2h?r97u*~Pq3s|W7>4W>G+v#v8;$|tvnVL)gGkahNP54W zKAAf9rj#DoFLjj*gMZ7M@*z9McC@Yh!4}_eaM_&Czn5 zhnPS!^3Ln+9N{r`>itUyyHY0>*prE4*k1IP?3*3-hN}xqzsne(q#$KSATyO*j1$;Q zMi__CclB6GVKek=J$Gft>rj7wL1wj_C>*Bks}S-FsfXnCQj?|$MULQ2X}Hhm0{2yn zPKIl7V4AKWc0pQ#-cAzy?j5L@JwUD3`j*0{2A*0BFqvu+JKjE17ygE=(Q!Dq7DK<} zl3<>$X+WoXh+-_G(-Ng97od)j!_Yrw?I3{%r0_~9Koe#+R zm`O0`2#12Sg@dc)QJcPwAskvypn4PXZc7#QJq4MIXaR+}&=a&H1$oEWh3kRoa+(y2bY zLs(7;PR+S#%P4-kqz6%#f>){G9BK%xlhrC&hWZA4KYX{`qd-9}^^k@wy`cA(QiQ(g zA!6SV!Qda}gf#C5d4UBUo6OZ^MsBhAQG2leh%;svS# z4V9FT!p>M75+tPWZ9qxE6?#jp{TJ`M|{x0vmzaZsKNmC2h z|1gy`rJ8bzyCNNx!>u#!49q>Hbzhg(bx?Y(IxO3Y;nuB_Fj_Z8?IC=mb#66yn_yYu zrFFBKiHY|1M8=Gi`{9!GzC5JA zWSmIci+m~LGA)C)4`G(?%xPxIEYU|>PpYRO`EnB_M|MN%-}n%Db5f#zP7Bb~JfQ9; zST6q1BRJ~~NrMFPNiMP*P-`GcrOi)!I|^j%XpLK~?fs-$|3^WFF3P1ol8t8SnlYy; z0W5@|N7Oa93=8hF)Mb2#bS)*2rM_(S={K1%l@Dl3U~h-)IkXTCus$EJsgYZ)y;VRG zk~v0%n%l3iPm+UWfeslP1pP}g$e0!5a7ToJd(`1S48!ME*J=1fIHo)D2aA)9+@l-$ zec1NW=9i5n|F3=lN{e0>> z^Pa;IPwGf1Q_Z5l(l+f%1NYYp-Azr1?Iio|<tW6$6L zjs=;DT*`PC6mXg8T}76LL2lxE+1v3mlAGObZZcpP;ZbP6&D3S7RixKKd>i$aFxqljCrwU>{Y$2aF($jX)*eCwd!{GKbVCwt?cm<2ftduA4abI zu$2B3BTg(7cB-?Pz{yjq`7ve(`sG#}b33MS!-LXpTyw_Zgl`o>(R znn6-ar;~^aVY3 zrakIBo$0WB9yV4rkSS#dtUhn1=4RCBInbzZ4|nZ2wfZ(WAwhus)-qcJCMXsG_;25q z#*>_e``D1mA+j;x4T)d!6i)wqC#4#P1+A3>8kdE7>#AfHv3U${C{%vkMUVr#tZ3zv ztX>YAwA+Jy?jlMNT;`~}9WdDMOPhmZIbqD=Gu*bPv~90tyx|RvrBh2c6dIn=GQqLd zfsi{iE!4Ry3QIQ>8M$^Z4&M+It|&^;gZ~s9iiOA+^&ih9D|D)Dykh;wU6J0<>XXBx z+#8z2kXO5w*!T3YU2!J2D~ABSLr)P_WuD|XIpAQhHK@OEc$1nh zZvgHfqFdlZ4dW{Sfvz%WH-5BLl(5*ocsqWmhx7wUwX7`27Chk$i4J}{P^;mL zeKSdWbn)tYh5)4jU6=wicVmVqJ+VdrXbQ+2pjY4fFRt6oN{34>RTLn8I|AI4zJFs7 z1RPSGQ@N3%>9PF)r+>X9E|cuo(rjkofTUytfTi`plEm`|jAWTFozOwS-wY~FlI06| zMPuue3r8q8y5m|&xAmq-)kTf9VU%oVsq*t7b$dMF4Fyb|@P?vg(JB$|jZ(f8ji8wr z&sk-1H)YVt?G0AdFJhb+oEHpDM!(=7twkhZn4fRxK9@!VA`;>QA9KqY#%{%ghbyGmN8F z)z$2=_te#fu@ZiRXby?2nmt@3$Gfwuby4DqFj}sahr(4{z;;`w^C9yOPUr5{v570X z;+b4rn0Tw?;ITCxEGcA@wairLT;?;YanLiYYbkJNn=R9{Fb`gPKV^rjrdd_f(sh!x z6@c$qM#aYB{r&@OV|I!UsaTG)i$;etM%}cCDykkQVy{xGc9Dl>W!I|T)Y4Ctp3zr} znf-Yh0?doL!V9Jazho_RTc?F<2E#S+aLo{^1$B7R>(3J`EgVQpe0c=CziWi@nd>|m z8*&pXUFyJ1RF-WjU!vM$xXfw~U+E3vsMYPLr|L$INZa(CpxizgdKWK8?*g~>qgAiI z0`UImhAiv-V)3!HVWw3xWF0Fu+{K?&sTUSfMUC5eG?I5lLnN>E#)zxBp;j&WwFD0# zkS;#asQysTmoY?n<%8N~Z_wa+RVvpwb0G?{ zKGE8F3()Wv0wGZ+7BkhGhhn#giNqeTf@Vt9v<=IV5Vq~dZsq4c*D{UTX|kLhiB|3x z6k3Z1YwN`^%0A{II=gtM#YLkGqiKi>hYP_kMlY#a9cn-6a4%-wh0@&*V@_TzF7igK z@lhQSJ?cAvKRlKI;U75c5=4V%W30G^x7~Ho3bsg0FksF$KCGR1;lag{i0p_)QIP;QI=DU zls~%G;pi6eT6PITDm}h6f9GFiAk2k2S#N9QEG=&q2~1+TIPsTRXN|KaM2!9hv)^Y8 zSp$zh{&*d0MaJWae^S5_>J+ZP`*7tr0zr(Rsl9ESBREmime7_P=@_WXRV{MUMevB_ zwA@-ZIq`jw1!D+zKzNPoUA4+BWBBj0ma8p5Io#g~X&ETp#vtLCP$-vA<_cXjVWIw|B;r}o^~AltBvL|p-eMHcpw zzE`K8#y!*-#!XCBBBWq-V_-R>N|I+LCfH08c5(gc*G~bjXms>ToKqD8zpXo!sWN^g z?N?e8Gb1&NDA3=H;$!N&%8%LaJ@A34&f#>F(JHPO)d0;*ZD1HvtEA}z=h5^}#e8X? zbQW6{O<=lcS8{B*)y?ouGR9>b&~?&6M!v}Nxuw}C&=d70MpJSyz<}(s>x*rdA(xmq z*N&7D4&P`u`TN4OF~$zNgX3G>DCitU0x)sDp1RmO_p8OSJFoHBNch20`Ov$#AJ%{m zU21Zkvk4znce1Ub@STy~T-VN@NOLo2ZplHG3FPRz4KJwHmy)U&d(2poguBBFhwLF= z7l7p^g?g1pUQ<iYi>G>UhL0p+K0VovLk!KnTy6tPB)R$j|cXV2HlYuNcQHrcQIfI*=z!+Fe> zBH(IKiUgw8CuKiZu1r8q4K}OGbSFf0IYq4@39P*F1Wc3|`vtS*3as(ik+>j3W8?z| zhbi`{H9rvV1PS-C(H297Lo!->^;Pyq%vlLdRJgPr_isnEw_Mb*8Fa`48H%TuwS%jm zNADGkOUV~QH|I!(ATX2r$XVV7S=00gWGXp3MI}x`Y&Vb1hGeiu-`eF-^_v&+`k6G0Ke&4Dx!K zC^4%W&elpE(znkL6ljMy(4{~#(ItS)b483!S^d?6pJX~>ht$11>Yr~XZ9}UIPj>;{ zI`vc(bI|Y@aBJiswERBM$9@J8qvHC++7x2CgL!Bv`E0DrOd=%q71Yh}fLNcn(sqtV zg=YZ8VQjwxdt?zL+{Vk17`Gw8F{C5sh$^9}ObcYvZHKU@1vygT2y!%aHv6YHN(T-? zairJyKdBBb&|5ukM;$#=A2_5}-08H$I^=)f+i?}4fxUtgT;UKZXOrg;#?)b$dpl0& zy`lAUu7xI&rf-|hPQg}>2wcP~Xe=T|l70Z9wn~_}7k+()B*auT8M%i@nMkSd2a@43 zU0Y-m{6delx9fvU|K67HjA>+TiDq5M14lRC-AtM~_3xYy`J+wSE+;g2g;n!rOStBv z+5CKrG@*{R#`*suVaP)z>@qOE8?G^*8adq{TjfcaXx zu%T3tkDzGfAc%0p*a~>~Tne__JQrJ)gUgZ{fypi?(ymGcNN>lzy3r8oBr@zq2LpJ9 z{X>g2QQEgvx7B%ob{vh=4AKsRelu{^ClAE$Go&bBj@yN_gcp91n0UDjbZ9 ztXoDJt_FwHAFr1^#`?)PDn(N=E*=zdLOgM%hEZo^Li*d{2%y9{X=?A2X3C$UUBb~M z%{z@rfk7FnNHPD5ttB1b6wVH2%(Gn{r^*l>$n5vXPQqs5!_Dz<^Pr~W!fZ5J8(g)v zchM^%mD-1Tty%@4VSm7htvB=|61BG_E(!+u9UJ6MsyM=^(#rUnt2J+9oF3qG4YPK) zi--CWn2epCCGYUK;#;v!T~Ppb#kBA$i%nCNE3)35*ixC;Jcberv7B$?+L8ASC(|-< z#XNQ9MOsSh?O+v7n6n(l`5^Wpn>v49tKRbh8w9#3rm*Sz5dI#nwJG*4FBA* zfOan)?j_xB=sNbS&nDB_MU7(}Nfl+bxKkde%~D4&t8Wig+bG{UlkO#dE`~@mp%drJ zH;=33(PExoZhuyz06DzCfO0EW(qUP%j}fw-JxtK!-GNgM%urt)ZKVQ}_oL&@x~W?;-X!U}@+X1%7=1DN>5{U;c? z!D-rcJ(DMxXU%w$L~@ZTcc;o6dNVdwt_38J0hPQnTtx9mHpkNUrOk0~hlqMhn}_Th zY4wF{f6~((8Ydg9+i8|i#r-;PXKkx&>k<>6rc4R`4Z#WHur))Z8HqO~jvtJOdK1QT ztYcc`dM9ymtt&9uW+TrfCO#+WVV=r5^-COIzOpeL~L(wB@LyJhawnkxT!%iMhG_RdSYUb+>NpY2F5}8!HufE{v1q*X8|e@ zWPuJwrvy;41i-kCOn8~B$pJ^VOoKUrN4ZFNl(WR)8yu+r<)b_ttZ@7HOiIyZFl`nu8#ZFM*D+lqSkU+IZlcX1NN5bMvAxlM+iLb5 z>xNNH7%Rff*fn}Gf>}qqGmQpD-17pYR+Vi~^DDLd$<=yStSqci4655a;VFVZyIIC* zjjcGA{&(mP*ZAl*$ztPG%T6Q`JLM{sn6tHUk@lXR?s%OJlDh(~k_~SKVrj_<67b>A z9=KLbg{|f@iw=$Drv?#st?S5aB4xCs-vH3ins0rIfI zH^H2Fkuhj4FK|SvUFsgtnr4ojM&mnk?Darah%~CMdrTL&e!PFSt1ZXMiMVD<5&xqv z9qoN*B*XvgddzRHI_L0rieko@|T#hGO33>&uU2W z)%8h8riZI84_D1hgG3Tkj>U{95Yi%fF$ z;IyM^qdJA;U^5`t+QorKBGVXW>hs8~Ra0YA?63?Y-wt!F9cSd4ViS{atZ`8$D9Vpk z`9v8HQMCuB_G&C!T^cYZ*#Hr{_LRJ`R2?PF2=9@YQOhJoGN?|Ho~~Qkw}2Oo==M9^ zj%Y>!g$2js+6i7@4AZ6`5Yy%WlM$V`JJ`rLAh2meUbs5QO>q23ia4vu=oI3+*p2@d zOyn~XzSy~?l^$p>^E-okG9VzD6e9!I6)0*>8$(nuQuGlWm(Eg3hg=1MQtl55p`?HB z{=m}G_i9(vF`agn?2DG?WlG;^zf>*Z>U{ps440qkbLv``mC>A7?$UTFIT)FpH)p`x z<%`UACsEUth9ZFgXrkFt1*ad{Kixk7Mcf4W%oh`KJh1wIIUW!hf2F4(;>nEMk?{X@ zX>MpMZE5GGkFdkZ?_v z7QklUT)1buFSu7Dc=`<@p$JcpmV4ly9N~5D)KWb>tA(oss8N+6FQGD@u8fs1VpqoH zvK)hXq%wzH8Jv}8xV~EGYB2kTv^vrk4bv zqX7od^(K1L2Z@cm?^@0~?(c)yoAx={33!mhmxQ&utJiI9=iDyg>YA;l+Nez|Co`nFP5=zsjhqTGpfvqiwToW*g;QjgHN>d?~qZdNPq)J*X4Szc?9?qw&#HuXeF*p*AE_YL!m% z9{tks$VEUC%<=AsOy`C5e{547p5X)1B`Szsy_RcP%uYQ>vJk=BeX9&hD>bJ%&)EfW^Zs@wDK@5M@n|MTYX5an!&IIL}yamZeP;%h9~f-RMB;oRB?oh zIACJDzpV6Sphumoa@++_NV~p)q;M)FYjRVCdXc=8C)Uiygi&`Li7+#bGO^g_*^9jp z+gpXtJxhJYRB4^GG(5dg{#1ldb59=2Xk}@eFE|N({Z$iS5h2ZPbq(?y*sNF=vsXk0 z>TFm&0wm=#q+C5-JtoDS*A&KOUhEgGli&f9vok4cDb)0-)!p>`!;r^FTkmI&BPfB6 z;01?ub!pIZ)ZrF;fI&z3Lmb;9PC?czyBzj3;#zRu2wMRG8YZ@Kvx^YhX)@a3EJiOp zRBCqRn4LLWd{<2{@;eR&eOqcXuByv0JmuxHXLlSlJ_I9KXLXu~5+(bXv%(Y(7Xdff zDVx!*$l+wOQa+nfoi&cm(w%fA zsEdsy=O&QV)Xfz>i5ZVAXJ<4~mQ1GYB-JcKW`KJrV0SoO;wzlpq%3Ve;5m$ZRmTBOA8bSOI$^V_buhRRM8} zF^OBeGOElTuleD?NUPtn4k5|Eq%_O@RqpTfEDcVSq4nZCp*u}MFBtE;ivrZOSAm7; zYL0#^v$_|@@*31Ok~OJI2#Y#AfGisXyE*<`;ns}I&$|15PNI<+uGwxbEMpdn-@)|p zX-A!AD)*+DzyCc82Q*GNrPLBL!nU)*3v*`}YAaM!j*{KM#a8w0;qg{=0Br_qyg&D; z>)GrfT6y_8wmH=+MP6Me9f`chpSAy5A}1la8i{wSJRCE?MG|_nd}XF_o>{#zQ}{i$ zLsD2cd#hxNjp1BoyjAX!q^Az`P5pT+HzS-X2Z-3%ANQZM-U&Z4$m|GjAK=$0P{(J= z%wN_Zf8m+x2YfQ~`!B``YqQO+-j=Ytyu77!x^V>2{TwN0rR%p2$ZJDGYof8fF0owzP^;=0gL`p> z<$pCC=@(!otJ|qP>OgGhP`B`EIr>DOskFwu+DqlCMqoX!?*_&}#Fc{;j>EBfd?X|U zH5`ib8(Zgpb;JDD-jaihj(y7hd{ud5ZjQUm0UNmXbT#Q5irj;czaYv$V8xk$w_N(w z--COxs6)D1oK1Pm5$X_S>oTWO%=k+vpQ z=G~~mHwsD%p$3!e7>(B`WJETr@t8*$l-hcskjvwxx?h4GJEgi^hMg6SJl;!*?4;r7 zOI}f5CUlsc7oNKx!t9fSt6PW>rivq}g#+qSzS`S{98I`ks)?Joto&PgF71p-CfQE% z08084F#8Kd9Bz1wDe5hhQT6bMIwncatmK;^V(#S(kA((ZL@H-VqLhu)joQf^lGSqS z2L~r;aw4)_nqDtyq-uAd?~cq2715RZ(ko}5*T!8&{*%EhAXs~jT1bs{qUi`!c)OyC|ur7QL54W>!u=)HNVIeIvD&$ zN#~ZiMQ#rMCvEydM)0cfX4w&Fwg`By`?iR_kDFh(R$Y4RZSwRDg0>R^3cVNC%O4$HY> zn|@;C)9kUnIb?lpDA7A|W7eXk@41Q+y(9RltAmcLuxLL7${31P#$P=rQ(qy>{*BW$ zq^CuwgisG;$?W}`5ZBqR6)Ye8Ssb|eSA=!VA_`Bf;ClSJDHvyMI-ZZgBk1=0jjqaV_-=#}PEec64WM=)Q zHuqGzz&LH^ndfF^2)GEx%ln=JT=UjLu$70eCtQZxmVK)Og}1LX745!n)bmqTkY*SC5DXR zG#Uk##KJe4;3oFMDhxS-lZQJ@RFR!YB#PBtvS2K(M$Q&MXk9(msClevcM10)9gjnp z=wj)TmY{g1jg}V)93o-6J=V&No#Hz!U;kp%$GeR@{i^Zz$aggU2r$CxZJl|dD(5S$cC}^stKA#2Bt=dFr)JG~8h4B1kG2=_rB@Y3A0+*lLCQH4-7RD~1Qe@ld1c+vc#EXj}Z)b;Lwf|UynHKYK(lN*|=Fsrd)ND1KZU*N zGS|5r#-~f0`|<>El2^N82S=u!*`Ke6q1+q6v1y4GVW4A!?hiu$%Di&O?v;g^8KVEk z1Wyh9E40s zct=PzDiVWsRvSP6OFpi$`v07jQ;YwvwTe%Z|2{VtBd!P|j)P}hCKTmQ&2`Dnt{Gr; z-S;_4%!g>Ijs2Q_!nRA;o>W+?FW}e>-#uYG$P^7n1A!ObI0jj)`?O86NVvJ$P(?qJ zpdCz#azz5J+&a99t%NP4cSgcA{Xw-U7t#(2)E+0C=!$uy+~3%Nx`;Wak4y&!IQgywaeW zx1JF|{z?uAR6v8*PrQ2tlfOiS{k{z~8K z0u?50lADs9iaL*Pco{i3@&2;L$0X$fRp@F{(8$&jx9XP z>~S@yy9nDRfCxVH!QKe*MKPzfPO}@3eD?8ASyP*ZNqx`wirNl^Pv?*q@J*iz>ka2!88?@&{F^Y@ z8~ZxxEV&off0Aq)PRS+<$3M=tnQR=%mafb*uJZ11wv^3wtjcLAyV7CdO~=nT%}Ffq6{b}2c5y{=XrWeC`r4ck}R)zckJb$ zw74tT5PJ)8%3`XuW~y)Hbkq1YgMfyeqaJ60OUK77zG;|7L2Qw=(BpqI_|D&htgFjB zr5*F;&6(5Ue^uH6PaD|io07v2brF7d_95JV6k^c0cXYsi{CNavuC%I7PyT+wTQ~aY4hfJcT8qC%l+gp z!P_rx?q*QON0#M=n4C!UI?M16ELx)+gTmT?GtMIp zID7mj*SP(0?{^<#Mc8y)?+ngK?F=S0Hkf}#UgGtiT)NC~FJ0E$w~8J+7IiW(jEQUl z*b11(-K#GA?D0g=gDWn|%(&l*J}a?ipKF!J>~%W#CW=DqiaF~piUcK|XPnRbH}mr} z|A+XWdSFZT<}q8UGB(f4*iw~Axtr%@nmtZ4s!M-)xb(=ij^VUARfgGzhJPJS zb-$g)?8y)a*~AdLc{r9b76V8%|Ke5m$t!JDdZ~coH2}LK?lE|IPeAEM-@M$9>)RON-Or?batl zfN>N?Ku0QVBzWmiWPRySvJ;9%IwALOpFKWAy^c$$C2{O+F#XiUnHiLo>XYMkpA<`< zxQHT!B)&h7_nG`J=YLC;bLxT3^PJ|#8hf8;?EN~eXn`%6W~8q^&NM&Ky|6hg<^5^t zXaOxu_SwfyuGghkyx(nrJf-EJ?5Ba@IsW(X|1$q?@PC;9Bm5uZA6yrJ>jIwnJo9m3>>3w74$VcPz`JcxB)%>S$4BTKKtuOR9txf9(JB=MS zs_#Z-DVk;@qA*#M-)l6^RtdzHX_Bvp_=HGV(Y|tKAQQP>O`4r1`zndO0_CzYhs9qd zR(DGLHCv0nZWi%Z;VALf=Zr!`LNCa47V%eoW8IBvT{+Y|Dfy}yMC@fybyDor$}3{8 zUF1uOy-HLAVTiqMq>B)5Ri%lyu1XhgT?s#Ns(9;?kN!@)g^f^UTy$0ArAFMyRdG0l zB+Il(H&BbPjJN=+U4XTU$4f(G_Z180|>8nlHV3yaX)=rJxh&<-Xpj6 zFI#F$tL`KuefuTrXmT0-HBl_1qSYx3^bHVlEw5@?{69;qo(2`vpT3A|y&r@*=1Zc- zP@v8QEPa@flv+KjnT@=0-LW}Gt2}1#$_7;uq*JUz>hsk+)lPnE)z($gGR{8RvM4Dw z!Kx~Nw?<9xZ+{kULyX`&RyS*4vTnGOa^X(0k#CwcN6@ig>nExdxUS}&$|>q0>a{_z zk5(?T;SlMW2SCb$Sk_@JaYW@%LU)5Y8@et#fd+LxjZEinvKSMw|DyN?wOkU}+zkLM z7XU(xFe zjayn`6sj|Zi)q!`!vQ%DP+l$2@WeV8cRt!xrQ3x-=I@S37!P*S`WY?hXC$rVN%rIP zQG@a2!1R*LjO1<_5u7i4dyy}~e{nCoyCy@IBIo*DEthOCUyzX`k;$xN9whhH7~}rq zEvn5zBn4Rc!r?SntKB6tq*aY#KX9_AGJ)VqYduHq@&$8|B{*Lz@yt}8e#m57;Ne)o z{{BF|D>-9uwP0OL55kRLh7(41wAf>wIEjFZOA&(0SPLSq#n$}0^FsR~bMFR819Hw8 zNB#0GwG;e{RYf9Wefh?D!t2IQT8E5R(V=w&i!?xKAbcwcLW2h3Dz#LDAQpZi)05*q z>AZc2uQ!#-2`V>NmHEC2@eCFQ>h54p$-!gBpw(Tx+YfNx(>qo&H>kPaVouen zUvCubAlLAA?4We46}%n)%u7aPy!g-sl|!*<4Qe}GK}3_cBP>Z|YqgLbMelnvs$luo zCu)!%4~E{wn;4yfL@ah`$x-BkiJ+Hx3LSH+HA=4$(OVmx z$44G}BRD&m=>UoB3|dBATN{g-3p|BZw76SZ=Zo#;(--?h4+n_yqeA-zPN171v=6mQ z|9in=t1XXHjUpkvZkx^(s&%^HNdk%dsR=1))G=~%x{;4A`rZVUeCCg@`O4@fZDUme zb5dZwO%i{ZN*g_^vpl#Jxu1H8Qw1UHymKUF@0oV7m#58X1W%S1)}h#D$&!#PqqfgU zd3-C!OKv6Op4{o{FLRc0$jbK`OLw!!|Nh#u)FTDLVGU~)T*)W7I8M2etB6>oIZ-T) zy=@)v?#ONlXU>^ZRmI)TLFe_x$Nu+?2}G*JQbO{qtuE z;TW^bpQTUMuS9V5pLCI|(SnK#MfoKSjmrd$%}4z22hqZCv$Qy%adZ(ax}^bwE@K%r-*;fpJL!ZS+AUjs<0xoNM<++Znny zT3w6O!|SQ_*8bK4aUr5L=$}h!O$qnFU6`yza=B|_Vy6fZp(V{vED)XSO&>a;PgT$v z-1^0hs{)760D;0`+f70cGcC|b2pMe~sbIcHJS4$>QB)WUrT0hW);`wWcx~ja1PU#2 z<<`R+dbED!vA52}MypefvANM*IC$`tk#+DN*sG@YFmGCTO3d_3}@>E z4yu&%kpIN0i(6d*p|Ipe3w5t<7hvGT9ik*bFWW&{;Td^ZNsh2(*8AEV+EuMdlZ|i}#uzc%5Bm?HkwC)HjwBv*FM&sn9`|-%;zG z<*-$qj0??mJ|`Z#%Ja$!vwmriay%mvQBGKV)_BA=nTZ1k7yYOq^^aNr)$d$-uck^ETZtKu3U9aCn^a4o zk4Ntj0`ux4--uiGw~_1+3Z)VTRp|#O)Z^$HSQ?cBwqW_1+!aFkM zMi-D`Hm0a6PAT9uken>ww9*+;LhxNHSE~<#)lsFGW&1Y@dv!)Yp6dOT_6)^0D-D*Q zCN&*KN1t>AdO6z-?Bz#`YPHZ-)i58lr1*-!9%6RR=SPY9gIRXD$fuivX9eU%btH>s zS#9M`HB8|M9X>i^!R!@s{sKoRVE0DBr^tCB%cV0`NDFQY&sffLOQr8$mhhC5KVu%& zf>QRTT!;jAb|Q6FK|bUTNKO@klgw`(adF_W3Nndu9{urgVt?$Tz<_{yL_ihzn*wq9 z9+dAg`A%(b;Jx59d=6t;SOWoMZ-84F4(eSNP@vP-bGwQ7tweOI3L22MZp!JXq_!8# zQ5#mGusd<944c@b_LCf^<3K|6VHmv0ofHN@gdIu`!&Qq~FB^X3=--EpX;6xOYEZB0 z-+x&v@1J$cuLD7N;FDRXT6{20#znz4nI-A=R&uP*#Yjg*?E*HzfA!~2kRq6a2cgM) zulks$*Az2UT+mEuqmXz8e3%$i5#S+!5rA%bnP|phJ^3kU+msmM*V}Oxi2_gG=E!&< zj~Uwde1N`dP`5$vWeLK7tzQ1r9a^U_Z}XJUlB5aBZ#R)rPLC!&si&*)rNF}!6F8A6 zr&<>d7vN2@W9kyShV~NXu_ayBg z(fm^ry<{f}_zE*{fk86D`=OR^I1QlJ#Qbj*KlCvr+jt zj_Gi`w)oY$8=?DRHmo^Ln`LyW-BKT@4Ne5D+xZZ*A_LZF#ja8?s;*-c*h+WhGE~#4 z4tAgF3_T{0`*&1JcC506O|TUaSSnIdc}(i3Q6! z%obr{Xs3kd9-?Ba?6CYEa31%+)mC)dK999wiMgWGExuYLVv0o1alQ{g{@2(40UZ&` zQFbo|Io5_1MozS9g-KDdrDgXMq`x=G_kyzTr@lMndsf*GQr{u@zN8Ehqn-Y{@_lyM zeW~xe<$HYDKgl0A6)owK zgDgr}JLlej%D7g2D_ol+2Mb^39N{c<;LbC0&>cM}x}yim%VX~nRJe9*dAWW5U<{-Y zhq^j^R58l&QI$nS`?`gJ=7O?Vk<}GhnJFR+Yq}gh95s#>R}LM(WZ3+oEazV>u@?NwQ9 z_169$u$p)d!QGKH-Ymq0Mg1IE6=W}rBP15s@TB1Q(uITTa}aBql^$34suH@R^gP2y zC%)@_V0Y_zvu9iH+#EYE!T#6s1%3X7LmS7nSZqBe&zl!+^MPZ|gXmd5XxRhy3xnDG z<`_F!T(Vlsub<>Jl1cGyOX(Tj4iWN7uZ$$)G%5C%Yp;#HqvLyJtYr&~7LfTD4sINQ z3|%UXl6Pay*|RHva!K%*hV|N`-5JLD+NvfuQkU6cUUeQb^+hhcL)nO?mK-cy=%y{< zh3*5f*jd*nn{paWA$H@~mgtxz4&4}<@Imsx>k7Pt9-~|v z89v{>ll-&lPu z1E#&dw$+8W`Pc_H)~cO)Pn!>K9uSE#*099sn4jtxVdxh~7OhO)ug*m|Ll5coGud;f z%-3aS%JH;Yzg`c_HK8t$Pr_LA!F zMtSudvm}M|ckCQnyW5PfGsfcAd7DJUvUCZuL0Rno%<$UTG%Xfs zOiSt6R!?xe=-B2P$L+^oi+jq;Yr&Md`e3pZjPeB5{0~z(XBtAPX;4F>YX75db?lE~ zzv{xD#S@8%k4hpy<4_>@(9E-gmvFIqqCuO`=AnCzzZ^3I4XvKYx{NLE@~d7lju%HG z!R(BgXB!Bl5yA{{OEP)`VL28T6tX)W6sgO^$0)h4f^h-bcq;l{;ym)V2 zV|pd5JF}^wI%A8k{HoWfp-XCjI8|r1SU#HRe;vxybs#?T?8y3gnZ|Z@Pc32aCsokb zx*xD|R`vTe#~zPu8riDU)or~W%!WEKV0O_W<6>>GOixI#?uo*Yo=676X*go1(akHQ zD_j_tF9v7E$E@8CNS!kTH^Q#9a!m~{>>|ZV9nvrzM=EOlfXy1yE5UObYejMrmt?wT zOt*$&=+O^)Nx)VL+S!2U1-96>mV|&WSj5RxptXxUFsY{Zv zrGKlX)OgjtmI@y^Qs5`F+?p9##9^HuPTE!yiLXZl2+JE=AONUGK9TC_wQD1u?Yi;| zGfS#3jNGs5KQFC*Syj^kn6PQLu6*~ZcdAM?(Chyl(Writi_$}h4c`)$-EAIE1RKrc zCxbU`aXyg|od1N|7>7aPJz#^X%PgbU>gkGSnIE2W+tDKD(=p;-1%V0--&}gWTo^kWxEbMD=O?5p#*CCw9^-xi zr{Ut&Jp$IUBv`?j0#aJgU>LWl}H5m(hiNqTKj$#l5x68^CJ1fu&Lp!`V&JXP8GbuNb zWU?dN&q)ibb7}|4p5$JDOJ_maE8qpJ@sAzS#tX0=de z=xIpy(y+!vL2MDNTCyCTmWxBe8}dM0k&Q@eiU^<*uZQmzYzL%`V$a04AR)YAWzXGv z87mb%cVEwU#m*-xnax!JnfT;u^{8t&Ytvg|5%E{uPD9v?<2<8x$@~HJ7T*60by8 zMhvy==vH^2ToC&kYzaW6>|(@>#i}X-kr?6JNwH11*tEZHv%fB~znbl@>GszR_LsOI zCEslO>uUR}#QrL`zs|S6=GtGA?XPP4E64s?QYJkr1;x1ssi`$r{d!EQGd2X6^k>r0 z4!Vg>@oq{;*9FHks+S~js!JN_5<25f$w(Nja#$N~C+}1aUJD5HmuW+xyiJKi7zzbF z<~7(IHL8C_9y?qJ=N4dkwUn#gVHsxzfF|IXMUSIN$-75grr(oGUt`+%l$FWIQA0!Y zP3N+1Rzew)fx_F_5hvaZ5VDj> zLPrn@fFyDRIiJJ-hs?)q60=oiQ8Y_3eq}x?$}=RD&TCI(CfhcdV!^N3Q`hz;(xCEL zVG=VWoB9$*A$FvOP)*X`x*R>(v-P)tvrPikQH}*TjM9~;Z?Vh)(O8wCo4#~ecVNr{ z%_!<$LJzKd^VSbC#Z09xyi-}Pbh$E0ug&g~cm|x(dJ zaX3(Qjkv}_5;x5h+w0)?7>?ZZswKWUhfmq_C@w|E^%hVBK1um(5tq~eF;nqO?~d{% zEoGSw*5AxU&D||!PF^)|EJx`QTMk6^;X}$2qw$3WAC~`?a{%AZ;D519KDK|`kCDm5 z>gkX;SXxALIRq&YPdH0>FB!nKPV%{2Kecko#1)c`lZ?uNk^wondj1B=4eMj~V{e9M zNSUHdW{Yy%fz5)ezrK&|at>p{Dqlw{CTYdh{lkbzk%rq_>4$N>sRdlUB}ZE2>&b!e zxbK&sAXVxUQtHE$nwar~jPfv+9m0YAHzoJ2PUQ`}dZWny5cfB2lbk7P%!X`J|9*zp z$carj)XQ!s962n%4<3@=N8XU%?fd0-ms~l~a?gWt{fPAI$0K|7BeX|99`DqTUp&X- z;rl`ZJgiDrIHY)qRJx*}>AYmHSq_H=C9ZrZa5)qv96G`m4kZQC`t+l`XZZ9(kB#QO z>rxHU-}F8Hk6o|i3+*_a(RlHyPb1ZTF^^AJJH9oSabme%DIl)7J4?*RTgNx~_ny@` zwQI!OQ+)&mna0^W+vkE6B5cg4gQ@TiJ3J|g)zxxPM`=ufy0BXbikmDhDB;Pu+3;8t zd&?J|0@dc#pWj0nOt^^a^Ub^km>sV0Ow9o%rkl}x^`%>7`inHwgUGdL{mYDO96g#v zIz3{N*r!QSQg2Ne1U(JUOF@m+EY^?HO-|oAQcJwvsG7Q&;J6(#d)%8&$}+V6uui#5 zA4TLyN}LKMULT47aOvW-u>eAmeoEQ48bBaAR_#l53ipwDTA{ml!~d3(tzNMt)VIQnM+glJx9hVSmP|snS8C z+t?7X8!2AgPOnu{NFgN9-8w=28%$G^__`DiGb6ppJPDMuW<#CQ&TpZv{db9RF(VBh z=I1-UDk84)6=9BTgZx73a^=uCnic1lGeAd*oZ90zSCVd(GH%&4e&k@=afW3QjPWar$IImT1~IQNWLLFBsL;h2Ya?jbdi$ba1D zHo3&`tKMxbb}^Nms2twx65T?6Z*+(2wao6os9Kxj<{trO&>Omsa*9!mYL0iEaJ628 z5y|yS{qL>GwBByAB!#WFL3!6xQ*poK-cCP@5wH(xS99W=eM_c9e5~uQRDRW z81Uqrq2D!j=kA=FlOMZ4C-Q;Tv{X$lzMqv=z5Y?8L&Kee($7#hJ3MzfR(=Bs1n!>^ zzg32xY;O+u6^j;O)O5=`!qb5o2Myf9sFV7$B<&iP`Av78o+5fikz$*Khs&gWX`*?V zU~sfDp+pH~z_{G(^kF{89#5(owrx@%1iehGJ!0!jds^DL^i;m>Xr5E25A2!EY#*hq z!9^5RzF@SK)BHkqa56<<`}gYdxKF@14O`RUYNW}r|A!rYB0P(06cSkim{lbT6IM^k ziLai|90JI6MtR1%!-x1k=5%&epniJX>i$Njp3fcAWj-?#D&sffl_-oIXQ%r*1L3B1 zLNj|^fA%`>^aP}DlMq2N^$EZu^k3vh4vt>^Um)vWTVcIe{90F^Co<1B+pLZ!%+39X zM-q|yhj@g2JYl(`5=tFq}E-TzarEIX^mf@Z4IrR-{emViBHd zY+z`Eh7gi`kM&k@9RzN2*GFFK#Zw5}pmoC#J|wR7e=2(T);VD`Q}F9}??RwMYVHkH z^5T5k|C#qbKW|W?uH&w2&EwZ_V6`T@^_nI*xLW4&FLN6eoYL6j?o64S@yPrPP4Dg( zdiN!v9%sbP!M8zr6~CInr3PrLdGCFk)OcEj zx^yL6H<*cQBJ(dD5}LcrY95RfoCr7jpqG-r*%xkdhp%@Dfw|tzufNG{G;Wzc2BLC0 zL?r=H8RJ@A(B5{s5S7=`$i*3vx%bbtUW*m#IA7$3Q)o$t2Abg1wD$>J=EjJ*mkov8 z;rSUukh_JRNL5C74yF{B#rv)Xb!h0$j;qJzd_cQlf&!FtE?iy>=eGokE|~X^!lP>{tnr! zFW`HvroC(3lq%%cKnH~lRtg@w!kWQ|=TqxWL1p+Zjo<6}bte}((3+j3)h9t~wnnR_ z)vvw)^zE;IBDA_xqxDrmD{(ZwH!bo`S0stn1!-u-yxSR`n~zC5%-ktQ1bJIEJ*aVu zfpeA+D6?y_`N05-Hbb5#4!ytpD7rl>d;i(V8L!|D!Z-+iBi57RNm1T^PSfPQv_$Rt=Ez z(qzt@;W)p}G2nmOn5e!l+Y@}XkX|22h~9d=nl4*n>v~kftO}nwGZFk#c!~zH!e{x+ zFA`86^o8%yqt9A@|Ji_Gh6xe?R@_ z-#@n`G4)j(*V+YBD3Wm~=)QTOeNa$N0dG3)vc?v}3w@dzqc!mjGCaYNzc9Bm1G_`s=Ll@ea98P(?x~WcZ51{*_KR?;o*6mJ4}K)Nf54cC*fLZ z56^MTdV6@bO_eJujB$GWS+BnSNsiwd8L2}#>o?C8MuH@{Bcn9uF5$x>&)J|Z=+#`( z>mTP_38`ZvKQ25rJb!}Bd+k>Ki6}dAbJj*!KKSkDF&|Nd+*9?X+ zCdu^@5_U)CxK0`AvRu2!GQ2Hp!y&(ALzLHY&%|kbwA_IxuTcKh7V}kBj?xJbYhPAxW%C z$F@BEbZyJ$i5t9aMe-f$MM@OST24~gq;8eSZ3TQqvwZyGoV7EWm8WBq`{r=y5Lvd0 z3?Ljj%&Q17b_(YeU|-7}4)QAb5)<>;Rv>k#B_z4-UHljg44!8yS8%eaa)X}gAa0Yo zce-5I9qc?!|GE(2cJG`Z@5zlI8s7r}8NMaB+$?CURX_PH`%ms`+Q1cOr~^kf__)gE zH?jwWqasQ1t1n3MEqNK6edRY8$0B!6;q!Spvfh&CCTxLmY!qLzslNcx(1={1p4%i9 zQ@>Vw_phb_p-yeqs@Yc*_0JRfAhXACt7Bvq$^pDGXi`dE^rUywB|Q zKI8Q7T|23`tGqVyK(1tHExb`3epbYFo{BBb3bbSnfxk7LNN9*G)K*K)l2D(qsVTQm z*}L_G6NO!ewB_L1hExyb}{#z=yII3d!BUe$@v!}jjDr&6k6pA;P5K0kYk zZ_7PdQfzF((XPzkLrQ1xw+6hamHFt`~30La-e;8Va#iOamHGz9Zwwx+CM7X?29>j9FE!k*H-Oj zMlCCOWwG*a9~Ode?x18J{*zabEBy++bSu79dzG5EGC0NDcsq_lthE88_im2oe*dNr zD5U#mXxCG6HP@LO(+*3owb(#hsOA1A>9wP&@VtPfQmb2OTY9q-Fxz6c*;e$LO#xpX2}zzFii>= zNHe*rr4rI34vJz|n(_>O(vj@^8C zpQ&Ot?Cbki-qVVCV}}Tqj!?{P`=;YPDe@}?Bmfn=5Yxtv&fqlfGqb$UbmByzvUf}S z8$`P2`lnDDj(Fp1K0?i_s;V8a#sT_-<4puTcxB*@^dqbKtSE3rE8K#2bvMg(Yj(K8 zgMEJ_e3)`>Zm}-qQikcX%ge7bKC?bkM@iE6C+1!IeCHHbxxZ`8QOe$s;CjK!L1Cog z?BATEsCKuz{95CRI>h+>W$0d=8HG$!QkDXtj%s#lORi`~g z;_%Wyy;UZIJ@MAjs+F1Ekf^;K&5{X6Q-03}gZ^iKA*r5K6|QYp(@3kry{%k7J=^B; z)Y5Jn=>(U@l#VHrY0#FtWqy9a=FNNONkpF=A(XidYLqY3WAo`uLy6TPsztr*#e3BnPMYi)ZI)ex(iV=ATK}f%=*1@H;II}~+N9T3nAY9T zQHQE+v;+ckkirVi~8k%1;98?w5Igp`D-NXsw#2 zgZ3xC&eVl<@}<1Ia()zf{tdpYF=cyipb?Wxe=JZLUaot%8RB)NQooQoBOADD&&{!$ zW7Ed?emT9HV2z=1;{bs}!){4bx@-t1fH>V44t`RC&8RO?<2IrV>Jj;beM)DQz~&4Y zfug?25uNotX)|JCWVn0?3Wq-gu;Gu+k&no-Vc71nVau_c$%N)z7*UO7=BB>8H82z{ zt=WWaaq(4L0l~qRWyPLl%A|4Jm1`rgN3(v__m6iql*1!*EuE zDrbNp?``6M_M|>wWr@2^h=LVeI_&kA1$LScp;8VWR4qqS)_}dhR;`3X+rR=_ph_ip zy7tXi>y{wGDAY5fY=mQsF&|ML!|+c3qd zLA%*^ElOj-obq!2OZctdC)BvOE2A=RtCS#GPG{f~5(BVw|7(di`+iB`DRr?M(H)el zTHMwctUaK{rOk!*hbvrJD8%*$i>b1*uqo&kH45xw3auH3X^Cih+XeF|6jdp?&|^UF zBE^Du!yMbR{TP6(x*{@f*l}a?E8u(&Qzy-F<4r$D2eCWdaU=3E)}7|KTy61-dW_e_ zC7qb9*|mvi^j#UWItHa!AhK#z7tJllDm@NY@N#^{A)kzLMNsfsf6qo|WMu-^PGWO+W0P>MK6*6i zg?9;R5F87~&XvR60>f2X%QZ&2d+=%m?1hPmqJf6lW4)>X9oQwr)m6N3P}vl@^$0VU zr50nKW579D%;p0w;mj^2b%LX*8o;IRMs>XkFh8&t80GyXy%+(@vt|PF5$!C+jSpQ+ z?^gXaorK9LMT1&$Ijj{9>8dX4%vv7uxT_K9NS6Xn5N^$i_$9#OGB z!at((b=ng?Wb8o$AAe@=$?$l&ycG-n-cQWEnJquVptZ_Fyiv62 zTS)mp7+Z_tjg03rL@O*0{|&w8+}2stbv(=IiiplwENiEllbwM%bzyLleVL0#ox@a^ zx|?2=oKvZEOO1Q8J2nw1sdmIJwSDo`WNp|C<|OeE6Fn=fJ+^G|sADMYG9cUBfRp`m zwNdNMG^?eI&Tle&4eA%1u<5SN-%ud=5S8IYPdy=I@tQ4j`NciKiF|oO zCz)qvMAEza{yO~b>icl`J)`d(-Z$wq?aaKsm-x)tE?N5I>Jn7MFElPPyIpo?Sc``c z|2-nmcq5Mvm*P66gZl0z#L}?d);ZR@W_+zTltq-?Co(V(9`_ZTKdrw`W+vR#8XdFW<Zi-8S38^WhIh#nkBw;w>o_}w z-Nx`P0k-c>V6X%P+>C3Q82qZW(uMqHv6vQ>R%d#@>!%Bn79zk`aR&@V6_Mw@-yYE8~ zWyV)}?~@ZsZ%04F#*E+Uz2^-b{&nxY&kOGm|3>f*GyW~i;o{%MDvUTAckR)T5Ku;{ zBX~=4Dp5{sp;auo(BrDy zt^&$9jFEP4$97U#P7Y1XZg<}RAI53Wwyzssvq!L$5o;{fqj^I%>Pb|!Nss?+#B3XP0B`f+f53ojV-5Um_}o!l4IvNsg4n zcA@g#=Xc{8ofo@%YZJPVh|U3DGUEyMpz4R1cc86kY2plXw&}xm$_}2JoTf&8vPe#% zpO*A3cKW8K7IT;2{Od*mTbHy{Pf4osRI2uCE;c44dxS>Lv*$WS7{i_4C!X-_R!RSb zBeF2ZicHL|JEkK@lj+mWw9!k??gWFB-JduOESsT%LM-mwM@uD4HOxXqI`R?ajvi9e?4;^=W> z^^EH_vd`74b!68GkY?%el+X)%sB>esqzGPY>8bG0jkWa+s`565Z`XA5cxm{NduX~f zV7;>;+o@5!*?U85S0p3_6-Vc#pvy#EPcWsaUr=-b$5^kmmrf5ZNJM0MTl%*rV|sb) z3Qaoq43kbv6VQ?6BbKwE#hmE~W|x;ULOv_j(Ffvo1*RrXi2fd=M=edC;nuHg`5E_Z zj7#?0#g143?0V|6*b&QpA~Se;GPaO`Al*rRhou<>OOxN?_lh5paJx@D#^i)v-6DFP z*CWU-$Mr7EctQ^bD{`2$c4Xv187=BY;_b}treLA{<~p^Q>FT&`zi4WzO+&T-2MXr* zeplepZ4K+52qN%p@6-kQpRsc#jg5Hol;M z5<)O23DFuzh$KV}sKt~6IRz$RDLbyplt_HOg z1#PrkyQjmgvC_#A&ws97XD(W;mEfb?0VRXz1h zKCHGm%PrB08J}yP>}bWFlw`qZ+b%b%^j~SV$8}h>Y4N3CgtB(%LKt5*gThOgdL=RY zLUUk-`FY+ZOcsC>_T*?1W`9I445dT*g zi3!ViXB}OYqz`r$vfbFs4rY$O&^5eyEN&hHrINL1A1IqLpZSgIZ5dO#jmF&nb@*bd6!MQMKT_ivEjM* z5KJ-}W?1{K+%(qOs|;Ct6;scF+d9U0o^5ewfdx_L?o`%>Wxq}{z^DG96wkL;bHmQ1 zIWTJHqN}5MAdCCA_j8(faAZmnp-cJD-no>>&Sg$+r;!6+nyFV0H*d%2)jTvtveLJ% z&}YI4>-&VzN8DR9yNP)mdY?yo-|{B6df$?3;y*!{CaVwhM#K|*E|y4i+wyZlzF^y8 zO613-s~jOlj+O4Yr=%;dJwajT?Y&#*I-R0~e{R=;1-!+jUu~4juBC8f*D{S)_APZ|G?3K0DOUJ)CN70sEDSwf=9hUpa%-(*BZ03cW}S zy;ISzFSTClWh~N2m;6+}mOaV?`cC#JroNLsO0&L`J<2A1Cwr7T^quTczRI2daTy%6 zq>THZCh;SLUwuYkXaUipTa;@w|M)|G^%i9U%fo55DC>nmmo18nDOo|RVQsT|(JhMR zoGk{-+Nfw!=Ly!5y0vKQEy{oJXs@5LMcG4gRzle!5vj*s#kMF``jHnP0gXno`LOgT zL&{oevaNAtHlDN=VerWy2w2*rJ=z;vYyFQObU zO1(cZ5{io>y5M;a%ST%dh76+U?aBR=T==UtDY8o82BRCB~<6GAZgJ>ulEGDKSR*zwSXB0JlNX_SRNT81USOik7f&B*hXNU9zqRyqxTbA@3;07 zk`eof=eQUd(fx!Bg>VMGo8+2oYN~FR+DL8ITev1ETuMYQBi1;xwiEsywjG)+S;DsC z5fWgh!R|tm4C)t^W!5lJt8S)I_GC3JywK;h8-YqhG<#$*Nh3`jYGd#PG&kF$?*X1Z@~O7(air6nE&yFn@eJhzoz~K{g0Xcq5nTje~-oVAC{)w zep;rV7{c^RCkxYGrOMdnMw$L62o+hYF#WZFMVgt7MtmR<%^hp zAw>IQgec1N%cRo7bIeyPrr+7k_8^Ywmkj*7gaE)Rf0d}GVfv+y;+TGUjAQ!cQJ8*d z3&|?V^h?UZ^h>xk5H!=TmkZ6A2-80uC=<=}ODt2%X@mM@FevG&FZ15|7^Z)+b?3q% zbw%AQE1r4>hL#lkyEOgqeXVt{;j5OAD6m}`-y($(J>!F|LLSkgI=@WDKh!Uv_oNLo zt!2!7K;B!6nVL_+&Rx<|{#}xgqwyJB+y#aGiolEN4HqoDJS#tOVd3T8F^YvBUE52d zWtBOvS$Mfo?cX7f$8}h>so6DCk78NhrHGQdF%9N@2CboHy?iS3{)!}P?KU`A&^IZ* zH&JloyBP0&=sw}yUu#ZG*w>htaFYM3ro@D8{Qs8!fAIfz-q~trTCFD=hk*6ze)!Wd zw%4!ltY#`7xnkov>R0G*p!Fkv)=okj-`XtecNhji7mDoY5&1wQ#caTf`!L&8nC&=B zJ;t7c?KY5!U7Q)!25xK^u0JEM=E#<1U}r4N|8(OuwSj5jqDbkH&EWU-w`HN|5*{b% zRH$;a2RLfMN5whv9?c-$M_3S2%q0| z?-$_nXPKC#<^(h4J=Y)cc;8HZuH%0*|1r${e}>O5se{=Yi)&-OpMuY?%kYKx{B|0j zlBeRopZ|mW_wnD$e+U21@eii^5is5V5ApdWGczKUp2Gi3{ufBd|8so)M}m^C#^*2N zmNq*bKL2Bsg}#u;_dUb?LH->)L^1VGi_b5yNiQ1bpWyTBbmQXvf8+DpXlf zA9jS@!N+nS7LNOBJ&lAkF7e7ww*1VJpF;U5lb@U9XI=26CSqUh>k|WusCzg|2~pB6 zazBX5-zT}B;6+0&u;&ZJ_r?ML`;8dHo!8f2{f+Y>i#foFVJ{38Oyt%tk zED&sRK+cYR{43stFFO7w6n9WvkYI{EAi)D-xw69R?9J_4Ugs!R4LU8L-QDqJLdB#+ zR80Q3jhs+@ul3CAwB7lJXNUu2(Ekfyg#lqJm?{@h2DUAy#l@?w)MWw~e~={NL?L(x zo83_X*bMprl(^!dm1gf;ha=lj_V7d3=N>Somp{zeZ<0yZ>R99@mat4zVjikX8xAAw8 zDmjp6@|0j4L6XT$qH#!@bHpE#O9xA*l`gxg-^eP^u;sbxj9v8aBlppuj?mFgmgfWs zcuog2q^pmAABiN%Lj*NRJ2kwDt(Bqi!lNwD^`+zQ@D zj0BhZE$7`b6vLAQ1#2dKtbJ~4twvo8)7LL5s6U7*6iy^9$u&u>B`?xbr4+eKY#u0H z7R4K>XlksmwrOf3Xe>V3H&4Nrwd#2ReqisS zKwCN+vN#{@b?&wINqd=`!!Y--WB9U-8h?(tXbK%VS^MZim_+5uB6nq}+#oDck?4{~ z)Q|a!i8ZfCd>{y#PN=A36qq|kpjZ_8+|F)kw=G^1%Z#ht=A28-^bF@dXXhy4BJxN7 z(J6QdN>$g9Oz6Z*lqt1!Z#Q844Y%P$LQvmt9l)1zaI*{a{CWiw1iC(*x&m^;6VUBP zVCssFHC$p|iM7Un*)SzIg?B&%&#F-4W%DYWDX!*9kNJy+TzSB&y%%d3@6gk#(&}~msI%yPjpWKw5jD)V{My(Bq(rOr%d_&g+Yco?m_ieB)w*n2s8pkZ>Yoi>0bTRr(WSH@RYn$UGf6+ zzBYL$MQ0iY#DOLl+@iE3dSzAWHIjwZq-iBe_)h4lx|fEN?o~}B;eSNN5X!%q6t20! z)x?9$u4OX2Feh8(wN+p9i_$*HDP_$;Nz98oIX6U5?l4Dn+xA86O*} zSv755sL9Ed{ss)&7MR60xN?RCuAHL5m3=w>`uz^yG;t~pg5Sn0RU*kU{(%=mCFl>7 zGYS*R>eD10wDMt5R}ZW>B18@kl>0m_D#tq&N^7r}Dk4rt8JS}1Z2#v$3 zVaH|{evi^wF=ijSs^SSZkZtwu$fhiH%XOBizNh8ye8X8*c`V>PX@@0Qc)Tvn^mn>h zX8%{nJgV`2dh5)j#BN3K*W*(S&auQAi*Y27Lb zB~#}uNIR;+59L*5`R1wrtgs4Y+)RNwq7T=Rh{UpTRT;W4WtA$$N-&(K9*9Q$65q8& zAErn>@0KU>@eHQ8>KrSf9gM>x;Urb_`xtGZ)596|njQL3s8@%|eTYGz7eL6?Bhm+N z{43pF7Q?N?m|o(WQ{IDojB>In)U|EwK(nH6E{l6}DQ}{L?@{uNaulKR7vgE2zdp-hpcz!3 zS^rEZpwIo15QMyip4LtXnUIASWbO2{S~yvm=*XU75r;HQ96YU`lCr;H3Q`<_Wmb1T zBW){`1{vw^NkgqA#MD$4R|x8Qd1?|WTbED?g;jwvEtamP1@EQ^c?*5#X=;=WH6pjs zq$o{tB4!yel}BfFzhq@>Rtb{ubMmAH=|=w}`bCx61=30j=o{sNK%F-oJ>?}X6JUn5 zeF&}8;s)VFePfILU0!!4g^ZOAoW;3HcYN(FY|0emhyDA{(ce zvVviNY*M}lm$1VN5P9y!51`FnQe~Cf76)M{Kax>-?LcD>{MZPlRz@zfu@hV>wXTm-@fyb*{f4 zuR3$%`DXbPKu_44V5a5E5wZiBv=Wp0l}BE~xmtL-6;1=hy;84PRo8otsAY&OBG#_C zqFhvxwMTc>vK68`%bxa*lj=mNP?Ja>9@yTgA#8;iwmeGul6DIJQ?A}5Mol$N*wbxD zL+{!SS(L7LKvpX_d34nOBo1Z7h1nlzYD<`BIqtZVXG@|Nu86uynTJ!7hnSm|6XU7O zf3da#cq`4^aMJ&`F|vVhvOS2dLg!Kpap~(t8{dZ1If6ny$MfAPa=)xl;uww(NcW~4 zq!zc~WTz`S{3x(1wQdUo2_~iN69!?Bh45aZjaZ$K}9C;LI|d#O;zLV)<7GsNu~^AtQQjs#^c>g}!8e zX;xwwJm}$EpB0m|e6uUnudcOD@(_<^%8609SZk1Ob{HN}2{t6$E#Vh&BfKIRdwpkv zN^gDWJ2M(TS>pRr#0x0}K{-3#Z5f-hNf?U6GOtAQ;wFMI$ zn=ZF+aNvrQSe5PX*qdLJxgncEV-v`bz9_HesE1u_In9_1bGi&pT z;CFvOaUnP9!P2AZ9O@({{^gmBrq|ge6uE;XFQFST(RQ@k5w3YoI34+P!K;=A`3+%*z={t%;F(Q2Y7!ku2`LFus9)HW<3CKHm zt5$Oy;a#cQNdRB#T}>`-aF&;=YhyWtCq*M8_CqB8@#7}tb?QXL?SRztSWI+i$(2|~ zaRaf_o@Yj>e>mY$G4=2z4;EgV0373RqHoGz;Y=_v>k`#>iB|^{uxjHbzlSr3DfsB% z9`<}fHikOPh}0!MM76@hoIDxL#}*+g33@K@Jyl zaEzp=U#RtWIUu4R(ZYWcQ=~1p zwi0~d^=B&=udti@FAQL?y-RS4ws$cHta6)ao@cMU#N~evh&$Cq zOcHqPlmrT_1f(8Ul&J`{mUY5(4pL1F?_~zHv;2$Kmas?F#=aJhn$Z;l4IsUpeveJQ z_BFXmnl^4(pd~E#ds9^oDQIX-`wVIqCa(61R`Djw*iacdMa-oAZw061XxBT_1OXWtFFe_ z9U*{@V!EC(MXS$mT#PFM-?%bWEL9eZr)ikbhbQJ?XP+r>3aIks+OI%^f)B1gsbnE6 z4!4M7vwodiWX@B}Ih_d}AuVAYepgr|-JCldVM!`FtPbDHAfWI?bgVrFc$y@MTO;C8 zsRwZHb(yPtDOwvSoSffXpJH}zlQ(s)GIjWSx(%zhNj-hj>RgopU3=9v>P1rBBs|j3 zer=~0tFmyCE@L1)(chWv-|cEXaoT+t(R3HwHL{4`(BRn!!il-Xxid9XI2?o0^ z5CDN$1?woorg6EQ0)*b-EmPyARce5<%!cv^l41PnCj?_ClVx3EhJK@CYCiG{B7bo< zz5f=M%}KboFU;mAR?Jv7r$&~^RH-gw>r$q&FSItC#sRu|q7KU;JwYquNcQMzDE_1+RKOjQa}-h)Kd@IUFxNnGhcH!Wd==w~0u{i{UYo@t){u5nJ8o9fd4G zuVVDUgJ(yF)~)+7lgQrEsBBNmKV(j+G^4V_SmIY%#wC80iXDMMn&x8^=G6~*T17r* zdnWO}><#VavH9gDenUI>L7T?Y`UE%rmkTwLMNiQH`8h(ewIA#PGC@mV5P|%8*x!HB z|8nBkCf?8(;ct<=Z(S_uY<5%XI-}CBGK?!rIJ2Q#=zt~zx&T2Fou3Zhm1%E>7w6$t zBCq+@laZH1prxJMhA)=DoG~kKq-zC059Nb#%`ZF1FYI|vhDGZ^4tVy46VndHnyU3} z63?q2Fd9et1S+t0mK;pohup*B=vr%u=I?f|T|juHN^Z2s2FrhUDp@#OI3QYEqTl_s zVdR}x-X%K1=d8U^zk9`ghsK#sFGJoZI7YIr6|v4Iu&ZmEXhnG0U{N}ZDK?gWS|()y zP2SV`ci!XN)S3hEw9Ry36lMy^UyJlYJPSeeA(O{2 z2NtB&Q53(Eo{3TXEThb#_`XHK;?Mj;-c4r&iy;?(|0Q3FXh1f~Bh>ma7Ru^s{mf#Z zSBi923ei=ot}YJ44~B2){2;Mzp8Clh1es%nqy=0V`U___7U{(r@zrz)$x_#jl@+G7 zL%;TokIYF{jkoiiehy4Y4riIJY-yfF?U zjY6a5{Rqd5U8Zh{WT}rudiNteFt5wOZGBFrd2LSCisfbHpH&3B`T}>oYt64er_bhvwPKfG~)_$ zZI%FTuJhuhT`gqFGF)qP9I6pW6mu&_*9wwxn_r6DHH8DP$Xzql1Un+8$TE_r6j0%xyh$0B?Pd3M~Dj2oxHD$Gl>HcpkJm6G)Q6Zj`x=?3%h)Ai~8 z-3k7VnfZTQ|ADa1QH~1ndx=bm6`Weh&h~j)DPg&~o@NobegVs4?(VYX3c)agmyleu zFsD;ZKi=o>kK!JMv+a}m*?aX_y)Jf~M-Jru`6M65o5}(wxTMDsr+oYI(KJ4rXJ&Uu z^J@hJ%{@w)J=v^4XpE3}u!%47?fn?4AU|uWLhX|-( zIBOU=^n<{bf|k=FpTdGPV$Z$Rx2#g_C6-7J47kZ%`hFOtsY>-U_wW&wtg5orQIEhe6B zuqcob99WzF^E1MYYBxU{i>I!182g&AQFQu||P&-BA!ZMx6f$L;}JIW38SC-l{cx@KC{rv!594WEf>69QJ z+8lLvTUH;1XAfv9WzwQqzf4;~QGyvUc`sZEiE|%ECyg zmiGo$40JEfNJ=%9nQ86UW+Y3@#X9Cfi6_zWRv=azp#2@hOiuNkt7E1}%t<*2dcOUNggFy{Jzkvb8kD1& zo5>G(bfTx_ZtmE;e-p7qQ(mg?t|r{r+-X(YceZ-^XSyA~$0c5dn9DQ05zaSv)g5y=2z6P*H58FZTp!=XFszQIBF8z3uzLjkQV<&V61#8OwRQB%FttTH2 z3;Fwi$KOW;lVttk5&S6!`b%4R z(v~spXPIej*JUIV;PgeGQkLWnh0i#C@WHx-L+LE%AtLkTYv0;A}pPGgUt3A zYe(oaxEqaJ0(DJyc5s}sDTx||$63OWGW7uEsrHv1h0{3(qKJ52X;?!LR2NZ(r**ST zwW5KLoZ2w*G8lOvkfcge0k|1A97wf94+)Q7@Np0>imkN&1ViF2D$3nhWU>Lzk|l+O z^m=GJfv8^iJBRZ-H=He=$0S*(m5aBvJt?UUJ#=IU`HAW;^5V)?50XSVSNx(ua=jiT z*F^^j^4j{LRO7cKIF?z}UOFV>L~c%SlWaP>7bi0gza--(#ft0K17}#;_DU2>88}vW zicVvR44x7_dM3)~$uiS|MuuxJSw>GrV%S}4VpEG4nckouLXR`0thTO8T7~H7EcNkE zqGV1ve#(TM6{?qIeCQ5O>m71)>rqGOPHs>J8*x;IzLKn@Y4w&A3L(>EwVQ;5O#h7Q z2$_DE#yUmR^dJ$SuXo4dXEZzc;Ad(bI-Ys0q9fj`c$*C`hL#! zTsd7A>OF=d9Ma$5otIT9xZ^&It0R}Gwf9q;9+y8d6JgSNH40}Feq-Afwu;-DGestSlx|1Wmj`aNQ^+$24>MV7yV!$TJYXm;_w~@J$np#8V zQ4vY@+BWI2>Tks)KnFQx@qe1uc&#zP?5azw#sKde{@whq$?fzlR=u1E2@c;ubS5)} z;@-?edQnRJsQiR)ifp>kd|g#AqL2u7yUpbA^=6k}d5i^Y;Edc7FtZ+bC`$aJ&U!B( zM7sGmFG?97m7nmLFG>?UDnH@W7o`y%l^60MQ%Gh&MR@s$UrAuH-Z231~Bzb^%E zxxa#^&qFu_r8tv5CJwty|EF#vmC6}O{!d-L$>y?~0;t(6xruO~;g+oll=k&3S#z&U zWI`^oJl1kZYmU`UYbS_@TIasJ(!=!=&CLk|xF(Yj0;yi*QLiNa+-kUR=C0k==3qcS%DR z3*nx17!&y;y%xUMW|`3fgKK7e=!kL6&Gm=n8IF&_8{{BLSa!p*gs2aZcE<72R;ube zMqD*gE_=f1lZk9O0=|_Z2x!`ms3Qvi8(l!bv}=ifqm$MSQS!(H7uje}B)BCCRm<8i zS9`-c%3I*nP5%b`_y9YotpO@?eol@Xf|&YMJV3)@6zo$q_BmG1vMr z&84vE*^cmheSBc&iGqpWd%au)H*mTCSIi4E!g6HZ^| zqf-#;hG*(aV61+atnMb6_>+q9RuLeyqXdfLPDu2OjqQLO)V;cRxaTDcp;UFrY-9p8=iP|onqi_>dS7!N$3TnLVI&@PW<8Z}# zlGVO-Ch>yWy;L2BX9CI%SuO;>i@QMU1-!Lzxq~_;sAq4%EC6s5u;{*&nDm$b4Y; zC_3ls0QSuhNJY_q;}E=NCK?+-mw>J~&~MW$wk+L!`JZ}P%18#9D(@N=*7dKP{n~MG z78BQB8eDvZ4RSb69b;q#ZdK-a`Az&V_DU;bzSnW_=&)v;&Ph7iWHtOA>r08x|>GPtkrU3R{AHtNfzWl@r|- zcv@!gRwPYU1bwWp8{EXd!5f0~ixG}vC$d*ZYpEB3F*=H#mcNl=eoeOLJO9OvrBHL~ z_aPa)v;(byId((7)e;{|OI#dWJlUP!xoMpGZ|ZWIW_WLn+40bh|OQn=~zWwsDUtM9p?3b7QYJ$8p zyFwyZq&;B@YI+%6c{qcz$Ce;_rk^s@AU|pcR=!lB53-_hJ2#R-p0Cf-axpg*@DW*T z_E6pGt5k0j6F-K z(iOt9oTe+#ZjupssWhnW3K{2nY@Q)JDcT{oZL&C?=o{$&r@r$=^?HINAPj(~g)z^L z{2->dD{d6}pKgy$!srv`)*GKIH9*t)Bk7W0(LsR0MH978{YA^%vdn>iulM-7lLu$` z;Q#hJ4D@$*t)P2SEr*{!kj%y0LcX_#e|#43(e-bjCPdEa9zJ8=+skN32Lx0Y+zmRY zJ9z6eY8C@-6az%-YFtlTuTrO(XfW!Moth6vgr7=PbG{JU6NlL`Op`|kAWq`XT)df| z@XZ$<2b_UQO%0(l=}|&BS0aYz#9`2iOLHBVq!ZB;PNbMZ;d;1g3a7Y%PQs;eqzz5sO5CK)G<>Y5<=1@Nu%KDs?Hf1E86jrl)%kS(si?8jXuHv< zu^O@U>q4XIaAQ&diDLv*NtWa%M2Vbe{BexxGD6`{rMhBZrCud5R6wi4KBi&K6sz#e zn<-Rgq$YH+YGyEtuvMRBE`jPB)0iuzUc_(dWn`g#BXYz6W(y{+&=i(=Tm4aTWQ*q{$P+zlDpIk3fd-Y?s>eSa9wNqbbs;Bk! z0`*IMouwYt*E#AzeVwAdtFQA^KwlTAX0HA{g^`VgYNHNXtiGYIC8}0muT`t`wMvd|WzOGco`g)^UsINDv%k*`Px=3Hws`K>qb~R03*Qqo0)leDwx>+Ub>lQU!ZWTdm zN2vjxBG0xk^wfXp+cvp8 z7@4q9g!?-a+LQIG?gY;^SI&mNnVo3Wt%{!xT3ZKZt|qfeZz#O7(w$**?M%7YP9$uO z+7PtzXl)4oAiABdwrSygoKBz`D7OdD+r-q)=V?!Ea~EF8W@H+hk?_RWqkEZ=6e-R0 z{Y^ejY|CQtG36UnfQr)V%Gz#Bj;stp!Xs_d)LoLZy`R_`6Fk{B|FiZmBf24Qmu5A@ zN5NuBKe{FGur@;2>yl+_xYot)4{Fg}^a*lVw!R?%IyP0{`}55feH<%4O=)rKc_tB1QyQ{0JBX z#=)TLGFb@nXAU&FeDA}h<5_)b2ClS7Udi-tI(*IwnR+ZfQ=_@+AiYTN;vtpycC+pj76 zGnP!;d5wsXQA_J#%eAML_IxE0@Ui~&GiXF)S3BX*Q546#HKOxcW_vzm@el=761K3c+`sb@TlI1KlflIuQ;WzhavL@2p+la~l zs4(C4|9SdthjPqr4u3_ZN)CU`6zm^M+^ESS#R%+bl3X6XU4Da``9-dJA^{k^Qt$Bt za%g&<+C#!R*3lfN`B7v-4ss9KSLP5GJlwJVT~a#s zgX0N61i&2gKfXnR7S_AospUqT$Pce&khdH%J1)?B275 z(7MFf2irNDhKlG9Zsrx(Us4QN{OXdhwH1x=pd=2N+%847BLs9I?QP(I@Mtq6VMnl4 zDq4n3{xPe8A`|Yln`h8TyXw~otukQpyFK^*jWGW%sSeF0lPZ=1`RXLPi;j(@okx=S zt^P!EN$}kJ8y+I9GVmfg&5&?E)RUostn+-&j*Qu_amq%^?Jh3%>#8LNbbYJ}1RjvQ zV;_`9wo6^rpK>{z%h!!s?P)F0nMjfCq8eYqy*I)j9rt&m;*N{OwTi`}l&7=xY|UY6 zhCb7S%h|;sWOO~nlK>)0S6j(>T3?_B9Hp5-Jxqeq8=iZTNfyLR?q?6?>Ng-~@xWS0lEi7&xmR;@oedd7mb?$&PcMBiWVLH82 z`|L_7u8uL%XY+NKWSOhSR=%7XuciQ@Y~EOdAJv3jyB@ow9-ey^%U7>{zVr)$xnHtm z(IT)CQ+?B#hd-mHo6h&^K20?BY_IKcjCB+iqP7_D%B6WYLZtPb;lyRj{#jL8gJb}j2tcEy3%91x9N{a-BPgGW>PAGq~5h1>nRbR^?K4uSLZyz^zMzak2?RzG!|+rX^w751ETh)_6uNwhLTp9?vR)VZQCYmTmttv1^>l-78^BH` z#W^uC)3s5D=h?*xE`NVvbUS9NWO+w;53SOyor2)1ojFsf8ebtngc~o3`x8+o`-Tp% z)X8q)UaON?DpqQ_ytCBFoFch{tt@osI2R!KUZhjI#7<2TIyWw%iKiqq<_i)^@ZE^I z8E3DXGtA_+EHlH_JWKby*u?_O_Q#%&wa+D?^zK%REK5`F{=SL$Ouh$|i z?I3QYy6-=&>Pi7y^u1Nn&D_V=bgjG_Ra04Wj8!6+DLPh{q|J-feKQ%`;sU_aii)e8 zThcv>#!qpR|32Da?4W%h)#{lywl zIfIAVK!QIxF$}0LhB0O8x^o$lvX8TT@BR78*c8;Ai(Vo8Rnia=JnLXK{#;f2;}#3c zkB#wpHC`iQ-ZbpF*w}F-RvQ3!NHmW1-e5qzOTwbdv@+8--HL)K+Orw80e8HwMkKW1 zePUqkc{aIBy+X^%gfQy)(rkN@M_9{-bMs2QTe4@rhQ}$-1G|^gazJm4iL7%)&k2aF zjb8KhC+oB4tx7N^la1LVNwCqRb!^9yAw=F0mBzV_rRfx37$Gk6rS%avl2ZqJPk>hj zFqR&bo2OMGg`Xd3899&BL6E*U!o!@N-N9}(9k?gi zapBy9y+h#P$K-c=pZxAREWfQ!=-&bT+pB-&1QCxV`u7q2`;`9O!>_ua5ORFTH&^{P zhYgmJVpLD1g%B1JAkc}3{X7t*+(bPEU57^b;8p2`p4Okxr4`Dzmv^F5JEuZ@UEf0* zW>%;g?ja2o!8w*rZK=GoNCUeKDLJGt>ORU1PQiK_t3sU@m)xYduLEKyP9H9GkE}YSIXwOw}PY5reDv*iS#1CXpZ%U+Pq@HdZv9SZge9I)N*rrW3d_`Yz#Cg&f?4wo0^x zxBJd4wgZviS)CR78n3M8@rG`Q-DiiEao?W2;4~>_%Li9V!)LWl0?pr(BTV>!?8#992Wzd-~s}h)Iv$c+#N|jP|jmqEOi4LOZ*`MJi%{}1GvT9BRFw{_V5TSue6h3iHj|HTsM82 z$JkpRFwGKAIuQJ3?);xbd`Zts3o~ZycwTpGPy%X9BwCX_Bbpv*k6OMvBoO}#{PyVi zB2@@IO&kpop25zHnr_bBjgMlImKyiT%g^Ot%<}A5avL91`phFMFbwWQxAybJs0#gw2j94+6JPVK zvId>Bd3brA9)RMZNQRLODK57Smz!EyChDT-;(2UDvt37Um3OzkLBv^Yt$)xt z$jZf-aD)noQ+vEqfXNq0!T^(Z3NU#mz~p)!oB~YVISQCuVi5JTz~s?tMrVw8BeH?X zbz_P*BDvAy0&heTKkAK0!lPrvw1LT`$U)qD>yH4x=25#MvQf!pT3e{(;?^ySN-ljI zMJ0cNdkdAk=;K@sNv?}1eu#KuO-thz4gyk44M|=V3F`jQzuFJc71zYv4810ZSg>2CVA+X3}+k> zNi1{M$je|*hJ`xDd+TEhlU&La!z7nP)Gw?Q{F;POYRS|9%DdqfQxiI1`U%01HxiVa zGV6Q}SuqdAWyR{Nljszo%f6X;I?%{QIKxY;kOIwkr)059zQ)(_b^#`roHa1Hv??TC zm!yPf0F%qTV+;7{x~zuar)S$SKK)qN*1zjXiKE>Laiaz=CHQe2R&BAa;>My)ui?f; z<^%T;$lsfT`hZ|FDw;JfdA!?MP*ZGb{QqX4Jjs!d(I*d}%19NDDa_x?MH34c12 zm@w{eV#1yLzrcSF|6>WC|E`Tr9)D(9i5X;O8qI|DqgZsNqwV&Qd5}9d*8#KZ_!`1{ z&2G6m4}_O+e2Z)8kSKfG!f8ac@J|mxao80R? z`NZ8gW@#=jCSR8WW(;?3?$VFHjX$xs9p*msb#O4rXVq?R9Q>{S=**FQ4f`a{OxX{& z6ipFeb;ISqBQtxjXi8Ie(cyEAl)A+3qIYJjtn6N#boipW)ZpTzci5k1sSgk_1&agTB&(?{M0B_?;;^n-)4& zq2bZdAlSFbb!T>`I9BST!quvaj*j1s3QiwjT;KaW1BV)kCtr|&FdcagxrHSdi~91F z0~77b_hJ3&9H!YRzI<1il^Dtm2hPN*k)%%bCNJmj$CimM!Xc9{>A5Yu zjFXa5ZPg96iULC4e1QzMI525lex{1Z3vka7wR90YG&KKLXw&4Ce+91{G05+)1L91M zzJUY(oGtaGOy*3p+ox8z)aOR{_;=YowFVi9hp|1NNCPP&+c zZ}}ZI;b&Pk;fsIgR#)qF)Z zchj8G?Ohe+FZ7{V6Hc>lNF+V*6?fn&SKzkv73F0ss{ww)`a9AW*YYAftSy-3@NAU> zn%aW-jtzM0>F}#rHC;Qsz-86ucY6GC6tnXtHa_ySb%~Bym%Jf`n7Np3?;MD}xmM2?JR^@ zl(5Tc3$EcbE-YdF7_~^)Ikg3UJuvW0#;VK_jxM@m3LHwhZago_o^mOrvW! zGe}?+ylK5MdVOPfb{&6@HMHXRd-gKUlI*k=^!J>v-!N)ul}C{=l^3WVRYPL7t?t#-xdPlueHQc#S#4|9Npmd7qG@kdLQJhKdk&omtr zY=;%;M_g?FV8o~eAnHOn^?Owg3jnn1p*_swR^vD=DXDGuH(2a;`SLJISy(G9E7L)T zOxA!Q!Q%8XHFYm95G2Wv~|Q+ zza(@nbGj~v9n;-Qqzu~X!?-n=uB*eLl%AE~ur}}3RolH`a(AK2;de*63zJg)H@O}9 zzaxha!*6x|B?({`Yk<7!e{cG2dvk1cvhKxh7i`ee>^P)>U8g&ceFo8f7cD+jS3{E* z4xHPhqpstXo*2Wv@#^E7;|6KySG196yT?vZC#lzB5iA?E+jy3t-e;+m*)$~ziRS?l zFT({Sp3z;Vyi!1VNOYQ#Y3NIYI}r$d!37-B$~owZSSkQ)X~ ztK_R!*r(3dN>0|QPA#91T7H9fsA1L!?$60vtb)`zSEGTLUm!qe^;PC^pm|1+coy4G zZ(y}$7Z4x9VK{+t^G7nb%(S3(qToBn7%e(P5PIIpA+mmgNG=KgBsvLM>e+}KQD2rq zQi$(UmH6r9KYD?f77P}7o2u%(SWeU&#Xm_~%`mE-EZ^9L99(UTVGM(K?8TpU(P1Ww zqpc>QAv8bZtc!!gs8!>aSL7JGh4*;}BJLTxwVhLbXX8R+HYctZ7XTaOFs2tc_{P&B>f{9u1edshj4+CehICRF zb3?Hl;q$oRts^|Oz=25B@Dw;;(~LFwyTM$xK9Ckgi%%n)w)Vc%#3RVa6SA>aJpS5@ z6ZFY*|dxTVHHJ4J)1V^)2aR3>s5@q5YLwt?+%ScQc8tlwA#_6~2a3)!kr6c^a z!A`G{tV6x(HRS$`sz5TE$M3I-Htk*9^*G=T9N{vir2za6^+-k8$YsoY_1JSVD*&hUO$IgM$RxqFJ%*T1EHvP5b!x_Ll$9@2Ml^Nf z-v^5#l;A>2uv6vGtD1 za3n7ivtvdl$vFx=E#KiHw@t=Z*pRe}l}cXCkmsJw+yJqW=(*=!-g4*Kv_*F7RM4gO z$lkQwhQTZ*;A8f((fzxpW#=$W=M>FHUfsS!w;N;C(<16$+Is0eNpXlX+Sj;gu0QtS z3oki*4N{{}LNxy&E7!!0AovWqHeSMSlBe~3LOm}Q4bP4sfc_Z|9L>DKg*hJ?)&zvM zc7)^%$u4Y_-e*f;d#u71TLF}pGPQ!f8i*aut{b z4(uC5E|7dMBCJa!O2F!RUAx^Z9J&J*={EU>^(y=zMU(p3utBtcZV&t10^K@Sy*<)8 zTlwBO1e9Lmp)VTWo@YykXNxNFB|t=+ehM%kIzwY6lKT>rMLzSL&DeM2#^;NsxjWnJ z8Zj?t$?KIR?^_&t2(;Z1Q19~vljO|nr5RQS=4oD4Jyk6Q&oq0&*ZMt?JfGWmC(k#< z7<0TRvysO1y^2j|yiT`FBw*u~a>&DHFeTBYO)wwOu*a6uNkXcGe7j~3l53vth%qzI z*H6(?G=E@Ak}Mr7X1l8?dpL5rdI-sA{90%kBxrM9xOJ$V_Qu54KVHd0beKii4yncp zu>a7^@EYSiI}(s18)>!qkgG+DuDmj=2=eSmm3_Iz_9Uu(a6IwZX(@_M33M6opoA}x z-Mv4i#S-0*!3I}VRv-X`MSdz`MliRzv^^O+llJrdJqh#sjlW&6Xu+gOdz;?NPV96o zx~G5CZNs`%BE!Mg*av(mr>n938Ty{({Wq$qB=Fl6b;{ghd`0 zr@mxS*$S_#M588(q#c+m*+@I@3B+ltm2F(ydaVq4dzlU z9nJV6>F7yc&MDH-u`*abcvEvcK1>9^&26_L^U=LMnP|p4R)RHa(wNTM%(< zybP^34_TyD;SK20Y+YEjo9`_}GPQ$S(K#DYT zGo~;idST-S;k~B?2`^=`;=o@=f~D!@6;+r91xqu_D-e@wT{s!X)IAfU zHcu-&KAQ%2{>#R$KtgzDbTZuSX@wt-ep02ADs^EEKL<>2H zvuhFG%TT{jgy?7{X4qnHd{l)dNkP3(2(ikc8)ktjbAfv3x)=r@s8tR%muFHLC3(W| z>U~)_>!W@c_G&*2U0~=}v`=G~9BfGN?|hQa6B^DErr+~ySDm{|9YGLC9JrUT*Zi(X z19<`5{+0fI#6Lz-`}8H@o~BQuej1XRKF#$x38Y&&v%hx*d5S?s%eDF@$MYS*_k&0J za?_`?w2KBgm=DisPg$_p7`tHQh}VX*ZLbaCRDe-^Yvb859}W14%n}z3*&5t0=Az*iVC+4=t;uwPntxJif6xDhcZR>S zclaNAXE?<=ZQv!Rpdp8}$J zCFRv~*#=5JgkYDEMVmAG0c3nx%-W2zK`@xAZF=M5{&pnpa>!hyF~1k-#U?gvM<7hv z%-bvee{6@u0iQw|qrL`7Wo6Vx*puR%3#Uue3YBpO6c?-P5q?Bh(_f$NFp|3yE?ji{ zAjcarAl&{>#%xS#+Fb=y-R@a!;}*4cZ2C)gg5&D;GeP+qcXeBGasEf^|Beey8$s1h z$cwjQw=cD|qdr;I>u{1xQA{q=)%U0bmVEZ)wATCQ{&bczVVeMu^(5WT?}Hgm?ta8w zp}u>FyJmg&2zLj^68SN1dXk>tcRAnoBt6O9310Lhi3&!!zI%qdx6+;Qr#9-lWG}n~ zh~@Lyj~{w}G?j>3;>GxtW0ZOUUHUuI?a|&^tjy+$MZLfiUj;foXhlP16&hNgLfe13 z15+hdcv_T{ZT6)Ktbh(o(kZZ+8!v|XF@p8S(EWN=Cj!IUS3CPJT3nXbro7Vq+XjS zqb$8jtVZE@gaTWcik{K$pen&)Y%UuX7idG*0t{VKt$Zh`%O&5#xb0~%iPz&4V^@Ed zJ3I-$=AX;nJP7W{q5MA?x6nATmVmp>M!6Pi)d44*@KA8hbXldFGpML9yS8~L<+QK} zYMUFlXAf$eS=+pjw;sVv$WpsobomiD-g*p;*eA)jTI#ip8@4Ic!CQ~WnVbeKxQVb+ zxIw*~45%OTh72`)1zj`Jvt{ZRnl4QFLbYje4g^}#p}GXNGU9#`iPAAO_CKVsD$0`J zUBsx?n=Dw~kLY%@cg8*@x9~ZOG@I+q9hm%yr>&coY;?0~-t&9zG`@U=pp>1&^i{;i znoERhLX`1Tsc-}$vQm$8YYH;*-mbMGZxY5@2BgSB@e8pfwb7OT@p_lph5bV}d=T3W zw>pE9PkLU4-enI;?wgYvUvC}U^eXsYdMvn=*e+GmoF|$2(IN~{lG%qeZl@|DU;n%$ zUz(bs3nM3m+NAbacvt?Xjd$Tn?&A%&(JWqiRq`C{8e!8sEsJS%3GMw0oks1BQai!x zJ$|s{L4efst`l*U?@MaWwNh6}ULD}p9I)mlE*^aYGknK5_Vlz2GCs)aXvC)^QFByQ zZ}AcuAeRxALr8X|x`y0ogldi}YIHUtK@_@P%0vK@m7-HNu|2k=K=g)KPYEm$>kj7u zReYsgrcC}K>Ck6FrsIi}IAPuGe}6is5VyUSBWFRCwyw6B98C9mo?YZRmwt2v*bFRk zS7|{AK)+0%vgS{vhm9+_iumI^xgznHCRbz~1x;7Gn#_#gwd368IRE>f`S&FR#)Z!x z9Xjmaml#$}H@h0cV@KZj&pxSlXt=K+hj5y7#kr0Shr=n0%&6jzcs}{^S7Jvpn&axa zMn+X;-I*)#E;)8TgfxH_{iw=otPpLbFoV-gSYZEi7PmEC>@zHzf=!4fEKaLQSQ{8j zIIR+Yzw3+Vp?w}WnXbcGk^us=92-9J73@o%buHr4nl9-~x7x(08*Rs+=P>;&4!*wQ zrA#&M7inBa{<|Bl1DQC=4Q7g*mwB$<;+=%U3XV0KEmqLA61;8(gvV@#4#FF{vO>S$ zX0$h{uFxZd;ea<<=Su=L?#_BQCOS&Qkms^v0~6pPwlms3Jr6i( zl4+cwFphLePt5LUzjY|B(=H}hv^TH&EQaTAaRPSBB6$<iSFeq&AB3Am8EvrZ~_7GsMp%$kA&DhQLH*c)tUV=AR1VV7!bj$60{tA6Y@x9 z>QTvY^h(g1PuGEW>_4RghuUgR2;fC^I8}OWsJrMytk;J~_)$F;b--X3-pgvPXIST03h!eZfYH;%bQBuN;q+o{1J%um5M#{u zk$!oGe!02Yhqn-IG$iYfIi@|ieN07VZ{&P9>refIdlua-KILlw+lVJ$ z0|_1M{Hz zuQZW6=D&;k!N-cU#8Jx|F;88{qu%yvQ!lHd%%0HMkS)ZI8~gHn=?zKLO4cxc#~8@v zrZMU!N#mUG^G)KEcbd6EwoATr&rdsM@Ac2WP;B&uoG+VrCZMYDZ2l`QG(YwwcRMc( zZb*<_M+2UK(d25&K;IFgx+dLoj&?^IDh%_AvN(To7Nt9Z8UtV|$^UXBoRs^nv&Oyl z2-6m+K<|@MW^DeTp=0&#N$tN%SAz{$a4D|&x|54=b6P9|<0724mV7hm_`&Yv3yZPG z*pm8S-5Pv+I+D%ZJg3TYs{QOJP8dw;V<7_bv?*_v&x5>uQ37gs-@aStXs;+%D`|+H z(PR385s4w=N|dtd(>aqvD0l=q`$oBlL}sZK^fZ2Q@ap?1Txlk(O;qypv{eg0KJR3! zn%}kYJ-7{AEnAQ`nY3>G8`8YFowN7C`2Ca{NjA7Np}b6>hvg-GpkHA@-dnDg!lB5{ zu*-0@O%G3$0a5fu1$xLt&!;BZ@( zw@^FxP5Br4TrzyRlV=r+BkHm${0MADVo~|L6YZ_Z*IaM*6ku{MoOi zb^6XGGOx{)=J(LVb`%qo#BL!xmNp-zt->UFNwL{ooVS9_QaU(L-mnLBXft3@M#!@} z?tA<>I}Jx!Ik1L?<6egF*2$DH~p}s{Pbz2OPU34$Hq&5 zglcy;ov1Z%IZ+I`aLg(R|4lIVGbK3Bm(tWVAGF;u!<}t7p&l+|V^2}X(U*0zD%BiF z97F*yTc{O^`MyNKD$H^`Jtx|_>nNmHuN12vF)L;tI#L zmUA-YPGysZc8(iiIi2YJ^B8kO1;=52ie}$f9fmueRngNxB#f8Oy!k`w9;HR)SWC*bHNoew@5LLw_&`E^JJUHN~9+Wv)8 z{t;^X^MxbS_A2ga4yf(*Bh*$CiL^LM3;7iqi;}wYl{hNv>?ujVg;5LY3q z7!}{Zn#{`>Va;%jCanIWvrbtpppI3X1y)(BMW4M|oF6Oy$D9{_P!KKuJmu$J%HN~U zWjUeDe|h7(0JfKK4k)WZpY$JMgW!T$)C&J%xxK-nK4^btd6k+QSN3V)mGW60zQZ5t z^beifav|<`>t_NQm)f4Pq@}}mqHQ()9auX01x-sn}OoA>f| z<8dh*UaswR#p4`ZeWB??|BJkLkB_Rl9=<1;NivYY1WgbT1qCk^s%QWW0g2!sUV_00 z0Ts|H=`?DKa1LloAn9b3@i^9AZ0S>NwW7tx8*NqM1xSE~fXH18LTyB9cburBq)AZB zJm0nVOcK!k+V_1w&*%BQ@B2qG`<$~cYwx}GT5GSp*4p!|xxo&c3h4rd3RLOWUkc=b9Cf4e7Ftti=OfgABsV<6Zn@fgyVEM!sj>( zA>W{cG#FrS>*8OEOTEH5`Hyrl{%J4b*qCQ2RWjcxjakkX!l5PI{OTD z)z}2VH8SZ3`DT6AH;Ww zM>?CAp2;je&`Z`~924kuTk;M>-Q^=^5z%pK!I5Q$0HWZ?@&QY79s5}2Wuuq$iMoYp zR$iKKwRgHI$6jQo#V_{|OJ%ch!_n~w>kErZuUD_Iy%!N$0`T>eVW)9$?^`o5y~Jsl zQ3@~DaUj-fM!7>yk0*MZlYDFTucJeiXNyb_I)F)nbu#KEZ&jUZ98$=6>*LG#AwW60 z?~{l}sRMp+77(84;h6Q>s=5@iaSr5LbJW>d*PL6|l$qI(S;YO?-nv7Xq3!Ib!r27q z@H)jFXTN)QU375f9`}~2&2mN^+7~YNguT}LRG7JA?wqpl6z_38IDxL_pfSs4es;3* z#MqpbcQOlm)+{mIg8TL(>SV-@Ime!Qs_ZG1c`A}0XY-TT2K)Dg-M;nQe=!Mb^+q9X zGt|__W!TV*j1v$Hd)J{J83k}3pwOZ@W}z+Wzi{xszQ zf7Vw#ZBDr~L++fDU^B;(<_Ag4H@~Gbrsu`;KE})(ud#~UEB$g4V0&ZxyTL{m7yjjh z&BcI&_?UAg-v)Gl8^pH(30JcLQbAtQ)hxTq)og&cfZ;?oK)aeXg6RXOB!}GDyP~`H zYge-axSHXbMy^5RzJex_u4Xw2SF>#6YBm4|vq9R`Ogo(w-J9_>Z?o&{4TQwTVl+BvYL?UxR?VhHjm)4jrh=8d`N!)OJ??g6&pj!B+d^ zKB6CwX7!Fq8Qg67O>NsaM1E6bkkhNS3e>(e?DE&i zzzyX-4t>~Toi<+jXL0|J-Je{Hd9*LTjCoW_G3E>^Z>?+wmAAHh<+c9G3%MXw3^IA? zA>>;7PG`r^BJa=*nRUeP;BK+%=jkpgjSj>Q?O~F&|E?@Lk%eab|9&U_cfr2_xnFFD z4r9;kt6nO-|Hojrm(6gC?**}=ZRNUccX{?4yn_XIw|hwlGH>^gkO|nHN zl}xX~Fwg698rBO+4BcU!&o~ZYJO}Yxs7HPWBfmW{^4k+5zg&K-=(X3jD0cZMQRm7%q%)W z)YOi=+UniXm6>Irv|fi^N$Hh|4ekcrn}$?LNp~7jwB9_lPu78SXe)ggmgvjCL|-xs zcG%f{bXR)y(LG7ctRK23_z^DYRUhddWbq*DLps3f75Jq>z;DPaie>s^?R5+|2f*^O z@&tB=g*RNsw`NN35@$bnSqWn*YH_Bdhl`q=DXG3SuBs1&n^i^Q6I`ykrtGROQ)=0z za2(H(oqaAR!?pQ0hnnm@g&1BgY31DLDG&RN+DJvyv(J{NK0CAd5uStdysjA$yD3t! z_o=6zaz>wrK6xry8>jBqob0&uZQQ%64h;rCITZA_-kWxHaBI`V$dXV~`C#sOAHKD4 zPZN9TP=>EMLO;M8g1+uK)V@1(L#Qd579^v~_G%!GM8EIU|GIqetf9Mf%~c;tz4xX~ z4Q_9mEPfWlw{t`Ko_!+`B7Wgc7uagOoi@XQI1l6%Z==l)GmM?86)KYL7fCdrK>2 z(i6kzwbL-XkMzD^e?_0fC_UTU2QpC5==+I}-{JmP#NO*PFn~{)!FdWV*N!wZI!`4A zMow^LP%%7qw(j`{U=0DRZ%see{*LJ1Z+@)F$#b?3-63}FTlahdq?vYWI1l~uo)6=A z996Ia?ban6mzSsFVu0@c#^Kuwb}nHg!qy`)ptnELCnl(>K_Bwe=${M|J77^U3t5QZ||C_$hiImoeMk#U&uUYKiY6U%I4@`rtFNa z>B7kAb2D3F7azXJ*)iqb_vGS0Jw@D5|4mcm^clgVT%N?o9wYvLWWdWg|DV}>BdAtz z`Vhey4W948=NZf-)>IWW@l^TW1>`3ToTGp?GDxmCYyJ)3Jt5Ck^O}%2m*EnaSnmJD z@NO4;{;&r=?_-9YC6Ok$;C6$}pYkS?%M#^fh4_*0`9~6^D_WQA{LfIDb5=oDJ+6B` zz*en&|7lx9+HqB8?sxi(1nkV&O~Ipub{_<7XZqprEDwjny@N-G*aY&<3ZFXn1gZP~ z2q7&5&j0@r(sA@!2&we`|7C>qznZZBZxGTen3VsPg!EFunmz*Fe{Vu+;Jg+%dlFK4 z{_jjkWn903kWS(C|3yMNpr=|Gb8?BMpH;fqucj6vosJB1wc32I@4m@!MbSZ-)<4%g?oR+zZ(D=@gx=#kXw`6W-uYvzE*W^0yqDpaZsrz(r=9|v!n7hrr zJM*2$xHAemsTQru6fe@nmm*!Xh?FPxtS#Y7t$|tyOv)4oCZY0~@3B0Wli!)*ai+IM z2KD1`c|V2UUFhcarBEBH9y~J(MKnwyNIz{yvL-n1)O`eir)7TO6dULwgHA${%zQ8G z4d3Gl&qV2RQe;%Wg3s7AQQ7BRkOV7Q=(#m0a$QQ^?t&eFax{gOc25lLMpmvJyW_r0ZV5D=8hwq|oa<;qM7}fB|$fyaKpUI#~9~jiFp*LkBrg*KLWtjJ%h3X9_ zHbt%Vz^|n0Y`q=J?(deH?K9D*wcr?>@|Hn*Nd|2dr zquePEG*mWURC(09I2|ec>gZp``iI$RL4&`s%ph)_LQ;Z4Kkb$-j)7J7Ido^$2bz-1~Fi<$f zGEi7_$IHFVHGB&et%#Rr%xV^|gYpc0Uv=1iJnGhfT1Q7*G|s!I=IZEq$x0VJJ9cAq zty}_VU1{o#Tpg!TYXi0a$>E}&Rcz-T4rlwSSE3dvA22iD|I#8ZUvE!a;_mdfU&kPX z{Vz$6efK>h>XcdKy=Pr57Z-#&*@s8h(R|*P=&AI4$Q@e<0~(85d(qCSqs7=eFY9H- z?}KRCtJ8P}qoSGP@_j$S`z~%ieb#&(>3Gpgv~ykM1eets@oz%g`I6XVM&x8{(tE+g<9jw10*pv~f z4cjmIs_*9g9kp5RdZ)3hszA2L4=>1ZjzlVIpnCD5*BoG-UBg@7ni5vwcDVzRA=^vn zD!JolgVsmq)eYS-^c}6vE~`2kUl!rYj5Tpz@C$IZtm=#SG9Ox5Nyfig4*@aB^%EAe zOAY(6!Qa&U4|&I(^najP@qSY%M|f_I^k`-Ms_r#{Ybm%GAqjuG}c zo*wkCOby=kcxt@p(O|`+u4S(~vmc&3IqqK(FIpuSx9p*+&8fB0A*{sP{Rb&tX@A?X z8=a!PyXNhM<~jS^ZIHa8g9(CX)bHdE+l^dAy=*JVWkp?YD<(QS+V}g`eCF5aSYI#~Dq;9XI+7DapO4h`PboVj%>-t*jfZSTsrS6gXi!J@q>L~+^c zs$7g>VXv4t;K_Z|Rf*a)FIcfJZl52Vw~s!)8@Epj&U=@cs(25R;9g~${qMOduZ;FB zi~HY;asl?cLO1p?k@00<#==3+=Y4`>oZyCw>VYYO`MY$aJ7p+;zvu5){7vSsTchjvhrvxyxDHbFgO9hdoy&7@ z6ZTor;oQoaFtqZ$;$new+WNIK&ucj}m20_rn!p8HtZw$jY097(r4c-!PfD$yQE(jdM%q-fcAZCx`aYUe%$H*j4dH-TH^*$M&th_d{JTor4*PpIO7%0-B=LcH4 z^PctfyytY~#n7$!3;^}5+3M{7-j?hlw2M(MHyo(}5znMMYKtP(-mH(`XsPSS_N{5~ zt>2GakXQSvP}KGV!RA{E8q31dKMLC)hAR%0ao!&;TH&jHK~vaQg}&-cyhN>5Ew}Ur z{VQ&8-VDBF1^I`n)`$I%It{)x z_x5(Cf5dU}j&(A*d0U6>8`>H6*U07NSoiol&>2)R-Ua(2o>NfA`+^eEwv0`CTxF@r&!}dqbyQL(K(!-qT zj~2F1OcK-uLQpS;po;s`VLYIkpCMxerHH*UT=B+cf32pe(^oont$R1(sJ(jW%*YM7 zP2uUQs42L?i$n#zb?oPSYsNrXiL>o{abIxW!>-7vQwrLX!so-(3dwoc8UaTo= zM$o?s%K99Xb#<^}wQE_;l~>07E8|5NT-3T~eA)6eLs)Btu!_6A5LOI?Dq6wiUGri> zPvgkI(>p{y-<`P+dmVZSvjhI=9YR=ZmtCKHSuw@g+5WC?4SeAZ_l7oqOq13cS$C$w zyE=rRekv8l>OLI2>!Zwl@uD|$T`_w{u%ZUlH`FyI)HSB5>-3nIb3t9dNK)6&HFf=* zuC{6F+9uTXiv)EgSD~h^UkG(=6YBao&Zo~gk) zaC?RxNg~-ERG?x1sxIWy99%TgWtcn$k4VDsZ)K98oEL;^7U{UGV5faLtUHNlenB+9 zMl}CN&AW7tn4Lq8MvaQS^Zk1z0LxBmZE)UmMEbx$E`e6h^+2oZ;o%yyz`W^93n*7( za4ItFB-M}xW55=8*BBh0!8Csp+Uv0Y5ccGmN5g(Ft>U9-lg6~3SVkFBl{k$eIXOiy zm4$m-7yH(4Nh!+7CzQguWx=xq#edEbt&GL zn69e2PqNFsRiAWD0GYt?n~1OA5dNv?@UD4H*gCVQF$xEt2fJ%C>#iX(lpGPg8)~b# zxc$AV4_V``rRRx5fGKmAs%w&UOPMASj#OsdR4A2{YFgHac~oJjHgcmUJR6evlJ6lg z(&QpOtYh^#5_I}0nsq9gq;a;@S*}d3|5Q#aK~$W&$f#aG70F_Yy-BwG4K$S59vPQf z&;c%fu~2NV*-h88o9;;NrZGiFdF5}2pXshA428?_KiIXU-gQu9q{@PkcG@*ayeJ!_ z;9F(EA<1bhlxf`1xwZ08pUMqGmKUcI(3zNBuI6S8(X^;k?hPo3!O`B@3FXO8lOtJc z2Jt)#ttt6w!>038h#imb1x~>(kh4#h;~EbiskM5KKV5#@d9~Oe=qu%eXG?xLf?6;6 z(buyZj7QOQm>23D8Zl??k%{q}HYQD!ZXbq-@&FM8OPt2KCNGv2-u4ab>0DOs1HHLF z7E_-c)myA-=A|9YY%Fy))_ssYBeT)zyFd($0r9}RvM7Zax)dG)~!9`GM4 zCwtJ@+SqB2{G{04st>8U(%v*@?oGh%OgzS}dkj`kpN{>&Iu}c*e<05CDwkIWd6mwq zz0qIJnQLI4*h#sKk%?cKpJVc4EH&b156g8K0d!mZ?DKNE(^<*)TJxbV@ze%5rOx;v zd_p-C&STOd{%pLrbP&jsu(gw@Chvr5mt26=aH=&l-#%s!4G!(f)lpoIO`%t3;Vx0U zy7dvnMRJ(CA*)C5Vu=GBJjYe;nJLi-iZNIcqn~naGshE8+NTYCYVvUrd!#Ph_e3}5 z7P02ynw?UNlf>EATZ}_xIq{lCP}J$DiZ2lv5&vuI^p@joIzYcK_;Se|oFaoe5}-qT zM8tgXOE5w%abI#daeo9b%M(5$nhEC3ZpALMFw6zFa>2G&T@LE#oc*y8v-&yd`0mT1 zJb3}e>e=D+Ir+q6;Kuc1@!a#I!Oa}K9Or*+pq^3cs}3^XQpr+h^e|kQO3G8#cU0pg zECaBwV52q1Iei50rP=r%Bo3V4uE7XOGMF1}$MY#mK3REa=3afP^R3SO-u8w0UVC`C zXIA8%L6HR*_#F9zD_FrxtvySjBAtXbl@ ziee1H*%EN+jesk}n5k1=)xtl9Q16}A#?r!$w5ndRL#8;#&3kK(`c~uds=wLsv%S>R zy;ZAf83O4{}$)PNl z%|~LA>k+N>ti5WEHSsF@JHao~?2Da<;>+*`YuHs*|Enxdc~10hjPYAj7Koh&hEvh8 z@|e1ywcH#16!l(7@C(o4i*aG+myn}tr8x1{OrkddqV<~_ZAf7uz2>sQrpp%63x8b^3lY(ESE?F6!;4H%YGE$sc=6DMWMWfRuHzx)=rZ41* z*{{8iT;WO;T*_95sF$gM49@-<)uHjrbjXWKxjc5dpq`olW`IKH$_t9mP!cl`zRtV8 z!njyzy!?oflC^_1DCFq)NbEU1@PwUG{^-59CfPsKuCFOG0wY=30mS7>^!1n|%LUnZ z9^(c$6I;g?2vKV>H$^IPNqo9ouF{r-(}8a|$%4+g_J!r{_^5wY{+RtOnN?v*s(8`@ zpm%bxbI^kK5&Ip_Ke1s*DSJ}d7DRtS5`ydF-##AKITgmuc?))+NKVAHNvkP!WmH0O z$7*vk+*Wg>%aOYYCy;kQZD0q2eLUYW6?W`Tjj^Y}1AzLbo-BBTZBxF7@2PLMjrHrWi(jQK%le(zpf!vfN0e zbW#V)K#k$(yu`D}G1dEqjM)9~=4}dVL zX7OC}t!x;9z9jX^vGQB z*3WOxhTF;fpEP`7SHp7r)ZM@8A#{kyXJD4JP@sEeCe>FhD%KO)yZ=9IZ$0@;NB>SG zU+bt~iW;TYtX10G(G^JkiH=@=Lc@Lk!-gj(8n)-x^kTxm0GUbaViMA*^(`|;J&_3Rj3G3_WT(75EQmd)fh zE{jriKEzwcbD^4li$k|3+kPmS^uiE^Ha!xN>mI{6F@cpvHm&5?oW<*~kPM&MsAL8;%Adxmm(i2>`G z9hPXg!%Ur3=6NYuoA2gdTbeMk4C_uoE$d1KOs2hy1tC(t@lV~^YyRy-e8H$VvF6jL zm6#6M#Q|Y+-9-OKd}acoYx3fV9o8)g0+7E}*qAlOFfzlM4ZMNdyXBh8bm5#*LiZV7 zudePES$a)+0OaniNw`0xr@%eIxymc2R$HB` zJSaE?{*i3q@Tt~YI9fE|>-*;cf1K{_e*!+Ug(e~3X-LzTlBiGr(rL7Y+no4}!28qO zDct|_GawA7%dI~~4;H^jE#p;BS5TBDG7<&vShLIW$67O;4UzeYQO1>_ZFv2z4r`Kc z&DE)B!pee2@9|YvGBBiOT0e}Q0d`CzJ#xXR{Lf!Z=-e%YWc0nb-F@_qw%$|j{aduW zEI4lBaO*ZR0UnlEq6Aw?O2ELRMD!e+;$E zTt2&P`RsNRx+aYav1Pltky9JY)N#Ng@D}vxPP13V{W~U+xN@_2hNd|Fm^jci)gQmP zJaO|EXq?9q&)vj=1uw*dZe-x_bD|~59ig`-R*T-TL?W%A&b0VWawRS@Ao{Mgqf39W z_-hTxWkB6sEiF%<8_=>qNR%O>qS?ANp`wu&Xkz3?YhrRmPa}eXsNsTZJ9wXJUlY{Y z5YOT?%<-dlnx{1T+~8W#+HfRYp2`J~`XPk}ck~qVkZKpAkm;STOWUK$)8^s>tm{nS zB41OdeR`r6QPD_sT16v?8@YmtSgXgJ&3SLlRX+opdwO{$RI(L0(UFdSP-IlWu21kp zU4(OhXe0-)7~-Q=tBXDp>M81glPc08b5f%2$kKSh-xh6Sw7G+Phy)fW)Vj4nW&>M3 zC~zqYNPUc8Cieq2a)ayhGEIB+xLA3?j`YPBpmA!vi2l#pi~jr=(XZ0>rgkpFUMI?K zQft=}>U4b4h3u;jsji3r8j4ptOa6IrMjw4GdU&>@G$0Prf(U&vRw8)nBXyB$d&Ql) z6Nouo8wo~&BRV3;#kJA2I^`Lbay)a95jY`R2HDzn{+o=l-DgW1O|MgK_}%f9o`}QR z`8pu$KR)KqbmA6(ijN_-omm-IIq3*(8h!B{4~%0Z!R~7{c01$u@=NF)!fht<6pMP? z$}0Di7{x>}K{!1`k&nMK7d)8rfc8rbZxm+jNle$JH7Ctwy7q44Kx7NHIKiSueLoE5 zJ@`M#FjAm4wAdLf3fuTT4%-+@u%3kj205;B7x_>t zXAr=7pVOhQlHvPpnm$u#se!^YAHU8XaMz(KulobXYi5;;ZN3X0zF zg>rRkI}7|2>Lux2nLB;ea^B8pO)DmXqHag@$5K^O!A4*8`+Vi~wz$2M@4AVD@_q~N zouZx2^lb%iF7_IS%0<04`%&02neOoPtnlnjXU=Yjy2;{j3CC$g4H(>PU)*;=8Uacc zW(?mG=G2H7C|RP9w!JjcAkpcXoYA7C764k9o!fhSI%tMQVUbcKa)aH1BK>Lma)_Zb zT69M7eG;Bq;8qgG!4)hzhNPNb z(IRIC5LT%xze35AGEXMHS$;*6l?I6Mefbp!B?UxsO^zfFOR`ZX8-dfmSKWq~hgeke z^33A`tcs@7`OG0?SSsdo5(CG?D)J0Z-_?PLkU%vp)ph56?2~_&7YYRO-&dzqUT~20z!;ht~45p+2;ZpGWFL>-qU)bEuY|JO1)NUDUOS-0 z$1KBe=uTb`F0lDW;8Xd?xuvvmo^E3Y^aGXqVVZ|< zC|k-2$qR?FNYbT`c--+1myxPvV5_{(~B zJ3m4Ib##-eWixBE=hlG@2)mcimL-Fo^nxj^6+~C@Mm$r9Ttg`xG%{V*DM7L1`m^M& zr`%KXoWtU%Ml`~yIyf&zln0j40Vm&C+=@K|E2`kVC7IJ8TdMWgS8)PO{<`fe3GH4y z{76Z}v$$9tDgja8?p)6yG>P$4X9{$Qja9OVGo^{6;<;U0fU#~&S|QFC!NUC6Y(WUu zG{3b({phjIctQQr-aHnz4o8s8b}n%yw-z)m^#tc-1Du^rAtD7Ac}vx80?;W0;7Lz_ zBxequImPO_uY>du%_q06<=Hhq6q4`|vXZkOr;CMNt=@i1z{i!c0E@U7SDhljI&Z2? zPo|(sB;;f3mtSEK3y)O57Fm*^=SDvK?Vn$9fk({Ver4X4^3)qKxjE!}xUPMFCQ;eW zVEBcg&BA)k`i)$o8m_=*C1!DOqpP21s&7$`P(9+1jG+*7Tg^~KA`%E;ze1RV-4d}T zrZw9VEKxT@cCoo-hnqFOgn9TA7dUJ3`*mKRMv_-J$aeK0 zxxSHKs1;!S*DhL>19cdJS<)20`z!FTZ~Shqf00w)RYu@Er995JJxBPK&awZLR-{6w zE^*e@vkN9Bg6wH1E*G>M3)>yU(N^DSW~Mp{I$xg&f3X&ps(ZMGr=Xr-$*tBrIbK1_ za4~sc?o(v${7jkm{P~ zR&_qE2ADz2vU8lXE!RuExbb=I0RY3P- zWkML#BnF+hQL2F7b#gXFq(Y_6W(C21o$(DI7$We zU9T6@w-cHC0WJ5|d?8&pLf=3vhcm4F`K|T-&t3KYBQ6^Yk}Rp}G~6LB zu5HezA6g70kL{ioJP+(#hecSl@*^6 zGGzJe&w<30+gRKw98`*Ae62|PXWd1PR91ZMvWhAzj&Sx=ErcUk#V;V(OfNfSIrrr( zlwi>JgR^jDH?SA0IpzUZ^enZKDiaVzzblntlWcH`g7jk*d8tfnTxh~h@Ntjt!mleFXXf*MQFQYp4N2n0|t7R9i%30Tm_rWG#_xtO4KbZjIZzgnl znlSWK9ujbMUWeWoCYJbER`72=1}}sq2k&WhS?5KHI#BQ5x>+x~EDS|-T-K>VCr(`E zT}uQUdh9-M=tb_d)eD@dDcByiTUh0Ge=`VPb-pZ!dbOxllQ=b$c8!&ak(e07gq2ET z@XNS$(~KFi=1Y|408K+Btg}FHHT~rTiD+TrGCPvv(7TcY%0t|7&D+V$6|YQ7jD?_n z#(K!Njl`}lcnN)|#$aeM(LC#gYJJu`ll~=_gYL@Vb##TRJde>8f-UU`SHy7jvj^n0 zCA)Ler@A|TpwCcjbwdjx58&vyo2BJ_b^L5L(YcT79j7(T8NGs(;8-b^0f4p-x^|ub zSOrXSh&;<8P&AsK?$5n&wChvecMitEdhbiXbqeM_N=4nT(9oT%d)rc?o(2Wb#<LyOOsH<jh|ph`{q^90>`ZIE@~ z3-y=!)8P^^1lpS+-3;;-d2PwgUk5Ie)}5bZ(z^4PkWO@d1j%IQhmhv{uv5565py0Z zR(B&%p|*@ZZ>Gpov!#qFn)3#d0}(!KH0U$cxU~$H$0bvfBUB7V6G(#~%Zs-VM+ihA zOj@1734;m#Ti99K+ko_?*Nsdn4J>A%3l0fC(@OQTK}TL_K!;IVatRI4HLKg{C1kOy zFDV8<=c-m`w6Mv@^p@+nn+y7Y!UG*mZYzFn8hmU9DHjDryh^V*uljODng*h75v?y2}A-AznI7&;(R zu}-E!5AfyGSWKYvT9F|Q$Pk-?jzy;$aDMku0_Xvf5lxBHVLsf)2jKkL48(ZP41|+2 za0TVMW*}({T0|H$6paM3K}}+g3Q&TLz8xS+i;E-Ek6r(|3+9CSX1%K)o>XJ;=!Nc2 zvA=`#B=N4A2uQRG?>c<BPL=v2@8oL zX*%ijamtip9v5@)rY$P78Ervo&Teq)Q5XFKVHNSITegC6JRqcK5rQ8~SMyFk^j2R6 z%avTM#0%CEN48Espwl@zE#aPIod*WHQe7nh*9!@IEn}#KIc7#)yjck|7T#6@j!Y{F zOdtj?igic_BK|ETGHiS-MyXs{5;%x-s_8YEpB2{8l{rg& z#NkOXc{cUPbAR9s(U(BdmI-S4bGi%Us8^)``mw;uWC<>HA_D*qOn@*z{UT8-;kZo8 zD3XuQGf2%(mLP$097v}FWRL`(TMYHeQa@Em z>XofRCauB0m$ar{1Jw6PnpHbUeTOvX7vqz~uQx>?e%WfWN$cWcNhgY5NHSS`C}}AF z4-mqW##!Y#HHQQZey~P!Bv54(l;KRQntQk}qqy-}lGa4UoI)+^*ds*T*kvZ@a)<9~|Efub3n9huJy<>6WPDsrbh{u6>B z9TtsuLLDCtS$wg_^vLyVP)H`oRN?TX2S7KZmhgCSt$Lr<8G}{XcHYGYdX$~bXC7UN z!c&hgJb}jtHb(0rApr+1lmH1k-Uer?u1zS|FLU&3RT z&FJ8JVWZ1aag@H9AyE_Hg=Ou!djv$A04;D0!9BmEJcCpUMkF$DMzll`XZ3}Hi^cPw zQ<_KcUiA1T$2)SF9jYJ6p4hO3cCsZYR+k)xE^~L>7clIS8G!(_28EXX6kn$WgzCTX zDH5tQy_HXSZN-!0xy4Xl8a0FOt1g0Q5$nqrx`M>8%e;OL7@(36{&xnF`Iv zpX;`)iZW+qDq-A2*r~K*esIQg$coF4y#oN5qhyen@~F;Wj+#P@oEw1usV}cLP7$k!n)8b za3&$f#p~I?R%+I27YYT}mc%-rusF_r%(vkQVonAj=JX85H}T061%N3OO}@dgs#*2` zNm1=L(z{;Ly91C@Sb%zym5S&OfpOAViOH_T)RIGVZKG-+GhNgCRmj-(FOofr6|+I} z7OU40mo=I9swtGwJXty>!m_rY?lPTTsX36aD6|IAUuvMt;f3>Ae2m12_NrOjYwYwz z7)uKiUDq9+?7>H)>!_oANKI6_F!_(dC}OIQkR4<%?9&)e%>i1Zme&ojO_32-vzTKz zKyFfz5>t=AFXXBD1ua5A+%H2fj+>4LUCF`n-b5r<6Q*}+0*bWhOld85&n?*Dd+^7M zacE0L?|_)9s8jcUg}D$%)^o}Td0T#SsFut(SwqNA1eGPYm105>{9>%#H+VF430FZ* z@st(3$v&{Sl^FG}`__1Qv@gPycgYK=t5VAlFNPu+NWXs5EFdq*DJ<5H5Ax{91!C?i z@9k%>Xs=rR-dFMFtC?J#rTyiA*d8jFhZ~T zP19G{G%g)AmAr6J@?JVYciN*Lnwkv@5#BhTtTMnV7vEL}Dk5QX4iY?q);Yft?QM&7 zmE@3$FJn?@gsXRjOG?#y)n^e==?c0$_%TlO8dGcZy0r3=0QII7_UZN-Uci#~s_lPG zvgDZh_-|juSEs1_)fyd>Mc(0UGWX-;ZXQ4flgNJBcfgcEFB(kwfHfzWBHek1G^O-j zT!0jexwu7d6o+Xhr*r*?x{W{(Q$HY${^oTmO9}*cYkC>oWm?OsxlLMo;RP+n%3_p; zzHb_0xKVbRG~x<_(AuP1o0#0s!vH9ShCayq>-O46j(4R z5@pxlQGhWmuNqN^`ul#OQGQkV1u`KAKyTrEzZU)8RjtDtrpfkMzI>l3wY1@J@Z6o> zN~^^x3c>H1IjQ^r7{nG@@J?^^Zxg%|=R?V*&Nn^z4l}9_{z-VDy)Um#Pq6575?kd- z$-?KDPU#UDXwtf~eNCEC^Qu(R$CqTQ$Fx>HGA# z#3nW(<|S&%LoSq{B6&8d5d}J(DhHa|)u;n9NQAsL8UMhA^iY_|AW~o(Jb$*x#=ahzUW@6o1bS&y-aDN1kZ*uOU7b#^an(KSp$%E?hK($ zjcOJV4di$H81`}Ke!PL@iB|7Tv|5`#88^IIPHQU-g1Tysh3H`)dZ;yY8V##kHJV%y zxO6o>1F7nnQ4)M#f6BaBcj+>%-~^-Ae@8!`T0Uw{66zZdYUcMeRRVQj#f#M8R`YZn z>nd?Nh`t@@0Iq0r_e{aNX2eHg?)4dax#KkHo|?Tf1$RCR{r?QUs~783kD4=vIYEue zs5dj}>JpkW1}khHwdx^X7m@r`KuWIabBpkPftYV*)jQ5-$#QN*>M# zk2CjG@k|aq?OuEuz^CX3Rq+%zFEV+NJx69Qy1Oc#oswB;_X;%pQNNgux1#;Mz~elj za#XXaD-e)RDQf-2f}L`igkDn*V|4=i@dgU{ToN5LUc=EVkgan^xtuc9&JL^=EODzK zWoONbW(St@EU50GVh*m&FvZl#&(gEa>LG?CsqE$?>}DCJhqz3OK%0_N^_C^}Ju>w$ zKjKVP?PRUvIGkc}o%zg1MO$0nEdUsCL44dopb2A_R_Ll!5AFI2<1voI)(D-LJR*Fh zH$K(lO!gE^9u!_WVqyQ_(jhK8TTo)+0uBlI9WVo!mqIHtc!*ASxy0tO>ktja$!GD& zLpU!z!#X#Aj(5>nrRX=tt>Bu$&CW8fR3OqPx|&ZX70mH2IFxr_ZhVY-^*0!jFv-KVvq?owI$fmFznOGK56bm1TL@GS%X(6- z(HF^dW>bSMwXm_fw(pv>ZsjJE)~#P>(i*BsCJoJtsY^)f9i}FEZ_|(DS~{ICqW`6< zLh?*UKV_^iV>3^_kj-LHkMT}ZBvA`PWN*|Dca~~$G*)+ohAw7h$)bm|#Y7L#Sgh8V z5%JQG3}r=iKm8(;u6T*{Ga``956JuSg@VnR!^l$5wfFv1YPVmIQK?~ZlIg0VQY@{xk z8tm{aJtx?aW?x}=NV2BErlwSCa;bErsOaG1Gju{``DldqIWz?qbC>t-;yZ`l)@1mC zr#0%B#nUQ0yg~pO4j_VhpCg_dflsLqNlT5g2;MbmJxJS4+8v3wScYomeR`c?TE^Y> zUiE=dBO%8Mqt`js-OnP9Q59p)UIjonC%=m4WEG#ADs=5*`J8O^0+WW$DE=;VbBLrK{+kF`ht1dTn6K~il~4`(SnxUlEpUt_qxD|xC+VF+dZ2?h zX3fYRf!<~~E-gtS8a_dJ-hjSdb%BA5VQW%n2{d9kWA)O^XAzi>aA%%Xu`1KgVp`94 zw$3y3^8oYAikmFYEOR-(Lo1`t??nA#Fu@Das5u~*aORVU5^`;q`tY3uE<{?$r{-5D z>FG9g1H=v>^@Ob_TecGevc%gb^xX=f4fT45UwkJ8@+mv;IE)D(C6}M7x za?Hn(jioi8O}$KKKqByzs=#jEVw_SjDA2S$Zc((MNi0XH}`X z$>{bxA~i%ll;r@@<(39kP{E86bssfAuA6TLh4pyW?oH(BEz~YRu4rSs-;1Cgw|WN( zd2Z%*nYgOjB?Q&Z2{gSTkUV$3?r2)`N%HXn{n0ulaEt!<6a8`BXk?J)BaC5BTFaDO zV`zSwfcR6h-M%u|G0GPbdjZYNPYI}(rIpF1A`uUp;r(bN*|5g~m3%}jIghXOMXv+q z?Pe%}Cdy`b3mIPB`94xa1nuTy0*m?jr#mRyk#3!pc>X=l#R0Ewb?3Jv92WF@a`R|P z01-FDm%E~=v^f$%j2B*~!O*0VH3hlR zglDzKbAyWCo6Q}08~KIkY(qC35(J^mblUV3tFv!Ubj4fMsYQDz&Ng)y-7i(Y+soKI z1Yu0#6(5Q_WJ4fcCq6})`m+ywUPrSW(D zu?zcusy`a+PX}jD!2Tjb+%@+991S-F_I!=~pb1K?o(O&=HT6L7AF*zFAh^uK2?#DK z>WF_E8$#?v1&1&!xs_Z5Owo9Au4Y!-*t}IDmeVrPf&)9DTr<>xbEHeri^{)0Cpv-^ zvo4hl1M%K`f#vcY)V~I3qJ8z|5tH8WJQ2&77%;f ztpuV>)07jD?8~!zV9FRxIw{3^YfD%k0Y2gqBm|)!zwTxo_%b8+RWGKWp6J0+JEyfh}p{hX*ckO2haYrVo41eR^Crt zcD0%28Kwqx7t_xE;}mwWNyW)4!}QQLzf7wU|4Ifz+EPzGDvt=>=5b)Tt_W*c%slBD z0&tV+gqV;<%24WalV%eH|2JtQk(KHI`bY7XB6cG|rl=AlX+*YqZ(`%AHdS+lIiRb|JP}jGNKrKiZO$*EyTufE9XXz^QXE*vn za;TLU)QjLsjGrE}C0o14a=uCHF}~fTQwxq*z1h#U3>EV|nE}S(pqk8AIHZi|D={_# zV*h3)tno)MrXT)~TCXlsR_?h z@kJixp%BcitNQq&EEO{f8jr60aD0(hy=UH}C90lJ)yPbJbhRbw9$l-@%N#n61%phz zCfB{hI5*$rO4Kw)Jz^@#0Cw;Y{29SR(4>Gu#sVMcnk?C>N3t;toD^YDwFhXxcgfQ6tG=)t}M^+7{KDG?aM;fOP?zu!EZMtIl}9 zTC10XhpwWeWS#hcR#=Go-DcKoMRo{3sGl>EsJSH!>vEE^6CEl$(SEWMJyjOXH1&PX zAfsmQ@gDBkiR~q(Px)f4BvwS1w~DXpP$vWX+|xOEwzE_oW66PkM(nw9)IfUjcMbsODFs>%Y{ z)LS#|yG+q7gi$aXn6sfDW;5HOP9?1eKXO>p#l?mYMv6A6`yrit((CkdvraMMKkQ;V zGgEJ8fZ#7@Xf~t`vUJ9u(=iGm@NH5n!RH=z54%S{vY%{Gcakj$_bq}~_3Am`+bB4Z z*xWdO;k9gTPEWGvnKafV%sFOvbAL~ETdFFI=0A;QJ?e%1G97Gh`X{zG0Q@v{h&Xp` zf-$NlnQh6`EzZ5uzOm=_CRZei-*3|VH&<_OsAn5-28I7Nb7yQ^x&NYeNp8g{?`w!!H!-9s#&-|+{FjMbisII~k&>I=l$x!od?Gvjc-^^0F?$-C;)ys8_ z5cS8`Yb?Hu;t=(9BE&V{t;-9~*Hb!^DLsMb>phqGx^)f^=_EXV84vJ$YiOeA^E{hP z4KI))Y(t1C=-QMq~4xqmagu%|)}R7eXI5<#kxqPz)UhK3O8R`o!s*;|Wf z!hoU^r!8s_TIW$C*bG5Jq>exbm)b?g)`*gu_|d+=3Z6y(TulwpZz0p__Zfjg$@gKf zGu1vt#9lYjXkfKjf16L!jg_jgn;G|`oSPY~!FjUi{v?Yo`ZE?Cw`QX_Xi1P$^)&0$ zoSH8`?4j1UiEt< zush@K3*?W)^0{^c2eKTcHdTB#xqb!!ZdFB7LOpOzQpS}A)B&>7m~ME7NkfoVt0pju z_uz8ikMPXn6}I&24ss?G{`YS+dvuOY^S~XdNL*z4<~EI1E2kTh7pSpOT5AB95>Ar z;tymM+$xn^^S;se9k4iB;c6R(+qk<5zu+{%ThJO1%gViwap|A}VCd%$`}quO8Mzas@j( zJ4ZJ5$nUiqHC4acRHJFZY^o6-Pevr6)F|Pr?5c!`LP+mtk!WUW=I0JM7ZCGylo~BY zse#EAQj?y&zurjMj$Xd%U*O!Ac(+Br%VZ_-4qp;|G3yMi%eZ|LZ;k{%^aMX35Qmqr z3r*Z8uwL{OzUox|C#twlSCQ@u9bz#iJN#U+3L0Ku2*9bUW$F2pVamd3Rz0>+qp(Kv*7es5f z1fF9BVrLb+CqZUo%_D(8A1JOaDOS&4PL@j(Sv;)tznV|Qs-A6E^BFz9Z0xT1gE2wkUc4PBElh)Wg9OUmow4eQc zH#Q%6!w89&MYNFkaGyuqDLwz5=lRrMx>LU5SN`Pr1O96HGt5m(l*=)%SA8Fp{k!ID zN~CbLt+q@0-=x;_01yDwsQzryOhHWj!K69k(BjEOcWGEX1xLcO8`%4WXKxG7-Wi^~ z*H=A*IaN#eZ*07Ku($l2f5F~NLrtb=Q3yZ_1QM0&`_~HbMBb8urq>g8CG^ZL~P4GBT+yl<%Cnb2C));;3wc~jl z@j*a9!{dI(0x-Ns&*Z0fGF>7`ck`aprSE3=Z@oeIg=9p~?9YP;nwSjW57>gbut9H~ zny=-%++vLyk$o(>h1+#|&Jky$@S!GdBl4n?M>2wqczRZ` zdfZfCSlrv&bp@g8g}eG2j0Is9W3E2Lq<}UOrW^4~)Z6BT9NFp86~VrFzu$8U*`{e1<6-wj>)4_8k;;MNc6iY%1E< zDC%HaqCUriZzqm5hq)@8|0%YuM|TZ|6Uavc1F?FI6$hzFc#hIyi8+T7TeofG){6p2 ze!fokGD}0UXirxU4$=s$S?xd>mgrPO+LhJ-Vy6JX*iP*lvx72be=-LFeRb{I7_%qL z3tw`~ILtfKv?$gE9<}n@T`e->wAg~@VqgXE;{v-^4WtKJB0wnca>a8@MGm5MTSdaV)}9_kTH-wHxf&I8n$4Cf_BJFU!yc_=Z*S-Q^HeAhRElO4)_;0Gn(N%1(myw|gDv*&OLQ+QHQo)}T^{B2Ot4Gm! zrf9A%I`!=CqP2fX6fKlQin`Sl6;W>Ys|iug(3IWZ)sLE_4}3*;Mzfo~Cap<&D(NIi zZ+lrpITaW(?QcsKY=b)-2A8AplzzjG0 z8>!T#%FSD?lb&PJX%d~gPVWFUt9*r|u*$>9o#4cm468g`u`sz4?Au3>>2P8vSeo1k z>S}k9J<+iGi%DyMUNvbA=vMHRJP>r8IhR!8QEZ)4+4d-&1Ybn30vyJrRqJS z5lp9Sn({Ry*{0bS3fs&8FvB+Q(rgn|$%I$P)`3d*ON*VMIf9m*;lV7EwY_;FS=kw) z?TK4mY;!oD*%@lKd4!!|ItK%VqJA&PpCfE@jr8)FLy?{a;(OZW?clCe2PY1-B z->d+fGt3L&wSZHj=Aa@KDPca7j`n={n|g3o_v$$~GO1GkuciLkbUoSla$Y1qE+GxH zdGSCYbzE7h?xYTk;ThwC%~k-mVciz&a9eZGK3lWUK3je^@>XH6gUbrF!Bb7K+Dur< z=v7!P*gpMAv2!~7EDV2iup`6bnhLLdF(23!lKHzI^MPCC5|8gsq%262cs#XN3LO*c zS46Z}VCPodv3I<>W6=umX^52@DCTvks^hJw(3Kj3QbUI2+YGkZ>0}4II?rQ?l2a19 zo#@9@yIvn5|BXc?N5}*g;=O5-pUo-kO>dG?>ya(fBuy^ff zkj}!o0+L8k?`P;0hZ;s*|7SgYnod4rrcVg8dd#FXo&2duYdZOR27*SCbn>iQsY$C9 zVyc{M*h59j%yH?`9-F-RG^n|fZs+nI?Tqfx&bNBBGxqp)Uc05com8?3hbmgugG0#? zQO!%jp{`!4In=wdGwZ{8)KLnEQzxwHmvc4r^aOcDi;BdD3s*F$!)Do~N>G5iJ%XrU zsjs?>Wcbo96+S}!^KhQch*%ZZB{|P8m`}u*1n0?1avo_zmA;_oiP6%{x6*uaw8rrX zt}>1mgsZ%zqwnbXR3&P;(KDG7_E%BL={BwYC3-g|65VQA#d`_58U2}1OZCxtBE zCHP8WH+H*j5x$Z^o9Z|EYd2q+tiKw*qV;^56X?;qmC-Zf(9H@y6V_t}clTu3j!90Z zL*36qFrIGZvi`YCObm@L!Re4=`&*?%kFy9bx)?M0#sc6_9RrImkuMYirL^u4XG#{$ zd8;*-;YuWxS{F@Y=Y|rI-Bsu<=rW6RuBfWb1d1-{FN9o^)u`X>Wr@CIUZle%wf>at z#VFITaLH7a0p*p3t^N=aNN@2DO`%4EAk7jMl%rp9{wChL)W?6+P06uB zs>(elQH|*K1CMXlmBe7W5Z-2XFi6>6mj#)G^BG36$9!Bb_xI^atnHMewVGuCu-6(-ec9X zq$;k-rxmPdVpGBVpqp%T**!NE%+lbEdgu&4gjlM+=0#=_U^W#G8I~%Wz*N<5h#{;k z@iDR|IDxP(huH#;5Z0s5caa)Z{6CK)10UZ+t_0B>%_p42FHOqtlc_;dN=-jbIlkaa zJqjLs#!$g4f7ne0jrG4;{S4~0nLA|y(M+@o?sTTknziKoV5etk|6pgDJ%l4*q8w(a z7tjSdv74eMjFDIUmBZ_<^IoD&8{<28Qk;p?^xP=3oWmEURNnqr`|WA_ z<{5#IOo7&)z(qrAjp3PyI5Ij3ZkagFSE&PXNJlV=`S17yS_$K+ttzhHjyK%dcD6?AY zsOL)JK78Y~HTgt7(nBNTki4}f>(~Futu=&;>25%7=Q>@$u)X=G3l!D@VqO#zw>EOc z7*8pNmUqD|5wB-S__yrhvhZM8>vKIA&fW-pW&@GIHAoqi_C-%fH9Yr?k5;5gv_EQm z=Mz#ZulvPEOYf7fSp}u){X1|^ktj<&y8LyBtm7g2`JG9K@U+S?5M>*P8lc_X5alHy z@+BYwaxtsu0_mg#NXhparlR9vDvt;2223Ngybj_Uw=(d4&%!Jt2{Dc?!KFFGeF`oe zKuA)xP1xvU?%}aA$tk|N!Ln>(=&H)eOio#jE8#Uqfkj?OfXLm$1O5&qkbTYnp07X373dP=GEKpEK68p_c zpP??HUJfS6oa-eEW;Hv6x%#~~&7HF)sT!c4rIEX{iTQe&iIsm|{=GRwyuT_d|K3*X z)EPSbNa?iW9G$Iq!k0-5@nnbT>St;pbt5gjxJq{|gAKzPlJVRQI>f!-lLuHCP@gAx zCo^I9OW4b7huL3BQHgHF<#dTU@u%t#S@1GTWI+yJ65-F(aI!+jQ?N%@u>SfX%(Yn629q9i!eDRV`k<;dCcM%A{-g~!XMEUD87LqU6gHZyOPVj zlQ(Piq>JV*x-4eb?$1HmS z(fVGk#o79l_tkiwx_m_nm@x~w5>ByJmB_&(>>T}>&SLce72+e&(6BC}VGuKVpGLa! zg!^M(W&yMnn<4DhL>-WA7?oE&_#~oqZL~!9Qd}1P{*~ zqu91+42_5{=hI*zgC7ASuP2?v)X65DD)dFj4be1%QwL~-W%T@vltER{eq=dlf2FPz zoQDs(Sei?7>|yIf_ezBt&h<|u6@FRj&!pLih=?g$rgxvx?WVHM^Y0|>t3L@Xr&M1s3yMmmiS2P{1eEpgG|BX zV$OK+f)*?ljnowcpk~Pv^ocNcY4%xxN*X1XiI^eS97|?Bl}xedv&l+7V%9jF8bG7T zSMTsD+2=P&8)VK>>q&#m=O**L*dyPQJ@WmEv}`pG&F_JrqiSZ#ARqdH%$)T84-gD( zBA7oXr#nEzO}Y*B2Przp5UtZ7_I)t)Vc8_sRZQcWvoZP(;ihHqN+XjQ-}|2z)Ea`) zY`QabK)7fV0+G`zT-4zB!bRI~29k%J#72|fy-v{p`EA59kLc-6(IB4uD&ntYhfq6` z;gf*sat=v+qZnfw9VPLST)LPuJ;yo8nE*|3PUC(7MmJnBNCJ6Gn07q}0)tB9iwL*X zs<}$@lpfft5$(P2%NQgOO~sI>n8CsSi?ug_kFrP_|8r-6ff*$zDk3T>Dk^Ibi4g@Q zfUdw`lz_^y9yrDebp|hj;AHl790u9-Tou=IMa30eS+mL!6iE~WRMri`VpgMeoT%fm z5Coaux2m6KGJw1P_y764@^RA7^Yl?&U0q#WRbB03eBvKFFg}_z*UoPPniZFiVrwAN z<;h2dg@lI-5-)`nP!L~ARDV#A;^7V^o9=Eudt3=l^czY*yB{M!=Y#H3iYKjboK_eb z6>M{+_fY%d)L^6Y;w1axXpKlEF!Z)BdIX<#a&*-fzUUx23*mPJr}CmwWO`R41xvxd z%fsG`&F%VRAaaTAe9}zQ$H0}#Cd$d&(*AaF;6biGF~vBNphCS+D@0YueaZy+)LiPpLZNbLy>IsPr## z+IOm5fx(0hsP2(DfjCtPuWl7F++`zQFz~`-cNBz(_mR(So;&h<(G9#Ek7M|4==to{ zzKc4Ngd~po%di@_Vt@nD=0-SRRaJUW!-x{$Y346Oa&r=sIcU6wdv;9@Kn#1B z4@|FvNPcrVY=vcB>#+ww>oXMJ>rhC$6P~|Vu|s&3+BbV5Z>`8@Z^p=@o<|2cYi_%WvDcC z=9;jtLk?*DOmJ4?iKKIVwL9r)B%SB0{azFKL`qp*Fy1VF7Uo$;zDT-TxHCK_)Bw9p ze!(DKw@sPLYmsyp5hGGE4U+vLX|F!X97rAlG~HaUg*<3mK85C)H{ zvT%Y)I9vO<_x7#uwTL3QGp>vv{lIV)%Al|qotx4l4IYS`IPziU*v`;?E{*?< z=hnNVr3#6q=gK<7zu^Ns`6B6bct3zu9`p^3NnoB$qS^ZklUmsivrN7+0^`kwXQ@ka zw*^c+xz&lg&5WZux6KOD$X#ZBbq7@_HyAh0y`?z5Dcseih}H!{?GI`k3SaGf9^66N z#r>-6u^G1XO@1~74AbiH72IK+J7Y=&<1qmSP3b&_N#aGfhbHhmK49XABRWL8VD;@C z70A-di;&0kxI|>bV_9&_KvWF8G)fkV1C~Br7~cextN>u}e$y2z}!K`4{L2AiuaQi;e_Hrve8^U$jcvfrRc| zXaW4q#n~jZ2Y|j>>xYF;DM#rAx^opeU+vjc;zFq52?dQ7LeL1cgGNj3ZF3z6>7uwv zS}2ERL+J%drxFV#S7yE}?Gf|uYvwE5$-HsVe}R&)y+RinSlbCnj5HUx8fJ0pmC9)4 z3TYSwYyS11uC<1*sIk1_HtfNc0#k-!i&Y_+ zQfN&dtJ?CkKDlS!M{HiJyi6(n&5u?)UzEHxtI`{7#(bwzAxE>uW-LgGs?u%!9GkI| zlTp}>dD3R=oIEBgV;`H*o?l7nL^ikDjCsr_n8F>Z$aOpW;{(jq)n+J2%5d47x?2Qs zL*Ym4W<=j9ck>V**{jWabPr>Wq*hrFr6#Iv{~mSp4vJ(|QzzBzxI&pV+8$GY&59q8 zedH*ubB0@|6S!bKT774)nH<~kEhydni~T0+SF*I@o9sl)gZ3M`OT1b-zRAYM+|IWw zI5!KNqTl(NNjCej&BZt8^!^gV@FZj^z){YgiAGsdB*EQH* zUwTc6zLa31ye!a{ev?L!wL4819TH0ibNU0|q84aZFBltcPk5gkbWz51vzWK!?ss8K zZ91dcU`%c1VFJ(S`eB4IiDLQtDbAc1pLx?!f$VH~0b`n3rHn~B_6PzuFU?iF62{cF z4`X^Oe4AAeh0^`FpmP6kcBXct(UI}=U7Jb+^W^ zpL$)9>2>%_dhB7@Gi9dt68 zv_mJABwacgNgBbJ&-SUvh(tbi!dL}VVxYtttzQLV+>>9`J?rWPSyxSk z8sOVvpYPAM$*ro7P3u^c*fgf>EyiQhn4X)>E<2ZrSt227WT)vNfW`vZT!wBGY^oOu zAIvcOa*~7o7V{&*22F9boJf3<6F--&9NY ztnp0*uuPt8SHJP*Sh|u~QP!9b{~}Wq(F^J5MjUt03Cr~^a<|L0$Qe1|M)N6kxb=2O ztN1N=w0tw#BoNMyLyQ}}8%bpz{qUJKcM4-?AhI_#1v zeC8ABMG9Vsg{Gn&+y=`Q;^?5 zmQcke4M8-Slhpr;+uxbW^if_lyk_gtQN@c)WQDO-Qz%~C`BeJSS30t%yMTQecc)8} z*-ToQm(INxAyjwnJwX~``kD?ikj!l9(?o5p-dea*HcZpMtgf)gX>Ay-xh>UXwRD5F zG$MKL9{7BcT}1|;(x2~W)-C?IO*^6sBkshft~Y(ND(-31+JUyY9UW*U&2rq|c3|I8 zH~~JN8HN5ZV=)r@YS)9?tRZ;7^7G6PELv<=GE5Mv1r?ZQv!-Y4{>~VzK{}xy?id`e zJ-C&l6{y; zjnzXjM0IzT7twBbs3kc_`0@C?YQVsalf`KN`(|2f<_NY;ZSR}$lH6@R$1REF6>W4e zd`=?pcid0du`somN3#ZIK52Jy)RLAnCk?*c!y?++%We7bgc1 z?v-IIYLN2@Ig#?5@K8D9rA)14xhxOk&8@bsZa|}F3#e;{#hZv_9@7WdQ8;wbQvmGC zwk6LJvpHyd>J@>?t!}(I$=2l0s_7+Llh`Ia=4upZ$rbLhtRwN4-)cv6gr2o&9l6`@ z()T)|*W0v?^kp{9^uYhirdhf@=GQh2Wq8e*q+OY?lr-G-jtS6{W4f<)=;;pjH(B&_ zCOiQswUttv*4B@)X@%+#n^tHaw=HTf?z3sG`YgeEvc|B|^ zv*fiBmQmgiK3g0lgs9>$){aWXn~$w7Fq^0@vgIvN9RM)N!WF_G!I%!mQ&&ro(!$P3v%XvuP!&JeyXc+7{J%l&HQU?Gn{i(!>}&j-6kIsQT9hg-IR9zQ~Ht zZRYD33+3jsb*Do7OP&CbMvVE;rfEE78f+S%8qImO@j^Y~*};@zy^a6(Dn(zr2TR)d zJAFAJGj?7pr#x%5JmL)J&gf(8VlKPzLNf+al8m3aeuWBf{yMn4J=9rGYZSzKb7|z! zP*Dd`6}Cipbbis5SbO@E<+Gs}3 zB-8u8*E*HfPb1o15AHj^y8du}^H-jAmhZLBybOPn?dviQmjOcKY#xE3r>3Z>a5f>? zHW3VHL`l==UFP#!a&tC~@RP*5!Tf`#Nc(=V5xs6Ih)nC1Gx;6Y{eWPrn<_CSe-ag3 zhKuXuoK5l^3={DQV(bW?LF`p*IBG`4hBr>_s7Je7+`{mo)`mLBno#3|#L~x&eyDsy zMdF8X@mu_#K1$54PR|AIG5sRb`V-GR)XDs67MPmW&-HD=igA}vQct#ZV!JkC{A|N& zLA2hk;`{jXAuR7)z3eIHdYoEuz%T0^?p7&griTw@Kl%)B&}Npt6K|+4xG3vx$~$l7 zLQ$*=GAzk%bp`Lsjiq5j)z(S6bFdq}NM`wMGFmH}^W+A@HuKlfD($mN;55XHfnWn` zsrN2|$sZb@=!p;WmVU5(J2F6`U_REhah{kbzJ_}{KeHG7l#lS-xE5sg!k)*?XKk^f z*etme@-zf;(|SGxkqLW?BZ%A!?1`UBFcimdcQUYyH%*oFsCY_ZL_v0#T=G|{pT>9Q z&n!{6W%y&c05gM&h2$0=KWGun;dtCcXC|d=s3p${RBWL8{ri{IC1g* z0_xMC%=^c;#%Fkd;1eCZNlb9cu1@~YLAIMJyfTENJ)0(bH&yt>S)jCj(`5gqijvqE z4~E}OlS%ZAucNcvAV3u05q)AK`o%`{H)o3+AQ6R2vfRkH*+`|pT?iWODCj)qFa*3Te>kYPM{w$@Id+PH&hto`huLB|t_q&IMxmicDFO$A8f3kkWZ4Vw#>q7=8%S2ya(tRU9qRGUApt$SQ|CyKH)8@ zqez*%EYHEPCi(_ZVG%oZuFmD&cU92ulsPF=dz(3uG)9%Kvgq<@VGWJ7SD#pAT5gir zK9Tvsq-q4@VCqABVNC*tF7JI&@l+IZ*viE8ylHX&(4o_!AB49Zzxw@lHrT#9?~~6r z$R)bY6%8|PmMa=^eXnicB8IV{uF-8Xzu-EnX+y5h30HFu%giR<`v!cbfD<<{ zUgNGcVnlh^b7Qk4?e$*I!e%VKxyk4b7vF7WKhofP61d%);wFu8D9#)K^H+@HM{|Ir zMXSHs^rKM5Ip(104AF5Eabs)?v75q77G?QN9K%u3OpNUxhYDZVJI40DGomZIGYG8U zZ8-%(2C$3kkv!t|WuL{2e6d zW{%pl&d^(JS`|~*+O*D<-;j3a%7vt>&!1o(2O73ZVh=kPj@hAVV>W*j)5$Ql`h2+s zPn+y*t5f*)w`py%$fmW)pJ0gO*CxLq?Kb(TEYqu#bF~e&Uh8bXi_l?W28q<~LiaR- zfXNdO=32~G|0BYj4L#yTxC!oJQVB2ml%NNW>Of|$Zb-I-4^#egyRA}Wl_Jxdxy-;H z4*1FQlowrMKZ`?tk$IQTvat#eVB4sdpY)s2wYs0o?dxcpm+sXP^C<>`bT2o@azBK^ z7PB86YL%c%N<^-^$ECmCoi-1BC~b3Y;|%J!rlVTtgQ81bokrCd`E~D#UgIh*e{Zi5Y%Thr88WE%{W4eb@ zE_xYKy^Q4^$_ZVJmuwWFxKQG4RuPq1K+N6019K8)8`^I(-`|R0z;)D!*ZlApVT^_}3mlR_aJ;{nl?pgFZ*d;@t3 zNd>>$l%H!(J5-F~;U3~UG5tq8OC2wXMshj+oo(hb+2nKo6E(U#kWp1O{M0B1uY#ku z1gCVE`4$_Y7bQ^Y+U!#Px{<=XpK(gbp}EQZ!>3mhx211lL_coiWi7*M=SYZ;PN$Cu zIPb6gv>-7&@5gh>*Ua?A$M=r~JP0`v<3H!t@8YxiV;|ayvX^qjdQ(DjHR`bVNXdgl z606a;jd~Kn1AiU{p|X#V0ZfnJP8cr|XuMn8>xqr^#Jj~#Ck!SxD)gMugF6oFZ3;j7 zEvdgN+tCK|2+#`?L^l=S^n(#ubX5P+&6SmAXZdbBz1O;nkZ9g}5-X4(i&qc`LT-Gu z(r^9sOf>GNU#Chlk7)*Z$ZOdb8H@k8_bR0Vb^YiEvOBD{? z?u{M2>Q)G*^G7m1EMu(_fiXT9e+dKOl7{7i41r>L0lx6~h_`-= zC>0sf&Ore5shkzcG27NDhq^=59<$&pEJ>obWBd+-czyT3mA^GkB{TfyUaKDJ#3^N& z1hw|Dv#J0fJ7L1+x>{8+bF?;Pewv~DU#G}7qyZ$jaFggJa@W%-_reXYd7qCFuC}Zl z19ZXmCKz9S!^q2J8)ZHMyyT^LUOXj0x1K}}bG8K;e(jHs^3b``56TVPt4TAH zS#r3V+@`ri$DbY)IZDweoAFz%iVAhC7c>wARm^^m-|1@GjrkT4LKY^`#f>d67cuQb zLwKtU*(I_gTX>a~9m9ptXfki;P?bU}VewXS97|4_=3@uqM`}EfE31ke`w^s05RB#8 z$keRgb?^@$#>*RZh>F-~2mcNW?>?2U)s=o@NvTz1pHFdyh)9VKB$=CbFszbV!9r1b*{lr~`k-ronNE*+N>` zH2B^?8fE(VbW%6Zu_y)2%J$P}1P37aX1H+y5>jV2;)5{q$ZB<-2`3O!{)LbVM^RCA0J+VS!2Z zq938&(gXdRBX#AQf3A@}aQ{;4a`=b+w93i^Z`w~TRsQ}a#js-ax1?RH-bT7QZ-ROI zY`B`ExeTslZr3fl0S5BsS?uhHJ}hT1Ks?2pO$|)i4qTJD!KU+L;Z-(7u?l_{+qcL) zZDy=ZbKglnAT(`IHHE=la{$8g2h4&y)MnN!=KDsj`MO1G_pn)2i~ibx`M|!C!-mBY zwQ!AT64^f7EFtA$H3S2z;K1pMft&4V?Z$T*kBSoJvaD$rh-F77oGG;}H90NaZmaD~ zOC|D(mV)+`XfTUu<_4M(&y^+uwS;e$li=4-Hx;ufmq2LdR+a|xGNI||y)Z`Y+UO1D z$QvQn(LK%i_hG)#gTEWlp@2bNYRM4pHt7K}wbu5*?nx}>SsslA=&PL#o^32s!GZLh4c$UM{{Q}O)r4cfZm{~4RkkB2t`5Tw{-es5pFp_bfccUOom9kCs{&sFw0+6DYr^GR*~}< ziFgyah>Rl57D(9X)J`d0hm^nv2cA6r@Ut}hM?OS1`)X5CF>p0htP&Cq9R+ytQJfU{ zF(-6T{LDzbmxdDk84pv75D63Oj;zj!i<%9-=y}X5XfF6FgCOJINIbZjRqWGizoRZ( z@MBS->xBLWf;Vl+&&8d&X0F-sj_8{q9A9k%IfHjOqqvkBgfwgBa$foZUZ%ry8Qf=h z9j}R};vM{IYg$^1gcV?i<1FS3 zf6y<0=bOJYD7VY1xtaCBtvQrcbI_@o8h>nLM6YPc(PJmAFT>drqZgWP;FW@`3tP+$ zmaXiU7(L%?vjz7TuhY(*keRBOLZ>vz6betHfMz=G+K$4zuhTTT^o3@B``+#L*;&B- z!)DWNud!+E_B6T)kYfQ-x?RO1Q(5^4s-y*$4|lH?FO~hpDdHm;5MS+$QcULW!hkvJ zC^@JWI=HN6@r)kwrn-QfjsFmseYK<9oSgDruV#yQy^E9+aa*E@(!!fInBSgk7ymAI`h#%0lM zeYmYhTOVxG+E#y?*0%cCw6;}3+HK228Zr6;HkFyHu`jO+-pFcm!Rfoxd++x~&;RB9 z-X{DMY*YwX&+VCZ+ssx=p1{*$KCo#t5XP*wX<$y7*KN8`PRs}?5D+hbYgVjgOXLeC zxeZ%U1Xnj^8M2P;w%?3qoJ4}PvRlh7s`0>6941Aj_mYz#IMfWfRwXY9QBt}s*zZ&2 z94D{pSUFhrZnbR`Q?kuW%xa^RF=ubVQLXSXGP!ZAMu1^9r-JqW(y46T(xD-eRhQYV zgd#_U+D5Q_8WnMo9bJx;yHLxuNx7*~F4rv9a^l!71dP}h-ePWsXVW`bZH>Go9_;1z zm8`af=3ual)vO;8By;Nm)DtSK8^|9d&r~qgto2T?@U(sgn_HgWr_*eL-Cm*4krKaAFOVH%Q5$lnmtul9vmqJx_V=bDbg|%qKSBzyr2U;_DuUFfU&o=Zl0T zoawYy%zN#4;u`6=L>3a37p&ikFH#pu$U{FKq|it$RMPqZvO7a`Sk`C0KKQ0ZXo18| zc!?~+I%)Euf6~7^bBp~f19icbLI54%a~a4}EGdZ;;bQOz`%0vULcMcU&&{zD(NQHn z4fP6CFKfl#R$?#^&wA<>`}PsuwlbK+?Waz?IgMr<-Y@YB3OO=n%8?7>?HXB|+fKR= zvb895RO|N)x*Rr%Hb|nzn;-6M>~QK}c0=0ZCQ5hwIJiHnOm~ECOM~6Y@nx87_OVuN(%7 z_D(i4)p=BOAsB+U6ZpjwFgKnl$`%wX6Wz$#Typ}A29n+8?3Rf{>Nuy@YrJgmGRF`y+U_vUSJuO=cp(xJTg%FK0>XpH6yi@#wI8i|$o50$>t zkhAoPoZQ%`g4pO?4LN;3M`L@7XZdu@tIzOXY*f$Ks9v&Ym}3FTwWEqfY(!7Swd3S| z&x}daX``#Op$Eo`q*mFa8>mWGpoSu8)ODBI_J7gfYiTw^k%*ITReL97wTwzO>=$z+ zr|Cb4H*CKlV^usT)R%LehaZeVu~WQY4qYyZ4=S7P4HuLRCLBOhe5jl}{HDWvcyRLH zt_pCmp!$qcEE?M4MRIH))G1(oK8Ak9dJmrDCG=c{X9!(BbA}i@0&2W-`0)4uw<_pf8~;&_fVXPV&OaU3K7||Z8p%*$I`JA21>`Oy=FZ6wu$jZ z3_+y?CEZot$lwgS3f=(7qBOrjD=@9(&qBFl%q>{KLYrj~qV~d@sC|8D#hObu?X=g6gW3?^cp$kYOD)XXAdMhWMDJ4>>DSKY@vi#2+0a9dDAy5PRKzx3Cf zmfhGG0qJzDLGBACmX)Po)jW;yG!zYn!p&Fi3@0MOR(Ix)@tY4wOYPL!LLC+fbm2*< z^^21&eSkb$r&y6Ib~O~8PL+%$Oi4p5B(f@;W7GMGF(oG4QThL5SFRw(+{)RS!L3o8 z`%SmYl+$UYc{Z)5*cvY1x%2q^D<9#Pzt%)TBp#3PNB$3BJ#4Xd@Kmwvsmw6DAwC4HfLoMm21hz z_bu5%uDu&<86bU=CInV#@(EcIcutJ)#yzq_c0dmK>HeJ6(m-ImdGR_H3pz1>&pLH- z&euAtOU&Qk+Iv&`@q#~Ra&Gh~wVy{FnL*JS#@HHqnVWy1hBLEj_`R*6bEXDY>uyAm z?DFy*M+h8Q6Frk_K~}C0_s+!?LhV8#?XWi)|9y=WuTIW3R(x9pJNdshR-ije|Nk>q zj6{PWtnbh*3{ivm2I&8Tv0}`TdmAgv=P)A|ZEqgF587A)#hy`U{GxDJ-4Xi5eJ|cN zS?}$_nq>mH6o;@56Uegh^e)Q;ayO;-GJ))4W{*)u&Fs22|F4%|4?#I5nCQM4nsYC~ z{^H|3%L8DvB$Xib>L|T|AiH9TibYb&gxu#!uP4Rn8sRLN`)q3Yfe29hZ?P zWWQ_yT1CfL`I(U&7NC2;-5wU8|COwQ{xVbG6CxA$hQBK#A^k~{poB9T!a^fkb)S0wTfzqCr#g1u)B+HA0;TV6vX!LFUu-353S^jEuewnoDmj&RvC%E1o8eex+;@i& z1-~^yDD8?PbDMGTMswY5s>aO~g{;SvcgdBTyD;tHeC9FVE|KHv;=U#kjgO&pCG!7( z--tEO7ZrzRmZ5xk_wyyxjPKc`T@pZ`EnS!1j?6^EH6DV4`sV%v?#b3NKDq@CZw?0e z@f1CIidjdHxAkVrRH-xMjTa?Gd5oFBuHu+#y^ENSW!Gv$h4F3yDLoz9Qg_H^^V-Ft z!Y9k4S>?TEp)F4=jx7N`?zl$F7ioE~8Q_*j2O#AO$a0BWevmAxL0cXRQInMan=M}) z9~6+UDRwH_ev>(s^6d5`Q_mZX@r|lf_m&PLQri|i&W4)@#i#Wu)>GNdJCv!lRVs)2N~Y1**sVrC7h!zP!Jrd z&9<21ZL@68QRs*ZuL+oH#0+XJl4jdDZL7_a!_x6FTAv{UThkUZhh~Wlg12X%SZ^hF zuPM+h)^tDG1F(0thM8tE0q=rCQM6d-u|85TtPV9nE>;; z#2PFL710Yp^hN@2U8j|)ivK5}Ah}iMqgO<3r=SYZXzM)aSMC9ZUe+4m(awB@jo zC3dCp>SYg4D9dZ6VVr|ZqT+D^7g?~n30Tdhs|Aa%kAuQ$Q~zfH>G9-L!Q5QI*B#Te zyP|0im^wSWT@$0{n%x)3@WMd^jOaXCl920^cpn7|E&6N=FR;ei_8-+jnyf868n0=F zdHsPe!jNH^mdy>-ufpb#Lt4zk(#GhvM8!h{D6-Xdw)nijRtr8K(rSyf;6t)fM;Ehv zB?miuA8CPzGo1nKvlpdvf{jdj8}wDCz2j()&&4jYe_1~Zvu7S`q@g3sWBO`ivS;ve zh=f`%gK;`-CG%lLWLBQDwNmS1X@MM(M6S?+!jf`z67lMMeYI8@AcW4Fq57s#SH00q z`sxII)dW8_TM_y*0#e_)J~Lo~TiN;u6BI_)nG=_<+dQ&1{hnD4SJc&gomq`18Gy&h4;Y$$<*$N4{Ws`^~tU#rJV7+-^k<^n)z8!pA=fwtdwKZ+gx0Kk! zb=iF0mWIi1FrS{U<5s>VEH`n`Us*d}G?!{cZONyEx-eE8dAU+bVZ5SA;$28+F^V^t zrzoCWt)OxBvCM%_?0Qv%^yO5_%1AP_)^ycMTas@Ib1}cMtd$Ym;$)-@Niw#)Al=xK ze6t$AnC5_a<}z&^m0dF|00*=gw9m9|Hb>GvX{V&eJa0?#%4yYO9@JOjoTdt?ftHIGeBm08`Ly`bB*Q5Mil*VaF; zNXhQnQhk-_+5vlYZGnEy?pj>mq~cp!d$Z^};H^#yc>Qxi0{|^^Baid(Ir$hXxOqgn zVxyp+xi#uX-`pc8o%EFdU?acb7jq^rtX8z3?vl%(_7wY@j6NsC#zyye*)EH(_sJ3z z3hMqPbL4fmlnSUv3qQ);O9Ca{<1Y)LC*eRw{=9wikNV~O>Gw20I<{L(F#uAwATe(t zBV*cKv-mvG#F$q5Sd^G2?B0BAABz+72Fv3b`{+r`3(DhC``9Tl?;&}7(LQ!g%xjd# z$LwR5#JqNSyvIHwTXOh*ojK|zM~;?DhdE2Md7+5v#0OYy=7O0FM=7Wa-wd|q_I7Zb zm{ovqb(pXaqG1lV{7lt!gB{)`dx`!wtxCl{HjPv81nzw9xNCh(&tFE?2UvN8^b%u6 znUAe~yhsqY&ZfbIB5s9!1Q&|9*X{J|L{!|OkM}5{<>u{4h0ITi0|((1?_gc%`9`KqpvKaH6_v^1XzP ziUrP7WuAXlaZ{k6U4T;pOH${u{4j@)Ih!e(OQG0nzJM)mYyEZB^q+AHDEaP?j?u#e zpX1PbEB=jC$mG!3@UeYlGD>?@+>Q z`-s~5789~*Ms}3B7Pk}t(2*VbjWgTUnTkI#u*dew(4fEWY3apfPA~RVN*;D@wo>vU z&c^(oDj~S;mi$sd|=@#?CImkViAXJHbgfNrOWdnTcY-0xE;i z(nV7>v+g&DX=__zowqH0`K0urSwy=(Qe|Q5A(^@5sT(dsb$?GgRA-C26@+SBLGd5A z+hBIRp+m7l9wgTWmB_V0KDjoivs@d*EkK>Zw@U{P6LumN2p9+R`23Xeo(tG-SK(8j z=Zrl8vjYCg2m*(kKG}d?NpA3K3-yoxRd?J!(8_LUWw*4lTUyyIt?VXzP1zJrh#TYE zRLLYw&KLy_D!5V?_%lmcuew#zrvA@Alofls9Y)I3xk04w|4Xa?nBa z1)*8G+q`PJ3;Y%{e*|Y*%D2yW50Ta#wwb(>k#u)!`P!n4Dss4;x~V_v)KzvN+Xau= zs&86kMBTZuMqf3v^wk`oG^BB2lt&yy6Ulq52s*g#zT2RwYjM~_Bb~U_6E9}lC9*k+ zl%r7$RhbndRXtwYlPAbbX-?sh5!{Js9k{B@L*pSEtAT7beXv8>PQWyp84WgK-?5BJ zC+$}Esvq@9He1?*UJ9=4Ghs)~W`+;lBW-O){`gGNaygSVy*a=ISE84RJ`Uho9J0}2I-&FJcV?yMRD&jZ=mGogHLe}XOPp{KV72O^V>l$Ai%NB z;Z@wf_NWZH*R5?oYO}cSFKmY5S6-k6_7gULjuj|r*K58!%js_OXqAZ=kEQ1Op#W3o z0CS=R22W!M!2#x43ru$l%*}Xb(=s_MFvn$p(b27?iFi20A^quCUe=jQVJpa|11w~` zSW~Ixxx)qrwndh?Q_^eBm6D_VcypWubg&{eN4D_MT;)x-crniw?xBUR&nn!N!a&8P z((0&t|09ASXBuua-$G+qQpwHwRih}lyy$X$Wl(mQ9{Z~drF_LTXlGek+cA1}SXwK~ z!~o|P0~{w|JZ1^I%DpVDzievUy1;$MmwA;&EfdP#~aOJ&|(F-l-b7En&JC(EWS>MD1x&Qmu%n(HQFyinfVk+l75v) zcnJGu7T}DJuTNj-ozl^z>XNz^;k5aHL_lejjPO}(f44EAJ8Xa45Zq{Pv<$?t{hj%3 zjFoCnz`4yv#k*3X&fvcut3(N<^cCL12s%){-n0{>5$Pu~!N z5&zxD%28&%ELkaNWVNtz?0kOJd`(~qstNG*p<*tmIT$KYxnlE~N-=QdHnW~|@J3}-Ti67UxNggie^q4PUyOwM`X5s5fHnKaynuG&tyckw{WqizVd;=r1 zhUSDu2!g+{lM0?9reTQ$cr>_;*2jx6t<;zB4ji*2wzC&ZdJ>~b%)D|JsVQ^Y84S;u z!Qz-|eqcibMGU@_jNRg6PDj;I%yle%gD$BO|B3A84}k2R)a2Cf1=5W_bfp_R2+$}b z>#N-gap0z_G#-HnUm{*dA#XN2ZMukfh{p(*JPCTMyzv=x7rxg%Sm77q1WYYl}= zR$`{vC7sFIY_4I-h?@W#$#R{K=eR|6I|XpB*rIYa0B@;G(JkgznWCX7Tp>_4VQ6TW zR#9ZD=x2p%s<_@(A+G-^GdWX5llca^OsdLFz6|FJ9Ab0|k1ivqQB=Hb2Ms~YW0sTO znVf$--wwFN(5+v&7_yU70<7xf#N5l>@k~zE8JGLsLM>)>m#mrDudeS*26J_16;tJq z1yKoh1xG1)8nf8yhS*4Z;vF{cukC8CV?<2vIkO(Coc6uO{-$t@>Xi+HtBUk#I2?Yt`H75go6oyaR*U=(Vp3(3HK?GcviSiN@zFLB1T$yL+uF|IwVFyoBtVHSjpmEwcn3uTEa zUxxJrdeSO(ylRQE+!fx$T8&G++K<+o8Ry!8lv!;oIiE95Upcz7alyqs3vxw-`Uy`X zx(dpyATME=ncq%3Z!_S|()%Ze(^;`^_Lm*YH%J(0p^FnK61o?Ef~-a9K>e69YrsZk z*YB-cDi~T*oQ)xe&|yRsb`4ZvA!YV=TT?#t2>mv1{!ZbvND$keXNZFY7>LRWH=LtA z&KD$zdX!ps+fhbi=wV89AmQJ{F$4)PXp4j^Y~_lCHdBA9AVJ-3o*1KbI%9PE7Y-7P z%HbzzDg2jy2?_h_M{c;H!x<#>tQ%M(^Phu+@$XhV4S8@*v&Gc0J#v>N8#K<`ZL!aC z(P9QDJoIs$nS+-dMU08s9aX++)$E|NRX@&dG78>!l&UMi0lKFH>^pI9Qght^TeIWK zvJhblOo;7Fm#T~B3n(q}PWkq$gLH1H)pQ|g#q~O~14p$Lx47`PJ34v6yui;o`L0DA zkqBDMbSP+I5P;P5aFzMOuIYe?zR*hk>_~`5G-H8wD`UNj7rYCD}69(fYZ~ zR+QiY zhaBIKIjLda{7lMVvvP}+dEU{}xgrL1{G1)LT&qMM=ajFM9{t^tGOWu=5f9I^QiQt* z{V)(jH#$|1rE14*JbtDXVAw0KH|IEW-RkVAU)1m8g0X`w&$z+1ftJIzF=($g?x79y z&w?yMXSNHL{+SP!c4RgQe+M5On*^5MMl;%Sb7zxq@Mq2hLz{w7BlG^m5T$-WgH!S&J4Z{kA%KeUbB4?6>_)S{ek8~U9nH-0es$N(qVvspE~}G^Nn#_05-=k+f>`k- zu@R>?jF3edLnG^8uyu|!;G6TO$2foB@ZLFeXiy9szcJ=R%7L>ts%dWu-hWc zid3v3?uerIb*uYe${DLxK6?b^~+(0&*~1OY%ADk-W#ArX>WsBY10~IW(nym32Fe;PZ7APF%R=eI5qvg9X$=N zQTE9$>cUT4#9LLmN1NKhAw`iDqIOb>z`?jF0>6w2ERKE2Ylcy=xhIDS_FW0|D?DvDa##CiBM8KX(*GA)qG&$LxiC*E(1GWgZ_Wp&{%6z5<%w_t1wn9x9CoIEhxp^>jZa`^JY7Wb+0O z@jSPcrq3$>=%)SRxr7*v49?;B;^4E0z?PiMm9&`-277uK<&R~Q4-!COnbBr(i^qc` zx`l-9cw-9;$=CvkcCtK&5@q_r7kv;)7@vryc+Y(~Z?=OV?plks@Ema72N*0ZSjm9J zmdGcxxJ#t&^b%<*-NSI?09JO|w6XE`O0o&04O8kZIgZb3RIqmuB8dRlP+NTA1_PA%!lLA#k`!8Sj2A*4hcu4 z(>r%|mGO+}mpyCSqP6*Ir-Dpjp4xD;^HLlU^i}nN=^ib-Ohl#gbd?~!_Y3}*7GB$} zSX8jHO*706cbT?bQ=FLh#w~&Y1(9uuG3JwPWmsWBL}mx?|lI4L70Ic zO889W$5gV~tRa6!YCKQI_r0IsbfeYL%v=O2%)))LJa4m@8{ivKpZFHunVoB>Lu2_WaAw#+xMhVRhBy(movInlte{YeVs^7?yNqM+<6`K!7+8*BFF zL4v7JKehyA6Mqc#!K0l+1EKut{0WsdShR-B_qdJd#Nd*xW8I0tO$MYTNJ0;ffVA3t zKu0nT@db6CNasF)BdK?s43|V5G?dW+CeRAy%>oK#s3kFnB7dZaOD#*Fa&ucp7JuD= zv&k}(EYhj7VJ*rZWn~`3gWK!lY&tK9KQ=2RF$a+**tT#gwrq-6pr^Au^i_$>`XC3t9`;wQ2l7tvH!dkDuDMpY~~nmHcPk@i6Ng!q|U zSSIbK!!4DXr%LlUC{1#>OCW1FM(B@0zKyu=1J|Q4_yczl#_?7 zrPayx?X1k{9&4HX@ph<;Xpco$hnZEx{etX_i+zr5-2@SQyDy8`cX zps%hsmJf-W`x@x{?{RalSpmPK=Vv_yNxBF6dhj5IpGbT&?yw_VyWsPQ*tJ z$H#Dk-mkKfhITHu4ZcthXzR@LPg1Vq*jwv-9$n6>ljr__gBgiC0OriySPI-pB#zTM ziB_9Q+ms@;T(F>kC3S|(koTmhh!8m&z@NhAxk1Etp+OX^{mke@*r~t zI)Zj(7RmX6kMR_6@Azi+_@eLdBE3Y4CM=lqAg1&Zfmi@tO1tpj6BJN?A|ZV;=k5Tn z+P&%mW+p^%(lNre33@|qiNxt-8rKk&KO!Lz(o-ad5*}jrEkbMfpU1$n;Ngys=> zC@N3{fZn(ar(I0>qpf%~A32tPzV6IN_1u-}7sC3O&kT8+Ol zy(A!|qCcV>2qdDrI)qjt?OlDf_wyKOKgw4-SJ3ak)cloJc8G-uHq~P( zBcPZaKoxKu8HlY?(-N5!wp@CNkgRNYo@tl%%#r0fyaf*MX9B!_P3hM{`?WB=MCueW zFay!85s&AG@D0gpU04DZmV037Ory$bYU?k}R91Jk10P!;&rk%6(UHFGowO0n*oQBf zLT(w!TCYUqtCbxog2ys}A|3KtaMC?i@wse)45z%4zm~z{A26^?=`wVL3uAjL_{ulQ zTNTEJs7wRFvq<$a86+)i!6Ex64~L#d$0+z1h%o{wPI6AnNHA9x8NxG zlxGI=X`&n<$aMxP5BQK#Fc5cT-p6l4hFBm>!7kt)^C<(#0zQ&`oGYUQ&-21AR|>m) zEM=MN@?HpO>?$_yK;+ILI(?G&vZ0a}fy%1b5=o?cEON%786+h}6qRlpA3%b~`8uJ$ zP_I)U-D9z44foD0lJ1qf9y3N^YJE;Bi8ewiHS2-!4~dSH7rI?yFNMPV z7KE-(Bo@(<8PV5~ccSu0%p|QsD0!hgn~8%hUl$~q=SuMdSRL-0ULu*LnQPo;{BqrZ zt#TPko@9&yFg0ys5ieW}K291zxe!#j7-Wl`D`(h+J@ZslZO_yHU)QW-73sFv6gHsMe;|N zq7k|jE#%S8DOt1@lXHb^4%1Qj0Do-ePa=93zra*0e==M#)JZsEbP{FQ4D%_r;8lLh zG)+KL-d81}hF9nb9!5A3vQ9kB^EiT4%KLGp@04j7^aLwo%j6S*Er+qNDX>|L>T~8d zssbrwt8A6H#&HJXhFZ=LT_`Mboe(z%3@_XAxs5sd84YBA- zg8ldd;dOP|o^6G;!?kVrzF@%eeWiGd;ZF}i9$lbg5e*?JIGENuJ>v<%dSdu6^g8HV z#+SM4Bmp zAlpbgSI6(Wvf-fgTR9#wS1>K!?uKv(2Z@IdGlbG)1}N`Nmo}*FuAs3QB55d zA5i{7EW9P$n+?2EQ{(-W);4{2lZW>8OJ-6c+G75 zRi}%#{3DBlJU7H1D)qPQD@VL?;pOr796|{B@fO|QbV&VC zU(tNLovXG)tAY2px%&VRuuhpE17$@K0v$Wbf$go0kdxh{3}w>Q^}mxr>c^H2JK4v& zt%MR3Q zi6#2t`IM-`LT52y_-<#kqni8OfvTE2W{&ExMJWgGClipQdsq!zs53imo<#Kq`M_7S z()m4cPWS;hIeFl#eM=JszS`GGI60o~rEnlZEfuitw~zUiE?C_yScr}Coy$a4%j*5A zq|J%!V`)rIB_X68uM5fW^b*M^6XDNaJ3_($GPn)wrY`~@I=y6qxb|9O*OjpezDo4{Y zzIq^fhRGJ29A{}X{m@_&NT)tll!-h<=%y=$UWa@^JHHsZX-7%+DXD?o$oGF8x~Yvl zKa58P^?!LvYVMyQBxvE%-_zv=QwsF|;gr<9o7`}D)dTBZlCCB`m#G=6X>^tE+pv)} zRIKen$W>yqn|BVfqz!K%D0=u;a7ECI#&LtWrH7?Laeumov?W8YnMxWl?`>)ZjPl(C zFyxqCci~!SwksPdRBhiO{yfh_O6JFF8q*c4;8tESU)2Xp4;BiV!UqVwz7Vtd8%OiR zk&{dwn<)|AXugIc^I0ZPUi>566C!Lz$ksV>#ag)tXH4?oIW^iJISRa1m};cPpA+uG zw_aL#qGp3>hZjO@#DCooCn&^KC}HGVI=l)tEt?-%_$CNphu3DQ&i!>e1dAI#b5XSSJUP%Q_^UPvDAVv5qM?Lg*3T4djPs#{n> zBHd15mx(+{X_KNq?)vPS$+DcpKYUT;W9LSa6T2`IaSnBz#QzH6W_eT(teX=7MK6Eq zpvW1NUS38i!!Fs&o*~MuU&JBG(K=Tz^ zkC_!9PN+c7D8Za%`MB;H*)%k@zQ_XzMr`}bfz}>=oqa=)3+%`X`)W~Cy%Ld2Lv$Ue zKnDALC(okWKl5sp_w0#yU+oc;4+NWKUyy5lhXhFnM9$VzG;IO%!f{%qMsKS9l<~!h z66i42V=QMS9%NxxQ+|udU#YqIxR(Twd>jw^r@h2P$Z5<25Z}@Seh)pf)iaXJS|UyIO!3r%iRT z8cQQe;Ee%ulC4_Zvxv@Rt7g<1wd$o>u#uyJzUa9K64ao+Omceaas8Ge=1J&SA!6s4 zRZL$SAz#<$VurpVc2DxStc+9l%{ba-tjm?VV0JYtzjD?KEY81tvY{`>7+@6s>>z5J zME|hIH9Ax4{v|}e^b?2ZR|r4lIEvi2T?{(t7>5BJ$E-GU8Ow_FH8J`=<6W-k26OkB zGu}nUK)Nk|K}am6ZXkc}14YOQlqsyg!Qg8Wdl66f+f z6XuktkxPyyHT$B!0aj+Km6Tn4OkLf9j{!&#yoU+e@ppvMGwkZ{jEXXXvv5Is+f4b9A#bQt5rP{!Jb$PAACd6u)&-JAHb|$TCetW zS=xvU4Q;X3TPyk*aB-HqM%Pp>Mk|767(eup446**k=RUsbBNb2j*W@%T64E zuey+Qdd3zi-K1v<+1@po)nuizJXyw(7eZsJ30{iO;3qPDlldp;5H!2vo5EYuzOo|1 zI+KIs7f;REErb&u5p4QiDfZpVMbn5DbKrQlwwfk$k1bl5RohJasz^Y9S-zrzCO7S}owpCxRRj0$f5S5r4fA|Zi7U!AylE5A2!V=^af3TnA&N0I@ zce0mjktO)J{U$NS|H?-@uaLo-Oi5=Kuf+Xh1YH?y3u$I@KQQOsm0*2u(!n*L^ztX5 z;G95Rs02bj<+|PJC+-_olw0~X&NG$cne;ziE{jl(D&)|vBt;vyP=2G2^4kzy#IH_* z=b6iP+nsovcjjSdZYL76<*}F7judJl9;@@lSH*hT{S_><6a=C&(2VDGa#RLdOK-O) z+O+ZUh%;0@EsoFL8FWGOK((!5KA?{!85qyDQWB!xb+xylrpme_ct&%(7Oy`XHuy!a z<`l?iZZkIB+rodsvqp5Qw(n*q60LgUba>zl{X57;8{_UtRC89ExviW>SKfG)|*olEQo%xDrT4ELZU5Ch`6)Lh=E?Xdwin&4l z+Uy9f>tYyQ>3e4S`k%Az6281{Fag<2 zq=_Nt`Y7i6eDl*^m03dYb>>~Fl8J}0ls|g#toPIOnr!>AHFp=u#RrKRJankLM55F) zx5J6i*{%2{U$nDAGZl5vy52&d3ml8XZ!gp81tIxn7|N`_=>oShTpZug`aEUI!u4Fs z)v?aqhtm#ppzC|9*vGPmm%~*Zt5$pKCGt6Ikmc}IQ>eMRX#9A5iZZ&OW}>85)RnJ( zL-$}6EgWr$6F?N(V9s(sHx9^coxbQdJgPf7NO^gQa(QF^WjSLZ0OGI_Z2Gryw$v2u zI2ZQe6M>{I>YA`unH-DXi?&Q=Jw!Zx*uIkW5YPE$a}!%qIMjK66OLKFGBgO(9tfd$ zgNtY(zy-zm$^C*oNhiyLvwuV)iSMq?Yp7@=dydJsC`ulSFH2s0N3anxPj<(Ew)A-w}Jf7er4e{ysl*E*mu7EB_G1gjzuiI zCDcimT5(hnDv>319kS!)Q(01#Vw8xs7Pwqe+F-u_2(IZkXDZj6 z(>99zM}|j=zjNdnRZDTkl%01h+Q{@X4=b8{(HOlxT$M}J{)B3*lrM?G!o#Nq)dCf2 zN)$|jq;!v`m1%^A!{G|8qZb$-l!$I1hZdJawvH@x6#iWsc{w{bbZ}fDXi)#IWX2mz zK#Y&xWpy#7?@ciUym`mn*b#nry3Dl$VP(q0<25}?w=qZuvB`H)a&Mt7B%hE6r#O7} zBo2q@NW%ry-I7gg9&gPI^{rSTCi!EBaru@f+?ySWC9$3Xb1n>=;mAcAHwVLG zl0B99_%gtL$&)WfeDHfMh{1qVVZF}8m?L-zZDI_a zj+sH5WNomp*jB@*2mKUc*4}fmb}$cQaIo@Itn;MT|Q_JXQYJY-e(ovy+EC&o* z3s{rG{+iNaIg%b{S&o8g z#&fMlzGz=XXUZk<)ds2zhKjl72fOgz^Qt@Da98PLC!4UZr^s^&d;U)1$$e9DDbxdse@; zW%X;PmiNotb_@lBa^P&r&C*MRD6KIslF+o%tMn3~XUNx8LZTMMQm0u&8tW)W9_tq( z{4ISdH}tBGJa`vCFiF2+x7B(t+&4JXzSzVI`ctNyP^uEvkS*8=ZJXbxcUfcJB6+Ii z$qII>fy4-E?9q%Vm$kH*qzBKU4sCyq z+?TP>ceh$JKuApJltj0Q=chOApR|WmFs_o|1CgDD;+p7BDRVzs;9{P^e6(OoRuG*wiE3t>9onIevo+1EO&6JOr?BoKRnIM6fm{p5OgPhDE?F8{7x7eg=hbiEcy$R{!eSu1n3vsm~< zFqPYy(&8>K!ZTSeXL6*YYHu*-JC^9)TSQzhUl;aOv;PT!aQ16*9M1Pz?zAB%d%6HH7#{PIkgc z`mOjCrgR&F%08Y8*25mRXKbrOn;~7tan#(>mXov6tHz;ZTWV1=leg$4yY4_rD1pJZ*c0KgZ7%PZxR<1!H4aLymtx)_q!}PmX>m2%W(Z zJ!aTZvXux_(Oq-vYTd?0YIZS3;zv3-R+>C8x^2dBXo?fVZ{8itZ4enfM{419cdbbR z&D*@w0H?H``@7gw7lQ7_1%hL3XSYZ#*NiLwfh@4@;h9haQz zStIi^izLmiE+Ct*2%4R&?RuG6E)!sG?V+1LY#N`W+B!uPDJ5jP7xj@HFFkkD>-dc z(H{ReMh%m8g}h}IezC9l<jej z6n)sZ{Q5uwujO~lNmL)7v@5!;#xrO4Z4?UkV8205_Mlvq6|a!n+wx*PFP_58c?Y#t z`G)X89I}(i{84=S_N|9(i+pk9oYm4^^4?g5C&*CUA*+g9<-Xpuy)E;RnRI+pbX&qZ zGDfYFcKPOBLI1dqciM9~d7*icow+k&3GX419eFe6L{{$BxiXF#IBb^}Fz39b^QHAN z%IH4D9hVOpGUKA;&A6Cj=up;3Jc4AAH4;=A&yh1NVYx@m7f1_NpRLsFTc8)gX#)+p zjZBx;QPky_Cgj!`+~mwDyedtb+l98E&5>1V`Aqj{mpani$)%KdJ)d!UPltUDJ+6y4 znV%2?rc92%b#WFvbJ|vQ&fetG%<{fJao(t=>6vGyJ+r(-(=FVvH|-f9>Vq|e(34o-pMEV8KHnV@ZY-_Am7}T>;-T)h)$K# z(aZHW#IMa)ePY1;y_GuJqi8^4%Yu9f6$pQgEE9!_~iKUTL24LlXpKDS48kMLyC z6Y3gS_WwwG7x*ZvqwzmCV3CDgHIb+wsHiAvsfZF0iemb3YbTP%aKr*-sGpIEL~l3$3AHis}D>{sLh zuOGb##eqFer!sWgND5k7Z#UA5_GwNj0?QD#7pAT+YPgn$6~&>rGRy8}dwI?Njka!H zsW?(59=eUVI0U5?p!pb@5^*JKzl+l5l}eB$vB9f!89*ZXQyq|VtBk(c@al0wN(Y}@ zsgyEm`>57Yug5ngQup&Fv2qFjM)zzipH<&jHf2iw4G#aN)aBpUZZuF9YWW#3+>z2H z)mVCa>S-W-Zazhmr_}ZPH2+5CW}cfL)`lH6TiC?1TuJg57;7|nZFMOA5S6qnrS)pg z2!@(}Oo^?6d|b)Cv9KEQ%WWChk|->O_!sb(nz%09Q2d{Kic@dqZU($At^k!dU`wO> z&rYTkJ&nB^$4{x7ok%U?ZSyPR^KWDWvzs5*U(i>ydWwd)Dq_$Ip2f7Mq;r+RZ4i%* zE6*_Ku0J7ZbTf9P7C|ajNtsEFExYRnh2keznNIlFxH6c~EyF3vk{gtTq6=IX?iadn zXML&*ckWS1Z^>#qNyBr(+E3W`>usD2t|aq50tc<~Q5|u4k$6B(-e zfUDY&f+WNUC*oT`B_f+M7akA*i#PK(vA9kD#Y;1;KM67;KgeiR;VfWmU}A9x#ZFKW z70q+zCm?fbL{}|mGWw0W=jt_TZ}40BtM1HVr1w4^7zwG~?fV*QF*?9iPQ?wL} zQnhQ(6|J`qbrQ3OthbI!OHs)7^)~{*uPwV*YQt*WHH*^;*LtWWPDy^bw(UZv$9tQ> zWJ*VmYeQLrcnw8Kv}SB=<#n)Z9o!GKzk|FF*>f$|8uHhFDA_fIyE}88(_eOo2T=3F z3J#`ZZARhoHY$XBQ{ZMbX=9Ru-ZImz(^g8-JJqB*>_zW*B~phJM~U$fDeDe2aXJ$OAMX@jt=ey$W{t4zO&s2$OUDl&IwPI$?)ZPs}IiTB5)oB%YgI5 z-@3tBsECib6kR#FLn{N#pXs>(O=UW!*!eS-Of$k;cr%k`o*RCEb4RG<&t>pgvmOEV4EJp5iiQA;qI;nJwUA~_R}!d`2fx^*G0jKgNv9UNL#Hws^>z8T zbgux!*(x`OnYU5XQ?U{?N|6R|CSv}6%+io=}B{i-vh3S zD1Ncx@17TY{Bj*MPPqnJ5sV~>xGlYL3%h3_FR__Dm|`|Pk^W}%Yn2(`N{LeOhcnpT@1UX zkSy}eY0nEkQ4 znhr~@U%exIh;f=7#%Vs!?%lk@IL+rCrzz){<|`w5O-Xv43@#o~N&3%<_rZ+FeA1Pu z?twS3ZsV=Kt4FXddW8Ha=V((GrA$P=b1uQ=T4#NZYj`bYSs>=wsD zD$;x3MrX^O^LB_mZ%t0k<1Xej=t}5uYiVj$5VxJFEPZ~JvEaxs{i+O!8*IFJip_)q zz=+Or9rx(uEWZ=irYD9psq3F01qDs-QyG35E{nQGLNdG}#eYuUOw~gq=AS1XL_R&3}S7cI)8_}!szLEfE zdpCx4D`{g}BUdwyf=#J9U9SkdTDx=E2UGKcc9y3ZTH7)6hFKR;cz8BMax~HfS-~6% z*x#K(%H`RJhZ&G5H#HziSgLzzy@uv>f%@bIITBFR6I{^)d#;lK0H<`p(3pX2h<_5* zeCXY^C9eTVVGRefnfF%^rnfgDMW)lZ`(o5awBmbK zCqFfu)5F_T(-A3$eVJ!RwmDg?pOfZ9$KVIk1k!)gG4y8)I9lb&+o{>hd)Lg_hT{W+ zACtYQUM75^2YDpE4$D4>B*SO^bc}G|^I#IM1m$lh+YuMX?wVUaSO>n?VPXw~(I|o; zM|Q~$$G;}4M&G8KqG~(UxTAhju=%pwe=JSkv*UGP2HAvo*dIeTKK7>qWf9~fZU^$I zzvF;^67#E5hBaOfdSiR;HgDNE3gqW3TTgg9Ss9ckRT+V2p1DnPIZUp@EY_939(H*R zB*f@ODB^?C>n|#Yf_g{+MXzUx!vs0G)^}I(PNA(*i`tOx@~K+BUVaID1(>VTw<*lO zT(0!Ym`fDmP_jOVC69erYG(UXwrlyS+giBeY$tm0wojlugAn+(Rqrw+bn&xDfzD3Kpx~RiQ z7#Pw!w9ogo&!(_>294cCjw8Od@Pch0Ad|a=r^%e|v?{8eM^?aN@#X)DI(umqIPbxiKAhE9p@1gjR`W+^BwTV^e@th294D6=1_BGVPy&lsq8VeI$DsV8X{ zXEAAp@}htN%T9S1jqzdmD&0=9QdO-YMBpU`MR7%>U8T4A#;08o7FbO=%2YxpTF36H zyPiIIw(AnFwEl71{s>%qP2w;-$9><|<$tp#FLOnExF+cqPN>b8w>df8?>1*IUc2;j zkDaJvQ02{%U6vR6Y?seJ)G_B`KVVQDGUZEyhP-w7*y9AOBf8r)-QUFs{MfY*Hr* zn!5S=@<}Q%BY12a1FK`>AI-(&Q8B%5ZH{k@k?+j}C?mJDrP&i0V^bI0TUm}W_+2WG z#zET3Elsj|yj|^=ay`Q;40UAsjyo9Y^?1uTxz8pr7#aShD&6ggA#xFN1>h~iwgp4H zB1>?=((#b9|DM{<^re4k&36=%nQg|d+hs+^PLd9O7VW*h4=bZ2QtcX*{;o;vrl=Jf zUxB9Lnye@^$ppH4a0c{f@`|2KI$l1tj+MUzKHfZOL;gjzbY(;_7x6af9rN_+HZMWO(E3&}xwY+*~IuC>Zp!hky4M zK9=8iMe4CHCtI@!K)15wChVG2*IRNm`bRy16;-mA<6Q#^bVm(GclRD|q^$J&z2tQP z{sVJc&=J3DFvU)xIhdTw;(Ucw<|vy&aIr;Jzv~=2CKNAX0wY^DjWzuLkdw1IYM zgc0yeuuHp)e24B)t+N-}ApW=}u*=%nKmuNxdZYqOK>Oz3{v^)A|4 zA^%{n!t1?VXiTd%*-E53MRxghr;Kq;!UJ0^pef5CU1Fy0EEwVnsckoNL#Sl}t-@i| zja3eJ48|_M-YnbiFrWyDN?Qw$Sqo_|Hf#XjX>#C2+|=}U>)2g>U5DP}I$0zgTNQim z@@q>%VZ7{F8@~hAZob|m$wF7|p6!#YEB~JD2hqAxwae-xfnk-l?dIz(QeXIJnZeiB zK3ZmX6l0a=BZY5aovbFJlFzvLnuJ(r!4ABIaB~H!s5f7qz-L}u;1iO-?nGs7 z%6-D|;8*2*Cu3RTPe>`>+Xdon5Q59`O4fbu5nsxUL`Wz;wTtLF{cW-?_`QL-D8Jf<5oOb_t}j$LQ$v z#sYGgyAIo|sLTU9roM|PSoaW{9!sD!nbO<^5>rxZF#^9W@ z4Y!T3foiDF*M;?{cJ49e+H<6H=ZbWlR$5x0cWpP98FQ6e0{JYZK*e>@QwL}u>Wk3 zx8daaQS$T(`~Ylh>tz3)pS5dO`}?11ZE0s|DYlwtYxJL~v!A1a`0B`QZ?SUM$s2rA z)O~OP?Y!}b*S)o~&}#=^>0TEJMEaXTOFmAQne3aZ<2X{^_(VM{9JQ|So%7FA2%@Vh_16MbwNE3*6$ zG_1Y7Nwy#U6kCk9_XA*3dVqTKE^Re7ObyOMP;}rRkyh1{q!lr!uB9kHRmU=bY*^o2 zWA7f~-HaKIT3q=UD_>ycV?S#;0fS|i9d?jl+rxalDU@5}0;lto5iHSew_QfYeBR3x zX1VQW&IqWh{x^Td8|%x5i)LeFdalmKmk+TY=xt$xYd?Ej&+}?G;oN~4YH}o~_$EhW z?Pk1Z5cb@sT{gvLt!sVlS_D-|zHXS0YMX(sDXK%IrmY3#Zeb9GH*`rwNeq9ou+vg; zBQ!K~*J@$C^h9P8+r4TT3EzM&Xed=%9htvV(Rc$SHe&|AJ#4%V!&G{9*l3rt-R4!X z(J6mGzw)rbPVt6CguQ>kX#H@x#&grpDh9qwnbL9XT0If}B2l{nP<8*&)Q0m@?Iw8? zu48Krt#iv{Weh`=F@)kZNR%W*D zoqV%-ieXsERhwI2n>jZXOLQt$G?pZ4mnLgBrKT+bpIZ{O%aXN(b1qTLCuTljR9Tta z@q!|ls18uP)gvR^PH$R1LaXwo7ai-qdwpVWm)6%I z_QM`zs(q)$;pqu?GSvfqK+|+wNsw?p6>ivxNKo2eZ+o?C=qq0 z@ys;s^0HI(kzG{MEo4WX{DRCqG+>Dex@4c5ef`d5s^2(5C8P>GQ-}`+IJM(U*9ANA zO{3r5M&#`gDJuoH3*qfbC|2w>tul4i*s@fz6I(3rJK$QJ=(cL^Ounk#w51D1% z#f%+tkOS4_Bv5+^bMYOgdU%&&UOC@DrAE7n$^#74b_Z&Q%z4e3UnVH155PfqLH-QZ zV>>y&71RvsZZW8*I8asg_ft@>bDE{|!&SS$`L2}#-PV;G*`H9I>o0iS=+-l7{M+T< zn^nu@x~y$617N88RQ+2OfR0iHAhH_(s~ti$Q5$P`ll;r6W_x8`)m_{czR_;_zM=>4 zG0tU^&H<*>fZ7^!nvpJpU zI@Nef?UbZVbh;2CCq)xFf4O2kQ=%NxQ)mm4qaR48juBa-FcXy3n;iD z))2P81ME-_VIvKQO8}x6VJjR~52M>d6=6BIDIT8r6Z}*2&ofUIZajzUKHwy$p&NT@ zfu7yriuLz6EP!T?@cQd8;qV!Jxwt=X7Ds4B?Qg#8@5sz~9>2B$po*_LRL7&t7JVHW zrs#1iQXd2+&Psk}C3cCj9>RXM31)usVwkzF5FeR-QknS;_anE7&$z79PBQYNB4a94 zPm~XbVeug6@o@W@YHF980Z579U6!PFo@(k!#X8J?q+_b298N9mj=ToPvLla;tS)d9 zh2n|$cL5VDYYcN8b;($J@=bY;*|faZue_wD`%{~ngb)Cfh(vXf=$)a*IG4JPNzB$| znv88LpMY5+Ly>jW_PdvZqe8_UbI~UA-DX}dBita{*dcq2egpYuenf_^ylGah$Ek9X z+d`YYZxs;Prl6K9<-rq?oivK~cBf`}lamj2roCH@gH*~NVJf_5e;AbK+;d-)Ec)Fn zXSinf;0I^Sj-WBrSIc?yfp&=MlpopLw5249NZ%4!R?Zxh%Pe|mUR#+BMaH{=Nmif> z#d;ac#|n|&x3Z^u?ybH3ei~7o~gO#avIj?|&2A|Go@fpc=PrYJR*G zWVLvgL+|Gdn-xm;p2@e%2^~*jUwVTrJ8wT4kb%RLO!Y=UTN2;@lg}HF*+E&v8t0Xh z*-)9W+YZP?mxSfEVt#7egnKf3i+sgGIeB%C z&kLMQ@RoxOD35gpWlskN*`Z*aMClmooUt1iPrJ#~t9ZCoY>WKGU5XUUMDsrD1lZfb z7B%|&*m>mx`)&4&E}7r}DHtceAT4wOI~!!`MjtzH&Y=7d{y|&Z2Axwg5v||%jwh&jrTklrq2d?jl zt#cia1#Eo;7oj#JYS?moa|9q1Pg;xXv`Q;#r)g&&*(WnWJ4K`~cC8F#a2$Yg7|ZZp zc-!F6#Uk=+$1Vrzh)&o5VhZ{TS<~Fd`+6V@@1?SCDXh8}gi;5B-txPzf-u^Fuz!ZI z1jeoBcy?y-v6FU#RBm9?xz0upT#i=i540)Ldn4DV+~EFix5hMGf=)pi)>tPacI#Yu zKOG;y{|DRn@dw~}NM-_^+dZQUaXpkBxr^@&A+W3>sP1t>#S^*xcY~^Rs7JwFv7t1} z+7hW+Z(U;1$;lFXpUXVQwkk5mdT5uKY(%JxB<9f1tzWs{4gMe~v`nsWT^>xA%pjuhi??i;!<u;FfUuKBE9uC(2PIdjomarr_7Um#fB0gs7?6o1(@_Wj3UpcOhGByRt+GYA? zGhXn#SzoIbPi!7~FX=K_@dGRt+GLlTyhF9c`I`PfllYuV3%d7??afH7=Z$~Vk#3H? zn5|L!Y(0xKUp1X-_4&7&n>vEJLG(_Fvi-~+LO2ROasKxSzK4b8Ydf&M#`fTo{-ImW zgHdRIZ#jG}ljb!xAvSvrd_lPibNTQ9MS}`*?+)|Vte_yrM^IO8V1H7u2UVwMDQ|yj zp~u^m9>&53w8c~t2O1`zJ0V&58!VV&C!&v5_UQ}vPI(B8la3=Qu(gRlK*hee<{s~9 z!Q=);CH+}R>g6FaWUcuxunALXU7Z-yAZ4gww4oMX>s0xK#9XHut6wVDq8I2Fdq?K? zM#7cJ+(DYi!oD|CjL4a^t3sF4_$Psd&w9yQ`xi^vu$&K5;03a-TF!CLee@D$o#yx4 z-b`ggwOr)Zq6x?xC`SkJByERD+Kxzx!ci{U=)p-^kf{Y9S%FGg>*X0YUz`kueRgVV|97zl80F`&NqSew#cm?wQI-%Z~5xh zdA@r#!ic)(3XRJm2n+p2U2&!TP{l@aoVq&2sv~3mfX7q090R~JW>|Xo%-=!WDs{`? zyqdd$A$+bu;!`{G>{3rn5`CMKV_oE;J#4(6?WYK^J6O0!2BQx_BzaD(Y@*;^WAsz zyzfqQ-$jo>c3Fy<09zn8uP%q$^IP=LHntaD$dS|Bx2L#o)eaV&8%YNYkfSJsUjqZN zIkVM@M#3S?l08PeqmR`sbq&}!$0nzB)SS1CtIH;J>h(V(oXCkDTsIk;QCJZ#e?pJM z*cJw5u7TEl4P}ss&DLG3-Rja=Y;n&vyK{Ip+Rn-Mcyw0cF8L16Sc!jgHw5DwSig!}a%CIu*K(q=cv0P+H&?e*>s~*;~3Nk_lLM_{9i&QD0 zcvc$?QwxRyslV;oHE>S$NT((_ty5?4n;hus3?A;tAbMauQ>a*x*Z*CZzkb$ViEBoc z=RIIL%wvVWXe#9okYCZFamXnwnhkEztQuHYG?g;oMb%U)p*^GHO5Kuh>=kqKC^EKT^o*yD&^a>!m~~7N$%NngkyL%D(aNOcyv^hll^&yqseS`GJZHp_zHiy6*Hy7{YD1xj2>M1>LD5uZSYUVP4OJpRXmCEG-s}#;2so64>^HiNd zb7}}B@!KZZI9P+mqWv^Xxy!;@%K2EIt5mFK2k9@zqqJukV2w_6joLL^R&8IOUZ=YX zBOGXa>`)Yt9V-<@Fg4Io*!>Pm&%PTJ-N>$ivvgf#9o&2{r?RwERZ{u5$_A(rIykfn z26SC^?bA(zA#D>sVy~z++bOTQrCx>>)LEx5_D8Q`AsrqG&>DF78ExsB>o}h^$J8>N zopK34F`bz^Rh{x2JoMwKZ(0}S2+2?jhvZnONnqOU$>H6}UQc^@6wXlJChR4y}V=_|Bz7z0q+xobDt z8Mvj0cJx$lH`>WMRI@>Mw6lgim(tEak9IUVM6!E1*pJVa^Sdy$*qBbQ>_I>eJii+O zEpW{n0p0MWBOp~o636@0lXeT@cGpGs95@;!Hdv`g(=x+;G6Q_-d1udJ>gkkOt9aS+ z=WfW2dJef0>bc{-uTalEHWH(r^$DoQKfxWAKKjQ{PyY5Eyyu#IJl=DKb@#waG4%DsA!%vE#_E8UG8Zg3xWBh@E|Gn5=&Bay0-!&sjj zULsFcH#6y!bKnc~$uj5uU-w)fhu7b?Q8{ur*D-1%htA*s59A>9(1RS_vVsCRJj%yD zIsBaGZpe)s_E`Wq{Ota(kb~wk_Lxcfwjy$>-Fshwc!xCP zsG)}zLljQWQUW~`i2Tqwj{4+dx8n1oH%#koI!PaA7sEi6Q`xT?q5N~rE~lr6+(gr= zIY)+YNh7XzxmBFu%*X#OfPB|0JEH@Qn>R#u3^a{CDxTGFMi**)^d)kSjqa7leo9PE$Qn#i6S`ilws?KongyGpj=_t7%k4ZNTw3 z)*ka1)@>%{dL_7he1dx|s00TEXKvves~4=BDrGERs-Yf+BlGDS5_^=Iu!HOyaNJQU zv`>$WkdJGuehG0q(R^>Tqlq0z&EPDW;>}3vq_k-9b-c6pqiP3 zqHO!Zk+y1U`z+Tsu@Hzr1$^IU)n0=ULUZ}|dO;NuN86pU>lr3|jeNU>39l~k1He}4 zgnzr)6Ww~3HV)j;V|Bi8ze#<^%tZlN2|{!oLf9N&%zxL{xdZYj%{DjXN@AO!roYor zftrp@_x=fu|NR!Xa9Wv9%ttZ~5KV>bkOv&^C}pFds`vc*sZ%x8uW#JyjAB0vCQFFY zjNuR}P9taMdzPnZYYcgGqK=noEm{Tq!Y4*GVna)Zlqry?GJ4>@&r{%`woOh}SSw42 zHxgS$R^O-Ub{XRgWGEvYbPr`?)jPO#m-0H6d%OJhX|_bVpr<;zW{t)=B1+kKSkp6H zIesy0IyqVwtm3n(8kPXcs4ITfdS)`1;2{q-GFCzC7l>1FRsV()YJ|%+00I zlibPEXYfbnm4Q0{5x3|Z_JZ<#CLudLf1LI=k#lr~S)UKZ7O!TJP|Kl!%jn8m>*|Y^ zHP}ct+1mj#rM*2c_>+TKvv@6)SD>9Nia;Py554 z6fRrD7!1ggA-AG2vyP9B)D1>Z$VUNP?M6&FS-#9CrZ(C5H6a-61>OyL}Y)SABGUWg5$lHf;r;aK(l;~{Da4lWR+D9 zl3)m+yH0*dvGSS79Ga=OdzR!R1Cm$^CvtKUECO^WfU-a*f`G#6KQcW_kuvcnR=5;uMFFP4yE$zLHIvX8S#u|mYH5ZHd{N`HlL{tZRwy!w1!yw#y> zUkP$0%Pu+2tqzuvPq45F<VG(n5f`)90p zYGUaO$Y0$vw%!eiS_oc`YBtEM3yfU`Wjjkldke}xAJ=8U%h(TeQzy3|ATX#$uVLBR zSe)X~! zWz1uu+K2q}-?({H;Icd`Fjpxb(*ZpCd$;_Q@pkRD^gPL`OYb<@3+Id(J>{vb4w0YI zren1m5L;frwx9CvsUGY0F*uv9+fqhVShtt3 zh#6J)y1lSLUSYU}b=x2#dKInP|2*MC)+T!b+1IVxDTDvNuiNt<>Rz|sLb+$HLY=H$ zTeo3bx3?Fs+Xk~w%dIf-88cY7+dT$zZV{l7U(@J1SqK6O>o$@;U?J;v;f*R-O_VM` zq)2;Tw=+`X%;~IPaD(BjRW{Ka&s>&NiTQ#s$RWv+30NFs?PL-Uwfq+HW+p>%O_i^f z`F`$Ob~sQdXUMnDFskm7p)g9H`nseVQJ^cUo$n)&gG?WJq1?FEdcoRmaMlV*j!g<7>RX{J0=fQ`Fr6oI@LgOZh#tS&fN(8l%<5?kL#y%hU%yPD)UB> zX+xej>ZK6oDhq#8-p^-dzf@QcLM zS{T-%`_22a>UuZ5s(uA#H|Ege_y0iM+f#K7zf@*-snvi8yx7A?41$A0E$<-{F(ACf zACN*3X&YJFBeVT@&Wtc%FRYo#q2Lgnb%Xvb77XwbkKL+mfg0R7^0(+-y4=vZx4n!B#cnbF5{OZdFP5=HK zTqbLmYUf>1d>uOE3CDo?Ii9mr4tE`Q$ZB{h#-;o<+khNY(>|&lDF(61^p9M_)aZ!j zIeu+%og*MYt+Est*vT%H6CQQLRpvIpUbctg%z|z?-5 z>KW*>yRp?uU!&o^j=NAc8gwcDphPLKULJ4!PoDj(xA~*0=n4d9MC4a9Bx=vo$^ojZ0w=;BZf2E!@fhgF`)(0#l$&29*{K@tI>GC9Jr95#dTP^ zUb)bK>)lTM&#Y9B8*P>uV26I$5<*9MGNLCW+_a*n(5jtc8rRK$a5S^8%0J!yRv`l# zB5j8luedf(v>}_{>3ZT}re8CO^1ZF*oLOp_l0)3gxkFs_-bHld4UsdADAF;^8au=q z3uZ$E+eA-U-y05sZ*z2vos%`NMZ4EkAUWmCaDC{WRyq-5+Ncs7+IHr&-RL28kuzv8 zySENOIzO?(a9w2c!Pd)*yA)*jklT&OClQ35A#=0*3en(@nUm|lH=5WVt^=3;QP+{_ zdli>|h@pwqR;!tT8-wg31aI5jAu*O<(YCu&e(DTS;Mi;pY#UiLH$UTTyE|n7{V1e9 zh06c7?cVht!+DQw_us6nd)wWixcmRP?e5TRw|Lub)jCEv3M>QrDh$F}_I(WZoKLHY z#Ejc?hwJ4yhzz18*=kOy9K`Yk2Q6;jdA?Tm&4W!o zTpjMtI!@;}fXbJ4qUYA?!Ej-L$b zqMzSBbH}>cFUW)iv9s^4bgFvTb%~WI`K8@6jB`MuwnOI~bI4lGHi_dYEg6DO(gq@U ziQXl07p*JNL`e3Dw4tg%E_QiiW#f2EfIzR@Urlg%k5Ck)gUdIh+KIU#blSqQZI;Icf^LUKabTnV&|a-1oW*HvOt zF2^CtPrK}5{?uGnpB$~uQj8*C$Ht?YZ>%V7hz@^o8*7nas;gAt<)NibJ8y2<89uC} ztZ9+CA$X^wM9yF3k zgDea-2h^y(wzDqMn=cW(aF3ok%=A?4j=E;D9g&`p@G_d3z}l+0ArL)WiA8?+7+MX- zx0~hiaLn z$v#u%#qa1AvuTIE*x@4FG_y7N?R}?h?___$RqTWEjGWy~&%^!1m>$Rbjmmn5mXsDB z?~hh1p^gF?549XZ4OBFsbk|%&Lwb439$el+9`+rjv9otOy^mGp-%=GJvSTmfy)SGx z)@Voi+a3Dx1PMf1^>vxt&uf5$J9{=UHCm1b0Ju~OA-=Y;xn1j)$~D%M^4G+oo&EVPH9bswMs;u;@NdC=i!4)CkUM{hh z!6H|L?=5y(W%_$Xnovdhoof-l>F=sds&Tk{poYEVh6LWRSf+Q5-4N|1L(XzmZM9JE zy(B!KyKb;9V?_sXFa9O~-m6wLiSU|wapVRt!Nowo<0;o=(t*$Le zTK9AmSanXmP0LPrcF9TZ8673e#j7oFW<+IWsO1G73O08_WPO`^d#ul>?U@GN;w=}D znLpd9$x>n6>I|c-8%ek?Z;LGOp}0ENf!9dg5xPh7h+DQhiy^*mx&zR?E+VsIsjpfD z<+}_iKYEKB~KNqcgi@b>x?*sR+Y0C7Xe-IIU2Kcqnb<2s)&5c zk8ZPLGv}~$k=mn2N*zJ)n9$jwB}$_csg5x0S@HOSIf`BsqxE!@9H+-bB->TTuqrjO zjMwh@oy=((`3cz|x@G`8Xri!c6cXo>tAeIOb33+EvnkxuXojzC|Kzt2LVX%!)ddBm zr%USY^OPQuS|0tKSPjp~b=gDw={>x=+5tt?_TrfwFtgY``U*v3W%^zvmRVO6hy{zz zFMLW7sdUe1;X#HR`-Ua|Fa>NaL|_V7mpsR#56f?OX6b!FgKT8H$?ufBGs`jUI*&T- z>`O9__|x7gF~0Msy+by;>P)**iE7SWb@F_nI`L0bN3-Dk#7#W0z~FxEz2Lsbaf_+? zL)bo`n%RkgKlOHE;LVYmkXnYJqZMX2gNu}RNKl=AQ!#WRdYEm&Y>(P5U$U;v%9fKH zkBa)jVE1y+dp2-3VvO}a58GHlU8f`WTn=&-T?r z;>zZHS@e<~C&k%st19PHFcL*g>WDBE5q3Phd|v;z$UUXN6kZ~p7+^^i4iJd#u1J5u$K z9b8xUH9E-os%bQ@AyCJc6A= z+FX=9`_8(l-Ssic?WTsPRG2FNX3Jpuh_80oNpX`qHm~ls?EadJ*_?eYAVb5NV4R8NT|A%4fi4 ziL!^p$|I=|YWce|+HrcRQ!=GZrs7&&Cm)}x#=F#be#l-Pjiyqk@pJO}xc(Ugt`m!o zYGvPXeNHSM!@ne*N~J1zm~0o3rBWB^^QetA_I7G$;p&qodyTQt5ITne4s!ya zc}M?H%PtyshfWTHh4+v+sg_<+sQ^vMS*%r7O<5|gEd!^O{s`NAGOmw^mH2S}avK0G zrR>?S)z6LWL~Umb0)TeP^UHOhsgxDo{bSui$EGrhsgVu(ed;21tVIxSFo;7fuk(_H z0u;QjOWJJYNBUP6``6oz+kr?A#T62R+4Zt|A(qC)24q}!h{^=KajXM?d|VOPyi)%y z3*GW-I`A--U#^G-ICcuU7v+9BKTncgkrRsJA!5wcp>BrtW0M`20(lfnEUa05ojX-EpsW&IMYU5`nO7%3on5()ln}s=^^cOj(Yfz!0k?cIeSHutg&Petn$in?jxy!c!BX_qqbuI$6MA#GE;lC$k@cHX3G z$$tX#1x!5_=p{=SBj%&!Y+g0#mKmVd*2{8NM=!0nYN@Bx6u%|9U$NKV^1l@@H_~ly zIAc3mBVJ-{uAv6`u4^b@tz6_RSykTXsU}aI3#-YT0I}$=2lW?Kz2O98L)L~vk>Tke zCYn5{a0@15ldArh1KkAuj?n7&->mTY-r-&9-+)Oz-pr)_zR>RuifyPnp^6(DbglzA;2a+e{oH1it5$7>`N zKhbL|^q6&CQL|V}V#jqamH}VWW_dED)mSpWkV`YlZS-gM?!@9j{7oz#&cCVn4)9S2 zpXLXD^o=c$hr>KMRLBBk04Q*d{!_7jvY75kEbin>cdJ787hHPgD))As^I3%)`y>m? zsz(QVYYU!q{$Jh7itGHi-D{0O7M-47u2AKdD^R+~ih;>H9Bbrkg?zS6fr;(kjZ62D zoHSXN+~+n1ZAFgKg2)mL`Az8{WtclQ33y{p#m7)u=b)DJmKvh>@aq2PPDn7N6^OGR z#}KNOX1HzjFE)hk-W4dnVyI<~_Vc&DD(3auOoV?3dW{d|Z=L+=m)1u=gcrX#(TW{O z%V&JkT0ZG(S_T@eDWyP@y~k??twCFZ(8fGs?FEx5EdkII^T*|v>lcxZk=AJDV+EIM zC5i~IpI<$?SiWVT>et!3Qt^@6uB0BfR{Pg1*55^Kj(2U|^EGV(f>t)d%l<`&uRoRW zg8WS^uH;`8hAZ`>Dm|5VCm z{GA0xwcWtFJAbI zdAe($H1i4BQX>li*#qsdgy}hfzAF_W*&&fR-S1NNE);)+;WnrAJ-SwyKhUP4QU38O z>qK+Cxq|Y;tYAPgrPaWCKZ$*;Sl6H4yqyeKK4MlUH2^4IwE|r#5wE5aR_NHw$Vi9N zH6N+f#Ip<=sbmzNMGefSfqYyiFQce%OD?kXXq%E2K3AideW@{1M)oP|r!K!-nX){< zvPI7u90|K`>a5Fdy3CwLEeC&M@lItA@$r$+y&GeR@%25S2nkj?aAXw zD7Y+(9$-3+J<46m#{!MnUX4pe79C~S@BN`+KQoCXRixF=S7PEu#C{1kp-1cM<;DjG z%quCGk<2BQDDPi^~c{TwE}qL!>1MV+j#W(v@8G9kGUHT{q z(OlnrE1LQbzU;ANCNck+@qFnzAE*0;=>Bdt>LTNhw8=Zub)T8Qw>7qUV}spnT+GlS z%ba$7FtujP$Q1m?kl#kf=Qz!2AGW{1FYQc%qugX%RYP)+ ztZd_N2|+>2^}$zbx6pqNwRVjas9Ng-a2IVUtR8qeziwK*7_q%QkhY(Piu>HlEEt5x-utF` zJ3rm=rO{};6uamU^N)v|O6A}08a1QQ;;g<=f~d1F-Gk7L*(o0vS#q2a-4?4A#?4mO zPv(b+D$POir=f;k^jv;iaxW9i25}{_2}G1P;_%a6r*8r%N;F!u`D<~#dhJo0y=1#g zirbf^nfF9s_wHl$@KfJ1@ht(!WUx()koda>_XOtKZ$@t$~F!3drGb} zP(+);ocSVq72cAHmCcvmhNsEBv{kWF)CN827VRlH%-ZP8KB{9Oi<~U9Rxl@Q(Xkxv zK=D+vmff3SbGSt&H8Y&YBXglS{ke3$c?)u$wk47qZFD%%zQeh$O_hf1IyEqCX5gK2 zgX@lDo12TeJHvfewR?9#-G9D!3*9x7cR4Y0Eh!ASaV^w*8Z0+q$(>0bw?P&i!19W% zUHzAe@>saJE1cYEXZf)W*k*zqO79D07Ept(kfzI^_+9e&soh7D%Hj9J}YvwHDV3!yvc7cx(AJ2>Ke<}uFZ78a6E9y zuCARoJ+78&8)EbWThGtwK=>@+x+Gn8WXULGI_6y#Rcma&NQc`2-TSr*BL-t(_f0g- zC~OMOcfi=}pW~iAf(po+@95C%N(Vn6b&VBio?s*Y!S0KKjpQMFZPA^M5d`FJE7HYo z93~3}z1xoPQ1A^Sl@eVbVH zjdvSHmYHE>nJ+^$l!h&UAV=O4(t0jZ~9ll-7Wkx;i9+@4YuB{gFz9x^vuF#|k^^*dj~l%g#F5rNRxA zwbvm#98s5XfL1s({J#Y1;NqaZ2jeVI2VcJb9;m~}tf%SFAR{vV&!aQwtdQ?CsX=Fp zECAlG1nqdIF>TV~42_(xv~y^Q?F#SZs~7kuPi^w$Jf_4ua12VlXO?sfGYBIm#Ake= zdQdh|os*~kF~mn#ntvkUh4|>X6K4+2y~Gdk;h33KU?D#3cHp@}&b|NZLwuH9>w+8z z@%a;rqeqC(t}}KE@o|S-mNFm||2fswRJ%fs`3If`-61}^-2ACE2EtzHQ(RFtLqSUAC2U#pq?OENF=${DpQ(vSZPLcVPx zh~YF{kNm&w7(po~I@&u%m*1oRqH>J3a2|f|O^B2EfXz^=s(tkST%X#4@&?bGYz~J$Hv(mmow7d@)FMB zz||+R0fjER+Sh<`5RF^KU2-GO;B`ItD7e0k zOy8(DTF>Ul1tG1)My+S=RZIe`2m<3^!O@sp<0KTELgZ5ST;{F*cDdCZ7O?s|-RfsK zfB$xW^|#A^eptNvr@PwTxwEc*be&TQ)r0a1)pe@jyNgy~?e10-9plUU5LvUbVXgG`WRhz6_!G1Z|d8wpJ!^ z+)%XE_i|L_t@X7s{1>{`LoI$7(~)pBCWrWaZi{lyga@oGgfXp^pW=aL;_MqNe;39y z;(A^B^yY;zy?_d(rwZ3tq`=xbdFqe4sEb>f>{`XXd3`=TCbEdZ-AQ+#&i~CP=bXq8RR2*K_$3ZP=@iWz~J+Q%K(1r2E`wC;s8 zJyw`=4mFso692{%k72!edN;%R8KkcLb~CKrDCflt>*CE{VOU2OpbE-CJhQN_LOK}3 zDg++Eu)>aELGIdOPlnZxW{Vltk?HdLSd1qCn;i;rO+l~35JH1gPelQ~nSK;d6|Vf8 z^MEeVoSTQ7eCr)(KpDyg)dNr9}z#W&w=7BLQ56_4Tm@;h+W$#EArk+dal*wU&d6_<`bKX+dPamGxq>LW9b`;AHh^O zkjqt6EFkNLfy~ZQT_O!+GBgDv)5jRdnO6*C0Q4X}+qGU4S{)=;BIsX}nS%6HZi>#J z&EYsJOGhb`;zQZpOdr>b!tiq#4d7S!P9-|J=dssQ#(qXUMLh2GP_r!z0uVc8BwZ>- zeD0L%;ky|P8%A@|mui`5z!jOucub6{7Zc1WtnLyTdgaB9yi$1dK79qmIabQbbRPH} zgim$KQ*d!$>rFkG^QKwT_Eka&wdgj%p_#3CKBNEO(j96+dgR>>I5O5wG6pd z7ic(%i&r7M4bQju+f2M-`zY5Od;YNLhrd3^m|adzG3C%PrJS5VGrDiv3Y0FotxVqC zq=Fb%)0r-~4Ms11aNBC$S#X=*z!%>)WeE)D)TMsXFTkb^Pu_XipT?YA5mnYjeA^5L zu3#RcY~2fQtCuUSRTta#jdCE-*tU8(*NPEc0gVi3*PymkQ=3#fLh1wgDoW%imb`NQ z*BJxJ72F!Ec1_}3awZL0e1RpI!`{&5P-6$+9;~Olxyy@hQ+E`cV~psCAaFyu(6o(c zC+b2k;^i4HiJRpE4is7(X*6<;kI*pqroypBMAi0kvu@**!n7X7(@?Sq@p$zU7Vq zy5zTRKts?ay|_15Fn|$ygZ<PEGl7X2o5 z@_RbMSxuwgIttNmLdtOmls0t~B&t<3`i&nSw7+wUeLVXOv0d4w7EH&q={>Z&u7 zYOhmwKK5YQd4BZ{x!wq(J8-VOh;d<;pT{(4;M^IO1!Lfio*~SM#Uc;6X9UV!lD^930KKGDo^a2`#pY8-8LK0aA3+yR(2=Z;H@NdWjitY^ zJ0F=YRUhW3+505*niSQ5d&Gma%Vls5+o_A3$0IQMUWpwtmy-huaQd~a0kIvd?%7yc zw3hDY|9N%yoCQS>8hC@^tS*?IusOa^Jt%9aj!-Ld!G!bqez%(J0(mkpe|#Z<%d=c_ z>m_jc@A+B^3X{$v4eX%}SiY8(|5Lse@thYeUyH{3s=}86Kt{l9gk_>zw=j6*R#-s6Y>zL!W&Bc;n)4w?Yxo>2^^on(rk6TDIQWO};N*B{xa zk?vljG*ioTv;6?oEtDc<*V{@7Tm%p}<(#jtZ*fqRHTF)NG$XN6&qxw0b(fx4x!L~J zLz2WwJ!=`&np(Mtzn?vxifhv(#lY9|GgJa_S#4=C;6uqj>P)<7rwE-$zUGx_>Xu%& zPb7DOF{(zZ(AfXNjBdBwYhoW<_Kf$F*0gttb$nxNk4UfaCG+L=OF@7pDRG0i0+yB^ z-GSBRMRio?h|i9l4#LeIU~t9ET89>s;1ui4Acf{Rq-rF10nRlI{ebGJmlSj-wxIo+w|Bd;aTe3Z18n5Fti45gJW3UM;8 ztu|QHgrEH;8)9cn>NMpIK>;(n?IUQ=G8}8yUc##T_zcCv+hSaKh0m$F`$1oyj$_p94sx9|xje9&0;9!?YI)wBCL8g1Hj>bkx@D zDrfu~nXbB@S^o!Do_aCeUyZzf1N1o`vmv$kUVR{^>$@KvE!jVzQ5B0ORgM7lV|vn7!EGqO*kL+iicJLBISFTl4h%a9&dukF5jW1uTwn94)4mZe~ZWf%^;(4E{H13Ws ze{Yqk4gW2^TxU;v`yJB9=vyQ?+o$hNxrt}!`=#TcZ!gw7vPdOyN~vTl#n|wYGjSL& zu5&AF#goJfl+E;UV}J=6*SVb6%7jxXl@E!P%ARG4D=bIJv_cgQiIr>h)dqT36d<|B zBDs|tI+?9<`T~BboYsz1PQM~&vF$U?qv@+y8Xv9fmeT8j$^oDPD}B02r1i;Ly-;r7y((>fCAy~dZ*Gl8XB zPV3A6E~gKt?*CCvt7M{GMJ$q8vD_`E^a4fVZP0#teJrOU!|fUbo(DvD(X1xBYluP4|P(}BQ|LT#%_X?yDzbyKDplFej+oK z5acIRb~4jw{Zif{Gxbtic0iuy(^v)_=FzB)4m}BY{tHHjc5pl&Y^ahQithpisd!W| z-KaM~r?4KW?dC6vnmPzHySBM0ToP&-%_phULkhxg;o894TeH2PR<%>6YL!iwyy`@} zyJ^SOuR+ARL_bI6muqcHw@@z|pEsPE_M=nkHUA{*A@iQWr9;%53$C4`+=xQ97pa#! z_?)Z@t+rbjQW%6+d9B;H^l^iD%`3eRBouXZDqZP5#IzM(#2;f|6>UXA@sEMS>%gOs zv?j_N zS<@7bj;Lv>j2_xpDL;n@Q87NBfu4&*zxQa?kcM*4s)&&+E5qsWRE>oW;Maoa|F-ZJH2X-}}I} zgC8Ld^Fa?z0`SLv9;=|UHKYd^yFPJJbPrgKYLKcun$t;19QhYoM<3L^o!6LmB{oMRc3)Hk62dG|1w>nd}w$u^n} z5#NtQFXS?wlzS)p8x(Utj$AMfJF#Ed%ay*Giffw=MgBXL!A#6ENqaY*4cY-UwZUum z_1=T021#B}geWp)ShkXX9IV+&{4CzbMwayLT`Fgt=q&k~^+p{4-W;LFo1yqX7;hwU zVreN>q!kQ7_sWElqg@U3DJd0OAp-#~xjvIozRAx3KT(efkR*WGHqYp(Rc-@4z-?&6)^{G5*-%>7G@Yzu+pFnTPW zk@2m4OQ`Y$RrsTP06!xj$V(+(+jz7vNpi#sI6)-SzGgYw$6k1AtZ!k2Zly*#P`uKk z7NafpKT`J;K~@;1!PJR(^(K}N(|_Yf>c5L8@~@nBk=tCP&d^`S@e84WKt4@>9_D@` zmB#4LYWK5z>}NQsQUKaYVrMos%;FQj&Z_f!wD4Q^6M1UwFX5*X=?aeA%}=!5?IUbA z=jJ80wF*eHtgl7rd;o^( zq3`7o?Q2{es7}R~D1qmfYc-hQ4ltrsVj+i1th=#0NDy`FDqS_(tv2b!RxsS;;PEsN zxyE`oB`*Jl%B3Ynxx1I%K`G12gL0%n`3I+B_e!Po^T|Lz$jX21%6kL7q5d7G3sFzs z%jM_!gVAPWIzMSyYtq7SYa!xV=o7hyRZVH7l}>S`YUVPmcsf>}R7&4VsL-x9A|?Dx z#q0D-A3e#o<}dRx&$S)YxM>N+Ut{u{AJ!=67*srgNcr(NgzYpU+83vao5sr8e~b#R%XOq1KHhtqfIvO0CR^V8Ww`SkWg z3@@~t{0HWz3#TT=9X#rRPBzC-%Nsn5Z#ZRuE0AO*3+^; zog{lUiWxj0p`u31U84tb#4F!F1Y*}D_-vVTatXf>s)yH0WDxoW5ZNK=>kaOauYY%n?b7%RQ`Xkmts!85uM z;hS8}r-hcS(O!*Z)3cW}U)E)3{*9&RG#tny9_PE9@c@hf_YSVp%Cxhit&4!%LKmJ# zr(vegHQI}7KOkS$@62!Cy8qVZkN(xXra#7=afjKUd@9H(z~g*?yX06}bA-SY%IneH zA?}jLXc345jZ>y)S^&h&bFQx5&gJXt(KUhz&oqI{m!BFudbkJg>+E@p;cd8@?Sy$i zZ~#i%*+-h2I!j{*>d_3uj{TT}RGu5l>mz5t4VXiB3}z3StbnPyoDmcxb4hwkAM0vUk#JG7rX=0mbz%>lg`>NvqqaI_ zvUTidiN&Vz+b-a7{+BRQbi(ayT}-V7&A4EY#A4lfJRWMnIa>F16?iC+?ug_|q&^D8 zH9rm)RJUlZb|+2}`^Nf9^XDp1LUHv9OvSrsfyn2T!e2QQwgRQ}X}l`4oVSttSwTHC zDS2k9=|1mydQm}N+UHy=UzeJYAK_FcXX17`5!v{wEu+QUhf^sXq2vy7TFyBJ#5M(j zjg-FfR2>z0s(QtaadIeSdm*&vL$@hsQm{p;`FFWN{w|MP1Gqk88(XU@JF)1Sc_P1D zTXO19a;1Jcc1_a7%k1Y=qrL51Ar${9^w>CbmL+MM+8`Wga8dKDvFlO{Xexz^oXRJx6*9GbGqWju8!>mCye!_l||HJYxHS$ zZR1t5ve^7}CQBLz%p#3fgZz*mKG3^uA98-V>Ou|jG5X(Bd7pn7{km*9dlA((XN`LK ze7R{~RX4qSnMQqV2>{(TEC+mLJCMI7n7<|?R}6o>W?@u7>%lktS4h zzlSrkZfGL&dDmE;Y?DuaZ5#7zljDx0QY1pTWpGGLsE$sH)bTTaT~JdxupNw^78z4q zP#GN+8D3m4D0)yNTwE|Tx@RO%Tre!QpMxOTM)Ix!k@>7`w$)b}6c^4)t|{KGVf0Sw zF3dab`shsbpKxMNOlgIYHddtX(mYz%aBC}(?y=mc{QBN2F4OI3MWSiv`raGpU5-5p zXQ+7rcBJf5HErI!>cqT3iFrd4^M)xGLWA3%OK<(1BAby+6TZM`t{Dd?Mr9P%)}|SzB8ktM)!1`{{*u3{{_f8 z6^+1grSvY37>hxKWF(U1^DFTu;w5L=g$lzP7cR<2?xdb;)yia z9v#AcIg{)fx8~J2;W9S433y#YjUJc3nOaKP?3&~xQY~0+KTz@HZ)}}ckJ|1FmmKgi zy(iLylj=BKs8kK)uVXE!r$n+XdyIMm5I7}2LRi~*;!w67_+d0O_EDkiD(m@u150KA z_Z!Wivp>o%7F~R6XkjZF{+&*^R;15X6nAietD(4gc14M!NEJflj0WF>*El_BeeX?B zr-%9b7?L;0iu4`Y#{GU9UOm3ouP=bl=r^uaxw!D1aSNtQUSPtv*9J5jy66&^;dY92YDfNcRox^nKJyYsk0L>}# zbU{A`(}Xk*qzw=8fwU-u2U`oWb1kHURcnalcj_{`OkQ*PYF4{td~7pEHjP=~#OXNZ zg%f@969O1`U*-D(R9>5}qIQDxrP}9&Fg&F;N2YLVlN0jfv_SHN#GF8KN=2eBsO=`s z4yy`!22noYM1XeXhN4yrQ~gKU^Cxk3Xt$~6T(SC>jAyF${e>R8c-P)c)B`dwa4+Y~ z=7Y9wic^wEdv30FXo1gA1m5+A;}XXEClpN9{UC)+)$T=5k6nlJEQ;8BbO6`z-_7Nn zTCe_UOTrUkmB~PSV`5TebP)4;z0&Yyl_ib%_EqY^6iAi+$0*^9pIONNe@1$|g1>No zMV(s4CUxPiTQeQI2cGk!^uG!@{$dH{f(E%{yy_-1Kx?RF9K|)Wg0T=o{M6Z9U=BBQElwcUjhWUB? zWMF?KFI>WN``K)FPi}o98Cmn!k&P@MX(jNpE8-iY6^YVpnOb2hc4t0C)!+R;R9Dj= z3tFkL0=i-Ou+Yv%nulX5{w6ZRMRwW7q3MWH&1)!9$-$M}mON@Zrr8ee^q#aO5o^o8 z(Y)i3=y&xRihONb^NwSp7d7t~6+ORs$KYta-W8$S;das79VFec;Kx*~L*pN1&qwKf z5_yu^>r^(WNm$W4YI{wrJxbhYyX^ZQ(->>F{>W!!eZ}sDsAUV_6bZx0a8<11%l*W zhSAcL{`ZE~joS1#ZlP*tOpY50Dqc9Hqy)ag)`~;+MfkO_`b9P@7Om7s5W`=CuwO@2Ye0!`BFZ1NO+0A20q8Bz| zJA%scRn;^C^B6~E!=COQ(MnzEz}cVIc9vAcLfdOQ%c9sV?IQPVMe}8Cx(m0cTHRw{ zUp$SzQ_%@!vzt#Ui9O&*Tckfq2p11wD>=w$*9*_dS84iVTEO4t3aM`WSrz*{W>U0#)YelSP6F};eHeRQ%kKYMK-#U;nI8H7NP8PNtH$*Wc)qHg zcFo4bAPgp=5L0SQ5q*sqCZn0651(UBa&)vuIf^>Fp=q}X`3psqBgdel(sUG)MomN$ zVbsK6JJxMBc1lID-|M>9v-VyyoO<8iJHOwa>-o69p09g7>silw)-LM+3h7)qdbDqS zQf ztM@9)$59^D7KhYx@QUaME7#yg+u}-H&W@t-Y1UnMMuWRr%zixQ*I4N&*80kYb)eyP zmJkEh_T%6cRx;=uZ1~ntts%2=`B3iuds4q zHKO*ZEnJrU5kvW{Bv=`%U3Y~9>59@HTUWW3SQUuOi{tDyWpNmmg(|$9kulk&Iqm zn!AvaLo4||edaGLNChhqj~#Gh`YfI_>hkn5&E{1&sLc3Uclu1;V8iEyYLPj-(B=ad z7F3X6O+S-sc79K;M$2=?XIgLI(!(iTXaxvR11Wg-x1IITAS@uIb0V`>bWXM##Sn+op}H4IG{KUiyP#Mt)%sF^Q@a;nA;oW zo|InE4y|VQ0t^mN2iEdG&Peik?RKgFe2NxWS%~nx%kCThQW%v3jtUOgu zS%h9+g}&tuzFkp~FuMz`2~HO^L7L z<>A_ffwS%@9XKGAGm&B*J#5rHA{;yD{m3g4KH&gA!e&t}biIa`;=fgeVFJ0SsDB>% z$!iLRL15jH!Cyk5MTia8Lxs#^jrdroxC5r!&Q4DO&4dn|7nEL6!t|A7w7%(W&pvgZ zZh42wg;hwE4Q5o^M7GiBOUNoJ!DYb=Hvd*T4d;1M)b;;1#{tbn3>}1NXAD)#?GIwu zWEWy798U>LgSwst$5Am4l%a|x_>bCOK!{&rPb%G~DY(iWy;nmi8+9Wnijq{`wsPTo z<)s9SZ8o5FH6{leD_C!&$z4dRxmA>Cc~dbAa~CqVyl1W!HhpG32<^7jay)(!oTx4i z26Sr{h5{B}W6kLl8{&4#H^1*;gxTX*iosSdu;kUo#AJI;EoAE^xROOU3K2`343S)Ks17t;5 zRo2p)`i%Fz_ItI0J>}zY)~3!=hJ`|GYb$7vM~_pZOhk>of}RJr-`VaFWtM$ca~XJX zJ!9(|_G3^yxA9IFUKO6Q}6sb;6{@`BKry>a}!` zLEQ9UDvgzM;Wlc_v_}Lu`whhM{ z{pzX4RBs$y&oNab7XV^|aXwLPs9=HB5#?d=Z1*&#Htw4WHrtK~LK02`_rUd12Qep!<1P0nil@Yj|3X<-zwYl+=BC4S6IvK*3mCc zF?ZAT>TvL$-wH;bdzvpsFTo)!{?5FMX4>!}B&i%+SKg-iWSq|tEZB^~;J7gy2@AdI z^J?@;SDp&)pMqhDjK{rw={POf9D&U%$GHWnmaeIYviVC-tEh5O)6R@P*!yt<{I zs78jl>dk))onml3s@}TMo0#>E?^)gMSv2Qm9k{yKq;qF%VSTcnsq%s*-fCN!_SE)N zGy~eK*7_*dbQCOjyns%FrH5;>M3IXjJ;lZ`o#p zqq!6J+&c^>!AGr*pZp#x?VpuY?!kukr1DFe&(h?*crCBAe~%=8H%-P_==zyo&D+kr z%CyPYa1?)mPVd(aepkfxj-m z8t2|2CDw7jalLtzU*EeR&*Nws9gQ$8h%lbt;STI4`WaK3gPz2=+Ka1sKc_we5YC38 zq7v`~H^@t1KsMeZi&%qPBKsmNcn}#I(!5S*E#tdX_hA#>k9(EiE`I2(ky`aGsKGwz zDyrv5TBYuls}HQ6c%p<`19CLed+FLTV?Xzy1=cA?xcZ4va!OpubGGYdct4d|tJM z9Xp}ily>l+*zpbS3)rT%9r-Qnn0|rUQ3ah&eUweWM5_8*R!J9{V&P49fUy6Bmp+xWa1t+fQGkKCbX4nXaG({JACgGGP%p*9Tv+jSyE?00Rqh`kPfpsGy=;h{u1=+7_ zLi)@t7{c~E6t#g}JbG1rsRO2eMMVL8ZzyO#9>rWemlEtz`G>h}POPZ;mc%q~5rl_y zVuooRXXk}OtTQ!(wUN6}E3pE5c6o1N*Q3>5^R>#gjS{o8gRm$*wNOji zhtV+3ERBY?oTX8G%URmX5TUcQC6r~?ZXb*!*qG*Y4BCv2aVv`xshdSX9&^D(VvWB&}9*%;@@_G}C_VGJwlotTWJ zq4SvDqy5)93Oy6%VY-rF4YsPV6@RF(2E0oChq>4ckZ~@iWitIZ-ajxGW6?e3W39i> z(Ye@VW-=9nXGZzjIrV1dVy9x1{BbU}7QC2?Y2oadTrUi2m+j6}>pW0Aq^i~ z8WSC09Y?F2FAil?kET_}sa57%&EQqzBG$uj3Qz}`52EElljFu+V~p!>8yB^TjB)DJ zn_!i79egZmTIW?kSa1Vw*|9$q6C(SZ_oyCcRzHWf;^S^sd6n9_#afFbPBd_8#$REU zV=A682l36QiVuVD`a6~8w)M9eGGti!-ii;)+vwioir82`wp34T)BWAb^!0%=l3Z(+ z*d-8b2>z5)gPrbs{ik3+^<1#W_3lgNpFt_|s1d@_8e7#O^7q9IMX*$7WQX#IN*?P5aX?4MyXi88lz5!?JM7 zR&=V3@(j$tbgdA(HE>JH=hOQG8+|3?v4+eY@F;W=)}lEUBL4N`Q%XSS!vIGX>f|*n z(UBQ-VM~7f675WUaS*#9oUah3&zy*X!*zYvmk85mUWY{Uwd$dmG7VP7> zcZ|Kni|qJ5b^8mCo1E2g75_i>j`R7D*~ZO1bmLFQBm2(%^x=Zv1AilPQKiK#wnU*8 zYwhP4`7Jk=zkQCHcz#(8`b&JHRF$3DCG;m7%eQKty#xQu;30OGpAEuz@y4PKbKW0k z&Ba}PypZGjmSv`6clr0Y>7*~@?sA-8i&xjCt>MP9mvGa>$hMbW$~}>l%#U%0<$Dw_ zR8Vc<6n}V<^ol0L%cjGAY}KwtN&j>cdbRb)2io6WBX@bV6+jg3t>q^4qwJkl@vl*a zoB!(UKv(_vXlA(iI(z%M-Nr?@{-Yztx1XaE)E8Q}8_5j2!6jrg? z=^IDo?O9~)J<8+-)BoH}-U)T||7JJ2b{zPeEWG!@tsK}qlR@;^ozC9f&K2sm2W(ucnJgNLx*5)5as#yU>~ao$E8S5Vp3e=ajh z#;2hnr`XAqg3}OcnoqI~j$IIJFz?`8)C_KYW2GUu-nkF>${&qeFZRV6t#i3rgvok!iGTP?fy>Be4 zFe&v5I3N^a4?Gu|Wn6ju^#GiZcMu=NH1CvZ^3WZ%7Y3fw?>G zF3b>=5TZdn*nu|;QxUTn^VZqhAxZ;T$fu~%{+A@(*|xO*wMloi59L2o8u;b7$=RiW zwLc;6!t&!0+Jn&O^a?)f356J`3`OvN{OEC|c^k_wEX`Hg^7CxxiVODDC()KsSe@V* z?SUFxeXNU2TBYtvwI4PcKWV>Kf>!i*&i3tXIH9KN&+rayI)GQ+!8ozEfyRlWTz`$` z={C{yna_ftH20$Piunkb#QXWOODQh+HqZFch1Pc`qCPl>80Eh+ebqQT8oo8fWTO)K zS_|}-R*p+4?~r}T!mhTkoUdFT$J2&r1?A(0kN4j$Nv>{lKiSC~P@8d&v_m*HURb{(C zCmef?K2n)lnQX1X1eNPZ-tv>-nu=n5Vi#DJlZI2v;5gWo!Gry1I-Z@+cW0IL<)z~7 zcDPi;DyM}jr#0c_!StEWp@#4zI&WY4Ozw;2gMRy{n|dG?*WsCX)Cc|U#`60raYQ{g zGGbc?7O?Pk56;ic8|;~MX36+7xS`_$>-o2FM_w1)9vs}5{(90AsdYzhuk3_rO>A%@ zwof$Ui=O(rPjfFG>6jEus^lTqU*I6~^}$=Pe+7pvz|(BA_8`TOVrL~5slp|&E_YfJ zM=0jSm;lsU-Qe)~$!UF}jHmX~!gxY%Wuu7BG+?~1Y$S{QURzi-#*?!*@ALADxjpv; z9)EKbiq4N+;_>pR(#oE^^z--#(BLp_uf^q7&Bsx&8?d?>TqjQllxBTi)JrV$A=Zofcd0%!>5^2Z!2dvi^&B;FhpcBbdK%^$hPOS{I-UnD$~XSzo?0)eYrr;X?Qc zWLyoBap3S0ItW}OJh(`l$WH|7g&yx@4#7u!QTFB|obF>^w_5Z}%z5+E0@tTG&ax!f z7mj0i>=TGt1i|=Jv};d|I$Q3l=c}L1aO0j(VM=MxGhjd|PNUgd-2Ls6t@}e0@#)`U zYu}NqF}z=Q4Eh@C=z^{~_ea^TMUFkCkMH_a!Fs=dfdcAaf3`vqzar`l@&M4i!TtUd zLTQuuiC_AM^i@fv{YNMH;n&VhDo?%=!@@~Af=Re8e%OrPrB~r%Td^rLs80&+QF!uf z-p0uTaZ}VGZ{MU2r5n>GjZPYpx9?8OQx1NBPSDIA65QB*Lm`z}GqTm_w&|-dI1i#Q z>7{|2@A*B6Jy+=i(+(zKJo3tek2!M(1o|sZL!Vc(De*!lURqtixV%<-LLF5$F6lFAdOBVvt8=hdZBmoCz0K8RLv=Ipu-gAW+OeC=B0dB0Eon_2y`#zB2W z&Ei=Q;U8TZSTc>&m`N0u-JH)?<&@caM5+9FPW()pYYw?ix6b$3s#!AsvDT{?g|vhv z#2ov?37~2ohkahU@K@QJ@yZitlDCb8#!Jf8=c0&oLm8f>DX3=bmcF$UoI-ELpQR={ z@oG(EJa;9RX}~raN#jgSUXE+av(#{%-Xn~)CP>S>tU$zDHU;^1zX|jwt&Yr1>x)C> zd#r#K%|9a6k#$^tsr6r!&Uz3k)G|z1Iu=^*cZ3V-O3RYeG&^;brqZ+|>uU|Ga7tx0 z!mBu2(RAd_fJ;cl5&xYKn3tTynGpOAW?3x9z*G!yIwgFnrs*4to+itc#mBKBH_3uP65;aVg&1 za~;e(0H=x%2;#%4xE1$yMfac1S1LQfIY!`wW)yI(V0v&Lw)*WGtYLrW^i^$3aVygU zE+ctxc*qdsH8-2YW08nLkk_64C0u;YhpT@>vG(nv&-ZoTxAk|_R%K7;EZq*MP+mC# zw=~7C7TAL6gS{=T3)rW~Wg>XdE_>G}``ZS-{;hHhPRGUexjpcm(}Ni^X8gJbFE^ym(T##b)AH)mL)_#V>UCuBs*J$8wmBc}!$-aMC++)a zKqcyPXeKuP1-{09w$PBYLf$cYp&}aV+yH+BCvbv0L-#28mr_QB8TvqairU&8WPJtha>EKLQS9nC&*D9lv%Iiu6fib z^rJJU_6)Y=1oNE!EJ0=Y{;5ah4NaTYG1w z<{bsj+uKu#GdHmn7*`g&*w34iEcZR3R%@SB94kh1bq1-@GeU!gw55iO5(xHLXW4>r zbsm5lg%)>&m^D&L-nfj(=NF?aZdtL(8Q25a77os8fM;Oi7F(aAMgp~&9H1P36%AU|6@N3LJ0`1PLnb)61UGYNDe69Y! zL0NEU1}X)UR;G|9xS@MHeBx+CMzB-hml&>Jdz;f>`7Tsb8rBY939QGWUHK!lkk&c# z*o?CI9Du(b`qMFKGM8BCgRwZihxGvn@m;R+ zMxIBJt}n}t?@>~0ZM3wlW{vF<9Gq1-Bm;*^mUfM$mi9k6sl5HhA(`zl@oH|DzG`q6 zyx)}m`ucpFri#G;*b2@0=@m~ShS?taX5|pP9fX&KyumTPiXG+NHZ9X7l(%8<~^`JibX)yy~jVWi~@?V2m7up>oc1R zOCm$zXl6`nq7RtVDcBazP6OUqc;?;IEY;ejhon{F*zzpAVSrO4+F^1Rye|z4+TOfr zS*S36GU!gO9h-Tl?1Xmu499sTicye}x5e+P?1ZihDVU0gMN7x{0tbAP@NF5Lk;~$i z#k=muc<}~Ex1M|*5I3(M|c*j(1MIaAbyq((#Jm}r=>%my-)f-Rx>qENa`+Md4kIpa8i`~^N zc2_nV=ogf`x%;1KI0pKtp}8YxQT}$gN16du#nwIek8$jtW{=k;Cl3~5JI{xS`TKAW zjQ1PdB=GjJiD~wMBk>&E=G1UhgnvPIE(qf(dS!VNycfoAXeg!?&!AFK*lcr^pfhQQ zjdsPw*lo)rHZdeiz^qMYI!5!crWGj9q~+{|z})gCfBV2jFic>>;ih9bv^p0TSkgpG zu0@)2pNX4B+;!;NVA#J$Lyn6Z!qY>cz1Z4?hcK?&p<|48m0{FF2yXz}2R4BIN6dPt z`~oU>KHlOhOY%Ya&i#L?{0Xr9x$;&2AC=!}l&^*&m+~wszy8macd?asiI8;nhl%wBPoNHm%5u7NZ#vnHRo7;dBkS z#Cqum`;v(oZuEN~$XX4{9Y4yK5OA;%B@BXu!M;FKDqeq28`ZroxxfcEjfYSuTqq;6 zazs6Do-k*+A9Gx6QP25seCoJ;`%-JxSsezfYwCa{2wzg(+jph}-wwPR>;6&R+mlmL z8}PmDys@51ZG&U5S!rAF7)Y^Q(d7L6^6L_gXks&-ewJn@;l?g_uLnE%2yEhEXFZns;JwL7ATn8qZd35KMhucel^LHR0^yI+Kru;%!j zIpOINCV$K}!chBQPhzOS$xUa6E=QWWTlDBAmzV2_afSk&6d&t8Yd$s=m2et|0R(&8 zuI|qqEyqd1*QIjY!iHhA!M#9?8o{%P-t%$)Wk2SNUlH_n3))&xSnvh$;cX~f0(eqZ zT!8)ba4|(=XfK`zwD0i){TarEEYMX09QcWeBYVr_qj8bCvkivVWPCag>uc~v9Z>xN zPptZwly=iw-gxOdDT6)xZt-4SXjSX=K_C2ThvD@iZt=5Ap&0dwyU~l09_Y$=(zDg^ z)H5}eah=k%w5r(et*#%4qYkR@uen|IbU-kQH$(M>8|y=i8_348s@J*?JCGc)ZE?PZ zdVB9;3Z^fiW3`@B`mr{{m1Hp1f#F%hz!X$ZyZ~C|fk-B70vGmsqe(nl47+bso8Wu0Xzx3A! z@4>Q6oAGUMmy0uJ^FCmB6vC1g*x&B1)bVXc27XZ-1@Vikz*E_A0sd1HPCrdEnjWw< z(KF+wjd#pZ(l4W;MI=AbKFZdDJH-?GJ*Y(*$S?N6KwJi}{za~Xc^fKwv&JfWvl{&Q zrMW!|{r%1DO~JuX+9id*@#H&{=g z{-KIlr*M7zpj(^O7A-&qyvT(2>p-=CESKK)+u!s+o0zB2&(=Ve-K97x16>(UdbT#6 z`iZ78p7d;8Jhets8E@6mbXBs*x(J(5T%sRNh%TdovKmx&ku{F{%Xs3<4K9T^A_!)v z!ZQ9ZTcWf%4sCCj8Mz5F%4kg4ETwG+T6E)Jv`Ei+z|B+}>u4n5`4VoVoY%*aO+^ku zj%xr9FT&%nGc;D^aMxv zDvtm&hUu?xt2tPIH6C?ytm{0?)75JrJl7TOi~4YRlSmehbHSmxxY<9+hod?8w=wA$ zTq&$o`WjV+&TI6_;muo*=Q$q#Dfk@piPn$Ec9YMFlQ&~*#9|lY^~_7bS6Pk^p*7vb zr{YMxuenVs##I<~h=Y0Y^n(*eTn5kPD_g$2-B|PP9V-Y=IRcMmW@YkT5X(4CIRAR* zS$OYa6ti$kh%6`l9Zlzx_dX1@G<^7n_=R8N1`bO^<$`aK{~(SG8-EIB?+5p|JuVR>b_5mSWiSFU^bk*Ee54{{<0+)<3TO zEe1|I-mcKP6LD0Ki|+{XwIScT>8t9}S8eFN4qpzz#VlB`KXAju-&$1&ikjOy{}wE% zm*1ylPM>+dmc*0yd-=)MDJY`5$p66}1EWUyifew|5y&yoYJarLx)~^sVUqM zrFkFF$U6V}^i}ogtJZaYyZI!1!t)ka%V0oZ^{zNHF_!)+Mq5rSbr%jD zDe#tOVdC7yx(lXpQe0wMC*3v`1NSJ*fbaSucUq=@d)@~{ceT%{xsHRhj?Mn0B^&dH4K>8{VpDpbw&Gq@Ypxlx)Sf65<* zY0G!=@!555Ib$RT_Yt;ZiT@Au58s39hyQGBQVT>pf$;vFU9x{l?~#_hX}yzoWFV6E zS-`eZQ zhlq9cVNKofj%lv&LEMQs=}AsHc3R4kBe-=xDhq&+>a2I`@eQ+HFSSXQ z8*zbEUTpCfztTr!g5kSa(UQ?_{wDCxCjS)jpX%_#mjs%U(`RyZJ#b6(ck~;{2H*O) z;s_XrlW_?d1^&k>`dE(4c;T5=k>zerdD|%OS+~69N*|V&6fIe-7?G!1^Kbe9 z{P{0~{{}bzBtw6jVw2pY7?$f4Ehz%V?P!D@Gbm>Q}BaY*CELM#YG{<>sFQJ>S0z{sI36e?@}7LGrZ?qVkZNo^`&K^nTKhaOnAP5Jz(U z-*Qf06CRu%^dV9&BJ~*#HEshWSFQ2Md5Rf$>zmEwl~df>*h8-(f0+E=!iU+lfTh61 zr6a7D4oStO1Fu@i!|Ea}X+T1}nzlhuTMEj04T7F_1mPSQxk2eOWGU!F1=+GlF+=7X zoNI6vFfOkF@?PHrd4nkL%7i>ijx_q)8%#F%i*bp&42r=oUH)tsUBg()olm)Y;mph> z&nq6ceh&2uNOzEQA0+6a(v-q-c%;E#y}>$zH3q8|SE7ahCzU8JieU8gwR;6_)&HfqEAAV1Ng*pU6n7$xJ! z@OJSE`Zn1vc*;0pR6a#KZWtA|AzzOb;9HpB^T>x|N$-^xls_uZwB~z%Blyb5cZ-|v zNyB%C@-;{p=j+Dx(ay)nw}gCWyZJ6Qdip5VOBMJy_ZX(~EIHY&e>3D1P);4?d;@Q1 z%IlHijs72#TzcuPP9%NyO3<%N(1&Fs=t2c`(ogk8WtGMw^5Ga4e=YcTZeV%IU!LH% zAMEC;W%>vyuoJ@SZ~pFVvd`K!p^JHg)|wcu+}KjoxrAYI1~K73kRRue#~!v39-#`l+NZ zPtZ5W14_R}4qj{9^Q6{eSRU8-5(z6tRfFe@ zIL|y%_94%FhR$Ya0Yj%Tw1}bO7+S(mIzvktYQsGDD3F-HuQT zeNU~2z6I2m6W13eQ2PsK-h?nqa!=k@Sn()Alga%NUPJw(5^a7e!0@ zx#g~b+)F7p^9{(|4yR^1NmRPr>C#Vok0;$!(!G|T+brekTAO6euXec`N7(jn1IF#A zYu#SM@{<3~1b>svR6a|lDmIE=u|Xy%Zj+l8BQjbsD%U86m!?Z;S6Isr=Vr>^Adz3}@`M!|rBSg-wkd{Xv!W%niV;}@3>MVO z3dPM*rC29R6l-Mx(b3C#fA=luoxTS3abrTSM;2Uz`qOU)$X~z`3SEPBYZ`S)E44O)s-4iLO zr{~^8N75Vd`3O}XWR3NN^NqW+@kQ)$VYyK;BG)R`$ypE; zIm(8ufTq5b-?SF;>*3&R`SvZ@7yDQ@dLOn;gL8RHwn>9}qxNPZxLkw0LzAT%oX+4v z4bEin5e-gZ@BsuJ6J}3D3aof-tyA8m2vrYy6ZQ;<+fz^_cmB+B)yjCqs9Z{U1@q)u zjW@~p8V}1^iV-=Hz8V69%GU{o7Pc!A_9xUnjS61yjX`@&zC8^pu znHAYXtr#c6Nm4Ii#U@#?m+9MNo5n5KtXM0xijA^HF)DMEE-Y1uJJ!tRTP85*)ip4v z%UdvLXxt#&evAT(Lpp3T=pG>5GSZ!#pu_Ct8Jg56MZh>cuUjRg50m~!I5X3MqVm<# zr1#2pMN2+ZjEL9WL3o`!mi*mn!2hb7|2d`iNKS-BZ055u;~Y3`DHiTFCx1Xqaun;@};6Bp8?}?*dN+aP6OpUlaSLz{;l+x z@}gpfJgexFCltN%FfcBM_Pn(ka3{oBXWacR7NRUGMxOFmtCguCb>k> zBe_g>E-|$4R?3}7xv@8$a)zeh`wY^FETM@ErfP5kgZqDCY8ivyBk0JQeKS%ZE)(Kx zPuz)uBNY74xL{OVHTmI*m${g=!9TACeb#%Rzbio>m03#fk?C%FR>&))zlQWf-1H+! z2R((#w@u2T)aQ`_8jna{MQlwXol_C@kT;g{rcmBq_`3MO_ZYq;H$C;AL;6P2Z%EL4 zrD2z?uU;`Cb&64`QM9BQ7}v`V{NGiOle8If?oG(?$XwEg_H*g^JRH1CH~QoiI6Bz5 z^6jygpqG;SW^&U*+rt&Z|7vQ%Y4_{WCW9_@eUJk8FNRwkn2YlVU{H5koxLS#HF(9~Sf# z&1#Z=DthE;VB9+HjzryWS_v(_51{1+x0XpNEyB)`q0%x(FLvz%1 zkH}AomV9UME0exmF)SYfS#RI*vJIQw)^v~j$K=08H1#&D4o%Ut9Qw9V-xNn5TXVL` z3ClkevAE;r<7ILw`L3@A-+%-k#=o16gMK>c=8%paI^HN}sa(weh@mj6bv+K?*|m5CwJxL9=in`k)`+YCDb9wXwh^jrnY=la z+AMj3S*WmwRceD&8l0h6C3h?0{$COI|3pV?ovn4=YtWj#64d{ZPrF@N7+AAcKYJE969tPnX^%-cjl1OB+S6{PrE`Jn}Qqv2KA~=UKFF z!iTV~8jdk;-Mb*g5dgIAHc+xluTm+NylSMqVB*gxZj;9q8|5K`4=8%&K1GjAHtBaL zZj)Pyjx|x+nn$p(QSJE%)|};7W3Gm|%7qJ~N6vAAUOCMrL$6`ODWHl11m4UP$14X= zGj!d%6{FJ3nlieT?>O>VYPNWX~m?GyBP{!2QP?`XxSbTIK$6F-161em{D z5#=}WuT6Z1iGO6`?-AYg^UEvHTT8tU!@+93Qr==W9T(2Fk}m&a(ETMr=aqX@u2&`* zyv^WEt@$4&{~Yq4>G1Q}WFF{v7nh@m{SS&!IgR}AJ|`&R`LBt0F!59qKkzN-BC?z4 zl#BI$3iU0czSVGf@&582=-6M@D@NoEMN5{u`8f;!@@4S*KLP*U34T2PH}nC+e~)5R zCMh<`ZHn0cposksz_?w!o2{hW`ILJ~LN3a$^eDe#gADwJcGgH9kaM-8DF@iWU@Flm zFZ=I#l)Hy=H^JG)%e&*hc6mQi49k0p=>NbtKQCLms=z<-Q}9ap#OK9&r+WQ$Cohh$Jnzh~{^0i`Eb|_l%kz!QdQ>>G(zotF#|A^DBpp1{CGb5*E;4mF@Kbk&whNH%J;}kiV?X^F)BrF`Sc?*$e;Zg_>W8QN2I%< zKdm+WaMF(?y}+Zz+xLLhGseGSM4A<&^0i`<>`+Ac7191cUPsp(e1qteoBdP%1wB)# zXNIH4IO;N-Z?C~#GKN8ii<6n< zLwYH_PkI=>Y{drYq8OHb7ukzPUX4d2jTq|2sr#rhHcOrC`id>PSwyi;zBKXAfL!X^ z%6O}-cJwf-k-lCtQsR5?e&gfeMe&TZc*X%vp&wvIs9>o)-9*uiau*Up1#{&=#5t%+ z5hso%nr<>X+rE3KT*_u*1<*sAemqx>YtOkzja;rGBXXLG49f|MmK?1J|EK7aRD%b; zq$;26R&0=F6aU)8cbND`2H#Wk%6b!j1IX*aa>kt=JqHc-fU`d>C$$BZg`x|k;Cr$1oKBTpB`G}V2_NoWHH`rkKg0yOyv$z&LU+yhtiC^i~6-v zI*6nzNZJTD7&q<)8pkyT?EllIKoIL6PS7XsGw4{yL37}r5VW6y9(4=KObB{A&&c%3 zLkU4MonVIC-CE`gl-cET$h_DsbLTy-flb}_k!~XCj&d$Gemh6q}^^3%g5xt=J$t6fHU6ZZ{6ZWXj9@0`i`4 z(6PU~1bK8AixqMGQ#}!RRO1ohIoZ5xy3XjoN8_0PX*?_gf$^5$eB*D_vy^(;C-h(* zpnP?br?^>i6ziqCVxt`RoaL>N-HNqxw5C_f*BW0VsTyA)2Xbl8Jb6#kmr1k6tK@6N zxw2f-m&!|u+vGh>kH~t(CGv)1Sb~~w$x9km>g6202>eVad^WqjXnnk^?(k^|%@G&!l|M zmyo{>t~TCJ4)oP-EW2&w5P9VvbetF>`SMhn2~Lr$fCgu*TZ-+-a+DKmU@Xmv@n`_q zYxB=o*OxKNiIp+7-HFX-Y_k)qW9&^Qwx6+OjJbzo89YO^p9M&A3SitopM>pcEH-WT z$}f#3=#w9uV1|6{1T*E$)Oa}u+NuNVrbu7M3N7bZPE5=BPcpl0+V&i5O8N>mO@RA0 zl^T_MLC34*U5b|cRh5RNRM8{%_i;5ouex(6$4@!gjvNlUv81#8)^Ri9#tvB^8p}zOw)#AOV1|ALUp2h+L)E zBqyu9s9db^ZIW){=Nh@O&u93JpXO?uydLH~Y&K104}u*)0EVY#BRMdKOr zBjRy>_J^OJ27fQ|hZ6kQ|ETn6f93bcDuWMbdXwCz*eH_~8{`hfusr0J&-Q+d@~2b& z*>3r_sys{jC|^WMjQlf<+>;gSUNSLzE>-+_;}cN zKI;MfTKhpnzBTbxreA!j@rY~z#_NaodLJ)@ypi8R-tBIAFDSi7o-urP7&#AV9Q!0& z%L!3V73FkI$ietG_LLaDGc?{PCo49|v5KJ6{IImscvQ~Nc$4h=gzelYKPrYL-L04Y zp)>XFr{0(0faCS?mePCVi}URM@v4b$K|Ef5_OFi@fPaa_@+bH`@`B-eM$wWxlplUV z;}LnlEr<0Oq8zv%ls_TID}9tNL(Vn&uTuUDIoZT7*0@Ld7;LBMJ~_joGwH`FV*lgE ztPj+a#qfMMW)T@LIR1B}jp& zinyqIC@M@*M?0ceLtV+l2~nDSQq`)4^2w4J0ZS9yoL$mT9iRUH_#q3shQY7~YZ?4Z zgH;UfJ)2cf!{B*Y$9Qr!wPmHgYtpON7A$m!(QFNetqiJ(t|{Hletp`Z}YDU@Hg#)CgH z=(O50dr%x7FO!eD_EXnVxI?WsGldiE^>V+g!v6HthZSUw(rVh|W}t%d$^Gjy#SQatNp@NXKN&fxu2 z5L&?Cb(9sF&tRcT>}=9O@;#7j8~-=zsHKkfaUJMrCr~Pu*7`hvuaDqKVt0sU^>#nY zuEx`VZ1&x#b`G^>#d`Ug=#*g&G#yF#z9{6s2v-^JK5r;}rYtx3lADjueLkB9zB%Ok zTbxfnz$4Q^YJVKeUNOIy_`ChQAfwP4wU_5 z7nAPrC?H+oF3^1gCuqtLm3^%4R@(@8{rUpwi6u5pbiEWXlLD3`1bF0ityD{1F_rsI z3Un%u6Y_13g0F^r6Wo0Fy6GstigXdu4RX^JlMdeFN`ph(e5_aj`{HE zY-C&pJd)SV1o8fto14?K3&?#lxj%r9(`w-+`AZ(sCB{}uzO0xZ&Twy^=L~W8BM>p4 zB7%+x_OgYW+1o66Ofe!MV7y{@g@1#5+sIes@U>oJrow4dNSj`MFm6+e)n>0;+rlPR z<~YjE+zr|L;QF*!eQ?Fl5iA>}ws_yy8uO4Xn7EY0F^CwX?<2ci8+b1hEG1rl4&UbYwyh)-_#) zncZX$m@7eQ512t@O4RPceT{<==|+@}lB4c~;Sqb(lT!0=pU*uY?-d^A^Teb;i%IrzBwy_Q@#yW~o%H7alOq zO-*+zHp*oxw^lAutdkoweT|%_@fsPf@oKqDalRa<=_@2xQ)>6jLhv1%_EdpnGrJPidQ-P^@Pr-ey>JL#K9-zGutm17NkdTaXr zq|ev~`j6oPO^?8R{YJ`%{|B<>Z^1s7xE}VTHsrgWd^OM(yUAIi5`?ohrO`S~fAz6IcW41D&$4N}5EN;oYc!6UtuGb|%cWgA7LKrt$V z6&t0q<~K=i6K|s$o8-3*Ea7GeKqFVezSnr8bTo835$DtGj}6^*N@vMChHkCFm4@zR zLziRZEp+HkpDkN1E&v`8vERdv>Ww8K+y zrSYEgvf+DP`Mk1FCv^&^HdOe>VJWroP-{a z3|9KE+>&Lt_ZY>fj8JTpuA1K>mGQ90(jn*5(@?fmb7?D3N{ zzfN{)yg`0DnR>kPzQ*h19Yep?&<`~7J~njEoBV|)|1{0FB&6{=sZjLFG(%Tz=nl-c z?Ftz=V@&=ClfT2{->&(M(qGdXWQ@j_Nx|E+ALTdcgAHA0%5my-F6wpQY}D%&zoK4u zzzdu9v*i2rtcS3CqnIH(fs}WX>c#U zN=UIuDip&qP0^BaAU)3QL`NPcj!#iu--B%bgglIE!{6WV_g0L^*@_v`703zWi9|;p zZvro;yg8KjHGEjS+$WyI_Qm(XwEiN}5pmvR?L?e*eXQyAavi=R&iZ&q<33rd=#`bE zbM$P6p08&?Pug$LbB|lk$`frp70MTp=Mkr#a`LnMnqDuv@d%kEC*uGx*S+P;R2ey!Z0_6|-9rBmM^P2jK z$~#JL$yzr(>$mv<(ASYZkf0CCV@ls7Aw{25xcS&`mXj|n2ENM^d>$F&rsEYZNV=Y+ zJ1s$1BO~5oIhy2bm5=^UKDNKcG5+0hc+)$Oa;~AA9q?qPT<{Mn$C7VqY8iL?`h%Xk`#Nj9NjfSvNE<`<8_FBE zpHr3Yw5KQSSr5+}Z|{$_Uh3uhwYJ`O+&TcwhzXp8& z<8tWdmQ#)=h2>Akv1E+Wqx^=y!0-=t^V5z=b!;A#qf4X)Ev6pm0jU4p|J2uw;H0~9@Vx3GNoim^o!ICEf(4UkF{pZK^>*wF( zFH{*y;zsPT8q8*}I|YUMF_=#qLOupNQ>-04A`y&fC3GRd9u4+o@JJPw#o*_f+JnIZ z$5LBQ2BRA6#^72dP!-z~sV``%uG_5n4;5o~L`)vm)NNdI&3G845AA309!*snCTdV= zZ$Z$hhS}Bj-Rs_fH*h9`!rrB z9>s`sQ@SSEuWe~brpCkKQ`{y^nqDsrigmL87|NfA*GDKfLuxdAg;XnM$TGz$xnM5a z)+dWJ?v?o_e=g7|l)i!c<_wghCJp5%hC4RpSR=DkexqEkd`&V^u|WzIy^>EkESHJ* zW!&jA)u^9R%3ab1a({}~vy<(%( zDSD-bZ3{c9O?;W6N0usP$Rg%DcGW=72h*Xa7xk1T^mt{8(nn>Yo1PcDr%Atz^cT44 z^Oe3%uCJzDQMtY|+a)R^4R+J~uw+plE{d6MxpcNeD0ds>ehxQi>c=BdmN(?*p2!q^ z0VyIowF+=Ywu-9e%J+)6{v(}>(Q6f(WTnzs^0J~=o>yEdYeCOD)yFg*mXKnDR46ve zG)1qJD>ljPK&Q%P^UyI^JO4gd+p`_4?U}GPESD;aB^Mgj^A){vmSU}3s;2tnc*Gr% z!`KS>l-EdkP4I1Y=|g90NPk6w9{YNfJ|g!X4Z%)NvOqtR^fjdK zlAsUEIZAKI>55HqlA=eBQLL47SQL!Rwur~&u}*qXUYPPW!{3@Z;i{SJ&^npJV02}? zdfw60MyCEpgIto9Eg%TmkDZoi>Jp~DeiW|NcItyjb@a|2iA<+(wbUp7gum=G0!ZB;M^v zSFfVKIftYNNxH#BDwn5&&R#{|it`cT)|2JHJz(kC0W6QYSu$-FE;rYB_I-B3rqk-= zvDUnAk@sfumN>k;T>XXJ4EI7eus1oKo{Rhsk$yhu&v59?X?bNgw|oG}db)wswWR(9 z?#{H6M>r|hZQa>mV&N5>9W0`dp1Fb(+^l;$6BtDeff8% zUw?{Fbs;(KNN`v(!!4N{uaa&m>4wJXLUq{KAooHz-Y#VDa&W~~u0YUw_AXs54lyD* zE@`uuA_W4+#!a|}qH8ER0`F(Wx1Pn({w6#Z3-NS$@x8{;P{&|LRd_vvzy2E)5aJdI z*|MAj+?64<2nItszE^8d30_fx8WJpKsxycNAqDn)aJN$>wFp&*Y0v1mJ$QEdf1P+G zTOjvSCzfl*x`Ygy4+(SP68=F6WgQ{m_=E(H^q>WpcMgIC4#zGUk4T1F5DMOcBeKs+`l$Q}-=rZ-@yFI%s<N1?Z zW=%q0DEo$lyK&JTc613KW))Dq1_*0&wpsoFxoSkqGy50>JJ9Br;tfJpU*mrGW=PH&b7rRTgNh~ z3&s8y*XB{yE(nQ>y{H)JyD=`x_KyG)Pwun+i*q_Vk;@*F=cW(|`j zFo_5HX)#}BQeP(VWIrwDlT1ou(o#Dq&3b@I7A@vkf1qk(`I%JDBp&*Qq_)-#Oj^by zp8kiVcGi_ln#&{}1BfJ#mB*wqCatlP+FRY2)SJcKhYRv3E5%*hS6JLM7WZH=n0QE_ zwGYehTHGck@w7lIjHI$!M(b>`$<|gTEnpJQ4utX)>rEz2WfBh&L{h5t5|gfH5>FFE zQkpfNNrRZgV+E1a1}8-#sRxsIz95p?T6Z!jgGo(xQafuLldfbz&e4M8!t8jveZ_+0 zVQN&CUjincK4_f-1}#VrCRt26LB%o}vBf4^9wyyNv7h3CKFazELTs^R)0w)CsU|_) zWY$Osy08jd4>6w;j28LX%s-y_&oO^SB7gN2aI@CRGFZ8iS*5P5S8R`@ByVi90%TooNlIQ^jlhOBYw$nGIw?V1MuOF})5;hRVP8?$@3{CH6V;Q;?bn$L zOt2%JeqbHWuKVW!`H0}J)KCR3uaO( zl1QR&WzyA&q$^MbOu9IcRA`cVB$9k4>9|BvhDmDcB(1(4De^`;Q{U>2<*Ime0&@pA z(Xtb+NlqT$c6?gdM5J3XA>I=;BNZ9y=jvN!bU?;Da;$2V(HFC`#CG&!X@z}c5Nn|;UsB~mch3;#bYm)g{4lEol#zPq8r<=U-m@r<#58Q zMv1?d+9cyOp(GEGiC4fBBGwjSM118`{}{jHGMi>H@YFVs9*6HiC1o2vLly@LA_o zb{R5(v@r`vJ>7t`vnPLIM`Ra@e*6Z}c$cP#l`wb8IYY)SdRgkEY{fD8=2Q5S<6&fDO z&wllP*M-vuryhcaWu0Q=P6txKEFfji1+u>916lq>K+@M4tOrsDX{!#SV?Ss?mXvg* zt-}K(pASeGl;3n18GS)Z5d(~j%MCAW%wG1t>wz}kWoRkj`oqvZZS=fo^sI2{nR6K4 zh)Yl1VQ4e1O0@l+!_f8vEz3R#$iNjq*55To2J0{D--oV)sYb?3!$ba?55rSsXsL7l zVQ9ZH^1lPJ&VMqv-N>MgwTF>$e4$;293XY(0ogy;#-7*ycln&YUUmo?+H{kVf1ANc z2JbN#Fc<_V=)3`+4cZQ*QLlVV>XboNt3bi(B;0u@LU6Aos9ytqYJgWCN+A z2hfqV`7pAs0WDaP#sV2A15(zVK+2j5q|Sc=5lvbKq-@gcIfQJ|908gu@Vy1{wf;0=12S+Y zkUFOVsq=my+kP&PI-dkm=Mo@o{s4$*QUlPbi=@?uE@w)KO-sgJhoLMe9Ed?KsdvebTK!2JQf|4ksHKY}-YLk@1X?@q*zYfA|nQEHnFKKYRuO z@P|JpvVOK0xt{`=_63l#zXh@$e+1ILeL%KL3`o8+MkVUXe;6HCfflSuB|rwo0jc9w zBSXu72pKF3=P(|8C;;$>KPJ+qhm4-bjh<(Wo)>^D%d0@@S_NcT-U4zQZ3MEewix`> z;1>qJHTWZtAEMl6Fb1S;9gw!t!?|cK-${}$U3+fNZW=2Y1>sm+ExOjZR3En z?N%Ut$sNG;GIuK}rhEs(nFfYkjFkh(tyQnvu9dk>Jh zV?gRY;<`4;^rfAFEPED^_0rwo1qLqxvfZu#`hX<{Z!~y2kbF~s8NdLrGw?wmKfC+{ zkY%Lqv^Ni3#;u?QS<+_)zXY<30%RF?0a?ahfGlJ3Xq&!0kUChdvcu>&&+zmEQrS=- zbzE(5jKMM>bxbnxdx5OSN}!`_+hKIA11(sRHUSyf3Z$;jfGp3K1_el6yMS!7Ux3t= zJjT}55#a%E9lGw$1TDytdI1^e2c)h+K5Qr9?>ek+i=?f|llCIeZI_W{{I z9srX6At1-+e}F7Mbu2xMO<#c)WJ%u{9@?|*Fg!<%wRw&QlKE61`OX4TFU#P;w-f+> z_+uh%yxPdP*2uWg$QbXEam8U|JYr}mbM9ei{{vbY^EQxycMUJ=ZOtKgvFlRTcG2y;Ul{BFh146@4L3lZ>|68ZimkAe%_(;-#JYFqVo^U zAL?`H{4;Y8oxkd$micpBRv_@G85V86M&ayXAZ{ zJOw+u{~QPJFG@+i0zY@YTRziA79TqQ&0AXLyZa&eZ~9Bi{MPb! zP59sW?H)==J`F$0aqMs1&)#|1&VK;-Fn$i5yPQ)VNlBi8-vA0Se-)3Ws83`RRj*^r z$)iI4s_3E7qW*fS`f`qJU_u2qU0FaTtomf*pHriBV#YU1N(HB}R=V#@J%) z!HzZ9Ni@#?dCu7f28AKtckg?D|NGr{C(CD-wbxpE?Q-_sb7rV8Q?)xQpR&k1x$W5Y z#<;jWSFINU><`k~Yw6e1^KEp$fv8(6xAe!51va__kY)LsetmC=_439m@|M5Z?=4oT z<FF&O7$G z2Vl_rebA!648cWoaUhm!pQV=US)|Z;8U$V1JPnunO;SH)X@P=CoX@0l;DLH`lYV*U zG>Vi!8+zj}Gr9LxZDLz`Al(gV>f;_EUFut(k3PyC1ldF*P_wb>(fsvMDS1DWytFmI z3NPhx|CeTkhw=_c8-A2{3dr_fkm<(~uK~HgybGjk#$Od8yK;ZEEsj9iTNg;#O@Q=I zE1)LJ`$y~T9spic5rcq?7=e^E3`kjHfLfd979wjccu_@c1v0W5NLdGfl=Y*;Q$V)! z0+7D4303Qr0J2_1AnVlxQa8u%u|jnBk}|?%9o8@Xll8Vwk#*)+)bX-Xhqmy%vDgX^ z+p|Z?{a)fvK-zO!;zfzqf#iR8fNJM^K%6k4v$v2swZc>z>jO0(Wff8< zNb<5xwSF$xSCq&7fyoLF{WM(KFb+tWGk|RG0w8_06v(l<8p!_H1mt+x3FMlwU*Zoy zo*93U>ED2qZxgQCSXSaYK&>CnSh0;fJc~9vUT_;Jzm=5R4oLeu0cn3XAnpGINc;5? zqk*(P0Z1Ke!@@#zq=6S~VhxayuYuID2S^>?OZ-XVX&_}&&(lI=zXmVZgq>cMT?RfrrBb|E?{4ODg108+7ZA|P#{EbkL)8Oz@c)cKv{Wxa7$c(t~jvBE=nccd+kq%D5}*%!7EYTJqf z*|u^Lt4gdTvA)D+KuxFjN$YLs2VS-<0?3FFNS(ug^uZV)bxxKzTjFODmjUU|wLt3R z9p2tTbp8rnu!+k+Ms5PB^8t`Lp8=^;M5<^nv9!dBKT$%fn|YlGCx^j3XpALotdYs`*9t3!6v>2GO`cIHXR1CO~-+3(^(+L z{uPP0Bt8VPP0uCTMXC2`C4tO;E&0kL&HKG-K-$K5{nOTM^8qi|L{}gqy@9kXT%tju z2}s+91372L$^5Aj=So}*pt5o>!e9sC~=LH z!EtS{B7=JPS-=@9JhbT?y(vMj{wv|Cy7l2G>BcJ0`7;?)wR~T4sk9>~P z(iY|1=PqE)n)SQ^NBE zBQkwL;yEDuI76m0f$ZZfna&1sEQ%PlU6f@wXWgex;AItOAR{h7`o^LcGRaZaF& zOerHv)?s~bD|INBeowQ)L%9`WRlN>C+U5kLY-b>S<^rV8+=29&H;_IHkk|uAIVnJ7 zizz_OFPVkt-XZJkv#3+`f^|RKk-StEPzZ0CIMo*N4!6QfyR~rkqI#dhI`ji^4eGLu z8c$2e)zWM${d5dGZ>X-;*WCoBnEReVq$i7%D)0=?oi=9B) z69MEri3PInlYrFC&u!NfqWgF7f=%28GV%wIx?cd<~;^MIW1UjVt+TM6VkwL#)`Alt$+(E#EKFffNYDCOgjVF78jXzm*_1q0LZrVkm>$F_D3p^Hq(Yz zR%}LbPB`?i@U_S3vMQHtazUK(y@5GGWP~=Hu&^N|A0Dj^dH=HDU0(=q?RZsR10WT< z0BMgWkZtBzIcG%%dFXrhD{tG4Y$ed97^x>w>KQHdOa-zp7XZn30I2yo(uzzeT7j`o z-=6DpQJ3;GzPnOB)09u0yTHRXuP8u|c0c{>MG?hD)}cRG=PBw?4#$`4@2VVIAd-0k zZK!Bc^Bp9*OU%QFgp%}oEJd`(6%3lbi!D&GD`5Kwt z3}icZ0ok|dGJOQdHR1%2x+){?lJRz%k*x&k$_B5JNKkdz1F5Stkp8R)q^?LHb;Zdv zk-CzB^l^&BT@q7)T#IJ{x&K%Q7u;%13!fRyh5 z83_PVS1%y_I}k`+LxA+}C?IuB15(%LK>GJ9Aji&5AlJx4K>GJIkhU90(iTr3ZSe=vmYzV`5&@)6@+>Sw=REL&O?&}lWF?R~H%QzL zq%4k+8->Wa3|_E_n?Odgq>Pt9+HaSv+FuSx+Z=(k-x)~z+X897FOa_c1W3O{0s%Rp z;=gQ_H2djokF7B15UAmy8Y+*1vg z>2VUL0@)_&K5?^P-_x(#zza69SK=j!2Z8kKF(CbV21tI}PgVZnK&^kpt%7xshqm^x z!b7>uq+C}Z(?t5#3rIVBft1C0wW1JNCh&qy3qT~!}~zWdJ3d{ zuYepAwjR{#~?04Yn^KinzAH?r;xAdSB-(@%kv^-88~N2+p)134zj$uyB;zbcTv zsU%LCQB-$WJSz&SF_-g+-k+h15AF>zuKuV}H+j z+s(30u@rR-lmgNR?0;7)b;-lAu-FO@`|@MR1DkM@@;d?P-(Vnh^apZo3aIS@&) z4#>z3pk~jpLUcWpb)U;R)MJ0Y;Pz84=X`%FJhZ9CXjMjiAZ_{>NZIXxY=a+=eB*(X zNuJa~WNrX2*u-`qBi{lk^N^H5pKP-tL-T*t2L-o}Ha(Dfo=FsAR6XwkX?uAfK-7@P zI78dEZ|?(rp^GwA4(y3hM@wsc6bBv(?46HYn!iuvQ>MwJyou7589>Tf0Hkl0N?Z*j z-+3VGT?I07N2VVEsf#|$wxWwXT#x!cwC=-7W2KLP)bo){6Y0aoK;DT#ovhpJkAnR`9=2tw6&}iME#-OwX_qgMvV(xMtG7&t13C8$K+0x3vk=*H z!3#FA6v)V0AZ2d{Qg%8}^Ix;9g4;qnv%!lhLX1~s*#jx7G?20?N^}6yHYXtc=L}^1 z9zY~Te~FPm>SkS&72R4}&sgE1{Fzej=Rhi21*CmjB<_{v+Iy{*?{5wZvDJ+Pfj#Fv zNBl0bGH=O0Hq2Hx5yW`8a9dqP5OqFV@G&T1)RlS~se8QThxktKO)L$(!O%Hvtp`>FMI)t;o5Hvn=z5ZQ;# zf$WbqK>D;jkZXdUOcPm7N8>-_-JE& z)Mb7%iLO!>{q1c<7AsAaBHwhw*NJdV?mfFp>sc%Z}>nZE?c@wQ6hMj-7;1oD1xq)bl$^1Hy(CC-=mJ0$J{ zRz>+?iN|IBSzvYKUyM zkbTX(-ml))fovsE)=2O&F#*WPbRheBK9Ke=0aDj0peA#3Au^AH7gfYrAR||R%J>0N z=0l(|et_iD=FH*+whBMX=qDCtKo|9L&N%(8%FD_}Kig7mR&HBaw+_9Q{M-!G)@ z4#`WUPUc)&Dcds87TTX|g@?9Wkv7}{QszU6&n3PFQWo2Oun<{w=BV~G1X5NDiESlz z0CL>>18G}#pyCf8l41f-^WiHiy2!)lZV>*S7whF0D0icjy8}qZeL(ilVIX~d97ubv z$n;r>w}5Q#Lm>P5IgoSpwM4tQYI#W@_n+l~)Yle>r04)-#2-josq0`NwkFEDBPC7% z($?ux2JIC1J6?qR0`<_RkydzU;|?i*ACSThOFhSdwDT;Ge6N9&NuHTjWNLadtng4? zoq2L>04cKtknL+Lu>(+(RUd!O3q|<`%8CIm3lo8ijFd7aNSqEN-wq)8_5m3=Eb%yy zy6DrOLUcWsGIaA*8O4FLy8=*?vCxVPuqtc+Uf5ITfUV+Vjw$X5XwxOsr5ujuTKJP$ z%)6hDJhrC`bfQ>gj0wA9~OMx8E ztAPN;ZvH*6-;dcU_4t`E_Z}>q3tMS7_p6tr-7A1d<_Wa>7b))oP-CDSH<6~@SwPzT zQli}gHC;wxRUm!#p-dBLcM~A(ZUdy<-az^_5J-RamFXxT_vI!a&w-%9x4E@hnJPk>QYet|snK@TfDl-pFwZ4IO_PatJ?mKZ3pm&7n2eLfh-_QnG#=W`&E zVmVN=FU^W>&0pD8cqsQLDfcvx3NFg@b&2FY!w$JaBMJJ%<)86=#V93fh(5L;u%fd(? zBXL0LPL}BuAlsh`B>!fKyMWqwm|BRA3|S}BqE31tbxJQ$<l9$HpEri#8soIXxK+U(Ah17A8yp&%R zf0Qe~_A-xc?r*{CT?p@N3tm$pyt~0m9fyF7oRD}~%Ha4(vm%3S<+!!SpXdVQ8)*Og zX{sIvAQ|fc+1D+A9RD6N-xtVv5(4C09U$|gft0fnh-|Sz=5Ghm2FeI1#D?3l?jN%5 z3m|PUOBrnI)Iwx@_@%195m4*r6NS_Xk#+h>jF5F{vn~E=7a-q2`CJFXt?qJy1dA>pm{3 z+XSNSD!}S8PJT;0jawGgH3wPBu`J(&vahX_*Kbu+=L{;S{(6*cexuy-cZK@3E~*Oy zS=Q$nOiNd33)?|jnrLa?XDC|&I#Nd2y|7r+s)#ra65Gn==A~D)6Zes4vdG)@ft^^7 zyo%*>`FcCpi2!V_Gc58<$g7Brwyj0pD&)C>uh#pybJEcone*ooq(`^qBE z7kSN)SKOj*cjS4YuB(N8XOU-ueL)s^c8+#pD)RbUq zpbMZML0dr#iD=7i&??Y$P&kO;1~6O31#G_C$WBxR9s@GWM;}cF4F?SXbpy2nwE)!z zIe^N8Y(Y=a$5%kdLCcV4aIaz~{6T#|aiHO#>7dU*YeBm}he35g0`*)fqi=v6Ks`W_ zpkbiNpfu2CP&M$D1=)cfgFgdw0(20x3$zS06EqSO2l@nb4ZNLzSFp=YuMfKsw+B5$ z`Y%v<$jxpDzk!Z}_JKBn7K0p79tZ3V@&-ABs)Fo6j~m#DGoZtuU7*iEsi0w?NKg+@ zJ5Uo)HBd>=Yv|t$x(~VlS^=618V5=S#ej}M=Wfsv&~(s9&_Iws=m^SPfCGRXKuthZ zK{lZK_3XqY&;igk&??YOPzop!6bb4L@&~zs8iJ~V-T`IRwG(GS`#>u|(?Q9gL7?s+ z7myREBFGl>^dtBcbON*kv;s68lngR}`ho&L?jR>nanMtY(Muewu-^vs6yrJ*bPBW& zv=NjBnhqKXiUIWn`GT5&Ccx(mc#frDpOAucd{_#2(SD^WzWKcIyLr`Uq z0Nt;RHi8a-)`RAPhJ(UE{-9PM2ao_|)Eye@J27%(uSvaW+suMdn07dE=~LlQy_jEd^Q!(fOF3pMFI zV#0l5qD)bthNxi*n+Vc%)SH502kT>eVg|+9?f9QPT zgY?O!;Fzee*l@krY!j$A1?s~Rj8UecUi$bjV^o|e*60-)W)MSk7HZyN;RDnoJlrFU z(rH?f9tpjOv#8|(DbBRbS058$id5R+;nm$Ez^7@GhT#SS-rNxFV-ljf>EmM)jA44; z(3psX&|~#bc)2-=x8YPjf&BSh{McjpB!cKg!eTiBE~i( zG|JQ=))*KS6JgMI88Ap6W)i;?LElJwyrPWAiZu>}XC~M{MhKc3n-p)EpQ*Hm>Vu-9 z^|5T?-mmXs^%&3osPw^hCWxkg_C%?08a7=V) zT=yt_QkQ{>yi!H{@}x&t$VW*W`~*+@$`+HKLq+s2(k(P99&;*8AE!(J;iH33F&q3a zL@}&T;~IU+8XhK-F={}9Nf~Pcg=cKsP}O$KKDbsHC!(4pGK<2HeQ2CA3`HGUB1aT+g;=JPszY&x^q(RM`u&tO#y4t! zG1NFzbW&2Du`%(n=*zE_ai=CQ{NiJc0kQE|Z?L3@a`1q`AUV4vnBo#loDrd6uqQ+e zhQ*=bc}k&cG21FL1JURDn05&R2kMPntMJ=%7TWx{B#HSJ{Ax~Hwk^;UE+?*V)Y^&# z$cVnhZKARk$o4?zJaufoVaohGxgp}3jZZw*;V5m)cW&R!zrC-vHl2{YA*Z8gtBt|f z7?V#-xH2O{#CBTirB93s(|d--#fuW^h)Bku!w3t*Qg#t*T)0*TC%7mH*V+87g_JI|{ty<&*)G3lf6-DIT)@W70X5UYfj5<#^94BY4t z@da9l2^%}qCk#XU1K}BJFsQoK1`nnGI>d%0V4S%M&q#gPU@t>Ny9ASoGh#QMs5wcc zP|v5e;>TQjf)&4n^3fuaC=XR=8=u7?VkL7_mAk2-<`a|1agEiw8y_X&6YyXVs!DUr8bir>^rmUPP;-TW98l{S zYrrEiR;FNt9NoC**YPRr6RobVKL{^lXcEm}C!JKv)DapYep5N&h@5S}YOG4UphfcA;#EA)6^`=H{!(OiY@FO8fWtK2=WMzj*7uh3G)t(kL+NKjph?TE>Iyp{z2V>1B28$gJBo;TRB@c ze9noS+`ScdQl7D%%Cl8b9)oR^z)CEe+r_uNM{tnV_HhEEGEQ$a4Mn@qd-zmGDBkzQ z1nJjJA7=;+(<>u6&LSV9p%OS#1<6$e6#Z3-B zJl<<)W$hB84B=kJs6_Rd$&SWn+FB``rb?GJmF0mu=m?D?NB^4~UuztYp+0x?;i@bl z=4fT~i>83fjhOXD30lRYjfm3n^XkKB_6ZLWZM0Ik*yf5<+p}3K&uP!w-g4G>)WQ0O zh;Eq6{h+%a^8K-s=*LG77HxN}7LOXab%aUFr@cAe_0pR{qYNQpj74dVZxpT&oVS#b z>cyT_It^0k@s(OWiY+@+>yo0mBh4cYZpGeM+3s>}R3t(kcFrB5^aeabe#psFGCc9b zM8C(|#A&SZ;9&@jz&bTu=A$rAerAr8KBitln|a?|V=Y6^=~6>>=Vs9Gib|C6|jJb#_dM!Z@$bQOJ>|H{$8rPvOv`e-<#GXDejN0SYhnu^6*xSwA1I ze#$z|0}Xal`In+z@LMGz;vDl7mud>IN-$5kZ|H|>13Vt0V-qnk5(Wfe9}YQw>V8JK z9q`jf55T4(G79_5wl-+1dKkgn4#6H0X~@pKwP?y5bsoxli*~tn6gI4GHi~N5ZE`xo z_jIVtgbjh}sa$4gd;BLVm#1H73?4%`T!dJocZQ`i8GPL|fY+Lp)fN z$s}Bvl0J50T$_!e4O7Y`z*Fk*&Arjus92%iXsNe0K6tLIP)3eAb@9%K))$I|o<)={ z3D6srdqh0D6=Ts0gJ@q+R2X)V%5!L)g>2am5FVJ@)4YW8h*#Rj>nOJ(>UDvaen3Km z>T~qb$F|(=Ma5uM)H+jm<2I;(p&)uIS_70(6v()TX8>*`s1>_Q#&6m9V$F!l(U57G zM$g13cN8=WBgFF70M^vA4K(QWal&2g3$73B8Ca}bg zquS!uqMfdTApvIvs8%mP@Kl(Z=SIRqd}bj-UavsXuk6?MG2!xZOx(+>8`cb!`*#ZW z5ZRXPQcla>x&0z{!IrmwOCdkh7#6ASTPqgv!$X3SShag@P4!He=gzTHZmGppWCrc) zkYF%SpgbeARyZSLjmqv=&HPGftvWs}s*4HeBR;4s^Ng~EmU(?_u~o<#p^r<;+D#Cg_R3v z&O$2;+WoDv%*JUqxai+2Ho-AQ%exDV6YLOhW#)?ub3WCYD5(IW9*5PjI)B`~;KKp7 zL|)^o7xzC4f9xo6ToS?H;qy~Y7kRJWN9L-tT+KXR1gB(wj6m*!gJQj6y2uci+F*~Rh=1ROyK##O?cuV$wh$}H4qOy6BYwJ1CvpTdl0QO$Tug~Q^|#F zoEK##%v27mVlTXlle5x1?RLwvTh9RPU>kwwQV=={yAWk1YNb=BFn3m-zJWeoc+(mN zD)r^|LKNj$lmj)-0I${>CMxEWLy@fAP=s2t+`fi9r; z5U&p`4tgJP0X$bh=*lC044B3^+Bg>&2jUkI!h!Cf4-jt#EDfrTxGm7q*Z0e#{h)Nv zF3@Js8qiD-KT+JPSNR*dd35*mFyR7Aeyri{5#;Oct`t&W9-$Tr_8Zf#KqsIBFe^gnY5=nb3Y{ac2N0Ki!V8E?1knsQ3|I^J z3g`gj%RZ%nMR7&QcT^ehUC4B}RSEMg;?odkyBMZh#AjHh~*XG6#9px^}P;p1`X9zZo07ig31e$QxLzFuc21|e=e3TFsN;IC@}ob@JZ-^ zuyFmrh{&ixgALI!v2jE2adARoQu0tPYX~0gJiXd?=;-a!sk5)2f0ux+-2#JxyNC4X z`H5QoKjGg3hX9u*gaU_vgHrrI<)8n{hr*in_#T{%t^Eo-u=~O@_R`B9pL|E*^OnD#m!}z%X$?o zf8>`+jjnjWcD>N|f`jOHVbFypZ}Xo%cO~Ob{C3<()v{X2LU}LRQ*!>h*6tIX^1ciI zznZ3< z-W2Zco@bAmTjnmkCh+%yRNX{HkyavC0WC@7^ZZTQ-z+E3f5Lw}@c)VjFlnp?n>Ruc z88rWdy|mH&6T)&TxHdRq-DBN=kwWJS@&Y$&VkN=PJoVqj(`q=_JTHp)_~GL$C## zyq5%;584Jg4SEbJ4ZcPoKac^$bva=nzi*C+Mc{XfwC^@4+3c%VW;4&9Jh!=n%ouzr zz|r94S(xWH2hb=`PDrzH&xs!?nj8PiJ}2(JxOh%H;QLZJ@mf#IYVnl$3(IS9hrRnM zsd3x=N2^S4#`u>HjvcRZ;IzNHL)Mb*arM@0IP^u7`-wh(*S=YKdY4M~#<~xg|Jyvz zl0~O|v}uX^pZn9i9*izKWAvDB-1ocwy6P9RPxa&jr`=-@XH315a_Icq%jjvpNS zVasVR;xdM}+k1KXl|R3Dd2i&}uAX1sO1ysT@kfoujjrS6RJy|2?2VdzguX)YgwYcc5|4WO^=Nlv^_-TioA28CAkc61 z%|2}o^=Un2$?Q+eeO6_C>5v|@a_YySC;y)E$&_=o`%L=tV#L~c*-t*)zwAh#%PBFw zM?YWSFxjSC|AVy?&|dNLv7e8r;icPsY1Z`a`Q9y1B2~5oOp^^xKao+;eN%W^cP;H-DYG|LkqI1AqU0 zJak-{q+{!DxHa&Br|hPj0X_xx_;mcZd<_w;l7?O~&7cKc?JfB&VITP?ShcUrFe@b0KJb=^j+ z|MJG(@ZlX!Tn=s<_Nm>mnn6ip&9g?gne|tzO2fixdp1sA?^0`^SI?cL>(}qRfp3SC$?>b;S1as&m@yP)cd9oHZpyIOoLa{yh7q!1msGwoqKx zctKSB2G-W=SVKYSpv9m#P!PxkR1stk%DRU8P*mYv67N_zC%LXu42{7wRpu7&kKVdp z^ct9)d%x%|GE3-mXFzK}=}x)vIK%_obh>2F3eX8qHpt0cr|SVq1}y}g0oi-#bS|K9 z(B)%$)~?-i>@rmUC;UI+0UO*@rs8W|J+Qpb1me+|!ZDc&!Jut}@HSh$`6$qIrZGgg7q_hQA1X@%^w1^1Dzs|^y1bPFbfc@X% z(<$vwE+BtqKKWdm)`0Q`onZd10`iX(kbka#{LBLKvkS<#=U~bcRC_8GkndDLzDoi5 z-Ua0MC?G$wfc)eF@>2`QUsyo?nga576_9_Vfc$d>8A>n2_x?B+WpG(!C)RK=lOba@(p)TLNZpL@4_e62+K%$tS}8sQy@)gyK^ffd zgWqD@J3Pv(um9t`Y3J$q4x?5XxC`l#miA#mUTaaF`9^x-EPN*yYmyc+Ul!x~lM^xE1`^dpB zD$E!gA3M;5d}CZJUiQSBs}2nskQ?7o!Ml`(p$wr>ZDg4z8xgMO3(xTOP+3uFnwD$wkS?<*R2Vc@zTI{fGvx0B zwamexAYbs^yx5oq@?D^Ki%4~=te zq>atC&5H10(j9Ly_rt&K@%cHvVkroJGx@vW!p^(UyLFvw^d%y$x>=zNI?x9JfVjL)Jku}!4LFigosqQzJg`{ z+!qbO?V;H3U?C+QxkhM-SFB?D!=V{((&MH}hyGXfu?J~|)`v`~ybT}*i&#U9jUa;%~FVxXtq=&*PttGjm5%2C` z91HxaFGmo2!T_`!Bmc79tURsLZH}aX5zlFcHoDykaxVB`#5;f@;RTKq2iR;te=*nu z@ZWgca_q*hBAh8^HjiP360|ZWlr=3BN^sdv?)?4U@W$?81U(GI*Tqy%erz-Gmc z_rX~X|D96g;qQIgVVhvYAC`aSpa=<47y>5`)AG zlwGnYW92vPRmZL6too;8*j}6xwZ-pw@wpwYX^3TC{)OjY4L^^fvAFOyqT`AS|n6pfeUgT4OJ!!eN6xJebUKYbF zdJLcObA&Ni1GI4(o=0-HG6FbW_>@pbau`-OBmUDXBg25+bii{=k5WGM98k)&HC)MM z8Rr14U^nn!)us9Z0(o&AWyq?XSvDQ4Hwu$ZJ%+9|J4$A1J2ecWS1ieN)WgSq1&xttH zH=^}J@dPS?b%f-j%yre%^l=#YqUD-Fi|AKsr?(1kiTa$gW@A(yk0e1Z=R0+9?V$zH zh^Z^aKk*HprD{;;M=^Xt^cYVHb)0!AQZ@hc+0Q)^pBry#{_pk;_h<~*wJBp~nwY1o ze>zx@g7vv4TJ+|+_k-lIkv#selEDe&0CETElxHO()nbOeV!8rKV0yPALOJ%t(&P7PJZWq_PyrrpQPh4SQ3VOi5~rjL~IE>q;{@#xa!{ zWy+LNn8!E?B|}$cMlwn#ubJ_J8L4HER+C9d$bhgJsVJSaYhqHWqoX70SFAXB`G(zV z*Q73UOtE1`8Dy+oGimqQRC8QT1~^h_whcu{j?|>&F-lU&AcvMzIUE&zRdusDtC;y!|03oryhoj`zDv!Qw6Z|ypp*iLW#HjE*v0U%ZYd-L9SX@fd>QTvSlj0_>9+f?DN>a+iQ7_^|Du(%stYXPxVqEEzVxv-}CV&+V7Z{w$b8cdMg z6DEj}D0{qVlh}ZD>I;)eJlVcoociJm@%)=_#1Hf5<8K+w5NDSy6T7BN5f@jj64~E= zD^8}RiLI%r;`Y|9;yUX7b>M)wi+Y#WtrNdON3V_@#e?0u#os^tARg`8C&t2-zYZT3 zr%?a-!Gprky}K|UJ0|}A@kg;1a_&LqojrTR&0V|1-USPU`NRqF>gS)ukUo8c`OF!S z{mU=n$m-Q%^PD+iGQLZCe#Z`x6do?j=g*5NXh+|!UB%PWr-k{-6>;~cpF|ks|B15e zM~;YrpL`-7oH`}iv}hsBH*ScXXnP9$=-;7(cme(l*bs-`2)hX#!(wB_ZOFNXxcS~a zaUU|yeE+@JvwppBYt>2|hJC-EJSmpI$DVF(V%efaVj=w6w`Wgbe)2>-zI97nK;2;2 z^Amj1zFj*p9qs!T_CA5U(`d6>n>OMY?0Wt3rC0&KehwLjpkp^=M+_V&x(5e~+mN## ze%to-*W#-+YsAO^oI#d-E@Jdu#oA?4MMy{&F?N2uaQ1J6KY)fw9#%xy^~Z1J1(g*w z+Sd@{ca0Zadv+DWM+_G>gKb68_%etdbbc&=QR?w;+;j^eoaLqpQfVE)NoOKQyWolvYTkx-A#lgMF`W`DWXa1W}=bN zLp1!ZmuR%68~(_kS=3x{R&+b~NQ^lCP&ka)BnE6cCBD0B7UMr#DL!c5Pc-oG7w`35 zAUdb47QMm-2|KURV#BxCS;w3cHjW7*dE{JC!R2@H{P}Zn`SN9P;lc%R8hx<`<6;N= zw`I!~v2o)@u?}NwCC1ot^y5;D?ax2|Tr7eQ=VPqSo;_Pkn>I~Mo;(>#N2(Y%Zk+P` zO~xEa!Z?Y?*olk7o)bT77ZnvH!ZC(ILqkR1zI{b6%!3|1dWgWlKoJlSAi8wvB7A*) zMJLQ5A0Hpl0prEf(^I&+yW?*?w-s&BkF8s`7A`I>q6NlQQ#_p*iv3$d=0B8v=+Bpj z|9o=ouRqTHg9?WZA3F5I4?nzq_43)(dxh|W_eX{wjvVNd_-t-gwiW)P3h&Q9{_x#* z`}XhOf7Ii7=Ia6*aIe_ypMU-tVh`-c(BJpnk$;u-c=@D2bN+a7XQ1w<@4rt6^O57l ztJP`Qv11=A#$;z_R6dvvvG10s^Knl#^eNDoV#S{Pam}Ml@oLSxgyn`27Hz$D?cTlY zdk8Y`$=$#Iyo~Xc6HJlga@lX)x_agE+^g4a+?ko3tyH=8`fjmn6J(h5>9{G8x!lEy z&6$1X*R#LPJ%91imEUjuk^P5Sc6k;C_M|IU9=v*a)slPGq)F4JsTt=`A>+oq7f+Pr z<;&;KUny2BGy5+~{>i6LpS7(1d&bopcb}foI0JgTq#)o;5jiPrs}PtNlCS ztn$N5`GV#r-;UaB*(%A-JnlDpRvu#fJ4umoG0+ zLlhNu4eK@i@S{e9{*;}3J;b|H2N?2UEeCK{e7{1Vk8ev?ci-Bj%iF$#!(LIxy1N!G zoZrAh&c*=M>{<>VRH;~@!UqjHcz1d46Q5Eo-P{`1E?v5^t$l6BU7vmVrGul&f2N~q z_6LB>?*AXL`3xK*H5eA?tX#GB zU3Ey5tx~1+;E^MZMx#ZILA`st=kMj!u5Ig<4I4FV*0OP{+S=;SzB^{=NQ9gkkx?7N zgM)j%+vB}1tl`?cUhVcnEymm2xzlIm)JTZ>eCd+K;lT;hOnsBy?cv+n)whSmN+PSB zIX`vMgps4hzx(;pFBUIexi-98`0LAkC#J-ihL7d|vXCyLw=$p3NF(#AwHr2U2=7#U z*zMp+Lncm|m~0w8^Cha>xS>vR@ZY+1>*Y6OUa@x5woRKiZP+j+PGiKD)R4?u*yaB~3A=IdjL)&Eem&chBC9ub<7h ze(A!c<@aB_-m;C2*mC>q*^KKquP=YN`m1*n;>L{E_=^=gfBUPMm(R#>_SV)-Y{dQS z*IU=DyY%&%)vN!=xO^}6)6ug}Ud#3C`D@EBPn&pt`6F2MWab6zJZ4|rx@q(3uWsB> z9e(3Rc4p2LQ?o8V_-6Ox-_OmNGw1UCufN{1C5Kln&gb9Dm#^Hpcm4X#9a{_M&8?oH z*^ry{e=deYC4zFdVK)D*BG0nS%}|lYQKp$By5rypL^e*Zc@QVr*F3#e$Gcmingd@r z&KAWo_+{gaSGufF;P>Wqx2wSK9%U5!9+<8J2g{3O;jjz-Qt3GGQZCNFL>!1m5uv?0 zF$Mc)plTH5Be<#OZt6v`qF+NMnXd#3OkoDIho_UjtsS&m@d;P zZ(xQ@(=IpqB_qdvH>Dls>u??Nm5_^h@2v_xr#Am+mI(5jFFLoLD>^rh#rbrG7?$`c zwF>_R{$lOYwW6zkS21Pc6oFrs5J?G1;)~C}5F_xy|EiU%#G-|ZM3UYldUx+7W{#gO z`hC(@==+9=1vBS~9ba!3X;T)86=};vf+1F%JNToB@bnY6*M1{5&sZR?ZQmz`d-{sm zVNv45s`VnO2k!U(cqDc&S}cB=yHKRBTrEy6NfQJ8yNLOt#|U%g9Wl9os2K4{FY*2C zIb!`doOQSF5XZh+E3Tz~C(IWvic|4M@!Rm>V&|Yi;sVaKS8;A#fj$1sdGo}L#h;72 zUw$bb;tcr+XSpPtv990@`xIx2WjGtW+`Cuo#QD~I=#aR_^DoY2-=O}tIB!1Lzh6AW z8RRw2iBEB+JA?D{7dU?(T(U%%an{Smc{3j82lKDLicL6wOioA;XK-HG_St8`eC?X} z^U@`89XxS3YXo)fEFOUGG|s@=ajxr#^WiI;3ooGjyKURVG@Rj|+`cV-MS9_^S>pB6 zr{WlRzk)p%aTaQavqw5?3C0Q6lS1%H)zE~tyPD&PYQd2~? zt^p#ro3EHOez-8j_7vkr4HRE2o*_1Vvrr8AG)xQ|nT%f_aT7IhMlBUmN__10u^13B zKunrEN!Uae72Z)kqI$2IqITp*qG4=PVLzp!=rlY)^qmtfM(i4^oLAr7;vh=Tb`+!L zjS*GHHV}OO`Jcb&_3#-=H8-Ypb^~j!Y0y(St&z&t4_oZyhJP4BIIZ#xE7^`tA^3UFPGQ*;T~E;LZVke)Q;3@h$E_zQ&n< z?b@|sF76n3o}G#Nh3V6$i>bKd;dvHchY`bYRmC%HVq&5&;m!t!4iOz4Eg~cFX$$T$ zaJ&)SaR<_^TQ}wW%CoDN7k*h?o?kh~?f$!A#Jm$P|0tmHPd^;}?b4YuGmpmS!*K4m z-!5G`bL#Z*KkQ zWZt{*5`Q_+?)G)Vr$e5{)!np>XvGU0JT;sxa2z8C)> z?ytYIpTGEvlGJeX=8a*)Zr)^K`PFCf4<9|ccHu>mEF|1yu(NycEV}!IJ}>`zw*20m zoQCDFJ@|9VXr~Ur0bYTo=)d3c``tf#H}-XMpM3txFCV6io`3Y{1xpDJV(U8ldbS<) z@id|P{p_#yPU9zpM!(WHp2hX>Ywy{nb*q*wnpG`U#OC)58{0bWqxwX@R+Q@-<2P;F zw03FUtcBaC)KOEb;)`pd#+=V*jSQuPVSepBTw8tIthr0u_I?3f8hNKqtY5X5!=m-; zXEmTp`g^##eB9inL${_uU44DKcJAoi$jfI`{TU0_&8hJt%UwQh-QkmZ^?EjK8Whyj zxwe0&P943xyc;&E(K3+w`}_2bN=~jdpx%IbO`FV}|6xdQa7u>`9UHbA%~FQ`LkAh6 zl517@Y-QSt)z$Uky=M0an9#?l7OMZ2Nc>_++KLrlZQS)iO?_X3A*O`!)$7;S66Nsv za%tL%uQqM@?&~e97f*K_+bb+0%rK}@(x1;>CZIkFH|_oIyIsXAy;tVYzOU9ysrzBS zeuMf&#EiK9BLuA4`TeeaKa_j-U)#P}w|?EOeMR57cD3??+2MnRjJTe~L6Uj+{qnp-1BbS+Rd9+Ze3PlL}bE^d+DlunVFec2j*QX`A((u?T5Fn+ps~&zMU?M znaa!@@!;;k{kwOlWoo^=|7Mn2E^Fq^x0+J-4g3%w8{bV!2jO#v*P_U)wxU>8F;OZ* zm+tmMMq9UZ7cnY58QTwqrHC2(nNiroR8PllCPmq?3G7Z3Qa*P;{wVCBu=^4^#C70N z5Etv$&De>VGgQraKX)j2p90z9yS1Cem7P1qRy>clEnX~+;ki8l&-yQM&%I&A3b77% zf!lF67>IkjlXy;V#Itoa*38{_majqi$WK2NvzUgA&rqi;_<9fcM3f0BBdY1EiQ2xk zMP0AD!Y{~Acy#m-?+hv_dd2h+eAXm>@u~1z)?18UG)W8{`>B|_WWMM-2A`NO-!DEK zw?|a#Vi1iw1PS}dbs}(JyqLV|8_~qoOAMX7NL1;uR5-Ru6pdO95Qnh0_y*5noRO3@ zem(XfUtuq?2DWPVnQ7RQ@J{m!+`oT@{R8hZx$aMzG)cM7#C4Gvk3GU@Jnx62{X_9t z0Pi)23>l)_YsTPn0zL-@;vSjzn!MBGvw-)S-rnAM?l9eOf6Y70miS!ZKjA+f_#fwi z#@#-0YS1I6S&f|=)%~zuFw*Thb!sO98rG?gpUptBg-f%RtvhusjaN=evb2f6kGo6T z=Iy!$1uB(WG{NghP21ylV3Z{PX;-y!QM-4$2XkK~s#K{|;e+Z$$`*g8%=;n1t`#d* zuU_4|WBD32>`T4(v6pM@+Mdl@b*WLq&7oScf0a{2)beOk)3H{q9C217X0zRJv-uV% z^PYKhww+mb`}%<*L!PHZ*x89vc6L*%-yE8yv$IPPcKG?FMkgzh<^xj0r`SLLBmRwDz)l^4?Z|jrBRD> zB}$aoiM$!WQT6NBzlb=$r`#Fze=5*!_B(y+0OD&$o*?=){@r)qonO9u`CS$2H9c9; z)&Fkgu7k}bLMNJqVVPOz7n>V5Zv3)FjT%=PHEQ$-@w+u^*8CFn27vzEfp%-Q|MHTqD${@3|&rGvV^eS_o#lM)N z_TDmw|Mc42@!KqOwKW&b_FtSZ7ybOWx#`re%{|91HRHVv^Zud3%{{!lUOG8BUBURd z?dvFkKy+tpsk5MKIZ9*mz(2{+%h-cbl&{Yq)m!%$HtE}?;MkA z{uQ>Khn*L`{@Q$T=gxeoPf!rYj1k23iuPCwY#Z0&N+7PqY%kYd_8HTZ!B@oj)qJjn zdE}vg%fSBY$d3XI@po~#us&>{c`OHTlQX&DZxbWVHrSXF-9s zU|XWRdejLR2^tC-2buy(0Wr4D^{3C#K+&l`pNa4KD7bm7pS{3$QS32H8`_ez^5LW3l}bYfb)(S?`GV_ zUDj>fV_ogjr_V{)w;Q}mKr=z?S3Y~{g4i#`F#ZF%PGIfvL%K|o(Rl6Y-MeOM^ve%F zn3pbGXdaoIe6y>c-^#jm>xN%fPbtaJQDaef#!DcyYnJd-rZL-a#?r z9TYP@JTtQmE8q*9)y#_*FE%e&u)sVE;}++y>o`vxM18hpJc#|$3{)BG#YaFzH~PSM z{pwZo-!EV0(2p2D3+Bx;#|$3)Bkt;M0YwVKeB(`ym=My z-p`X0Cr;4c5AlvoH0rb8cu7FwxT6hR(>X>O<6Rza+?7s5-D~UzwgDf)vK`qNUsozr zsPHxT7J^2AxKH5Ra{^TZ(YKsul*|2P9o&=NYuB!w8GZIM(%9rHKU)eYJiusF+!{a#iHk-ir8Hj5D_gR96KJ)p^bJ+`z-~aOZ@26%zGiT16IdkTmGq=w?K#=^Xs)PJiyGLh-W@Puod$sf)cmWUi?t-v<+qP{V zh!3MSZQ6+M$=?9q_rYHK0h#oLcqW@59qx#5DfoQ?zV64#S6|J`MvvzGdi3CJ+O+uz zpTK^A{Kp~h<=78;3428A!22iY&tudEd<%DM$dDob2Je%BNA{rl6VZqsYJYGb9U=b7 zhDrB_HE-Vh)e^t|_}D>%_<=Xx;5cvN_~um1Ti#f(fKP;MeJ<@PPGgMw>;3one-S_a>Z^R!t+(=z@4Js5{O3RU z>o?!b=g>US!TG~zXPf7C@)bLj`kjT;j!?kdtPTIUf?XZVqs zE|=@0o5qg)bJNpL3mw7t+(Ng1$9(tKHEZ~Po_&_%T%8|SvV?yJ8+~NdD839jgKyON zHu%TEE*Br*xSUTYEaaJ<_xZIKjO8s0w)5*Rxs&6&Up{@*{XDDLd_L>SS^SnO?&h!G zIiG(EJ2`>Ad= z-ZFI?->viG&{@*$!&qP7yLi4IHi~cN`R+M$IKHXpFJ5~s$2apF-`w+U(6Lpw-3Gp{ z5bd}I^UpCpANP3v$;b4#il_Uw@$34{;+e^J^C^9%@OG*9@v_Uy_`g8s4cN}+g$wyX z$Oqrr^Cgf)xBUEHpj+#~BaPW4<6xVpHGJsMPhW(N{0>_bx{bHe?ISO|zz<{2NxJ<# zXzziZ)KjXpggi9~w&!bISQTIORIKIi}F93J;Rafz01qFOG z){qx@a1EzxKi;Czo&1`TYeYLH^_s*V!&vYP_?_{^eE#H&NpSE5~;rLeI#Lp8EH{&2}8aJmbjv_53Z&>*vER zXJSl#4sEBld;_`cLVe3YbLNmC{3^enk1HzTH)6b=iTT^O?Q{6-*<<-FYi{AQUYo_= zgkHUafISnkg)RsI{Nt?WuF7=#3+VIL(24)R-<LcU%j3=k^RnX55psU#L zqWP-}fyQd{DJ?CXy?5!-5Z(TI?p%Hdz6SR>unm5I^Y8K96V^_A&mPWq@2;{PM==&3 zMjwz~ss6y-6n+Tnx%Z(HkK;PpgQW8}-6Y08YRBgo6JN$SJsbTsu2(NUv3qyEVbUZ% z8@dG>rulk^{SF^Kd_Xz+l5~3~^z$He1NTcfKXwe)yN+`H%P$;vN;p4sNVa4D{_yR< zd=+L+&dHh|F^Ifo&Rj~P=!254#(^2>{)s6$`(=G64xWmK$342e-tCaXX7~$yJH$EGru>gTI6rZM^WT5xxZhHBJFspb zy?O_Jsto$>?&07g;g4{Cho8V4d<({?IT#z}LVsSwJcH~!xE35pzFaF@l3W#;}h|%0-}{ZHrRA4tY4?zFm2lFxF^JMZ;1c?A8h)e zKXBKGzXKl1AWz%_5m4j-C?F>)e;buYu0uE|q8p?V&&}{P%6!_~(G_M!Rs| ziGP8)$Wn}hmtJxS?Xi4^@s{@ZX%0lXPV09Xb2AWV9+ZMWdueKJhiJ${ZKQr89BMzU zg)YH7=*jNgyZ?so(|AVLj1ZpVZ*9mME^5J(R%Y|0)!968RhHoYH2msf=yunV5>D$b z(xp#nzYY9{TCc@raAJne5;# z@6P4PYdVQGTnvByGRE{Dpo`QGUqDaD9zMj_@EOKQ+&|;EpT_ro`YHburo;ktuG@a*$mo+r?B4%Q3 z-f&(zPg|MKTfNzpXRIjZ9f!C%?sQQ-9P2_(Yw+9x2hSRw%i9gh;h7W9=WT~|;<$Il zA4WfHf9WOBhM&6AA~-AhcR(8<}SF?#>?TSK1CZhqD@4X*5&xl zjYGHp0R#vLzQ;JtxgcinXya}(Zdeg~egvJG!`XBI!dOD@MfJbnwt zq;>F{2cb(pLH?hkEw90bUx5$$2>pP&etZwvI@+}o_CFN~JxmV1A3|ua_{W-R zF4-Q126r~+X-^gK?DETa$LIR<^CxxTxqW<`Y>)!Zj?H*(Hy1y@w~J?w^zaeWN73Gd z2)LWahu$}myYA`C(-w3D-+7`fSgZ3}FqTr=uumfBjK?0tWALdvp;x%eDC}S@+VTR{ z7H?v#dja#Nt19Q8VQ=8}HbZi_QWed#yh}{?r%cu%FUU{%O5QHca{>9_$t8AxuSh)zQQE>!p}Kl1|_r zVWolgvashX`XO^s0q-!x1)Fe~HbHH~-9I6Vju++fi|**paeq?CVD$Vkyz|8FJgZ*^ z@ZW*X7mdk{X$^SgMb ziN&;E#<73P@lBMe`(y*uR@zs)^uAJl+3c%$_CFj5dEhr!@QX8z30SjtLgmDR;I$fz zC_MG9Mv(v%+k5Z5SByJ~i?B~!j^F0y_(u0Pzu*5c*(K>Rh2c}L;kYv^^eHDlFA{zO zDnA#AQ>RY->-XP(f9=N~f4ujz&pzvmGXd&HTF+7c3_y4Y;aTkKk2?$bo(mSML!+wx zt17=bb^L#WLtkZ{L~H3?Cf`S?@ko{%Z+anp+IXJ|eABN`K;oy!okh{_Qeb(9_g=!n zj%hi)X9m47f(p2Jm*4|biBE5;JPvQFh3@eXQz}iDX#{h{MtRUjw%nPexAI8UebbIE zJsKTBU-6H2l?epvZ6wW?$}cw{BukP9@m>5r-sLPx+}GJlAHgC`>nVDRM-F1?C{lu8 zV-)+}_JB*_JIVfO{&*e&`2!kPJw|AYH_g*pAe@Vb>V$%by>wbnv_i;1=!|eF!ng+Y z>Mm`Nn7k741=uIQ8{aBSM*KpANFJ&ey(-$-2Jc>Wo9)`zx(#O6O-Okf{bTS*KtsuXE{x9QR2$?vwEDFYe_-UfjU@7v%qiZ<=1jSoayq_N=0E)tBMWY#7^f zvHvx&RomQmS~zdz{=WBe+)3fR{`n>E_R=BlSiX~|FMOAKmcGYFEnLHKhl}4dXwWHa zs&2u2<}DCS3PYIIOw=wqU!eR%BTjp?Y94{87+W^tO!Ao_7xX-VyEE8hJjSnE^C=(u z&>D_AGW?N6Px1F2d7OXu_@jK!3(xT%-(1JLuMNL7=|5 z3gJqG%MdO=$U~rV4H057roGmrY14^AyL8@#gIDoNz>UG!cj*gBd-K2eU^-$fkPV{2#G9TxeZ{Qqs6V7Pg#s0%uoV`AW zvmx4dza3!$LSKYV(4(c8_q>HU)ST@bKNNc^e_#*d-zNOEdiCnx@m<34C!ToXPdd-XS@>z3 zRqnvqw>@=g*PaHP zV0i;1AAHAg9?nzuVIFiGbNbad^BjS5;$pPB1oOhnu!g(|->g52J50agESuI0KViN1 zHuf$Tfx?{#V-fme?J^GfFbMH%ggnflzNh*TzZ{`4g5G8%Yt1=^&JKuw1(N4pP#=5`y7v8olLykOly4f%ZuQh zV~)Fy$|9)I1JS?-KbBGZ>#x7wwqwVR6F8IIitj5v$G0MT@SVtU^u;f@{5=9Cnj^GE z=+dBJ!|6Ed-*DOB!Czp#^gHfwiM0mqYyE;X&pzxuAEI+(oZIijo(1__((ABo2V6Xu zw>A9gtFI>EY$+^$KGaBl+$fi)K$eF;4?jnjCcKh$33cehmYjgtN9Ik33P5s z=Xi96_XYNQzQW!&o$398{Te#2JO;cEu@84q1-=OyaoOA5j!*M^G<8CnT zH=rkf_o=5j?nd($upjyg!XEVB&yd+6*bSY#e1Nm1C#Fq1b>D;u2QKQ|`N`%jTKG{Z z)lIf;K0}8M%bYl6%2=#hSHAJuYv1m`9?>Z=p9U|#9OJle&T;RU_qpx@UcX;GjxVHm z+tSt?_s;n}(3AHdvwg7BXJIF=BD{ybylKRU6S>*h+v?Y=_ZTV~j!=lu7J>XJ$)9`y z@j?ASb5|Ol+SaLE=R&`)VD6;@2kyZAX5MvVF;BiKNz}=1j^VWzHsQEO#+_yP9QWTj z?z;2)(D$_OPJ4^rVvq4X*zMy>mi&qRt^c%Z+x82L(XU{>vjpJ|)Nu>KbqFL2#RK{2 z0q|RoHOXl5aiTBbaQwxwr`ebo9zC}aZ@91(Z+d$N-eGD!$K7b2c1K5^F}@?m9c+&K z+Z=c9`4Q;jcbMPof(?HGU%3IDRjHKe_Blj^760 z_?-ZbyVcxT>gRQ4H|Mz9%}WPg#ut{|#^1u(;pfoB&)?s`KYRZJz7_RYCeQTGi9%^e-`GE**Vb`p2IIqq%q4s$Nxj-e&ISO1GSeuIFI9yyBRcMCY~ zgLB;f=ELS+$EUx32ge;g-sVmx&$~9C<981D-E;2YBUNJL`P;V~cqC zOE2?Ju)jw8z;kexgDaZ!o!9rMdo)5*%uizT+^%k}i;5QH4Ji8j@=FFC#cv+))%dOe z^8${0+#J7u!14PC9KVggn~uohsn@mVU9RoPaSxs2w-m4sdlkpMc3!q*7QglWGTw7^ zuhZ#WGLN)7zum?Xf8oO;OG`gqFn8|JiQ~tM@7A!7y%lxSm{cD@e{AQU->EtFlCQ)! zkc+|RCsU_PJ&8N*ynp}x^qn2Q_2ygoJhb(h$CmTOFFwj~N1k6kb_j1dpxv=n1KVwE z=gC@})h=sj(6JV=-5njqfS00IV)T@K^|t^|%fZ z`RCQgj~}0l{ncUE%k7ByWcz$y{!Mvq$D?gKwj186O-6RJCaJU*P-+IJp_eTil9?egD^D<1Yu)7G7KwQSk)1Hfs_RCUmNwz&~#j@$s>R<)^{+PA#9 zcj_OwBg}DknBy)n$NgcBeDe1+<`92v5oqoB4+P?i*0^1hqC zAI)($n#z-(sE0XyEA0QT$C}`4e6PC$=PU1F4Myv)p%fN9@WA#z@NL2#tfO#Wn$IaK zd+q8guO#|nyxqEW>(ej2^wR5Hx^$U~HNtY7u|0Uj`hZ3 zoJsCSxvmK3AyB&+)@Q@9K01ou3j7so)3>p|K_($nsmsOoBJ8|Ggcz+L4+v?mmyGJ z(s)h2vZPK@o%1z*Dc0k+#~0>56*E{oa5d&pE_~kA*aLf5!R<`QDz>( z#r6BwpI&EhogW*FYAnjo7*6MjbY6wK<6=CaF$MR}IqsQr+%4z0&rN&(@1cC}w4oW3 zyjOSIhCAEhOoPs1Ab+zQoqyqOImbP7e&hJ@AOCaV!tZf6ont*uvLL+_51qYWjXeM| zUXC*Ra0i`VbIbK#7F<;L7`{*M4Kn%1hHk$07h|h`XSNl`@lUz_$r$PDL-1lVb}-g$MLA1YaI%=q;5>RbsZl&j zPj85KwD_02Yz%8Wk)nUc%g%D3FN8z?idPgcjTAgVG%#g~{spfX12bLx`(3dHCQ`tO zGceP|zuiSnq#Xh>7|4mb@M2~3$9O1@f_%zkW?7lQp}&YClriiJ00qq>i^N8+c;TGT&*y7(LBkK}`~zrol^ zgrhSV+gHrk#um8TIiIn|H!}9l_h9{31nSGbMKcWki6H&kIC_teOZ-14zgi2Z_9d*= z61?co5&$S5E?`Jdk^qE)M<&g*k+1NL@}exk4EnODC5y*@gS;T8jgZ&(klU}2A9R?V znknCuCOG929^sShL*++qPs)!54+{G4aW}$F2Zg|=49W%w^i90*7g3>mq=TqQ{2xyL zpr7F%l*0NW>?0_LF9mH!5tI${09;Waz~R31a`80FmrrmxY&J}=2nCdjpE-TziSXq% z@ss5<%mOi=A4*Id7?(6+(s&eXU>T-QK3J-uC{>23kt(Rl2_aIc z^7&;mDN_Kfa>#op%843y*&OlDrK2|>IkQ~(d@6{CYT3w1IA(p4HQwMP#2QBx z$7$uL;EDO=<O-kADk(S&Du+@)oGEq1nS2IdTM5BuP)#za`oY50V^pq!rwV@ty3&s?$9oC}=>JJS zj&G9A@q_u(Ie*SEi-O!aXA%T!_&m2#t&wMqJkOO5oLfmsYUheKolXs(x-=q>*T^$+ z8R`^jJ)0^sY@gBc#S+Gm!VFfMxMGfB9GU| zGjbW~6l&zDOC$1ljXWclp-!Pjp1L$5kJrdEavACrYUHU)Bl38SJR_H(PN7Dgx-=q> z*T^$+8R`^jJ)0^sY@gBc#S+Gm!VFfMxMGf zB9GU|GjbW~6l&zDOC$1ljXWclp-!Pjp1L$5kJrdEavACrYUHU)Bl38SJR_H(PN7Dg zx-=q>*T^$+8R`^jJ)0^sY@gBc#S+Gm!VFf zMxMGfB9GU|GjbW~6l&zDOC$1ljXWclp-!Pjp1L$5kJrdEavACrYUHU)Bl38SJR_H( zPN7Dgx-=q>*T^$+8R`^jJ)0^sY@gBc#S+G zm!VFfMxMGfB9GU|GjbW~6l&zDOC$1ljXWclp-!Pjp1L$5kJrdEavACrYUHU)Bl38S zJR_H(PN7Dgx-=q>*T^$+8R`^jJ)0^sY@gB z`2QpF?DjvvXSc|u7eoR7-1|A_6EuTri8Kj{=bTSLGg8I^$_t9;m`{UdKs}Kq42tKJ zPm^Y3tZAi9isz6|jiy#pB+&qh=g5vUn&;@>m(eIvW6uen8cm`Xi4cvl8bt{?2YhNY zCAEn8)F>)i5%a0hRJ0WBvwUk5EeeYES-v%z76ps?S;RDodTERLS;RD&dTE{h8OAk= zfhBeNXBgLL29^x0;S5q5#o$tbHJm|8qZwQ(xQ=S3HHx7ng6pVeTB8|SBD9ujKs1Ws zutRI921KJ74m(^uRlqcg;W5M2Qw2<;86GoyO_e}3iWOmnuc;EKMl%GfTXZzd7%Hl( z0=Pyo6a$l4F`*5qt8#!wGX&AdJ3~G-ieb2vxyT5qtulZ{GX%}dJ0m{LQsGtsX6Bjk zX%-7J8w|(`l}iN>tyCyRU}lJ1DgtQ5LQu54Gtf`1RHa;=iGFG{E9bJ>d}MPRLd4onPF8KCZ-h$lS^Jb@Tm@;S_zXiEuv-1 zsF4@8ehJiygvZhlC~6{U)dokkBEeZQtszjfk{Y@Kg}y673auRw4LNMS0W{HqkV0z*#6%7dQwXYw7L*fI z1tm??fX=KKF(fCb3QB4;!{llp15ty>Xh3uTlbBWB?btW;5rYL4IBr?p@8ALRS zMk$LQ%Cl0{k7+cGLMB5Z%S=@}qER%+B)F$CADg0QSqRWXfb2&!3$Kr&NRL^O(FFs(`m&q`Gu(`bf4G^-J4W~$PN zMlmd&RSjWUsmfv+%?i+rssv)#YJ-3p#h@IOv??N8D^+n!qgerxqAh3fW~#!7MzKO1 ztE%epsnN8G2F1+E1hSc`Afi#M0E56*71iNWqiGckiJ8?^gHMg3Su8Z7_jJ{KYBWQ5 zvjEy$vr0ZSilNMzkU-7?Q&znyrqNWOFmJ*NsK82@U=`6QDo~iW#ImZQw3P~uX*7eg zw6w%BD-%gG6%^4Z2Ic5Ug=JMj5GxfJQ`nWDpn$?M>k$Am6%bLlcn81(6Bd@m55cUI z9<#73KoH0lmRXkonJFz|;Z~2!;B|-2or-HT6)5x_fMhaM0iPPOi5eOS#ImX+xRp{d z4O@Xi-$6)bodRyAjEIIDoTa6Mv8+ld$4beVh8>)xr7bK8G^-OCW=cdX+{$`f+mAAe zTh$Q-D@8GlW?)8O%0yFOlQWTHrVuevE43aC(JZWJORlU`0GHi#cu3r++=83LgZR|A zMAXCz7m=u1jzyia0#g!M{IxQ|!~#oJv@X`D$*9If)lsWjy|xgV-7LJSV`i;_j7)`6 ztU{tD5gD;+wfGE2^Gp~ut1$TtOYw{t4dt@}nrFah2%i;^tXxM;G875svqF-(@>+$$ z#>@%?G2O^RsGF!&MXZWKXoLVP+{$BSogoxL0VZx05v#^f8X*u1xzd_O(Wn8Z%JdF^jRYYbPX^{x#vr^}&QPvp)*hoqQk+3R7OeW{BN{9SYkw*&UUgM}|!nKV_R0zS3M%_89D*Gc?A_U20rTvboDkFtFnXmX1tf@jGp(+V{S58!8NiRl) zL|WAn_^ym*083giQAVWoinMqoL#t>K#%-0L*HFBNfmTXkoaljyyG= zV^WW%nB+5nK_g-22D4|S2}OS-CsuxNOyCM&xC*s;HKKr0s^S7>Zg6c{nsBTK8I;iS zgQ5aez$&gbNTP&D#VRgf<_1+3kR&AaPzJ>V5CRecQ=nB`t-+TfN{AGvclbx;R#LCQ z7-8ZO3{=LZvUlKyuTnt;Ir)b`m7^1$XiI=dLK2c3{S|zM+VLJRQ{MmL#hh^m}r&j zu_$BVR$8@%)~!-JnW?a&1K0{mC?vdta>6AQcF6ijXGbC7t%EC!OMGO9t5L!vMYdz0u}KosfoYg8L9wEB`V-kBUu4Q;lZL@cs@0f z74TCLm`aA_QzIFkoeH6%WH>%Gk`=L2A)LyF;Zq}7AwLymh_a!4Y9uS80ToeIo5aIk1uz7p zrB#lBs*=WALPbK=rf8}(-V&q%WHbz>iQG+~sM-unl~i2ne9JsVGOz&(8ir5FiTJCq zG!R7@to-8a27Qm&>r-j^2U@O)HTqP3R}r6PodJ<Z$X(NYvWJ3cicf+sCa z(X-)GBO!Ux0EnIqpBe?lkp@CkL8~uMD$gr@jw9hVQi-6qA_m+u&1Ss)|HfCG@vK z#JeO=p7Z%kKEXuoYtd9JiJ~f@y)}~2(E)tU=Vc8vMyt=KMiLz9e1c?2Uv4pK@ma;V zsgWe!H9kR-u?E~?)a29J{}iQ7;TRE(q<~m_&KC@VPm@u_ClHnBL~)fc-Wo{>G5I7e z<8L57O-2nq2~n{YhEI*8fu;B)E_)(Dd@4pIp90ZfDOwOv3GvoQD!9R?kmXEpNPH?r z1)mbJLOwN;3diVoA>TicgD&pcw4k=;rDCcnu%j5Adtxs5G*lIgR?26^TuMF#qbKIW z%O5qk1iSMq3kSs+>!MPs1y*c+;BV_;i{(h1fey#RHiNlal?8afqItLO&59 zF2jyTRk0}Yp@xb}$dmL_$P!$J=aZ_g>H(iHn>j$6S*G}uTt>v_3F>2XeOdfW!KaXA zgnXVT=?m%clSZ#AE+gbK9$cM-v!`x1xr|7jveU^EA!czIA)jC`e&)&f;8Wu=Vm^C9 zAt6tl%SibIm*5pXQ|Kk4;t6L*Rp}<>LqEx@li8>jMa2_l9#UO9Iw83PYjX_-jaMXm zN-h<5);j_|)iNWHNu(m+bGpS@U@QVYL-1+SPD`CVpERyl^o06(t;!0c6;YFL z<@e2XaW(a`GOGXg@L8?KSnKx+Wq1}D)vWrA(9VL5DvO*E1Jy*XveL6asAkn?gmxBe zR9WPV7^o(4m6e_aLN%*CBeb(%qsk&@#6UHXtE}`a5UN@A8KIp88&wuLBL=F8TvDP2 z(@QSw(PLj8W1TO%FunJM0|xGmW^B}mNn?gh8$HSkq-C>hY+M70-4GMo#zsLK-g&W% zHAKX3!1NcpxM^KXlU~jjceM0Ry%-I zeKr=)h=($@okm3nU_2P^82z{Kx?9<;qbIXh?wihbzHkrwX5EwQrN+Z^jRO&Q2fR z#ZDjpiSbi^u+x8?WPfjcik>)78vZegc4&SuBHe3kKoo7w4KzhnH3 zQug<|6B*z64CDLOGk)|NcKYN|cKXlX+36QtjDPby+qZco@He8aRqWt4#D92+{b%21 zz+Pn^zVR62hu>iQk3%T?7URE_GyeBcwtXvLr}nYar%$nW-e1MmfB7kU{H<5n`&+lL zCHypd>W>p_`N1FAqmaeA)0}<(_uuRf#2207?4N&}Vo%_`;&0AgLYVv7>+D_Ne1x#; z@6&AIpPU^&^*6ik_tR_v!UHEcTm0RR>^I~;dHioSdH-+h?xUyKy$6o5&w+c@j>GKH zqntg0^wJMMWqmgvV0Zq^+3drd-F@H>cKgo5tnA&-*vey^z49ApV|V|~N?+Q*$_{XL z`$6Cw;_PAI-V3}3zCO$f)_=pgyt9XOeeFwD^4y1PP3OJDT0Q?YYj)=f*7Cu1Ed8#PEbE1xtkrE#u$)!9n0w7`R`l{Wto_Z4 zSjo$~SkL!G;t2cxzo*&7?;K-&%70^nJ~+v4-1;Y*`_(D7 z@LSFvUHLM*YSUkA_(y-UQJZ{r1mtU^T_^jUW7vqvq5?{m4LE(S#(Z+b-?_53mmZ$Ff0QjF0eh9RI!r!~_ z=DIhq*md*glr1a&gm42O^%dT(=La>so<-e%=MlDAg?@5~t?ATS`?b-53@3{V~?iCZ7HOe2;e_i)~Ja_aXJBwX)@XIZq^{U^W z#jNW-_`YaXN76@$ANDNl7u${1>p!AdYMVwy1K0KKd&d$c%KW(hn?*zFcSe~$eI|dZ z%A~D2c;LV)f8XA{diAyNZ&s3y;Y;V()~%lX`r7xFe&+8R+Xr-jHNITZv8}Q_6n8yqxa*Mfx<^38nZ5%YNGqoEzhy9aHDu{eZt;%n;i<(U;inwH*N$-~_otW3OP1)_-3& zq2-i^mtNw}au+m5{aMi|ajCZKw+Lfg?cuD}=m-3R23^vl$2%-~kZqA|vsr#{On-LX z@Y1>o%^PF zVtdY{(kr?%t96B-lGr$r#dKl`c|{lBl$M;7)G9Ht6+&WCYBpO>{Pb$jpG9AB`IR$c zXUX!c_Nv~UJ9qAXS!`$(e}7mWaD-0(W`9dMW@utU~wtu#Y0I`5wU03 z(aY3BajVE9HL%+n+LEKQ;!|wNw%Rdv7N6?Q%5tZqxSB&TO)rnNv@?5T$UYizeQj!rv) z*qM6lbBoVx7&)$fo_1XAEzYw|vTYVI#-hl}FneA20|g-;!tAw~J)6ZEhK_VPvt4C- z6TOlhmBM1B~K8XnlPGg-oZ35&azybvT8mjQ{!WRUS7 z6WlSR1`S%Y85CJO*+pVvi=^m87DaZUFqZoLmpsQ}LAynAlA--A3<~KidKUUUAtpIi zXgQT>#cW67flVqLo6MqRu6r_ViFV54M-OG;yRhxNl8hm4tQLN*Pe90eri%ua#CWd!=W-jJ&yQ^ z$slwVJquy(g*aEDO&mbBd2p&xy zD>jMxPGS?(mJpvDm89d5<;G;hB$KD2vK<9hbV_UnEL+H{Bk718`U?%qB9CA|Aw7E> zqw=w2)?}b4@4@#oqF>U?H2Nv6F=bLN+AL)57czGzMG4tDNlM7iGi07?$UK+TwM(0E zfG>M0R3;|Xk!{GW5QC zN3*Cbzmft;6cfz4seX7rjE`BSv`DJhhK(0FiD^QnU16=M{^m((qPDK44D*Bx3!6&4 z#mq>^u*i59LqnzPN<{mG>@t+>N{|~YyUqj)l3i!?ggw=g9i?NCruhK1y$khJ-885s z6o6!ILT%(R;#vr>loR3Fnnwd}H#EB|X*)?GItjvq>|&B242j)U=v^)3rHN4uI+m4X z$b+O*m*fWrfK0%pwq3$(3DH?BN{W-g1wwLJNkWX2QrcG{B-cAlO0Jh7v5Sp&A5-i4 zLc>rdj?_s>t}pdAw62j$88fr@BUtU!)=8Sy^@B8RnUI2M+XP{C(vvch65BdlpeXti z6I4hry>({m)|s9pj|AKM6D}+#BLl{j4Zob6X<&nDs?^I2e|EMYf|ijSV;D&{3bT~t z6vgc&LW($~6tn{M6Y@Z*IzkU2Mf5;Qf|O&a&_D<`F+oTiX=q?_VuIkI)Rbe9kmD8U zQjV7!^7{w9;W4E44H4SM;@W1DQ>`zw9bH{{iKbtWYtpdAS1x))7ayekXucu zr3k4_%ZRZ{$xNfZh)qp{SVX%-IzAO9sU#&JwNsOmVWk#~<~ONHuB2!O?8+!fGg^4~ z*bL~Rl+q1Ey>4o9Rz_k{l2^$NOCdlTXOZbZb_VWr!mZaFlAyjDz`;EYlN`W62PlDm~C$ET%ar8G{^AfVY?$dK{@7b9#_ zA)lKwjnne;8G*S~f7~X0Y%`;+ClRW23ZJ$Lp z5S!|8xjG1a5$QPSaWzQoPKT>1R$6gNgU;(0#dV!Xu+AuT&g_Td6x!dkPU zGi;5e%`PA;@+Vmt$!Vh8{Y0%6L9*O#qZjU%w8$s>v)U3Gvp*nd`J-vgVa%Wxp*66` zxCE?TF*g%bv=~hybO9Un)8Jn7rX2@)@ zl4PyaZ1~v563pxmlOEK`Zk~yuDC&QiP&Z5tk00D<_X-I-OLFZB6*wV0LAf&jA>cad&PVbogDUu63 zzL+lCms1M*D&_k;RvbiAyoH}zAmqFzoke3H7d)*7*V(NbCCCAi*`HRuR6DanQj$AE z`TnPb&ca{R&1{ndKjuzSIZsj>qEb5~V&^H!&7vC{^GgAb&u$HsLVIJ}Hql~&V#g&B z^R#%OT5|D+c1XXGn3$F(=R!{r-5B&q5|(jkQZtqlR-M#jOjBqGZmaYNn6(nzDby#) z685yzUI0?i0SZPvmj=<|e<7J?fMrXI$&zwe^)DS~bT61aZt4K1jX~`K`e%LXs zsxy+L#=b~>lWohWm1XywPAe@|)mL=watAI)R3{8<}XoJ*n7L9p;p_OC>U_41tT0?xqAwS747K|hjYB@AA zHZ7~M1ls>4`Y0CCw)v&6lMvY>vXw6hNm3t343S2=UZ$QBZ;XO?ze0T_dS{{Noqsoj zof*BeiEKR${UeNs+1Cr2s0DM&#z~Ev)5MBsys3DES5Kz37bd+jXB|l))#i_f9GV*z zvyS8dSZ$KODE|gYH8!thX z7yw0^u+EM4vuLP;Q72geX03o@uh`O1T+p-z4u}w$-GE_<1dstU zr4W^xl3?;Gq3EmBD5IA3qW3VnfDB~4Zwa~9p`8=K$J<69y+d9~^v$E9Z@z4bg`eDR zvVTE+(?Yo?X8(6U-y|g{zqKj2ZD5ZfaI?N>=;!sypr50VNxWZO>wT?N#q=r}7O_{Y`xBvwXhx?S1Y2jio+* zPE?T7w7svX$Y`%@XdC6ghET?j0;XOHIQpqcd!N63`}UAAxh-bjik4#}AUXM2Y!k{5 z65UF4Fe0M>Xt3z^et%nu4|qMa5Y&B50To?EbOgLT%*)@jNs}gE$fVEeB(zUyk`0*8 z#75uLPHmDvJT~!(8n>ap&?|8Q)Lxd}2Cpu9s!99y%>}*Bp|O@Fk=$tfP?q$i(Mvnc zUXpWzpBl^2_YhW`*o44#yYyEFaU4QvIwF%GX8)0qPobUXm=vk|-wIoc20RWY6p#h* zNfzIbhGDjwoQz{BoY`RiFx57Ia&}WrqwG|y$i-n3PLPt*diLyT=EY1m_YOlfvTMP{a13In2HTRulzXJA8rLgf$c}Ia?qMk~exlqc2V| z1YP1;wg&A7N4u$i@c!25m+#De@eBAb=dma{(WTh~ixFqKKxg*gU9;XQ?9pN3;Uxg>*(*FW`(+ z8W*w7)@u05?|Dw_d&%sn$bZ|*j1M8R6sg*aF zoJt8yLA|3zxvNobEND&z1(I7QQ0WR7wR;$9CLV7@nm8bCMhiS@`3RJw`iRaH5)R#Qm)h+ghPEIrEvr3O=8!hw{t{qlLnABk&Tef65hFz7bXg9 z0O(OmrlKW`6`~jVnfBQU^-f2SmLav7?1|bfb4dS5J4vrdH!OXWmO(a1`kK!Aq8-#u zY3;Hl1AtE&O7`4|rJ(P~Qb>+ehT1z7d8D;hLk47h*CXBsu<6L}1b$tR5oxdDitNLZ z81Z%Pq;;Mt7wNZ%E2^739QiTo*E>yr6x@FDbJXgoc#~%!?UNcI?N=N9)y2F?EM!XyTUVwKVv%??cu_)$&p69@icNg_`WS2rJ(jKbz{^_{O*&QW% zV`Ru;&Pw!$IHP`=0NGkPaShr;Jx3#ivM#A-Bn3Igm@AWi*`w8KfwK^OKBRh&aHW?ezf1JTv9_QsDj_8b7-i#@HR`%TG9i7TwGw%L<%5O+CcU#9d1*YRcFM7x;^b?{W>ooMB45fmSf*4r zwdz95TYKP5YYOQtNw=qnHj`8ouY&|1q!Bj=JRjz|w6c`zWQj$yxdI&z z4Ww=iYNHwR1W+bDA0hk+`FOeNBY8{9wMHD`nCc=M(Pu|wB?_BnK4eu=N>&R< zj*3scUu5r$67-Z6(M*{5q#h^BodAkb4`_xU=K<6+R95+8(!p!c9@Xj+(SvdhrN)g; zhL5?yT-%c*h@Prh_tXS+WB%9?G^Gs$)=YB}8fU|=jbytl9SfXGS>s4o(F*D(**8iv zs4nHPrFD=5RA18Sk+fEyFg9>(AE`m4QL@J* z=N%0XLY80k{5f#eP8LCyL}O2QizI7PegD5hhItrSsJ)i&rx{`(gsfPuYt?8#X}J=# zJY1lBEn6rCZT*Qh+xKDE-jn`<{2_65Zs)5lK~c^k$$td;$8hsUYu=%3P3@A<+D7K8 z5vbxhue37D-$+jyIxkSXS+!Qpp^|#!Gh92%N1GVDLiO}6c!o-zOO9z3@5xzy)0&A^ z?m@|3XgpG~CaoaNlQSi?8kG{YW_z?-QU@MPpHcOJ7SoKjsZGss1J`Z;lX^#1ZPiLW zuhxZC`6yZPe`2Qpf3F=V|JMU@A}_03f$SwYQzQRC@(b)oYu2G`L(bmRT!5sn?*>(5 zZTd(>`#asBJ8E|`9W!`+^sdV;K`!Y%>8iAJInQhYf1|If$pYk@LXJS~#9Ba~IcBlU zD$8_5ZM4pyxdDwPau$(}RgOOM$pY7v)lObHO{^nn)~9wvXxCNFXaXf4C}mnRGS&4q0Ub*H`e$iS3}m`dN}J1Y>71{29Fw+WG$_uxmw`aNU**K&$5*-Bs{rS ztkxq+(q}VM>BQbRr*cwre_AaM!>A%xDbhAbYvp{LW)jrTdS6igQ`>Z_2q(GVxo0}c zb%h3zRA}$q+6Pi{(QR7oI$AwZy%9{dfFv9`Q_|<%awSCCLi6Ix(Ln zb8EDcUhaQp4%8`NpD=QJpcM*@!)hec*EMqHO#Lg@6uPW~Po0#{v2f1ZGL-L;>p;bk zTsz6VIcb}6KCiSQ)Jm0w1)gijeNwBB1FfF?x%51P{-4@wE@nyMBwDV@|vC+hl3J(`8R z40&or98fQ-k(=PbW2Dkzor+xDl0PG_F$EO68Yg*j4MnTSz|&6M8wQU|w1zoX<0PFT zNk65JIf33dxc-VqM&JTIgErdepVdwcNGBwbHf5^@?k)YrU)7wb`}BwcWMDwcB;jb=Wn* zJ;goUJ;Pn*zQaA&J2teGp_mFa5d>fp+8IbB|t-&Nx3 z7E<4O*C_uOf5XDYg(-#23bA})+fjCzW4U96W2Iw_;}yqR$9hM(V}oOpW3yw6W4mLA zW4B|kW545|zDT;N>jTa~<;>3mgj_ ziyeKorepgRuRVlP-I<#m3v}XmhhBRh}YrpHL z>y#_r-PoP(?%?*iySe+hhqy;Ud&;0Si`~oJYuxMIo7~&od)1 z<}L6(>RsV|#ar&(?A_tr?>*{0<&F0>_NDte_`JSuzJ9(TzEQp@zB1oD-(uf#-x}X~ z-zML7-(KHg-$`F|e#87``R($Z`6cOM!I*;S1$PuID0sABMZqftcZ=B&-x zkh3Lcch13_<2fw1Zf;6$W^PWdKeuP@z}(W@F}c%o@5o({`)KZp+*fkTb2sPi$lafN zH1||)d|u{;Q{}AAD#j{O1H{d>Q=a0{F~j(E2s-mF2EY@RU1Td*LUKx=y;7 zJKo(8cj40A?c6!AfD(64*uY?SDXd@ud}tZ`=K@&5GWQDD!diDZtYM3L2khaX`zS1e zdE#LcDV}s#MUKY{yXfiZ2g@k+jDl@U_msgp7I+rJK2~_vz(UGBn_we5JbPg!M?EKD zC-L5fu#|LfJJ^cXTLNq8=N$}t8ReY-iz)NYh0QGXE`!yq@vep4Z1Qe_@S5CPVi5M9nSSHfF&;Tub=^Ii=6wc&0P=8 z-H^Kp+PfupdoJ56$G1!|hIR9>qjK)G$+sD;-H!I|MvM3R*5-poj!n$FHo*evQ|y5N z(MT%C(GAd#CHeo&w-q#o_i&1qJW;T!;D>@S;O1`s7XMHFCWZY9rxmU)e68@^!Y>Px zidq*H7hO?wbJ3{cduS}_XTz6IjO}vlarj&VT*F;=yPCO2xF@;4asT4}+noYU@p-y? zE{E4#?pfve*t6gBi-&vSy_b4B_}sobeEkcqD)_tLAko67+s!Z;?UJ(swp^HZLEb|c znO5a};iv~|aXSm({qJ}F%lSISzrUSvu2wD=^#5wtt*#~T@!z?Qx%#@VbKeR5eZl>M z`-r=l$KmPY8SS~l^P=Yi&vwuEo;F^Ow+K2n4SF`;yU6>L_f_w^-cP(cy+3%5c~5(5 z`;vUEe4Tvxz8=0yd_#TL`KI}9_uc1P>U+lblJ9NbN4_t8`+UFn{`AG>H_C6G-#*`+ z-zC3K{uTMx@lVSNO_W#R&IWt_>V913`xbVHgrbW3$Q;MD}+F5jQacS}B;;F^=7C$WJA~qI{ zK3|p7F?UyP{k&#*)AQcQ`v`N8BY9DdR*tdop-r7G=Y`JU&igRh?{jiz1I%aIxg4(U zu0fbNPIleydcgIt>uI!OpDW3Ip}W8PYWHaOME4^Y%|CW;MVp#B3Y#pu1!v(59B zXP@Ukp5LH_(cVkEBfYb{%f0V=KlOg)J>+fVYvXgE|NHtbh0iF=e>8t}{vY{q1^ETt z3wpuMrxd(gu&&_af^7v2{mK3`zsrBQ|0@4A{_FjdU@^D&m%w(P#yI+#|2_ZD{=fXS z3)2d_6izHWTG+bCjlLaUw5P~k+`71D@%6>`75@uk-HBqlQc#Y*ZII*3xj3gZXLQa@ z7~77ae_3u??vUJTazDuZIk$FRW}YvvIIn-+Kk|mJzN*L`naxeJ@0b4`?@c4-{^kD{gwNG`-HobXCUlwxaWG$WZ1+~*x`RY^}Wg7 zE4-tZp{5PHzu!T-i3J+ z^Je8eocDU(9%yF^M~0)VBinI4en-UPC~y>GK6trf7}_++aVzGO^BhZ|w=1E!?~5_? zkmDDOp?^E#ob{beoEgqsjGq0SgPd17Z^Uf*Ugv`tIiGZ{f`53+`5xxRJDlG-4>*7S zU+ujKTutl$HoU9dD5OGCN{T31YwdZjz1EJ5$y6bkqEMug2&bqdB7}$(NmOJk3L$ej znP(-Lr;Hgwc(1iG9q0W1zyI@op8xZE-p}j!oYUE*-RfT7`}$tj^}X)B#b$iBq$Mus zmEMw(=#>+pG-gTSC95SnB?lzeB{krMI?{$x6RC}~o74^caGZ3aR0mZfkBRW`l-B8L6cR6wds?L@|lX24+8VjS+0u;3&0V4SAD%K)xbBkV2{n)sgB$1yKtqsbmU% z%k`4Ik`B^jboCtRQRzgPq1;MdA%8BN)GG9t>TPqs|9!JE6YL?dM_*K`cHrAjX52 zog^+0kBE1KG1-~SASKjPDjo{s4r+HBnEpv{qDSIq;(d~Hk}Hxo*t>XjM5z+%?~V0O z2K&&FrDDHclRw7V8Y?C%_AC0M6Wf5{Zlm|n?`au2=xNm}l{4edWCA~Dvw19|7M#@( zB;Xo2R5YR{V6PmbZcvY?XVhEjGwRM*)LPU*L_vYD&>*g&!J;vuxuWHwrBD?o(O3FP ze87U{%ihVF$t~r5<>Ta2z)23u%duwf<=P4{n0p91SBfGF*OQ~nQ(mE`Vc&074QFzg zAa(|8q3)n2)hxFjRT`Y!67(Sk6RxD1nu{y36Ac0;KNqRQe}dUL1A!lan|+ZqmYxJM zX@DVF$iw6dp%j`!CsZm7lr5BYN>aH|Sp+_EQF&8&gML73sm7o?zX857sE!Hv{qqSJob3k|_a{Cqt^Mf4*)iLt~3qM9(mOQd!s|3uAL0&n$1e~Lbc z?9pM>;)&w9xFgN6OP)xyq+O&((8n!gLuF%S!LsSHOELrbAh|EFVvT&We5ZV${IL9^ zyfyH$v!XA$<9!9q_rk-FcLP7U4)g<ts zUJ~YH53)bFdLp@xJkNKtPE=!&STq)VdZXw9`j)M@lX#?fmUyFhxA*|qa2rW`@EV zlo`ufq2qM`o0ZBKnTKql?6izhbW!wD3;?ejub85US8P)3Qj{tlE1D{m%6`fr$^_+l zWj=V}UFB0{OIkz^p@ZlMdLg}@&P8`PNnfI$(I06B*f}2PIZw4nwOVyjb)DZ$wJIIP ziV;Ci&4r5Ej1@V;d_s2|0)%a)?x)_ZKBLBqg7SO1F+o7@_z_Eptwb^LiEts^$q=ki zI=P1|C(S7-?p--$EAGo<;CMI(v&6|jz%5vx3bDDwN@6E*kdTsS$pWyWwNg#l2U$n3 zpDg(v)K(*f74DmxVjZgZgyOTpP&o{0@-DiakZuol>qWn11nNENlL)403bIi{F+>Wn zmS`*L4qQGDPS#VrQ+!Ws3C*!cauX}A4^{0W9SiQaM`{b?>?u1SI|t6M4ZJB6*-BQ)5v=}|c5IU4(hB6DG zJXbQQShq~nQZBQd`2gi9Wc8pveOX)}p(#khhy4UgULgaiQ`BRsi^u}biwZ8wX7L57 zM7{)FuA1k1XBEcaZ>~xY{NBD`YM+#LbQfUC7rG~|APBWP6P?_g83um#2Q!;l!ED6+ z+Rp4^jxi^hOH3tnuaIrUwq>1ICEJ4yVW+TB>>TXAQR*OdBJl90`mtKzj29M0Z!{oG z2}`0aP;oK(;|by#@rG!H-hK&p@-mR-l%GIBrpMEM``x$>ppXa(}diZ(zeSH&d7A=K;_g+6e@ z6%|{gJgsbvp6-Ea^@CQKLoc9{=+*EA&eJ#ONAzcKxQ1ANQl(aPLr)r}8m*eBnhu`2 z5S(T$_VGT|Az!6st*OVyXqDQ>FosUNAIt6!@>s0IBs1=YB3nuGy#emlYu99Kp3 zK!>Vh2elV{>;iawC3@Kl$_u-2hp0VRUl_l`S4lG9 zk#ai!gTw^LM@WaDi*1sc%Ve@yvRg83`3!lSd=+r}IJoX*`BS+Lw2p&<1PZ8u-cuCo z75Pw(>u}vI>2c_zG@G2mSWWO8}c^w zk}?!^lBUS)z@Pt6jHiz=rOYYj9QMmK<_2?%IRxy_P#;$dqBQv)eVH0B>L?y5n<9?} zZ;DqeR4h>}SFFN{?^Qlm>d}qq=ClR$wk_R$V;{rmCXB zpw>e<=YoHq1NZI-=St3~z@WM^J%PCWnUwF?@s!O~|Ea#Ae#7iwqHdT$^l@dk6>G21`u8{13x9EXB zGgBH5_ObzWwNH9NT7i0MEVD#^QOkzO;$>;Fjj|(fL(Je8c7g+PQN9SB{V>#`h#pEu z!a>+gALreI=Bjoog^I=g+o0M3UJ=2rz%IgdY}XXz<3o2MhL}UY_ff%~*DoxU1WS$zmqJ`AdNBm&5L253(nq8#KX1xR+lK#5EfL1IL1Yh@c3vCU>8i@NGBgzr&7VQ(2idIM*r9Gsc z(jaM^Gzo0^j`X9n9G%<=Rr^R;M;oY&RaSVVo$kEG2v$u68(FH_q7q|I?1h`d!hPi? zZeh5L4`c(%6Y6WE$QM0mqG+-xToj4gj^lm(6~G$K)!!uA3ZLh_=nGV*mAD<8c^de- zRk8zKp%_YO1uC18tD&h^$o&Hq%D3Rj*`Sl?4?dp7M<^nG)=ZlzFOW_K`2X=XOxq`Bj(cW zusbHJy0cqY0ilKDnLt2;BL#rPP7+UnDvT6Ii|2^rp$(Uamy5L}c*cc5{4yZY z1Ex9KOl_lfQujh%5;$w|+C_*iv=Dcq7``$=P9SHIabya45YF*is4Hh`AQek(5Jh4- zlLEz1EH1?i=C)W9-fR=NuO*U7$z!nj&Tt~8O1DT4Nz0_opuC66!elYfk0FX6WdXeV zrp!x*VUyWH_6plroed3j2ClcDzZTE^))PC4CgebJ6d4B3?jMoM4)5`e%8Plzq_A1+Ep``ouYBs)M3)c|M~II^9C?A9OA(j^ zofI_@dtg=XLN7QF@{)5 zoFVkdR-~Hr03Y6jUGka~QJ+M^p{Imky7SS;d&vUfMVF!PH3Y^UMXxhgNZ{%XR*Zpv zwFD0FR`_{O6ivX}r(j>K2Z|kI`m)>9htQj}cpm9Rq!D!lL;8}FQ3318YEqw4QKP8< z?1F}1-C=MY)`=g30S}ghqgJ0uq~K|n;5`bUCpN?Dydir4wY@{$3K%;WYGRV&lcJ^4 z35>xP{J@2tO5dcP(rr{7Re$nMYanXU0{Yq=3VSx@CN-=rctag`9}=~A9p^-_#2~Q9 zMMMi+Q5mkO1=R|^%1~-L=697;6YQK@VtpW-qf`Yp9tQ4M0B%!({VA8J;MjKsFLwjJ zdcwCK1}~^q77O3!3?@?=P@8J_7Yo63-YEvsO`)zsRWYhG+@s!%CzHS|XHH<|;{b=U zje4K@K6hQ|T7n{c&_R93314Cgv5crh<@%9p$fi^@7>l=P5>S4FXs_su=&7g&<{gK` zog`@zW2r(q6jizq*l|kw1a(>`>ntAumXZZs`4n!f39cs{UFiu_WdfYI{mRDBB{S&^ z-d#TkZ@s&!uWB$DYBFY5Tj4iW!Ev-=+Jl>RX2!#{PG>GM6gw2F8O+XO&$3!-OEsl{Xnp?j>)K6!ieEK|}00cg$v1i!X~6 z@YjZ7zPJ(Clq|g5Us<@gCYv9)rvauqrz=NtM%=~O}E3vQVAqn_7Io<7%uwF_BnBd?7Z$ugDcyi7h1kBr<83thszBrZ7fe zj*ghVxMO}f6RhE}A_UI&Wu+lK73`}(MWR{)Q7?teXC{ulkDh0so(N5pqZU+YA$!aD z5fZo&!T6p0$qvBOW0?5rh+B$hinmDZuzR|oM!(21n3%H3*8k#;FKxgC{2{@ zlyY#vY&dDos#4WMXc9ff98Q%Jh61Fq9{zgK+2kwVcbcdQMYJs_3dpNgwn1XpL5}+v@F`FT= z8xLYOYtGDKikYdHt)6Dv!&6U%P8agHwTdV~9ou03ISU9;2CeW^)B#<>O>#lvF8u@j z$W1ngcYq(tKFN&b9Z|cvK!7)LIVNbFA8-;=q)P0&cVH6+%3$RZWtQ?f6mlHBhCU4E zyA$00KIpv5R7X^I87u6t@zB3f;C;)Wf6LhGtPnN4QGHr1AcTTc{A!l)hDU*~mJ+o@ zGtwSz;92q`JPaqQJC#5!g-3S|jQSJRS2P>wS}0lwpQjKm?gMdu_zP8%M)0wlf_?020t}is)g=(7<^p=ym=7j`d87@ zI%D1#iuvLd#SP3A-zm~C=jlPGfw>parC>Z2aDY0ar)W5&|# zXfWbp?&tRx3UcwG5z&I^0KMCX7>->RMq~g%-xKYz)2EY3Kp_EoP62fuZiXCri6But zy8mJD{YGL3F(vMS`SDaFK3ZY|&_m)b87B#qSYrAy7Yd|Mx>UAXeiGMQBmV-fFH^Xn z3x#8)w!$B1th9nd?X29T%%Th6Th-9IDw%44Y6>uak!p=n`5%QX2 zC()EVN19Mx)EH_Dav~onCD)@i_#RTTPOh?{W-b~&`?tuyLe5lh5^dinxJS|rOTS~#HAA`@b=$EUN zTi{|C&`z`(`@SL1`(xlc2-sE78V%KiTB`1%?yDZC9*Mep!L4tIkk7dYfo7w@uFHt# zn4k=$G)2us6QDTGiEQ8zY{I0yNPI;6S!^INMqk>5sW~Z)L?@R)8$6fK21m#Pg4cj; zj!y-=Al?U+uC1d8V_1CNYtgzUpWb|$-)bx`{v%K+pS2%_qLwgw!GBse}7h#Q#J zbpe~)3{SczHHg|rsW6+1!QEUf%7EtFhwFJP8i*cagp5;X{Cv5rpKJt_4=KNZ>_~Hk zEhdsj6?KZ*x7DrQ}*L zaYHHvKFBR3l*~l^M2nF*%7&lb5i`OJ+|B*ClYMaAcO)Lj_e{Y)*a{_nR;nxO0oLOq z%aYxbjlv9g8Rowa<(djcaaCzev-B)_6Yg{o_HQNp^^deBrg{fehKwU-FQIU*Z!tEk zh`#5z4 z*WD5;KO9^>QM5&L1DdxbvLG(lLvi92sKU1;B^b6OQ0>Z!0JXKb8!aKJQ>d-^ zk!I+z@cu5-)#(3PNF%gE5`lA0iZHXirn-af-4FAG*~}fr2Y1OB(>_%_UKR^^t!PPf zA-1E#%pl`1%R5I|VSaK()CSyW04DJ@lAh93xK5{Jk7Og{LGnoX7Wpl?y z8hS~gvM=;;09N`T-3U{YAk3G_pv7cR@msK}J)z{6sVlj4JuBonnhjioEyOwE9$`VI zk%z!~6QGLM!h3lvYLDC;hdLX?dE)0tgV|usreG?ZA$cy@i93H?`bydg`IJRq|Da%-LoMSxS3D^@BuDb49NNKj3qqv=I(^Ut6=t5wTYJ(+!QfXvv|xce-- zl~t=fFkpa#AUJ-_H{UNba&tky`hXn++F5UZYPijbbum_6z9M(KOnvY z&14}N4&Sas(oVV&9?^Z|B&NgfNCm%|2@Jds_G_u2;DlX7X2n9;0|}L6IJiQ%VIq1w zyxU^>Iha5f)gbh?dDv;2ReO=#s(?FNtT%q{{uXpKo!S7<3XT`z>Zk8cXD z-V^x&BTQ$r;Xr>O2Vq*cpGpwjLOP&U)K@$Ld88NOQJ9V1hM%Z|WKarr(N5_#>0{|< zsj;jhJjEW!R*c8IBuSQo{#YivDO-WePBBtCW|*NXFbVa9n<#?XdyWi*6)mM>=qvOB z)lFQXidl=~*kh&!Q$=H7XDEA*Z2^9m4!wtajs!3_(@Z0>h+V)g8`6ToBdIyETmvyT-3F~!gV~mm%pAF!k+M)Qra0W4Ct!M( z@?fMA*U0xGQT0l`L2+JTr|hmAhncDdI7=-(0JUR+?&b;>eT8`icDs??4i;U;UIvSQ z3RY(fuSBf&M*XjZtG^iuh5~SzQ|cP-z7g8IKWap@A-WTR$mqqO`)meQ6ob)xCWNE` zvUw69V{d4fahUTxBe@-uMv~$zPM{*xT9y$ zhx@YS@CZIZ%~`;^A<_3)_yr#7BS<4$!&E`wtSuFVBnJtg)hJ>zv4XFrOT=}e z5ot*Pm3M)@_g0LBuW$~0nVb60hoh@WyTc{Djy|GtcwJ90mH52!K-Gmhh^3Kpq|1ETw)w(uTi zq82`jyGsUQQX7B_Oe9i&S?CgF@VM?_4c}vW+Yl4b)^IB&n1GH%B4#pJ&0KVi)!56$ z;A7XMuc5l#paja0VQnuTfa%yFp4~<(T7dH(!OqA7!^nk-7P4KD=gdWhQ>eCuH30A9#=eX2z`KoOrK z&XY4R`8f=2)+eF$-YKugO3{I9SR(d$N+$?1Lm%?{hhrXGQsoe>v z+3T3*KShrh$h2i9NT;@yvGBP1U`pzP4izkmlr2N{FbA2oqq0(Dcj^;}`tp{@p|(ZB zt&>~=F6#z$H%=aotlLVYPxnEA+>*bR>%uqe2&cmXyL&oP+H2u<9E0w=&rc#6DlO4b zq)Ih%0baAGbn;YqS-Gl0^zu5Wd_&}bEErpGP|mgQiWycIrr@cVfaiiU6~RN|^4F%U1)sj| z4==$F`!s<~;*-}U;MWy==Gq?mNCk!+rJjdAyAqu?8)@r8^-=X*xX!QZJ;v?Yf-uyJ z9bYO_AZV#~d>d zdFlu_~tA_3c!p{ON%kVAI6^mNaXX;xu~tb zCZZkTZ+P-)kPuW=eFi!onZPP2m-^JREuZ-3a?Y{HH~%%+tcRq(EuUxh10E)T)uf_k zw*Qh|Hbigl{||ZPOgPMCP(l^ZY1OEjI=N6`h@3K)@%K{%Vm4HtNzRAkUi(uD*^$rn zPs7YO6N&zOKHtM7jq9LCe$5ugV(OWTiDwaf&uZEbvw91@cEp(QbXB>jJTbHPgDMUK zkC}%l=yudlF*xB_-WPoc-O45Ue^iniJUTAh5QvJ3z~nCBFLiYk{O2N{?$=|Dpl_|= zQxdG4b!Oe*(uZLd`K`(_(G?1j2CHHpBHv#N6{?4fpc%SESG60M@-VfZIuM+PO9LdR zli*Wj0-FkP5`|0o3yQV5_e4O?GsV=!7IoJj34b5LA3g||?=Oaz#ijd=NP=`jLMRY5 zHw{&n1P)k;nyV$vFgf=^5@sc^>?$zK6bL2E;;Yd^ zYr)>-aMP!ua?+7`uRS_*5(9MW~c-RU$x|-5N=DF_^Ls@?@EcTuk7L zpz+K2Gax_G>Rg?8BB$bqsqD0WkxzfEG{vOco=>C?<1yy18T7k2`SEM&JP;mc5|V3N zom^DC2IA=P=RLfbX>h3L!LwQk4?P>HmqK`rCGh^QGF3d{nDMpHljRV{2V5ou6_Ltv zwU7^0e-wS_>QA-6o%i@r3)6nWpJJT%IIFIx$Dgy>f|aNRA@Qvm9C7AD1srk%!x)B| z;Ldy$0*9(_+T#V#NI>e5Mwpgb1C!j~cMikcArPD{0!~2!Y9t-G`ES_t5K6ojUYZ_d zM492_hdo>nIh^O7IQ`*8`S9mIl908?q;jczs)#xYhvXtv0k&67y{76geKq>!6x82w zD<5ZvLhsgRKJ$CM}u=eM$vN3Di7twSd?#F;Fv)*NvPO@-NA|LdEy*-&nZbfK->Q7*<=rLL&WkYCPS6rw&K;}OnG~-TF zh*6(DI6sj96f41riE4PSMwr&f|Au2xbi!90JBl875xV0p@a#4GEFm&rzoMGgFQ+2@ z3DpGXl4AaRQXp_F5!G4*rJ z`{N{12-2+FS)}c#Ne-VL!bhxy#@FWJ3(z-u{7IxA73l|+69%0V3#Tdx95@qwGM^|y z^0f@9#tPytvX-ymUI|g91m;T4IF;0&^uoEMZ@n{<%;l3nN8!|8L>}ladgyEZbkg7J zGX(wgM}3z3%Ts#&fqh~8Nu*qyP^_=WYVhznoTxLzc|;2&;vB))hT%L#ET-68eP&{k zoe$P_6m@zLsnq|KlSW*Zt-$HJYRPL%poK`RnSrql!zqwJ_*4;?@n!#Vny6Z8C^O|d z?$4F$^Oy5O`9D>z;JL|07vIkaStwNKy-#TzCf1>VN2d+Sh+8W48AdlpXERq*L zqvFstpYO8uwOR+2WyqSM*LLM=_17-@&&bL#J2@)Y4b}TIvnz(C{L1WJXbZ}|;;I>V zoip5@Z(TNom_|hX4OdNpsbaofy8>6e&~c-Hr}dRu0+sxlL->+d_Ae=sSLyU&sbn=613$v#&}d&2CY#6wZUO-9j`Y`!31pK z^orqTcBS2rm;a_cB9OdIfcu;Jl}(oL2wac77GL?K^Iy)RC8_e^3?EgMfgx1z{vwCB zrcfj*=n=PHIAl7nOUl0D@3)G!fI4yHxuYjAID+SnsXwuWvtOB`A%8A(*iT$Bo972V zVKH7AAn_{}o1&KMx#G7fuGcEraGbdG4#ym?0)vG(6>3UaVCv}h3p+Ua9dCtT1;6rv zpYgVsKNVevth~|J)6hTE%g@>+pFi_ih4Y@ZV4QmY#c5BDh1P4CY~XGQ^vpvjntzA8 z^S)}ETsS@dHR{IVOmh;P_)O#w3sK*7NCW;rU5>B%{F_socmKi{s6%$oP-zN3+g9nwpKA8`<=kZ^X0~NO-C9&JSHE)p zL}DOcvFZFN=4vnn0e^nk4f*IFtH4QyZ~A#m2=->rt@4IHC2Bdnot9C>ZcaR?5DJ;7@^wZriU4v9G? z!Buf8xCB?n9Gl=OxeR=QTRjze z6SppI{K|OoYvaeSP6*$nxV7O{HWkW{TOaQ9cL?}Cfu7*?53eMViwVn**NUhDM|lXA z@&Y`i7EDDT6{1TRf@hmbaVLNO^Lqt;ufXpW_`L$ZSK#*w{9b|IEAV>-ey_mq75KdZ zzgOV*3jAJy-z)HY1%9u4+__X_-8f!{0edj)>4!2gRCklW*%G&Kc8jJ>0d zeuuc8aqk*x=xV0N*jwULEi^SW$OeSIjEBW?DN2~yUaLE71a|%t9`hW2%=(fReOWKz# zlQ@-jsD`gbdW_8{B1ULT#Atp<*V5F`)NCv`Bv_zQ&1-Uwed}9&)0t@a{em@wxU>+` zfoQLzHBi{V#Kt8!WO``8IR7wfd*Alfq(ULH?i1h}8az38Y?!r6aA=4V*^02J|I1DP z`jz0&QDFhWL1b&fiu<~jiP?{@y9Wn{S$Cci<{umy5H_7?)x4oXMvx?d7sH1{70xlh09+tPk*^0X1ejRj7r*Jxa;KJhUVrr@HO^x3+Ssvq$0p1e-R82T zsf+F)^&~HnXx7`u42_gA;uMWczv>>#7EQ8Tv7EtbyLf^U;F_Ki^G{ zXciqVy#DsU)~My5x1XAIImqqB+V4McPX z?yseWFgk^ZPL47fdN$;FKh8e^cYQnHqxS4;vIOtVuSL zJT26)y&B%6W|$APc1?pnS?wkB7MCjTw|@0v@G{3;={-tDKd-w|M$_IoQqO?TwiDH* zWt%FsBW{ojnYBhCaYuOurNxb|*Z}H5+HMnB z#@UkbEytHjZe6qx-p%eNEb$h}gZfxB99>|Lk#OOXr@H$oi-8+M%8iwCmrYrlbv_-a zkVeF4&8bHPgHb))_wv9e{JHYA0SME1=)xY>G0`08L7EUvxDBmmVla5rWdDGmabeiy zMnog-le#9l?qmEW1_$|l!vzDAe}M~ah}QMEVEzLaJOd_Y!;^lhqkci&+KXPsD2s`fD8kn>SatR3W9}{Zb)uWqrH;=x`uJX=` zjxOC@Tslhd!UN>jzo=!>>R=MZ{%yPh*$(y2~uqg`KVP7N<9&8f|3 z?KQLEevRw4$3J&%qAgKsWIy&a5IN30KeKb*L;Y*p58PR0x3S3X>6HtM%3l{uY}u>9 z{R>Ct20iOuI;&>llLxa~WqWj2pR}EMKcdv}<8)K|ZpHni6Fkhb;_@EU-8!^(fPQ(= zhJBw;&eeZgK(#n<%XL6eQADoe^6|-&ABPTJm%1SC>gxTy>)MG9O{8+G{LQWgDV=Ow z&b?p!^rb+fp z`%Uo;o7^dEa)|FJCwz+NsiTJut*x!Ap&?WeN<#kaF`*gf`1J|H!^8jS3CD!~^h{xd z33okqLPNsf+f!)i@!Q3+v4+MMp-@Xxr}&pW#{-nCU8^?<&Q)*6v}OH{CKj=G2G6M9Zwm^9MA))C9%U%ndslaBojYS?@9HbtO4n*ThCDfFz?dKbI4Cfs82 z;gqz&w*n#q9?m|a8Qj5UpG>3-{cxGET(aeDfAc3MmC9oU!%pS>3Q)Zwp7H>t{mWwg z-vUq)iUM`{4p7{~|MLJv5?$pTC33N>qf3|W^8bAR^?0wgZ^^akTVuPj8}g1mbz5V5 zQ!%xb{$nIVstV`Shzv9jWlWtcaU1@!;FfEoWnt2^@-MCCH0{!T(&<_41{PEH(%I0Gg|c~!1?j5_=CFr_J;lO$nai|$D0FRUK$szSKI7lq{+U?xAomV zjQ;pEU6G>v{G>_As8*xBt{QmGzC`!#eS6^kPCn*~mTJ3PAO0l9piQ!Vx;8mx!Lq)s zI=9YPn)s<(*KWa*9o=MG0&>Q@7rX3eewbF=GD>)5KKG7ie=9nT-15%=H8b>ulX_75 zD!X_7g&K9eZ_PY&hyDK#Kn2W*(Kl>%uJTOxc&CHs#51C2wX^Tsq2@yCrHzuejPw}M z{{7QKp03%E?@Z1%Fn!q&gy2{ z!tUt&?#Jxzw1^65Z~C_R@fS9Z(cMQlynes&csSc8`29w|Ig2tzH_YkSD*Ij{JL}8# zMRm#5FFy&lo$7VkJ1qA@jfquD#p(6=my2HKKP%btYEY|>s+T2~J9I5Nn5vE(Yj$d< zwXeZRXJ!o5B4TGDbEs|4zBVmZ1tk!NUM~47KpBm1u&Q6Nz;1Js@@`gMGsb273RC|P zVfBqZ5n{27Lnpk)3@Q00tp0<{?%xrj3R%#{Z6#e?Cz+L;?#X%-f7oog*OA(1+|Rva z_ET1Ltrxk}e)kf;Dy#mndk^+%G9o8vw zx1BJ5(9rP~zF(puRL?F6?fNLf!*quq9(FZO<~XIa(c@MM{fMcb(*lELR8_N!lh;gY zG_pfKv(Y}(n)9>!I@k>J@3x?_Q>;<{o$q&h> znXsE~dG2FwR%yYG0j*mG>jli5pp{|M{b^wS#0Z)(_(|X?3{Cs22gx9Hu$GE20 zw@vk#-Hs9(#%~f&yLB6$LFxRmSVVO6|A>zIY9wiy*!+-3 zg9C#6g2N|U4;m91FgC!KH;KM$JsEeTkfQLWF=BufUg3-49s;z)-f$5(2kn2i3^gar zxaZU|Y4W#A{I{YlKg{lZ^6l8^@wO>j3yaPS7ffxC5v$S0KhJg0vI*llwH%!-{dn0l zyR2;J*lC;ZR?K#p7&75TOY+9!Ze2W2?Aonzi`vuKc-OgvH3^$X=9HI6EL0aINtf4H zU3nbeJ@a|%SnKm`JhGqVOvt&ptOv0>;&N16@8=!dr#8tskhS6HNsV{T7Z!-tKJD== zsPbyy;hWP|j(vJ~qVkgLRAf%%d6#aM#T)w@w`+Q=RphaQqZak)th_j4-JdH@&uwV07roc1JMXjG>hQ;R5$@lv8uG9^4tR9=_89U|`rnX; z->dEWbB6u0DR~5I-E*o>14-2Fz%5U5x4D|$F>O-ZCsI#!qGz1{`XxEBtjUSPhYMP) zSa-xZ;*3_Nt*+wUmu;RC9c#@DwF)LMZDwgC#+LQ>=}@9Kw%4uP;#SdXy7BV4l^v$| z3>uI>;Z>63XPb9b(-m7BJ~TTzwp;X)^=}q8F>iju!oc;#1gXJ_D^V%Bp$?Xb>KCS2 zgIKTLp&ssg?iDAWY&xiVf~q8@L3R zgpBu#2>HZ|gKRf*lyq()vJgktIao7>(+?S16cz+u_F zH0?X#HcQ@#{ZuwfEJxhZPb@P1TsOt5#jWWDx=Yt@oi{}9>RR2CT3&Vj?I%)K&S(GG zyY5zlo>ag4PYSp6yE`n{BxzOo@=H_7v&{>VmgI*Iebdu$@%ZVHrXj+}iQ5w2To1Y0 zqu}7`PJs`!3vM;>$(_E$G~(FhjdyR&ymVydy`|?>f?d*>%IUWz*6a$ZT`Ma6(>-$X zN@@9mbrUj{C$C;#dALv8FFGs6+3A!#$i4f_ShSp6RU*;rD?T~h z!ahv-P+ieouY=Fk9)4H543P}})VN!Ot+3CU%kr=$$u^0n{kw)O4ZhSnczfKP6Vfi} zXX_3Xd%s^lY7fz4(h7rS+m{q~S^nH;mi_oU9jtcTJ*A}%pCmkCUDvCzWw&{mSN@zi zZ_A>N)!oXE4iJ`hsH=`!lhP!4Xys{-dG~J@e9B#Z*xtGPxiIl&v`BGnXSWS)=IU%1 z7*Fog>FCp_vst8-)2`${n>=ZH$#z51^QNZppL$RJKn<-sClb6LGCD+;dhE2>x#J%v zZ?qoQl32C4*`_wxozHC#YO-2-w9+{&^_JI|Lq4r$43(EG-Y0Auaj}{6z1N?yq@K`?NZYJ?}+V<0w2&S+W zS8Kt6gH~NtL_NIda#%3hFF-{$8V>IDa0kO2>tM)G#+SJ8A<7H}l*`KH7)^-yk#?{QU zHMZ5#i|$dqVNLQIvnyxzU3xxs#q&?6%Lk<`Pw(esV1K-E$X zR2}&=e?pq;r*rGlR=I?$w_hh>bk>1l#($%ix|$U{y=?Fwfn|U)-j0owNW>&3bfts{ z4=I9s_x%kw&{jZOY1)8#UHwVs>F(n)n6j+zSObs znd33*$wwcWUtM^5hX1ay(-z$GX`1o2y|?9v;dP@g92(X>{-db2*>dTP9o@qMLOm{R zdN_ZT`PFyh^snh#+sJ2J)@pMleyxQ@!GN19$9B+Mpi_Of{qvz+nm0`zurSijV4K~; z%8J$Fni&k5oR()fXLf^`B8SnU(-zjzy+>R)-mCDf(}lj;m6t0+zvgbD%#;{^VAgtM}p7w!Qb)4-wfvC4P*Jg%YA<+ zo`L)CD10UW{}&{1`uCdQADWaMxz6jc`@LJu4e!5HPg}1J-Ig`j$Rplp1eLlwMioAKXEePE%O+YUc&m7D)+=3cuH*FUZ-I+vDi(rd)Ay1lJ3htX&>M16dFMWK zF+`Yws)m4l9rC^?a=UGeeND@2Zj4 ztMZD~r>;Kk>2lk3=9rxoZ#wq18<$LEA(co#_p7zIvmP!XC zmQRb^Lp2^T^?cJ|w5;{<^3oPTPs2J4b8Il6S2vICJM-FHT(^B>!xpDZv+k%?Cx<4P z-u!YXvE_!FZ*po{Hov~Bu4~o9+p3Hi`}U2w-)G0Jrt7YaqqiGI(vO}E=+NU$)5Zx~ zhC4}azRq-dYa;V@csMYnmt*qjG>tXM-G^Q4SY+$Hd`NJglWCU+w8I^`x0*HPEn46aBQz2ya;J9{%KR|36tU2LBxm2QC1~BfoX#QD*-NKYs4{$jb<0Gz3l?!cS9Up0z!kSuiYjXY(!z zjO^;c)Yen0ZwiM`X*k*`ZCjAejNLWO5>`I7bB#>*)F_zeq!#55Y96W>7k_F`gEkf7 z;M$>gMmQYsj8T>A>oTpz4stK3HP-EoSEO0Js`>GgC%aaR**|OREswl23$4g5T`ZrwSGAeEPD(zc>8c2mQ^&i! z)R(rLsiza1wB`*p_i&fw*o`+vDczQwZ{!_Hec1HwbI7`8y0eE{7#&;}qiKa^*7C=_ z(II0r4e=>Gz8dHL2Pc;(!XNWg-LJv4^x<&BeyYF*-yi5|pa8#rMVoBQxpRb^;sRSz zQtJJ;0^IYl`~KJN7Prs(&6qh|RY56>wNC$BBG3?AiBx^;e98W~j%o3gW6`Ys-dX-* zZ#J2cU)p}p_O`JPgpC7c%)ENsCn8|LvDTeV_4l5wVR`*%BLRi9HpJ94b&sz;s%_;kvtyTwLCY457(9R5 zVBKqHU(6m->YwSpU1o&oEHFCf?$_?wqDi(!4{y#opl9CT)b$J>zr$UO4w%MBLne0)^B7qh zx@K5HpRA*sJ{8_@+aSzYtdnA%VzJEO{)2lDRMq}RGo4qMPuku@``D?Nz?}aFPTT8h diff --git a/branches/winverbs/core/complib/user/cl_timer.c b/branches/winverbs/core/complib/user/cl_timer.c index 649a7951..4f2ce0fc 100644 --- a/branches/winverbs/core/complib/user/cl_timer.c +++ b/branches/winverbs/core/complib/user/cl_timer.c @@ -161,7 +161,7 @@ cl_get_time_stamp( void ) if( !QueryPerformanceCounter( &tick_count ) ) return( 0 ); - return( tick_count.QuadPart / (frequency.QuadPart / SEC_TO_MICRO) ); + return( SEC_TO_MICRO * tick_count.QuadPart / frequency.QuadPart ); } uint32_t diff --git a/branches/winverbs/docs/Manual.htm b/branches/winverbs/docs/Manual.htm index b01b505f..fbeca2b0 100644 --- a/branches/winverbs/docs/Manual.htm +++ b/branches/winverbs/docs/Manual.htm @@ -15,7 +15,7 @@ div.Section1

    User's Manual

    Release 2.1

    -03/17/2009

    +06/10/2009

    Overview

    The Windows OpenFabrics (WinOF) package is composed of software modules intended @@ -42,7 +42,7 @@ style='background-position: 0% 0%; mso-highlight:yellow; background-image:none;

  • Infiniband Core modules: IB verbs and IB access layer

  • -

    Upper Layer Protocols: IPoIB, WSD, Network Direct, VNIC, SRP Initiator and uDAPL

  • +

    Upper Layer Protocols: IPoIB, WSD, NetworkDirect, VNIC, SRP Initiator and uDAPL

    OpenFabrics utilities:

      @@ -64,7 +64,7 @@ style='background-position: 0% 0%; mso-highlight:yellow; background-image:none;

      WinOF Features

      • @@ -74,7 +74,7 @@ style='background-position: 0% 0%; mso-highlight:yellow; background-image:none;

        Winsock Direct Service Provider

      • -

        Network Direct +

        NetworkDirect Service Provider

      • @@ -102,6 +102,7 @@ style='background-position: 0% 0%; mso-highlight:yellow; background-image:none;

         

         

         

        +

        Tools


        @@ -136,7 +137,7 @@ style='background-position: 0% 0%; mso-highlight:yellow; background-image:none;

        qlgcvnic_config         Configuration utility used to configure IB Stack to create VNIC child devices as per user's requirement.

      -

      Performance

      +

      Performance

      • ib_send_lat     Infiniband send @@ -154,24 +155,71 @@ measurement

      • ttcp                 TCP performance measurements

      -

      Diagnostics

      +

      Diagnostics

      • -

        ibstat             - Display HCA stats.

      • +

        cmtest           Connection Manager tests

      • -

        ibv_devinfo   - Display HCA device information.

      • +

        ib_limits         + InfiniBand verb tests +

      • +

        printIP          Display +an Internet Protocol address associated with an IB GUID.

      • vstat              Display HCA attributes (lids), statistics and error counters.

      • +
      +

      + OFED + Diagnostics

      +
      • -

        ib_limits         - Infiniband verb tests

      • +

        ibaddr              + Query InfiniBand address(es)

      • -

        cmtest           Connection Manager tests

      • +

        iblinkinfo           + Report link info for all links in the fabric

      • -

        PrintIP          Display -an Internet Protocol address associated with an IB GUID.

      • +

        ibnetdiscover    Generate a fabric + topology. +

      • +

        ibping               + Ping an InfiniBand address

      • +
      • +

        ibportstate        + Display InfiniBand port specific information.

      • +
      • +

        ibqueryerrors    Query and report + non-zero IB port counters

      • +
      • +

        ibroute             + Query InfiniBand switch forwarding tables

      • +
      • +

        ibstat                Display HCA stats.

      • +
      • +

        ibsysstat            + System status for an InfiniBand address

      • +
      • +

        ibtracert            + Trace InfiniBand path

      • +
      • +

        ibv_devinfo       + Display HCA device information.

      • +
      • +

        perfquery          + Query InfiniBand performance counters

      • +
      • +

        saquery             SA (Subnet Administrator) query test

      • +
      • +

        sminfo               + Query InfiniBand SMInfo attributes

      • +
      • +

        smpdump          + Dump InfiniBand subnet management attributes

      • +
      • +

        smpquery          + Query InfiniBand subnet management attributes

      • +
      • +

        vendstat            Query InfiniBand vendor specific functions

      @@ -192,6 +240,7 @@ tuning and/or functional testing.


      larger samples only marginally help. The default (1000) is pretty good.
      Note that an array of cycles_t (typically unsigned long) is allocated
      once to collect samples and again to store the difference between them.
      Really big sample sizes (e.g. 1 million) might expose other problems
      with the program.

      "-H" option will dump the histogram for additional statistical analysis.
      See xgraph, ygraph, r-base (http://www.r-project.org/), pspp, or other
      statistical math programs.

      Architectures tested: x86, x86_64, ia64

      +

      Also see winverbs performance tools.


      ib_send_lat.exe      - latency test with @@ -347,39 +396,1820 @@ Options specific to -r:

      <return-to-top>

       

       

      -

      Diagnostics

      -
      -


      -ibv_devinfo - print CA (Channel Adapter) attributes

      -

      usage: ibv_devinfo  [options]
      +

      Diagnostics

      +
      +
      +

      IBADDR(8) OFED Diagnostics

      +

      NAME
      +ibaddr - query InfiniBand address(es)
      +
      +SYNOPSIS
      +ibaddr [-d(ebug)] [-D(irect)] [-G(uid)] [-l(id_show)] [-g(id_show)] [-C +ca_name] [-P ca_port] [-t(imeout) timeout_ms] [-V(ersion)] [-h(elp)] +[<lid | dr_path | guid>]
      +
      +DESCRIPTION
      +Display the lid (and range) as well as the GID address of the port
      +specified (by DR path, lid, or GUID) or the local port by default.
      +
      +Note: this utility can be used as simple address resolver.
      +
      +OPTIONS
      +-G, --Guid
      +show lid range and gid for GUID address
      +
      +-l, --lid_show
      +show lid range only
      +
      +-L, --Lid_show
      +show lid range (in decimal) only
      +
      +-g, --gid_show
      +show gid address only
      +
      +
      +COMMON OPTIONS
      +Most WinOF diagnostics take the following common flags. The exact list
      +of supported flags per utility can be found in the usage message and
      +can be shown using the util_name -h syntax.
      +
      +# Debugging flags
      +
      +-d raise the IB debugging level.
      +May be used several times (-ddd or -d -d -d).
      +
      +-e show send and receive errors (timeouts and others)
      +
      +-h show the usage message
      +
      +-v increase the application verbosity level.
      +May be used several times (-vv or -v -v -v)
      +
      +-V show the version info.
      +
      +# Addressing flags
      +
      +-D use directed path address arguments. The path
      +is a comma separated list of out ports.
      +Examples:
      +"0" # self port
      +"0,1,2,1,4" # out via port 1, then 2, ...
      +
      +-G use GUID address argument. In most cases, it is the Port GUID.
      +Example:
      +"0x08f1040023"
      +
      +-s <smlid> use Â’smlidÂ’ as the target lid for SM/SA queries.
      +
      +# Other common flags:
      +
      +-C <ca_name> use the specified ca_name.
      +
      +-P <ca_port> use the specified ca_port.
      +
      +-t <timeout_ms> override the default timeout for the solicited mads.
      +
      +Multiple CA/Multiple Port Support
      +
      +When no IB device or port is specified, the port to use is selected by
      +the following criteria:
      +
      +1. the first port that is ACTIVE.
      +
      +2. if not found, the first port that is UP (physical link up).
      +
      +If a port and/or CA name is specified, the user request is attempted to
      +be fulfilled, and will fail if it is not possible.
      +
      +
      +EXAMPLES
      +ibaddr # local port´s address
      +
      +ibaddr 32 # show lid range and gid of lid 32
      +
      +ibaddr -G 0x8f1040023 # same but using guid address
      +
      +ibaddr -l 32 # show lid range only
      +
      +ibaddr -L 32 # show decimal lid range only
      +
      +ibaddr -g 32 # show gid address only
      +
      +
      +SEE ALSO
      +ibroute(8), ibtracert(8)
      +
      +AUTHOR
      +Hal Rosenstock
      +<halr@voltaire.com>
      +
      +
      +OFED June 18, 2007 IBADDR(8)

      +

       

      +

      IBLINKINFO(8) OFED Diagnostics

      +

      NAME
      +iblinkinfo - report link info for all links in the fabric
      +
      +
      +SYNOPSIS
      +iblinkinfo +[-Rhcdl -C <ca_name> -P <ca_port> -v <lt,hoq,vlstall> -S <guid> -D<direct_route>]
      +
      +
      +DESCRIPTION
      +iblinkinfo reports the link info for each port of each switch active
      +in the IB fabric.
      +
      +
      +OPTIONS
      +-R Recalculate the ibnetdiscover information, ie do not use the
      +cached information. This option is slower but should be used if
      +the diag tools have not been used for some time or if there are
      +other reasons to believe the fabric has changed.
      +
      +-S <guid>
      +Output only the switch specified by <guid> (hex format)
      +
      +-D <direct_route>
      +Output only the switch specified by the direct route path.
      +
      +-l Print all information for each link on one line. Default is to
      +print a header with the switch information and then a list for
      +each port (useful for grep´ing output).
      +
      +-d Print only switches which have a port in the "Down" state.
      +
      +-v <lt,hoq,vlstall>
      +Verify additional switch settings (<Life-
      +Time>,<HoqLife>,<VLStallCount>)
      +
      +-c Print port capabilities (enabled and supported values)
      +
      +-C <ca_name> use the specified ca_name for the search.
      +
      +-P <ca_port> use the specified ca_port for the search.
      +
      +
      +
      +AUTHOR
      +Ira Weiny <weiny2@llnl.gov>
      +
      +
      +OFED Jan 24, 2008 IBLINKINFO(8)

      +

      <return-to-top>

      +

       

      +

      IBNETDISCOVER(8) OFED Diagnostics

      +

      NAME
      +ibnetdiscover - discover InfiniBand topology
      +
      +
      +SYNOPSIS
      +ibnetdiscover [-d(ebug)] [-e(rr_show)] [-v(erbose)] [-s(how)] [-l(ist)]
      +[-g(rouping)] [-H(ca_list)] [-S(witch_list)] [-R(outer_list)] [-C
      +ca_name] [-P ca_port] [-t(imeout) timeout_ms] [-V(ersion)] [--node-
      +name-map <node-name-map>] [-p(orts)] [-h(elp)] [<topology-file>]
      +
      +
      +DESCRIPTION
      +ibnetdiscover performs IB subnet discovery and outputs a human readable
      +topology file. GUIDs, node types, and port numbers are displayed as
      +well as port LIDs and NodeDescriptions. All nodes (and links) are dis-
      +played (full topology). Optionally, this utility can be used to list
      +the current connected nodes by nodetype. The output is printed to
      +standard output unless a topology file is specified.
      +
      +
      +OPTIONS
      +-l, --list
      +List of connected nodes
      +
      +-g, --grouping
      +Show grouping. Grouping correlates IB nodes by different vendor
      +specific schemes. It may also show the switch external ports
      +correspondence.
      +
      +-H, --Hca_list
      +List of connected CAs
      +
      +-S, --Switch_list
      +List of connected switches
      +
      +-R, --Router_list
      +List of connected routers
      +
      +-s, --show
      +Show progress information during discovery.
      +
      +--node-name-map <node-name-map>
      +Specify a node name map. The node name map file maps GUIDs to
      +more user friendly names. See file format below.
      +
      +-p, --ports
      +Obtain a ports report which is a list of connected ports with
      +relevant information (like LID, portnum, GUID, width, speed, and
      +NodeDescription).
      +
      +
      +COMMON OPTIONS
      +Most OpenIB diagnostics take the following common flags. The exact list
      +of supported flags per utility can be found in the usage message and
      +can be shown using the util_name -h syntax.
      +
      +# Debugging flags
      +
      +-d raise the IB debugging level.
      +May be used several times (-ddd or -d -d -d).
      +
      +-e show send and receive errors (timeouts and others)
      +
      +-h show the usage message
      +
      +-v increase the application verbosity level.
      +May be used several times (-vv or -v -v -v)
      +
      +-V show the version info.
      +
      +# Other common flags:
      +
      +-C <ca_name> use the specified ca_name.
      +
      +-P <ca_port> use the specified ca_port.
      +
      +-t <timeout_ms> override the default timeout for the solicited mads.
      +
      +Multiple CA/Multiple Port Support
      +
      +When no IB device or port is specified, the port to use is selected by
      +the following criteria:
      +
      +1. the first port that is ACTIVE.
      +
      +2. if not found, the first port that is UP (physical link up).
      +
      +If a port and/or CA name is specified, the user request is attempted to
      +be fulfilled, and will fail if it is not possible.
      +
      +
      +TOPOLOGY FILE FORMAT
      +The topology file format is human readable and largely intuitive. Most
      +identifiers are given textual names like vendor ID (vendid), device ID
      +(device ID), GUIDs of various types (sysimgguid, caguid, switchguid,
      +etc.). PortGUIDs are shown in parentheses (). For switches, this is
      +shown on the switchguid line. For CA and router ports, it is shown on
      +the connectivity lines. The IB node is identified followed by the num-
      +ber of ports and a quoted the node GUID. On the right of this line is
      +a comment (#) followed by the NodeDescription in quotes. If the node
      +is a switch, this line also contains whether switch port 0 is base or
      +enhanced, and the LID and LMC of port 0. Subsequent lines pertaining
      +to this node show the connectivity. On the left is the port number of
      +the current node. On the right is the peer node (node at other end of
      +link). It is identified in quotes with nodetype followed by - followed
      +by NodeGUID with the port number in square brackets. Further on the
      +right is a comment (#). What follows the comment is dependent on the
      +node type. If it it a switch node, it is followed by the NodeDescrip-
      +tion in quotes and the LID of the peer node. If it is a CA or router
      +node, it is followed by the local LID and LMC and then followed by the
      +NodeDescription in quotes and the LID of the peer node. The active
      +link width and speed are then appended to the end of this output line.
      +
      +An example of this is:
      +#
      +# Topology file: generated on Tue Jun 5 14:15:10 2007
      +#
      +# Max of 3 hops discovered
      +# Initiated from node 0008f10403960558 port 0008f10403960559
      +
      +Non-Chassis Nodes
      +
      +vendid=0x8f1
      +devid=0x5a06
      +sysimgguid=0x5442ba00003000
      +switchguid=0x5442ba00003080(5442ba00003080)
      +Switch 24 "S-005442ba00003080" # "ISR9024 Voltaire" base port 0 lid 6 lmc 0
      +[22] "H-0008f10403961354"[1](8f10403961355) # "MT23108 InfiniHost Mellanox +Technologies" lid 4 4xSDR
      +[10] "S-0008f10400410015"[1] # "SW-6IB4 Voltaire" lid 3 4xSDR
      +[8] "H-0008f10403960558"[2](8f1040396055a) # "MT23108 InfiniHost Mellanox +Technologies" lid 14 4xSDR
      +[6] "S-0008f10400410015"[3] # "SW-6IB4 Voltaire" lid 3 4xSDR
      +[12] "H-0008f10403960558"[1](8f10403960559) # "MT23108 InfiniHost Mellanox +Technologies" lid 10 4xSDR
      +
      +vendid=0x8f1
      +devid=0x5a05
      +switchguid=0x8f10400410015(8f10400410015)
      +Switch 8 "S-0008f10400410015" # "SW-6IB4 Voltaire" base port 0 lid 3 lmc 0
      +[6] "H-0008f10403960984"[1](8f10403960985) # "MT23108 InfiniHost Mellanox +Technologies" lid 16 4xSDR
      +[4] "H-005442b100004900"[1](5442b100004901) # "MT23108 InfiniHost Mellanox +Technologies" lid 12 4xSDR
      +[1] "S-005442ba00003080"[10] # "ISR9024 Voltaire" lid 6 1xSDR
      +[3] "S-005442ba00003080"[6] # "ISR9024 Voltaire" lid 6 4xSDR
      +
      +vendid=0x2c9
      +devid=0x5a44
      +caguid=0x8f10403960984
      +Ca 2 "H-0008f10403960984" # "MT23108 InfiniHost Mellanox Technologies"
      +[1](8f10403960985) "S-0008f10400410015"[6] # lid 16 lmc 1 "SW-6IB4 Voltaire" lid +3 4xSDR
      +
      +vendid=0x2c9
      +devid=0x5a44
      +caguid=0x5442b100004900
      +Ca 2 "H-005442b100004900" # "MT23108 InfiniHost Mellanox Technologies"
      +[1](5442b100004901) "S-0008f10400410015"[4] # lid 12 lmc 1 "SW-6IB4 Voltaire" +lid 3 4xSDR
      +
      +vendid=0x2c9
      +devid=0x5a44
      +caguid=0x8f10403961354
      +Ca 2 "H-0008f10403961354" # "MT23108 InfiniHost Mellanox Technologies"
      +[1](8f10403961355) "S-005442ba00003080"[22] # lid 4 lmc 1 "ISR9024 Voltaire" lid +6 4xSDR
      +
      +vendid=0x2c9
      +devid=0x5a44
      +caguid=0x8f10403960558
      +Ca 2 "H-0008f10403960558" # "MT23108 InfiniHost Mellanox Technologies"
      +[2](8f1040396055a) "S-005442ba00003080"[8] # lid 14 lmc 1 "ISR9024 Voltaire" lid +6 4xSDR
      +[1](8f10403960559) "S-005442ba00003080"[12] # lid 10 lmc 1 "ISR9024 Voltaire" +lid 6 1xSDR
      +
      +When grouping is used, IB nodes are organized into chasses which are
      +numbered. Nodes which cannot be determined to be in a chassis are dis-
      +played as "Non-Chassis Nodes". External ports are also shown on the
      +connectivity lines.
      +
      +
      +
      +NODE NAME MAP FILE FORMAT
      +The node name map is used to specify user friendly names for nodes in
      +the output. GUIDs are used to perform the lookup.
      +
      +
      +Generically:
      +
      +# comment
      +<guid> "<name>"
      +
      +
      +Example:
      +
      +# IB1
      +# Line cards
      +0x0008f104003f125c "IB1 (Rack 11 slot 1 ) ISR9288/ISR9096
      +Voltaire sLB-24D"
      +0x0008f104003f125d "IB1 (Rack 11 slot 1 ) ISR9288/ISR9096
      +Voltaire sLB-24D"
      +0x0008f104003f10d2 "IB1 (Rack 11 slot 2 ) ISR9288/ISR9096
      +Voltaire sLB-24D"
      +0x0008f104003f10d3 "IB1 (Rack 11 slot 2 ) ISR9288/ISR9096
      +Voltaire sLB-24D"
      +0x0008f104003f10bf "IB1 (Rack 11 slot 12 ) ISR9288/ISR9096
      +Voltaire sLB-24D"
      +# Spines
      +0x0008f10400400e2d "IB1 (Rack 11 spine 1 ) ISR9288 Voltaire
      +sFB-12D"
      +0x0008f10400400e2e "IB1 (Rack 11 spine 1 ) ISR9288 Voltaire
      +sFB-12D"
      +0x0008f10400400e2f "IB1 (Rack 11 spine 1 ) ISR9288 Voltaire
      +sFB-12D"
      +0x0008f10400400e31 "IB1 (Rack 11 spine 2 ) ISR9288 Voltaire
      +sFB-12D"
      +0x0008f10400400e32 "IB1 (Rack 11 spine 2 ) ISR9288 Voltaire
      +sFB-12D"
      +# GUID Node Name
      +0x0008f10400411a08 "SW1 (Rack 3) ISR9024 Voltaire 9024D"
      +0x0008f10400411a28 "SW2 (Rack 3) ISR9024 Voltaire 9024D"
      +0x0008f10400411a34 "SW3 (Rack 3) ISR9024 Voltaire 9024D"
      +0x0008f104004119d0 "SW4 (Rack 3) ISR9024 Voltaire 9024D"
      +
      +
      +AUTHORS
      +Hal Rosenstock    <halr@voltaire.com>
      +
      +Ira Weiny    <weiny2@llnl.gov>
      +
      +
      +OFED January 3, 2008 IBNETDISCOVER(8)
      +
      +<return-to-top>

      +

       

      +

      IBPING(8) OFED Diagnostics

      +

      NAME
      +ibping - ping an InfiniBand address
      +
      +
      +SYNOPSIS
      +ibping [-d(ebug)] [-e(rr_show)] [-v(erbose)] [-G(uid)] [-C ca_name] [-P
      +ca_port] [-s smlid] [-t(imeout) timeout_ms] [-V(ersion)] [-c
      +ping_count] [-f(lood)] [-o oui] [-S(erver)] [-h(elp)] <dest lid | guid>
      +
      +
      +DESCRIPTION
      +ibping uses vendor mads to validate connectivity between IB nodes. On
      +exit, (IP) ping like output is show. ibping is run as client/server.
      +Default is to run as client. Note also that a default ping server is
      +implemented within the kernel.
      +
      +
      +OPTIONS
      +-c stop after count packets
      +
      +-f, --flood
      +flood destination: send packets back to back without delay
      +
      +-o, --oui
      +use specified OUI number to multiplex vendor mads
      +
      +-S, --Server
      +start in server mode (do not return)
      +
      +
      +COMMON OPTIONS
      +Most OFED diagnostics take the following common flags. The exact list
      +of supported flags per utility can be found in the usage message and
      +can be shown using the util_name -h syntax.
      +
      +# Debugging flags
      +
      +-d raise the IB debugging level.
      +May be used several times (-ddd or -d -d -d).
      +
      +-e show send and receive errors (timeouts and others)
      +
      +-h show the usage message
      +
      +-v increase the application verbosity level.
      +May be used several times (-vv or -v -v -v)
      +
      +-V show the version info.
      +
      +# Addressing flags
      +
      +-G use GUID address argument. In most cases, it is the Port GUID.
      +Example:
      +"0x08f1040023"
      +
      +-s <smlid> use Â’smlidÂ’ as the target lid for SM/SA queries.
      +
      +# Other common flags:
      +
      +-C <ca_name> use the specified ca_name.
      +
      +-P <ca_port> use the specified ca_port.
      +
      +-t <timeout_ms> override the default timeout for the solicited mads.
      +
      +Multiple CA/Multiple Port Support
      +
      +When no IB device or port is specified, the port to use is selected by
      +the following criteria:
      +
      +1. the first port that is ACTIVE.
      +
      +2. if not found, the first port that is UP (physical link up).
      +
      +If a port and/or CA name is specified, the user request is attempted to
      +be fulfilled, and will fail if it is not possible.
      +
      +
      +AUTHOR
      +Hal Rosenstock <halr@voltaire.com>
      +
      +
      +OFED August 11, 2006 IBPING(8)

      +


      +<return-to-top>

      +

       

      +

      IBPORTSTATE(8) OFED Diagnostics

      +


      +NAME
      +ibportstate - handle port (physical) state and link speed of an Infini-
      +Band port
      +
      +
      +SYNOPSIS
      +ibportstate [-d(ebug)] [-e(rr_show)] [-v(erbose)] [-D(irect)] [-G(uid)] [-s +smlid] [-V(ersion)] [-C ca_name] [-P ca_port] [-t(imeout) time-out_ms] [-h(elp)] +<dest dr_path|lid|guid> <portnum> [<op>]
      +
      +
      +DESCRIPTION
      +ibportstate allows the port state and port physical state of an IB port
      +to be queried (in addition to link width and speed being validated rel-
      +ative to the peer port when the port queried is a switch port), or a
      +switch port to be disabled, enabled, or reset. It also allows the link
      +speed enabled on any IB port to be adjusted.
      +
      +
      +OPTIONS
      +op Port operations allowed
      +supported ops: enable, disable, reset, speed, query
      +Default is query
      +
      +ops enable, disable, and reset are only allowed on switch ports
      +(An error is indicated if attempted on CA or router ports)
      +speed op is allowed on any port
      +speed values are legal values for PortInfo:LinkSpeedEnabled
      +(An error is indicated if PortInfo:LinkSpeedSupported does not support
      +this setting)
      +(NOTE: Speed changes are not effected until the port goes through
      +link renegotiation)
      +query also validates port characteristics (link width and speed)
      +based on the peer port. This checking is done when the port
      +queried is a switch port as it relies on combined routing
      +(an initial LID route with directed routing to the peer) which
      +can only be done on a switch. This peer port validation feature
      +of query op requires LID routing to be functioning in the subnet.
      +
      +
      +COMMON OPTIONS
      +Most OFED diagnostics take the following common flags. The exact list
      +of supported flags per utility can be found in the usage message and
      +can be shown using the util_name -h syntax.
      +
      +# Debugging flags
      +
      +-d raise the IB debugging level.
      +May be used several times (-ddd or -d -d -d).
      +
      +-e show send and receive errors (timeouts and others)
      +
      +-h show the usage message
      +
      +-v increase the application verbosity level.
      +May be used several times (-vv or -v -v -v)
      +
      +-V show the version info.
      +
      +# Addressing flags
      +
      +-D use directed path address arguments. The path
      +is a comma separated list of out ports.
      +Examples:
      +"0" # self port
      +"0,1,2,1,4" # out via port 1, then 2, ...
      +
      +-G use GUID address argument. In most cases, it is the Port GUID.
      +Example:
      +"0x08f1040023"
      +
      +-s <smlid> use Â’smlidÂ’ as the target lid for SM/SA queries.
      +
      +# Other common flags:
      +
      +-C <ca_name> use the specified ca_name.
      +
      +-P <ca_port> use the specified ca_port.
      +
      +-t <timeout_ms> override the default timeout for the solicited mads.
      +
      +Multiple CA/Multiple Port Support
      +
      +When no IB device or port is specified, the port to use is selected by
      +the following criteria:
      +
      +1. the first port that is ACTIVE.
      +
      +2. if not found, the first port that is UP (physical link up).
      +
      +If a port and/or CA name is specified, the user request is attempted to
      +be fulfilled, and will fail if it is not possible.
      +
      +
      +EXAMPLES
      +ibportstate 3 1 disable # by lid
      +
      +ibportstate -G 0x2C9000100D051 1 enable # by guid
      +
      +ibportstate -D 0 1 # (query) by direct route
      +
      +ibportstate 3 1 reset # by lid
      +
      +ibportstate 3 1 speed 1 # by lid
      +
      +
      +AUTHOR
      +Hal Rosenstock <halr@voltaire.com>
      +
      +
      +OFED October 19, 2006 IBPORTSTATE(8)

      +


      +<return-to-top>

      +

       

      +

      IBQUERYERRORS(8) OFED Diagnostics

      +

      NAME
      +ibqueryerrors - query and report non-zero IB port counters
      +
      +
      +SYNOPSIS
      +ibqueryerrors [-a -c -r -R -C <ca_name> -P <ca_port> -s
      +<err1,err2,...> -S <switch_guid> -D <direct_route> -d]
      +
      +
      +DESCRIPTION
      +ibqueryerrors reports the port counters of switches. This is simi-
      +lar to ibcheckerrors with the additional ability to filter out selected
      +errors, include the optional transmit and receive data counters, report
      +actions to remedy a non-zero count, and report full link information
      +for the link reported.
      +
      +
      +OPTIONS
      +-a Report an action to take. Some of the counters are not errors
      +in and of themselves. This reports some more information on
      +what the counters mean and what actions can/should be taken if
      +they are non-zero.
      +
      +-c Suppress some of the common "side effect" counters. These coun-
      +ters usually do not indicate an error condition and can be usu-
      +ally be safely ignored.
      +
      +-r Report the port information. This includes LID, port, external
      +port (if applicable), link speed setting, remote GUID, remote
      +port, remote external port (if applicable), and remote node
      +description information.
      +
      +-R Recalculate the ibnetdiscover information, ie do not use the
      +cached information. This option is slower but should be used if
      +the diag tools have not been used for some time or if there are
      +other reasons to believe that the fabric has changed.
      +
      +-s <err1,err2,...>
      +Suppress the errors listed in the comma separated list provided.
      +
      +-S <switch_guid>
      +Report results only for the switch specified. (hex format)
      +
      +-D <direct_route>
      +Report results only for the switch specified by the direct route
      +path.
      +
      +-d Include the optional transmit and receive data counters.
      +
      +-C <ca_name> use the specified ca_name for the search.
      +
      +-P <ca_port> use the specified ca_port for the search.
      +
      +AUTHOR
      +Ira Weiny <weiny2@llnl.gov>
      +
      +
      +OFED Jan 24, 2008 IBQUERYERRORS(8)

      +


      +<return-to-top>

      +

       

      +

      IBROUTE(8) OFED Diagnostics

      +

      NAME
      +ibroute - query InfiniBand switch forwarding tables
      +
      +
      +SYNOPSIS
      +ibroute [-d(ebug)] [-a(ll)] [-n(o_dests)] [-v(erbose)] [-D(irect)]
      +[-G(uid)] [-M(ulticast)] [-s smlid] [-C ca_name] [-P ca_port] [-t(ime-
      +out) timeout_ms] [-V(ersion)] [-h(elp)] [<dest dr_path|lid|guid>
      +[<startlid> [<endlid>]]]
      +
      +
      +DESCRIPTION
      +ibroute uses SMPs to display the forwarding tables (unicast (LinearFor-
      +wardingTable or LFT) or multicast (MulticastForwardingTable or MFT))
      +for the specified switch LID and the optional lid (mlid) range. The
      +default range is all valid entries in the range 1...FDBTop.
      +
      +
      +OPTIONS
      +-a, --all
      +show all lids in range, even invalid entries
      +
      +-n, --no_dests
      +do not try to resolve destinations
      +
      +-M, --Multicast
      +show multicast forwarding tables In this case, the range parame-
      +ters are specifying the mlid range.
      +
      +
      +COMMON OPTIONS
      +Most OFED diagnostics take the following common flags. The exact list
      +of supported flags per utility can be found in the usage message and
      +can be shown using the util_name -h syntax.
      +
      +# Debugging flags
      +
      +-d raise the IB debugging level.
      +May be used several times (-ddd or -d -d -d).
      +
      +-e show send and receive errors (timeouts and others)
      +
      +-h show the usage message
      +
      +-v increase the application verbosity level.
      +May be used several times (-vv or -v -v -v)
      +
      +-V show the version info.
      +
      +# Addressing flags
      +
      +-D use directed path address arguments. The path
      +is a comma separated list of out ports.
      +Examples:
      +"0" # self port
      +"0,1,2,1,4" # out via port 1, then 2, ...
      +
      +-G use GUID address argument. In most cases, it is the Port GUID.
      +Example:
      +"0x08f1040023"
      +
      +-s <smlid> use Â’smlidÂ’ as the target lid for SM/SA queries.
      +
      +# Other common flags:
      +
      +-C <ca_name> use the specified ca_name.
      +
      +-P <ca_port> use the specified ca_port.
      +
      +-t <timeout_ms> override the default timeout for the solicited mads.
      +
      +Multiple CA/Multiple Port Support
      +
      +When no IB device or port is specified, the port to use is selected by
      +the following criteria:
      +
      +1. the first port that is ACTIVE.
      +
      +2. if not found, the first port that is UP (physical link up).
      +
      +If a port and/or CA name is specified, the user request is attempted to
      +be fulfilled, and will fail if it is not possible.
      +
      +
      +EXAMPLES
      +Unicast examples
      +
      +ibroute 4 # dump all lids with valid out ports of switch with lid 4
      +
      +ibroute -a 4 # same, but dump all lids, even with invalid out ports
      +
      +ibroute -n 4 # simple dump format - no destination resolution
      +
      +ibroute 4 10 # dump lids starting from 10 (up to FDBTop)
      +
      +ibroute 4 0x10 0x20 # dump lid range
      +
      +ibroute -G 0x08f1040023 # resolve switch by GUID
      +
      +ibroute -D 0,1 # resolve switch by direct path
      +
      +
      +Multicast examples
      +
      +ibroute -M 4 # dump all non empty mlids of switch with lid 4
      +
      +ibroute -M 4 0xc010 0xc020 # same, but with range
      +
      +ibroute -M -n 4 # simple dump format
      +
      +
      +SEE ALSO
      +ibtracert(8)
      +
      +AUTHOR
      +Hal Rosenstock <halr@voltaire.com>
      +
      +
      +OFED July 25, 2006 IBROUTE(8)

      +


      +<return-to-top>

      +

       

      +
      +


      +ibv_devinfo - print CA (Channel Adapter) attributes

      +

      usage: ibv_devinfo  [options]
      +
      +Options:
      +   -d, --ib-dev=<dev> use IB device <dev> (default: first device +found)
      +    -i, --ib-port=<port> use port <port> of IB device (default: +all ports)
      +    -l, --list print only the IB devices names
      +    -v, --verbose print all the attributes of the IB device(s)
      +
      +<return-to-top>

      +
      +


      +IBSTAT(8) OFED Diagnostics

      +

      NAME
      +ibstat - query basic status of InfiniBand device(s)
      +
      +
      +SYNOPSIS
      +ibstat [-d(ebug)] [-l(ist_of_cas)] [-s(hort)] [-p(ort_list)] [-V(ersion)] [-h] <ca_name> +[portnum]
      +
      +
      +DESCRIPTION
      +ibstat is a binary which displays basic information obtained from the
      +local IB driver. Output includes LID, SMLID, port state, link width
      +active, and port physical state.
      +
      +It is similar to the ibstatus utility but implemented as a binary
      +rather than a script. It has options to list CAs and/or ports and dis-
      +plays more information than ibstatus.
      +
      +
      +OPTIONS
      +-l, --list_of_cas
      +list all IB devices
      +
      +-s, --short
      +short output
      +
      +-p, --port_list
      +show port list
      +
      +ca_name
      +InfiniBand device name
      +
      +portnum
      +port number of InfiniBand device
      +
      +
      +COMMON OPTIONS
      +Most OFED diagnostics take the following common flags. The exact list
      +of supported flags per utility can be found in the usage message and
      +can be shown using the util_name -h syntax.
      +
      +# Debugging flags
      +
      +-d raise the IB debugging level.
      +May be used several times (-ddd or -d -d -d).
      +
      +-e show send and receive errors (timeouts and others)
      +
      +-h show the usage message
      +
      +-v increase the application verbosity level.
      +May be used several times (-vv or -v -v -v)
      +
      +-V show the version info.
      +
      +# Addressing flags
      +
      +-D use directed path address arguments. The path
      +is a comma separated list of out ports.
      +Examples:
      +"0" # self port
      +"0,1,2,1,4" # out via port 1, then 2, ...
      +
      +-G use GUID address argument. In most cases, it is the Port GUID.
      +Example:
      +"0x08f1040023"
      +
      +-s <smlid> use Â’smlidÂ’ as the target lid for SM/SA queries.
      +
      +# Other common flags:
      +
      +-C <ca_name> use the specified ca_name.
      +
      +-P <ca_port> use the specified ca_port.
      +
      +-t <timeout_ms> override the default timeout for the solicited mads.
      +
      +Multiple CA/Multiple Port Support
      +
      +When no IB device or port is specified, the port to use is selected by
      +the following criteria:
      +
      +1. the first port that is ACTIVE.
      +
      +2. if not found, the first port that is UP (physical link up).
      +
      +If a port and/or CA name is specified, the user request is attempted to
      +be fulfilled, and will fail if it is not possible.
      +
      +
      +EXAMPLES
      +ibstat # display status of all ports on all IB devices
      +
      +ibstat -l # list all IB devices
      +
      +ibstat -p # show port guids
      +
      +ibstat ibv_device0 2 # show status of port 2 of Â’hca0Â’
      +
      +
      +SEE ALSO
      +ibstatus(8)
      +
      +
      +AUTHOR
      +Hal Rosenstock <halr@voltaire.com>
      +
      +
      +OFED July 25, 2006 IBSTAT(8)
      +
      +<return-to-top>

      +

       

      +

      IBSYSSTAT(8) OFED Diagnostics

      +

      NAME
      +ibsysstat - system status on an InfiniBand address
      +
      +
      +SYNOPSIS
      +ibsysstat [-d(ebug)] [-e(rr_show)] [-v(erbose)] [-G(uid)] [-C ca_name]
      +[-P ca_port] [-s smlid] [-t(imeout) timeout_ms] [-V(ersion)] [-o oui]
      +[-S(erver)] [-h(elp)] <dest lid | guid> [<op>]
      +
      +
      +DESCRIPTION
      +ibsysstat uses vendor mads to validate connectivity between IB nodes
      +and obtain other information about the IB node. ibsysstat is run as
      +client/server. Default is to run as client.
      +
      +
      +OPTIONS
      +Current supported operations:
      +ping - verify connectivity to server (default)
      +host - obtain host information from server
      +cpu - obtain cpu information from server
      +
      +-o, --oui
      +use specified OUI number to multiplex vendor mads
      +
      +-S, --Server
      +start in server mode (do not return)
      +
      +
      +
      +COMMON OPTIONS
      +Most OFED diagnostics take the following common flags. The exact list
      +of supported flags per utility can be found in the usage message and
      +can be shown using the util_name -h syntax.
      +
      +# Debugging flags
      +
      +-d raise the IB debugging level.
      +May be used several times (-ddd or -d -d -d).
      +
      +-e show send and receive errors (timeouts and others)
      +
      +-h show the usage message
      +
      +-v increase the application verbosity level.
      +May be used several times (-vv or -v -v -v)
      +
      +-V show the version info.
      +
      +# Addressing flags
      +
      +-G use GUID address argument. In most cases, it is the Port GUID.
      +Example:
      +"0x08f1040023"
      +
      +-s <smlid> use Â’smlidÂ’ as the target lid for SM/SA queries.
      +
      +# Other common flags:
      +
      +-C <ca_name> use the specified ca_name.
      +
      +-P <ca_port> use the specified ca_port.
      +
      +-t <timeout_ms> override the default timeout for the solicited mads.
      +
      +Multiple CA/Multiple Port Support
      +
      +When no IB device or port is specified, the port to use is selected by
      +the following criteria:
      +
      +1. the first port that is ACTIVE.
      +
      +2. if not found, the first port that is UP (physical link up).
      +
      +If a port and/or CA name is specified, the user request is attempted to
      +be fulfilled, and will fail if it is not possible.
      +
      +
      +AUTHOR
      +Hal Rosenstock    <halr@voltaire.com>
      +
      +
      +OFED August 11, 2006 IBSYSSTAT(8)

      +


      +<return-to-top>

      +

       

      +

      IBTRACERT(8) OFED Diagnostics

      +


      +NAME
      +ibtracert- trace InfiniBand path
      +
      +
      +SYNOPSIS
      +ibtracert [-d(ebug)] [-v(erbose)] [-D(irect)] [-G(uids)] [-n(o_info)]
      +[-m mlid] [-s smlid] [-C ca_name] [-P ca_port] [-t(imeout) timeout_ms]
      +[-V(ersion)] [--node-name--map <node-name-map>] [-h(elp)] [<dest
      +dr_path|lid|guid> [<startlid> [<endlid>]]]
      +
      +
      +DESCRIPTION
      +ibtracert uses SMPs to trace the path from a source GID/LID to a desti-
      +nation GID/LID. Each hop along the path is displayed until the destina-
      +tion is reached or a hop does not respond. By using the -m option, mul-
      +ticast path tracing can be performed between source and destination
      +nodes.
      +
      +
      +OPTIONS
      +-n, --no_info
      +simple format; donÂ’t show additional information
      +
      +-m show the multicast trace of the specified mlid
      +
      +--node-name-map <node-name-map>
      +Specify a node name map. The node name map file maps GUIDs to
      +more user friendly names. See ibnetdiscover(8) for node name
      +map file format.
      +
      +
      +COMMON OPTIONS
      +Most OFED diagnostics take the following common flags. The exact list
      +of supported flags per utility can be found in the usage message and
      +can be shown using the util_name -h syntax.
      +
      +# Debugging flags
      +
      +-d raise the IB debugging level.
      +May be used several times (-ddd or -d -d -d).
      +
      +-h show the usage message
      +
      +-v increase the application verbosity level.
      +May be used several times (-vv or -v -v -v)
      +
      +-V show the version info.
      +
      +# Addressing flags
      +
      +-D use directed path address arguments. The path
      +is a comma separated list of out ports.
      +Examples:
      +"0" # self port
      +"0,1,2,1,4" # out via port 1, then 2, ...
      +
      +-G use GUID address argument. In most cases, it is the Port GUID.
      +Example:
      +"0x08f1040023"
      +
      +-s <smlid> use Â’smlidÂ’ as the target lid for SM/SA queries.
      +
      +# Other common flags:
      +
      +-C <ca_name> use the specified ca_name.
      +
      +-P <ca_port> use the specified ca_port.
      +
      +-t <timeout_ms> override the default timeout for the solicited mads.
      +
      +Multiple CA/Multiple Port Support
      +
      +When no IB device or port is specified, the port to use is selected by
      +the following criteria:
      +
      +1. the first port that is ACTIVE.
      +
      +2. if not found, the first port that is UP (physical link up).
      +
      +If a port and/or CA name is specified, the user request is attempted to
      +be fulfilled, and will fail if it is not possible.
      +
      +
      +EXAMPLES
      +Unicast examples
      +
      +ibtracert 4 16 # show path between lids 4 and 16
      +
      +ibtracert -n 4 16 # same, but using simple output format
      +
      +ibtracert -G 0x8f1040396522d 0x002c9000100d051 # use guid addresses
      +
      +
      +Multicast example
      +
      +ibtracert -m 0xc000 4 16 # show multicast path of mlid 0xc000
      +between lids 4 and 16
      +
      +
      +SEE ALSO
      +ibroute(8)
      +
      +
      +AUTHOR
      +    Hal Rosenstock    <halr@voltaire.com>
      +
      +    Ira Weiny    <weiny2@llnl.gov>
      +
      +OFED April 14, 2007 IBTRACERT(8)

      +

      <return-to-top>

      +

       

      +

      PERFQUERY(8) OFED Diagnostics

      +


      +NAME
      +perfquery - query InfiniBand port counters
      +
      +
      +SYNOPSIS
      +perfquery [-d(ebug)] [-G(uid)] [-x|--extended] [-X|--xmtsl]
      +[-S|--rcvsl] [-a(ll_ports)] [-l(oop_ports)] [-r(eset_after_read)]
      +[-R(eset_only)] [-C ca_name] [-P ca_port] [-t(imeout) timeout_ms]
      +[-V(ersion)] [-h(elp)] [<lid|guid> [[port] [reset_mask]]]
      +
      +
      +DESCRIPTION
      +perfquery uses PerfMgt GMPs to obtain the PortCounters (basic perfor-
      +mance and error counters), PortExtendedCounters, PortXmitDataSL, or
      +PortRcvDataSL from the PMA at the node/port specified. Optionally shows
      +aggregated counters for all ports of node. Also, optionally, reset
      +after read, or only reset counters.
      +
      +Note: In PortCounters, PortCountersExtended, PortXmitDataSL, and PortR-
      +cvDataSL, components that represent Data (e.g. PortXmitData and PortR-
      +cvData) indicate octets divided by 4 rather than just octets.
      +
      +Note: Inputting a port of 255 indicates an operation be performed on
      +all ports.
      +
      +
      +OPTIONS
      +-x, --extended
      +show extended port counters rather than (basic) port counters.
      +Note that extended port counters attribute is optional.
      +
      +-X, --xmtsl
      +show transmit data SL counter. This is an optional counter for
      +QoS.
      +
      +-S, --rcvsl
      +show receive data SL counter. This is an optional counter for
      +QoS.
      +
      +-a, --all_ports
      +show aggregated counters for all ports of the destination lid or
      +reset all counters for all ports. If the destination lid does
      +not support the AllPortSelect flag, all ports will be iterated
      +through to emulate AllPortSelect behavior.
      +
      +-l, --loop_ports
      +If all ports are selected by the user (either through the -a
      +option or port 255) iterate through each port rather than doing
      +than aggregate operation.
      +
      +-r, --reset_after_read
      +reset counters after read
      +
      +-R, --Reset_only
      +only reset counters
      +
      +
      +COMMON OPTIONS
      +Most OFED diagnostics take the following common flags. The exact list
      +of supported flags per utility can be found in the usage message and
      +can be shown using the util_name -h syntax.
      +
      +# Debugging flags
      +
      +-d raise the IB debugging level.
      +May be used several times (-ddd or -d -d -d).
      +
      +-e show send and receive errors (timeouts and others)
      +
      +-h show the usage message
      +
      +-v increase the application verbosity level.
      +May be used several times (-vv or -v -v -v)
      +
      +-V show the version info.
      +
      +# Addressing flags
      +
      +-G use GUID address argument. In most cases, it is the Port GUID.
      +Example:
      +"0x08f1040023"
      +
      +-s <smlid> use Â’smlidÂ’ as the target lid for SM/SA queries.
      +
      +# Other common flags:
      +
      +-C <ca_name> use the specified ca_name.
      +
      +-P <ca_port> use the specified ca_port.
      +
      +-t <timeout_ms> override the default timeout for the solicited mads.
      +
      +Multiple CA/Multiple Port Support
      +
      +When no IB device or port is specified, the port to use is selected by
      +the following criteria:
      +
      +1. the first port that is ACTIVE.
      +
      +2. if not found, the first port that is UP (physical link up).
      +
      +If a port and/or CA name is specified, the user request is attempted to
      +be fulfilled, and will fail if it is not possible.
      +
      +
      +EXAMPLES
      +perfquery # read local port performance counters
      +
      +perfquery 32 1 # read performance counters from lid 32, port 1
      +
      +perfquery -x 32 1 # read extended performance counters from lid 32, port 1
      +
      +perfquery -a 32 # read perf counters from lid 32, all ports
      +
      +perfquery -r 32 1 # read performance counters and reset
      +
      +perfquery -x -r 32 1 # read extended performance counters and reset
      +
      +perfquery -R 0x20 1 # reset performance counters of port 1 only
      +
      +perfquery -x -R 0x20 1 # reset extended performance counters of port 1 only
      +
      +perfquery -R -a 32 # reset performance counters of all ports
      +
      +perfquery -R 32 2 0x0fff # reset only error counters of port 2
      +
      +perfquery -R 32 2 0xf000 # reset only non-error counters of port 2
      +
      +
      +AUTHOR
      +Hal Rosenstock    <halr@voltaire.com>
      +
      +
      +OFED March 10, 2009 PERFQUERY(8)

      +


      +<return-to-top>

      +

       

      +

      SAQUERY(8) OFED Diagnostics

      +


      +NAME
      +saquery - query InfiniBand subnet administration attributes
      +
      +
      +SYNOPSIS
      +saquery [-h] [-d] [-p] [-N] [--list | -D] [-S] [-I] [-L] [-l] [-G] [-O]
      +[-U] [-c] [-s] [-g] [-m] [-x] [-C ca_name] [-P ca_port] [--smkey val]
      +[-t(imeout) <msec>] [--src-to-dst <src:dst>] [--sgid-to-dgid
      +<sgid-dgid>] [--node-name-map <node-name-map>] [<name> | <lid> |
      +<guid>]
      +
      +
      +DESCRIPTION
      +saquery issues the selected SA query. Node records are queried by
      +default.
      +
      +
      +OPTIONS
      +-p get PathRecord info
      +
      +-N get NodeRecord info
      +
      +--list | -D
      +get NodeDescriptions of CAs only
      +
      +-S get ServiceRecord info
      +
      +-I get InformInfoRecord (subscription) info
      +
      +-L return the Lids of the name specified
      +
      +-l return the unique Lid of the name specified
      +
      +-G return the Guids of the name specified
      +
      +-O return the name for the Lid specified
      +
      +-U return the name for the Guid specified
      +
      +-c get the SAÂ’s class port info
      +
      +-s return the PortInfoRecords with isSM or isSMdisabled capability
      +mask bit on
      +
      +-g get multicast group info
      +
      +-m get multicast member info. If a group is specified, limit the
      +output to the group specified and print one line containing only
      +the GUID and node description for each entry. Example: saquery
      +-m 0xc000
      +
      +-x get LinkRecord info
      +
      +--src-to-dst
      +get a PathRecord for <src:dst> where src and dst are either node
      +names or LIDs
      +
      +--sgid-to-dgid
      +get a PathRecord for sgid to dgid where both GIDs are in an IPv6
      +format acceptable to inet_pton(3).
      +
      +-C <ca_name>
      +use the specified ca_name.
      +
      +-P <ca_port>
      +use the specified ca_port.
      +
      +--smkey <val>
      +use SM_Key value for the query. Will be used only with "trusted"
      +queries. If non-numeric value (like Â’xÂ’) is specified then
      +saquery will prompt for a value.
      +
      +-t, -timeout <msec>
      +Specify SA query response timeout in milliseconds. Default is
      +100 milliseconds. You may want to use this option if IB_TIMEOUT
      +is indicated.
      +
      +--node-name-map <node-name-map>
      +Specify a node name map. The node name map file maps GUIDs to
      +more user friendly names. See ibnetdiscover(8) for node name
      +map file format. Only used with the -O and -U options.
      +
      +Supported query names (and aliases):
      +ClassPortInfo (CPI)
      +NodeRecord (NR) [lid]
      +PortInfoRecord (PIR) [[lid]/[port]]
      +SL2VLTableRecord (SL2VL) [[lid]/[in_port]/[out_port]]
      +PKeyTableRecord (PKTR) [[lid]/[port]/[block]]
      +VLArbitrationTableRecord (VLAR) [[lid]/[port]/[block]]
      +InformInfoRecord (IIR)
      +LinkRecord (LR) [[from_lid]/[from_port]] [[to_lid]/[to_port]]
      +ServiceRecord (SR)
      +PathRecord (PR)
      +MCMemberRecord (MCMR)
      +LFTRecord (LFTR) [[lid]/[block]]
      +MFTRecord (MFTR) [[mlid]/[position]/[block]]
      +
      +-d enable debugging
      +
      +-h show help
      +
      +
      +AUTHORS
      +Ira Weiny <weiny2@llnl.gov>
      +
      +Hal Rosenstock <halr@voltaire.com>
      +
      +
      +OFED October 19, 2008 SAQUERY(8)

      +

      <return-to-top>

      +

       

      +

      SMINFO(8) OFED Diagnostics

      +


      +NAME
      +sminfo - query InfiniBand SMInfo attribute

      -Options:
      -   -d, --ib-dev=<dev> use IB device <dev> (default: first device -found)
      -    -i, --ib-port=<port> use port <port> of IB device (default: -all ports)
      -    -l, --list print only the IB devices names
      -    -v, --verbose print all the attributes of the IB device(s)

      -<return-to-top>

      -


      -ibstat - print InfiniBand stats

      -

      usage: ibstat [OPTIONS] <ca_name> [portnum]
      +SYNOPSIS
      +sminfo [-d(ebug)] [-e(rr_show)] -s state -p prio -a activity
      +[-D(irect)] [-G(uid)] [-C ca_name] [-P ca_port] [-t(imeout) time-
      +out_ms] [-V(ersion)] [-h(elp)] sm_lid | sm_dr_path [modifier]

      -Options:
      -    -d debug
      -    -l list all IB devices
      -    -s print short device summary
      -    -p print port GUIDs
      -    -V print ibstat version information and exit
      -    -h print usage

      +DESCRIPTION
      +Optionally set and display the output of a sminfo query in human read-
      +able format. The target SM is the one listed in the local port info, or
      +the SM specified by the optional SM lid or by the SM direct routed
      +path.
      +
      +Note: using sminfo for any purposes other then simple query may be very
      +dangerous, and may result in a malfunction of the target SM.
      +
      +
      +OPTIONS
      +-s set SM state
      +0 - not active
      +1 - discovering
      +2 - standby
      +3 - master
      +
      +-p set priority (0-15)
      +
      +-a set activity count
      +
      +
      +COMMON OPTIONS
      +Most OFED diagnostics take the following common flags. The exact list
      +of supported flags per utility can be found in the usage message and
      +can be shown using the util_name -h syntax.
      +
      +# Debugging flags
      +
      +-d raise the IB debugging level.
      +May be used several times (-ddd or -d -d -d).
      +
      +-e show send and receive errors (timeouts and others)
      +
      +-h show the usage message
      +
      +-v increase the application verbosity level.
      +May be used several times (-vv or -v -v -v)
      +
      +-V show the version info.
      +
      +# Addressing flags
      +
      +-D use directed path address arguments. The path
      +is a comma separated list of out ports.
      Examples:
      -    ibstat -l # list all IB devices
      -    ibstat mthca0 2 # stat port 2 of mthca0
      +"0" # self port
      +"0,1,2,1,4" # out via port 1, then 2, ...

      -<return-to-top>
      +-G use GUID address argument. In most cases, it is the Port GUID.
      +Example:
      +"0x08f1040023"
      +
      +-s <smlid> use Â’smlidÂ’ as the target lid for SM/SA queries.
      +
      +# Other common flags:
      +
      +-C <ca_name> use the specified ca_name.
      +
      +-P <ca_port> use the specified ca_port.
      +
      +-t <timeout_ms> override the default timeout for the solicited mads.
      +
      +Multiple CA/Multiple Port Support
      +
      +When no IB device or port is specified, the port to use is selected by
      +the following criteria:
      +
      +1. the first port that is ACTIVE.
      +
      +2. if not found, the first port that is UP (physical link up).
      +
      +If a port and/or CA name is specified, the user request is attempted to
      +be fulfilled, and will fail if it is not possible.
      +
      +
      +EXAMPLES
      +sminfo         # local port´s sminfo
      +
      +sminfo 32     # show sminfo of lid 32
      +
      +sminfo -G 0x8f1040023     # same but using guid address
      +
      +
      +SEE ALSO
      +smpdump(8)
      +
      +
      +AUTHOR
      +Hal Rosenstock    <halr@voltaire.com>
      +
      +OFED July 25, 2006 SMINFO(8)

      +


      +<return-to-top>

      +

       

      +

      SMPDUMP(8) OFED Diagnostics

      +


      +NAME
      +smpdump - dump InfiniBand subnet management attributes
      +
      +
      +SYNOPSIS
      +smpdump [-s(ring)] [-D(irect)] [-C ca_name] [-P ca_port] [-t(imeout)
      +timeout_ms] [-V(ersion)] [-h(elp)] <dlid|dr_path> <attr> [mod]
      +
      +
      +DESCRIPTION
      +smpdump is a general purpose SMP utility which gets SM attributes from
      +a specified SMA. The result is dumped in hex by default.
      +
      +
      +OPTIONS
      +attr IBA attribute ID for SM attribute
      +
      +mod IBA modifier for SM attribute
      +
      +
      +COMMON OPTIONS
      +Most OFED diagnostics take the following common flags. The exact list
      +of supported flags per utility can be found in the usage message and
      +can be shown using the util_name -h syntax.
      +
      +# Debugging flags
      +
      +-d raise the IB debugging level.
      +May be used several times (-ddd or -d -d -d).
      +
      +-e show send and receive errors (timeouts and others)
      +
      +-h show the usage message
      +
      +-v increase the application verbosity level.
      +May be used several times (-vv or -v -v -v)
      +
      +-V show the version info.
      +
      +# Addressing flags
      +
      +-D use directed path address arguments. The path
      +is a comma separated list of out ports.
      +Examples:
      +"0" # self port
      +"0,1,2,1,4" # out via port 1, then 2, ...
      +
      +-G use GUID address argument. In most cases, it is the Port GUID.
      +Example:
      +"0x08f1040023"
      +
      +-s <smlid> use Â’smlidÂ’ as the target lid for SM/SA queries.
      +
      +# Other common flags:
      +
      +-C <ca_name> use the specified ca_name.
      +
      +-P <ca_port> use the specified ca_port.
      +
      +-t <timeout_ms> override the default timeout for the solicited mads.
      +
      +Multiple CA/Multiple Port Support
      +
      +When no IB device or port is specified, the port to use is selected by
      +the following criteria:
      +
      +1. the first port that is ACTIVE.
      +
      +2. if not found, the first port that is UP (physical link up).
      +
      +If a port and/or CA name is specified, the user request is attempted to
      +be fulfilled, and will fail if it is not possible.
      +
      +
      +EXAMPLES
      +Direct Routed Examples
      +
      +smpdump -D 0,1,2,3,5 16 # NODE DESC
      +
      +smpdump -D 0,1,2 0x15 2 # PORT INFO, port 2
      +
      +LID Routed Examples
      +
      +smpdump 3 0x15 2 # PORT INFO, lid 3 port 2
      +
      +smpdump 0xa0 0x11 # NODE INFO, lid 0xa0
      +
      +
      +SEE ALSO
      +smpquery(8)
      +
      +
      +AUTHOR
      +Hal Rosenstock    <halr@voltaire.com>
      +
      +
      +OFED July 25, 2006 SMPDUMP(8)

      +


      +<return-to-top>

      +

       

      +

      SMPQUERY(8) OFED Diagnostics

      +


      +NAME
      +smpquery - query InfiniBand subnet management attributes
      +
      +
      +SYNOPSIS
      +smpquery [-d(ebug)] [-e(rr_show)] [-v(erbose)] [-D(irect)] [-G(uid)]
      +[-C ca_name] [-P ca_port] [-t(imeout) timeout_ms] [--node-name-map
      +node-name-map] [-V(ersion)] [-h(elp)] <op> <dest dr_path|lid|guid> [op
      +params]
      +
      +
      +DESCRIPTION
      +smpquery allows a basic subset of standard SMP queries including the
      +following: node info, node description, switch info, port info. Fields
      +are displayed in human readable format.
      +
      +
      +OPTIONS
      +Current supported operations and their parameters:
      +nodeinfo <addr>
      +nodedesc <addr>
      +portinfo <addr> [<portnum>] # default port is zero
      +switchinfo <addr>
      +pkeys <addr> [<portnum>]
      +sl2vl <addr> [<portnum>]
      +vlarb <addr> [<portnum>]
      +guids <addr>
      +
      +
      +--node-name-map <node-name-map>
      +Specify a node name map. The node name map file maps GUIDs to
      +more user friendly names. See ibnetdiscover(8) for node name
      +map file format.
      +
      +
      +COMMON OPTIONS
      +Most OFED diagnostics take the following common flags. The exact list
      +of supported flags per utility can be found in the usage message and
      +can be shown using the util_name -h syntax.
      +
      +# Debugging flags
      +
      +-d raise the IB debugging level.
      +May be used several times (-ddd or -d -d -d).
      +
      +-e show send and receive errors (timeouts and others)
      +
      +-h show the usage message
      +
      +-v increase the application verbosity level.
      +May be used several times (-vv or -v -v -v)
      +
      +-V show the version info.
      +
      +# Addressing flags
      +
      +-D use directed path address arguments. The path
      +is a comma separated list of out ports.
      +Examples:
      +"0" # self port
      +"0,1,2,1,4" # out via port 1, then 2, ...
      +
      +-c use combined route address arguments. The
      +address is a combination of a LID and a direct route path.
      +The LID specified is the DLID and the local LID is used
      +as the DrSLID.
      +
      +-G use GUID address argument. In most cases, it is the Port GUID.
      +Example:
      +"0x08f1040023"
      +
      +-s <smlid> use Â’smlidÂ’ as the target lid for SM/SA queries.
      +
      +# Other common flags:
      +
      +-C <ca_name> use the specified ca_name.
      +
      +-P <ca_port> use the specified ca_port.
      +
      +-t <timeout_ms> override the default timeout for the solicited mads.
      +
      +Multiple CA/Multiple Port Support
      +
      +When no IB device or port is specified, the port to use is selected by
      +the following criteria:
      +
      +1. the first port that is ACTIVE.
      +
      +2. if not found, the first port that is UP (physical link up).
      +
      +If a port and/or CA name is specified, the user request is attempted to
      +be fulfilled, and will fail if it is not possible.
      +
      +
      +EXAMPLES
      +smpquery portinfo 3 1 # portinfo by lid, with port modifier
      +
      +smpquery -G switchinfo 0x2C9000100D051 1 # switchinfo by guid
      +
      +smpquery -D nodeinfo 0 # nodeinfo by direct route
      +
      +smpquery -c nodeinfo 6 0,12 # nodeinfo by combined route
      +
      +
      +SEE ALSO
      +smpdump(8)
      +
      +
      +AUTHOR
      +Hal Rosenstock <halr@voltaire.com>
      +
      +
      +OFED March 14, 2007 SMPQUERY(8)

      +

      <return-to-top>

      +

       

      +

      VENDSTAT(8) OFED Diagnostics

      +

      NAME
      +vendstat - query InfiniBand vendor specific functions
      +
      +
      +SYNOPSIS
      +vendstat [-d(ebug)] [-G(uid)] [-N] [-w] [-i] [-c <num,num>] [-C ca_name] [-P +ca_port] [-t(imeout) timeout_ms] [-V(ersion)] [-h(elp)] <lid|guid>
      +
      +
      +DESCRIPTION
      +vendstat uses vendor specific MADs to access beyond the IB spec vendor
      +specific functionality. Currently, there is support for Mellanox InfiniSwitch-III +(IS3) and InfiniSwitch-IV (IS4).
      +
      +
      +OPTIONS
      +-N show IS3 general information.
      +
      +-w show IS3 port xmit wait counters.
      +
      +-i show IS4 counter group info.
      +
      +-c <num,num>
      +configure IS4 counter groups.
      +
      +Configure IS4 counter groups 0 and 1. Such configuration is not
      +persistent across IS4 reboot. First number is for counter group
      +0 and second is for counter group 1.
      +
      +Group 0 counter config values:
      +0 - PortXmitDataSL0-7
      +1 - PortXmitDataSL8-15
      +2 - PortRcvDataSL0-7
      +
      +Group 1 counter config values:
      +1 - PortXmitDataSL8-15
      +2 - PortRcvDataSL0-7
      +8 - PortRcvDataSL8-15
      +
      +
      +COMMON OPTIONS
      +Most OFED diagnostics take the following common flags. The exact list
      +of supported flags per utility can be found in the usage message and
      +can be shown using the util_name -h syntax.
      +
      +# Debugging flags
      +
      +-d raise the IB debugging level.
      +May be used several times (-ddd or -d -d -d).
      +
      +-e show send and receive errors (timeouts and others)
      +
      +-h show the usage message
      +
      +-v increase the application verbosity level.
      +May be used several times (-vv or -v -v -v)
      +
      +-V show the version info.
      +
      +# Addressing flags
      +
      +-G use GUID address argument. In most cases, it is the Port GUID.
      +Example:
      +"0x08f1040023"
      +
      +-s <smlid> use Â’smlidÂ’ as the target lid for SM/SA queries.
      +
      +# Other common flags:
      +
      +-C <ca_name> use the specified ca_name.
      +
      +-P <ca_port> use the specified ca_port.
      +
      +-t <timeout_ms> override the default timeout for the solicited mads.
      +
      +Multiple CA/Multiple Port Support
      +
      +When no IB device or port is specified, the port to use is selected by
      +the following criteria:
      +
      +1. the first port that is ACTIVE.
      +
      +2. if not found, the first port that is UP (physical link up).
      +
      +If a port and/or CA name is specified, the user request is attempted to
      +be fulfilled, and will fail if it is not possible.
      +
      +
      +EXAMPLES
      +vendstat -N 6 # read IS3 general information
      +
      +vendstat -w 6 # read IS3 port xmit wait counters
      +
      +vendstat -i 6 12 # read IS4 port 12 counter group info
      +
      +vendstat -c 0,1 6 12 # configure IS4 port 12 counter groups for PortXmitDataSL
      +
      +vendstat -c 2,8 6 12 # configure IS4 port 12 counter groups for PortRcvDataSL
      +
      +
      +AUTHOR
      +Hal Rosenstock    <halr@voltaire.com>
      +
      +
      +OFED April 16, 2009 VENDSTAT(8)

      +

      <return-to-top>
       


      ib_limits - Infiniband verbs tests

      @@ -424,6 +2254,7 @@ cmtest - Connection Manager Tests

      <return-to-top>

       

      +

      InfiniBand Partition Management

      The part_man.exe @@ -590,6 +2421,7 @@ vstat - HCA Stats and Counters

      <return-to-top>

       

      +

      Subnet Management with OpenSM Rev: openib-1.2.0


      A single running process (opensm.exe) is required to configure @@ -982,6 +2814,7 @@ run on the same HCA port which OpenSM is currently using.

      <return-to-top>

       


      +

      ibtrapgen - Generate Infiniband subnet management traps

      Usage: ibtrapgen -t|--trap_num <TRAP_NUM> -n|--number <NUM_TRAP_CREATIONS>
      @@ -1080,6 +2913,7 @@ Options: one of the following optional flows:

      <return-to-top>

       

       

      +

      IPoIB - Internet Protocols over InfiniBand


      IPoIB enables the use of Internet Protocol utilities (e.g., ftp, @@ -1108,6 +2942,7 @@ the Local Area Connection to be disabled, then likely your subnet manager

      <return-to-top>

       

       

      +

      Winsock Direct Service Provider


      Winsock Direct (WSD) is Microsoft's proprietary protocol that @@ -1292,6 +3127,7 @@ Manual control is performed via the \Program Files\WinOF\installsp.exe utility.<

      <return-to-top>

       

      +

      Network Direct Service Provider


      Network Direct Service Provider Installation

      @@ -1322,6 +3158,7 @@ host)

      See ndping.exe /? for details.

      <return-to-top>

       

      +

      Usermode Direct Access Transport and Direct Access Programming Libraries


      @@ -1415,9 +3252,9 @@ include

      -

      <return-to-top>

      -

      DAT ENVIRONMENT:

      +


      + DAT ENVIRONMENT
      :

      DAT/DAPL v1.1 (free-build) @@ -1436,16 +3273,6 @@ include

      versions of DAT/DAPL 2.0 runtime libraries, found only on 64-bit systems, are identified in '%SystemDrive%\%ProgramFiles(x86)%\WinOF' as dat232.dll and dapl232.dll.

      -

      DAT/DAPL 2.0 (free-build) - libraries utilize the IBAL (eye-ball) InfiniBand Access Layer Connection - Manager (CM) to establish IB reliable connections to Windows based system. - To facilitate DAT v2.0 Windows to Linux InfiniBand communications an interim - BSD socket based Connection Manager (sock-cm) is provided. The DAPL - socket-CM provider is installed as '%SystemRoot%\dapl2-scm.dll' and listed - in the %SystemDrive%\DAT\dat.conf provider file as 'ibnic0v2-scm'. Both - nodes must use the same Connection Manager IBAL-CM[ibnic0v2: dapl2.dll] or - Socket-CM[ibnicv2-scm: dapl2-scm.dll] in order for connections to be - established.

      In order for DAT/uDAPL programs to execute correctly, the runtime library files 'dat.dll and dapl.dll' must be present in one of the following folders: current @@ -1475,8 +3302,98 @@ include

      exist in the DAT default configuration folder '%SystemDrive%\DAT\', dat.conf will be copied there.
       

      -

      -DAT application build environment:

      +
      +

      +DAPL Providers

      +
      +

      DAT 2.0 (free-build) + libraries utilize the following user application selectable DAPL providers. + Each DAPL provider represents an RDMA hardware interface device type and + it's Connection Manager.
      + DAPL providers are listed in the file '%SystemDrive%\DAT\dat.conf'.
      + The dat.conf InfiniBand DAPL provider names are formatted 'ibnic-HCA#-DAPL_Version-CM_type'.
      + Example:
      +    ibnic0v2 - InfiniBand HCA #zero, DAPL version 2.0, (default + CM is IBAL).
      +    ibnic1v2-scm - InfiniBand HCA #one, DAPL version 2.0, CM is + 'socket-CM'

      +

      Each non-comment line in + the dat.conf file describes a DAPL provider interface.
      + The 2nd to the last field on the right (7th from the left) describes the + ia_device_params (Interface Adapter Device Parameters) (aka, RDMA device) in + accordance with the specific DAPL provider specified in the 5th field. +

      +
        +
      • +

        IBAL (eye-ball) DAPL + Provider

        +
          +
        • +

          File: + %windir%\dapl2.dll

        • +
        • +

          dat.conf Provider + name: ibnic0v2

        • +
        • +

          ia_device_params - + 'IbalHcaX Y' where 'X' is the IB HCA device instance (0 == 1st HCA), + Y is the port number (1 == 1st port).

        • +
        +

        Use the InfiniBand Access Layer Connection + Manager (CM) to establish InfiniBand reliable connections to Windows based system. + IBAL is the original DAPL provider.

      • +
      • +

        Socket-CM Provider

        +
          +
        • +

          File: %windir%\dapl2-ofa-scm.dll

          +
        • +
        • +

          dat.conf + Provider name: ibnic0v2-scm

        • +
        • +

          ia_device_params - + "ibv_deviceX Y" where 'X' is the IB HCA device instance (0 == 1st + HCA), Y is the port number (1 == 1st port). Socket-CM uses Winverbs + hence the ibv_deviceX nomenclature; see ibstat command.

        • +
        +

        To facilitate DAT v2.0 + Windows to Linux DAT v2.0 InfiniBand communications, a BSD socket based Connection Manager (sock-cm) is + supported. Both + nodes must use the same Connection Manager IBAL-CM[ibnic0v2] or + Socket-CM[ibnicv2-scm] in order for connections to be + established. For Linux <==> Windows DAT connections, the DAPL provider must + be socket-cm or rdma-cm; IBAL DAPL provider is not supported for Linux.

      • +
      • +

        RDMA-CM Provider

        +
          +
        • +

          File: %windir%\dapl2-ofa-cma.dll

          +
        • +
        • +

          dat.conf Provider + name: ibnic0v2-cma

        • +
        • +

          ia_device_params - + "rdma_devX Y" where 'X' is the RDMA device instance (future iWARP + support, today InfiniBand) with assigned IPv4 address (0 == 1st + IPoIB device with an assigned IPv4 address); Y is ignored although + there must be a digit present.
          + Alternatively, 'rdma_devX' can be replaced with the IP4v address + assigned to an IPoIB device instance. The 'rdma_dev0' is used to + instruct the rdma-cm provider to use the 1st RDMA device (IPoIB) + with an assigned IP4v address such that the dat.conf file does not + have to be tailored for each WinOF installation.

        • +
        +

        OFED RDMA CM manager + can be used to establish connections between Windows and/or Linux systems.

      • +
      +

       

      +
      +

      DAT application build environment:

      +

      DAT library header files are selectively installed in the DAT default configuration folder as
      '%SystemDrive%\DAT\v1-1' or @@ -1507,9 +3424,9 @@ include

      When linking a DEBUG/Checked version make sure to use datd.lib or dat2d.lib for DAT v2.0.

      -

      -DAT library environment variables:

      + +

      DAT library environment variables:

      +
       DAT_OVERRIDE
       ------------
      @@ -1549,9 +3466,10 @@ DAT_OS_DBG_DEST_ALL    = 0x3
       
       For example, 0x3 will output to both stdout and the syslog. 
       
      -

      -DAPL Provider library environment variables

      -


      DAPL_DBG_TYPE
      -------------

      +

      +

      DAPL Provider library environment variables

      +
      +

      DAPL_DBG_TYPE
      -------------

      Value specifies which parts of the registry will print debugging information, valid values are

      DAPL_DBG_TYPE_ERR          = 0x0001
      DAPL_DBG_TYPE_WARN         = 0x0002
      DAPL_DBG_TYPE_EVD          = 0x0004
      DAPL_DBG_TYPE_CM           = 0x0008
      DAPL_DBG_TYPE_EP           = 0x0010
      DAPL_DBG_TYPE_UTIL         = 0x0020
      DAPL_DBG_TYPE_CALLBACK     = 0x0040
      DAPL_DBG_TYPE_DTO_COMP_ERR = 0x0080
      DAPL_DBG_TYPE_API          = 0x0100
      DAPL_DBG_TYPE_RTN          = 0x0200
      DAPL_DBG_TYPE_EXCEPTION    = 0x0400
      @@ -1562,6 +3480,7 @@ debugging information, valid values are




      <return-to-top>


      +

      DAPLTEST

       
      @@ -1849,18 +3768,39 @@ USAGE - Limit test client
                               for a larger number of iterations,
                               validating the data received.
      -dt-svr.bat - DAPLtest server script; starts a DAPLtest server on the local node.
      -	dt-svr [-D [hex-debug-bitmask] ]
      -
      -dt-cli.bat - DAPLtest client; drives testing by interacting with dt-svr.bat script.
      -	dt-cli host-IPv4-address testname
      -		example: dt-cli 10.10.2.20 trans
      -		         dt-cli -h  # outputs help text.
      +dt-svr.bat - DAPLtest server script; starts a DAPL2test.exe server on the local node. + dt-svr DAPL-provider [-D [hex-debug-bitmask] ] +
      +
      +
      where: DAPL-provider can be one of [ ibal | scm | cma ]
      +
      +
        +
      • +
        ibal - Original InfiniBand Access Layer (eye-bal) ibal verbs interface
        +
      • +
      • +
        scm - Socket-CM (Connection Manager), exchanges QP information over a n IP socket.
        +
      • +
      • +
        cma - rdma CM, use the OFED rdma Communications Manager to create the QP connection.
        +
      • +
      • +
        or the DAPL-provider name from %SystemDrive%\DAT\dat.conf
        +
      • +
      +
      +
      +
      +
      dt-cli.bat - DAPLtest client; drives testing by interacting with dt-svr.bat script.
      +	dt-cli DAPL-provider host-IPv4-address testname [-D [hex-debug-bitmask] ]
      +		example: dt-cli ibnic0v2 10.10.2.20 trans
      +		         dt-cli -h  # outputs help text.
      +			 dt-svr ibnic0v2	# IBAL on HCA0
       Verify dt-*.bat script is running same dapltest.exe(v1.1) or dapl2test.exe(v2.0)
       
       
      -BUGS  (and  To Do List)
      +BUGS  (and To Do List)
       
           Use of CNOs (-Q) is not yet supported.
       
      @@ -1868,7 +3808,9 @@ BUGS  (and  To Do List)
       

      <return-to-top>

       

       

      -

      SRP (SCSI RDMA) Protocol Driver

      +
      +

      SRP (SCSI RDMA) Protocol Driver

      +

      The SCSI RDMA @@ -1942,7 +3884,9 @@ when the WinOF uninstall attempts to delete the files the operation fails.

      see ib_srp.inf file for details.

      <return-to-top>

       

      -

      QLogic VNIC Configuration

      +
      +

      QLogic VNIC Configuration

      +

      The QLogic VNIC (Virtual Network Interface Card) driver in conjunction with the QLogic Ethernet @@ -2024,7 +3968,8 @@ for the setting to take effect.

       <return-to-top>

       

      -

      QLogic VNIC Child Device Management

      +

      QLogic VNIC Child Device Management

      +

      Each I/O Controller (IOC) of QLogic's EVIC gateway device is able to handle 256 connections per host. So a single host can have multiple VNIC interfaces connecting to the same @@ -2054,8 +3999,10 @@ Executing qlgcvnic_config without any option or with -l option will list the IOC

       <return-to-top>

       

      -

      InfiniBand Software -Development Kit

      +
      +

      InfiniBand Software +Development Kit

      +

      If selected during a WinOF install, the IB Software Development Kit will be installed as '%SystemDrive%\IBSDK'. Underneath the IBSDK\ folder you will find an include folder 'Inc\',  library definition files 'Lib\'  along with a @@ -2088,13 +4035,23 @@ include folder 'Inc\',  library definition files 'Lib\'  along with a

       

      <return-to-top>

       

      +

      WinVerbs


      -


      WinVerbs is a userspace verbs and communication management interface optimized
      for the Windows operating system. Its lower interface is designed to support
      any RDMA based device, including Infiniband and iWarp. Its upper interface is
      capable of providing a low latency verbs interface, plus supports Microsoft's
      Network Direct Interface, DAPL, and OFED libibverbs interfaces. It consists of
      a userspace library and a kernel filter driver.
      -
      The WinVerbs driver loads as an upper filter driver for Infiniband controllers.
      (Open source iWarp drivers for Windows are not yet available.) A corresponding
      userspace library installs as part of the Winverbs driver installation package.
      Additionally, a Windows port of the OFED libibverbs library and several test
      programs are also included.
      +

      WinVerbs is a userspace verbs and communication management interface optimized
      for the Windows operating system. Its lower interface is designed to support
      any RDMA based device, including Infiniband and +future RDMA devices. Its upper interface is
      capable of providing a low latency verbs interface, plus supports Microsoft's
      NetworkDirect Interface, DAPL and OFED +components: libibverbs, libibmad, rdma_cm interfaces and numerous OFED IB +diagnostic tools.
      +
      The WinVerbs driver loads as an upper filter driver for Infiniband HCA +devices.
      (Open source iWarp drivers for Windows are not yet available.) A corresponding
      userspace library installs as part of the Winverbs driver installation package.
      Additionally, a Windows port of the OFED libibverbs library and several test
      programs are also included.

      +

      As of the WinOF 2.1 release, Winverbs and Winmad are are fully integrated +into the HCA driver stack load.
      +That's to say, Winverbs and Winmad are now integral components of the WinOF +stack.

      Available libibverbs test programs and their usage are listed
      below. Note that not all listed options apply to all applications

      ibv_rc_pingpong, ibv_uc_pingpong, ibv_ud_pingpong
      no args start a server and wait for connection
      -h <host>     connect to server at <host>
      -p <port>     listen on/connect to port <port> (default 18515)
      -d <dev>     use IB device <dev> (default first device found)
      -i <port>      use port <port> of IB device (default 1)
      -s <size>      size of message to exchange (default 4096)
      -m <size>     path MTU (default 1024)
      -r <dep>      number of receives to post at a time (default 500)
      -n <iters>     number of exchanges (default 1000)
      -l <sl>          service level value
      -e                 sleep on CQ events (default poll)

      ibv_send_bw, ibv_send_lat
      ibv_read_bw, ibv_read_lat
      ibv_write_bw, ibv_write_lat
      no args start a server and wait for connection
      -h <host>              connect to server at <host>
      -p <port>              listen on/connect to port <port> (default 18515)
      -d <dev>               use IB device <dev> (default first device found)
      -i <port>               use port <port> of IB device (default 1)
      -c <RC/UC/UD>  connection type RC/UC/UD (default RC)
      -m <mtu>              mtu size (256 - 4096. default for hermon is 2048)
      -s <size>               size of message to exchange (default 65536)
      -a                          Run sizes from 2 till 2^23
      -t <dep>                size of tx queue (default 300)
      -g                          send messages to multicast group (UD only)
      -r <dep>                make rx queue bigger than tx (default 600)
      -n <iters>               number of exchanges (at least 2, default 1000)
      -I <size>                max size of message to be sent in inline mode (default 400)
      -b                          measure bidirectional bandwidth (default unidirectional)
      -V                         display version number
      -e                          sleep on CQ events (default poll)
      -N                         cancel peak-bw calculation (default with peak-bw)
      -
      To verify correct WinVerbs and libibverbs installation, run ibv_devinfo. It
      should report all RDMA devices in the system, along with limited port
      attributes. Because of limitations in the WinOF stack, it is normal for it to
      list several values as unknown.

      +
      To verify correct WinVerbs and libibverbs installation, run ibstat or ibv_devinfo. It
      should report all RDMA devices in the system, along with limited port
      attributes. Because of limitations in the WinOF stack +in comparision to the Linux OFED stack, it is normal for the programs to
      list several values as unknown.

      <return-to-top>

      diff --git a/branches/winverbs/etc/makebin.bat b/branches/winverbs/etc/makebin.bat index 846a7a63..59336dd1 100644 --- a/branches/winverbs/etc/makebin.bat +++ b/branches/winverbs/etc/makebin.bat @@ -2,13 +2,13 @@ setlocal rem usage: -rem makebin src dst [win7 | wlh | wnet | wxp] DDK_version WdfCoInstaler_Ver +rem makebin src dst [win7 | wlh | wnet | wxp] DDK_ROOT WdfCoInstaler_Ver rem rem src(%1) - OpenIB src path ...\gen1\trunk rem dst(%2) - full path tp where binaries are copied, 'bin\' created here. rem OSE(%3) - (Operating System Environment) which windows version rem {win7,wxp,wlh,wnet} representing {XP, server 2008 & server 2003} -rem DDK_Version - {blank == 6001.1801} assumes \WinDDK\%4\redist\wdf +rem DDK_ROOT - {blank == assumes %SystemDrive%\WinDDK\6001.1801} rem WdfCoInstall_ver - 5 digit WdfCoInstallerVersion # (blank == 01007} rem makebin is designed to take an openIB build tree path and produce a folder @@ -36,17 +36,25 @@ goto usage :os_ok -rem if not "%4"=="" set DBG=TRUE +rem Enable tracing to indicate phase for potiential debug. set DBG=TRUE set OSE=%3 +rem setup DDK root path if /I "%4"=="" ( - set _DDK=6001.18001 + set _DDK=%systemdrive%\WinDDK\6001.18001 ) else ( set _DDK=%4 ) -set WdfCoInstaller=%systemdrive%\WinDDK\%_DDK%\redist\wdf +if not exist "%_DDK%" ( + echo Missing file %_DDK% ? + exit /B 1 +) + +set WdfCoInstaller=%_DDK%\redist\wdf +set DIFXP=%_DDK%\redist\DIFx\DIFxAPP\English\WixLib +set DPINST=%_DDK%\redist\DIFx\DPInst\EngMui if /I "%5"=="" ( set CoInstallVer=01007 @@ -111,6 +119,7 @@ for %%i in ( %CORE_DRV_FRE% ) do ( exit /B 1 ) ) + xcopy %WdfCoInstaller%\amd64\WdfCoInstaller%CoInstallVer%.dll %dest_dir% /yq xcopy %bin_dir%\ipoib.sys %2\net\amd64\ /yq @@ -715,14 +724,38 @@ if exist %1\inc ( popd ) -rem Docs & SDK items -if "%DBG%" == "TRUE" echo DBG: Docs and SDK files -if NOT exist %2\Misc ( - mkdir %2\Misc -) +rem WDK/WIX, Docs & IB SDK items +if "%DBG%" == "TRUE" echo DBG: WDK, WIx, Docs and SDK files + +if exist %2\Misc rmdir /Q/S %2\Misc +mkdir %2\Misc + copy /Y/A %1\Docs\Manual.htm %2\Misc\Manual.htm copy /Y/A %1\tests\cmtest\user\cmtest_main.c %2\Misc\cmtest.c +rem copy 'Driver Install Frameworks for Applications' files so WIX makefiles +rem are not required to know the current WDK version/path. + +for %%i in ( amd64 ia64 x86 ) do ( + mkdir %2\Misc\%%i + if ERRORLEVEL 1 ( + echo ERR on mkdir %2\Misc\DIFxAPP\%%i + exit /B 1 + ) + for %%j in ( DIFxApp.dll DIFxAppA.dll DIFxApp.wixlib ) do ( + copy /B/Y %DIFXP%\%%i\%%j %2\Misc\%%i\%%j + if ERRORLEVEL 1 ( + echo ERR on copy /B/Y %DIFXP%\%%i\%%j %2\Misc\%%i\%%j + exit /B 1 + ) + ) + copy /B/Y %DPINST%\%%i\DPInst.exe %2\Misc\%%i\DPInst.exe + if ERRORLEVEL 1 ( + echo ERR on copy /B/Y %DPINST%\%%i\DPInst.exe %2\Misc\%%i\DPInst.exe + exit /B 1 + ) +) + rem setup Checked Drivers & Symbols for signing and installation. if "%DBG%" == "TRUE" echo %3 Checked Drivers+symbols diff --git a/branches/winverbs/hw/mlx4/kernel/bus/drv/drv.c b/branches/winverbs/hw/mlx4/kernel/bus/drv/drv.c index f10980a1..f88b2d16 100644 --- a/branches/winverbs/hw/mlx4/kernel/bus/drv/drv.c +++ b/branches/winverbs/hw/mlx4/kernel/bus/drv/drv.c @@ -185,7 +185,7 @@ Routine Description: // the list locked for enumeration. The enumeration lock applies only // to enumeration, not addition or removal. // - status = create_pdo(Device, HardwareIds, DeviceDescription, SerialNo); + status = create_pdo(Device, HardwareIds, DeviceDescription, SerialNo, p_fdo->pci_dev.location); } WdfFdoUnlockStaticChildListFromIteration(Device); @@ -437,7 +437,9 @@ EvtDeviceD0Entry( // start card (needed after Hibernetion) if (PreviousState > WdfPowerDeviceD0) - __start_card( Device, p_fdo ); + status = __start_card( Device, p_fdo ); + if ( !NT_SUCCESS( status ) ) + goto err; mdev = pdev->dev; // create child device @@ -724,6 +726,8 @@ __get_resources( // pdev->ven_id = pdev->pci_cfg_space.VendorID; pdev->dev_id = pdev->pci_cfg_space.DeviceID; + pdev->sub_vendor_id = pdev->pci_cfg_space.u.type0.SubVendorID; + pdev->sub_system_id = pdev->pci_cfg_space.u.type0.SubSystemID; pdev->p_self_do = WdfDeviceWdmGetDeviceObject(p_fdo->FdoDevice); pdev->pdo = WdfDeviceWdmGetPhysicalDevice(p_fdo->FdoDevice); @@ -744,6 +748,10 @@ EvtPrepareHardware( { NTSTATUS status; PFDO_DEVICE_DATA p_fdo = FdoGetData(Device); + struct pci_dev *pdev = &p_fdo->pci_dev; + struct mlx4_dev *mdev; + WDFMEMORY memory; + WDF_OBJECT_ATTRIBUTES attributes; MLX4_ENTER(MLX4_DBG_DRV); @@ -756,6 +764,43 @@ EvtPrepareHardware( // start the card status = __start_card(Device, p_fdo ); + if( !NT_SUCCESS( status ) ) + goto err; + mdev = pdev->dev; + + // get VPD + status = pci_get_vpd( &pdev->bus_pci_ifc, + &pdev->pci_cfg_space, &pdev->vpd, &pdev->vpd_size ); + if( !NT_SUCCESS( status ) ) + { + MLX4_PRINT_EV(TRACE_LEVEL_ERROR, MLX4_DBG_DRV, + ("pci_get_vpd failed, status=0x%x\n", status)); + goto err; + } + + // get card location + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = Device; + status = WdfDeviceAllocAndQueryProperty( Device, + DevicePropertyLocationInformation, NonPagedPool, + &attributes, &memory ); + if( NT_SUCCESS( status ) ) { + UCHAR *ptr; + + // get location + ptr = WdfMemoryGetBuffer(memory, NULL); + status = RtlStringCbCopyW( (LPWSTR)p_fdo->pci_dev.location, + sizeof(p_fdo->pci_dev.location), (LPCWSTR)ptr ); + WdfObjectDelete(memory); + if( !NT_SUCCESS( status ) ) { + MLX4_PRINT_EV(TRACE_LEVEL_ERROR, MLX4_DBG_DRV, + ("Failed to move location string '%S', status=0x%x\n", ptr, status)); + } + } + else { + MLX4_PRINT_EV(TRACE_LEVEL_ERROR, MLX4_DBG_DRV, + ("WdfDeviceAllocAndQueryProperty failed, status=0x%x\n", status)); + } err: MLX4_EXIT( MLX4_DBG_DRV ); @@ -779,13 +824,26 @@ EvtReleaseHardware( ) { PFDO_DEVICE_DATA p_fdo = FdoGetData(Device); + struct pci_dev *pdev = &p_fdo->pci_dev; + int i; UNUSED_PARAM(ResourcesTranslated); MLX4_ENTER(MLX4_DBG_DRV); + if ( pdev->vpd ) { + kfree( pdev->vpd ); + pdev->vpd = NULL; + pdev->vpd_size = 0; + } __stop_card( p_fdo ); __put_resources( p_fdo ); + for (i=0; iFdoDevice = device; - if (!g.p_fdo) - g.p_fdo = p_fdo; + p_fdo->FdoDevice = device; + for (i=0; ivalid = 0; return 0; } - pPciMsixCap = (PPCI_MSIX_CAPABILITY)(((UCHAR*)p_cfg) + capOffset); + pPciMsixCap = (PPCI_MSIX_CAPABILITY)(((UCHAR*)p_cfg) + cap_offset); n_supported = MSIX_FLAGS_SUPPORTED(pPciMsixCap->Flags) + 1; p_info->num = n_supported; @@ -428,15 +428,148 @@ pci_free_msix_info_resources( memset(pMsixInfo,0,sizeof(struct msix_saved_info)); } +static NTSTATUS __read_vpd_dword( + IN BUS_INTERFACE_STANDARD *pBusIfc, + IN ULONG cap_offset, + IN ULONG offset, + OUT UCHAR *p_data + ) +{ + ULONG len; + USHORT addr = (USHORT)offset; + + /* write offset inside VPD data */ + len = pBusIfc->SetBusData( pBusIfc->Context, PCI_WHICHSPACE_CONFIG, &addr, cap_offset+2,2 ); + if ( len != 2 ) + goto err_write; + + /* wait for data to be put in the data register */ + while ( !(addr & 0x8000) ) { + len = pBusIfc->GetBusData( pBusIfc->Context, PCI_WHICHSPACE_CONFIG, &addr, cap_offset+2, 2 ); + if ( len != 2 ) + goto err_read; + } + + /* get the tag value */ + len = pBusIfc->GetBusData( pBusIfc->Context, PCI_WHICHSPACE_CONFIG, p_data, cap_offset+4, 4 ); + if ( len != 4 ) + goto err_read; + + return STATUS_SUCCESS; + +err_write: + MLX4_PRINT( TRACE_LEVEL_ERROR , MLX4_DBG_PNP , + ("Failed to write HCA config. \n" )); + return STATUS_DEVICE_NOT_READY; + +err_read: + MLX4_PRINT( TRACE_LEVEL_ERROR , MLX4_DBG_PNP , + ("Failed to read HCA config. \n" )); + return STATUS_DEVICE_NOT_READY; +} + + +#define MAX_VPD_SIZE (1 << 16) + +NTSTATUS pci_get_vpd( + IN BUS_INTERFACE_STANDARD *pBusIfc, + IN PCI_COMMON_CONFIG* const pConfig, + OUT UCHAR* *p_vpd, + IN OUT int* p_vpd_size + ) +{ + PCI_VPD_CAPABILITY *pPciVpdCap; + ULONG cap_offset; + NTSTATUS status = STATUS_SUCCESS; + int vpd_size = 0; + UCHAR *vpd = NULL; + + *p_vpd = NULL; + *p_vpd_size = 0; + + cap_offset = __find_capability( pConfig, PCI_CAPABILITY_ID_VPD ); + if( cap_offset ) { + ULONG offset; + pPciVpdCap = (PCI_VPD_CAPABILITY*)(((UCHAR*)pConfig) + cap_offset); + + /* allocate temp buffer */ + vpd = kmalloc( MAX_VPD_SIZE, GFP_KERNEL); + if ( !vpd ) { + MLX4_PRINT( TRACE_LEVEL_ERROR , MLX4_DBG_PNP , + ("Failed to allocate VPD buffer of size %d.\n", MAX_VPD_SIZE)); + return STATUS_UNSUCCESSFUL; + } + + /* read VPD */ + for ( offset = 0; offset < MAX_VPD_SIZE; offset += 0x4 ) { + status = __read_vpd_dword( pBusIfc, cap_offset, + offset, vpd + offset); + if( !NT_SUCCESS( status ) ) { + kfree( vpd ); + return STATUS_UNSUCCESSFUL; + } + } + /* find the size */ + for ( offset = 0; offset < MAX_VPD_SIZE; ) { + ULONG size; + if ( vpd[offset] == 0x78 ) { + vpd_size = offset + 1; + break; + } + if ( vpd[offset] & 0x80 ) { /* Large Resource Type */ + size = *(PUSHORT)(&vpd[offset+1]); + offset += size + 3; + } + else { /* Small Resource Type */ + size = vpd[offset] & 7; + offset += size + 1; + } + } + /* shorten the VPD array */ + if ( offset >= MAX_VPD_SIZE ) { + /* we didn't found VPD end. We'll keep it all */ + *p_vpd = vpd; + *p_vpd_size = MAX_VPD_SIZE; + } + else { + /* allocate VPD */ + if ( vpd_size ) { + *p_vpd = kmalloc( vpd_size, GFP_KERNEL); + if ( !*p_vpd ) { + *p_vpd = vpd; + *p_vpd_size = MAX_VPD_SIZE; + } + else { + memcpy( *p_vpd, vpd, vpd_size ); + *p_vpd_size = vpd_size; + kfree( vpd ); + } + } + else { + MLX4_PRINT( TRACE_LEVEL_ERROR , MLX4_DBG_PNP , + ("VPD starts from end tag.\n")); + kfree( vpd ); + status = STATUS_UNSUCCESSFUL; + } + } + } + else { + MLX4_PRINT( TRACE_LEVEL_ERROR , MLX4_DBG_PNP ,("Failed to find VPD capability.\n")); + status = STATUS_UNSUCCESSFUL; + } + + return status; +} + /* * Reads and saves the PCI configuration of the device accessible * through the provided bus interface. Does not read registers 22 or 23 - * as directed in PRM , Appendix A. Software Reset. + * as directed in Tavor PRM 1.0.1, Appendix A. InfiniHost Software Reset. */ NTSTATUS pci_save_config( IN BUS_INTERFACE_STANDARD *pBusIfc, - OUT PCI_COMMON_CONFIG* const pConfig) + OUT PCI_COMMON_CONFIG* const pConfig ) { ULONG len; UINT32 *pBuf; @@ -480,7 +613,7 @@ pci_get_msi_info( OUT PCI_COMMON_CONFIG * p_cfg, OUT uplink_info_t * p_uplink_info ) { - ULONG capOffset; + ULONG cap_offset; NTSTATUS status; BUS_INTERFACE_STANDARD *pBusIfc = &pdev->bus_pci_ifc; @@ -495,15 +628,15 @@ pci_get_msi_info( // PCI MSI-X Capability memset( &p_uplink_info->x, 0, sizeof(p_uplink_info->x) ); - capOffset = __find_capability( p_cfg, PCI_CAPABILITY_ID_MSIX ); - if( capOffset ) { + cap_offset = __find_capability( p_cfg, PCI_CAPABILITY_ID_MSIX ); + if( cap_offset ) { PVOID ka; ULONG sz; PHYSICAL_ADDRESS pa; PPCI_MSIX_VECTOR p_vector; PPCI_MSIX_PENDING p_pend; ULONG granted_mask = 0; - PPCI_MSIX_CAPABILITY pPciMsixCap = (PPCI_MSIX_CAPABILITY)(((UCHAR*)p_cfg) + capOffset); + PPCI_MSIX_CAPABILITY pPciMsixCap = (PPCI_MSIX_CAPABILITY)(((UCHAR*)p_cfg) + cap_offset); USHORT flags = pPciMsixCap->Flags; ULONG table_bir = MSIX_OFFSET_BIR(pPciMsixCap->Table_Offset); ULONG pend_bir = MSIX_OFFSET_BIR(pPciMsixCap->PBA_Offset); @@ -732,16 +865,16 @@ pci_get_uplink_info( IN PCI_COMMON_CONFIG * p_cfg, OUT uplink_info_t * p_uplink_info ) { - ULONG capOffset; + ULONG cap_offset; PCI_PCIX_CAPABILITY *pPciXCap; PCI_PCIEXP_CAPABILITY *pPciExpCap; MLX4_ENTER( MLX4_DBG_PNP ); // PCIX Capability - capOffset = __find_capability( p_cfg, PCI_CAPABILITY_ID_PCIX ); - if( capOffset ) { - pPciXCap = (PCI_PCIX_CAPABILITY*)(((UCHAR*)p_cfg) + capOffset); + cap_offset = __find_capability( p_cfg, PCI_CAPABILITY_ID_PCIX ); + if( cap_offset ) { + pPciXCap = (PCI_PCIX_CAPABILITY*)(((UCHAR*)p_cfg) + cap_offset); p_uplink_info->bus_type = UPLINK_BUS_PCIX; if (pPciXCap->Status & (1 << 17)) @@ -750,9 +883,9 @@ pci_get_uplink_info( } // PCI Express Capability - capOffset = __find_capability( p_cfg, PCI_CAPABILITY_ID_PCI_EXPRESS ); - if( capOffset ) { - pPciExpCap = (PCI_PCIEXP_CAPABILITY*)(((UCHAR*)p_cfg) + capOffset); + cap_offset = __find_capability( p_cfg, PCI_CAPABILITY_ID_PCI_EXPRESS ); + if( cap_offset ) { + pPciExpCap = (PCI_PCIEXP_CAPABILITY*)(((UCHAR*)p_cfg) + cap_offset); p_uplink_info->bus_type = UPLINK_BUS_PCIE; if ((pPciExpCap->LinkStatus & 15) == 1) diff --git a/branches/winverbs/hw/mlx4/kernel/bus/drv/pdo.c b/branches/winverbs/hw/mlx4/kernel/bus/drv/pdo.c index 48ec280d..2144404d 100644 --- a/branches/winverbs/hw/mlx4/kernel/bus/drv/pdo.c +++ b/branches/winverbs/hw/mlx4/kernel/bus/drv/pdo.c @@ -46,7 +46,8 @@ create_pdo( __in WDFDEVICE Device, __in PWCHAR HardwareIds, __in PWCHAR DeviceDescription, - __in ULONG SerialNo + __in ULONG SerialNo, + __in PWCHAR Location ) /*++ @@ -71,9 +72,9 @@ Return Value: WDF_DEVICE_PNP_CAPABILITIES pnpCaps; WDF_DEVICE_POWER_CAPABILITIES powerCaps; UNICODE_STRING compatId; - DECLARE_CONST_UNICODE_STRING(deviceLocation, L"MLX4 Bus 0"); UNICODE_STRING deviceId; DECLARE_UNICODE_STRING_SIZE(buffer, MAX_ID_LEN); + DECLARE_UNICODE_STRING_SIZE(deviceLocation, MAX_ID_LEN); MLX4_PRINT(TRACE_LEVEL_INFORMATION, MLX4_DBG_DRV, ("Entered CreatePdo\n")); @@ -152,6 +153,11 @@ Return Value: // The driver can specify the driver's default locale by calling // WdfPdoInitSetDefaultLocale. // + status = RtlUnicodeStringPrintf(&deviceLocation, L"MLX4 %s", Location+4); + if (!NT_SUCCESS(status)) { + goto Cleanup; + } + status = WdfPdoInitAddDeviceText(pDeviceInit, &buffer, &deviceLocation, 0x409); if (!NT_SUCCESS(status)) { diff --git a/branches/winverbs/hw/mlx4/kernel/bus/ib/cq.c b/branches/winverbs/hw/mlx4/kernel/bus/ib/cq.c index 8b05a8a9..77f56d3f 100644 --- a/branches/winverbs/hw/mlx4/kernel/bus/ib/cq.c +++ b/branches/winverbs/hw/mlx4/kernel/bus/ib/cq.c @@ -128,8 +128,6 @@ struct ib_cq *mlx4_ib_create_cq(struct ib_device *ibdev, int entries, int vector int buf_size; int err; - UNUSED_PARAM(vector); - if (mlx4_is_barred(ibdev->dma_device)) return ERR_PTR(-EFAULT); @@ -212,7 +210,7 @@ struct ib_cq *mlx4_ib_create_cq(struct ib_device *ibdev, int entries, int vector } err = mlx4_cq_alloc(dev->dev, entries, &cq->buf.mtt, uar, - cq->db.dma.da, &cq->mcq, 0, 0); + cq->db.dma.da, &cq->mcq, vector, 0); if (err) goto err_dbmap; diff --git a/branches/winverbs/hw/mlx4/kernel/bus/net/mlx4.h b/branches/winverbs/hw/mlx4/kernel/bus/net/mlx4.h index 0409ba09..d3f74cbd 100644 --- a/branches/winverbs/hw/mlx4/kernel/bus/net/mlx4.h +++ b/branches/winverbs/hw/mlx4/kernel/bus/net/mlx4.h @@ -65,6 +65,7 @@ enum mlx4_port_type { MLX4_PORT_TYPE_ETH = 1 << 1, }; +#define MAX_HCA_CARDS 8 #pragma warning(disable:4201) // nameless struct/union typedef struct _GLOBALS { @@ -86,7 +87,7 @@ typedef struct _GLOBALS { int mod_interrupt_from_first; int mod_affinity; - PFDO_DEVICE_DATA p_fdo; // for debug purposes + PFDO_DEVICE_DATA p_fdo[MAX_HCA_CARDS]; // for debug purposes } GLOBALS; #pragma warning(default:4201) // nameless struct/union diff --git a/branches/winverbs/hw/mlx4/kernel/inc/l2w.h b/branches/winverbs/hw/mlx4/kernel/inc/l2w.h index 9f8b5640..57f8b347 100644 --- a/branches/winverbs/hw/mlx4/kernel/inc/l2w.h +++ b/branches/winverbs/hw/mlx4/kernel/inc/l2w.h @@ -170,6 +170,8 @@ struct pci_dev // driver: various objects and info USHORT ven_id; USHORT dev_id; + USHORT sub_vendor_id; + USHORT sub_system_id; DMA_ADAPTER * p_dma_adapter; /* HCA adapter object */ DEVICE_OBJECT * p_self_do; /* mlx4_bus's FDO */ DEVICE_OBJECT * pdo; /* mlx4_bus's PDO */ @@ -178,6 +180,9 @@ struct pci_dev // mlx4_net: various objects and info struct mlx4_dev * dev; volatile long dpc_lock; + PUCHAR vpd; + int vpd_size; + WCHAR location[36]; /* bus+func+dev */ #ifdef USE_WDM_INTERRUPTS PKINTERRUPT int_obj; /* HCA interrupt object */ KSPIN_LOCK isr_lock; /* lock for the ISR */ diff --git a/branches/winverbs/inc/kernel/complib/cl_timer_osd.h b/branches/winverbs/inc/kernel/complib/cl_timer_osd.h index bd3af5e8..4cb99b08 100644 --- a/branches/winverbs/inc/kernel/complib/cl_timer_osd.h +++ b/branches/winverbs/inc/kernel/complib/cl_timer_osd.h @@ -68,7 +68,7 @@ cl_get_time_stamp( void ) LARGE_INTEGER tick_count, frequency; tick_count = KeQueryPerformanceCounter( &frequency ); - return( tick_count.QuadPart / (frequency.QuadPart / SEC_TO_MICRO) ); + return( SEC_TO_MICRO * tick_count.QuadPart / frequency.QuadPart ); } CL_INLINE uint32_t CL_API @@ -99,4 +99,4 @@ cl_get_tick_freq( void ) } /* extern "C" */ #endif -#endif // _CL_TIMER_OSD_H_ \ No newline at end of file +#endif // _CL_TIMER_OSD_H_ diff --git a/branches/winverbs/ulp/dapl2/test/dapltest/scripts/dt-cli.bat b/branches/winverbs/ulp/dapl2/test/dapltest/scripts/dt-cli.bat index 9d42311b..9cbb2021 100644 --- a/branches/winverbs/ulp/dapl2/test/dapltest/scripts/dt-cli.bat +++ b/branches/winverbs/ulp/dapl2/test/dapltest/scripts/dt-cli.bat @@ -65,6 +65,8 @@ rem set DAT_DBG_TYPE=0x1 rem set DAT_DBG_LEVEL=1 ) +if "%4" == "-Q" ( set QUIET=1 ) else ( set QUIET=0 ) + rem %DT% -T T -V -t 2 -w 2 -i 1000111 -s %S% -D %D% rem client RW 4096 1 server RW 2048 4 rem client RR 1024 2 server RR 2048 2 @@ -84,61 +86,47 @@ if "%T%" == "bench" ( ) if "%T%" == "conn" ( -rem Connectivity test - client sends one buffer with one 4KB segments, one time. -rem add '-d' for debug output. + rem Connectivity test - client sends one buffer with one 4KB segments, one time. + rem add '-d' for debug output. echo Simple Connectivity test - set STIME=!DATE! !TIME! - %DT% -T T -s %S% -D %D% -i 1 -t 1 -w 1 client SR 4096 server SR 4096 - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T T -s %S% -D %D% -i 1 -t 1 -w 1 client SR 4096 server SR 4096 + goto xcmd ) if "%T%" == "trans" ( echo %T%: Transaction test - 8192 iterations, 1 thread, SR 4KB buffers - set STIME=!DATE! !TIME! - %DT% -T T -s %S% -D %D% -i 8192 -t 1 -w 1 client SR 4096 server SR 4096 - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T T -s %S% -D %D% -i 8192 -t 1 -w 1 client SR 4096 server SR 4096 + goto xcmd ) if "%T%" == "transm" ( echo %T%: Multiple RW, RR, SR transactions, 4096 iterations - set STIME=!DATE! !TIME! - %DT% -T T -P -t 1 -w 1 -i 4096 -s %S% -D %D% client RW 4096 1 server RW 2048 4 server RR 1024 2 client RR 2048 2 client SR 1024 3 -f server SR 256 3 -f - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T T -P -t 1 -w 1 -i 4096 -s %S% -D %D% client RW 4096 1 server RW 2048 4 server RR 1024 2 client RR 2048 2 client SR 1024 3 -f server SR 256 3 -f + goto xcmd ) if "%T%" == "transt" ( echo %T%: Threads[4] Transaction test - 4096 iterations, 1 thread, SR 4KB buffers - set STIME=!DATE! !TIME! - %DT% -T T -s %S% -D %D% -i 4096 -t 4 -w 1 client SR 8192 3 server SR 8192 3 - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T T -s %S% -D %D% -i 4096 -t 4 -w 1 client SR 8192 3 server SR 8192 3 + goto xcmd ) if "%T%" == "transme" ( echo %T%: 1 Thread Endpoints[4] transactions [RW, RR, SR], 4096 iterations - set STIME=!DATE! !TIME! - %DT% -T T -P -t 1 -w 4 -i 4096 -s %S% -D %D% client RW 4096 1 server RW 2048 4 server RR 1024 2 client RR 2048 2 client SR 1024 3 -f server SR 256 3 -f - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T T -P -t 1 -w 4 -i 4096 -s %S% -D %D% client RW 4096 1 server RW 2048 4 server RR 1024 2 client RR 2048 2 client SR 1024 3 -f server SR 256 3 -f + goto xcmd ) if "%T%" == "transmet" ( echo %T%: Threads[2] Endpoints[4] transactions[RW, RR, SR], 4096 iterations - set STIME=!DATE! !TIME! - %DT% -T T -P -t 2 -w 4 -i 4096 -s %S% -D %D% client RW 4096 1 server RW 2048 4 server RR 1024 2 client RR 2048 2 client SR 1024 3 -f server SR 256 3 -f - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T T -P -t 2 -w 4 -i 4096 -s %S% -D %D% client RW 4096 1 server RW 2048 4 server RR 1024 2 client RR 2048 2 client SR 1024 3 -f server SR 256 3 -f + goto xcmd ) if "%T%" == "transmete" ( echo %T%: Threads[4] Endpoints[4] transactions[RW, RR, SR], 8192 iterations - set STIME=!DATE! !TIME! - %DT% -T T -P -t 2 -w 4 -i 8192 -s %S% -D %D% client RW 4096 1 server RW 2048 4 server RR 1024 2 client RR 2048 2 client SR 1024 3 -f server SR 256 3 -f - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T T -P -t 2 -w 4 -i 8192 -s %S% -D %D% client RW 4096 1 server RW 2048 4 server RR 1024 2 client RR 2048 2 client SR 1024 3 -f server SR 256 3 -f + goto xcmd ) if "%T%" == "EPA" ( @@ -160,76 +148,58 @@ if "%T%" == "EP" ( set TH=4 set EP=3 echo %T%: Multi: Threads[!TH!] endpoints[!EP!] Send/Recv test - 4096 iterations, 3 8K segs - set STIME=!DATE! !TIME! - %DT% -T T -s %S% -D %D% -i 4096 -t !TH! -w !EP! client SR 8192 3 server SR 8192 3 - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T T -s %S% -D %D% -i 4096 -t !TH! -w !EP! client SR 8192 3 server SR 8192 3 + goto xcmd ) if "%T%" == "threads" ( echo %T%: Multi Threaded[6] Send/Recv test - 4096 iterations, 3 8K segs - set STIME=!DATE! !TIME! - %DT% -T T -s %S% -D %D% -i 4096 -t 6 -w 1 client SR 8192 3 server SR 8192 3 - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T T -s %S% -D %D% -i 4096 -t 6 -w 1 client SR 8192 3 server SR 8192 3 + goto xcmd ) if "%T%" == "threadsm" ( set TH=5 set EP=3 echo %T%: Multi: Threads[!TH!] endpoints[!EP!] Send/Recv test - 4096 iterations, 3 8K segs - set STIME=!DATE! !TIME! - %DT% -T T -s %S% -D %D% -i 4096 -t !TH! -w !EP! client SR 8192 3 server SR 8192 3 - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T T -s %S% -D %D% -i 4096 -t !TH! -w !EP! client SR 8192 3 server SR 8192 3 + goto xcmd ) if "%T%" == "perf" ( rem echo Performance test - set STIME=!DATE! !TIME! - %DT% -T P %DBG% -s %S% -D %D% -i 2048 RW 4096 2 - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T P %DBG% -s %S% -D %D% -i 2048 RW 4096 2 + goto xcmd ) if "%T%" == "rdma-read" ( echo %T% 4 32K segs - set STIME=!DATE! !TIME! - %DT% -T P -s %S% -D %D% -i 4096 RR 32768 4 - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T P -s %S% -D %D% -i 4096 RR 32768 4 + goto xcmd ) if "%T%" == "rdma-write" ( echo %T% 4 32K segs - set STIME=!DATE! !TIME! - %DT% -T P -s %S% -D %D% -i 4096 RW 32768 4 - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T P -s %S% -D %D% -i 4096 RW 32768 4 + goto xcmd ) if "%T%" == "bw" ( echo bandwidth 4096 iterations of 2 65K mesgs - set STIME=!DATE! !TIME! - %DT% -T P -s %S% -D %D% -i 4096 -p 16 -m p RW 65536 2 - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T P -s %S% -D %D% -i 4096 -p 16 -m p RW 65536 2 + goto xcmd ) if "%T%" == "latb" ( echo latency test - block for completion events - set STIME=!DATE! !TIME! - %DT% -T P -s %S% -D %D% -i 8192 -p 1 -m b RW 4 1 - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T P -s %S% -D %D% -i 8192 -p 1 -m b RW 4 1 + goto xcmd ) if "%T%" == "latp" ( echo latency test - poll completion events - set STIME=!DATE! !TIME! - %DT% -T P -s %S% -D %D% -i 8192 -p 1 -m p RW 4 1 - set ETIME=!DATE! !TIME! - goto xit + set CMD=%DT% -T P -s %S% -D %D% -i 8192 -p 1 -m p RW 4 1 + goto xcmd ) if "%T%" == "lim" ( @@ -338,10 +308,8 @@ if "%T%" == "interop" ( ) if "%T%" == "stop" ( - set STIME=!DATE! !TIME! - %DT% -T Q -s %S% -D %D% - set ETIME=!DATE! !TIME! - goto xit + %DT% -T Q -s %S% -D %D% > nul + goto rxt ) :usage @@ -379,6 +347,18 @@ echo regression {loopCnt,default=%LPS%} - regression + stress. echo interop {loopCnt,default=%LPS%} - 2007 OFA interoperability event tests. goto rxt +rem Execute the single daplest Command (CMD), observe -Q switch +:xcmd + set STIME=!DATE! !TIME! + if %QUIET% EQU 1 ( + %CMD% > nul + ) else ( + %CMD% + ) + set ETIME=!DATE! !TIME! + + rem fall thru... + :xit if %ERRORLEVEL% EQU 0 ( diff --git a/branches/winverbs/ulp/ipoib/kernel6/SOURCES b/branches/winverbs/ulp/ipoib/kernel6/SOURCES new file mode 100644 index 00000000..a528904b --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/SOURCES @@ -0,0 +1,59 @@ +TARGETNAME=ipoib +TARGETPATH=..\..\..\bin\kernel\obj$(BUILD_ALT_DIR) +TARGETTYPE=DRIVER + +!if $(_NT_TOOLS_VERSION) != 0x700 +# WDK build only - transform .inx --> .inf adding date & version stamp. +# see .\makefile.inc +INF_NAME=netipoib +INF_TARGET=..\..\..\bin\kernel\$(O)\$(INF_NAME).inf +NTTARGETFILES=$(INF_TARGET) +!endif + +!if $(FREEBUILD) +ENABLE_EVENT_TRACING=1 +!else +#ENABLE_EVENT_TRACING=1 +!endif + + +SOURCES= ipoib_log.mc \ + ipoib.rc \ + ipoib_driver.c \ + ipoib_adapter.c \ + ipoib_endpoint.c \ + ipoib_port.c \ + ipoib_ibat.c \ +# ipoib_cm.c \ + ipoib_xfr_mgr.c + +INCLUDES=..;..\..\..\inc;..\..\..\inc\kernel; + +C_DEFINES=$(C_DEFINES) -DNDIS_MINIPORT_DRIVER -DNDIS_WDM=1 \ + -DDEPRECATE_DDK_FUNCTIONS -DNDIS60_MINIPORT=1 -DNEED_CL_OBJ -DBINARY_COMPATIBLE=0 -DVER_FILEREV=42 + +TARGETLIBS= \ + $(TARGETPATH)\*\complib.lib \ + $(DDK_LIB_PATH)\ndis.lib \ + $(DDK_LIB_PATH)\ntstrsafe.lib \ + $(DDK_LIB_PATH)\strsafe.lib + +!if !defined(DDK_TARGET_OS) || "$(DDK_TARGET_OS)"=="Win2K" +# +# The driver is built in the Win2K build environment +# - use the library version of safe strings +# +#TARGETLIBS= $(TARGETLIBS) $(DDK_LIB_PATH)\ntstrsafe.lib +!endif + +!IFDEF ENABLE_EVENT_TRACING + +C_DEFINES = $(C_DEFINES) -DEVENT_TRACING + +RUN_WPP = $(SOURCES) -km -ext: .c .h .C .H \ + -scan:ipoib_debug.h \ + -func:IPOIB_PRINT(LEVEL,FLAGS,(MSG,...)) \ + -func:IPOIB_PRINT_EXIT(LEVEL,FLAGS,(MSG,...)) +!ENDIF + +MSC_WARNING_LEVEL= /W4 diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib.cdf b/branches/winverbs/ulp/ipoib/kernel6/ipoib.cdf new file mode 100644 index 00000000..eb21da98 --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib.cdf @@ -0,0 +1,13 @@ +[CatalogHeader] +Name=ipoib.cat +PublicVersion=0x0000001 +EncodingType=0x00010001 +CATATTR1=0x10010001:OSAttr:2:6.0 +[CatalogFiles] +netipoib.inf=netipoib.inf +ipoib.sys=ipoib.sys +ibwsd.dll=ibwsd.dll +ibwsd32.dll=ibwsd32.dll +ibndprov.dll=ibndprov.dll +ibndprov32.dll=ibndprov32.dll +ndinstall.exe=ndinstall.exe diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib.rc b/branches/winverbs/ulp/ipoib/kernel6/ipoib.rc new file mode 100644 index 00000000..330f19e7 --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib.rc @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib.rc 1611 2006-08-20 14:48:55Z sleybo $ + */ + + +#include + +#define VER_FILETYPE VFT_DRV +#define VER_FILESUBTYPE VFT2_UNKNOWN + +#ifdef _DEBUG_ +#define VER_FILEDESCRIPTION_STR "IP over InfiniBand NDIS Miniport (Debug)" +#else +#define VER_FILEDESCRIPTION_STR "IP over InfiniBand NDIS Miniport" +#endif + +#define VER_INTERNALNAME_STR "ipoib.sys" +#define VER_ORIGINALFILENAME_STR "ipoib.sys" + +#include +#include "ipoib_log.rc" diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib32-xp.cdf b/branches/winverbs/ulp/ipoib/kernel6/ipoib32-xp.cdf new file mode 100644 index 00000000..faf8ea6f --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib32-xp.cdf @@ -0,0 +1,10 @@ +[CatalogHeader] +Name=ipoib.cat +PublicVersion=0x0000001 +EncodingType=0x00010001 +CATATTR1=0x10010001:OSAttr:2:6.0 +[CatalogFiles] +netipoib.inf=netipoib.inf +ipoib.sys=ipoib.sys +ibndprov.dll=ibndprov.dll +ndinstall.exe=ndinstall.exe diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib32.cdf b/branches/winverbs/ulp/ipoib/kernel6/ipoib32.cdf new file mode 100644 index 00000000..50225ba6 --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib32.cdf @@ -0,0 +1,11 @@ +[CatalogHeader] +Name=ipoib.cat +PublicVersion=0x0000001 +EncodingType=0x00010001 +CATATTR1=0x10010001:OSAttr:2:6.0 +[CatalogFiles] +netipoib.inf=netipoib.inf +ipoib.sys=ipoib.sys +ibwsd.dll=ibwsd.dll +ibndprov.dll=ibndprov.dll +ndinstall.exe=ndinstall.exe diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib_adapter.c b/branches/winverbs/ulp/ipoib/kernel6/ipoib_adapter.c new file mode 100644 index 00000000..78eff263 --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib_adapter.c @@ -0,0 +1,1632 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_adapter.c 4226 2009-04-06 06:01:03Z xalex $ + */ + + + +#include "ipoib_adapter.h" +#include "ipoib_port.h" +#include "ipoib_driver.h" +#include "ipoib_debug.h" + +#if defined(EVENT_TRACING) +#ifdef offsetof +#undef offsetof +#endif +#include "ipoib_adapter.tmh" +#endif + + +#define ITEM_POOL_START 16 +#define ITEM_POOL_GROW 16 + + +/* IB Link speeds in 100bps */ +#define ONE_X_IN_100BPS 25000000 +#define FOUR_X_IN_100BPS 100000000 +#define TWELVE_X_IN_100BPS 300000000 + + +/* Declarations */ +static void +adapter_construct( + IN ipoib_adapter_t* const p_adapter ); + + +static ib_api_status_t +adapter_init( + IN ipoib_adapter_t* const p_adapter ); + + +static void +__adapter_destroying( + IN cl_obj_t* const p_obj ); + + +static void +__adapter_free( + IN cl_obj_t* const p_obj ); + + +static ib_api_status_t +__ipoib_pnp_reg( + IN ipoib_adapter_t* const p_adapter, + IN ib_pnp_class_t flags ); + + +static void +__ipoib_pnp_dereg( + IN void* context ); + + +static void +__ipoib_adapter_reset( + IN void* context); + + +static ib_api_status_t +__ipoib_pnp_cb( + IN ib_pnp_rec_t *p_pnp_rec ); + + +void +ipoib_join_mcast( + IN ipoib_adapter_t* const p_adapter ); + + +/* Leaves all mcast groups when port goes down. */ +static void +ipoib_clear_mcast( + IN ipoib_port_t* const p_port ); + +NDIS_STATUS +ipoib_get_adapter_guids( + IN NDIS_HANDLE* const h_adapter, + IN OUT ipoib_adapter_t *p_adapter ); + +NDIS_STATUS +ipoib_get_adapter_params( + IN NDIS_HANDLE* const wrapper_config_context, + IN OUT ipoib_adapter_t *p_adapter, + OUT PUCHAR *p_mac, + OUT UINT *p_len); + + +/* Implementation */ +ib_api_status_t +ipoib_create_adapter( + IN NDIS_HANDLE wrapper_config_context, + IN void* const h_adapter, + OUT ipoib_adapter_t** const pp_adapter ) +{ + ipoib_adapter_t *p_adapter; + ib_api_status_t status; + cl_status_t cl_status; + PUCHAR mac; + UINT len; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_adapter = cl_zalloc( sizeof(ipoib_adapter_t) ); + if( !p_adapter ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate ipoib_adapter_t (%d bytes)", + sizeof(ipoib_adapter_t)) ); + return IB_INSUFFICIENT_MEMORY; + } + + adapter_construct( p_adapter ); + + p_adapter->h_adapter = h_adapter; + + p_adapter->p_ifc = cl_zalloc( sizeof(ib_al_ifc_t) ); + if( !p_adapter->p_ifc ) + { + __adapter_free( &p_adapter->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_create_adapter failed to alloc ipoib_ifc_t %d bytes\n", + sizeof(ib_al_ifc_t)) ); + return IB_INSUFFICIENT_MEMORY; + } + + /* Get the CA and port GUID from the bus driver. */ + status = ipoib_get_adapter_guids( h_adapter, p_adapter ); + if( status != NDIS_STATUS_SUCCESS ) + { + __adapter_free( &p_adapter->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_get_adapter_guids returned 0x%.8X.\n", status) ); + return status; + } + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Port %016I64x (CA %016I64x port %d) initializing\n", + p_adapter->guids.port_guid.guid, p_adapter->guids.ca_guid, + p_adapter->guids.port_num) ); + + cl_status = cl_obj_init( &p_adapter->obj, CL_DESTROY_SYNC, + __adapter_destroying, NULL, __adapter_free ); + if( cl_status != CL_SUCCESS ) + { + __adapter_free( &p_adapter->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_obj_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + /* Read configuration parameters. */ + status = ipoib_get_adapter_params( wrapper_config_context, + p_adapter , &mac, &len); + if( status != NDIS_STATUS_SUCCESS ) + { + cl_obj_destroy( &p_adapter->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_get_adapter_params returned 0x%.8x.\n", status) ); + return status; + } + + status = adapter_init( p_adapter ); + if( status != IB_SUCCESS ) + { + cl_obj_destroy( &p_adapter->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("adapter_init returned %s.\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + ETH_COPY_NETWORK_ADDRESS( p_adapter->params.conf_mac.addr, p_adapter->mac.addr ); + /* If there is a NetworkAddress override in registry, use it */ + if( (status == NDIS_STATUS_SUCCESS) && (len == HW_ADDR_LEN) ) + { + if( ETH_IS_MULTICAST(mac) || ETH_IS_BROADCAST(mac) || + !ETH_IS_LOCALLY_ADMINISTERED(mac) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_INIT, + ("Overriding NetworkAddress is invalid - " + "%02x-%02x-%02x-%02x-%02x-%02x\n", + mac[0], mac[1], mac[2], + mac[3], mac[4], mac[5]) ); + } + else + { + ETH_COPY_NETWORK_ADDRESS( p_adapter->params.conf_mac.addr, mac ); + } + } + + *pp_adapter = p_adapter; + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +ib_api_status_t +ipoib_start_adapter( + IN ipoib_adapter_t* const p_adapter ) +{ + ib_api_status_t status; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + status = __ipoib_pnp_reg( p_adapter, + IB_PNP_FLAG_REG_SYNC | IB_PNP_FLAG_REG_COMPLETE ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +void +ipoib_destroy_adapter( + IN ipoib_adapter_t* const p_adapter ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( p_adapter ); + + /* + * Flag the adapter as being removed. We use the IB_PNP_PORT_REMOVE state + * for this purpose. Note that we protect this state change with both the + * mutex and the lock. The mutex provides synchronization as a whole + * between destruction and AL callbacks (PnP, Query, Destruction). + * The lock provides protection + */ + KeWaitForMutexObject( + &p_adapter->mutex, Executive, KernelMode, FALSE, NULL ); + cl_obj_lock( &p_adapter->obj ); + p_adapter->state = IB_PNP_PORT_REMOVE; + + /* + * Clear the pointer to the port object since the object destruction + * will cascade to child objects. This prevents potential duplicate + * destruction (or worse, stale pointer usage). + */ + p_adapter->p_port = NULL; + + cl_obj_unlock( &p_adapter->obj ); + + KeReleaseMutex( &p_adapter->mutex, FALSE ); + + cl_obj_destroy( &p_adapter->obj ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +adapter_construct( + IN ipoib_adapter_t* const p_adapter ) +{ + cl_obj_construct( &p_adapter->obj, IPOIB_OBJ_INSTANCE ); + cl_spinlock_construct( &p_adapter->send_stat_lock ); + cl_spinlock_construct( &p_adapter->recv_stat_lock ); + cl_qpool_construct( &p_adapter->item_pool ); + KeInitializeMutex( &p_adapter->mutex, 0 ); + + cl_thread_construct(&p_adapter->destroy_thread); + + cl_vector_construct( &p_adapter->ip_vector ); + + cl_perf_construct( &p_adapter->perf ); + + p_adapter->state = IB_PNP_PORT_ADD; + p_adapter->port_rate = FOUR_X_IN_100BPS; +} + + +static ib_api_status_t +adapter_init( + IN ipoib_adapter_t* const p_adapter ) +{ + cl_status_t cl_status; + ib_api_status_t status; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + cl_status = cl_perf_init( &p_adapter->perf, MaxPerf ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_perf_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + cl_status = cl_spinlock_init( &p_adapter->send_stat_lock ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_spinlock_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + cl_status = cl_spinlock_init( &p_adapter->recv_stat_lock ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_spinlock_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + cl_status = cl_qpool_init( &p_adapter->item_pool, ITEM_POOL_START, 0, + ITEM_POOL_GROW, sizeof(cl_pool_obj_t), NULL, NULL, NULL ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_qpool_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + + /* We manually manage the size and capacity of the vector. */ + cl_status = cl_vector_init( &p_adapter->ip_vector, 0, + 0, sizeof(net_address_item_t), NULL, NULL, p_adapter ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_vector_init for ip_vector returned %#x\n", + cl_status) ); + return IB_ERROR; + } + + /* Validate the port GUID and generate the MAC address. */ + status = + ipoib_mac_from_guid( p_adapter->guids.port_guid.guid, p_adapter->params.guid_mask, &p_adapter->mac); + if( status != IB_SUCCESS ) + { + if( status == IB_INVALID_GUID_MASK ) + { + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_ERROR, + ("Invalid GUID mask received, rejecting it") ); + ipoib_create_log(p_adapter->h_adapter, GUID_MASK_LOG_INDEX, EVENT_IPOIB_WRONG_PARAMETER_WRN); + } + + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_mac_from_guid returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Open AL. */ + status = p_adapter->p_ifc->open_al( &p_adapter->h_al ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_open_al returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +static ib_api_status_t +__ipoib_pnp_reg( + IN ipoib_adapter_t* const p_adapter, + IN ib_pnp_class_t flags ) +{ + ib_api_status_t status; + ib_pnp_req_t pnp_req; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( !p_adapter->h_pnp ); + CL_ASSERT( !p_adapter->registering ); + + p_adapter->registering = TRUE; + + /* Register for PNP events. */ + cl_memclr( &pnp_req, sizeof(pnp_req) ); + pnp_req.pnp_class = IB_PNP_PORT | flags; + /* + * Context is the cl_obj of the adapter to allow passing cl_obj_deref + * to ib_dereg_pnp. + */ + pnp_req.pnp_context = &p_adapter->obj; + pnp_req.pfn_pnp_cb = __ipoib_pnp_cb; + status = p_adapter->p_ifc->reg_pnp( p_adapter->h_al, &pnp_req, &p_adapter->h_pnp ); + if( status != IB_SUCCESS ) + { + p_adapter->registering = FALSE; + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_reg_pnp returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + /* + * Reference the adapter on behalf of the PNP registration. + * This allows the destruction to block until the PNP deregistration + * completes. + */ + cl_obj_ref( &p_adapter->obj ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +static void +__adapter_destroying( + IN cl_obj_t* const p_obj ) +{ + ipoib_adapter_t *p_adapter; + KLOCK_QUEUE_HANDLE hdl; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_adapter = PARENT_STRUCT( p_obj, ipoib_adapter_t, obj ); + + /* + * The adapter's object will be dereferenced when the deregistration + * completes. No need to lock here since all PnP related API calls + * are driven by NDIS (via the Init/Reset/Destroy paths). + */ + if( p_adapter->h_pnp ) + { + p_adapter->p_ifc->dereg_pnp( p_adapter->h_pnp, cl_obj_deref ); + p_adapter->h_pnp = NULL; + } + + if( p_adapter->packet_filter ) + { + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + cl_obj_lock( &p_adapter->obj ); + + ASSERT( cl_qlist_count( &g_ipoib.adapter_list ) ); + cl_qlist_remove_item( &g_ipoib.adapter_list, &p_adapter->entry ); + + p_adapter->packet_filter = 0; + + cl_obj_unlock( &p_adapter->obj ); + KeReleaseInStackQueuedSpinLock( &hdl ); + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +__adapter_free( + IN cl_obj_t* const p_obj ) +{ + ipoib_adapter_t *p_adapter; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_adapter = PARENT_STRUCT( p_obj, ipoib_adapter_t, obj ); + + if( p_adapter->p_ifc ) + { + if( p_adapter->h_al ) + p_adapter->p_ifc->close_al( p_adapter->h_al ); + + cl_free( p_adapter->p_ifc ); + p_adapter->p_ifc = NULL; + } + + cl_vector_destroy( &p_adapter->ip_vector ); + cl_qpool_destroy( &p_adapter->item_pool ); + cl_spinlock_destroy( &p_adapter->recv_stat_lock ); + cl_spinlock_destroy( &p_adapter->send_stat_lock ); + cl_obj_deinit( p_obj ); + + cl_perf_destroy( &p_adapter->perf, TRUE ); + + cl_free( p_adapter ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static ib_api_status_t +ipoib_query_pkey_index(ipoib_adapter_t *p_adapter) +{ + ib_api_status_t status; + ib_ca_attr_t *ca_attr; + uint32_t ca_size; + uint16_t index = 0; + + /* Query the CA for Pkey table */ + status = p_adapter->p_ifc->query_ca(p_adapter->p_port->ib_mgr.h_ca, NULL, &ca_size); + if(status != IB_INSUFFICIENT_MEMORY) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_query_ca failed\n")); + return status; + } + + ca_attr = (ib_ca_attr_t*)cl_zalloc(ca_size); + if (!ca_attr) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_zalloc can't allocate %d\n",ca_size)); + return IB_INSUFFICIENT_MEMORY; + } + + status = p_adapter->p_ifc->query_ca(p_adapter->p_port->ib_mgr.h_ca, ca_attr,&ca_size); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_query_ca returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + goto pkey_end; + } + CL_ASSERT(ca_attr->p_port_attr[p_adapter->p_port->port_num -1].p_pkey_table[0] == IB_DEFAULT_PKEY); + for(index = 0; index < ca_attr->p_port_attr[p_adapter->p_port->port_num -1].num_pkeys; index++) + { + if(cl_hton16(p_adapter->guids.port_guid.pkey) == ca_attr->p_port_attr[p_adapter->p_port->port_num -1].p_pkey_table[index]) + break; + } + if(index >= ca_attr->p_port_attr[p_adapter->p_port->port_num -1].num_pkeys) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Pkey table is invalid, index not found\n")); + NdisWriteErrorLogEntry( p_adapter->h_adapter, + EVENT_IPOIB_PARTITION_ERR, 1, p_adapter->guids.port_guid.pkey ); + status = IB_NOT_FOUND; + p_adapter->p_port->pkey_index = PKEY_INVALID_INDEX; + goto pkey_end; + } + + p_adapter->p_port->pkey_index = index; + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_IB, + ("for PKEY = 0x%04X got index = %d\n",p_adapter->guids.port_guid.pkey,index)); + +pkey_end: + if(ca_attr) + cl_free(ca_attr); + return status; +} + +static ib_api_status_t +__ipoib_pnp_cb( + IN ib_pnp_rec_t *p_pnp_rec ) +{ + ipoib_adapter_t *p_adapter; + ipoib_port_t *p_port; + ib_pnp_event_t old_state; + ib_pnp_port_rec_t *p_port_rec; + ib_api_status_t status = IB_SUCCESS; + NDIS_LINK_STATE link_state; + NDIS_STATUS_INDICATION status_indication; + + IPOIB_ENTER( IPOIB_DBG_PNP ); + + CL_ASSERT( p_pnp_rec ); + NdisZeroMemory(&link_state, sizeof(NDIS_LINK_STATE)); + p_adapter = + PARENT_STRUCT( p_pnp_rec->pnp_context, ipoib_adapter_t, obj ); + + CL_ASSERT( p_adapter ); + + /* Synchronize with destruction */ + KeWaitForMutexObject( + &p_adapter->mutex, Executive, KernelMode, FALSE, NULL ); + cl_obj_lock( &p_adapter->obj ); + old_state = p_adapter->state; + cl_obj_unlock( &p_adapter->obj ); + if( old_state == IB_PNP_PORT_REMOVE ) + { + KeReleaseMutex( &p_adapter->mutex, FALSE ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_PNP, + ("Aborting - Adapter destroying.\n") ); + return IB_NOT_DONE; + } + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_PNP, + ("p_pnp_rec->pnp_event = 0x%x (%s)\n", + p_pnp_rec->pnp_event, ib_get_pnp_event_str( p_pnp_rec->pnp_event )) ); + + p_port_rec = (ib_pnp_port_rec_t*)p_pnp_rec; + + switch( p_pnp_rec->pnp_event ) + { + case IB_PNP_PORT_ADD: + CL_ASSERT( !p_pnp_rec->context ); + /* Only process our port GUID. */ + if( p_pnp_rec->guid != p_adapter->guids.port_guid.guid ) + { + status = IB_NOT_DONE; + break; + } + + /* Don't process if we're destroying. */ + if( p_adapter->obj.state == CL_DESTROYING ) + { + status = IB_NOT_DONE; + break; + } + + CL_ASSERT( !p_adapter->p_port ); + /* Allocate all IB resources. */ + cl_obj_lock( &p_adapter->obj ); + p_adapter->state = IB_PNP_PORT_ADD; + cl_obj_unlock( &p_adapter->obj ); + status = ipoib_create_port( p_adapter, p_port_rec, &p_port ); + cl_obj_lock( &p_adapter->obj ); + if( status != IB_SUCCESS ) + { + p_adapter->state = old_state; + cl_obj_unlock( &p_adapter->obj ); + p_adapter->hung = TRUE; + break; + } + + p_pnp_rec->context = p_port; + + p_adapter->p_port = p_port; + cl_obj_unlock( &p_adapter->obj ); + break; + + case IB_PNP_PORT_REMOVE: + /* Release all IB resources. */ + CL_ASSERT( p_pnp_rec->context ); + + cl_obj_lock( &p_adapter->obj ); + p_adapter->state = IB_PNP_PORT_REMOVE; + p_port = p_adapter->p_port; + p_adapter->p_port = NULL; + cl_obj_unlock( &p_adapter->obj ); + ipoib_port_destroy( p_port ); + p_pnp_rec->context = NULL; + status = IB_SUCCESS; + break; + + case IB_PNP_PORT_ACTIVE: + /* Join multicast groups and put QP in RTS. */ + CL_ASSERT( p_pnp_rec->context ); + + cl_obj_lock( &p_adapter->obj ); + p_adapter->state = IB_PNP_PORT_INIT; + cl_obj_unlock( &p_adapter->obj ); + ipoib_port_up( p_adapter->p_port, p_port_rec ); + + status = IB_SUCCESS; + break; + + case IB_PNP_PORT_ARMED: + status = IB_SUCCESS; + break; + + case IB_PNP_PORT_INIT: + /* + * Init could happen if the SM brings the port down + * without changing the physical link. + */ + case IB_PNP_PORT_DOWN: + CL_ASSERT( p_pnp_rec->context ); + + cl_obj_lock( &p_adapter->obj ); + old_state = p_adapter->state; + p_adapter->state = IB_PNP_PORT_DOWN; + cl_obj_unlock( &p_adapter->obj ); + status = IB_SUCCESS; + + if( !p_adapter->registering && old_state != IB_PNP_PORT_DOWN ) + { + link_state.Header.Revision = NDIS_LINK_STATE_REVISION_1; + link_state.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + link_state.Header.Size = sizeof(NDIS_LINK_STATE); + link_state.MediaConnectState = MediaConnectStateDisconnected; + link_state.MediaDuplexState = MediaDuplexStateFull; + link_state.XmitLinkSpeed = link_state.RcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + + IPOIB_INIT_NDIS_STATUS_INDICATION(&status_indication, + p_adapter->h_adapter, + NDIS_STATUS_LINK_STATE, + (PVOID)&link_state, + sizeof(link_state)); + + NdisMIndicateStatusEx(p_adapter->h_adapter,&status_indication); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link DOWN!\n") ); + + ipoib_port_down( p_adapter->p_port ); + } + break; + + case IB_PNP_REG_COMPLETE: + if( p_adapter->registering ) + { + p_adapter->registering = FALSE; + cl_obj_lock( &p_adapter->obj ); + old_state = p_adapter->state; + cl_obj_unlock( &p_adapter->obj ); + + if( old_state == IB_PNP_PORT_DOWN ) + { + /* If we were initializing, we might have pended some OIDs. */ + ipoib_resume_oids( p_adapter ); + + link_state.Header.Revision = NDIS_LINK_STATE_REVISION_1; + link_state.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + link_state.Header.Size = sizeof(NDIS_LINK_STATE); + link_state.MediaConnectState = MediaConnectStateDisconnected; + link_state.MediaDuplexState = MediaDuplexStateFull; + link_state.XmitLinkSpeed = link_state.RcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + + IPOIB_INIT_NDIS_STATUS_INDICATION(&status_indication, + p_adapter->h_adapter, + NDIS_STATUS_LINK_STATE, + (PVOID)&link_state, + sizeof(link_state)); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_INIT, + ("Indicate DISCONNECT\n") ); + NdisMIndicateStatusEx(p_adapter->h_adapter,&status_indication); + } + } + + if( p_adapter->reset && p_adapter->state != IB_PNP_PORT_INIT ) + { + p_adapter->reset = FALSE; + NdisMResetComplete( + p_adapter->h_adapter, NDIS_STATUS_SUCCESS, TRUE ); + } + status = IB_SUCCESS; + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("IPOIB: Received unhandled PnP event 0x%x (%s)\n", + p_pnp_rec->pnp_event, ib_get_pnp_event_str( p_pnp_rec->pnp_event )) ); + /* Fall through. */ + + status = IB_SUCCESS; + + /* We ignore events below if the link is not active. */ + if( p_port_rec->p_port_attr->link_state != IB_LINK_ACTIVE ) + break; + + case IB_PNP_PKEY_CHANGE: + if(p_pnp_rec->pnp_event == IB_PNP_PKEY_CHANGE && + p_adapter->guids.port_guid.pkey != IB_DEFAULT_PKEY) + { + status = ipoib_query_pkey_index(p_adapter); + if(status != IB_SUCCESS) + { + cl_obj_lock( &p_adapter->obj ); + p_adapter->state = IB_PNP_PORT_INIT; + cl_obj_unlock( &p_adapter->obj ); + } + } + + case IB_PNP_SM_CHANGE: + case IB_PNP_GID_CHANGE: + case IB_PNP_LID_CHANGE: + + cl_obj_lock( &p_adapter->obj ); + old_state = p_adapter->state; + switch( old_state ) + { + case IB_PNP_PORT_DOWN: + p_adapter->state = IB_PNP_PORT_INIT; + break; + + default: + p_adapter->state = IB_PNP_PORT_DOWN; + } + cl_obj_unlock( &p_adapter->obj ); + + if( p_adapter->registering ) + break; + + switch( old_state ) + { + case IB_PNP_PORT_ACTIVE: + case IB_PNP_PORT_INIT: + link_state.Header.Revision = NDIS_LINK_STATE_REVISION_1; + link_state.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + link_state.Header.Size = sizeof(NDIS_LINK_STATE); + link_state.MediaConnectState = MediaConnectStateDisconnected; + link_state.MediaDuplexState = MediaDuplexStateFull; + link_state.XmitLinkSpeed = link_state.RcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + + IPOIB_INIT_NDIS_STATUS_INDICATION(&status_indication, + p_adapter->h_adapter, + NDIS_STATUS_LINK_STATE, + (PVOID)&link_state, + sizeof(link_state)); + + NdisMIndicateStatusEx(p_adapter->h_adapter,&status_indication); + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link DOWN!\n") ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_INIT, + ("Indicate DISCONNECT\n") ); + + ipoib_port_down( p_adapter->p_port ); + /* Fall through. */ + + case IB_PNP_PORT_DOWN: + cl_obj_lock( &p_adapter->obj ); + p_adapter->state = IB_PNP_PORT_INIT; + cl_obj_unlock( &p_adapter->obj ); + ipoib_port_up( p_adapter->p_port, (ib_pnp_port_rec_t*)p_pnp_rec ); + } + break; + } + + KeReleaseMutex( &p_adapter->mutex, FALSE ); + + IPOIB_EXIT( IPOIB_DBG_PNP ); + return status; +} + + +/* Joins/leaves mcast groups based on currently programmed mcast MACs. */ +void +ipoib_refresh_mcast( + IN ipoib_adapter_t* const p_adapter, + IN mac_addr_t* const p_mac_array, + IN const uint8_t num_macs ) +{ + uint8_t i, j; + ipoib_port_t *p_port = NULL; + + IPOIB_ENTER( IPOIB_DBG_MCAST ); + cl_obj_lock( &p_adapter->obj ); + if( p_adapter->state == IB_PNP_PORT_ACTIVE ) + { + p_port = p_adapter->p_port; + ipoib_port_ref( p_port, ref_refresh_mcast ); + } + cl_obj_unlock( &p_adapter->obj ); + + if( p_port ) + { + /* Purge old entries. */ + for( i = 0; i < p_adapter->mcast_array_size; i++ ) + { + for( j = 0; j < num_macs; j++ ) + { + if( !cl_memcmp( &p_adapter->mcast_array[i], &p_mac_array[j], + sizeof(mac_addr_t) ) ) + { + break; + } + } + if( j != num_macs ) + continue; + + ipoib_port_remove_endpt( p_port, p_adapter->mcast_array[i] ); + } + + /* Add new entries */ + for( i = 0; i < num_macs; i++ ) + { + for( j = 0; j < p_adapter->mcast_array_size; j++ ) + { + if( !cl_memcmp( &p_adapter->mcast_array[j], &p_mac_array[i], + sizeof(mac_addr_t) ) ) + { + break; + } + } + + if( j != p_adapter->mcast_array_size ) + continue; + if ( ( p_mac_array[i].addr[0] == 1 && p_mac_array[i].addr[1] == 0 && p_mac_array[i].addr[2] == 0x5e && + p_mac_array[i].addr[3] == 0 && p_mac_array[i].addr[4] == 0 && p_mac_array[i].addr[5] == 1 ) || + !( p_mac_array[i].addr[0] == 1 && p_mac_array[i].addr[1] == 0 && p_mac_array[i].addr[2] == 0x5e ) + ) + { + ipoib_port_join_mcast( p_port, p_mac_array[i], IB_MC_REC_STATE_FULL_MEMBER ); + } + } + } + + /* Copy the MAC array. */ + NdisMoveMemory( p_adapter->mcast_array, p_mac_array, + num_macs * sizeof(mac_addr_t) ); + p_adapter->mcast_array_size = num_macs; + + if( p_port ) + ipoib_port_deref( p_port, ref_refresh_mcast ); + + IPOIB_EXIT( IPOIB_DBG_MCAST ); +} + + +ib_api_status_t +ipoib_reset_adapter( + IN ipoib_adapter_t* const p_adapter ) +{ + ib_api_status_t status; + ib_pnp_handle_t h_pnp; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + if( p_adapter->reset ) + return IB_INVALID_STATE; + + p_adapter->hung = FALSE; + p_adapter->reset = TRUE; + + if( p_adapter->h_pnp ) + { + h_pnp = p_adapter->h_pnp; + p_adapter->h_pnp = NULL; + status = p_adapter->p_ifc->dereg_pnp( h_pnp, __ipoib_pnp_dereg ); + if( status == IB_SUCCESS ) + status = IB_NOT_DONE; + } + else + { + status = __ipoib_pnp_reg( p_adapter, IB_PNP_FLAG_REG_COMPLETE ); + if( status == IB_SUCCESS ) + p_adapter->hung = FALSE; + } + if (status == IB_NOT_DONE) { + p_adapter->reset = TRUE; + } + else { + p_adapter->reset = FALSE; + } + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +static void +__ipoib_pnp_dereg( + IN void* context ) +{ + ipoib_adapter_t* p_adapter; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_adapter = PARENT_STRUCT( context, ipoib_adapter_t, obj ); + + cl_thread_init(&p_adapter->destroy_thread, __ipoib_adapter_reset, (void*)p_adapter, "destroy_thread"); + + IPOIB_ENTER( IPOIB_DBG_INIT ); + +} + +static void +__ipoib_adapter_reset( + IN void* context) +{ + + ipoib_adapter_t *p_adapter; + ipoib_port_t *p_port; + ib_api_status_t status; + ib_pnp_event_t state; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_adapter = (ipoib_adapter_t*)context; + + /* Synchronize with destruction */ + KeWaitForMutexObject( + &p_adapter->mutex, Executive, KernelMode, FALSE, NULL ); + + cl_obj_lock( &p_adapter->obj ); + + CL_ASSERT( !p_adapter->h_pnp ); + + if( p_adapter->state != IB_PNP_PORT_REMOVE ) + p_adapter->state = IB_PNP_PORT_ADD; + + state = p_adapter->state; + + /* Destroy the current port instance if it still exists. */ + p_port = p_adapter->p_port; + p_adapter->p_port = NULL; + cl_obj_unlock( &p_adapter->obj ); + + if( p_port ) + ipoib_port_destroy( p_port ); + ASSERT(p_adapter->reset == TRUE); + if( state != IB_PNP_PORT_REMOVE ) + { + status = __ipoib_pnp_reg( p_adapter, IB_PNP_FLAG_REG_COMPLETE ); + if( status != IB_SUCCESS ) + { + p_adapter->reset = FALSE; + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__ipoib_pnp_reg returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + NdisMResetComplete( + p_adapter->h_adapter, NDIS_STATUS_HARD_ERRORS, TRUE ); + } + } + else + { + p_adapter->reset = FALSE; + NdisMResetComplete( + p_adapter->h_adapter, NDIS_STATUS_SUCCESS, TRUE ); + status = IB_SUCCESS; + } + + /* Dereference the adapter since the previous registration is now gone. */ + cl_obj_deref( &p_adapter->obj ); + + KeReleaseMutex( &p_adapter->mutex, FALSE ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +void +ipoib_set_rate( + IN ipoib_adapter_t* const p_adapter, + IN const uint8_t link_width, + IN const uint8_t link_speed ) +{ + uint32_t rate; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + /* Set the link speed based on the IB link speed (1x vs 4x, etc). */ + switch( link_speed ) + { + case IB_LINK_SPEED_ACTIVE_2_5: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link speed is 2.5Gs\n") ); + rate = IB_LINK_SPEED_ACTIVE_2_5; + break; + + case IB_LINK_SPEED_ACTIVE_5: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link speed is 5G\n") ); + rate = IB_LINK_SPEED_ACTIVE_5; + break; + + case IB_LINK_SPEED_ACTIVE_10: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link speed is 10G\n") ); + rate = IB_LINK_SPEED_ACTIVE_10; + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid link speed %d.\n", link_speed) ); + rate = 0; + } + + switch( link_width ) + { + case IB_LINK_WIDTH_ACTIVE_1X: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link width is 1X\n") ); + rate *= ONE_X_IN_100BPS; + break; + + case IB_LINK_WIDTH_ACTIVE_4X: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link width is 4X\n") ); + rate *= FOUR_X_IN_100BPS; + break; + + case IB_LINK_WIDTH_ACTIVE_12X: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link width is 12X\n") ); + rate *= TWELVE_X_IN_100BPS; + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid link rate (%d).\n", link_width) ); + rate = 0; + } + + p_adapter->port_rate = rate; + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +ib_api_status_t +ipoib_set_active( + IN ipoib_adapter_t* const p_adapter ) +{ + ib_pnp_event_t old_state; + uint8_t i; + ib_api_status_t status = IB_SUCCESS; + NDIS_LINK_STATE link_state; + NDIS_STATUS_INDICATION status_indication; + IPOIB_ENTER( IPOIB_DBG_INIT ); + + NdisZeroMemory(&link_state, sizeof(NDIS_LINK_STATE)); + cl_obj_lock( &p_adapter->obj ); + old_state = p_adapter->state; + + /* Change the state to indicate that we are now connected and live. */ + if( old_state == IB_PNP_PORT_INIT ) + p_adapter->state = IB_PNP_PORT_ACTIVE; + + cl_obj_unlock( &p_adapter->obj ); + + /* + * If we had a pending OID request for OID_GEN_LINK_SPEED, + * complete it now. + */ + switch( old_state ) + { + case IB_PNP_PORT_ADD: + ipoib_reg_addrs( p_adapter ); + /* Fall through. */ + + case IB_PNP_PORT_REMOVE: + ipoib_resume_oids( p_adapter ); + break; + + default: + if (p_adapter->guids.port_guid.pkey != IB_DEFAULT_PKEY) + { + status = ipoib_query_pkey_index(p_adapter); + if( IB_SUCCESS != status) + { + break; + } + } + /* Join all programmed multicast groups. */ + for( i = 0; i < p_adapter->mcast_array_size; i++ ) + { + ipoib_port_join_mcast( + p_adapter->p_port, p_adapter->mcast_array[i] ,IB_MC_REC_STATE_FULL_MEMBER); + } + + /* Register all existing addresses. */ + ipoib_reg_addrs( p_adapter ); + + ipoib_resume_oids( p_adapter ); + + /* + * Now that we're in the broadcast group, notify that + * we have a link. + */ + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, ("Link UP!\n") ); + NdisWriteErrorLogEntry( p_adapter->h_adapter, + EVENT_IPOIB_PORT_UP + (p_adapter->port_rate/ONE_X_IN_100BPS), + 1, p_adapter->port_rate ); + + if( !p_adapter->reset ) + { + link_state.Header.Revision = NDIS_LINK_STATE_REVISION_1; + link_state.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + link_state.Header.Size = sizeof(NDIS_LINK_STATE); + link_state.MediaConnectState = MediaConnectStateConnected; + link_state.MediaDuplexState = MediaDuplexStateFull; + link_state.XmitLinkSpeed = + link_state.RcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + link_state.PauseFunctions = NdisPauseFunctionsSendAndReceive; + IPOIB_INIT_NDIS_STATUS_INDICATION(&status_indication, + p_adapter->h_adapter, + NDIS_STATUS_LINK_STATE, + (PVOID)&link_state, + sizeof(link_state)); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, ("***************Indicate connect!\n") ); + NdisMIndicateStatusEx(p_adapter->h_adapter,&status_indication); + } + } + + if( p_adapter->reset ) + { + ASSERT(FALSE); + p_adapter->reset = FALSE; + NdisMResetComplete( + p_adapter->h_adapter, NDIS_STATUS_SUCCESS, TRUE ); + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + +/* + * If something goes wrong after the port goes active, e.g. + * - PortInfo query failure + * - MC Join timeout + * - etc + * Mark the port state as down, resume any pended OIDS, etc. + */ +void +ipoib_set_inactive( + IN ipoib_adapter_t* const p_adapter ) +{ + ib_pnp_event_t old_state; + NDIS_LINK_STATE link_state; + NDIS_STATUS_INDICATION status_indication; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + NdisZeroMemory(&link_state, sizeof(NDIS_LINK_STATE)); + cl_obj_lock( &p_adapter->obj ); + old_state = p_adapter->state; + if( old_state != IB_PNP_PORT_REMOVE ) + p_adapter->state = IB_PNP_PORT_DOWN; + cl_obj_unlock( &p_adapter->obj ); + + /* + * If we had a pending OID request for OID_GEN_LINK_SPEED, + * complete it now. + */ + if( old_state == IB_PNP_PORT_INIT ) + { + link_state.Header.Revision = NDIS_LINK_STATE_REVISION_1; + link_state.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + link_state.Header.Size = sizeof(NDIS_LINK_STATE); + link_state.MediaConnectState = MediaConnectStateDisconnected; + link_state.MediaDuplexState = MediaDuplexStateFull; + link_state.XmitLinkSpeed = link_state.RcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + + IPOIB_INIT_NDIS_STATUS_INDICATION(&status_indication, + p_adapter->h_adapter, + NDIS_STATUS_LINK_STATE, + (PVOID)&link_state, + sizeof(link_state)); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, ("Indicate Disconnect!\n") ); + NdisMIndicateStatusEx(p_adapter->h_adapter,&status_indication); + + ipoib_resume_oids( p_adapter ); + } + + if( p_adapter->reset ) + { + p_adapter->reset = FALSE; + NdisMResetComplete( + p_adapter->h_adapter, NDIS_STATUS_SUCCESS, TRUE ); + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + +NDIS_STATUS +ipoib_get_gen_stat( + IN ipoib_adapter_t* const p_adapter, + OUT pending_oid_t* const p_oid_info) +{ + PNDIS_STATISTICS_INFO StatisticsInfo; + IPOIB_ENTER( IPOIB_DBG_STAT ); + + if (p_oid_info->buf_len < sizeof(StatisticsInfo)) + { + *p_oid_info->p_bytes_needed = sizeof(NDIS_STATISTICS_INFO); + return NDIS_STATUS_INVALID_LENGTH; + } + + StatisticsInfo = (PNDIS_STATISTICS_INFO)p_oid_info->p_buf; + StatisticsInfo->Header.Revision = NDIS_OBJECT_REVISION_1; + StatisticsInfo->Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + StatisticsInfo->Header.Size = sizeof(NDIS_STATISTICS_INFO); + /*StatisticsInfo->SupportedStatistics = NDIS_STATISTICS_FLAGS_VALID_RCV_DISCARDS | + NDIS_STATISTICS_FLAGS_VALID_RCV_ERROR | + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_FRAMES_RCV | + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_FRAMES_RCV | + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_FRAMES_RCV | + NDIS_STATISTICS_FLAGS_VALID_BYTES_RCV | + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_BYTES_RCV | + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_BYTES_RCV | + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_BYTES_RCV | + + NDIS_STATISTICS_FLAGS_VALID_XMIT_DISCARDS | + NDIS_STATISTICS_FLAGS_VALID_XMIT_ERROR | + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_FRAMES_XMIT | + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_FRAMES_XMIT | + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_FRAMES_XMIT | + NDIS_STATISTICS_FLAGS_VALID_BYTES_XMIT | + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_BYTES_XMIT | + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_BYTES_XMIT | + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_BYTES_XMIT; */ + + + StatisticsInfo->SupportedStatistics = NDIS_STATISTICS_FLAGS_VALID_DIRECTED_FRAMES_RCV | + //The data in the ifHCInUcastPkts member is valid. + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_FRAMES_RCV | + //The data in the ifHCInMulticastPkts member is valid. + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_FRAMES_RCV | + //The data in the ifHCInBroadcastPkts member is valid. + NDIS_STATISTICS_FLAGS_VALID_BYTES_RCV | + //The data in the ifHCInOctets member is valid. + NDIS_STATISTICS_FLAGS_VALID_RCV_DISCARDS | + //The data in the ifInDiscards member is valid. + NDIS_STATISTICS_FLAGS_VALID_RCV_ERROR | + //The data in the ifInErrors member is valid. + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_FRAMES_XMIT | + //The data in the ifHCOutUcastPkts member is valid. + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_FRAMES_XMIT | + //The data in the ifHCOutMulticastPkts member is valid. + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_FRAMES_XMIT | + //The data in the ifHCOutBroadcastPkts member is valid. + NDIS_STATISTICS_FLAGS_VALID_BYTES_XMIT | + //The data in the ifHCOutOctets member is valid. + NDIS_STATISTICS_FLAGS_VALID_XMIT_ERROR | + //The data in the ifOutErrors member is valid. + NDIS_STATISTICS_FLAGS_VALID_XMIT_DISCARDS | + //The data in the ifOutDiscards member is valid. + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_BYTES_RCV | + //The data in the ifHCInUcastOctets member is valid. + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_BYTES_RCV | + //The data in the ifHCInMulticastOctets member is valid. + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_BYTES_RCV | + //The data in the ifHCInBroadcastOctets member is valid. + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_BYTES_XMIT | + //The data in the ifHCOutUcastOctets member is valid. + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_BYTES_XMIT | + //The data in the ifHCOutMulticastOctets member is valid. + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_BYTES_XMIT ; + //The data in the ifHCOutBroadcastOctets member is valid + + cl_spinlock_acquire( &p_adapter->recv_stat_lock ); + StatisticsInfo->ifInDiscards = p_adapter->recv_stats.comp.dropped + + p_adapter->recv_stats.comp.error; + StatisticsInfo->ifInErrors = p_adapter->recv_stats.comp.error; + StatisticsInfo->ifHCInOctets = p_adapter->recv_stats.ucast.bytes + + p_adapter->recv_stats.bcast.bytes + + p_adapter->recv_stats.mcast.bytes; + StatisticsInfo->ifHCInUcastPkts = p_adapter->recv_stats.ucast.frames; + StatisticsInfo->ifHCInMulticastPkts = p_adapter->recv_stats.mcast.frames; + StatisticsInfo->ifHCInBroadcastPkts = p_adapter->recv_stats.bcast.frames; + StatisticsInfo->ifHCInMulticastOctets = p_adapter->recv_stats.mcast.bytes; + StatisticsInfo->ifHCInBroadcastOctets = p_adapter->recv_stats.bcast.bytes; + StatisticsInfo->ifHCInUcastOctets = p_adapter->recv_stats.ucast.bytes; + cl_spinlock_release( &p_adapter->recv_stat_lock ); + + cl_spinlock_acquire( &p_adapter->send_stat_lock ); + StatisticsInfo->ifHCOutOctets = p_adapter->send_stats.ucast.bytes + + p_adapter->send_stats.mcast.bytes + + p_adapter->send_stats.bcast.bytes; + StatisticsInfo->ifHCOutUcastPkts = p_adapter->send_stats.ucast.frames; + StatisticsInfo->ifHCOutMulticastPkts = p_adapter->send_stats.mcast.frames; + StatisticsInfo->ifHCOutBroadcastPkts = p_adapter->send_stats.bcast.frames; + StatisticsInfo->ifOutErrors = p_adapter->send_stats.comp.error; + StatisticsInfo->ifOutDiscards = p_adapter->send_stats.comp.dropped; + StatisticsInfo->ifHCOutUcastOctets = p_adapter->send_stats.ucast.bytes; + StatisticsInfo->ifHCOutMulticastOctets = p_adapter->send_stats.mcast.bytes; + StatisticsInfo->ifHCOutBroadcastOctets = p_adapter->send_stats.bcast.bytes; + cl_spinlock_release( &p_adapter->send_stat_lock ); + + *p_oid_info->p_bytes_used = sizeof(NDIS_STATISTICS_INFO); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_SUCCESS; +} + +NDIS_STATUS +ipoib_get_recv_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN pending_oid_t* const p_oid_info ) +{ + uint64_t stat; + + IPOIB_ENTER( IPOIB_DBG_STAT ); + + CL_ASSERT( p_adapter ); + + cl_spinlock_acquire( &p_adapter->recv_stat_lock ); + switch( stat_sel ) + { + case IP_STAT_SUCCESS: + stat = p_adapter->recv_stats.comp.success; + break; + + case IP_STAT_ERROR: + stat = p_adapter->recv_stats.comp.error; + break; + + case IP_STAT_DROPPED: + stat = p_adapter->recv_stats.comp.dropped; + break; + + case IP_STAT_UCAST_BYTES: + stat = p_adapter->recv_stats.ucast.bytes; + break; + + case IP_STAT_UCAST_FRAMES: + stat = p_adapter->recv_stats.ucast.frames; + break; + + case IP_STAT_BCAST_BYTES: + stat = p_adapter->recv_stats.bcast.bytes; + break; + + case IP_STAT_BCAST_FRAMES: + stat = p_adapter->recv_stats.bcast.frames; + break; + + case IP_STAT_MCAST_BYTES: + stat = p_adapter->recv_stats.mcast.bytes; + break; + + case IP_STAT_MCAST_FRAMES: + stat = p_adapter->recv_stats.mcast.frames; + break; + + default: + stat = 0; + } + cl_spinlock_release( &p_adapter->recv_stat_lock ); + + *p_oid_info->p_bytes_needed = sizeof(uint64_t); + + if( p_oid_info->buf_len >= sizeof(uint64_t) ) + { + *((uint64_t*)p_oid_info->p_buf) = stat; + *p_oid_info->p_bytes_used = sizeof(uint64_t); + } + else if( p_oid_info->buf_len >= sizeof(uint32_t) ) + { + *((uint32_t*)p_oid_info->p_buf) = (uint32_t)stat; + *p_oid_info->p_bytes_used = sizeof(uint32_t); + } + else + { + *p_oid_info->p_bytes_used = 0; + IPOIB_EXIT( IPOIB_DBG_STAT ); + return NDIS_STATUS_INVALID_LENGTH; + } + + IPOIB_EXIT( IPOIB_DBG_STAT ); + return NDIS_STATUS_SUCCESS; +} + + +void +ipoib_inc_recv_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN const size_t bytes OPTIONAL, + IN const size_t packets OPTIONAL ) +{ + IPOIB_ENTER( IPOIB_DBG_STAT ); + + cl_spinlock_acquire( &p_adapter->recv_stat_lock ); + switch( stat_sel ) + { + case IP_STAT_ERROR: + p_adapter->recv_stats.comp.error++; + break; + + case IP_STAT_DROPPED: + p_adapter->recv_stats.comp.dropped++; + break; + + case IP_STAT_UCAST_BYTES: + case IP_STAT_UCAST_FRAMES: + p_adapter->recv_stats.comp.success++; + p_adapter->recv_stats.ucast.frames += packets; + p_adapter->recv_stats.ucast.bytes += bytes; + break; + + case IP_STAT_BCAST_BYTES: + case IP_STAT_BCAST_FRAMES: + p_adapter->recv_stats.comp.success++; + p_adapter->recv_stats.bcast.frames += packets; + p_adapter->recv_stats.bcast.bytes += bytes; + break; + + case IP_STAT_MCAST_BYTES: + case IP_STAT_MCAST_FRAMES: + p_adapter->recv_stats.comp.success++; + p_adapter->recv_stats.mcast.frames += packets; + p_adapter->recv_stats.mcast.bytes += bytes; + break; + + default: + break; + } + cl_spinlock_release( &p_adapter->recv_stat_lock ); + + IPOIB_EXIT( IPOIB_DBG_STAT ); +} + +NDIS_STATUS +ipoib_get_send_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN pending_oid_t* const p_oid_info ) +{ + uint64_t stat; + + IPOIB_ENTER( IPOIB_DBG_STAT ); + + CL_ASSERT( p_adapter ); + + cl_spinlock_acquire( &p_adapter->send_stat_lock ); + switch( stat_sel ) + { + case IP_STAT_SUCCESS: + stat = p_adapter->send_stats.comp.success; + break; + + case IP_STAT_ERROR: + stat = p_adapter->send_stats.comp.error; + break; + + case IP_STAT_DROPPED: + stat = p_adapter->send_stats.comp.dropped; + break; + + case IP_STAT_UCAST_BYTES: + stat = p_adapter->send_stats.ucast.bytes; + break; + + case IP_STAT_UCAST_FRAMES: + stat = p_adapter->send_stats.ucast.frames; + break; + + case IP_STAT_BCAST_BYTES: + stat = p_adapter->send_stats.bcast.bytes; + break; + + case IP_STAT_BCAST_FRAMES: + stat = p_adapter->send_stats.bcast.frames; + break; + + case IP_STAT_MCAST_BYTES: + stat = p_adapter->send_stats.mcast.bytes; + break; + + case IP_STAT_MCAST_FRAMES: + stat = p_adapter->send_stats.mcast.frames; + break; + + default: + stat = 0; + } + cl_spinlock_release( &p_adapter->send_stat_lock ); + + *p_oid_info->p_bytes_needed = sizeof(uint64_t); + + if( p_oid_info->buf_len >= sizeof(uint64_t) ) + { + *((uint64_t*)p_oid_info->p_buf) = stat; + *p_oid_info->p_bytes_used = sizeof(uint64_t); + } + else if( p_oid_info->buf_len >= sizeof(uint32_t) ) + { + *((uint32_t*)p_oid_info->p_buf) = (uint32_t)stat; + *p_oid_info->p_bytes_used = sizeof(uint32_t); + } + else + { + *p_oid_info->p_bytes_used = 0; + IPOIB_EXIT( IPOIB_DBG_STAT ); + return NDIS_STATUS_INVALID_LENGTH; + } + + IPOIB_EXIT( IPOIB_DBG_STAT ); + return NDIS_STATUS_SUCCESS; +} + + +void +ipoib_inc_send_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN const size_t bytes OPTIONAL ) +{ + IPOIB_ENTER( IPOIB_DBG_STAT ); + + cl_spinlock_acquire( &p_adapter->send_stat_lock ); + switch( stat_sel ) + { + case IP_STAT_ERROR: + p_adapter->send_stats.comp.error++; + break; + + case IP_STAT_DROPPED: + p_adapter->send_stats.comp.dropped++; + break; + + case IP_STAT_UCAST_BYTES: + case IP_STAT_UCAST_FRAMES: + p_adapter->send_stats.comp.success++; + p_adapter->send_stats.ucast.frames++; + p_adapter->send_stats.ucast.bytes += bytes; + break; + + case IP_STAT_BCAST_BYTES: + case IP_STAT_BCAST_FRAMES: + p_adapter->send_stats.comp.success++; + p_adapter->send_stats.bcast.frames++; + p_adapter->send_stats.bcast.bytes += bytes; + break; + + case IP_STAT_MCAST_BYTES: + case IP_STAT_MCAST_FRAMES: + p_adapter->send_stats.comp.success++; + p_adapter->send_stats.mcast.frames++; + p_adapter->send_stats.mcast.bytes += bytes; + break; + + default: + break; + } + cl_spinlock_release( &p_adapter->send_stat_lock ); + + IPOIB_EXIT( IPOIB_DBG_STAT ); +} diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib_adapter.h b/branches/winverbs/ulp/ipoib/kernel6/ipoib_adapter.h new file mode 100644 index 00000000..f8ab4c47 --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib_adapter.h @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_adapter.h 4226 2009-04-06 06:01:03Z xalex $ + */ + + +#ifndef _IPOIB_ADAPTER_H_ +#define _IPOIB_ADAPTER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ip_stats.h" + + +/* + * Definitions + */ +#define MAX_MCAST 32 + +#define IPV4_ADDR_SIZE 4 + +#define PORT_NUM_INDEX_IN_GUID 3 /* 0 based index into big endian GUID to get port number */ + +/* + * Macros + */ +typedef enum +{ + CSUM_DISABLED = 0, + CSUM_ENABLED, + CSUM_BYPASS +} csum_flag_t; + +typedef enum _ipoib_state +{ + IPOIB_PAUSED, + IPOIB_PAUSING, + IPOIB_RUNNING +} ipoib_state_t; + +typedef struct _ipoib_params +{ + int32_t rq_depth; + int32_t rq_low_watermark; + int32_t sq_depth; + csum_flag_t send_chksum_offload; + csum_flag_t recv_chksum_offload; + uint32_t sa_timeout; + uint32_t sa_retry_cnt; + uint32_t recv_pool_ratio; + uint32_t payload_mtu; + boolean_t lso; + uint32_t xfer_block_size; + mac_addr_t conf_mac; + uint32_t mc_leave_rescan; + uint32_t guid_mask; + uint32_t bc_join_retry; + boolean_t cm_enabled; + uint32_t cm_payload_mtu; + uint32_t cm_xfer_block_size; +} ipoib_params_t; +/* +* FIELDS +* rq_depth +* Number of receive WQEs to allocate. +* +* rq_low_watermark +* Receives are indicated with NDIS_STATUS_RESOURCES when the number of +* receives posted to the RQ falls bellow this value. +* +* sq_depth +* Number of send WQEs to allocate. +* +* send_chksum_offload +* recv_chksum_offload +* Flags to indicate whether to offload send/recv checksums. +* 0 - No hardware cheksum +* 1 - Try to offload if the device support it +* 2 - Always report success (checksum bypass) +* +* wsdp_enabled +* Flag to indicate whether WSDP is enabled for an adapter adapter. +* +* static_lid +* LID to assign to the port if that port is down (not init) and has none. +* This feature allows a LID to be assigned, alowing locally targetted +* traffic to occur even on ports that are not plugged in. +* +* sa_timeout +* Time, in milliseconds, to wait for a response before retransmitting an +* SA query request. +* +* sa_retry_cnt +* Number of times to retry an SA query request. +* +* recv_pool_ratio +* Initial ratio of receive pool size to receive queue depth. +* +* grow_thresh +* Threshold at which to grow the receive pool. Valid values start are +* powers of 2, excluding 1. When zero, grows only when the pool is +* exhausted. Other values indicate fractional values +* (i.e. 2 indicates 1/2, 4 indicates 1/4, etc.) +* +* payload_mtu + The maximum available size of IPoIB transfer unit. + + If using UD mode: +* It should be decremented by size of IPoIB header (==4B) +* For example, if the HCA support 4K MTU, +* upper threshold for payload mtu is 4092B and not 4096B + + If using CM mode: + MTU will be not limited by 4K threshold. + UD QP still may be used for different protocols (like ARP). + For these situations the threshold for the UD QP will take the default value + +* +* lso +* It indicates if there's a support for hardware large/giant send offload +* +*********/ + + +typedef struct _pending_oid +{ + NDIS_OID oid; + PVOID p_buf; + ULONG buf_len; + PULONG p_bytes_used; + PULONG p_bytes_needed; + PNDIS_OID_REQUEST p_pending_oid; +} pending_oid_t; + + +typedef struct _ipoib_adapter +{ + cl_obj_t obj; + NDIS_HANDLE h_adapter; + ipoib_ifc_data_t guids; + + cl_list_item_t entry; + + ib_al_handle_t h_al; + ib_pnp_handle_t h_pnp; + + ib_pnp_event_t state; + boolean_t hung; + boolean_t reset; + boolean_t registering; + + boolean_t pending_query; + pending_oid_t query_oid; + boolean_t pending_set; + pending_oid_t set_oid; + + struct _ipoib_port *p_port; + + uint32_t port_rate; + + ipoib_params_t params; + cl_spinlock_t recv_stat_lock; + ip_stats_t recv_stats; + cl_spinlock_t send_stat_lock; + ip_stats_t send_stats; + + boolean_t is_primary; + struct _ipoib_adapter *p_primary; + + uint32_t packet_filter; + + mac_addr_t mac; + mac_addr_t mcast_array[MAX_MCAST]; + uint8_t mcast_array_size; + + cl_qpool_t item_pool; + + KMUTEX mutex; + + cl_thread_t destroy_thread; + cl_vector_t ip_vector; + + cl_perf_t perf; + NDIS_HANDLE NdisMiniportDmaHandle; + ipoib_state_t ipoib_state; + ib_al_ifc_t *p_ifc; + + ULONG sg_list_size; + +} ipoib_adapter_t; +/* +* FIELDS +* obj +* Complib object for reference counting and destruction synchronization. +* +* h_adapter +* NDIS adapter handle. +* +* guids +* CA and port GUIDs returned by the bus driver. +* +* entry +* List item for storing all adapters in a list for address translation. +* We add adapters when their packet filter is set to a non-zero value, +* and remove them when their packet filter is cleared. This is needed +* since user-mode removal events are generated after the packet filter +* is cleared, but before the adapter is destroyed. +* +* h_al +* AL handle for all IB resources. +* +* h_pnp +* PNP registration handle for port events. +* +* state +* State of the adapter. IB_PNP_PORT_ADD indicates that the adapter +* is ready to transfer data. +* +* hung +* Boolean flag used to return whether we are hung or not. +* +* p_port +* Pointer to an ipoib_port_t representing all resources for moving data +* on the IB fabric. +* +* rate +* Rate, in 100bps increments, of the link. +* +* params +* Configuration parameters. +* +* pending_query +* Indicates that an query OID request is being processed asynchronously. +* +* query_oid +* Information about the pended query OID request. +* Valid only if pending_query is TRUE. +* +* pending_set +* Indicates that an set OID request is being processed asynchronously. +* +* set_oid +* Information about the pended set OID request. +* Valid only if pending_set is TRUE. +* +* recv_lock +* Spinlock protecting receive processing. +* +* recv_stats +* Receive statistics. +* +* send_lock +* Spinlock protecting send processing. +* +* send_stats +* Send statistics. +* +* is_primary +* Boolean flag to indicate if an adapter is the primary adapter +* of a bundle. +* +* p_primary +* Pointer to the primary adapter for a bundle. +* +* packet_filter +* Packet filter set by NDIS. +* +* mac_addr +* Ethernet MAC address reported to NDIS. +* +* mcast_array +* List of multicast MAC addresses programmed by NDIS. +* +* mcast_array_size +* Number of entries in the multicat MAC address array; +* +* item_pool +* Pool of cl_pool_obj_t structures to use for queueing pending +* packets for transmission. +* +* mutex +* Mutex to synchronized PnP callbacks with destruction. +* +* ip_vector +* Vector of assigned IP addresses. +* +* p_ifc +* Pointer to transport interface. +* +*********/ + + +typedef struct _ats_reg +{ + ipoib_adapter_t *p_adapter; + ib_reg_svc_handle_t h_reg_svc; + +} ats_reg_t; +/* +* FIELDS +* p_adapter +* Pointer to the adapter to which this address is assigned. +* +* h_reg_svc +* Service registration handle. +*********/ + + +typedef struct _net_address_item +{ + ats_reg_t *p_reg; + union _net_address_item_address + { + ULONG as_ulong; + UCHAR as_bytes[IPV4_ADDR_SIZE]; + } address; + +} net_address_item_t; +/* +* FIELDS +* p_reg +* Pointer to the ATS registration assigned to this address. +* +* address +* Union representing the IP address as an unsigned long or as +* an array of bytes. +* +* as_ulong +* The IP address represented as an unsigned long. Windows stores +* IPs this way. +* +* as_bytes +* The IP address represented as an array of bytes. +*********/ + + +ib_api_status_t +ipoib_create_adapter( + IN NDIS_HANDLE wrapper_config_context, + IN void* const h_adapter, + OUT ipoib_adapter_t** const pp_adapter ); + + +ib_api_status_t +ipoib_start_adapter( + IN ipoib_adapter_t* const p_adapter ); + + +void +ipoib_destroy_adapter( + IN ipoib_adapter_t* const p_adapter ); + + +/* Joins/leaves mcast groups based on currently programmed mcast MACs. */ +void +ipoib_refresh_mcast( + IN ipoib_adapter_t* const p_adapter, + IN mac_addr_t* const p_mac_array, + IN const uint8_t num_macs ); +/* +* PARAMETERS +* p_adapter +* Instance whose multicast MAC address list to modify. +* +* p_mac_array +* Array of multicast MAC addresses assigned to the adapter. +* +* num_macs +* Number of MAC addresses in the array. +*********/ +NDIS_STATUS +ipoib_get_gen_stat( + IN ipoib_adapter_t* const p_adapter, + OUT pending_oid_t* const p_oid_info ); + +NDIS_STATUS +ipoib_get_recv_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN pending_oid_t* const p_oid_info ); + + +void +ipoib_inc_recv_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN const size_t bytes OPTIONAL, + IN const size_t packets OPTIONAL ); + + +NDIS_STATUS +ipoib_get_send_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN pending_oid_t* const p_oid_info ); + + +void +ipoib_inc_send_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN const size_t bytes OPTIONAL ); + + +void +ipoib_set_rate( + IN ipoib_adapter_t* const p_adapter, + IN const uint8_t link_width, + IN const uint8_t link_speed ); + + +ib_api_status_t +ipoib_set_active( + IN ipoib_adapter_t* const p_adapter ); + +void +ipoib_set_inactive( + IN ipoib_adapter_t* const p_adapter ); + +ib_api_status_t +ipoib_reset_adapter( + IN ipoib_adapter_t* const p_adapter ); + +void +ipoib_reg_addrs( + IN ipoib_adapter_t* const p_adapter ); + +void +ipoib_dereg_addrs( + IN ipoib_adapter_t* const p_adapter ); + +#define IPOIB_INIT_NDIS_STATUS_INDICATION(_pStatusIndication, _M, _St, _Buf, _BufSize) \ + { \ + NdisZeroMemory(_pStatusIndication, sizeof(NDIS_STATUS_INDICATION)); \ + (_pStatusIndication)->Header.Type = NDIS_OBJECT_TYPE_STATUS_INDICATION; \ + (_pStatusIndication)->Header.Revision = NDIS_STATUS_INDICATION_REVISION_1; \ + (_pStatusIndication)->Header.Size = sizeof(NDIS_STATUS_INDICATION); \ + (_pStatusIndication)->SourceHandle = _M; \ + (_pStatusIndication)->StatusCode = _St; \ + (_pStatusIndication)->StatusBuffer = _Buf; \ + (_pStatusIndication)->StatusBufferSize = _BufSize; \ + } + +//TODO rename to 4 +#define IPOIB_MEDIA_MAX_SPEED 10000000000 + +#endif /* _IPOIB_ADAPTER_H_ */ diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib_cm.c b/branches/winverbs/ulp/ipoib/kernel6/ipoib_cm.c new file mode 100644 index 00000000..e69de29b diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib_debug.h b/branches/winverbs/ulp/ipoib/kernel6/ipoib_debug.h new file mode 100644 index 00000000..69ec3c5a --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib_debug.h @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_debug.h 3719 2009-01-07 12:31:52Z reuven $ + */ + + +#ifndef _IPOIB_DEBUG_H_ +#define _IPOIB_DEBUG_H_ + +#if defined __MODULE__ +#undef __MODULE__ +#endif + +#define __MODULE__ "[IPoIB]" + +#include + + +/* Object types for passing into complib. */ +#define IPOIB_OBJ_INSTANCE 1 +#define IPOIB_OBJ_PORT 2 +#define IPOIB_OBJ_ENDPOINT 3 + + +extern uint32_t g_ipoib_dbg_level; +extern uint32_t g_ipoib_dbg_flags; + + +#if defined(EVENT_TRACING) +// +// Software Tracing Definitions +// +#define WPP_CONTROL_GUIDS \ + WPP_DEFINE_CONTROL_GUID( \ + IPOIBCtlGuid,(3F9BC73D, EB03, 453a, B27B, 20F9A664211A), \ + WPP_DEFINE_BIT(IPOIB_DBG_ERROR) \ + WPP_DEFINE_BIT(IPOIB_DBG_INIT) \ + WPP_DEFINE_BIT(IPOIB_DBG_PNP) \ + WPP_DEFINE_BIT(IPOIB_DBG_SEND) \ + WPP_DEFINE_BIT(IPOIB_DBG_RECV) \ + WPP_DEFINE_BIT(IPOIB_DBG_ENDPT) \ + WPP_DEFINE_BIT(IPOIB_DBG_IB) \ + WPP_DEFINE_BIT(IPOIB_DBG_BUF) \ + WPP_DEFINE_BIT(IPOIB_DBG_MCAST) \ + WPP_DEFINE_BIT(IPOIB_DBG_ALLOC) \ + WPP_DEFINE_BIT(IPOIB_DBG_OID) \ + WPP_DEFINE_BIT(IPOIB_DBG_IOCTL) \ + WPP_DEFINE_BIT(IPOIB_DBG_STAT) \ + WPP_DEFINE_BIT(IPOIB_DBG_OBJ)) + + + +#define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) \ + (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl) +#define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) WPP_LEVEL_LOGGER(flags) +#define WPP_FLAG_ENABLED(flags) \ + (WPP_LEVEL_ENABLED(flags) && \ + WPP_CONTROL(WPP_BIT_ ## flags).Level >= TRACE_LEVEL_VERBOSE) +#define WPP_FLAG_LOGGER(flags) WPP_LEVEL_LOGGER(flags) + +// begin_wpp config +// IPOIB_ENTER(FLAG); +// IPOIB_EXIT(FLAG); +// USEPREFIX(IPOIB_PRINT, "%!STDPREFIX! [IPoIB] :%!FUNC!() :"); +// USEPREFIX(IPOIB_PRINT_EXIT, "%!STDPREFIX! [IPoIB] :%!FUNC!() :"); +// USESUFFIX(IPOIB_PRINT_EXIT, "[IpoIB] :%!FUNC!():]"); +// USESUFFIX(IPOIB_ENTER, " [IPoIB] :%!FUNC!():["); +// USESUFFIX(IPOIB_EXIT, " [IPoIB] :%!FUNC!():]"); +// end_wpp + +#else + +#include + + +/* + * Debug macros + */ +#define IPOIB_DBG_ERR (1 << 0) +#define IPOIB_DBG_INIT (1 << 1) +#define IPOIB_DBG_PNP (1 << 2) +#define IPOIB_DBG_SEND (1 << 3) +#define IPOIB_DBG_RECV (1 << 4) +#define IPOIB_DBG_ENDPT (1 << 5) +#define IPOIB_DBG_IB (1 << 6) +#define IPOIB_DBG_BUF (1 << 7) +#define IPOIB_DBG_MCAST (1 << 8) +#define IPOIB_DBG_ALLOC (1 << 9) +#define IPOIB_DBG_OID (1 << 10) +#define IPOIB_DBG_IOCTL (1 << 11) +#define IPOIB_DBG_STAT (1 << 12) +#define IPOIB_DBG_OBJ (1 << 13) + +#define IPOIB_DBG_ERROR (CL_DBG_ERROR | IPOIB_DBG_ERR) +#define IPOIB_DBG_ALL CL_DBG_ALL + + +#if DBG + +// assignment of _level_ is need to to overcome warning C4127 +#define IPOIB_PRINT(_level_,_flag_,_msg_) \ + { \ + __pragma(warning(suppress:6326)) \ + if( g_ipoib_dbg_level >= (_level_) ) \ + CL_TRACE( _flag_, g_ipoib_dbg_flags, _msg_ ); \ + } + +#define IPOIB_PRINT_EXIT(_level_,_flag_,_msg_) \ + { \ + __pragma(warning(suppress:6326)) \ + if( g_ipoib_dbg_level >= (_level_) ) \ + CL_TRACE( _flag_, g_ipoib_dbg_flags, _msg_ );\ + IPOIB_EXIT(_flag_);\ + } + +#define IPOIB_ENTER(_flag_) \ + { \ + __pragma(warning(suppress:6326)) \ + if( g_ipoib_dbg_level >= TRACE_LEVEL_VERBOSE ) \ + CL_ENTER( _flag_, g_ipoib_dbg_flags ); \ + } + +#define IPOIB_EXIT(_flag_)\ + { \ + __pragma(warning(suppress:6326)) \ + if( g_ipoib_dbg_level >= TRACE_LEVEL_VERBOSE ) \ + CL_EXIT( _flag_, g_ipoib_dbg_flags ); \ + } + +#define IPOIB_TRACE_BYTES( lvl, ptr, len ) \ + { \ + __pragma(warning(suppress:6326)) \ + if( g_ipoib_dbg_level >= (_level_) && \ + (g_ipoib_dbg_flags & (_flag_)) ) \ + { \ + size_t _loop_; \ + for( _loop_ = 0; _loop_ < (len); ++_loop_ ) \ + { \ + cl_dbg_out( "0x%.2X ", ((uint8_t*)(ptr))[_loop_] ); \ + if( (_loop_ + 1)% 16 == 0 ) \ + cl_dbg_out("\n"); \ + else if( (_loop_ % 4 + 1) == 0 ) \ + cl_dbg_out(" "); \ + } \ + cl_dbg_out("\n"); \ + } \ + } + +#else + +#define IPOIB_PRINT(lvl, flags, msg) + +#define IPOIB_PRINT_EXIT(_level_,_flag_,_msg_) + +#define IPOIB_ENTER(_flag_) + +#define IPOIB_EXIT(_flag_) + +#define IPOIB_TRACE_BYTES( lvl, ptr, len ) + +#endif + +#endif //EVENT_TRACING + + +enum ipoib_perf_counters +{ + SendBundle, + SendPackets, + PortSend, + GetEthHdr, + SendMgrQueue, + GetEndpt, + EndptQueue, + QueuePacket, + BuildSendDesc, + SendMgrFilter, + FilterIp, + QueryIp, + SendTcp, + FilterUdp, + QueryUdp, + SendUdp, + FilterDhcp, + FilterArp, + SendGen, + SendCopy, + PostSend, + ProcessFailedSends, + SendCompBundle, + SendCb, + PollSend, + SendComp, + FreeSendBuf, + RearmSend, + PortResume, + RecvCompBundle, + RecvCb, + PollRecv, + FilterRecv, + GetRecvEndpts, + GetEndptByGid, + GetEndptByLid, + EndptInsert, + RecvTcp, + RecvUdp, + RecvDhcp, + RecvArp, + RecvGen, + BuildPktArray, + PreparePkt, + GetNdisPkt, + RecvNdisIndicate, + PutRecvList, + RepostRecv, + GetRecv, + PostRecv, + RearmRecv, + ReturnPacket, + ReturnPutRecv, + ReturnRepostRecv, + ReturnPreparePkt, + ReturnNdisIndicate, + + /* Must be last! */ + MaxPerf + +}; + + +enum ref_cnt_buckets +{ + ref_init = 0, + ref_refresh_mcast, /* only used in refresh_mcast */ + ref_send_packets, /* only in send_packets */ + ref_get_recv, + ref_repost, /* only in __recv_mgr_repost */ + ref_recv_cb, /* only in __recv_cb */ + ref_send_cb, /* only in __send_cb */ + ref_port_up, + ref_get_bcast, + ref_bcast, /* join and create, used as base only */ + ref_join_mcast, + ref_leave_mcast, + ref_endpt_track, /* used when endpt is in port's child list. */ + + ref_array_size, /* Used to size the array of ref buckets. */ + ref_mask = 100, /* Used to differentiate derefs. */ + + ref_failed_recv_wc = 100 + ref_get_recv, + ref_recv_inv_len = 200 + ref_get_recv, + ref_recv_loopback = 300 + ref_get_recv, + ref_recv_filter = 400 + ref_get_recv, + + ref_bcast_get_cb = 100 + ref_get_bcast, + + ref_join_bcast = 100 + ref_bcast, + ref_create_bcast = 200 + ref_bcast, + ref_bcast_inv_state = 300 + ref_bcast, + ref_bcast_req_failed = 400 + ref_bcast, + ref_bcast_error = 500 + ref_bcast, + ref_bcast_join_failed = 600 + ref_bcast, + ref_bcast_create_failed = 700 + ref_bcast, + + ref_mcast_inv_state = 100 + ref_join_mcast, + ref_mcast_req_failed = 200 + ref_join_mcast, + ref_mcast_no_endpt = 300 + ref_join_mcast, + ref_mcast_av_failed = 400 + ref_join_mcast, + ref_mcast_join_failed = 500 + ref_join_mcast, + + ref_port_info_cb = 100 + ref_port_up + +}; + + +#endif /* _IPOIB_DEBUG_H_ */ diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib_driver.c b/branches/winverbs/ulp/ipoib/kernel6/ipoib_driver.c new file mode 100644 index 00000000..95d67cdf --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib_driver.c @@ -0,0 +1,4057 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_driver.c 4226 2009-04-06 06:01:03Z xalex $ + */ + +#include "limits.h" +#include "ipoib_driver.h" +#include "ipoib_debug.h" + +#if defined(EVENT_TRACING) +#ifdef offsetof +#undef offsetof +#endif +#include "ipoib_driver.tmh" +#endif + +#include "ipoib_port.h" +#include "ipoib_ibat.h" +#include +#include +#include +#include +#include "ntstrsafe.h" +#include "strsafe.h" +#include + + + +#define MAJOR_DRIVER_VERSION 2 +#define MINOR_DRIVER_VERSION 1 +#if defined(NDIS60_MINIPORT) +#define MAJOR_NDIS_VERSION 6 +#define MINOR_NDIS_VERSION 0 + +#else +#error NDIS Version not defined, try defining NDIS60_MINIPORT +#endif + +PDRIVER_OBJECT g_p_drv_obj; + + + +static const NDIS_OID SUPPORTED_OIDS[] = +{ + OID_GEN_SUPPORTED_LIST, + OID_GEN_HARDWARE_STATUS, + OID_GEN_MEDIA_SUPPORTED, + OID_GEN_MEDIA_IN_USE, + OID_GEN_MAXIMUM_LOOKAHEAD, + OID_GEN_MAXIMUM_FRAME_SIZE, + OID_GEN_LINK_SPEED, + OID_GEN_TRANSMIT_BUFFER_SPACE, + OID_GEN_RECEIVE_BUFFER_SPACE, + OID_GEN_TRANSMIT_BLOCK_SIZE, + OID_GEN_RECEIVE_BLOCK_SIZE, + OID_GEN_VENDOR_ID, + OID_GEN_VENDOR_DESCRIPTION, + OID_GEN_CURRENT_PACKET_FILTER, + OID_GEN_CURRENT_LOOKAHEAD, + OID_GEN_DRIVER_VERSION, + OID_GEN_MAXIMUM_TOTAL_SIZE, + OID_GEN_PROTOCOL_OPTIONS, + OID_GEN_MAC_OPTIONS, + OID_GEN_MEDIA_CONNECT_STATUS, + OID_GEN_MAXIMUM_SEND_PACKETS, + OID_GEN_NETWORK_LAYER_ADDRESSES, + OID_GEN_VENDOR_DRIVER_VERSION, + OID_GEN_PHYSICAL_MEDIUM, + OID_GEN_XMIT_OK, + OID_GEN_RCV_OK, + OID_GEN_XMIT_ERROR, + OID_GEN_RCV_ERROR, + OID_GEN_RCV_NO_BUFFER, + OID_GEN_DIRECTED_BYTES_XMIT, + OID_GEN_DIRECTED_FRAMES_XMIT, + OID_GEN_MULTICAST_BYTES_XMIT, + OID_GEN_MULTICAST_FRAMES_XMIT, + OID_GEN_BROADCAST_BYTES_XMIT, + OID_GEN_BROADCAST_FRAMES_XMIT, + OID_GEN_DIRECTED_BYTES_RCV, + OID_GEN_DIRECTED_FRAMES_RCV, + OID_GEN_MULTICAST_BYTES_RCV, + OID_GEN_MULTICAST_FRAMES_RCV, + OID_GEN_BROADCAST_BYTES_RCV, + OID_GEN_BROADCAST_FRAMES_RCV, + OID_802_3_PERMANENT_ADDRESS, + OID_802_3_CURRENT_ADDRESS, + OID_802_3_MULTICAST_LIST, + OID_802_3_MAXIMUM_LIST_SIZE, + OID_802_3_MAC_OPTIONS, + OID_802_3_RCV_ERROR_ALIGNMENT, + OID_802_3_XMIT_ONE_COLLISION, + OID_802_3_XMIT_MORE_COLLISIONS, + OID_TCP_TASK_OFFLOAD +}; + +static const unsigned char VENDOR_ID[] = {0x00, 0x06, 0x6A, 0x00}; + +#define VENDOR_DESCRIPTION "Internet Protocol over InfiniBand" + +#define IB_INFINITE_SERVICE_LEASE 0xFFFFFFFF + +//The mask is 8 bit and can't contain more than 6 non-zero bits +#define MAX_GUID_MAX 0xFC + + +/* Global driver debug level */ +uint32_t g_ipoib_dbg_level = TRACE_LEVEL_ERROR; +uint32_t g_ipoib_dbg_flags = 0x00000fff; +ipoib_globals_t g_ipoib = {0}; +NDIS_HANDLE g_IpoibMiniportDriverHandle = NULL; +NDIS_HANDLE g_IpoibDriverContext = NULL; + + + +typedef struct _IPOIB_REG_ENTRY +{ + NDIS_STRING RegName; // variable name text + BOOLEAN bRequired; // 1 -> required, 0 -> optional + UINT FieldOffset; // offset in parent struct + UINT FieldSize; // size (in bytes) of the field + UINT Default; // default value to use + UINT Min; // minimum value allowed + UINT Max; // maximum value allowed +} IPOIB_REG_ENTRY, *PIPOIB_REG_ENTRY; + +IPOIB_REG_ENTRY HCARegTable[] = { + // reg value name If Required Offset in parentr struct Field size Default Min Max + {NDIS_STRING_CONST("GUIDMask"), 0, IPOIB_OFFSET(guid_mask), IPOIB_SIZE(guid_mask), 0, 0, MAX_GUID_MAX}, + /* GUIDMask should be the first element */ + {NDIS_STRING_CONST("RqDepth"), 1, IPOIB_OFFSET(rq_depth), IPOIB_SIZE(rq_depth), 512, 128, 1024}, + {NDIS_STRING_CONST("RqLowWatermark"), 0, IPOIB_OFFSET(rq_low_watermark), IPOIB_SIZE(rq_low_watermark), 4, 2, 8}, + {NDIS_STRING_CONST("SqDepth"), 1, IPOIB_OFFSET(sq_depth), IPOIB_SIZE(sq_depth), 512, 128, 1024}, + {NDIS_STRING_CONST("SendChksum"), 1, IPOIB_OFFSET(send_chksum_offload), IPOIB_SIZE(send_chksum_offload),CSUM_ENABLED,CSUM_DISABLED,CSUM_BYPASS}, + {NDIS_STRING_CONST("RecvChksum"), 1, IPOIB_OFFSET(recv_chksum_offload), IPOIB_SIZE(recv_chksum_offload),CSUM_ENABLED,CSUM_DISABLED,CSUM_BYPASS}, + {NDIS_STRING_CONST("SaTimeout"), 1, IPOIB_OFFSET(sa_timeout), IPOIB_SIZE(sa_timeout), 1000, 250, UINT_MAX}, + {NDIS_STRING_CONST("SaRetries"), 1, IPOIB_OFFSET(sa_retry_cnt), IPOIB_SIZE(sa_retry_cnt), 10, 1, UINT_MAX}, + {NDIS_STRING_CONST("RecvRatio"), 1, IPOIB_OFFSET(recv_pool_ratio), IPOIB_SIZE(recv_pool_ratio), 1, 1, 10}, + {NDIS_STRING_CONST("PayloadMtu"), 1, IPOIB_OFFSET(payload_mtu), IPOIB_SIZE(payload_mtu), 2044, 512, MAX_UD_PAYLOAD_MTU}, + {NDIS_STRING_CONST("lso"), 0, IPOIB_OFFSET(lso), IPOIB_SIZE(lso), 0, 0, 1}, + {NDIS_STRING_CONST("MCLeaveRescan"), 1, IPOIB_OFFSET(mc_leave_rescan), IPOIB_SIZE(mc_leave_rescan), 260, 1, 3600}, + {NDIS_STRING_CONST("BCJoinRetry"), 1, IPOIB_OFFSET(bc_join_retry), IPOIB_SIZE(bc_join_retry), 50, 0, 1000}, + {NDIS_STRING_CONST("CmEnabled"), 0, IPOIB_OFFSET(cm_enabled), IPOIB_SIZE(cm_enabled), FALSE, FALSE, TRUE}, + {NDIS_STRING_CONST("CmPayloadMtu"), 1, IPOIB_OFFSET(cm_payload_mtu), IPOIB_SIZE(cm_payload_mtu), MAX_CM_PAYLOAD_MTU, 512, MAX_CM_PAYLOAD_MTU} + +}; + +#define IPOIB_NUM_REG_PARAMS (sizeof (HCARegTable) / sizeof(IPOIB_REG_ENTRY)) + + +void +ipoib_create_log( + NDIS_HANDLE h_adapter, + UINT ind, + ULONG eventLogMsgId) + +{ +#define cMaxStrLen 40 +#define cArrLen 3 + + PWCHAR logMsgArray[cArrLen]; + WCHAR strVal[cMaxStrLen]; + NDIS_STRING AdapterInstanceName; + + IPOIB_INIT_NDIS_STRING(&AdapterInstanceName); + if (NdisMQueryAdapterInstanceName(&AdapterInstanceName, h_adapter)!= NDIS_STATUS_SUCCESS ){ + ASSERT(FALSE); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR,IPOIB_DBG_ERROR, ("[IPoIB] Init:Failed to retreive adapter name.\n")); + return; + } + logMsgArray[0] = AdapterInstanceName.Buffer; + + if (RtlStringCbPrintfW(strVal, sizeof(strVal), L"0x%x", HCARegTable[ind].Default) != STATUS_SUCCESS) { + ASSERT(FALSE); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR,IPOIB_DBG_ERROR, + ("[IPoIB] Init: Problem copying string value: exiting\n")); + return; + } + + logMsgArray[0] = AdapterInstanceName.Buffer; + logMsgArray[1] = HCARegTable[ind].RegName.Buffer; + logMsgArray[2] = strVal; + + NdisWriteEventLogEntry(g_p_drv_obj, eventLogMsgId, 0, cArrLen, &logMsgArray, 0, NULL); + +} + + + +NTSTATUS +DriverEntry( + IN PDRIVER_OBJECT p_drv_obj, + IN PUNICODE_STRING p_reg_path ); + +VOID +ipoib_unload( + IN PDRIVER_OBJECT p_drv_obj ); + +NDIS_STATUS +ipoib_initialize_ex( + IN NDIS_HANDLE h_adapter, + IN NDIS_HANDLE config_context, + IN PNDIS_MINIPORT_INIT_PARAMETERS MiniportInitParameters); + +BOOLEAN +ipoib_check_for_hang( + IN NDIS_HANDLE adapter_context ); + +void +ipoib_halt_ex( + IN NDIS_HANDLE adapter_context, + IN NDIS_HALT_ACTION HaltAction); + +NDIS_STATUS +ipoib_query_info( + IN NDIS_HANDLE adapter_context, + IN NDIS_OID oid, + IN PVOID info_buf, + IN ULONG info_buf_len, + OUT PULONG p_bytes_written, + OUT PULONG p_bytes_needed ); + + + +NDIS_STATUS +ipoib_reset( + IN NDIS_HANDLE adapter_context, + OUT PBOOLEAN p_addr_reset); + +NDIS_STATUS +ipoib_set_info( + IN NDIS_HANDLE adapter_context, + IN NDIS_OID oid, + IN PVOID info_buf, + IN ULONG info_buf_length, + OUT PULONG p_bytes_read, + OUT PULONG p_bytes_needed ); + +//NDIS60 +void +ipoib_send_net_buffer_list( + IN NDIS_HANDLE adapter_context, + IN PNET_BUFFER_LIST net_buffer_list, + IN NDIS_PORT_NUMBER port_num, + IN ULONG send_flags); + +void +ipoib_pnp_notify( + IN NDIS_HANDLE adapter_context, + IN PNET_DEVICE_PNP_EVENT pnp_event); + +VOID +ipoib_shutdown_ex( + IN NDIS_HANDLE adapter_context, + IN NDIS_SHUTDOWN_ACTION shutdown_action); + + +void +ipoib_cancel_xmit( + IN NDIS_HANDLE adapter_context, + IN PVOID cancel_id ); + + +static void +ipoib_complete_query( + IN ipoib_adapter_t* const p_adapter, + IN pending_oid_t* const p_oid_info, + IN const NDIS_STATUS status, + IN const void* const p_buf, + IN const ULONG buf_len ); + +static NDIS_STATUS +__ipoib_set_net_addr( + IN ipoib_adapter_t * p_adapter, + IN PVOID info_buf, + IN ULONG info_buf_len, + OUT PULONG p_bytes_read, + OUT PULONG p_bytes_needed ); + +static NDIS_STATUS +__ipoib_get_tcp_task_offload( + IN ipoib_adapter_t* p_adapter, + OUT pending_oid_t *pNdisRequest); + +static void +__ipoib_ats_reg_cb( + IN ib_reg_svc_rec_t *p_reg_svc_rec ); + +static void +__ipoib_ats_dereg_cb( + IN void *context ); + +static NTSTATUS +__ipoib_read_registry( + IN UNICODE_STRING* const p_registry_path ); + +static NDIS_STATUS +ipoib_set_options( + IN NDIS_HANDLE NdisMiniportDriverHandle, + IN NDIS_HANDLE MiniportDriverContext); + +static NDIS_STATUS +ipoib_oid_handler( + IN NDIS_HANDLE adapter_context, + IN PNDIS_OID_REQUEST pNdisRequest); + +static void +ipoib_cancel_oid_request( + IN NDIS_HANDLE adapter_context, + IN PVOID requestId); + +static NDIS_STATUS +ipoib_pause( + IN NDIS_HANDLE adapter_context, + IN PNDIS_MINIPORT_PAUSE_PARAMETERS pause_parameters); + +static NDIS_STATUS +ipoib_restart( + IN NDIS_HANDLE adapter_context, + IN PNDIS_MINIPORT_RESTART_PARAMETERS restart_parameters); + + + +//! Standard Windows Device Driver Entry Point +/*! DriverEntry is the first routine called after a driver is loaded, and +is responsible for initializing the driver. On W2k this occurs when the PnP +Manager matched a PnP ID to one in an INF file that references this driver. +Any not success return value will cause the driver to fail to load. +IRQL = PASSIVE_LEVEL + +@param p_drv_obj Pointer to Driver Object for this device driver +@param p_registry_path Pointer to unicode string containing path to this driver's registry area +@return STATUS_SUCCESS, NDIS_STATUS_BAD_CHARACTERISTICS, NDIS_STATUS_BAD_VERSION, +NDIS_STATUS_RESOURCES, or NDIS_STATUS_FAILURE +*/ +NTSTATUS +DriverEntry( + IN PDRIVER_OBJECT p_drv_obj, + IN PUNICODE_STRING p_registry_path ) +{ + NDIS_STATUS status; + NDIS_MINIPORT_DRIVER_CHARACTERISTICS characteristics; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + g_p_drv_obj = p_drv_obj; + +#ifdef _DEBUG_ + PAGED_CODE(); +#endif +#if defined(EVENT_TRACING) + WPP_INIT_TRACING(p_drv_obj, p_registry_path); +#endif + status = CL_INIT; + if( !NT_SUCCESS( status ) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_init failed.\n") ); + return status; + } + + __ipoib_read_registry(p_registry_path); + + KeInitializeSpinLock( &g_ipoib.lock ); + cl_qlist_init( &g_ipoib.adapter_list ); + + NdisZeroMemory(&characteristics, sizeof(characteristics)); + + characteristics.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_DRIVER_CHARACTERISTICS, + characteristics.Header.Size = sizeof(NDIS_MINIPORT_DRIVER_CHARACTERISTICS); + characteristics.Header.Revision = NDIS_MINIPORT_DRIVER_CHARACTERISTICS_REVISION_1; + + characteristics.MajorNdisVersion = MAJOR_NDIS_VERSION; + characteristics.MinorNdisVersion = MINOR_NDIS_VERSION; + characteristics.MajorDriverVersion = MAJOR_DRIVER_VERSION; + characteristics.MinorDriverVersion = MINOR_DRIVER_VERSION; + + + characteristics.CheckForHangHandlerEx = ipoib_check_for_hang; + characteristics.HaltHandlerEx = ipoib_halt_ex; + characteristics.InitializeHandlerEx = ipoib_initialize_ex; + characteristics.OidRequestHandler = ipoib_oid_handler; + characteristics.CancelOidRequestHandler = ipoib_cancel_oid_request; + characteristics.ResetHandlerEx = ipoib_reset; + characteristics.DevicePnPEventNotifyHandler = ipoib_pnp_notify; + characteristics.ReturnNetBufferListsHandler = ipoib_return_net_buffer_list; + characteristics.SendNetBufferListsHandler = ipoib_send_net_buffer_list; + + characteristics.SetOptionsHandler = ipoib_set_options; + characteristics.PauseHandler = ipoib_pause; + characteristics.RestartHandler = ipoib_restart; + characteristics.UnloadHandler = ipoib_unload; + characteristics.CancelSendHandler = ipoib_cancel_xmit; + characteristics.ShutdownHandlerEx = ipoib_shutdown_ex; + + + +//TODO NDIS60 set g_ prefix to global variables + status = NdisMRegisterMiniportDriver( + p_drv_obj, p_registry_path,(PNDIS_HANDLE)g_IpoibDriverContext, &characteristics,&g_IpoibMiniportDriverHandle ); + if( status != NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisMRegisterMiniportDriver failed with status of %d\n", status) ); + CL_DEINIT; + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + +static NDIS_STATUS +ipoib_set_options( + IN NDIS_HANDLE NdisMiniportDriverHandle, + IN NDIS_HANDLE MiniportDriverContext + ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + UNREFERENCED_PARAMETER(NdisMiniportDriverHandle); + UNREFERENCED_PARAMETER(MiniportDriverContext); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_SUCCESS; +} + +static NTSTATUS +__ipoib_read_registry( + IN UNICODE_STRING* const p_registry_path ) +{ + NTSTATUS status; + /* Remember the terminating entry in the table below. */ + RTL_QUERY_REGISTRY_TABLE table[4]; + UNICODE_STRING param_path; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + RtlInitUnicodeString( ¶m_path, NULL ); + param_path.MaximumLength = p_registry_path->Length + + sizeof(L"\\Parameters"); + param_path.Buffer = cl_zalloc( param_path.MaximumLength ); + if( !param_path.Buffer ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate parameters path buffer.\n") ); + return STATUS_INSUFFICIENT_RESOURCES; + } + + RtlAppendUnicodeStringToString( ¶m_path, p_registry_path ); + RtlAppendUnicodeToString( ¶m_path, L"\\Parameters" ); + + /* + * Clear the table. This clears all the query callback pointers, + * and sets up the terminating table entry. + */ + cl_memclr( table, sizeof(table) ); + + /* Setup the table entries. */ + table[0].Flags = RTL_QUERY_REGISTRY_DIRECT; + table[0].Name = L"DebugLevel"; + table[0].EntryContext = &g_ipoib_dbg_level; + table[0].DefaultType = REG_DWORD; + table[0].DefaultData = &g_ipoib_dbg_level; + table[0].DefaultLength = sizeof(ULONG); + + table[1].Flags = RTL_QUERY_REGISTRY_DIRECT; + table[1].Name = L"DebugFlags"; + table[1].EntryContext = &g_ipoib_dbg_flags; + table[1].DefaultType = REG_DWORD; + table[1].DefaultData = &g_ipoib_dbg_flags; + table[1].DefaultLength = sizeof(ULONG); + + table[2].Flags = RTL_QUERY_REGISTRY_DIRECT; + table[2].Name = L"bypass_check_bcast_rate"; + table[2].EntryContext = &g_ipoib.bypass_check_bcast_rate; + table[2].DefaultType = REG_DWORD; + table[2].DefaultData = &g_ipoib.bypass_check_bcast_rate; + table[2].DefaultLength = sizeof(ULONG); + + /* Have at it! */ + status = RtlQueryRegistryValues( RTL_REGISTRY_ABSOLUTE, + param_path.Buffer, table, NULL, NULL ); + + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("debug level %d debug flags 0x%.8x\n", + g_ipoib_dbg_level, + g_ipoib_dbg_flags)); + +#if DBG + if( g_ipoib_dbg_flags & IPOIB_DBG_ERR ) + g_ipoib_dbg_flags |= CL_DBG_ERROR; +#endif + + cl_free( param_path.Buffer ); + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +VOID +ipoib_unload( + IN PDRIVER_OBJECT p_drv_obj ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + UNREFERENCED_PARAMETER(p_drv_obj); + #if defined(EVENT_TRACING) + WPP_CLEANUP(p_drv_obj); + #endif + //NDIS6.0 + NdisMDeregisterMiniportDriver(g_IpoibMiniportDriverHandle); + UNREFERENCED_PARAMETER( p_drv_obj ); + CL_DEINIT; + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + + +NDIS_STATUS +ipoib_get_adapter_params( + IN NDIS_HANDLE* const wrapper_config_context, + IN OUT ipoib_adapter_t *p_adapter, + OUT PUCHAR *p_mac, + OUT UINT *p_len) +{ + NDIS_STATUS status; + NDIS_HANDLE h_config; + NDIS_CONFIGURATION_OBJECT config_obj; + NDIS_CONFIGURATION_PARAMETER *p_param; + UINT value; + PIPOIB_REG_ENTRY pRegEntry; + UINT i; + PUCHAR structPointer; + + int sq_depth_step = 128; + + UNUSED_PARAM(wrapper_config_context); + IPOIB_ENTER( IPOIB_DBG_INIT ); + + config_obj.Header.Type = NDIS_OBJECT_TYPE_CONFIGURATION_OBJECT; + config_obj.Header.Revision = NDIS_CONFIGURATION_OBJECT_REVISION_1; + config_obj.Header.Size = sizeof(NDIS_CONFIGURATION_OBJECT); + config_obj.NdisHandle = p_adapter->h_adapter; + config_obj.Flags = 0; + + status = NdisOpenConfigurationEx( &config_obj, &h_config); + if( status != NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisOpenConfigurationEx returned 0x%.8x\n", status) ); + return status; + } + + // read all the registry values + for (i = 0, pRegEntry = HCARegTable; i < IPOIB_NUM_REG_PARAMS; ++i) + { + // initialize pointer to appropriate place inside 'params' + structPointer = (PUCHAR) &p_adapter->params + pRegEntry[i].FieldOffset; + + // Get the configuration value for a specific parameter. Under NT the + // parameters are all read in as DWORDs. + NdisReadConfiguration( + &status, + &p_param, + h_config, + &pRegEntry[i].RegName, + NdisParameterInteger); + + // If the parameter was present, then check its value for validity. + if (status == NDIS_STATUS_SUCCESS) + { + // Check that param value is not too small or too large + if (p_param->ParameterData.IntegerData < pRegEntry[i].Min || + p_param->ParameterData.IntegerData > pRegEntry[i].Max) + { + value = pRegEntry[i].Default; + ipoib_create_log(p_adapter->h_adapter, i, EVENT_IPOIB_WRONG_PARAMETER_WRN); + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_INIT, ("Read configuration.Registry %S value is out of range, setting default value= 0x%x\n", pRegEntry[i].RegName.Buffer, value)); + + } + else + { + value = p_param->ParameterData.IntegerData; + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_INIT, ("Read configuration. Registry %S, Value= 0x%x\n", pRegEntry[i].RegName.Buffer, value)); + } + } + + else + { + value = pRegEntry[i].Default; + status = NDIS_STATUS_SUCCESS; + if (pRegEntry[i].bRequired) + { + ipoib_create_log(p_adapter->h_adapter, i, EVENT_IPOIB_WRONG_PARAMETER_ERR); + IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_INIT, ("Read configuration.Registry %S value not found, setting default value= 0x%x\n", pRegEntry[i].RegName.Buffer, value)); + } + else + { + ipoib_create_log(p_adapter->h_adapter, i, EVENT_IPOIB_WRONG_PARAMETER_INFO); + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_INIT, ("Read configuration. Registry %S value not found, Value= 0x%x\n", pRegEntry[i].RegName.Buffer, value)); + } + + } + // + // Store the value in the adapter structure. + // + switch(pRegEntry[i].FieldSize) + { + case 1: + *((PUCHAR) structPointer) = (UCHAR) value; + break; + + case 2: + *((PUSHORT) structPointer) = (USHORT) value; + break; + + case 4: + *((PULONG) structPointer) = (ULONG) value; + break; + + default: + IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, ("Bogus field size %d\n", pRegEntry[i].FieldSize)); + break; + } + } + + // Send queue depth needs to be a power of two + //static const INT sq_depth_step = 128; + + if (p_adapter->params.sq_depth % sq_depth_step) { + static const c_sq_ind = 2; + p_adapter->params.sq_depth = sq_depth_step *( + p_adapter->params.sq_depth / sq_depth_step + !!( (p_adapter->params.sq_depth % sq_depth_step) > (sq_depth_step/2) )); + ipoib_create_log(p_adapter->h_adapter, c_sq_ind, EVENT_IPOIB_WRONG_PARAMETER_WRN); + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_INIT, ("SQ DEPTH value was rounded to the closest acceptable value of 0x%x\n", p_adapter->params.sq_depth )); + + } + + + // Adjusting the low watermark parameter + p_adapter->params.rq_low_watermark = + p_adapter->params.rq_depth / p_adapter->params.rq_low_watermark; + + /* disable CM if LSO is active */ + if( p_adapter->params.cm_enabled ) + { + p_adapter->params.cm_enabled = !p_adapter->params.lso; + if( !p_adapter->params.cm_enabled ) + { + NdisWriteErrorLogEntry( p_adapter->h_adapter, + EVENT_IPOIB_CONNECTED_MODE_ERR, 1, 0xbadc0de0 ); + } + } + + if( p_adapter->params.cm_enabled ) + { + p_adapter->params.cm_xfer_block_size = + (sizeof(eth_hdr_t) + p_adapter->params.cm_payload_mtu); + } + + p_adapter->params.xfer_block_size = + (sizeof(eth_hdr_t) + p_adapter->params.payload_mtu); + + NdisReadNetworkAddress( &status, p_mac, p_len, h_config ); + + NdisCloseConfiguration( h_config ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_SUCCESS; +} + + +NDIS_STATUS +ipoib_get_adapter_guids( + IN NDIS_HANDLE* const h_adapter, + IN OUT ipoib_adapter_t *p_adapter ) +{ + NTSTATUS status; + ib_al_ifc_data_t data; + IO_STACK_LOCATION io_stack, *p_fwd_io_stack; + DEVICE_OBJECT *p_pdo; + IRP *p_irp; + KEVENT event; + IO_STATUS_BLOCK io_status; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + NdisMGetDeviceProperty( h_adapter, &p_pdo, NULL, NULL, NULL, NULL ); + + /* Query for our interface */ + data.size = sizeof(ipoib_ifc_data_t); + data.version = IPOIB_INTERFACE_DATA_VERSION; + data.type = &GUID_IPOIB_INTERFACE_DATA; + data.p_data = &p_adapter->guids; + + io_stack.MinorFunction = IRP_MN_QUERY_INTERFACE; + io_stack.Parameters.QueryInterface.Version = AL_INTERFACE_VERSION; + io_stack.Parameters.QueryInterface.Size = sizeof(ib_al_ifc_t); + io_stack.Parameters.QueryInterface.Interface = + (INTERFACE*)p_adapter->p_ifc; + io_stack.Parameters.QueryInterface.InterfaceSpecificData = &data; + io_stack.Parameters.QueryInterface.InterfaceType = + &GUID_IB_AL_INTERFACE; + + KeInitializeEvent( &event, NotificationEvent, FALSE ); + + /* Build the IRP for the HCA. */ + p_irp = IoBuildSynchronousFsdRequest( IRP_MJ_PNP, p_pdo, + NULL, 0, NULL, &event, &io_status ); + if( !p_irp ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate query interface IRP.\n") ); + return STATUS_INSUFFICIENT_RESOURCES; + } + + /* Copy the request query parameters. */ + p_fwd_io_stack = IoGetNextIrpStackLocation( p_irp ); + p_fwd_io_stack->MinorFunction = IRP_MN_QUERY_INTERFACE; + p_fwd_io_stack->Parameters.QueryInterface = + io_stack.Parameters.QueryInterface; + p_irp->IoStatus.Status = STATUS_NOT_SUPPORTED; + + /* Send the IRP. */ + status = IoCallDriver( p_pdo, p_irp ); + if( status == STATUS_PENDING ) + { + KeWaitForSingleObject( &event, Executive, KernelMode, + FALSE, NULL ); + status = io_status.Status; + } + + if( !NT_SUCCESS( status ) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Query interface for IPOIB interface returned %08x.\n", status) ); + return status; + } + + /* + * Dereference the interface now so that the bus driver doesn't fail a + * query remove IRP. We will always get unloaded before the bus driver + * since we're a child device. + */ + if (p_adapter->p_ifc) + p_adapter->p_ifc->wdm.InterfaceDereference( + p_adapter->p_ifc->wdm.Context ); + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_SUCCESS; +} + + +//! Initialization function called for each IOC discovered +/* The MiniportInitialize function is a required function that sets up a +NIC (or virtual NIC) for network I/O operations, claims all hardware +resources necessary to the NIC in the registry, and allocates resources +the driver needs to carry out network I/O operations. +IRQL = PASSIVE_LEVEL + +@param p_open_status Pointer to a status field set if this function returns NDIS_STATUS_OPEN_ERROR +@param p_selected_medium_index Pointer to unsigned integer noting index into medium_array for this NIC +@param medium_array Array of mediums for this NIC +@param medium_array_size Number of elements in medium_array +@param h_adapter Handle assigned by NDIS for this NIC +@param wrapper_config_context Handle used for Ndis initialization functions +@return NDIS_STATUS_SUCCESS, NDIS_STATUS_UNSUPPORTED_MEDIA, NDIS_STATUS_RESOURCES, +NDIS_STATUS_NOT_SUPPORTED +*/ + +/*void foo1(int i) +{ + char temp[5200]; + if (i ==0) return; + cl_msg_out("i = %d\n", i); + foo1(i-1); + +}*/ + +NDIS_STATUS +SetDeviceRegistrationAttributes( + ipoib_adapter_t *p_adapter, + NDIS_HANDLE h_adapter + ) +{ + NDIS_MINIPORT_ADD_DEVICE_REGISTRATION_ATTRIBUTES atr; + NTSTATUS Status; + + NdisZeroMemory(&atr, sizeof(NDIS_MINIPORT_ADD_DEVICE_REGISTRATION_ATTRIBUTES)); + + // + // setting registration attributes + // + atr.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADD_DEVICE_REGISTRATION_ATTRIBUTES; + atr.Header.Revision = NDIS_MINIPORT_ADD_DEVICE_REGISTRATION_ATTRIBUTES_REVISION_1; + atr.Header.Size = NDIS_SIZEOF_MINIPORT_ADD_DEVICE_REGISTRATION_ATTRIBUTES_REVISION_1; + + + atr.MiniportAddDeviceContext = (NDIS_HANDLE)p_adapter; + atr.Flags = 0; + + Status = NdisMSetMiniportAttributes(h_adapter, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&atr); + + return Status; +} + +//NDIS 6.1 +#if 0 +NDIS_STATUS +SetHardwareAssistAttributes( + ipoib_adapter_t *p_adapter, + NDIS_HANDLE h_adapter + ) +{ + NDIS_MINIPORT_ADAPTER_HARDWARE_ASSIST_ATTRIBUTES atr; + NTSTATUS Status; + + NdisZeroMemory(&atr, sizeof(NDIS_MINIPORT_ADAPTER_HARDWARE_ASSIST_ATTRIBUTES)); + + // + // setting registration attributes + // + atr.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_HARDWARE_ASSIST_ATTRIBUTES; + atr.Header.Revision = NDIS_MINIPORT_ADAPTER_HARDWARE_ASSIST_ATTRIBUTES_REVISION_1; + atr.Header.Size = NDIS_SIZEOF_MINIPORT_ADAPTER_HARDWARE_ASSIST_ATTRIBUTES_REVISION_1; + + NDIS_HD_SPLIT_ATTRIBUTES nhsa; + NdisZeroMemory(&nhsa, sizeof(nhsa)); + + nhsa.Header.Type = NDIS_OBJECT_TYPE_HD_SPLIT_ATTRIBUTES; + nhsa.Header.Revision = NDIS_OFFLOAD_REVISION_1; + nhsa.Header.Size = NDIS_SIZEOF_HD_SPLIT_ATTRIBUTES_REVISION_1; + + // BUGBUG: We are just cheating here ... + nhsa.HardwareCapabilities = NDIS_HD_SPLIT_CAPS_SUPPORTS_HEADER_DATA_SPLIT; +#if 0 + ... Only supported on B0 + + NDIS_HD_SPLIT_CAPS_SUPPORTS_IPV4_OPTIONS | + NDIS_HD_SPLIT_CAPS_SUPPORTS_IPV6_EXTENSION_HEADERS | + NDIS_HD_SPLIT_CAPS_SUPPORTS_TCP_OPTIONS; +#endif + + // The bellow should be left zero + if (pPort->Config.HeaderDataSplit) { + nhsa.CurrentCapabilities = NDIS_HD_SPLIT_CAPS_SUPPORTS_HEADER_DATA_SPLIT; + } else { + nhsa.CurrentCapabilities = 0; + } + + nhsa.HDSplitFlags = 0; + nhsa.BackfillSize = 0; + nhsa.MaxHeaderSize = 0; + + atr.HDSplitAttributes = &nhsa; + + Status = NdisMSetMiniportAttributes(h_adapter, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&atr); + + if (nhsa.HDSplitFlags & NDIS_HD_SPLIT_ENABLE_HEADER_DATA_SPLIT) { + ASSERT(pPort->Config.HeaderDataSplit == TRUE); + pPort->Config.HeaderDataSplit = TRUE; + } + else { + ASSERT(pPort->Config.HeaderDataSplit == FALSE); + pPort->Config.HeaderDataSplit = FALSE; + } + + return Status; +} +#endif + +/*++ +Routine Description: + the routine sets attributes that are associated with a miniport adapter. + +Arguments: + pPort - Pointer to port object + +Return Value: + NDIS_STATUS + +Note: + Should be called in PASSIVE_LEVEL + +--*/ +NDIS_STATUS +SetAdapterRegistrationAttributes( + ipoib_adapter_t *p_adapter, + NDIS_HANDLE h_adapter + ) + { + NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES atr; + NTSTATUS Status; + + NdisZeroMemory(&atr, sizeof(NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES)); + + /* setting registration attributes */ + + atr.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES; + atr.Header.Revision = NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1; + atr.Header.Size = NDIS_SIZEOF_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1; + //TODO NDIS60 Port or adapter + atr.MiniportAdapterContext = (NDIS_HANDLE)p_adapter; //(NDIS_HANDLE)pPort->p_adapter; + atr.AttributeFlags = NDIS_MINIPORT_ATTRIBUTES_BUS_MASTER; + atr.CheckForHangTimeInSeconds = 10; + atr.InterfaceType = NdisInterfacePci ; // ???? UH + //TODO NDIS60 PNP or PCI ? + //RegistrationAttributes.InterfaceType = NdisInterfacePNPBus; + + Status = NdisMSetMiniportAttributes(h_adapter, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&atr); + + return Status; +} + + +/*++ +Routine Description: + the routine sets generic attributes that are associated with a miniport + adapter. + +Arguments: + pPort - Pointer to port object + +Return Value: + NDIS_STATUS + +Note: + Should be called in PASSIVE_LEVEL + +--*/ +NDIS_STATUS +SetGenericAttributes( + ipoib_adapter_t *p_adapter, + NDIS_HANDLE h_adapter + ) +{ + NDIS_STATUS Status; + + NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES gat; + NdisZeroMemory(&gat, sizeof(NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES)); + + /* set up generic attributes */ + + gat.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES; + gat.Header.Revision = NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES_REVISION_1; + gat.Header.Size = sizeof(NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES); + + gat.MediaType = NdisMedium802_3; + gat.MaxXmitLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + gat.MaxRcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + gat.XmitLinkSpeed = IPOIB_MEDIA_MAX_SPEED; //TODO NDIS60 NDIS_LINK_SPEED_UNKNOWN + gat.RcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; // TODO NDIS60 NDIS_LINK_SPEED_UNKNOWN ??? + + gat.MediaConnectState = MediaConnectStateConnected; //TODO NDIS60 Check the current state + gat.MediaDuplexState = MediaDuplexStateFull; + + gat.MtuSize = MAX_IB_MTU; + gat.LookaheadSize = MAX_XFER_BLOCK_SIZE; + gat.MacOptions = NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA | + NDIS_MAC_OPTION_TRANSFERS_NOT_PEND | + NDIS_MAC_OPTION_NO_LOOPBACK | + NDIS_MAC_OPTION_FULL_DUPLEX; + //NDIS_MAC_OPTION_8021P_PRIORITY; //TODO NDIS60 + // DT: Enable for Header Data Split WHQL + // | NDIS_MAC_OPTION_8021Q_VLAN; + + gat.SupportedPacketFilters = NDIS_PACKET_TYPE_DIRECTED | + NDIS_PACKET_TYPE_MULTICAST | + //NDIS_PACKET_TYPE_ALL_MULTICAST | + NDIS_PACKET_TYPE_BROADCAST; + + gat.MaxMulticastListSize = MAX_MCAST; + + gat.MacAddressLength = HW_ADDR_LEN; + + NdisMoveMemory(gat.PermanentMacAddress, + p_adapter->mac.addr, + HW_ADDR_LEN); + + NdisMoveMemory(gat.CurrentMacAddress, + p_adapter->params.conf_mac.addr, + HW_ADDR_LEN); + + + gat.PhysicalMediumType = NdisPhysicalMedium802_3; + gat.AccessType = NET_IF_ACCESS_BROADCAST; + + gat.SupportedOidList = (PNDIS_OID)SUPPORTED_OIDS; + gat.SupportedOidListLength = sizeof(SUPPORTED_OIDS); + + + gat.DirectionType = NET_IF_DIRECTION_SENDRECEIVE; + gat.ConnectionType = NET_IF_CONNECTION_DEDICATED; + gat.IfType = IF_TYPE_ETHERNET_CSMACD; + gat.IfConnectorPresent = TRUE; + //TODO NDIS60 This value is absent for ETH driver + gat.AccessType = NET_IF_ACCESS_BROADCAST; // NET_IF_ACCESS_BROADCAST for a typical ethernet adapter + + + //TODO NDIS60 is it possible to reduce unsupported statistics + gat.SupportedStatistics = + NDIS_STATISTICS_XMIT_OK_SUPPORTED | + NDIS_STATISTICS_RCV_OK_SUPPORTED | + NDIS_STATISTICS_XMIT_ERROR_SUPPORTED | + NDIS_STATISTICS_RCV_ERROR_SUPPORTED | + NDIS_STATISTICS_RCV_CRC_ERROR_SUPPORTED | + NDIS_STATISTICS_RCV_NO_BUFFER_SUPPORTED | + NDIS_STATISTICS_TRANSMIT_QUEUE_LENGTH_SUPPORTED; + + //SupportedStatistics = NDIS_STATISTICS_XMIT_OK_SUPPORTED | + // NDIS_STATISTICS_GEN_STATISTICS_SUPPORTED; + + + // + // Set power management capabilities + // + gat.PowerManagementCapabilities = NULL; +#if 0 + NDIS_PNP_CAPABILITIES PowerManagementCapabilities; + NdisZeroMemory(&PowerManagementCapabilities, sizeof(NDIS_PNP_CAPABILITIES)); + if (MPIsPoMgmtSupported(pPort)) + { + MPFillPoMgmtCaps(pPort, &PowerManagementCapabilities, &Status, &unUsed); + ASSERT(NT_SUCCESS(Status)); + gat.PowerManagementCapabilities = &PowerManagementCapabilities; + } + else + { + + } +#endif + + // + // Set RSS attributes + // + gat.RecvScaleCapabilities = NULL; +#if 0 + NDIS_RECEIVE_SCALE_CAPABILITIES RssCapabilities; + NdisZeroMemory(&RssCapabilities, sizeof(PNDIS_RECEIVE_SCALE_CAPABILITIES)); + Status = MPFillRssCapabilities(pPort, &RssCapabilities, &unUsed); + if (NT_SUCCESS(Status)) + { + gat.RecvScaleCapabilities = &RssCapabilities; + } + else + { + // + // do not fail the call because of failure to get PM caps + // + Status = NDIS_STATUS_SUCCESS; + gat.RecvScaleCapabilities = NULL; + } +#endif + + Status = NdisMSetMiniportAttributes(h_adapter, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&gat); + + return Status; +} + + +/*++ +Routine Description: + The routine sets an NDIS_OFFLOAD structure indicates the current offload + capabilities that are provided by the miniport adapter + +Arguments: + pPort - a pointer to port object + offload - reference to NDIS_OFFLOAD object that should be filled + +Return Value: + None. + +--*/ +static +void +OffloadConfig( + ipoib_adapter_t *p_adapter, + NDIS_OFFLOAD *p_offload + ) +{ + + ULONG ulEncapsulation = NDIS_ENCAPSULATION_IEEE_802_3 | NDIS_ENCAPSULATION_IEEE_802_3_P_AND_Q; + + NdisZeroMemory(p_offload, NDIS_SIZEOF_NDIS_OFFLOAD_REVISION_1); + + p_offload->Header.Type = NDIS_OBJECT_TYPE_OFFLOAD; + p_offload->Header.Revision = NDIS_OFFLOAD_REVISION_1; // BUGBUG: do we need to support revision 2? UH 17-May-2008 + p_offload->Header.Size = NDIS_SIZEOF_NDIS_OFFLOAD_REVISION_1; + + p_offload->Checksum.IPv4Transmit.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv4Transmit.IpOptionsSupported = + p_offload->Checksum.IPv4Transmit.TcpOptionsSupported = + p_offload->Checksum.IPv4Transmit.TcpChecksum = + p_offload->Checksum.IPv4Transmit.UdpChecksum = + p_offload->Checksum.IPv4Transmit.IpChecksum =!!(p_adapter->params.send_chksum_offload); + + p_offload->Checksum.IPv4Receive.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv4Receive.IpOptionsSupported = + p_offload->Checksum.IPv4Receive.TcpOptionsSupported = + p_offload->Checksum.IPv4Receive.TcpChecksum = + p_offload->Checksum.IPv4Receive.UdpChecksum = + p_offload->Checksum.IPv4Receive.IpChecksum = !!(p_adapter->params.recv_chksum_offload); //TODO NDIS60 + + + p_offload->Checksum.IPv6Transmit.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv6Transmit.IpExtensionHeadersSupported = + p_offload->Checksum.IPv6Transmit.TcpOptionsSupported = + p_offload->Checksum.IPv6Transmit.TcpChecksum = + p_offload->Checksum.IPv6Transmit.UdpChecksum = FALSE; + + + p_offload->Checksum.IPv6Receive.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv6Receive.IpExtensionHeadersSupported = + p_offload->Checksum.IPv6Receive.TcpOptionsSupported = + p_offload->Checksum.IPv6Receive.TcpChecksum = + p_offload->Checksum.IPv6Receive.UdpChecksum = FALSE; + + if (p_adapter->params.lso) + { + p_offload->LsoV1.IPv4.Encapsulation = ulEncapsulation; + p_offload->LsoV1.IPv4.MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; +#define LSO_MIN_SEG_COUNT 2 + p_offload->LsoV1.IPv4.MinSegmentCount = LSO_MIN_SEG_COUNT; + + + p_offload->LsoV1.IPv4.TcpOptions = NDIS_OFFLOAD_SUPPORTED; + p_offload->LsoV1.IPv4.IpOptions = NDIS_OFFLOAD_SUPPORTED; + + p_offload->LsoV2.IPv4.Encapsulation = ulEncapsulation; + p_offload->LsoV2.IPv4.MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; + p_offload->LsoV2.IPv4.MinSegmentCount = LSO_MIN_SEG_COUNT; + + p_offload->LsoV2.IPv6.Encapsulation = ulEncapsulation; + p_offload->LsoV2.IPv6.MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; + p_offload->LsoV2.IPv6.MinSegmentCount = LSO_MIN_SEG_COUNT; + + p_offload->LsoV2.IPv6.IpExtensionHeadersSupported = NDIS_OFFLOAD_NOT_SUPPORTED; + p_offload->LsoV2.IPv6.TcpOptionsSupported = NDIS_OFFLOAD_SUPPORTED; + } + +} + + +/*++ +Routine Description: + The routine sets an NDIS_OFFLOAD structure that indicates all the task + offload capabilites that are supported by the NIC. These capabilities include + capabilities that are currently disabled by standardized keywords in the registry. + +Arguments: + offload - reference to NDIS_OFFLOAD object that should be filled + +Return Value: + None. + +--*/ +static +void +OffloadCapabilities( + NDIS_OFFLOAD *p_offload + ) +{ + ULONG ulEncapsulation = NDIS_ENCAPSULATION_IEEE_802_3 | NDIS_ENCAPSULATION_IEEE_802_3_P_AND_Q ; + NdisZeroMemory(p_offload, NDIS_SIZEOF_NDIS_OFFLOAD_REVISION_1); + + p_offload->Header.Type = NDIS_OBJECT_TYPE_OFFLOAD; + p_offload->Header.Revision = NDIS_OFFLOAD_REVISION_1; // BUGBUG: do we need to support revision 2? UH 17-May-2008 + p_offload->Header.Size = NDIS_SIZEOF_NDIS_OFFLOAD_REVISION_1; + + p_offload->Checksum.IPv4Transmit.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv4Transmit.IpOptionsSupported = TRUE; + p_offload->Checksum.IPv4Transmit.TcpOptionsSupported = TRUE; + p_offload->Checksum.IPv4Transmit.TcpChecksum = TRUE; + p_offload->Checksum.IPv4Transmit.UdpChecksum = TRUE; + p_offload->Checksum.IPv4Transmit.IpChecksum = TRUE; + + p_offload->Checksum.IPv4Receive.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv4Receive.IpOptionsSupported = TRUE; + p_offload->Checksum.IPv4Receive.TcpOptionsSupported = TRUE; + p_offload->Checksum.IPv4Receive.TcpChecksum = TRUE; + p_offload->Checksum.IPv4Receive.UdpChecksum = TRUE; + p_offload->Checksum.IPv4Receive.IpChecksum = TRUE; + + + // + // BUGBUG:: + // During a HW bug that didn't handle correctly packets with + // IPv6 Extension Headers -> we set IpExtensionHeadersSupported to TRUE + // + p_offload->Checksum.IPv6Transmit.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv6Transmit.IpExtensionHeadersSupported = TRUE; + p_offload->Checksum.IPv6Transmit.TcpOptionsSupported = TRUE; + p_offload->Checksum.IPv6Transmit.TcpChecksum = TRUE; + p_offload->Checksum.IPv6Transmit.UdpChecksum = TRUE; + + + p_offload->Checksum.IPv6Receive.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv6Receive.IpExtensionHeadersSupported = TRUE; + p_offload->Checksum.IPv6Receive.TcpOptionsSupported = TRUE; + p_offload->Checksum.IPv6Receive.TcpChecksum = TRUE; + p_offload->Checksum.IPv6Receive.UdpChecksum = TRUE; + + p_offload->LsoV1.IPv4.Encapsulation = ulEncapsulation; + p_offload->LsoV1.IPv4.MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; + p_offload->LsoV1.IPv4.MinSegmentCount = 2; + p_offload->LsoV1.IPv4.TcpOptions = NDIS_OFFLOAD_SUPPORTED; + p_offload->LsoV1.IPv4.IpOptions = NDIS_OFFLOAD_SUPPORTED; + + p_offload->LsoV2.IPv4.Encapsulation = ulEncapsulation; + p_offload->LsoV2.IPv4.MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; + p_offload->LsoV2.IPv4.MinSegmentCount = 2; + + p_offload->LsoV2.IPv6.Encapsulation = ulEncapsulation; + p_offload->LsoV2.IPv6.MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; + p_offload->LsoV2.IPv6.MinSegmentCount = 2; + + p_offload->LsoV2.IPv6.IpExtensionHeadersSupported = NDIS_OFFLOAD_NOT_SUPPORTED; + p_offload->LsoV2.IPv6.TcpOptionsSupported = NDIS_OFFLOAD_SUPPORTED; + + } + + +/*++ +Routine Description: + The routine sets offload attributes that are associated with a miniport + adapter. + +Arguments: + pPort - Pointer to port object + +Return Value: + NDIS_STATUS + +Note: + Should be called in PASSIVE_LEVEL + +--*/ +NDIS_STATUS +SetOffloadAttributes( + ipoib_adapter_t *p_adapter, + NDIS_HANDLE h_adapter + ) +{ + NDIS_STATUS Status; + NDIS_OFFLOAD offload,hwOffload; + //ULONG ulEncapsulation = NDIS_ENCAPSULATION_IEEE_802_3 | NDIS_ENCAPSULATION_IEEE_802_3_P_AND_Q; + + NDIS_MINIPORT_ADAPTER_OFFLOAD_ATTRIBUTES oat; + NdisZeroMemory(&oat, sizeof(NDIS_MINIPORT_ADAPTER_OFFLOAD_ATTRIBUTES)); + + oat.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_OFFLOAD_ATTRIBUTES; + oat.Header.Revision = NDIS_MINIPORT_ADAPTER_OFFLOAD_ATTRIBUTES_REVISION_1; + oat.Header.Size = NDIS_SIZEOF_MINIPORT_ADAPTER_OFFLOAD_ATTRIBUTES_REVISION_1; + + + OffloadConfig(p_adapter, &offload); + + + OffloadCapabilities(&hwOffload); + + oat.DefaultOffloadConfiguration = &offload; + oat.HardwareOffloadCapabilities = &hwOffload; + + Status = NdisMSetMiniportAttributes(h_adapter, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&oat); + + return Status; +} + + +/*++ + +Routine Description: + An NDIS 6.0 miniport driver must call NdisMSetMiniportAttributes + at least twice. The first call is to register itself with NDIS. + The second call is to register the miniport driver's general + attributes with NDIS. + + NdisMSetMiniportAttributes takes a parameter of type + NDIS_MINIPORT_ADAPTER_ATTRIBUTES, which is a union of several miniport + adapter attributes. Miniport drivers must first call + NdisMSetMiniportAttributes and pass in an + NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES structure + that contains the pointer to its own context area, attribute flags, + check-for-hang time, and interface type. + + All NDIS 6.0 miniport drivers are deserialized by default. + +Arguments: + pPort - Pointer to port object + +Return Value: + NDIS_STATUS + +Note: + Should be called in PASSIVE_LEVEL + +--*/ + NDIS_STATUS + SetAttributes( + ipoib_adapter_t *p_adapter, + NDIS_HANDLE h_adapter + ) + { + NTSTATUS Status; + + + Status = SetDeviceRegistrationAttributes(p_adapter, h_adapter); + if (Status != NDIS_STATUS_SUCCESS) + { + //ETH_PRINT(TRACE_LEVEL_ERROR, ETH_INIT, "Set device registration failed Error=0x%x\n", Status); + return Status; + } + + + Status = SetAdapterRegistrationAttributes(p_adapter, h_adapter); + if (Status != NDIS_STATUS_SUCCESS) + { + //ETH_PRINT(TRACE_LEVEL_ERROR, ETH_INIT, "Set adapter attributes failed Error=0x%x\n", Status); + return Status; + } + + Status = SetOffloadAttributes(p_adapter, h_adapter); + if (Status != NDIS_STATUS_SUCCESS) + { + //ETH_PRINT(TRACE_LEVEL_ERROR, ETH_INIT, "Set OFFLOAD attributes failed Error=0x%x\n", Status); + return Status; + } + +#if 0 + if(!pPort->Config.fWHQL) + { + Status = SetHardwareAssistAttributes(pPort); + if (Status != NDIS_STATUS_SUCCESS) + { + //ETH_PRINT(TRACE_LEVEL_ERROR, ETH_INIT, "Set Hardware Assist Attributes failed Error=0x%x\n", Status); + return Status; + } + } +#endif + + Status = SetGenericAttributes(p_adapter, h_adapter); + if (Status != NDIS_STATUS_SUCCESS) + { + //ETH_PRINT(TRACE_LEVEL_ERROR, ETH_INIT, "Set generic attributes failed Error=0x%x\n", Status); + return Status; + } + + return Status; +} + + +//TODO Don't pass h_adapter inside the function, use pPort->p_adapter->h_adapter +NDIS_STATUS +InitNdisScatterGatherDma( + ipoib_adapter_t *p_adapter, + NDIS_HANDLE h_adapter + ) +{ + NDIS_STATUS status; + NDIS_SG_DMA_DESCRIPTION DmaDescription; + + NdisZeroMemory(&DmaDescription, sizeof(DmaDescription)); + + DmaDescription.Header.Type = NDIS_OBJECT_TYPE_SG_DMA_DESCRIPTION; + DmaDescription.Header.Revision = NDIS_SG_DMA_DESCRIPTION_REVISION_1; + DmaDescription.Header.Size = sizeof(NDIS_SG_DMA_DESCRIPTION); + DmaDescription.Flags = NDIS_SG_DMA_64_BIT_ADDRESS; + // + // Even if offload is enabled, the packet size for mapping shouldn't change + // + DmaDescription.MaximumPhysicalMapping = LARGE_SEND_OFFLOAD_SIZE + LSO_MAX_HEADER; + + DmaDescription.ProcessSGListHandler = ipoib_process_sg_list; + DmaDescription.SharedMemAllocateCompleteHandler = NULL; + + DmaDescription.Header.Type = NDIS_OBJECT_TYPE_SG_DMA_DESCRIPTION; + DmaDescription.Header.Revision = NDIS_SG_DMA_DESCRIPTION_REVISION_1; + DmaDescription.Header.Size = sizeof(NDIS_SG_DMA_DESCRIPTION);//NDIS_SIZEOF_SG_DMA_DESCRIPTION_REVISION_1; + + DmaDescription.Flags = NDIS_SG_DMA_64_BIT_ADDRESS; + //DmaDescription.MaximumPhysicalMapping = pPort->p_adapter->params.xfer_block_size; + + DmaDescription.ProcessSGListHandler = ipoib_process_sg_list; + DmaDescription.SharedMemAllocateCompleteHandler = NULL; + + status = NdisMRegisterScatterGatherDma( + h_adapter, + &DmaDescription, + &p_adapter->NdisMiniportDmaHandle); + + if( status != NDIS_STATUS_SUCCESS ) + { + //TODO NDIS60 + //ipoib_destroy_adapter( p_adapter ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisMRegisterScatterGatherDma returned 0x%.8x.\n", status) ); + + } + //NDIS sets this value before it returns from NdisMRegisterScatterGatherDma. + //Miniport drivers should use this size to preallocate memory for each scatter/gather list. + p_adapter->sg_list_size = DmaDescription.ScatterGatherListSize ; + + return status; +} + + +NDIS_STATUS +ipoib_initialize_ex( + IN NDIS_HANDLE h_adapter, + IN NDIS_HANDLE config_context, + IN PNDIS_MINIPORT_INIT_PARAMETERS MiniportInitParameters) + { + NDIS_STATUS status; + ib_api_status_t ib_status; + ipoib_adapter_t *p_adapter; + //NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES RegistrationAttributes; + //NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES GeneralAttributes; +#if IPOIB_USE_DMA + //NDIS_SG_DMA_DESCRIPTION DmaDescription; +#endif + IPOIB_ENTER( IPOIB_DBG_INIT ); + +#ifdef _DEBUG_ + PAGED_CODE(); +#endif + + UNUSED_PARAM( config_context ); + UNUSED_PARAM( MiniportInitParameters ); + + //foo1(100); + /* Create the adapter adapter */ + ib_status = ipoib_create_adapter(config_context, h_adapter, &p_adapter ); + if( ib_status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_create_adapter returned status %d.\n", ib_status ) ); + return NDIS_STATUS_FAILURE; + } + p_adapter->ipoib_state = IPOIB_PAUSED; + status = SetAttributes(p_adapter, h_adapter); + if (status != NDIS_STATUS_SUCCESS) { + ASSERT(FALSE); + } +#if 0 + NdisZeroMemory(&RegistrationAttributes, sizeof(NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES)); + NdisZeroMemory(&GeneralAttributes, sizeof(NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES)); + + /* setting registration attributes */ + RegistrationAttributes.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES; + RegistrationAttributes.Header.Revision = NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1; + RegistrationAttributes.Header.Size = NDIS_SIZEOF_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1; + + RegistrationAttributes.MiniportAdapterContext = (NDIS_HANDLE)p_adapter; + RegistrationAttributes.AttributeFlags = NDIS_MINIPORT_ATTRIBUTES_BUS_MASTER; + + RegistrationAttributes.CheckForHangTimeInSeconds = 10; + RegistrationAttributes.InterfaceType = NdisInterfacePNPBus; + + status = NdisMSetMiniportAttributes(h_adapter, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&RegistrationAttributes); + + if (status != NDIS_STATUS_SUCCESS) + { + ipoib_destroy_adapter( p_adapter ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisMSetMiniportAttributes returned 0x%.8x.\n", status) ); + return status; + } + + /* set up generic attributes */ + + GeneralAttributes.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES; + GeneralAttributes.Header.Revision = NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES_REVISION_1; + GeneralAttributes.Header.Size = sizeof(NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES); + + GeneralAttributes.MediaType = NdisMedium802_3; + //TODO + GeneralAttributes.MtuSize = MAX_IB_MTU; + GeneralAttributes.MaxXmitLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + GeneralAttributes.MaxRcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + GeneralAttributes.XmitLinkSpeed = NDIS_LINK_SPEED_UNKNOWN; + GeneralAttributes.RcvLinkSpeed = NDIS_LINK_SPEED_UNKNOWN; + GeneralAttributes.MediaConnectState = MediaConnectStateUnknown; + GeneralAttributes.MediaDuplexState = MediaDuplexStateUnknown; + GeneralAttributes.LookaheadSize = MAX_XFER_BLOCK_SIZE; + + GeneralAttributes.PowerManagementCapabilities = NULL; + + GeneralAttributes.MacOptions = NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA | + NDIS_MAC_OPTION_TRANSFERS_NOT_PEND | + NDIS_MAC_OPTION_NO_LOOPBACK | + NDIS_MAC_OPTION_FULL_DUPLEX; + + GeneralAttributes.SupportedPacketFilters = NDIS_PACKET_TYPE_DIRECTED | + NDIS_PACKET_TYPE_MULTICAST | + NDIS_PACKET_TYPE_ALL_MULTICAST | + NDIS_PACKET_TYPE_BROADCAST; + + GeneralAttributes.MaxMulticastListSize = MAX_MCAST; + GeneralAttributes.MacAddressLength = HW_ADDR_LEN; + + NdisMoveMemory(GeneralAttributes.PermanentMacAddress, + p_adapter->mac.addr, + HW_ADDR_LEN); + + NdisMoveMemory(GeneralAttributes.CurrentMacAddress, + p_adapter->params.conf_mac.addr, + HW_ADDR_LEN); + + + GeneralAttributes.PhysicalMediumType = NdisPhysicalMediumUnspecified; + GeneralAttributes.RecvScaleCapabilities = NULL; + GeneralAttributes.AccessType = NET_IF_ACCESS_BROADCAST; // NET_IF_ACCESS_BROADCAST for a typical ethernet adapter + GeneralAttributes.DirectionType = NET_IF_DIRECTION_SENDRECEIVE; // NET_IF_DIRECTION_SENDRECEIVE for a typical ethernet adapter + GeneralAttributes.ConnectionType = NET_IF_CONNECTION_DEDICATED; // NET_IF_CONNECTION_DEDICATED for a typical ethernet adapter + GeneralAttributes.IfType = IF_TYPE_ETHERNET_CSMACD; // IF_TYPE_ETHERNET_CSMACD for a typical ethernet adapter (regardless of speed) + GeneralAttributes.IfConnectorPresent = TRUE; // RFC 2665 TRUE if physical adapter + + GeneralAttributes.SupportedStatistics = NDIS_STATISTICS_XMIT_OK_SUPPORTED | + NDIS_STATISTICS_RCV_OK_SUPPORTED | + NDIS_STATISTICS_XMIT_ERROR_SUPPORTED | + NDIS_STATISTICS_RCV_ERROR_SUPPORTED | + NDIS_STATISTICS_RCV_CRC_ERROR_SUPPORTED | + NDIS_STATISTICS_RCV_NO_BUFFER_SUPPORTED | + NDIS_STATISTICS_TRANSMIT_QUEUE_LENGTH_SUPPORTED | + NDIS_STATISTICS_GEN_STATISTICS_SUPPORTED; + + GeneralAttributes.SupportedOidList = (PNDIS_OID)SUPPORTED_OIDS; + GeneralAttributes.SupportedOidListLength = sizeof(SUPPORTED_OIDS); + + status = NdisMSetMiniportAttributes(h_adapter, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&GeneralAttributes); + + if (status != NDIS_STATUS_SUCCESS) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisMSetMiniportAttributes returned 0x%.8x.\n", status) ); + } + +#if IPOIB_USE_DMA + + NdisZeroMemory(&DmaDescription, sizeof(DmaDescription)); + + DmaDescription.Header.Type = NDIS_OBJECT_TYPE_SG_DMA_DESCRIPTION; + DmaDescription.Header.Revision = NDIS_SG_DMA_DESCRIPTION_REVISION_1; + DmaDescription.Header.Size = sizeof(NDIS_SG_DMA_DESCRIPTION);//NDIS_SIZEOF_SG_DMA_DESCRIPTION_REVISION_1; + + DmaDescription.Flags = NDIS_SG_DMA_64_BIT_ADDRESS; + DmaDescription.MaximumPhysicalMapping = p_adapter->params.xfer_block_size; + + DmaDescription.ProcessSGListHandler = ipoib_process_sg_list; + DmaDescription.SharedMemAllocateCompleteHandler = NULL; + + status = NdisMRegisterScatterGatherDma( + p_adapter->h_adapter, + &DmaDescription, + &p_adapter->NdisMiniportDmaHandle); + + if( status != NDIS_STATUS_SUCCESS ) + { + ipoib_destroy_adapter( p_adapter ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisMRegisterScatterGatherDma returned 0x%.8x.\n", status) ); + return status; + } + + + +#endif +#endif //if 0 + + + +#if IPOIB_USE_DMA + + InitNdisScatterGatherDma(p_adapter, h_adapter); + + + +#endif + /* Create the adapter adapter */ + ib_status = ipoib_start_adapter( p_adapter ); + if( ib_status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( h_adapter, + NDIS_ERROR_CODE_HARDWARE_FAILURE, 0 ); +#if IPOIB_USE_DMA + NdisMDeregisterScatterGatherDma(p_adapter->NdisMiniportDmaHandle); +#endif + ipoib_destroy_adapter( p_adapter ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_start_adapter returned status %d.\n", ib_status ) ); + return NDIS_STATUS_FAILURE; + } + + ipoib_ref_ibat(); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; + } + + +//! Deallocates resources when the NIC is removed and halts the NIC.. +//TODO: Dispatch or Passive ? +/* IRQL = DISPATCH_LEVEL + +@param adapter_context The adapter context allocated at start +*/ +void +ipoib_halt_ex( + IN NDIS_HANDLE adapter_context, + IN NDIS_HALT_ACTION HaltAction ) +{ + ipoib_adapter_t *p_adapter; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + UNUSED_PARAM(HaltAction); + + ipoib_deref_ibat(); + + CL_ASSERT( adapter_context ); + p_adapter = (ipoib_adapter_t*)adapter_context; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Port %016I64x (CA %016I64x port %d) halting\n", + p_adapter->guids.port_guid.guid, p_adapter->guids.ca_guid, + p_adapter->guids.port_num) ); + +#if IPOIB_USE_DMA + if (p_adapter->NdisMiniportDmaHandle != NULL) + { + NdisMDeregisterScatterGatherDma(p_adapter->NdisMiniportDmaHandle); + p_adapter->NdisMiniportDmaHandle = NULL; + } +#endif + ipoib_destroy_adapter( p_adapter ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +//! Reports the state of the NIC, or monitors the responsiveness of an underlying device driver. +/* IRQL = DISPATCH_LEVEL + +@param adapter_context The adapter context allocated at start +@return TRUE if the driver determines that its NIC is not operating +*/ +BOOLEAN +ipoib_check_for_hang( + IN NDIS_HANDLE adapter_context ) +{ + ipoib_adapter_t *p_adapter; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( adapter_context ); + p_adapter = (ipoib_adapter_t*)adapter_context; + + if( p_adapter->reset ) + { + IPOIB_EXIT( IPOIB_DBG_INIT ); + return FALSE; + } + if (p_adapter->hung) { + ipoib_resume_oids(p_adapter); + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return (p_adapter->hung? TRUE:FALSE); +} + + +/*++ +Routine Description: + The routine sets an NDIS_OFFLOAD structure indicates the current offload + capabilities that are provided by the miniport adapter + +Arguments: + pPort - a pointer to port object + offload - reference to NDIS_OFFLOAD object that should be filled + +Return Value: + None. + +--*/ +//TODO +#if 0 +static +void +__ipoib_get_offload_config( + ipoib_port_t *pPort, + NDIS_OFFLOAD *p_offload + ) +{ + NDIS_STATUS Status; + ULONG TxChksumOffload = ((MP_GET_PORT_CONFIG(pPort, TxChksumOffload) == TRUE) ? NDIS_OFFLOAD_SET_ON : NDIS_OFFLOAD_SET_OFF); + ULONG RxChksumOffload = ((MP_GET_PORT_CONFIG(pPort, RxChksumOffload) == TRUE) ? NDIS_OFFLOAD_SET_ON : NDIS_OFFLOAD_SET_OFF); + BOOLEAN fLargeSendOffload = MP_GET_PORT_CONFIG(pPort, LargeSendOffload); + ULONG ulEncapsulation = NDIS_ENCAPSULATION_IEEE_802_3 | NDIS_ENCAPSULATION_IEEE_802_3_P_AND_Q; + + NdisZeroMemory(&*p_offload, sizeof(NDIS_OFFLOAD)); + *p_offload.Header.Type = NDIS_OBJECT_TYPE_OFFLOAD; + *p_offload.Header.Revision = NDIS_OFFLOAD_REVISION_1; // BUGBUG: do we need to support revision 2? UH 17-May-2008 + *p_offload.Header.Size = NDIS_SIZEOF_NDIS_OFFLOAD_REVISION_1; + + *p_offload.Checksum.IPv4Transmit.Encapsulation = ulEncapsulation; + *p_offload.Checksum.IPv4Transmit.IpOptionsSupported = TxChksumOffload; + *p_offload.Checksum.IPv4Transmit.TcpOptionsSupported = TxChksumOffload; + *p_offload.Checksum.IPv4Transmit.TcpChecksum = TxChksumOffload; + *p_offload.Checksum.IPv4Transmit.UdpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + *p_offload.Checksum.IPv4Transmit.IpChecksum = TxChksumOffload; + + *p_offload.Checksum.IPv4Receive.Encapsulation = ulEncapsulation; + *p_offload.Checksum.IPv4Receive.IpOptionsSupported = RxChksumOffload; + *p_offload.Checksum.IPv4Receive.TcpOptionsSupported = RxChksumOffload; + *p_offload.Checksum.IPv4Receive.TcpChecksum = RxChksumOffload; + *p_offload.Checksum.IPv4Receive.UdpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + *p_offload.Checksum.IPv4Receive.IpChecksum = RxChksumOffload; + + *p_offload.Checksum.IPv6Transmit.Encapsulation = ulEncapsulation; + *p_offload.Checksum.IPv6Transmit.IpExtensionHeadersSupported = TxChksumOffload; + *p_offload.Checksum.IPv6Transmit.TcpOptionsSupported = TxChksumOffload; + *p_offload.Checksum.IPv6Transmit.TcpChecksum = TxChksumOffload; + *p_offload.Checksum.IPv6Transmit.UdpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + + + *p_offload.Checksum.IPv6Receive.Encapsulation = ulEncapsulation; + *p_offload.Checksum.IPv6Receive.IpExtensionHeadersSupported = RxChksumOffload; + *p_offload.Checksum.IPv6Receive.TcpOptionsSupported = RxChksumOffload; + *p_offload.Checksum.IPv6Receive.TcpChecksum = RxChksumOffload; + *p_offload.Checksum.IPv6Receive.UdpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + + if (fLargeSendOffload) + { + *p_offload.LsoV1.IPv4.Encapsulation = ulEncapsulation; + *p_offload.LsoV1.IPv4.MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; + *p_offload.LsoV1.IPv4.MinSegmentCount = 1; + *p_offload.LsoV1.IPv4.TcpOptions = NDIS_OFFLOAD_SUPPORTED; + *p_offload.LsoV1.IPv4.IpOptions = NDIS_OFFLOAD_SUPPORTED; + } +} +#endif + +//! Returns information about the capabilities and status of the driver and/or its NIC. +/* IRQL = DISPATCH_LEVEL + +@param adapter_context The adapter context allocated at start +@param oid Object ID representing the query operation to be carried out +@param info_buf Buffer containing any input for this query and location for output +@param info_buf_len Number of bytes available in info_buf +@param p_bytes_written Pointer to number of bytes written into info_buf +@param p_bytes_needed Pointer to number of bytes needed to satisfy this oid +@return NDIS_STATUS_SUCCESS, NDIS_STATUS_PENDING, NDIS_STATUS_INVALID_OID, +NDIS_STATUS_INVALID_LENGTH, NDIS_STATUS_NOT_ACCEPTED, NDIS_STATUS_NOT_SUPPORTED, +NDIS_STATUS_RESOURCES +*/ + NDIS_STATUS +ipoib_query_info( + IN NDIS_HANDLE adapter_context, + IN NDIS_OID oid, + IN PVOID info_buf, + IN ULONG info_buf_len, + OUT PULONG p_bytes_written, + OUT PULONG p_bytes_needed ) + { + ipoib_adapter_t *p_adapter; + NDIS_STATUS status; + USHORT version; + ULONG info; + PVOID src_buf; + ULONG buf_len; + pending_oid_t oid_info; + uint8_t port_num; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + oid_info.oid = oid; + oid_info.p_buf = info_buf; + oid_info.buf_len = info_buf_len; + oid_info.p_bytes_used = p_bytes_written; + oid_info.p_bytes_needed = p_bytes_needed; + + CL_ASSERT( adapter_context ); + p_adapter = (ipoib_adapter_t*)adapter_context; + + CL_ASSERT( p_bytes_written ); + CL_ASSERT( p_bytes_needed ); + CL_ASSERT( !p_adapter->pending_query ); + + status = NDIS_STATUS_SUCCESS; + src_buf = &info; + buf_len = sizeof(info); + + port_num = p_adapter->guids.port_num; + + switch( oid ) + { + /* Required General */ + case OID_GEN_SUPPORTED_LIST: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_SUPPORTED_LIST\n", port_num) ); + src_buf = (PVOID)SUPPORTED_OIDS; + buf_len = sizeof(SUPPORTED_OIDS); + break; + + case OID_GEN_HARDWARE_STATUS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_HARDWARE_STATUS\n", port_num) ); + cl_obj_lock( &p_adapter->obj ); + switch( p_adapter->state ) + { + case IB_PNP_PORT_ADD: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NdisHardwareStatusInitializing\n", port_num) ); + info = NdisHardwareStatusInitializing; + break; + + case IB_PNP_PORT_ACTIVE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NdisHardwareStatusReady\n", port_num) ); + info = NdisHardwareStatusReady; + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NdisHardwareStatusNotReady\n", port_num) ); + info = NdisHardwareStatusNotReady; + } + cl_obj_unlock( &p_adapter->obj ); + break; + + case OID_GEN_MEDIA_SUPPORTED: + case OID_GEN_MEDIA_IN_USE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MEDIA_SUPPORTED " + "or OID_GEN_MEDIA_IN_USE\n", port_num) ); + info = NdisMedium802_3; + break; + + case OID_GEN_MAXIMUM_FRAME_SIZE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MAXIMUM_FRAME_SIZE\n", port_num) ); + if( p_adapter->params.cm_enabled ) + { + info = p_adapter->params.cm_payload_mtu; + } + else + { + info = p_adapter->params.payload_mtu; + } + break; + + case OID_GEN_LINK_SPEED: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_LINK_SPEED\n", port_num) ); + if (info_buf_len < buf_len) + { + break; + } + + cl_obj_lock( &p_adapter->obj ); + switch( p_adapter->state ) + { + case IB_PNP_PORT_ADD: + /* Mark the adapter as pending an OID */ + p_adapter->pending_query = TRUE; + + /* Save the request parameters. */ + p_adapter->query_oid = oid_info; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NDIS_STATUS_PENDING\n", port_num) ); + status = NDIS_STATUS_PENDING; + break; + + case IB_PNP_PORT_REMOVE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NDIS_STATUS_NOT_ACCEPTED\n", port_num) ); + status = NDIS_STATUS_NOT_ACCEPTED; + break; + + default: + CL_ASSERT( p_adapter->p_port ); + info = p_adapter->port_rate; + break; + } + cl_obj_unlock( &p_adapter->obj ); + break; + + case OID_GEN_TRANSMIT_BUFFER_SPACE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_TRANSMIT_BUFFER_SPACE\n", port_num) ); + if( p_adapter->params.cm_enabled ) + info = p_adapter->params.sq_depth * p_adapter->params.cm_xfer_block_size; + else + info = p_adapter->params.sq_depth * p_adapter->params.xfer_block_size; + break; + + case OID_GEN_RECEIVE_BUFFER_SPACE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_TRANSMIT_BUFFER_SPACE " + "or OID_GEN_RECEIVE_BUFFER_SPACE\n", port_num) ); + if( p_adapter->params.cm_enabled ) + info = p_adapter->params.rq_depth * p_adapter->params.cm_xfer_block_size; + else + info = p_adapter->params.rq_depth * p_adapter->params.xfer_block_size; + break; + + case OID_GEN_MAXIMUM_LOOKAHEAD: + case OID_GEN_CURRENT_LOOKAHEAD: + case OID_GEN_TRANSMIT_BLOCK_SIZE: + case OID_GEN_RECEIVE_BLOCK_SIZE: + case OID_GEN_MAXIMUM_TOTAL_SIZE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MAXIMUM_LOOKAHEAD " + "or OID_GEN_CURRENT_LOOKAHEAD or " + "OID_GEN_TRANSMIT_BLOCK_SIZE or " + "OID_GEN_RECEIVE_BLOCK_SIZE or " + "OID_GEN_MAXIMUM_TOTAL_SIZE\n", port_num) ); + if( p_adapter->params.cm_enabled ) + info = p_adapter->params.cm_xfer_block_size; + else + info = p_adapter->params.xfer_block_size; + break; + + case OID_GEN_VENDOR_ID: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_VENDOR_ID\n", port_num) ); + src_buf = (void*)VENDOR_ID; + buf_len = sizeof(VENDOR_ID); + break; + + case OID_GEN_VENDOR_DESCRIPTION: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_VENDOR_DESCRIPTION\n", port_num) ); + src_buf = VENDOR_DESCRIPTION; + buf_len = sizeof(VENDOR_DESCRIPTION); + break; + + case OID_GEN_VENDOR_DRIVER_VERSION: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_VENDOR_DRIVER_VERSION\n", port_num) ); + src_buf = &version; + buf_len = sizeof(version); + //TODO: Figure out what the right version is. + version = 1 << 8 | 1; + break; + + case OID_GEN_PHYSICAL_MEDIUM: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_PHYSICAL_MEDIUM\n", port_num) ); + info = NdisPhysicalMediumUnspecified; + break; + + case OID_GEN_CURRENT_PACKET_FILTER: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_CURRENT_PACKET_FILTER\n", port_num) ); + info = p_adapter->packet_filter; + break; + + case OID_GEN_DRIVER_VERSION: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_DRIVER_VERSION\n", port_num) ); + src_buf = &version; + buf_len = sizeof(version); + version = MAJOR_NDIS_VERSION << 8 | MINOR_NDIS_VERSION; + break; + + case OID_GEN_MAC_OPTIONS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MAC_OPTIONS\n", port_num) ); + info = NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA | + NDIS_MAC_OPTION_TRANSFERS_NOT_PEND | + NDIS_MAC_OPTION_NO_LOOPBACK | + NDIS_MAC_OPTION_FULL_DUPLEX; + //TODO: Figure out if we will support priority and VLANs. + // NDIS_MAC_OPTION_8021P_PRIORITY; + //#ifdef NDIS51_MINIPORT + // info |= NDIS_MAC_OPTION_8021Q_VLAN; + //#endif + break; + + case OID_GEN_MEDIA_CONNECT_STATUS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MEDIA_CONNECT_STATUS\n", port_num) ); + cl_obj_lock( &p_adapter->obj ); + switch( p_adapter->state ) + { + case IB_PNP_PORT_ADD: + case IB_PNP_PORT_INIT: + /* + * Delay reporting media state until we know whether the port is + * either up or down. + */ + p_adapter->pending_query = TRUE; + p_adapter->query_oid = oid_info; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NDIS_STATUS_PENDING\n", port_num) ); + status = NDIS_STATUS_PENDING; + break; + + case IB_PNP_PORT_ACTIVE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NdisMediaStateConnected\n", port_num) ); + info = NdisMediaStateConnected; + break; + + case IB_PNP_PORT_REMOVE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NDIS_STATUS_NOT_ACCEPTED\n", port_num) ); + status = NDIS_STATUS_NOT_ACCEPTED; + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NdisMediaStateDisconnected\n", port_num) ); + info = NdisMediaStateDisconnected; + } + cl_obj_unlock( &p_adapter->obj ); + break; + + case OID_GEN_MAXIMUM_SEND_PACKETS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MAXIMUM_SEND_PACKETS\n", port_num) ); + info = MINIPORT_MAX_SEND_PACKETS; + break; + + /* Required General Statistics */ + case OID_GEN_STATISTICS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_STATISTICS\n", port_num) ); + src_buf = NULL; + buf_len = sizeof(NDIS_STATISTICS_INFO); + if (info_buf_len < buf_len) + { + break; + } + status = ipoib_get_gen_stat(p_adapter, &oid_info ); + break; + + case OID_GEN_XMIT_OK: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_XMIT_OK\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_SUCCESS, &oid_info ); + break; + + case OID_GEN_RCV_OK: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_RCV_OK\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_SUCCESS, &oid_info ); + break; + + case OID_GEN_XMIT_ERROR: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_XMIT_ERROR\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_ERROR, &oid_info ); + break; + + case OID_GEN_RCV_ERROR: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_RCV_ERROR\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_ERROR, &oid_info ); + break; + + case OID_GEN_RCV_NO_BUFFER: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_RCV_NO_BUFFER\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_DROPPED, &oid_info ); + break; + + case OID_GEN_DIRECTED_BYTES_XMIT: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_DIRECTED_BYTES_XMIT\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_UCAST_BYTES, &oid_info ); + break; + + case OID_GEN_DIRECTED_FRAMES_XMIT: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_DIRECTED_FRAMES_XMIT\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_UCAST_FRAMES, &oid_info ); + break; + + case OID_GEN_MULTICAST_BYTES_XMIT: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MULTICAST_BYTES_XMIT\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_MCAST_BYTES, &oid_info ); + break; + + case OID_GEN_MULTICAST_FRAMES_XMIT: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MULTICAST_FRAMES_XMIT\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_MCAST_FRAMES, &oid_info ); + break; + + case OID_GEN_BROADCAST_BYTES_XMIT: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_BROADCAST_BYTES_XMIT\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_BCAST_BYTES, &oid_info ); + break; + + case OID_GEN_BROADCAST_FRAMES_XMIT: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_BROADCAST_FRAMES_XMIT\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_BCAST_FRAMES, &oid_info ); + break; + + case OID_GEN_DIRECTED_BYTES_RCV: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_DIRECTED_BYTES_RCV\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_UCAST_BYTES, &oid_info ); + break; + + case OID_GEN_DIRECTED_FRAMES_RCV: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_DIRECTED_FRAMES_RCV\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_UCAST_FRAMES, &oid_info ); + break; + + case OID_GEN_MULTICAST_BYTES_RCV: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MULTICAST_BYTES_RCV\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_MCAST_BYTES, &oid_info ); + break; + + case OID_GEN_MULTICAST_FRAMES_RCV: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MULTICAST_FRAMES_RCV\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_MCAST_FRAMES, &oid_info ); + break; + + case OID_GEN_BROADCAST_BYTES_RCV: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_BROADCAST_BYTES_RCV\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_BCAST_BYTES, &oid_info ); + break; + + case OID_GEN_BROADCAST_FRAMES_RCV: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_BROADCAST_FRAMES_RCV\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_BCAST_FRAMES, &oid_info ); + break; + + /* Required Ethernet operational characteristics */ + case OID_802_3_PERMANENT_ADDRESS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_802_3_PERMANENT_ADDRESS\n", port_num) ); + src_buf = &p_adapter->mac; + buf_len = sizeof(p_adapter->mac); + break; + + case OID_802_3_CURRENT_ADDRESS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_802_3_CURRENT_ADDRESS\n", port_num) ); + src_buf = &p_adapter->params.conf_mac; + buf_len = sizeof(p_adapter->params.conf_mac); + break; + + case OID_802_3_MULTICAST_LIST: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_802_3_MULTICAST_LIST\n", port_num) ); + src_buf = p_adapter->mcast_array; + buf_len = p_adapter->mcast_array_size * sizeof(mac_addr_t); + break; + + case OID_802_3_MAXIMUM_LIST_SIZE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_802_3_MAXIMUM_LIST_SIZE\n", port_num) ); + info = MAX_MCAST; + break; + + case OID_802_3_MAC_OPTIONS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_802_3_MAC_OPTIONS\n", port_num) ); + info = 0; + break; + + /* Required Ethernet stats */ + case OID_802_3_RCV_ERROR_ALIGNMENT: + case OID_802_3_XMIT_ONE_COLLISION: + case OID_802_3_XMIT_MORE_COLLISIONS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_802_3_RCV_ERROR_ALIGNMENT or " + "OID_802_3_XMIT_ONE_COLLISION or " + "OID_802_3_XMIT_MORE_COLLISIONS\n", port_num) ); + info = 0; + break; + + case OID_TCP_TASK_OFFLOAD: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_TCP_TASK_OFFLOAD\n", port_num) ); + src_buf = NULL; + status = __ipoib_get_tcp_task_offload( p_adapter, &oid_info ); + break; + + /* Optional General */ + case OID_GEN_SUPPORTED_GUIDS: +#ifdef NDIS51_MINIPORT + case OID_GEN_VLAN_ID: +#endif + + /* Optional General Stats */ + case OID_GEN_RCV_CRC_ERROR: + case OID_GEN_TRANSMIT_QUEUE_LENGTH: + + /* Optional Ethernet Stats */ + case OID_802_3_XMIT_DEFERRED: + case OID_802_3_XMIT_MAX_COLLISIONS: + case OID_802_3_RCV_OVERRUN: + case OID_802_3_XMIT_UNDERRUN: + case OID_802_3_XMIT_HEARTBEAT_FAILURE: + case OID_802_3_XMIT_TIMES_CRS_LOST: + case OID_802_3_XMIT_LATE_COLLISIONS: + case OID_PNP_CAPABILITIES: + status = NDIS_STATUS_NOT_SUPPORTED; + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received an unsupported oid of 0x%.8X!\n", port_num, oid) ); + break; + + case OID_GEN_PROTOCOL_OPTIONS: + case OID_GEN_NETWORK_LAYER_ADDRESSES: + case OID_GEN_TRANSPORT_HEADER_OFFSET: + case OID_PNP_ENABLE_WAKE_UP: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_PROTOCOL_OPTIONS or OID_GEN_NETWORK_LAYER_ADDRESSES or OID_GEN_TRANSPORT_HEADER_OFFSET OID_PNP_ENABLE_WAKE_UPn", port_num) ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Number of OID: 0x%.8X!\n", oid) ); + status = NDIS_STATUS_SUCCESS; + break; + + case OID_PNP_QUERY_POWER: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_TCP_TASK_OFFLOAD\n", port_num) ); + // Status is pre-set in this routine to Success + status = NDIS_STATUS_SUCCESS; + break; + + case OID_TCP_OFFLOAD_CURRENT_CONFIG: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_PNP_QUERY_POWER\n", port_num) ); + //ulBytesAvailable = ulInfoLen = sizeof(NDIS_OFFLOAD); + if (info_buf_len < sizeof(NDIS_OFFLOAD)) + { + status = NDIS_STATUS_BUFFER_TOO_SHORT; + *p_bytes_needed = sizeof(NDIS_OFFLOAD) ; + break; + } + + //ipoib_offload_config(pPort, &offload); + //pInfo = &offload; + break; + + default: + status = NDIS_STATUS_INVALID_OID; + // IPOIB_PRINT( TRACE_LEVEL_ERROR,IPOIB_DBG_OID, + // ("Port %d received an invalid oid of 0x%.8X!\n", port_num, oid) ); + break; + } + + /* + * Complete the request as if it was handled asynchronously to maximize + * code reuse for when we really handle the requests asynchronously. + * Note that this requires the QueryInformation entry point to always + * return NDIS_STATUS_PENDING + */ + if( status != NDIS_STATUS_PENDING ) + { + ipoib_complete_query( + p_adapter, &oid_info, status, src_buf, buf_len ); + return status; + } + + IPOIB_EXIT( IPOIB_DBG_OID ); + return NDIS_STATUS_PENDING; + } + + +static void +ipoib_complete_query( + IN ipoib_adapter_t* const p_adapter, + IN pending_oid_t* const p_oid_info, + IN const NDIS_STATUS status, + IN const void* const p_buf, + IN const ULONG buf_len ) +{ + NDIS_STATUS oid_status = status; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + CL_ASSERT( status != NDIS_STATUS_PENDING ); + + if( status == NDIS_STATUS_SUCCESS ) + { + if( p_oid_info->buf_len < buf_len ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Insufficient buffer space. " + "Returning NDIS_STATUS_INVALID_LENGTH.\n") ); + oid_status = NDIS_STATUS_INVALID_LENGTH; + *p_oid_info->p_bytes_needed = buf_len; + *p_oid_info->p_bytes_used = 0; + } + else if( p_oid_info->p_buf ) + { + /* Only copy if we have a distinct source buffer. */ + if( p_buf ) + { + NdisMoveMemory( p_oid_info->p_buf, p_buf, buf_len ); + *p_oid_info->p_bytes_used = buf_len; + } + } + else + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Returning NDIS_NOT_ACCEPTED") ); + oid_status = NDIS_STATUS_NOT_ACCEPTED; + } + } + else + { + *p_oid_info->p_bytes_used = 0; + } + + if (p_adapter->query_oid.p_pending_oid) + { + NdisMOidRequestComplete(p_adapter->h_adapter,p_adapter->query_oid.p_pending_oid,oid_status); + p_adapter->query_oid.p_pending_oid = NULL; + } + p_adapter->pending_query = FALSE; + + IPOIB_EXIT( IPOIB_DBG_OID ); +} + + +static NDIS_STATUS +__ipoib_get_tcp_task_offload( + IN ipoib_adapter_t* p_adapter, + OUT pending_oid_t *pNdisRequest ) +{ +#ifndef NDIS60_MINIPORT + NDIS_TASK_OFFLOAD_HEADER *p_offload_hdr; + NDIS_TASK_OFFLOAD *p_offload_task; + NDIS_TASK_TCP_IP_CHECKSUM *p_offload_chksum; + + NDIS_TASK_TCP_LARGE_SEND *p_offload_lso; + ULONG buf_len; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_TCP_TASK_OFFLOAD\n", + p_adapter->guids.port_num) ); + + buf_len = sizeof(NDIS_TASK_OFFLOAD_HEADER) + + offsetof( NDIS_TASK_OFFLOAD, TaskBuffer ) + + sizeof(NDIS_TASK_TCP_IP_CHECKSUM) + + (p_adapter->params.lso ? + sizeof(NDIS_TASK_OFFLOAD) + sizeof(NDIS_TASK_TCP_LARGE_SEND) + : 0); + + pNdisRequest->DATA.QUERY_INFORMATION.BytesNeeded = buf_len; + + if( pNdisRequest->DATA.QUERY_INFORMATION.InformationBufferLength < buf_len ) + return NDIS_STATUS_INVALID_LENGTH; + + p_offload_hdr = (NDIS_TASK_OFFLOAD_HEADER*)pNdisRequest->DATA.QUERY_INFORMATION.InformationBuffer; + if( p_offload_hdr->Version != NDIS_TASK_OFFLOAD_VERSION ) + return NDIS_STATUS_INVALID_DATA; + + if( p_offload_hdr->EncapsulationFormat.Encapsulation != + IEEE_802_3_Encapsulation ) + { + return NDIS_STATUS_INVALID_DATA; + } + + p_offload_hdr->OffsetFirstTask = sizeof(NDIS_TASK_OFFLOAD_HEADER); + p_offload_task = (NDIS_TASK_OFFLOAD*)(p_offload_hdr + 1); + p_offload_task->Version = NDIS_TASK_OFFLOAD_VERSION; + p_offload_task->Size = sizeof(NDIS_TASK_OFFLOAD); + p_offload_task->Task = TcpIpChecksumNdisTask; + p_offload_task->OffsetNextTask = 0; + p_offload_task->TaskBufferLength = sizeof(NDIS_TASK_TCP_IP_CHECKSUM); + p_offload_chksum = + (NDIS_TASK_TCP_IP_CHECKSUM*)p_offload_task->TaskBuffer; + + p_offload_chksum->V4Transmit.IpOptionsSupported = + p_offload_chksum->V4Transmit.TcpOptionsSupported = + p_offload_chksum->V4Transmit.TcpChecksum = + p_offload_chksum->V4Transmit.UdpChecksum = + p_offload_chksum->V4Transmit.IpChecksum = + !!(p_adapter->params.send_chksum_offload); + + p_offload_chksum->V4Receive.IpOptionsSupported = + p_offload_chksum->V4Receive.TcpOptionsSupported = + p_offload_chksum->V4Receive.TcpChecksum = + p_offload_chksum->V4Receive.UdpChecksum = + p_offload_chksum->V4Receive.IpChecksum = + !!(p_adapter->params.recv_chksum_offload); + + p_offload_chksum->V6Transmit.IpOptionsSupported = FALSE; + p_offload_chksum->V6Transmit.TcpOptionsSupported = FALSE; + p_offload_chksum->V6Transmit.TcpChecksum = FALSE; + p_offload_chksum->V6Transmit.UdpChecksum = FALSE; + + p_offload_chksum->V6Receive.IpOptionsSupported = FALSE; + p_offload_chksum->V6Receive.TcpOptionsSupported = FALSE; + p_offload_chksum->V6Receive.TcpChecksum = FALSE; + p_offload_chksum->V6Receive.UdpChecksum = FALSE; + + + if (p_adapter->params.lso) { + // set the previous pointer to the correct place + p_offload_task->OffsetNextTask = FIELD_OFFSET(NDIS_TASK_OFFLOAD, TaskBuffer) + + p_offload_task->TaskBufferLength; + // set the LSO packet + p_offload_task = (PNDIS_TASK_OFFLOAD) + ((PUCHAR)p_offload_task + p_offload_task->OffsetNextTask); + + p_offload_task->Version = NDIS_TASK_OFFLOAD_VERSION; + p_offload_task->Size = sizeof(NDIS_TASK_OFFLOAD); + p_offload_task->Task = TcpLargeSendNdisTask; + p_offload_task->OffsetNextTask = 0; + p_offload_task->TaskBufferLength = sizeof(NDIS_TASK_TCP_LARGE_SEND); + + p_offload_lso = (PNDIS_TASK_TCP_LARGE_SEND) p_offload_task->TaskBuffer; + + p_offload_lso->Version = 0; + //TODO optimal size: 60000, 64000 or 65536 + //TODO LSO_MIN_SEG_COUNT to be 1 + p_offload_lso->MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; +#define LSO_MIN_SEG_COUNT 2 + p_offload_lso->MinSegmentCount = LSO_MIN_SEG_COUNT; + p_offload_lso->TcpOptions = TRUE; + p_offload_lso->IpOptions = TRUE; + } + + pNdisRequest->DATA.QUERY_INFORMATION.BytesWritten = buf_len + + return NDIS_STATUS_SUCCESS; +#endif + UNUSED_PARAM(p_adapter); + UNUSED_PARAM(pNdisRequest); + return NDIS_STATUS_NOT_SUPPORTED; + +} + + +static NDIS_STATUS +__ipoib_set_tcp_task_offload( + IN ipoib_adapter_t* p_adapter, + IN void* const p_info_buf, + IN ULONG* const p_info_len ) +{ +#ifndef NDIS60_MINIPORT + NDIS_TASK_OFFLOAD_HEADER *p_offload_hdr; + NDIS_TASK_OFFLOAD *p_offload_task; + NDIS_TASK_TCP_IP_CHECKSUM *p_offload_chksum; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_TCP_TASK_OFFLOAD\n", + p_adapter->guids.port_num) ); + + p_offload_hdr = (NDIS_TASK_OFFLOAD_HEADER*)p_info_buf; + + if( *p_info_len < sizeof(NDIS_TASK_OFFLOAD_HEADER) ) + return NDIS_STATUS_INVALID_LENGTH; + + if( p_offload_hdr->Version != NDIS_TASK_OFFLOAD_VERSION ) + return NDIS_STATUS_INVALID_DATA; + + if( p_offload_hdr->Size != sizeof(NDIS_TASK_OFFLOAD_HEADER) ) + return NDIS_STATUS_INVALID_LENGTH; + + if( !p_offload_hdr->OffsetFirstTask ) + return NDIS_STATUS_SUCCESS; + + if( p_offload_hdr->EncapsulationFormat.Encapsulation != + IEEE_802_3_Encapsulation ) + { + return NDIS_STATUS_INVALID_DATA; + } + + p_offload_task = (NDIS_TASK_OFFLOAD*) + (((UCHAR*)p_offload_hdr) + p_offload_hdr->OffsetFirstTask); + + if( *p_info_len < sizeof(NDIS_TASK_OFFLOAD_HEADER) + + offsetof( NDIS_TASK_OFFLOAD, TaskBuffer ) + + sizeof(NDIS_TASK_TCP_IP_CHECKSUM) ) + { + return NDIS_STATUS_INVALID_LENGTH; + } + + if( p_offload_task->Version != NDIS_TASK_OFFLOAD_VERSION ) + return NDIS_STATUS_INVALID_DATA; + p_offload_chksum = + (NDIS_TASK_TCP_IP_CHECKSUM*)p_offload_task->TaskBuffer; + + if( !p_adapter->params.send_chksum_offload && + (p_offload_chksum->V4Transmit.IpOptionsSupported || + p_offload_chksum->V4Transmit.TcpOptionsSupported || + p_offload_chksum->V4Transmit.TcpChecksum || + p_offload_chksum->V4Transmit.UdpChecksum || + p_offload_chksum->V4Transmit.IpChecksum) ) + { + return NDIS_STATUS_NOT_SUPPORTED; + } + + if( !p_adapter->params.recv_chksum_offload && + (p_offload_chksum->V4Receive.IpOptionsSupported || + p_offload_chksum->V4Receive.TcpOptionsSupported || + p_offload_chksum->V4Receive.TcpChecksum || + p_offload_chksum->V4Receive.UdpChecksum || + p_offload_chksum->V4Receive.IpChecksum) ) + { + return NDIS_STATUS_NOT_SUPPORTED; + } + + return NDIS_STATUS_SUCCESS; +#endif + UNUSED_PARAM(p_adapter); + UNUSED_PARAM(p_info_buf); + UNUSED_PARAM(p_info_len); + return NDIS_STATUS_NOT_SUPPORTED; +} + + +//! Issues a hardware reset to the NIC and/or resets the driver's software state. +/* Tear down the connection and start over again. This is only called when there is a problem. +For example, if a send, query info, or set info had a time out. MiniportCheckForHang will +be called first. +IRQL = DISPATCH_LEVEL + +@param p_addr_resetPointer to BOOLLEAN that is set to TRUE if the NDIS +library should call MiniportSetInformation to restore addressing information to the current values. +@param adapter_context The adapter context allocated at start +@return NDIS_STATUS_SUCCESS, NDIS_STATUS_PENDING, NDIS_STATUS_NOT_RESETTABLE, +NDIS_STATUS_RESET_IN_PROGRESS, NDIS_STATUS_SOFT_ERRORS, NDIS_STATUS_HARD_ERRORS +*/ +NDIS_STATUS +ipoib_reset( + IN NDIS_HANDLE adapter_context, + OUT PBOOLEAN p_addr_reset) +{ + ipoib_adapter_t* p_adapter; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( p_addr_reset ); + CL_ASSERT( adapter_context ); + p_adapter = (ipoib_adapter_t*)adapter_context; + + switch( ipoib_reset_adapter( p_adapter ) ) + { + case IB_NOT_DONE: + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_PENDING; + + case IB_SUCCESS: + IPOIB_EXIT( IPOIB_DBG_INIT ); + *p_addr_reset = TRUE; + return NDIS_STATUS_SUCCESS; + + case IB_INVALID_STATE: + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_RESET_IN_PROGRESS; + + default: + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_HARD_ERRORS; + } +} + + +//! Request changes in the state information that the miniport driver maintains +/* For example, this is used to set multicast addresses and the packet filter. +IRQL = DISPATCH_LEVEL + +@param adapter_context The adapter context allocated at start +@param oid Object ID representing the set operation to be carried out +@param info_buf Buffer containing input for this set and location for any output +@param info_buf_len Number of bytes available in info_buf +@param p_bytes_read Pointer to number of bytes read from info_buf +@param p_bytes_needed Pointer to number of bytes needed to satisfy this oid +@return NDIS_STATUS_SUCCESS, NDIS_STATUS_PENDING, NDIS_STATUS_INVALID_OID, +NDIS_STATUS_INVALID_LENGTH, NDIS_STATUS_INVALID_DATA, NDIS_STATUS_NOT_ACCEPTED, +NDIS_STATUS_NOT_SUPPORTED, NDIS_STATUS_RESOURCES +*/ +NDIS_STATUS +ipoib_set_info( + IN NDIS_HANDLE adapter_context, + IN NDIS_OID oid, + IN PVOID info_buf, + IN ULONG info_buf_len, + OUT PULONG p_bytes_read, + OUT PULONG p_bytes_needed ) +{ + ipoib_adapter_t* p_adapter; + NDIS_STATUS status; + + ULONG buf_len; + uint8_t port_num; + + KLOCK_QUEUE_HANDLE hdl; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + CL_ASSERT( adapter_context ); + p_adapter = (ipoib_adapter_t*)adapter_context; + + CL_ASSERT( p_bytes_read ); + CL_ASSERT( p_bytes_needed ); + CL_ASSERT( !p_adapter->pending_set ); + + status = NDIS_STATUS_SUCCESS; + *p_bytes_needed = 0; + buf_len = sizeof(ULONG); + + port_num = p_adapter->guids.port_num; + + cl_obj_lock( &p_adapter->obj ); + + if( p_adapter->state == IB_PNP_PORT_REMOVE ) + { + *p_bytes_read = 0; + cl_obj_unlock( &p_adapter->obj ); + return NDIS_STATUS_NOT_ACCEPTED; + } + + cl_obj_unlock( &p_adapter->obj ); + + switch( oid ) + { + /* Required General */ + case OID_GEN_CURRENT_PACKET_FILTER: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_CURRENT_PACKET_FILTER\n", port_num)); + if( info_buf_len < sizeof(p_adapter->packet_filter) ) + { + status = NDIS_STATUS_INVALID_LENGTH; + } + else if( !info_buf ) + { + status = NDIS_STATUS_INVALID_DATA; + } + else + { + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + cl_obj_lock( &p_adapter->obj ); + switch( p_adapter->state ) + { + case IB_PNP_PORT_ADD: + p_adapter->set_oid.oid = oid; + p_adapter->set_oid.p_buf = info_buf; + p_adapter->set_oid.buf_len = info_buf_len; + p_adapter->set_oid.p_bytes_used = p_bytes_read; + p_adapter->set_oid.p_bytes_needed = p_bytes_needed; + p_adapter->pending_set = TRUE; + status = NDIS_STATUS_PENDING; + break; + + case IB_PNP_PORT_REMOVE: + status = NDIS_STATUS_NOT_ACCEPTED; + break; + + default: + if( !p_adapter->packet_filter && (*(uint32_t*)info_buf) ) + { + cl_qlist_insert_tail( + &g_ipoib.adapter_list, &p_adapter->entry ); + + /* + * Filter was zero, now non-zero. Register IP addresses + * with SA. + */ + ipoib_reg_addrs( p_adapter ); + } + else if( p_adapter->packet_filter && !(*(uint32_t*)info_buf) ) + { + /* + * Filter was non-zero, now zero. Deregister IP addresses. + */ + ipoib_dereg_addrs( p_adapter ); + + ASSERT( cl_qlist_count( &g_ipoib.adapter_list ) ); + cl_qlist_remove_item( + &g_ipoib.adapter_list, &p_adapter->entry ); + } + + p_adapter->packet_filter = *(uint32_t*)info_buf; + } + cl_obj_unlock( &p_adapter->obj ); + KeReleaseInStackQueuedSpinLock( &hdl ); + } + break; + + case OID_GEN_CURRENT_LOOKAHEAD: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_CURRENT_LOOKAHEAD\n", port_num)); + if( info_buf_len < buf_len ) + status = NDIS_STATUS_INVALID_LENGTH; + break; + + case OID_GEN_PROTOCOL_OPTIONS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_PROTOCOL_OPTIONS\n", port_num)); + if( info_buf_len < buf_len ) + status = NDIS_STATUS_INVALID_LENGTH; + break; + + case OID_GEN_NETWORK_LAYER_ADDRESSES: + status = __ipoib_set_net_addr( p_adapter, info_buf, info_buf_len, p_bytes_read, p_bytes_needed); + break; + +#ifdef NDIS51_MINIPORT + case OID_GEN_MACHINE_NAME: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_MACHINE_NAME\n", port_num) ); + break; +#endif + + /* Required Ethernet operational characteristics */ + case OID_802_3_MULTICAST_LIST: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d received set for OID_802_3_MULTICAST_LIST\n", port_num) ); + if( info_buf_len > MAX_MCAST * sizeof(mac_addr_t) ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_802_3_MULTICAST_LIST - Multicast list full.\n", port_num) ); + status = NDIS_STATUS_MULTICAST_FULL; + *p_bytes_needed = MAX_MCAST * sizeof(mac_addr_t); + } + else if( info_buf_len % sizeof(mac_addr_t) ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_802_3_MULTICAST_LIST - Invalid input buffer.\n", port_num) ); + status = NDIS_STATUS_INVALID_DATA; + } + else if( !info_buf && info_buf_len ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_802_3_MULTICAST_LIST - Invalid input buffer.\n", port_num) ); + status = NDIS_STATUS_INVALID_DATA; + } + else + { + ipoib_refresh_mcast( p_adapter, (mac_addr_t*)info_buf, + (uint8_t)(info_buf_len / sizeof(mac_addr_t)) ); + + buf_len = info_buf_len; + /* + * Note that we don't return pending. It will likely take longer + * for our SA transactions to complete than NDIS will give us + * before reseting the adapter. If an SA failure is encountered, + * the adapter will be marked as hung and we will get reset. + */ + status = NDIS_STATUS_SUCCESS; + } + break; + + case OID_TCP_TASK_OFFLOAD: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_TCP_TASK_OFFLOAD\n", port_num) ); + + buf_len = info_buf_len; + status = + __ipoib_set_tcp_task_offload( p_adapter, info_buf, &buf_len ); + break; + + /* Optional General */ + case OID_GEN_TRANSPORT_HEADER_OFFSET: +#ifdef NDIS51_MINIPORT + case OID_GEN_RNDIS_CONFIG_PARAMETER: + case OID_GEN_VLAN_ID: +#endif + status = NDIS_STATUS_NOT_SUPPORTED; + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received an unsupported oid of 0x%.8X!\n", port_num, oid)); + break; + + case OID_GEN_SUPPORTED_LIST: + case OID_GEN_HARDWARE_STATUS: + case OID_GEN_MEDIA_SUPPORTED: + case OID_GEN_MEDIA_IN_USE: + case OID_GEN_MAXIMUM_FRAME_SIZE: + case OID_GEN_LINK_SPEED: + case OID_GEN_TRANSMIT_BUFFER_SPACE: + case OID_GEN_RECEIVE_BUFFER_SPACE: + case OID_GEN_MAXIMUM_LOOKAHEAD: + case OID_GEN_TRANSMIT_BLOCK_SIZE: + case OID_GEN_RECEIVE_BLOCK_SIZE: + case OID_GEN_MAXIMUM_TOTAL_SIZE: + case OID_GEN_VENDOR_ID: + case OID_GEN_VENDOR_DESCRIPTION: + case OID_GEN_VENDOR_DRIVER_VERSION: + case OID_GEN_DRIVER_VERSION: + case OID_GEN_MAC_OPTIONS: + case OID_GEN_MEDIA_CONNECT_STATUS: + case OID_GEN_MAXIMUM_SEND_PACKETS: + case OID_GEN_SUPPORTED_GUIDS: + case OID_GEN_PHYSICAL_MEDIUM: + default: + status = NDIS_STATUS_INVALID_OID; + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received an invalid oid of 0x%.8X!\n", port_num, oid)); + break; + } + + if( status == NDIS_STATUS_SUCCESS ) + { + *p_bytes_read = buf_len; + } + else + { + if( status == NDIS_STATUS_INVALID_LENGTH ) + { + if ( !*p_bytes_needed ) + { + *p_bytes_needed = buf_len; + } + } + + *p_bytes_read = 0; + } + + IPOIB_EXIT( IPOIB_DBG_OID ); + return status; +} + +#ifdef NNN +NDIS_STATUS +ipoib_set_info( + ipoib_adapter_t* p_adapter, + IN PNDIS_OID_REQUEST pNdisRequest) +{ + NDIS_STATUS status; + NDIS_OID oid; + UINT info_buf_len; + UINT buf_len; + uint8_t port_num; + PVOID info_buf; + UINT *p_bytes_needed; + KLOCK_QUEUE_HANDLE hdl; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + oid = pNdisRequest->DATA.SET_INFORMATION.Oid; + info_buf = pNdisRequest->DATA.QUERY_INFORMATION.InformationBuffer; + info_buf_len = pNdisRequest->DATA.QUERY_INFORMATION.InformationBufferLength; + p_bytes_needed = &pNdisRequest->DATA.QUERY_INFORMATION.BytesNeeded; + status = NDIS_STATUS_SUCCESS; + + buf_len = sizeof(UINT); + port_num = p_adapter->guids.port_num; + + switch( oid ) + { + /* Required General */ + case OID_GEN_CURRENT_PACKET_FILTER: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_CURRENT_PACKET_FILTER\n", port_num)); + if( info_buf_len < sizeof(p_adapter->packet_filter) ) + { + status = NDIS_STATUS_INVALID_LENGTH; + } + else if( !info_buf ) + { + status = NDIS_STATUS_INVALID_DATA; + } + else + { + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + cl_obj_lock( &p_adapter->obj ); + switch( p_adapter->state ) + { + case IB_PNP_PORT_ADD: + p_adapter->p_oid_request = pNdisRequest; + status = NDIS_STATUS_PENDING; + break; + + case IB_PNP_PORT_REMOVE: + status = NDIS_STATUS_NOT_ACCEPTED; + break; + + default: + if( !p_adapter->packet_filter && (*(uint32_t*)info_buf) ) + { + cl_qlist_insert_tail( + &g_ipoib.adapter_list, &p_adapter->entry ); + + /* + * Filter was zero, now non-zero. Register IP addresses + * with SA. + */ + ipoib_reg_addrs( p_adapter ); + } + else if( p_adapter->packet_filter && !(*(uint32_t*)info_buf) ) + { + /* + * Filter was non-zero, now zero. Deregister IP addresses. + */ + ipoib_dereg_addrs( p_adapter ); + + ASSERT( cl_qlist_count( &g_ipoib.adapter_list ) ); + cl_qlist_remove_item( + &g_ipoib.adapter_list, &p_adapter->entry ); + } + + p_adapter->packet_filter = *(uint32_t*)info_buf; + } + cl_obj_unlock( &p_adapter->obj ); + KeReleaseInStackQueuedSpinLock( &hdl ); + } + break; + + case OID_GEN_CURRENT_LOOKAHEAD: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_CURRENT_LOOKAHEAD\n", port_num)); + if( info_buf_len < buf_len ) + status = NDIS_STATUS_INVALID_LENGTH; + break; + + case OID_GEN_PROTOCOL_OPTIONS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_PROTOCOL_OPTIONS\n", port_num)); + if( info_buf_len < buf_len ) + status = NDIS_STATUS_INVALID_LENGTH; + break; + + case OID_GEN_NETWORK_LAYER_ADDRESSES: + status = __ipoib_set_net_addr( p_adapter, pNdisRequest); + break; + + case OID_GEN_MACHINE_NAME: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_MACHINE_NAME\n", port_num) ); + break; + + + /* Required Ethernet operational characteristics */ + case OID_802_3_MULTICAST_LIST: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d received set for OID_802_3_MULTICAST_LIST\n", port_num) ); + if( info_buf_len > MAX_MCAST * sizeof(mac_addr_t) ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_802_3_MULTICAST_LIST - Multicast list full.\n", port_num) ); + status = NDIS_STATUS_MULTICAST_FULL; + *p_bytes_needed = MAX_MCAST * sizeof(mac_addr_t); + } + else if( info_buf_len % sizeof(mac_addr_t) ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_802_3_MULTICAST_LIST - Invalid input buffer.\n", port_num) ); + status = NDIS_STATUS_INVALID_DATA; + } + else if( !info_buf && info_buf_len ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_802_3_MULTICAST_LIST - Invalid input buffer.\n", port_num) ); + status = NDIS_STATUS_INVALID_DATA; + } + else + { + ipoib_refresh_mcast( p_adapter, (mac_addr_t*)info_buf, + (uint8_t)(info_buf_len / sizeof(mac_addr_t)) ); + + buf_len = info_buf_len; + /* + * Note that we don't return pending. It will likely take longer + * for our SA transactions to complete than NDIS will give us + * before reseting the adapter. If an SA failure is encountered, + * the adapter will be marked as hung and we will get reset. + */ + status = NDIS_STATUS_SUCCESS; + } + break; + + case OID_TCP_TASK_OFFLOAD: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_TCP_TASK_OFFLOAD\n", port_num) ); + + buf_len = info_buf_len; + status = + __ipoib_set_tcp_task_offload( p_adapter, pNdisRequest ); + break; + + /* Optional General */ + case OID_GEN_TRANSPORT_HEADER_OFFSET: +#ifdef NDIS51_MINIPORT + case OID_GEN_RNDIS_CONFIG_PARAMETER: + case OID_GEN_VLAN_ID: +#endif + status = NDIS_STATUS_NOT_SUPPORTED; + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received an unsupported oid of 0x%.8X!\n", port_num, oid)); + break; + + case OID_GEN_SUPPORTED_LIST: + case OID_GEN_HARDWARE_STATUS: + case OID_GEN_MEDIA_SUPPORTED: + case OID_GEN_MEDIA_IN_USE: + case OID_GEN_MAXIMUM_FRAME_SIZE: + case OID_GEN_LINK_SPEED: + case OID_GEN_TRANSMIT_BUFFER_SPACE: + case OID_GEN_RECEIVE_BUFFER_SPACE: + case OID_GEN_MAXIMUM_LOOKAHEAD: + case OID_GEN_TRANSMIT_BLOCK_SIZE: + case OID_GEN_RECEIVE_BLOCK_SIZE: + case OID_GEN_MAXIMUM_TOTAL_SIZE: + case OID_GEN_VENDOR_ID: + case OID_GEN_VENDOR_DESCRIPTION: + case OID_GEN_VENDOR_DRIVER_VERSION: + case OID_GEN_DRIVER_VERSION: + case OID_GEN_MAC_OPTIONS: + case OID_GEN_MEDIA_CONNECT_STATUS: + case OID_GEN_MAXIMUM_SEND_PACKETS: + case OID_GEN_SUPPORTED_GUIDS: + case OID_GEN_PHYSICAL_MEDIUM: + default: + status = NDIS_STATUS_INVALID_OID; + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received an invalid oid of 0x%.8X!\n", port_num, oid)); + break; + } + + if( status == NDIS_STATUS_SUCCESS ) + { + pNdisRequest->DATA.SET_INFORMATION.BytesRead = buf_len; + } + else + { + if( status == NDIS_STATUS_INVALID_LENGTH ) + { + if ( !*p_bytes_needed ) + { + *p_bytes_needed = buf_len; + } + } + + pNdisRequest->DATA.SET_INFORMATION.BytesRead = 0; + } + + IPOIB_EXIT( IPOIB_DBG_OID ); + return status; +} +#endif +static NDIS_STATUS +ipoib_oid_handler( + IN NDIS_HANDLE adapter_context, + IN PNDIS_OID_REQUEST pNdisRequest) +{ + NDIS_REQUEST_TYPE RequestType; + NDIS_STATUS status = NDIS_STATUS_SUCCESS; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + RequestType = pNdisRequest->RequestType; + + switch(RequestType) + { + case NdisRequestSetInformation: + status = ipoib_set_info(adapter_context, + pNdisRequest->DATA.SET_INFORMATION.Oid, + pNdisRequest->DATA.SET_INFORMATION.InformationBuffer, + pNdisRequest->DATA.SET_INFORMATION.InformationBufferLength, + (PULONG)&pNdisRequest->DATA.SET_INFORMATION.BytesRead, + (PULONG)&pNdisRequest->DATA.SET_INFORMATION.BytesNeeded); + break; + + case NdisRequestQueryInformation: + case NdisRequestQueryStatistics: + status = ipoib_query_info(adapter_context, + pNdisRequest->DATA.QUERY_INFORMATION.Oid, + pNdisRequest->DATA.QUERY_INFORMATION.InformationBuffer, + pNdisRequest->DATA.QUERY_INFORMATION.InformationBufferLength, + (PULONG)&pNdisRequest->DATA.QUERY_INFORMATION.BytesWritten, + (PULONG)&pNdisRequest->DATA.QUERY_INFORMATION.BytesNeeded); + + break; + + default: + status = NDIS_STATUS_NOT_SUPPORTED; + break; + } + IPOIB_EXIT( IPOIB_DBG_OID ); + return status; +} + +//! Transfers some number of packets, specified as an array of packet pointers, over the network. +/* For a deserialized driver, these packets are completed asynchronously +using NdisMSendComplete. +IRQL <= DISPATCH_LEVEL + +@param adapter_context Pointer to ipoib_adapter_t structure with per NIC state +@param packet_array Array of packets to send +@param numPackets Number of packets in the array +*/ +void +ipoib_send_net_buffer_list( + IN NDIS_HANDLE adapter_context, + IN PNET_BUFFER_LIST net_buffer_list, + IN NDIS_PORT_NUMBER port_num, + IN ULONG send_flags + ) +{ + ipoib_adapter_t *p_adapter; + ipoib_port_t *p_port; + ULONG send_complete_flags; + PNET_BUFFER_LIST curr_net_buffer_list; + PNET_BUFFER_LIST next_net_buffer_list; + NDIS_STATUS status = NDIS_STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(port_num); + PERF_DECLARE( SendPackets ); + PERF_DECLARE( PortSend ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + cl_perf_start( SendPackets ); + + CL_ASSERT( adapter_context ); + p_adapter = (ipoib_adapter_t*)adapter_context; + p_port = p_adapter->p_port; + + cl_obj_lock( &p_adapter->obj ); + if( p_adapter->ipoib_state == IPOIB_PAUSING || + p_adapter->ipoib_state == IPOIB_PAUSED) + { + status = NDIS_STATUS_PAUSED; + cl_obj_unlock( &p_adapter->obj ); + goto compl_status; + } + + if( p_adapter->state != IB_PNP_PORT_ACTIVE || !p_adapter->p_port ) + { + cl_obj_unlock( &p_adapter->obj ); + status = NDIS_STATUS_FAILURE; + goto compl_status; + } + + p_port = p_adapter->p_port; + ipoib_port_ref( p_port, ref_send_packets ); + cl_obj_unlock( &p_adapter->obj ); + //IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + // ("Starting NET BUFFER LIST \n") ); + for (curr_net_buffer_list = net_buffer_list; + curr_net_buffer_list != NULL; + curr_net_buffer_list = next_net_buffer_list) + { + next_net_buffer_list = NET_BUFFER_LIST_NEXT_NBL(curr_net_buffer_list); + cl_perf_start( PortSend ); + + ipoib_port_send( p_port, curr_net_buffer_list, send_flags); + cl_perf_stop( &adapter->perf, PortSend ); + } + ipoib_port_deref( p_port, ref_send_packets ); + + cl_perf_stop( &p_adapter->perf, SendPackets ); + + cl_perf_log( &p_adapter->perf, SendBundle, num_packets ); + +compl_status: + if (status != NDIS_STATUS_SUCCESS) + { + //ASSERT(FALSE); //???? + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Got bad status \n") ); + send_complete_flags = 0; + + for (curr_net_buffer_list = net_buffer_list; + curr_net_buffer_list != NULL; + curr_net_buffer_list = next_net_buffer_list) + { + next_net_buffer_list = NET_BUFFER_LIST_NEXT_NBL(curr_net_buffer_list); + NET_BUFFER_LIST_STATUS(curr_net_buffer_list) = status; + ipoib_inc_send_stat( p_adapter, IP_STAT_DROPPED, 0 ); + } + + + if (NDIS_TEST_SEND_AT_DISPATCH_LEVEL(send_flags)) + { + NDIS_SET_SEND_COMPLETE_FLAG(send_complete_flags, NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL); + } + + NdisMSendNetBufferListsComplete( + p_adapter->h_adapter, + net_buffer_list, + send_complete_flags); + } + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + +void +ipoib_pnp_notify( + IN NDIS_HANDLE adapter_context, + IN PNET_DEVICE_PNP_EVENT pnp_event) +{ + ipoib_adapter_t *p_adapter; + + IPOIB_ENTER( IPOIB_DBG_PNP ); + + p_adapter = (ipoib_adapter_t*)adapter_context; + + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_PNP, ("Event %d\n", pnp_event->DevicePnPEvent) ); + if( pnp_event->DevicePnPEvent != NdisDevicePnPEventPowerProfileChanged ) + { + cl_obj_lock( &p_adapter->obj ); + p_adapter->state = IB_PNP_PORT_REMOVE; + cl_obj_unlock( &p_adapter->obj ); + + ipoib_resume_oids( p_adapter ); + } + + IPOIB_EXIT( IPOIB_DBG_PNP ); +} + + +VOID +ipoib_shutdown_ex( + IN NDIS_HANDLE adapter_context, + IN NDIS_SHUTDOWN_ACTION shutdown_action) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + UNUSED_PARAM( adapter_context ); + UNUSED_PARAM( shutdown_action ); + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +void +ipoib_resume_oids( + IN ipoib_adapter_t* const p_adapter ) +{ + ULONG info; + NDIS_STATUS status; + boolean_t pending_query, pending_set; + pending_oid_t query_oid = {0}; + pending_oid_t set_oid = {0}; + KLOCK_QUEUE_HANDLE hdl; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + cl_obj_lock( &p_adapter->obj ); + /* + * Set the status depending on our state. Fail OID requests that + * are pending while we reset the adapter. + */ + switch( p_adapter->state ) + { + case IB_PNP_PORT_ADD: + status = NDIS_STATUS_FAILURE; + break; + + case IB_PNP_PORT_REMOVE: + status = NDIS_STATUS_NOT_ACCEPTED; + break; + + default: + status = NDIS_STATUS_SUCCESS; + } + + pending_query = p_adapter->pending_query; + if( pending_query ) + { + query_oid = p_adapter->query_oid; + p_adapter->pending_query = FALSE; + } + pending_set = p_adapter->pending_set; + if( pending_set ) + { + set_oid = p_adapter->set_oid; + p_adapter->pending_set = FALSE; + } + cl_obj_unlock( &p_adapter->obj ); + + /* + * If we had a pending OID request for OID_GEN_LINK_SPEED, + * complete it now. Note that we hold the object lock since + * NdisMQueryInformationComplete is called at DISPATCH_LEVEL. + */ + if( pending_query ) + { + switch( query_oid.oid ) + { + case OID_GEN_LINK_SPEED: + ipoib_complete_query( p_adapter, &query_oid, + status, &p_adapter->port_rate, sizeof(p_adapter->port_rate) ); + break; + + case OID_GEN_MEDIA_CONNECT_STATUS: + info = NdisMediaStateConnected; + ipoib_complete_query( p_adapter, &query_oid, + status, &info, sizeof(info) ); + break; + + default: + CL_ASSERT( query_oid.oid == OID_GEN_LINK_SPEED || + query_oid.oid == OID_GEN_MEDIA_CONNECT_STATUS ); + break; + } + } + + if( pending_set ) + { + switch( set_oid.oid ) + { + case OID_GEN_CURRENT_PACKET_FILTER: + /* Validation already performed in the SetInformation path. */ + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + cl_obj_lock( &p_adapter->obj ); + if( !p_adapter->packet_filter && (*(PULONG)set_oid.p_buf) ) + { + cl_qlist_insert_tail( + &g_ipoib.adapter_list, &p_adapter->entry ); + /* + * Filter was zero, now non-zero. Register IP addresses + * with SA. + */ + ipoib_reg_addrs( p_adapter ); + } + else if( p_adapter->packet_filter && !(*(PULONG)set_oid.p_buf) ) + { + /* Filter was non-zero, now zero. Deregister IP addresses. */ + ipoib_dereg_addrs( p_adapter ); + + ASSERT( cl_qlist_count( &g_ipoib.adapter_list ) ); + cl_qlist_remove_item( + &g_ipoib.adapter_list, &p_adapter->entry ); + } + p_adapter->packet_filter = *(PULONG)set_oid.p_buf; + + cl_obj_unlock( &p_adapter->obj ); + KeReleaseInStackQueuedSpinLock( &hdl ); + p_adapter->set_oid.p_pending_oid = NULL; + NdisMOidRequestComplete( p_adapter->h_adapter, set_oid.p_pending_oid, status ); + break; + + case OID_GEN_NETWORK_LAYER_ADDRESSES: + status = __ipoib_set_net_addr( p_adapter, + p_adapter->set_oid.p_buf, + p_adapter->set_oid.buf_len, + p_adapter->set_oid.p_bytes_used, + p_adapter->set_oid.p_bytes_needed ); + + if( status != NDIS_STATUS_PENDING ) + { + p_adapter->set_oid.p_pending_oid = NULL; + NdisMOidRequestComplete( p_adapter->h_adapter, set_oid.p_pending_oid, status ); + } + break; + + default: + CL_ASSERT( set_oid.oid && 0 ); + break; + } + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static NDIS_STATUS +__ipoib_set_net_addr( + IN ipoib_adapter_t * p_adapter, + IN PVOID info_buf, + IN ULONG info_buf_len, + OUT PULONG p_bytes_read, + OUT PULONG p_bytes_needed ) +{ + NDIS_STATUS status; + PNETWORK_ADDRESS_LIST p_net_addrs; + PNETWORK_ADDRESS p_net_addr_oid; + PNETWORK_ADDRESS_IP p_ip_addr; + + net_address_item_t *p_addr_item; + + cl_status_t cl_status; + + size_t idx; + LONG i; + ULONG addr_size; + ULONG total_size; + + uint8_t port_num; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + status = NDIS_STATUS_SUCCESS; + port_num = p_adapter->guids.port_num; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_NETWORK_LAYER_ADDRESSES\n", + port_num) ); + + if( !info_buf ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Port %d - OID_GEN_NETWORK_LAYER_ADDRESSES - " + "NULL buffer\n", port_num) ); + IPOIB_EXIT( IPOIB_DBG_OID ); + return NDIS_STATUS_INVALID_DATA; + } + + /* + * Must use field offset because the structures define array's of size one + * of a the incorrect type for what is really stored. + */ + if( info_buf_len < FIELD_OFFSET(NETWORK_ADDRESS_LIST, Address) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - " + "bad length of %d, not enough " + "for NETWORK_ADDRESS_LIST (%d)\n", port_num, info_buf_len, + FIELD_OFFSET(NETWORK_ADDRESS_LIST, Address)) ); + *p_bytes_needed = FIELD_OFFSET(NETWORK_ADDRESS_LIST, Address); + IPOIB_EXIT( IPOIB_DBG_OID ); + return NDIS_STATUS_INVALID_LENGTH; + } + + p_net_addrs = (PNETWORK_ADDRESS_LIST)info_buf; + if( p_net_addrs->AddressCount == 0) + { + if( p_net_addrs->AddressType == NDIS_PROTOCOL_ID_TCP_IP ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - " + "clear TCP/IP addresses\n", port_num) ); + } + else + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - " + "Non TCP/IP address type of 0x%.4X on clear\n", + port_num, p_net_addrs->AddressType) ); + IPOIB_EXIT( IPOIB_DBG_OID ); + return NDIS_STATUS_SUCCESS; + } + } + + addr_size = FIELD_OFFSET(NETWORK_ADDRESS, Address) + + NETWORK_ADDRESS_LENGTH_IP; + total_size = FIELD_OFFSET(NETWORK_ADDRESS_LIST, Address) + + addr_size * p_net_addrs->AddressCount; + + if( info_buf_len < total_size ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - " + "bad length of %d, %d required for %d addresses\n", + port_num, info_buf_len, total_size, p_net_addrs->AddressCount) ); + *p_bytes_needed = total_size; + IPOIB_EXIT( IPOIB_DBG_OID ); + return NDIS_STATUS_INVALID_LENGTH; + } + + /* Lock lists for duration since SA callbacks can occur on other CPUs */ + cl_obj_lock( &p_adapter->obj ); + + /* Set the capacity of the vector to accomodate all assinged addresses. */ + cl_status = cl_vector_set_capacity( + &p_adapter->ip_vector, p_net_addrs->AddressCount ); + if( cl_status != CL_SUCCESS ) + { + cl_obj_unlock( &p_adapter->obj ); + IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Port %d - OID_GEN_NETWORK_LAYER_ADDRESSES - " + "Failed to set IP vector capacity: %#x\n", port_num, + cl_status) ); + IPOIB_EXIT( IPOIB_DBG_OID ); + return NDIS_STATUS_RESOURCES; + } + + *p_bytes_read = total_size; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - List contains %d addresses\n", + port_num, p_net_addrs->AddressCount)); + + /* First look for addresses we had that should be removed */ + for( idx = 0; idx != cl_vector_get_size( &p_adapter->ip_vector ); idx++ ) + { + p_addr_item = (net_address_item_t*) + cl_vector_get_ptr( &p_adapter->ip_vector, idx ); + p_net_addr_oid = (PNETWORK_ADDRESS)p_net_addrs->Address; + + for( i = 0; i < p_net_addrs->AddressCount; ++i, p_net_addr_oid = + (PNETWORK_ADDRESS)((uint8_t *)p_net_addr_oid + + FIELD_OFFSET(NETWORK_ADDRESS, Address) + + p_net_addr_oid->AddressLength) ) + { + + if( p_net_addr_oid->AddressType != NDIS_PROTOCOL_ID_TCP_IP ) + { + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Address %d is wrong type of 0x%.4X, " + "should be 0x%.4X\n", port_num, i, p_net_addr_oid->AddressType, + NDIS_PROTOCOL_ID_TCP_IP)); + continue; + } + + if( p_net_addr_oid->AddressLength != NETWORK_ADDRESS_LENGTH_IP) + { + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Address %d is wrong size of %d, " + "should be %d\n", port_num, i, p_net_addr_oid->AddressLength, + NETWORK_ADDRESS_LENGTH_IP)); + continue; + } + + p_ip_addr = (PNETWORK_ADDRESS_IP)p_net_addr_oid->Address; + if( !cl_memcmp( &p_ip_addr->in_addr, + &p_addr_item->address.as_ulong, sizeof(ULONG) ) ) + { + break; + } + } + + if( i == p_net_addrs->AddressCount ) + { + /* Didn't find a match, delete from SA */ + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Deleting Address %d.%d.%d.%d\n", + port_num, + p_addr_item->address.as_bytes[0], + p_addr_item->address.as_bytes[1], + p_addr_item->address.as_bytes[2], + p_addr_item->address.as_bytes[3])); + + if( p_addr_item->p_reg ) + { + if( p_addr_item->p_reg->h_reg_svc ) + { + p_adapter->p_ifc->dereg_svc( + p_addr_item->p_reg->h_reg_svc, __ipoib_ats_dereg_cb ); + } + else + { + cl_free( p_addr_item->p_reg ); + } + p_addr_item->p_reg = NULL; + } + p_addr_item->address.as_ulong = 0; + } + } + + /* Now look for new addresses */ + p_net_addr_oid = (NETWORK_ADDRESS *)p_net_addrs->Address; + idx = 0; + for( i = 0; i < p_net_addrs->AddressCount; i++, p_net_addr_oid = + (PNETWORK_ADDRESS)((uint8_t *)p_net_addr_oid + + FIELD_OFFSET(NETWORK_ADDRESS, Address) + p_net_addr_oid->AddressLength) ) + { + + if( p_net_addr_oid->AddressType != NDIS_PROTOCOL_ID_TCP_IP ) + { + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Address %d is wrong type of 0x%.4X, " + "should be 0x%.4X\n", port_num, i, p_net_addr_oid->AddressType, + NDIS_PROTOCOL_ID_TCP_IP)); + continue; + } + + if( p_net_addr_oid->AddressLength != NETWORK_ADDRESS_LENGTH_IP) + { + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Address %d is wrong size of %d, " + "should be %d\n", port_num, i, p_net_addr_oid->AddressLength, + NETWORK_ADDRESS_LENGTH_IP)); + continue; + } + + p_ip_addr = (PNETWORK_ADDRESS_IP)p_net_addr_oid->Address; + + /* Size the vector as needed. */ + if( cl_vector_get_size( &p_adapter->ip_vector ) <= idx ) + cl_vector_set_size( &p_adapter->ip_vector, idx + 1 ); + + p_addr_item = cl_vector_get_ptr( &p_adapter->ip_vector, idx ); + if( !cl_memcmp( &p_ip_addr->in_addr, &p_addr_item->address.as_ulong, + sizeof(ULONG) ) ) + { + idx++; + /* Already have this address - no change needed */ + continue; + } + + /* + * Copy the address information, but don't register yet - the port + * could be down. + */ + if( p_addr_item->p_reg ) + { + /* If in use by some other address, deregister. */ + if( p_addr_item->p_reg->h_reg_svc ) + { + p_adapter->p_ifc->dereg_svc( + p_addr_item->p_reg->h_reg_svc, __ipoib_ats_dereg_cb ); + } + else + { + cl_free( p_addr_item->p_reg ); + } + p_addr_item->p_reg = NULL; + } + memcpy ((void *)&p_addr_item->address.as_ulong, (const void *)&p_ip_addr->in_addr, sizeof(ULONG) ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Adding Address %d.%d.%d.%d\n", + port_num, + p_addr_item->address.as_bytes[0], + p_addr_item->address.as_bytes[1], + p_addr_item->address.as_bytes[2], + p_addr_item->address.as_bytes[3]) ); + idx++; + } + + /* Now clear any extra entries that shouldn't be there. */ + while( idx < cl_vector_get_size( &p_adapter->ip_vector ) ) + { + p_addr_item = (net_address_item_t*) + cl_vector_get_ptr( &p_adapter->ip_vector, + cl_vector_get_size( &p_adapter->ip_vector ) - 1 ); + + if( p_addr_item->p_reg ) + { + if( p_addr_item->p_reg->h_reg_svc ) + { + p_adapter->p_ifc->dereg_svc( + p_addr_item->p_reg->h_reg_svc, __ipoib_ats_dereg_cb ); + } + else + { + cl_free( p_addr_item->p_reg ); + } + p_addr_item->p_reg = NULL; + p_addr_item->address.as_ulong = 0; + } + + /* No need to check return value - shrinking always succeeds. */ + cl_vector_set_size( &p_adapter->ip_vector, + cl_vector_get_size( &p_adapter->ip_vector ) - 1 ); + } + + if( p_adapter->state == IB_PNP_PORT_ACTIVE && p_adapter->packet_filter ) + ipoib_reg_addrs( p_adapter ); + + cl_obj_unlock( &p_adapter->obj ); + + IPOIB_EXIT( IPOIB_DBG_OID ); + return NDIS_STATUS_SUCCESS; +} + + +/* Object lock is held when this function is called. */ +void +ipoib_reg_addrs( + IN ipoib_adapter_t* const p_adapter ) +{ + net_address_item_t *p_addr_item; + + size_t idx; + + uint8_t port_num; + + ib_api_status_t ib_status; + ib_reg_svc_req_t ib_service; + ib_gid_t port_gid; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + if(p_adapter->guids.port_guid.pkey != IB_DEFAULT_PKEY) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR,IPOIB_DBG_ERROR, + ("ATS Service available for default pkey only\n")); + return; + } + port_num = p_adapter->guids.port_num; + + /* Setup our service call with things common to all calls */ + cl_memset( &ib_service, 0, sizeof(ib_service) ); + + /* BUGBUG Only register local subnet GID prefix for now */ + ib_gid_set_default( &port_gid, p_adapter->guids.port_guid.guid ); + ib_service.svc_rec.service_gid = port_gid; + + ib_service.svc_rec.service_pkey = IB_DEFAULT_PKEY; + ib_service.svc_rec.service_lease = IB_INFINITE_SERVICE_LEASE; + + /* Must cast here because the service name is an array of unsigned chars but + * strcpy want a pointer to a signed char */ + if ( StringCchCopy( (char *)ib_service.svc_rec.service_name, + sizeof(ib_service.svc_rec.service_name) / sizeof(char), ATS_NAME ) != S_OK) { + ASSERT(FALSE); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR,IPOIB_DBG_ERROR, + ("Problem copying ATS name: exiting\n")); + return; + } + + /* IP Address in question will be put in below */ + ib_service.port_guid = p_adapter->guids.port_guid.guid; + ib_service.timeout_ms = p_adapter->params.sa_timeout; + ib_service.retry_cnt = p_adapter->params.sa_retry_cnt; + + /* Can't set IB_FLAGS_SYNC here because I can't wait at dispatch */ + ib_service.flags = 0; + + /* Service context will be put in below */ + + ib_service.svc_data_mask = IB_SR_COMPMASK_SID | + IB_SR_COMPMASK_SGID | + IB_SR_COMPMASK_SPKEY | + IB_SR_COMPMASK_SLEASE | + IB_SR_COMPMASK_SNAME | + IB_SR_COMPMASK_SDATA8_12 | + IB_SR_COMPMASK_SDATA8_13 | + IB_SR_COMPMASK_SDATA8_14 | + IB_SR_COMPMASK_SDATA8_15; + ib_service.pfn_reg_svc_cb = __ipoib_ats_reg_cb; + + for( idx = 0; idx < cl_vector_get_size( &p_adapter->ip_vector); idx++ ) + { + p_addr_item = (net_address_item_t*) + cl_vector_get_ptr( &p_adapter->ip_vector, idx ); + + if( p_addr_item->p_reg ) + continue; + + p_addr_item->p_reg = cl_zalloc( sizeof(ats_reg_t) ); + if( !p_addr_item->p_reg ) + break; + + p_addr_item->p_reg->p_adapter = p_adapter; + + ib_service.svc_context = p_addr_item->p_reg; + + ib_service.svc_rec.service_id = + ATS_SERVICE_ID & CL_HTON64(0xFFFFFFFFFFFFFF00); + /* ATS service IDs start at 0x10000CE100415453 */ + ib_service.svc_rec.service_id |= ((uint64_t)(idx + 0x53)) << 56; + + cl_memcpy( &ib_service.svc_rec.service_data8[ATS_IPV4_OFFSET], + p_addr_item->address.as_bytes, IPV4_ADDR_SIZE ); + + /* Take a reference for each service request. */ + cl_obj_ref(&p_adapter->obj); + ib_status = p_adapter->p_ifc->reg_svc( + p_adapter->h_al, &ib_service, &p_addr_item->p_reg->h_reg_svc ); + if( ib_status != IB_SUCCESS ) + { + if( ib_status == IB_INVALID_GUID ) + { + /* If this occurs, we log the error but do not fail the OID yet */ + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - " + "Failed to register IP Address " + "of %d.%d.%d.%d with error IB_INVALID_GUID\n", + port_num, + p_addr_item->address.as_bytes[0], + p_addr_item->address.as_bytes[1], + p_addr_item->address.as_bytes[2], + p_addr_item->address.as_bytes[3]) ); + } + else + { + /* Fatal error. */ + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Failed to register IP Address " + "of %d.%d.%d.%d with error %s\n", + port_num, + p_addr_item->address.as_bytes[0], + p_addr_item->address.as_bytes[1], + p_addr_item->address.as_bytes[2], + p_addr_item->address.as_bytes[3], + p_adapter->p_ifc->get_err_str( ib_status )) ); + p_adapter->hung = TRUE; + } + cl_obj_deref(&p_adapter->obj); + cl_free( p_addr_item->p_reg ); + p_addr_item->p_reg = NULL; + } + } + + IPOIB_EXIT( IPOIB_DBG_OID ); +} + + +/* Object lock is held when this function is called. */ +void +ipoib_dereg_addrs( + IN ipoib_adapter_t* const p_adapter ) +{ + net_address_item_t *p_addr_item; + + size_t idx; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + for( idx = 0; idx < cl_vector_get_size( &p_adapter->ip_vector); idx++ ) + { + p_addr_item = (net_address_item_t*) + cl_vector_get_ptr( &p_adapter->ip_vector, idx ); + + if( !p_addr_item->p_reg ) + continue; + + if( p_addr_item->p_reg->h_reg_svc ) + { + p_adapter->p_ifc->dereg_svc( + p_addr_item->p_reg->h_reg_svc, __ipoib_ats_dereg_cb ); + } + else + { + cl_free( p_addr_item->p_reg ); + } + p_addr_item->p_reg = NULL; + } + + IPOIB_EXIT( IPOIB_DBG_OID ); +} + + +void +ipoib_cancel_xmit( + IN NDIS_HANDLE adapter_context, + IN PVOID cancel_id ) +{ + ipoib_adapter_t* const p_adapter = + (ipoib_adapter_t* const )adapter_context; + + if( p_adapter && p_adapter->p_port ) + { + ipoib_port_cancel_xmit( p_adapter->p_port, cancel_id ); + } +} + + +static void +__ipoib_ats_reg_cb( + IN ib_reg_svc_rec_t *p_reg_svc_rec ) +{ + ats_reg_t *p_reg; + uint8_t port_num; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + CL_ASSERT( p_reg_svc_rec ); + CL_ASSERT( p_reg_svc_rec->svc_context ); + + p_reg = (ats_reg_t*)p_reg_svc_rec->svc_context; + port_num = p_reg->p_adapter->guids.port_num; + + cl_obj_lock( &p_reg->p_adapter->obj ); + + if( p_reg_svc_rec->req_status == IB_SUCCESS && + !p_reg_svc_rec->resp_status ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Registered IP Address " + "of %d.%d.%d.%d\n", + port_num, + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET], + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET+1], + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET+2], + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET+3]) ); + } + else if( p_reg_svc_rec->req_status != IB_CANCELED ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Failed to register IP Address " + "of %d.%d.%d.%d with error %s\n", + port_num, + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET], + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET+1], + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET+2], + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET+3], + p_reg->p_adapter->p_ifc->get_err_str( p_reg_svc_rec->resp_status )) ); + p_reg->p_adapter->hung = TRUE; + p_reg->h_reg_svc = NULL; + } + + cl_obj_unlock( &p_reg->p_adapter->obj ); + cl_obj_deref(&p_reg->p_adapter->obj); + + IPOIB_EXIT( IPOIB_DBG_OID ); +} + + +static void +__ipoib_ats_dereg_cb( + IN void *context ) +{ + cl_free( context ); +} + +static NDIS_STATUS +ipoib_pause( + IN NDIS_HANDLE adapter_context, + IN PNDIS_MINIPORT_PAUSE_PARAMETERS pause_parameters) +{ + ipoib_adapter_t *p_adapter; + KLOCK_QUEUE_HANDLE hdl; + + UNREFERENCED_PARAMETER(pause_parameters); + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT(adapter_context); + p_adapter = (ipoib_adapter_t*)adapter_context; + CL_ASSERT(p_adapter->ipoib_state == IPOIB_RUNNING); + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + p_adapter->ipoib_state = IPOIB_PAUSING; + KeReleaseInStackQueuedSpinLock( &hdl ); + + //TODO: + ipoib_port_resume(p_adapter->p_port,FALSE); +// ASSERT(FALSE); + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + p_adapter->ipoib_state = IPOIB_PAUSED; + KeReleaseInStackQueuedSpinLock( &hdl ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_SUCCESS; +} + +static NDIS_STATUS +ipoib_restart( + IN NDIS_HANDLE adapter_context, + IN PNDIS_MINIPORT_RESTART_PARAMETERS restart_parameters) +{ + ipoib_adapter_t *p_adapter; + KLOCK_QUEUE_HANDLE hdl; + PNDIS_RESTART_ATTRIBUTES NdisRestartAttributes; + PNDIS_RESTART_GENERAL_ATTRIBUTES NdisGeneralAttributes; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + p_adapter = (ipoib_adapter_t*)adapter_context; + + NdisRestartAttributes = restart_parameters->RestartAttributes; + + if (NdisRestartAttributes != NULL) + { + CL_ASSERT(NdisRestartAttributes->Oid == OID_GEN_MINIPORT_RESTART_ATTRIBUTES); + NdisGeneralAttributes = (PNDIS_RESTART_GENERAL_ATTRIBUTES)NdisRestartAttributes->Data; + // + // Check to see if we need to change any attributes + } + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + p_adapter->ipoib_state = IPOIB_RUNNING; + KeReleaseInStackQueuedSpinLock( &hdl ); + + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_SUCCESS; +} + +/*++ +Routine Description: + + This function aborts the request pending in the miniport. + +Arguments: + + MiniportAdapterContext Pointer to the adapter structure + RequestId Specify the request to be cancelled. + +Return Value: + +--*/ +static void +ipoib_cancel_oid_request( + IN NDIS_HANDLE adapter_context, + IN PVOID requestId + ) +{ + PNDIS_OID_REQUEST pending_request; + ipoib_adapter_t *p_adapter; + + IPOIB_ENTER( IPOIB_DBG_OID ); + p_adapter = (ipoib_adapter_t*)adapter_context; + + cl_obj_lock( &p_adapter->obj ); + + if ( p_adapter->query_oid.p_pending_oid && + p_adapter->query_oid.p_pending_oid->RequestId == requestId) + { + pending_request = p_adapter->query_oid.p_pending_oid; + p_adapter->query_oid.p_pending_oid = NULL; + p_adapter->pending_query = FALSE; + } + else if(p_adapter->set_oid.p_pending_oid && + p_adapter->set_oid.p_pending_oid->RequestId == requestId) + { + pending_request = p_adapter->set_oid.p_pending_oid; + p_adapter->set_oid.p_pending_oid = NULL; + p_adapter->pending_set = FALSE; + } + else + { + cl_obj_unlock( &p_adapter->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("No Pending OID found\n") ); + return; + } + cl_obj_unlock( &p_adapter->obj ); + + NdisMOidRequestComplete(p_adapter->h_adapter, + pending_request, + NDIS_STATUS_REQUEST_ABORTED); + + IPOIB_EXIT( IPOIB_DBG_OID ); +} diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib_driver.h b/branches/winverbs/ulp/ipoib/kernel6/ipoib_driver.h new file mode 100644 index 00000000..5ba79d91 --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib_driver.h @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_driver.h 4226 2009-04-06 06:01:03Z xalex $ + */ + + +#ifndef _IPOIB_DRIVER_H_ +#define _IPOIB_DRIVER_H_ + + +#include "ipoib_log.h" +#include "ipoib_adapter.h" +#include +#include +#include "ipoib_debug.h" + + +/* + * Definitions + */ +#define MAX_BUNDLE_ID_LENGTH 32 + +/* The maximum number of send packets the MiniportSendPackets function can accept */ +#define MINIPORT_MAX_SEND_PACKETS 200 + +/* MLX4 supports 4K MTU */ +#define MAX_IB_MTU 4096 +#define DEFAULT_MTU 2048 +/* + * Header length as defined by IPoIB spec: + * http://www.ietf.org/internet-drafts/draft-ietf-ipoib-ip-over-infiniband-04.txt + */ + +#define MAX_UD_PAYLOAD_MTU (MAX_IB_MTU - sizeof(ipoib_hdr_t)) +#define DEFAULT_PAYLOAD_MTU (DEFAULT_MTU - sizeof(ipoib_hdr_t)) +#define MAX_CM_PAYLOAD_MTU (65520) +#define MAX_WRS_PER_MSG ((MAX_CM_PAYLOAD_MTU/DEFAULT_PAYLOAD_MTU)+1) +/* + * Only the protocol type is sent as part of the UD payload + * since the rest of the Ethernet header is encapsulated in the + * various IB headers. We report out buffer space as if we + * transmit the ethernet headers. + */ +#define MAX_XFER_BLOCK_SIZE (sizeof(eth_hdr_t) + MAX_UD_PAYLOAD_MTU) +#define DATA_OFFSET (sizeof(eth_hdr_t) - sizeof(ipoib_hdr_t)) + +#define IPOIB_CM_FLAG_RC (0x80) +#define IPOIB_CM_FLAG_UC (0x40) +#define IPOIB_CM_FLAG_SVCID (0x10) // OFED set IETF bit this way ( open OFED PR 1121 ) + +#define MAX_SEND_SGE (8) + +/* Amount of physical memory to register. */ +#define MEM_REG_SIZE 0xFFFFFFFFFFFFFFFF + +/* Number of work completions to chain for send and receive polling. */ +#define MAX_SEND_WC 5 +#define MAX_RECV_WC 16 + +typedef struct _ipoib_globals +{ + KSPIN_LOCK lock; + cl_qlist_t adapter_list; + cl_qlist_t bundle_list; + + atomic32_t laa_idx; + + NDIS_HANDLE h_ndis_wrapper; + PDEVICE_OBJECT h_ibat_dev; + volatile LONG ibat_ref; + uint32_t bypass_check_bcast_rate; + +} ipoib_globals_t; +/* +* FIELDS +* lock +* Spinlock to protect list access. +* +* adapter_list +* List of all adapter instances. Used for address translation support. +* +* bundle_list +* List of all adapter bundles. +* +* laa_idx +* Global counter for generating LAA MACs +* +* h_ibat_dev +* Device handle returned by NdisMRegisterDevice. +*********/ + +extern ipoib_globals_t g_ipoib; + + +typedef struct _ipoib_bundle +{ + cl_list_item_t list_item; + char bundle_id[MAX_BUNDLE_ID_LENGTH]; + cl_qlist_t adapter_list; + +} ipoib_bundle_t; +/* +* FIELDS +* list_item +* List item for storing the bundle in a quick list. +* +* bundle_id +* Bundle identifier. +* +* adapter_list +* List of adapters in the bundle. The adapter at the head is the +* primary adapter of the bundle. +*********/ +void +ipoib_create_log( + NDIS_HANDLE h_adapter, + UINT ind, + ULONG eventLogMsgId); + +#define GUID_MASK_LOG_INDEX 0 + +void +ipoib_resume_oids( + IN ipoib_adapter_t* const p_adapter ); + +#define IPOIB_OFFSET(field) ((UINT)FIELD_OFFSET(ipoib_params_t,field)) +#define IPOIB_SIZE(field) sizeof(((ipoib_params_t*)0)->field) +#define IPOIB_INIT_NDIS_STRING(str) \ + (str)->Length = 0; \ + (str)->MaximumLength = 0; \ + (str)->Buffer = NULL; + + + +#endif /* _IPOIB_DRIVER_H_ */ diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib_endpoint.c b/branches/winverbs/ulp/ipoib/kernel6/ipoib_endpoint.c new file mode 100644 index 00000000..1e82e5a5 --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib_endpoint.c @@ -0,0 +1,1170 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_endpoint.c 4226 2009-04-06 06:01:03Z xalex $ + */ + + + +#include "ipoib_endpoint.h" +#include "ipoib_port.h" +#include "ipoib_debug.h" +#if defined(EVENT_TRACING) +#ifdef offsetof +#undef offsetof +#endif +#include "ipoib_endpoint.tmh" +#endif +#include +#include + + +static void +__endpt_destroying( + IN cl_obj_t* p_obj ); + +static void +__endpt_cleanup( + IN cl_obj_t* p_obj ); + +static void +__endpt_free( + IN cl_obj_t* p_obj ); + +static ib_api_status_t +__create_mcast_av( + IN ib_pd_handle_t h_pd, + IN uint8_t port_num, + IN ib_member_rec_t* const p_member_rec, + OUT ib_av_handle_t* const ph_av ); + +static inline ipoib_port_t* +__endpt_parent( + IN ipoib_endpt_t* const p_endpt ); + +static void +__path_query_cb( + IN ib_query_rec_t *p_query_rec ); + +static void +__endpt_resolve( + IN ipoib_endpt_t* const p_endpt ); + +static void +__endpt_cm_send_cb( + IN const ib_cq_handle_t h_cq, + IN void *cq_context ); +static void +__endpt_cm_recv_cb( + IN const ib_cq_handle_t h_cq, + IN void *cq_context ); + +static void +__endpt_cm_buf_mgr_construct( + IN endpt_buf_mgr_t * const p_buf_mgr ); +static void +__conn_reply_cb( + IN ib_cm_rep_rec_t *p_cm_rep ); + +static void +__conn_mra_cb( + IN ib_cm_mra_rec_t *p_mra_rec ); + +static void +__conn_rej_cb( + IN ib_cm_rej_rec_t *p_rej_rec ); + +static void +__conn_dreq_cb( + IN ib_cm_dreq_rec_t *p_dreq_rec ); + +#if 0 //CM +static cl_status_t +__cm_recv_desc_ctor( + IN void* const p_object, + IN void* context, + OUT cl_pool_item_t** const pp_pool_item ); + +static void +__cm_recv_desc_dtor( + IN const cl_pool_item_t* const p_pool_item, + IN void *context ); + +static NDIS_PACKET* +__endpt_cm_get_ndis_pkt( + IN ipoib_port_t* const p_port, + IN ipoib_cm_desc_t* const p_desc ); + +static inline ipoib_cm_desc_t* +__endpt_cm_buf_mgr_get_recv( + IN endpt_buf_mgr_t * const p_buf_mgr ); + +static boolean_t +__cm_recv_is_dhcp( + IN const ipoib_pkt_t* const p_ipoib ); + +static ib_api_status_t +__endpt_cm_recv_arp( + IN ipoib_port_t* const p_port, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src_endpt ); + +static ib_api_status_t +__endpt_cm_recv_udp( + IN ipoib_port_t* const p_port, + IN ib_wc_t* const p_wc, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src_endpt ); +#endif + +ipoib_endpt_t* +ipoib_endpt_create( + IN const ib_gid_t* const p_dgid, + IN const net16_t dlid, + IN const net32_t qpn ) +{ + ipoib_endpt_t *p_endpt; + cl_status_t status; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + p_endpt = cl_zalloc( sizeof(ipoib_endpt_t) ); + if( !p_endpt ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate endpoint (%d bytes)\n", + sizeof(ipoib_endpt_t)) ); + return NULL; + } + + cl_obj_construct( &p_endpt->obj, IPOIB_OBJ_ENDPOINT ); + + status = cl_obj_init( &p_endpt->obj, CL_DESTROY_ASYNC, + __endpt_destroying, __endpt_cleanup, __endpt_free ); + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("Created endpoint: [ %p ] DLID: %#x QPN: %#x \n", + p_endpt, cl_ntoh16(dlid), cl_ntoh32(qpn) ) ); + + p_endpt->dgid = *p_dgid; + p_endpt->dlid = dlid; + p_endpt->qpn = qpn; + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return p_endpt; +} + + +static ib_api_status_t +__create_mcast_av( + IN ib_pd_handle_t h_pd, + IN uint8_t port_num, + IN ib_member_rec_t* const p_member_rec, + OUT ib_av_handle_t* const ph_av ) +{ + ib_av_attr_t av_attr; + uint32_t flow_lbl; + uint8_t hop_lmt; + ib_api_status_t status; + ipoib_endpt_t *p_endpt; + + IPOIB_ENTER( IPOIB_DBG_MCAST ); + + p_endpt = PARENT_STRUCT(ph_av, ipoib_endpt_t, h_av ); + + cl_memclr( &av_attr, sizeof(ib_av_attr_t) ); + av_attr.port_num = port_num; + ib_member_get_sl_flow_hop( p_member_rec->sl_flow_hop, + &av_attr.sl, &flow_lbl, &hop_lmt ); + av_attr.dlid = p_member_rec->mlid; + av_attr.grh_valid = TRUE; + av_attr.grh.hop_limit = hop_lmt; + av_attr.grh.dest_gid = p_member_rec->mgid; + av_attr.grh.src_gid = p_member_rec->port_gid; + av_attr.grh.ver_class_flow = + ib_grh_set_ver_class_flow( 6, p_member_rec->tclass, flow_lbl ); + av_attr.static_rate = p_member_rec->rate & IB_PATH_REC_BASE_MASK; + av_attr.path_bits = 0; + /* port is not attached to endpoint at this point, so use endpt ifc reference */ + status = p_endpt->p_ifc->create_av( h_pd, &av_attr, ph_av ); + + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_create_av returned %s\n", + p_endpt->p_ifc->get_err_str( status )) ); + } + + IPOIB_EXIT( IPOIB_DBG_MCAST ); + return status; +} + + +ib_api_status_t +ipoib_endpt_set_mcast( + IN ipoib_endpt_t* const p_endpt, + IN ib_pd_handle_t h_pd, + IN uint8_t port_num, + IN ib_mcast_rec_t* const p_mcast_rec ) +{ + ib_api_status_t status; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("Create av for MAC: %02X-%02X-%02X-%02X-%02X-%02X\n", + p_endpt->mac.addr[0], p_endpt->mac.addr[1], + p_endpt->mac.addr[2], p_endpt->mac.addr[3], + p_endpt->mac.addr[4], p_endpt->mac.addr[5]) ); + + status = __create_mcast_av( h_pd, port_num, p_mcast_rec->p_member_rec, + &p_endpt->h_av ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__create_mcast_av returned %s\n", + p_endpt->p_ifc->get_err_str( status )) ); + return status; + } + p_endpt->h_mcast = p_mcast_rec->h_mcast; + CL_ASSERT(p_endpt->dlid == 0); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return IB_SUCCESS; +} + + +static void +__endpt_destroying( + IN cl_obj_t* p_obj ) +{ + ipoib_endpt_t *p_endpt; + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + p_endpt = PARENT_STRUCT( p_obj, ipoib_endpt_t, obj ); + p_port = __endpt_parent( p_endpt ); + + cl_obj_lock( p_obj ); + if( p_endpt->h_query ) + { + p_port->p_adapter->p_ifc->cancel_query( + p_port->p_adapter->h_al, p_endpt->h_query ); + p_endpt->h_query = NULL; + } + + /* Leave the multicast group if it exists. */ + if( p_endpt->h_mcast ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("Leaving MCast group\n") ); + ipoib_port_ref(p_port, ref_leave_mcast); + p_port->p_adapter->p_ifc->leave_mcast( p_endpt->h_mcast, ipoib_leave_mcast_cb ); + } +#if 0 + else if( p_port->p_adapter->params.cm_enabled ) + { + p_endpt->cm_flag = 0; + CL_ASSERT( endpt_cm_get_state( p_endpt ) == IPOIB_CM_DISCONNECTED ); + } +#endif + + cl_obj_unlock( p_obj ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); +} + + +static void +__endpt_cleanup( + IN cl_obj_t* p_obj ) +{ + ipoib_endpt_t *p_endpt; + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + p_endpt = PARENT_STRUCT( p_obj, ipoib_endpt_t, obj ); + p_port = __endpt_parent( p_endpt ); + + /* Destroy the AV if it exists. */ + if( p_endpt->h_av ) + p_port->p_adapter->p_ifc->destroy_av( p_endpt->h_av ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); +} + + +static void +__endpt_free( + IN cl_obj_t* p_obj ) +{ + ipoib_endpt_t *p_endpt; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + p_endpt = PARENT_STRUCT( p_obj, ipoib_endpt_t, obj ); + + cl_obj_deinit( p_obj ); + cl_free( p_endpt ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); +} + + +static inline ipoib_port_t* +__endpt_parent( + IN ipoib_endpt_t* const p_endpt ) +{ + return PARENT_STRUCT( p_endpt->rel.p_parent_obj, ipoib_port_t, obj ); +} + +ipoib_port_t* +ipoib_endpt_parent( + IN ipoib_endpt_t* const p_endpt ) +{ + return __endpt_parent( p_endpt ); +} + +/* + * This function is called with the port object's send lock held and + * a reference held on the endpoint. If we fail, we release the reference. + */ +NDIS_STATUS +ipoib_endpt_queue( + IN ipoib_endpt_t* const p_endpt ) +{ + ib_api_status_t status; + ipoib_port_t *p_port; + ib_av_attr_t av_attr; + net32_t flow_lbl; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + if( p_endpt->h_av ) + { + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return NDIS_STATUS_SUCCESS; + } + + if( p_endpt->qpn == CL_HTON32(0x00FFFFFF) ) + { + /* + * Handle a race between the mcast callback and a receive/send. The QP + * is joined to the MC group before the MC callback is received, so it + * can receive packets, and NDIS can try to respond. We need to delay + * a response until the MC callback runs and sets the AV. + */ + ipoib_endpt_deref( p_endpt ); + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return NDIS_STATUS_PENDING; + } + + /* This is the first packet for this endpoint. Create the AV. */ + p_port = __endpt_parent( p_endpt ); + + cl_memclr( &av_attr, sizeof(ib_av_attr_t) ); + + av_attr.port_num = p_port->port_num; + + ib_member_get_sl_flow_hop( + p_port->ib_mgr.bcast_rec.sl_flow_hop, + &av_attr.sl, + &flow_lbl, + &av_attr.grh.hop_limit + ); + + av_attr.dlid = p_endpt->dlid; + + /* + * We always send the GRH so that we preferably lookup endpoints + * by GID rather than by LID. This allows certain WHQL tests + * such as the 2c_MediaCheck test to succeed since they don't use + * IP. This allows endpoints to be created on the fly for requests + * for which there is no match, something that doesn't work when + * using LIDs only. + */ + av_attr.grh_valid = TRUE; + av_attr.grh.ver_class_flow = ib_grh_set_ver_class_flow( + 6, p_port->ib_mgr.bcast_rec.tclass, flow_lbl ); + av_attr.grh.resv1 = 0; + av_attr.grh.resv2 = 0; + ib_gid_set_default( &av_attr.grh.src_gid, p_port->p_adapter->guids.port_guid.guid ); + av_attr.grh.dest_gid = p_endpt->dgid; + + av_attr.static_rate = p_port->ib_mgr.bcast_rec.rate; + av_attr.path_bits = 0; + + /* Create the AV. */ + status = p_port->p_adapter->p_ifc->create_av( + p_port->ib_mgr.h_pd, &av_attr, &p_endpt->h_av ); + if( status != IB_SUCCESS ) + { + p_port->p_adapter->hung = TRUE; + ipoib_endpt_deref( p_endpt ); + cl_obj_unlock( &p_endpt->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_create_av failed with %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return NDIS_STATUS_FAILURE; + } + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return NDIS_STATUS_SUCCESS; +} + +#if 0 + +static void +__endpt_cm_buf_mgr_construct( + IN endpt_buf_mgr_t * const p_buf_mgr ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + cl_qpool_construct( &p_buf_mgr->recv_pool ); + + p_buf_mgr->h_packet_pool = NULL; + p_buf_mgr->h_buffer_pool = NULL; + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + +ib_api_status_t +endpt_cm_buf_mgr_init( + IN ipoib_port_t* const p_port ) +{ + cl_status_t cl_status; + NDIS_STATUS ndis_status; + ib_api_status_t ib_status = IB_SUCCESS; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + if( p_port->cm_buf_mgr.pool_init ) + return ib_status; + + cl_qlist_init( &p_port->cm_buf_mgr.posted_list ); + + __endpt_cm_buf_mgr_construct( &p_port->cm_buf_mgr ); + p_port->cm_recv_mgr.rq_depth = + min( (uint32_t)p_port->p_adapter->params.rq_depth * 8, + p_port->p_ca_attrs->max_srq_wrs/2 ); + p_port->cm_recv_mgr.depth = 0; + /* Allocate the receive descriptors pool */ + cl_status = cl_qpool_init( &p_port->cm_buf_mgr.recv_pool, + p_port->cm_recv_mgr.rq_depth , + 0, + 0, + sizeof( ipoib_cm_desc_t ), + __cm_recv_desc_ctor, + __cm_recv_desc_dtor, + p_port ); + + if( cl_status != CL_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_RECV_POOL, 1, cl_status ); + + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_qpool_init for cm recvs returned %#x\n", cl_status) ); + + return IB_INSUFFICIENT_MEMORY; + } + + /* Allocate the NDIS buffer and packet pools for receive indication. */ + NdisAllocatePacketPool( &ndis_status, + &p_port->cm_buf_mgr.h_packet_pool, + p_port->cm_recv_mgr.rq_depth, + PROTOCOL_RESERVED_SIZE_IN_PACKET ); + if( ndis_status != NDIS_STATUS_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_RECV_PKT_POOL, 1, ndis_status ); + + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisAllocatePacketPool returned %08X\n", ndis_status) ); + + ib_status = IB_INSUFFICIENT_RESOURCES; + goto pkt_pool_failed; + } + + NdisAllocateBufferPool( &ndis_status, + &p_port->cm_buf_mgr.h_buffer_pool, + p_port->cm_recv_mgr.rq_depth ); + if( ndis_status != NDIS_STATUS_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_RECV_BUF_POOL, 1, ndis_status ); + + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisAllocateBufferPool returned %08X\n", ndis_status) ); + + ib_status = IB_INSUFFICIENT_RESOURCES; + goto buf_pool_failed; + } + //NDIS60 + //p_port->cm_recv_mgr.recv_pkt_array = + //cl_zalloc( sizeof(NDIS_PACKET*) * p_port->cm_recv_mgr.rq_depth ); + p_port->cm_recv_mgr.recv_lst_array = + cl_zalloc( sizeof(NET_BUFFER_LIST*) * p_port->cm_recv_mgr.rq_depth ); + + + + if( !p_port->cm_recv_mgr.recv_pkt_array ) + { + //NDIS60 + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_zalloc for NET_BUFFER_LIST array failed.\n") ); + + ib_status = IB_INSUFFICIENT_MEMORY; + goto pkt_array_failed; + } + + p_port->cm_buf_mgr.pool_init = TRUE; + return IB_SUCCESS; + +pkt_array_failed: + if( p_port->cm_buf_mgr.h_buffer_pool ) + NdisFreeBufferPool( p_port->cm_buf_mgr.h_buffer_pool ); +buf_pool_failed: + if( p_port->cm_buf_mgr.h_packet_pool ) + NdisFreePacketPool( p_port->cm_buf_mgr.h_packet_pool ); +pkt_pool_failed: + cl_qpool_destroy( &p_port->cm_buf_mgr.recv_pool ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return ib_status; +} + +void +endpt_cm_buf_mgr_reset( + IN ipoib_port_t* const p_port ) +{ + cl_list_item_t *p_item; + + if( !p_port->cm_buf_mgr.pool_init ) + return; + + if( cl_qlist_count( &p_port->cm_buf_mgr.posted_list ) ) + { + for( p_item = cl_qlist_remove_head( &p_port->cm_buf_mgr.posted_list ); + p_item != cl_qlist_end( &p_port->cm_buf_mgr.posted_list ); + p_item = cl_qlist_remove_head( &p_port->cm_buf_mgr.posted_list ) ) + { + cl_qpool_put( &p_port->cm_buf_mgr.recv_pool, + &( PARENT_STRUCT( p_item, ipoib_cm_desc_t, list_item ))->item ); + } + } +} + +void +endpt_cm_buf_mgr_destroy( + IN ipoib_port_t* const p_port ) +{ + + IPOIB_ENTER(IPOIB_DBG_INIT ); + + CL_ASSERT( p_port ); + + /* Free the receive descriptors. */ + if( !p_port->cm_buf_mgr.pool_init ) + return; + + endpt_cm_buf_mgr_reset( p_port ); + + p_port->cm_buf_mgr.pool_init = FALSE; + + if( p_port->cm_recv_mgr.recv_pkt_array ) + { + cl_free( p_port->cm_recv_mgr.recv_pkt_array ); + } + + /* Destroy the receive packet and buffer pools. */ + if( p_port->cm_buf_mgr.h_buffer_pool ) + NdisFreeBufferPool( p_port->cm_buf_mgr.h_buffer_pool ); + if( p_port->cm_buf_mgr.h_packet_pool ) + NdisFreePacketPool( p_port->cm_buf_mgr.h_packet_pool ); + + cl_qpool_destroy( &p_port->cm_buf_mgr.recv_pool ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + +static cl_status_t +__cm_recv_desc_ctor( + IN void* const p_object, + IN void* context, + OUT cl_pool_item_t** const pp_pool_item ) +{ + ipoib_cm_desc_t* p_desc; + ipoib_port_t* p_port; + ib_mr_create_t create_mr; + net32_t rkey; + + CL_ASSERT( p_object ); + CL_ASSERT( context ); + + p_desc = (ipoib_cm_desc_t*)p_object; + p_port = (ipoib_port_t*)context; + +#define BUF_ALIGN (16) + + p_desc->alloc_buf_size = + ROUNDUP( p_port->p_adapter->params.cm_xfer_block_size, BUF_ALIGN ); + + p_desc->p_alloc_buf = (uint8_t *)ExAllocatePoolWithTag( + NonPagedPool, p_desc->alloc_buf_size, 'DOMC' ); + + if( p_desc->p_alloc_buf == NULL ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate receive buffer size %d bytes.\n", p_desc->alloc_buf_size ) ); + return CL_INSUFFICIENT_MEMORY; + } + + create_mr.vaddr = p_desc->p_alloc_buf; + create_mr.length = p_desc->alloc_buf_size; + create_mr.access_ctrl = IB_AC_LOCAL_WRITE; + + + if( p_port->p_adapter->p_ifc->reg_mem( + p_port->ib_mgr.h_pd, + &create_mr, + &p_desc->lkey, + &rkey, + &p_desc->h_mr ) != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to create Memory Region size %d bytes.\n", p_desc->alloc_buf_size ) ); + goto ctor_failed; + } + p_desc->p_buf = p_desc->p_alloc_buf + (BUF_ALIGN - sizeof( ipoib_hdr_t)); + p_desc->buf_size = p_desc->alloc_buf_size - (BUF_ALIGN - sizeof( ipoib_hdr_t)); + + /* Setup the local data segment. */ + p_desc->local_ds[0].vaddr = (uint64_t)(uintn_t)p_desc->p_buf; + p_desc->local_ds[0].length = p_desc->buf_size; + p_desc->local_ds[0].lkey = p_desc->lkey; + + /* Setup the work request. */ + p_desc->wr.wr_id = (uintn_t)p_desc; + p_desc->wr.ds_array = p_desc->local_ds; + p_desc->wr.num_ds = 1; + p_desc->type = PKT_TYPE_CM_UCAST; + + *pp_pool_item = &p_desc->item; + return CL_SUCCESS; + +ctor_failed: + ExFreePoolWithTag( p_desc->p_alloc_buf, 'DOMC' ); + return CL_INSUFFICIENT_MEMORY; +} + +static void +__cm_recv_desc_dtor( + IN const cl_pool_item_t* const p_pool_item, + IN void *context ) +{ + ipoib_cm_desc_t *p_desc; + ipoib_port_t* p_port; + + if( p_pool_item == NULL || context == NULL ) + return; + + p_port = (ipoib_port_t*)context; + p_desc = PARENT_STRUCT( p_pool_item, ipoib_cm_desc_t, item ); + + if( p_desc->h_mr ) + p_port->p_adapter->p_ifc->dereg_mr( p_desc->h_mr ); + + if( p_desc->p_alloc_buf ) + ExFreePoolWithTag( p_desc->p_alloc_buf, 'DOMC' ); +} + + +static NDIS_PACKET* +__endpt_cm_get_ndis_pkt( + IN ipoib_port_t* const p_port, + IN ipoib_cm_desc_t* const p_desc ) +{ + NDIS_STATUS status; + NDIS_PACKET *p_packet; + NDIS_BUFFER *p_buffer; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + NdisDprAllocatePacketNonInterlocked( &status, &p_packet, + p_port->cm_buf_mgr.h_packet_pool ); + if( status != NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate NDIS_PACKET: %08x\n", status) ); + return NULL; + } + + IPOIB_PORT_FROM_PACKET( p_packet ) = p_port; + IPOIB_RECV_FROM_PACKET( p_packet ) = p_desc; + + NdisAllocateBuffer( + &status, + &p_buffer, + p_port->cm_buf_mgr.h_buffer_pool, + (void *)(p_desc->p_buf - DATA_OFFSET), + p_desc->len + DATA_OFFSET ); + + if( status != NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate NDIS_BUFFER: %08x\n", status) ); + NdisDprFreePacketNonInterlocked( p_packet ); + return NULL; + } + + NdisChainBufferAtFront( p_packet, p_buffer ); + NDIS_SET_PACKET_HEADER_SIZE( p_packet, sizeof(eth_hdr_t) ); + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return p_packet; +} + +static inline ipoib_cm_desc_t* +__endpt_cm_buf_mgr_get_recv( + IN endpt_buf_mgr_t * const p_buf_mgr ) +{ + ipoib_cm_desc_t *p_desc; + + p_desc = (ipoib_cm_desc_t*)cl_qpool_get( &p_buf_mgr->recv_pool ); + if( p_desc ) + cl_qlist_insert_tail( &p_buf_mgr->posted_list, &p_desc->list_item ); + + return p_desc; +} + +void +endpt_cm_buf_mgr_put_recv( + IN endpt_buf_mgr_t * const p_buf_mgr, + IN ipoib_cm_desc_t* const p_desc ) +{ + + IPOIB_ENTER(IPOIB_DBG_RECV ); + + /* Return the descriptor to it's pool. */ + cl_qlist_remove_item( &p_buf_mgr->posted_list, &p_desc->list_item ); + cl_qpool_put( &p_buf_mgr->recv_pool, &p_desc->item ); + + IPOIB_EXIT( IPOIB_DBG_RECV ); +} + +void +endpt_cm_buf_mgr_put_recv_list( + IN endpt_buf_mgr_t * const p_buf_mgr, + IN cl_qlist_t* const p_list ) +{ + cl_qpool_put_list( &p_buf_mgr->recv_pool, p_list ); +} + +uint32_t +endpt_cm_recv_mgr_build_pkt_array( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt, + IN cl_qlist_t* const p_done_list, + IN OUT uint32_t* p_bytes_recv ) +{ + cl_list_item_t *p_item; + ipoib_cm_desc_t *p_desc; + uint32_t i = 0; + NDIS_PACKET *p_packet; + NDIS_TCP_IP_CHECKSUM_PACKET_INFO chksum; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + UNUSED_PARAM( p_endpt ); + + p_item = cl_qlist_remove_head( p_done_list ); + + *p_bytes_recv = 0; + + for( p_item; p_item != cl_qlist_end( p_done_list ); + p_item = cl_qlist_remove_head( p_done_list ) ) + { + p_desc = (ipoib_cm_desc_t*)p_item; + + p_packet = __endpt_cm_get_ndis_pkt( p_port, p_desc ); + if( !p_packet ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get Packet from descriptor\n" ) ); + endpt_cm_buf_mgr_put_recv( &p_port->cm_buf_mgr, p_desc ); + p_port->cm_recv_mgr.depth--; + continue; + } + chksum.Value = 0; + switch( p_port->p_adapter->params.recv_chksum_offload ) + { + default: + CL_ASSERT( FALSE ); + case CSUM_DISABLED: + case CSUM_ENABLED: + NDIS_PER_PACKET_INFO_FROM_PACKET( p_packet, TcpIpChecksumPacketInfo ) = + (void*)(uintn_t)chksum.Value; + break; + case CSUM_BYPASS: + /* Flag the checksums as having been calculated. */ + chksum.Receive.NdisPacketTcpChecksumSucceeded = TRUE; + chksum.Receive.NdisPacketUdpChecksumSucceeded = TRUE; + chksum.Receive.NdisPacketIpChecksumSucceeded = TRUE; + NDIS_PER_PACKET_INFO_FROM_PACKET( p_packet, TcpIpChecksumPacketInfo ) = + (void*)(uintn_t)chksum.Value; + break; + } + + NDIS_SET_PACKET_STATUS( p_packet, NDIS_STATUS_SUCCESS ); + p_port->cm_recv_mgr.recv_pkt_array[i] = p_packet; + i++; + *p_bytes_recv += p_desc->len; + } + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return i; +} +void +endpt_cm_flush_recv( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt ) +{ + ib_api_status_t ib_status = IB_SUCCESS; + ib_qp_mod_t mod_attr; + ib_wc_t wc[MAX_RECV_WC]; + ib_wc_t *p_free_wc; + ib_wc_t *p_done_wc; + ib_wc_t *p_wc; + ipoib_cm_desc_t *p_desc; + size_t i; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + CL_ASSERT( p_endpt ); + + if( p_endpt->conn.h_recv_qp ) + { + cl_memclr( &mod_attr, sizeof( mod_attr ) ); + mod_attr.req_state = IB_QPS_ERROR; + p_port->p_adapter->p_ifc->modify_qp( p_endpt->conn.h_send_qp, &mod_attr ); + p_port->p_adapter->p_ifc->modify_qp( p_endpt->conn.h_recv_qp, &mod_attr ); + + for( i = 0; i < MAX_RECV_WC; i++ ) + wc[i].p_next = &wc[i + 1]; + wc[MAX_RECV_WC - 1].p_next = NULL; + + do + { + p_free_wc = wc; + ib_status = + p_port->p_adapter->p_ifc->poll_cq( p_endpt->conn.h_recv_cq, + &p_free_wc, &p_done_wc ); + if( ib_status != IB_SUCCESS && + ib_status != IB_NOT_FOUND ) + { + /* connection CQ failed */ + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Poll Recv CQ failed status %#x\n", ib_status ) ); + break; + } + cl_spinlock_acquire( &p_port->recv_lock ); + for( p_wc = p_done_wc; p_wc; p_wc = p_wc->p_next ) + { + p_desc = (ipoib_cm_desc_t *)(uintn_t)p_wc->wr_id; + endpt_cm_buf_mgr_put_recv( &p_port->cm_buf_mgr, p_desc ); + p_port->cm_recv_mgr.depth--; + } + cl_spinlock_release( &p_port->recv_lock ); + } while( !p_free_wc ); + + ib_status = p_port->p_adapter->p_ifc->destroy_qp( p_endpt->conn.h_recv_qp, NULL ); + if( ib_status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Destroy Recv QP failed status %#x\n", ib_status ) ); + } + p_endpt->conn.h_recv_qp = NULL; + } + + if( p_endpt->conn.h_send_qp ) + { + ib_status = p_port->p_adapter->p_ifc->destroy_qp( p_endpt->conn.h_send_qp, NULL ); + if( ib_status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Destroy Send QP failed status %#x\n", ib_status ) ); + } + p_endpt->conn.h_send_qp = NULL; + } + + IPOIB_EXIT( IPOIB_DBG_RECV ); +} + +int32_t +endpt_cm_recv_mgr_filter( + IN ipoib_endpt_t* const p_endpt, + IN ib_wc_t* const p_done_wc_list, + OUT cl_qlist_t* const p_done_list, + OUT cl_qlist_t* const p_bad_list ) +{ + ib_api_status_t ib_status; + ipoib_cm_desc_t *p_desc; + ib_wc_t *p_wc; + ipoib_pkt_t *p_ipoib; + eth_pkt_t *p_eth; + ipoib_port_t* p_port; + int32_t recv_cnt; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + p_port = ipoib_endpt_parent( p_endpt ); + + for( p_wc = p_done_wc_list, recv_cnt = 0; p_wc; p_wc = p_wc->p_next ) + { + p_desc = (ipoib_cm_desc_t *)(uintn_t)p_wc->wr_id; + recv_cnt++; + if( p_wc->status != IB_WCS_SUCCESS ) + { + if( p_wc->status != IB_WCS_WR_FLUSHED_ERR ) + { + + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed completion %s (vendor specific %#x)\n", + p_port->p_adapter->p_ifc->get_wc_status_str( p_wc->status ), + (int)p_wc->vendor_specific) ); + } + else + { + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_RECV, + ("Flushed completion %s\n", + p_port->p_adapter->p_ifc->get_wc_status_str( p_wc->status )) ); + } + + ipoib_inc_recv_stat( p_port->p_adapter, IP_STAT_ERROR, 0, 0 ); + + cl_qlist_remove_item( &p_port->cm_buf_mgr.posted_list,&p_desc->list_item ); + cl_qlist_insert_tail( p_bad_list, &p_desc->item.list_item ); + continue; + } + + /* Successful completion + Setup the ethernet/ip/arp header and queue descriptor for report. */ + ib_status = IB_SUCCESS; + p_ipoib = (ipoib_pkt_t *)((uint8_t*)p_desc->p_buf ); + p_eth = (eth_pkt_t *)((uint8_t*)p_desc->p_buf - DATA_OFFSET ); + + switch( p_ipoib->hdr.type ) + { + case ETH_PROT_TYPE_ARP: + if( p_wc->length < (sizeof(ipoib_hdr_t) + sizeof(ipoib_arp_pkt_t)) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received ARP packet too short\n") ); + ib_status = IB_ERROR; + break; + } + ib_status = + __endpt_cm_recv_arp( p_port, p_ipoib, p_eth, p_endpt ); + break; + case ETH_PROT_TYPE_IP: + if( p_wc->length < (sizeof(ipoib_hdr_t) + sizeof(ip_hdr_t)) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received IP packet too short\n") ); + ib_status = IB_ERROR; + break; + } + if( p_ipoib->type.ip.hdr.prot == IP_PROT_UDP ) + { + ib_status = + __endpt_cm_recv_udp( p_port, p_wc, p_ipoib, p_eth, p_endpt ); + } + + break; + } + + if( ib_status != IB_SUCCESS ) + { + ipoib_inc_recv_stat( p_port->p_adapter, IP_STAT_ERROR, 0, 0 ); + cl_qlist_insert_tail( p_bad_list, &p_desc->item.list_item ); + continue; + } + + p_eth->hdr.type = p_ipoib->hdr.type; + p_eth->hdr.src = p_endpt->mac; + p_eth->hdr.dst = p_port->p_adapter->mac; + + /* save payload length */ + p_desc->len = p_wc->length; + + cl_qlist_insert_tail( p_done_list, &p_desc->item.list_item ); + } + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return recv_cnt; +} + +ib_api_status_t +endpt_cm_post_recv( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t ib_status = IB_SUCCESS; + ipoib_cm_desc_t *p_head_desc = NULL; + ipoib_cm_desc_t *p_tail_desc = NULL; + ipoib_cm_desc_t *p_next_desc; + ib_recv_wr_t *p_failed_wc = NULL; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + while( cl_qpool_count( &p_port->cm_buf_mgr.recv_pool ) > 1 ) + { + /* Pull receives out of the pool and chain them up. */ + p_next_desc = __endpt_cm_buf_mgr_get_recv( + &p_port->cm_buf_mgr ); + if( !p_next_desc ) + { + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_RECV, + ("Out of receive descriptors! Endpt recv queue depth 0x%x\n", + p_port->cm_recv_mgr.depth ) ); + break; + } + + if( !p_tail_desc ) + { + p_tail_desc = p_next_desc; + p_next_desc->wr.p_next = NULL; + } + else + { + p_next_desc->wr.p_next = &p_head_desc->wr; + } + + p_head_desc = p_next_desc; + + p_port->cm_recv_mgr.depth++; + } + + if( p_head_desc ) + { + ib_status = p_port->p_adapter->p_ifc->post_srq_recv( + p_port->ib_mgr.h_srq, &p_head_desc->wr, &p_failed_wc ); + + if( ib_status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ip_post_recv returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( ib_status )) ); + + /* put descriptors back to the pool */ + while( p_failed_wc ) + { + p_head_desc = PARENT_STRUCT( p_failed_wc, ipoib_cm_desc_t, wr ); + p_failed_wc = p_failed_wc->p_next; + endpt_cm_buf_mgr_put_recv( &p_port->cm_buf_mgr, p_head_desc ); + p_port->cm_recv_mgr.depth--; + } + } + } + + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return( ib_status ); +} + +static ib_api_status_t +__endpt_cm_recv_arp( + IN ipoib_port_t* const p_port, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src_endpt ) +{ + const ipoib_arp_pkt_t *p_ib_arp; + arp_pkt_t *p_arp; + + p_ib_arp = &p_ipoib->type.arp; + p_arp = &p_eth->type.arp; + + if( p_ib_arp->hw_type != ARP_HW_TYPE_IB || + p_ib_arp->hw_size != sizeof(ipoib_hw_addr_t) || + p_ib_arp->prot_type != ETH_PROT_TYPE_IP ) + { + return IB_ERROR; + } + + p_arp->hw_type = ARP_HW_TYPE_ETH; + p_arp->hw_size = sizeof(mac_addr_t); + p_arp->src_hw = p_src_endpt->mac; + p_arp->src_ip = p_ib_arp->src_ip; + p_arp->dst_hw = p_port->p_local_endpt->mac; + p_arp->dst_ip = p_ib_arp->dst_ip; + + return IB_SUCCESS; +} + +static ib_api_status_t +__endpt_cm_recv_udp( + IN ipoib_port_t* const p_port, + IN ib_wc_t* const p_wc, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src_endpt ) +{ + ib_api_status_t ib_status = IB_SUCCESS; + + if( p_wc->length < + (sizeof(ipoib_hdr_t) + sizeof(ip_hdr_t) + sizeof(udp_hdr_t)) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received UDP packet too short\n") ); + return IB_ERROR; + } + if( __cm_recv_is_dhcp( p_ipoib ) ) + { + ib_status = ipoib_recv_dhcp( + p_port, p_ipoib, p_eth, p_src_endpt, p_port->p_local_endpt ); + } + + return ib_status; +} + +static boolean_t +__cm_recv_is_dhcp( + IN const ipoib_pkt_t* const p_ipoib ) +{ + return( (p_ipoib->type.ip.prot.udp.hdr.dst_port == DHCP_PORT_SERVER && + p_ipoib->type.ip.prot.udp.hdr.src_port == DHCP_PORT_CLIENT) || + (p_ipoib->type.ip.prot.udp.hdr.dst_port == DHCP_PORT_CLIENT && + p_ipoib->type.ip.prot.udp.hdr.src_port == DHCP_PORT_SERVER) ); +} +#endif diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib_endpoint.h b/branches/winverbs/ulp/ipoib/kernel6/ipoib_endpoint.h new file mode 100644 index 00000000..547d4652 --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib_endpoint.h @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_endpoint.h 4226 2009-04-06 06:01:03Z xalex $ + */ + + +#ifndef _IPOIB_ENDPOINT_H_ +#define _IPOIB_ENDPOINT_H_ + + +#include +#include +#include +#include +#include +#include "iba/ipoib_ifc.h" +#include +#include "ipoib_debug.h" + + +typedef struct _endpt_buf_mgr +{ + cl_qpool_t recv_pool; + NDIS_HANDLE h_packet_pool; + NDIS_HANDLE h_buffer_pool; + cl_qlist_t posted_list; + boolean_t pool_init; +} endpt_buf_mgr_t; + +typedef struct _endpt_recv_mgr +{ + int32_t depth; + int32_t rq_depth; + //NDIS60 + //NDIS_PACKET **recv_pkt_array; + NET_BUFFER_LIST *recv_lst_array; + +} endpt_recv_mgr_t; + + +typedef enum _cm_state +{ + IPOIB_CM_DISCONNECTED, + IPOIB_CM_INIT, + IPOIB_CM_CONNECT, + IPOIB_CM_CONNECTED, + IPOIB_CM_LISTEN, + IPOIB_CM_DREP_SENT, + IPOIB_CM_DREQ_SENT, + IPOIB_CM_REJ_RECVD, + IPOIB_CM_DESTROY +} cm_state_t; + +typedef struct _cm_private_data +{ + ib_net32_t ud_qpn; + ib_net32_t recv_mtu; +} cm_private_data_t; + +typedef struct _endpt_conn +{ + ib_net64_t service_id; + cm_private_data_t private_data; + ib_qp_handle_t h_send_qp; + ib_qp_handle_t h_recv_qp; + ib_qp_handle_t h_work_qp; + ib_cq_handle_t h_send_cq; + ib_cq_handle_t h_recv_cq; + ib_listen_handle_t h_cm_listen; + cm_state_t state; + +} endpt_conn_t; + +typedef struct _ipoib_endpt +{ + cl_obj_t obj; + cl_obj_rel_t rel; + cl_map_item_t mac_item; + cl_fmap_item_t gid_item; + cl_map_item_t lid_item; + cl_fmap_item_t conn_item; + LIST_ENTRY list_item; + ib_query_handle_t h_query; + ib_mcast_handle_t h_mcast; + mac_addr_t mac; + ib_gid_t dgid; + net16_t dlid; + net32_t qpn; + uint8_t cm_flag; + ib_av_handle_t h_av; + endpt_conn_t conn; + + ib_al_ifc_t *p_ifc; + boolean_t is_in_use; + boolean_t is_mcast_listener; +} ipoib_endpt_t; +/* +* FIELDS +* mac_item +* Map item for storing the endpoint in a map. The key is the +* destination MAC address. +* +* lid_item +* Map item for storing the endpoint in a map. The key is the +* destination LID. +* +* gid_item +* Map item for storing the endpoint in a map. The key is the +* destination GID. +* +* h_query +* Query handle for cancelling SA queries. +* +* h_mcast +* For multicast endpoints, the multicast handle. +* +* mac +* MAC address. +* +* dgid +* Destination GID. +* +* dlid +* Destination LID. The destination LID is only set for endpoints +* that are on the same subnet. It is used as key in the LID map. +* +* qpn +* Destination queue pair number. +* +* h_av +* Address vector for sending data. +* +* expired +* Flag to indicate that the endpoint should be flushed. +* +* connection +* for connected mode endpoints +* +* p_ifc +* Reference to transport functions, can be used +* while endpoint is not attached to port yet. +* +* NOTES +* If the h_mcast member is set, the endpoint is never expired. +*********/ + + +ipoib_endpt_t* +ipoib_endpt_create( + IN const ib_gid_t* const p_dgid, + IN const net16_t dlid, + IN const net32_t qpn ); + + +ib_api_status_t +ipoib_endpt_set_mcast( + IN ipoib_endpt_t* const p_endpt, + IN ib_pd_handle_t h_pd, + IN uint8_t port_num, + IN ib_mcast_rec_t* const p_mcast_rec ); + + +static inline void +ipoib_endpt_ref( + IN ipoib_endpt_t* const p_endpt ) +{ + CL_ASSERT( p_endpt ); + + cl_obj_ref( &p_endpt->obj ); + /* + * Anytime we reference the endpoint, we're either receiving data + * or trying to send data to that endpoint. Clear the expired flag + * to prevent the AV from being flushed. + */ +} + + +static inline void +ipoib_endpt_deref( + IN ipoib_endpt_t* const p_endpt ) +{ + cl_obj_deref( &p_endpt->obj ); +} + + +NDIS_STATUS +ipoib_endpt_queue( + IN ipoib_endpt_t* const p_endpt ); + +struct _ipoib_port * +ipoib_endpt_parent( + IN ipoib_endpt_t* const p_endpt ); + +inline cm_state_t +endpt_cm_set_state( + IN ipoib_endpt_t* const p_endpt, + IN cm_state_t state ) +{ + return(cm_state_t)InterlockedExchange( + (volatile LONG *)&p_endpt->conn.state, + (LONG)state ); +} + +inline cm_state_t +endpt_cm_get_state( + IN ipoib_endpt_t* const p_endpt ) +{ + return( cm_state_t )InterlockedCompareExchange( + (volatile LONG *)&p_endpt->conn.state, + IPOIB_CM_DISCONNECTED, IPOIB_CM_DISCONNECTED ); +} + +ib_api_status_t +endpt_cm_create_qp( + IN ipoib_endpt_t* const p_endpt, + IN ib_qp_handle_t* const p_h_qp ); + +ib_api_status_t +ipoib_endpt_connect( + IN ipoib_endpt_t* const p_endpt ); + +int32_t +endpt_cm_recv_mgr_filter( + IN ipoib_endpt_t* const p_endpt, + IN ib_wc_t* const p_done_wc_list, + OUT cl_qlist_t* const p_done_list, + OUT cl_qlist_t* const p_bad_list ); + +#endif /* _IPOIB_ENDPOINT_H_ */ diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib_ibat.c b/branches/winverbs/ulp/ipoib/kernel6/ipoib_ibat.c new file mode 100644 index 00000000..ba89e00f --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib_ibat.c @@ -0,0 +1,666 @@ +/* + * Copyright (c) 2005 Mellanox Technologies. All rights reserved. + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_ibat.c 4226 2009-04-06 06:01:03Z xalex $ + */ + + +#include "ipoib_driver.h" +#include "ipoib_adapter.h" +#include "ipoib_port.h" +#include "ipoib_debug.h" +#if defined(EVENT_TRACING) +#ifdef offsetof +#undef offsetof +#endif +#include "ipoib_ibat.tmh" +#endif +#include + +extern PDRIVER_OBJECT g_p_drv_obj; + +static NTSTATUS +__ipoib_create( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ); + +static NTSTATUS +__ipoib_cleanup( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ); + +static NTSTATUS +__ipoib_close( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ); + +static NTSTATUS +__ipoib_dispatch( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ); + + +static NTSTATUS +__ibat_get_ports( + IN IRP *pIrp, + IN IO_STACK_LOCATION *pIoStack ) +{ + IOCTL_IBAT_PORTS_IN *pIn; + IOCTL_IBAT_PORTS_OUT *pOut; + KLOCK_QUEUE_HANDLE hdl; + cl_list_item_t *pItem; + ipoib_adapter_t *pAdapter; + LONG nPorts; + + IPOIB_ENTER(IPOIB_DBG_IOCTL); + + if( pIoStack->Parameters.DeviceIoControl.InputBufferLength != + sizeof(IOCTL_IBAT_PORTS_IN) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid input buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + if( pIoStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(IOCTL_IBAT_PORTS_OUT) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid output buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + pIn = pIrp->AssociatedIrp.SystemBuffer; + pOut = pIrp->AssociatedIrp.SystemBuffer; + + if( pIn->Version != IBAT_IOCTL_VERSION ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid version.\n") ); + return STATUS_INVALID_PARAMETER; + } + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + nPorts = (LONG)cl_qlist_count( &g_ipoib.adapter_list ); + switch( nPorts ) + { + case 0: + cl_memclr( pOut->Ports, sizeof(pOut->Ports) ); + /* Fall through */ + case 1: + pOut->Size = sizeof(IOCTL_IBAT_PORTS_OUT); + break; + + default: + pOut->Size = sizeof(IOCTL_IBAT_PORTS_OUT) + + (sizeof(IBAT_PORT_RECORD) * (nPorts - 1)); + break; + } + + pIrp->IoStatus.Information = pOut->Size; + + if( pOut->Size > pIoStack->Parameters.DeviceIoControl.OutputBufferLength ) + { + nPorts = 1 + + (pIoStack->Parameters.DeviceIoControl.OutputBufferLength - + sizeof(IOCTL_IBAT_PORTS_OUT)) / sizeof(IBAT_PORT_RECORD); + + pIrp->IoStatus.Information = sizeof(IOCTL_IBAT_PORTS_OUT) + + ((nPorts - 1) * sizeof(IBAT_PORT_RECORD)); + } + + pOut->NumPorts = 0; + pItem = cl_qlist_head( &g_ipoib.adapter_list ); + while( pOut->NumPorts != nPorts ) + { + pAdapter = CONTAINING_RECORD( pItem, ipoib_adapter_t, entry ); + pOut->Ports[pOut->NumPorts].CaGuid = pAdapter->guids.ca_guid; + pOut->Ports[pOut->NumPorts].PortGuid = pAdapter->guids.port_guid.guid; + pOut->Ports[pOut->NumPorts].PKey = IB_DEFAULT_PKEY; + pOut->Ports[pOut->NumPorts].PortNum = pAdapter->guids.port_num; + pOut->NumPorts++; + + pItem = cl_qlist_next( pItem ); + } + + KeReleaseInStackQueuedSpinLock( &hdl ); + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return STATUS_SUCCESS; +} + + +static NTSTATUS +__ibat_get_ips( + IN IRP *pIrp, + IN IO_STACK_LOCATION *pIoStack ) +{ + IOCTL_IBAT_IP_ADDRESSES_IN *pIn; + IOCTL_IBAT_IP_ADDRESSES_OUT *pOut; + KLOCK_QUEUE_HANDLE hdl; + cl_list_item_t *pItem; + ipoib_adapter_t *pAdapter; + LONG nIps, maxIps; + size_t idx; + net_address_item_t *pAddr; + UINT64 PortGuid; + + IPOIB_ENTER(IPOIB_DBG_IOCTL); + + if( pIoStack->Parameters.DeviceIoControl.InputBufferLength != + sizeof(IOCTL_IBAT_IP_ADDRESSES_IN) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid input buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + if( pIoStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(IOCTL_IBAT_IP_ADDRESSES_OUT) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid output buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + pIn = pIrp->AssociatedIrp.SystemBuffer; + pOut = pIrp->AssociatedIrp.SystemBuffer; + + if( pIn->Version != IBAT_IOCTL_VERSION ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid version.\n") ); + return STATUS_INVALID_PARAMETER; + } + + PortGuid = pIn->PortGuid; + + nIps = 0; + pOut->AddressCount = 0; + maxIps = 1 + + ((pIoStack->Parameters.DeviceIoControl.OutputBufferLength - + sizeof(IOCTL_IBAT_IP_ADDRESSES_OUT)) / sizeof(IP_ADDRESS)); + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + for( pItem = cl_qlist_head( &g_ipoib.adapter_list ); + pItem != cl_qlist_end( &g_ipoib.adapter_list ); + pItem = cl_qlist_next( pItem ) ) + { + pAdapter = CONTAINING_RECORD( pItem, ipoib_adapter_t, entry ); + if( PortGuid && pAdapter->guids.port_guid.guid != PortGuid ) + continue; + + cl_obj_lock( &pAdapter->obj ); + nIps += (LONG)cl_vector_get_size( &pAdapter->ip_vector ); + + for( idx = 0; + idx < cl_vector_get_size( &pAdapter->ip_vector ); + idx++ ) + { + if( pOut->AddressCount == maxIps ) + break; + + pAddr = (net_address_item_t*) + cl_vector_get_ptr( &pAdapter->ip_vector, idx ); + + pOut->Address[pOut->AddressCount].IpVersion = 4; + cl_memclr( &pOut->Address[pOut->AddressCount].Address, + sizeof(IP_ADDRESS) ); + cl_memcpy( &pOut->Address[pOut->AddressCount].Address[12], + pAddr->address.as_bytes, IPV4_ADDR_SIZE ); + + pOut->AddressCount++; + } + cl_obj_unlock( &pAdapter->obj ); + } + + pOut->Size = sizeof(IOCTL_IBAT_IP_ADDRESSES_OUT); + if( --nIps ) + pOut->Size += sizeof(IP_ADDRESS) * nIps; + + pIrp->IoStatus.Information = sizeof(IOCTL_IBAT_IP_ADDRESSES_OUT); + if( --maxIps < nIps ) + pIrp->IoStatus.Information += (sizeof(IP_ADDRESS) * maxIps); + else + pIrp->IoStatus.Information += (sizeof(IP_ADDRESS) * nIps); + + KeReleaseInStackQueuedSpinLock( &hdl ); + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return STATUS_SUCCESS; +} + + +static NTSTATUS +__ibat_mac_to_gid( + IN IRP *pIrp, + IN IO_STACK_LOCATION *pIoStack ) +{ + NTSTATUS status = STATUS_INVALID_PARAMETER; + IOCTL_IBAT_MAC_TO_GID_IN *pIn; + IOCTL_IBAT_MAC_TO_GID_OUT *pOut; + KLOCK_QUEUE_HANDLE hdl; + cl_list_item_t *pItem; + ipoib_adapter_t *pAdapter; + + IPOIB_ENTER(IPOIB_DBG_IOCTL); + + if( pIoStack->Parameters.DeviceIoControl.InputBufferLength != + sizeof(IOCTL_IBAT_MAC_TO_GID_IN) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid input buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + if( pIoStack->Parameters.DeviceIoControl.OutputBufferLength != + sizeof(IOCTL_IBAT_MAC_TO_GID_OUT) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid output buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + pIn = pIrp->AssociatedIrp.SystemBuffer; + pOut = pIrp->AssociatedIrp.SystemBuffer; + + if( pIn->Version != IBAT_IOCTL_VERSION ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid version.\n") ); + return STATUS_INVALID_PARAMETER; + } + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + + for( pItem = cl_qlist_head( &g_ipoib.adapter_list ); + pItem != cl_qlist_end( &g_ipoib.adapter_list ); + pItem = cl_qlist_next( pItem ) ) + { + pAdapter = CONTAINING_RECORD( pItem, ipoib_adapter_t, entry ); + if( pIn->PortGuid != pAdapter->guids.port_guid.guid ) + continue; + + /* Found the port - lookup the MAC. */ + cl_obj_lock( &pAdapter->obj ); + if( pAdapter->p_port ) + { + status = ipoib_mac_to_gid( + pAdapter->p_port, *(mac_addr_t*)pIn->DestMac, &pOut->DestGid ); + if( NT_SUCCESS( status ) ) + { + pIrp->IoStatus.Information = + sizeof(IOCTL_IBAT_MAC_TO_GID_OUT); + } + } + cl_obj_unlock( &pAdapter->obj ); + break; + } + + KeReleaseInStackQueuedSpinLock( &hdl ); + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return status; +} + + +static NTSTATUS +__ibat_mac_to_path( + IN IRP *pIrp, + IN IO_STACK_LOCATION *pIoStack ) +{ + NTSTATUS status = STATUS_INVALID_PARAMETER; + IOCTL_IBAT_MAC_TO_PATH_IN *pIn; + IOCTL_IBAT_MAC_TO_PATH_OUT *pOut; + KLOCK_QUEUE_HANDLE hdl; + cl_list_item_t *pItem; + ipoib_adapter_t *pAdapter; + + IPOIB_ENTER(IPOIB_DBG_IOCTL); + + if( pIoStack->Parameters.DeviceIoControl.InputBufferLength != + sizeof(IOCTL_IBAT_MAC_TO_PATH_IN) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid input buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + if( pIoStack->Parameters.DeviceIoControl.OutputBufferLength != + sizeof(IOCTL_IBAT_MAC_TO_PATH_OUT) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid output buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + pIn = pIrp->AssociatedIrp.SystemBuffer; + pOut = pIrp->AssociatedIrp.SystemBuffer; + + if( pIn->Version != IBAT_IOCTL_VERSION ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid version.\n") ); + return STATUS_INVALID_PARAMETER; + } + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + + for( pItem = cl_qlist_head( &g_ipoib.adapter_list ); + pItem != cl_qlist_end( &g_ipoib.adapter_list ); + pItem = cl_qlist_next( pItem ) ) + { + pAdapter = CONTAINING_RECORD( pItem, ipoib_adapter_t, entry ); + if( pIn->PortGuid != pAdapter->guids.port_guid.guid ) + continue; + + /* Found the port - lookup the MAC. */ + cl_obj_lock( &pAdapter->obj ); + if( pAdapter->p_port ) + { + status = ipoib_mac_to_path( + pAdapter->p_port, *(mac_addr_t*)pIn->DestMac, &pOut->Path ); + + if( NT_SUCCESS( status ) ) + { + pIrp->IoStatus.Information = + sizeof(IOCTL_IBAT_MAC_TO_PATH_OUT); + } + } + cl_obj_unlock( &pAdapter->obj ); + break; + } + + KeReleaseInStackQueuedSpinLock( &hdl ); + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return status; +} + + +static NTSTATUS +__ibat_ip_to_port( + IN IRP *pIrp, + IN IO_STACK_LOCATION *pIoStack ) +{ + IOCTL_IBAT_IP_TO_PORT_IN *pIn; + IOCTL_IBAT_IP_TO_PORT_OUT *pOut; + KLOCK_QUEUE_HANDLE hdl; + cl_list_item_t *pItem; + ipoib_adapter_t *pAdapter; + size_t idx; + net_address_item_t *pAddr; + NTSTATUS status = STATUS_NOT_FOUND; + + IPOIB_ENTER(IPOIB_DBG_IOCTL); + + if( pIoStack->Parameters.DeviceIoControl.InputBufferLength != + sizeof(IOCTL_IBAT_IP_TO_PORT_IN) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid input buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + if( pIoStack->Parameters.DeviceIoControl.OutputBufferLength != + sizeof(IOCTL_IBAT_IP_TO_PORT_OUT) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid output buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + pIn = pIrp->AssociatedIrp.SystemBuffer; + pOut = pIrp->AssociatedIrp.SystemBuffer; + + if( pIn->Version != IBAT_IOCTL_VERSION ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid version.\n") ); + return STATUS_INVALID_PARAMETER; + } + + if (pIn->Address.IpVersion != 4) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid IP version (%d). Supported only 4\n", pIn->Address.IpVersion) ); + return STATUS_INVALID_PARAMETER; + } + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + for( pItem = cl_qlist_head( &g_ipoib.adapter_list ); + pItem != cl_qlist_end( &g_ipoib.adapter_list ); + pItem = cl_qlist_next( pItem ) ) + { + pAdapter = CONTAINING_RECORD( pItem, ipoib_adapter_t, entry ); + + cl_obj_lock( &pAdapter->obj ); + + for( idx = 0; + idx < cl_vector_get_size( &pAdapter->ip_vector ); + idx++ ) + { + pAddr = (net_address_item_t*) + cl_vector_get_ptr( &pAdapter->ip_vector, idx ); + + if (!memcmp( &pIn->Address.Address[12], pAddr->address.as_bytes, IPV4_ADDR_SIZE)) + { + pOut->Port.CaGuid = pAdapter->guids.ca_guid; + pOut->Port.PortGuid = pAdapter->guids.port_guid.guid; + pOut->Port.PKey = IB_DEFAULT_PKEY; + pOut->Port.PortNum = pAdapter->guids.port_num; + pIrp->IoStatus.Information = sizeof(IOCTL_IBAT_IP_TO_PORT_OUT); + status = STATUS_SUCCESS; + break; + } + } + cl_obj_unlock( &pAdapter->obj ); + if (status == STATUS_SUCCESS) + break; + } + + KeReleaseInStackQueuedSpinLock( &hdl ); + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return status; +} + +void +ipoib_ref_ibat() +{ + NTSTATUS status; + NDIS_STRING DeviceName; + NDIS_STRING DeviceLinkUnicodeString; + + IPOIB_ENTER( IPOIB_DBG_IOCTL ); + + if( InterlockedIncrement( &g_ipoib.ibat_ref ) == 1 ) + { + NdisInitUnicodeString( &DeviceName, IBAT_DEV_NAME ); + NdisInitUnicodeString( &DeviceLinkUnicodeString, IBAT_DOS_DEV_NAME ); + + g_p_drv_obj->MajorFunction[IRP_MJ_CREATE] = __ipoib_create; + g_p_drv_obj->MajorFunction[IRP_MJ_CLEANUP] = __ipoib_cleanup; + g_p_drv_obj->MajorFunction[IRP_MJ_CLOSE] = __ipoib_close; + g_p_drv_obj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = __ipoib_dispatch; + g_p_drv_obj->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = __ipoib_dispatch; + status = IoCreateDevice(g_p_drv_obj, 0,&DeviceName, + FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &g_ipoib.h_ibat_dev); + + if( status != STATUS_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisMRegisterDevice failed with status of %d\n", status) ); + } + } + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); +} + + +void +ipoib_deref_ibat() +{ + IPOIB_ENTER( IPOIB_DBG_IOCTL ); + + if( InterlockedDecrement( &g_ipoib.ibat_ref ) ) + { + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return; + } + + if( g_ipoib.h_ibat_dev ) + { + IoDeleteDevice( g_ipoib.h_ibat_dev ); + g_ipoib.h_ibat_dev = NULL; + } + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); +} + + +static NTSTATUS +__ipoib_create( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ) +{ + IPOIB_ENTER( IPOIB_DBG_IOCTL ); + + UNREFERENCED_PARAMETER( pDevObj ); + + ipoib_ref_ibat(); + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = 0; + IoCompleteRequest( pIrp, IO_NO_INCREMENT ); + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return STATUS_SUCCESS; +} + + +static NTSTATUS +__ipoib_cleanup( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ) +{ + IPOIB_ENTER( IPOIB_DBG_IOCTL ); + + UNREFERENCED_PARAMETER( pDevObj ); + + ipoib_deref_ibat(); + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = 0; + IoCompleteRequest( pIrp, IO_NO_INCREMENT ); + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return STATUS_SUCCESS; +} + + +static NTSTATUS +__ipoib_close( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ) +{ + IPOIB_ENTER( IPOIB_DBG_IOCTL ); + + UNREFERENCED_PARAMETER( pDevObj ); + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = 0; + IoCompleteRequest( pIrp, IO_NO_INCREMENT ); + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return STATUS_SUCCESS; +} + + +static NTSTATUS +__ipoib_dispatch( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ) +{ + IO_STACK_LOCATION *pIoStack; + NTSTATUS status = STATUS_SUCCESS; + + IPOIB_ENTER( IPOIB_DBG_IOCTL ); + + UNREFERENCED_PARAMETER( pDevObj ); + + pIoStack = IoGetCurrentIrpStackLocation( pIrp ); + + pIrp->IoStatus.Information = 0; + + switch( pIoStack->Parameters.DeviceIoControl.IoControlCode ) + { + case IOCTL_IBAT_PORTS: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_IOCTL, + ("IOCTL_IBAT_PORTS received\n") ); + status = __ibat_get_ports( pIrp, pIoStack ); + break; + + case IOCTL_IBAT_IP_ADDRESSES: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_IOCTL, + ("IOCTL_IBAT_IP_ADDRESSES received\n" )); + status = __ibat_get_ips( pIrp, pIoStack ); + break; + + case IOCTL_IBAT_MAC_TO_GID: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_IOCTL, + ("IOCTL_IBAT_MAC_TO_GID received\n" )); + status = __ibat_mac_to_gid( pIrp, pIoStack ); + break; + + case IOCTL_IBAT_IP_TO_PORT: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_IOCTL, + ("IOCTL_IBAT_IP_TO_PORT received\n" )); + status = __ibat_ip_to_port( pIrp, pIoStack ); + break; + + case IOCTL_IBAT_MAC_TO_PATH: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_IOCTL, + ("IOCTL_IBAT_MAC_TO_PATH received\n" )); + status = __ibat_mac_to_path( pIrp, pIoStack ); + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_IOCTL, + ("unknow IOCTL code = 0x%x\n", + pIoStack->Parameters.DeviceIoControl.IoControlCode) ); + status = STATUS_INVALID_PARAMETER; + } + + pIrp->IoStatus.Status = status; + IoCompleteRequest( pIrp, IO_NO_INCREMENT ); + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return status; +} + + diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib_ibat.h b/branches/winverbs/ulp/ipoib/kernel6/ipoib_ibat.h new file mode 100644 index 00000000..efcb0a6b --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib_ibat.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2005 Mellanox Technologies. All rights reserved. + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_ibat.h 1611 2006-08-20 14:48:55Z sleybo $ + */ + + +#ifndef _IPOIB_IBAT_H_ +#define _IPOIB_IBAT_H_ + + +void +ipoib_ref_ibat(); + +void +ipoib_deref_ibat(); + + +#endif /* _IPOIB_IBAT_H_ */ diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib_log.mc b/branches/winverbs/ulp/ipoib/kernel6/ipoib_log.mc new file mode 100644 index 00000000..bb7d1c1d --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib_log.mc @@ -0,0 +1,334 @@ +;/*++ +;============================================================================= +;Copyright (c) 2001 Mellanox Technologies +; +;Module Name: +; +; ipoiblog.mc +; +;Abstract: +; +; IPoIB Driver event log messages +; +;Authors: +; +; Yossi Leybovich +; +;Environment: +; +; Kernel Mode . +; +;============================================================================= +;--*/ +; +MessageIdTypedef = NDIS_ERROR_CODE + +SeverityNames = ( + Success = 0x0:STATUS_SEVERITY_SUCCESS + Informational = 0x1:STATUS_SEVERITY_INFORMATIONAL + Warning = 0x2:STATUS_SEVERITY_WARNING + Error = 0x3:STATUS_SEVERITY_ERROR + ) + +FacilityNames = ( + System = 0x0 + RpcRuntime = 0x2:FACILITY_RPC_RUNTIME + RpcStubs = 0x3:FACILITY_RPC_STUBS + Io = 0x4:FACILITY_IO_ERROR_CODE + IPoIB = 0x7:FACILITY_IPOIB_ERROR_CODE + ) + + +MessageId=0x0001 +Facility=IPoIB +Severity=Warning +SymbolicName=EVENT_IPOIB_PORT_DOWN +Language=English +%2: Network controller link is down. +. + +MessageId=0x0002 +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP +Language=English +%2: Network controller link is up. +. + + +MessageId=0x0003 +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP1 +Language=English +%2: Network controller link is up at 2.5Gbps. +. + +MessageId=0x0004 +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP2 +Language=English +%2: Network controller link is up at 5Gbps. +. + +MessageId=0x0006 +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP3 +Language=English +%2: Network controller link is up at 10Gbps. +. + +MessageId=0x000a +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP4 +Language=English +%2: Network controller link is up at 20Gps. +. + +MessageId=0x000e +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP5 +Language=English +%2: Network controller link is up at 30Gps. +. + +MessageId=0x0012 +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP6 +Language=English +%2: Network controller link is up at 40Gps. +. + +MessageId=0x001a +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP7 +Language=English +%2: Network controller link is up at 60Gps. +. + +MessageId=0x0032 +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP8 +Language=English +%2: Network controller link is up at 120Gps. +. + +MessageId=0x0040 +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_INIT_SUCCESS +Language=English +%2: Driver Initialized succesfully. +. + +MessageId=0x0041 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_OPEN_CA +Language=English +%2: Failed to open Channel Adapter. +. + +MessageId=0x0042 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_ALLOC_PD +Language=English +%2: Failed to allocate Protection Domain. +. + +MessageId=0x0043 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_CREATE_RECV_CQ +Language=English +%2: Failed to create receive Completion Queue. +. + +MessageId=0x0044 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_CREATE_SEND_CQ +Language=English +%2: Failed to create send Completion Queue. +. + +MessageId=0x0045 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_CREATE_QP +Language=English +%2: Failed to create Queue Pair. +. + +MessageId=0x0046 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_QUERY_QP +Language=English +%2: Failed to get Queue Pair number. +. + +MessageId=0x0047 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_REG_PHYS +Language=English +%2: Failed to create DMA Memory Region. +. + +MessageId=0x0048 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_RECV_POOL +Language=English +%2: Failed to create receive descriptor pool. +. + +MessageId=0x0049 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_RECV_PKT_POOL +Language=English +%2: Failed to create NDIS_PACKET pool for receive indications. +. + +MessageId=0x004A +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_RECV_BUF_POOL +Language=English +%2: Failed to create NDIS_BUFFER pool for receive indications. +. + +MessageId=0x004B +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_SEND_PKT_POOL +Language=English +%2: Failed to create NDIS_PACKET pool for send processing. +. + +MessageId=0x004C +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_SEND_BUF_POOL +Language=English +%2: Failed to create NDIS_BUFFER pool for send processing. +. + +MessageId=0x004D +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_RECV_PKT_ARRAY +Language=English +%2: Failed to allocate receive indication array. +. + +MessageId=0x004E +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_PORT_INFO_TIMEOUT +Language=English +%2: Subnet Administrator query for port information timed out. +Make sure the SA is functioning properly. Increasing the number +of retries and retry timeout adapter parameters may solve the +issue. +. + +MessageId=0x004F +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_PORT_INFO_REJECT +Language=English +%2: Subnet Administrator failed the query for port information. +Make sure the SA is functioning properly and compatible. +. + +MessageId=0x0050 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_QUERY_PORT_INFO +Language=English +%2: Subnet Administrator query for port information failed. +. + +MessageId=0x0055 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_BCAST_GET +Language=English +%2: Subnet Administrator failed query for broadcast group information. +. + +MessageId=0x0056 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_BCAST_JOIN +Language=English +%2: Subnet Administrator failed request to joing broadcast group. +. + +MessageId=0x0057 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_BCAST_RATE +Language=English +%2: The local port rate is too slow for the existing broadcast MC group. +. + +MessageId=0x0058 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_WRONG_PARAMETER_ERR +Language=English +%2: Incorrect value or non-existing registry for the required IPoIB parameter %3, overriding it by default value: %4 +. + +MessageId=0x0059 +Facility=IPoIB +Severity=Warning +SymbolicName=EVENT_IPOIB_WRONG_PARAMETER_WRN +Language=English +%2: Incorrect value or non-existing registry entry for the required IPoIB parameter %3, overriding it by default value: %4 +. + +MessageId=0x005A +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_WRONG_PARAMETER_INFO +Language=English +%2: Incorrect value or non-existing registry for the optional IPoIB parameter %3, overriding it by default value: %4 +. + +MessageId=0x005B +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_PARTITION_ERR +Language=English +%2: Pkey index not found for partition , change switch pkey configuration. +. + +MessageId=0x005C +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_CONNECTED_MODE_ERR +Language=English +%2: Connected Mode failed to initialize, disabled. Interface will use default UD QP transport. +. + +MessageId=0x005D +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_CONNECTED_MODE_UP +Language=English +%2: Connected Mode initialized and operational. +. + diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib_port.c b/branches/winverbs/ulp/ipoib/kernel6/ipoib_port.c new file mode 100644 index 00000000..da57d2cc --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib_port.c @@ -0,0 +1,8098 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_port.c 4226 2009-04-06 06:01:03Z xalex $ + */ + + + +#include "ipoib_endpoint.h" +#include "ipoib_port.h" +#include "ipoib_adapter.h" +#include "ipoib_debug.h" +#if defined(EVENT_TRACING) +#ifdef offsetof +#undef offsetof +#endif +#include "ipoib_port.tmh" +#endif +#include + +#include "wdm.h" +#include + + + +ib_gid_t bcast_mgid_template = { + 0xff, /* multicast field */ + 0x12, /* scope (to be filled in) */ + 0x40, 0x1b, /* IPv4 signature */ + 0xff, 0xff, /* 16 bits of P_Key (to be filled in) */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 48 bits of zeros */ + 0xff, 0xff, 0xff, 0xff, /* 32 bit IPv4 broadcast address */ +}; + + +#ifdef _DEBUG_ +/* Handy pointer for debug use. */ +ipoib_port_t *gp_ipoib_port; +#endif + +static void __port_mcast_garbage_dpc(KDPC *p_gc_dpc,void *context,void *s_arg1, void *s_arg2); +static void __port_do_mcast_garbage(ipoib_port_t* const p_port ); + + +static void __recv_cb_dpc(KDPC *p_gc_dpc,void *context,void *s_arg1, void *s_arg2); + + +/****************************************************************************** +* +* Declarations +* +******************************************************************************/ +static void +__port_construct( + IN ipoib_port_t* const p_port ); + +static ib_api_status_t +__port_init( + IN ipoib_port_t* const p_port, + IN ipoib_adapter_t* const p_adapter, + IN ib_pnp_port_rec_t* const p_pnp_rec ); + +static void +__port_destroying( + IN cl_obj_t* const p_obj ); + +static void +__port_cleanup( + IN cl_obj_t* const p_obj ); + +static void +__port_free( + IN cl_obj_t* const p_obj ); + +static ib_api_status_t +__port_query_ca_attrs( + IN ipoib_port_t* const p_port, + IN ib_ca_attr_t** pp_ca_attrs ); + +static void +__srq_async_event_cb( +IN ib_async_event_rec_t *p_event_rec ); + +/****************************************************************************** +* +* IB resource manager operations +* +******************************************************************************/ +static void +__ib_mgr_construct( + IN ipoib_port_t* const p_port ); + +static ib_api_status_t +__ib_mgr_init( + IN ipoib_port_t* const p_port ); + +static void +__ib_mgr_destroy( + IN ipoib_port_t* const p_port ); + +static void +__qp_event( + IN ib_async_event_rec_t *p_event_rec ); + +static void +__cq_event( + IN ib_async_event_rec_t *p_event_rec ); + +static ib_api_status_t +__ib_mgr_activate( + IN ipoib_port_t* const p_port ); + +/****************************************************************************** +* +* Buffer manager operations. +* +******************************************************************************/ +static void +__buf_mgr_construct( + IN ipoib_port_t* const p_port ); + +static ib_api_status_t +__buf_mgr_init( + IN ipoib_port_t* const p_port ); + +static void +__buf_mgr_destroy( + IN ipoib_port_t* const p_port ); + +static cl_status_t +__recv_ctor( + IN void* const p_object, + IN void* context, + OUT cl_pool_item_t** const pp_pool_item ); + +#if !IPOIB_INLINE_RECV +static void +__recv_dtor( + IN const cl_pool_item_t* const p_pool_item, + IN void *context ); +#endif /* IPOIB_INLINE_RECV */ + +static inline ipoib_send_desc_t* +__buf_mgr_get_send( + IN ipoib_port_t* const p_port ); + +static inline void +__buf_mgr_put_send( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc ); + +static inline ipoib_recv_desc_t* +__buf_mgr_get_recv( + IN ipoib_port_t* const p_port ); + +static inline void +__buf_mgr_put_recv( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc, + IN NET_BUFFER_LIST* const p_net_buffer_list OPTIONAL ); + +static inline void +__buf_mgr_put_recv_list( + IN ipoib_port_t* const p_port, + IN cl_qlist_t* const p_list ); + +//NDIS60 +static inline NET_BUFFER_LIST* +__buf_mgr_get_ndis_pkt( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc ); + + +/****************************************************************************** +* +* Receive manager operations. +* +******************************************************************************/ +static void +__recv_mgr_construct( + IN ipoib_port_t* const p_port ); + +static ib_api_status_t +__recv_mgr_init( + IN ipoib_port_t* const p_port ); + +static void +__recv_mgr_destroy( + IN ipoib_port_t* const p_port ); + +/* Posts receive buffers to the receive queue. */ +static ib_api_status_t +__recv_mgr_repost( + IN ipoib_port_t* const p_port ); + +static void +__recv_cb( + IN const ib_cq_handle_t h_cq, + IN void *cq_context ); + +static void +__recv_get_endpts( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc, + IN ib_wc_t* const p_wc, + OUT ipoib_endpt_t** const pp_src, + OUT ipoib_endpt_t** const pp_dst ); + +static int32_t +__recv_mgr_filter( + IN ipoib_port_t* const p_port, + IN ib_wc_t* const p_done_wc_list, + OUT cl_qlist_t* const p_done_list, + OUT cl_qlist_t* const p_bad_list ); + +static ib_api_status_t +__recv_gen( + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src, + IN ipoib_endpt_t* const p_dst ); + +static ib_api_status_t +__recv_dhcp( + IN ipoib_port_t* const p_port, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src, + IN ipoib_endpt_t* const p_dst ); + +static ib_api_status_t +__recv_arp( + IN ipoib_port_t* const p_port, + IN ib_wc_t* const p_wc, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t** const p_src, + IN ipoib_endpt_t* const p_dst ); + +static ib_api_status_t +__recv_mgr_prepare_pkt( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc, + OUT NET_BUFFER_LIST** const pp_net_buffer_list ); + +static uint32_t +__recv_mgr_build_pkt_array( + IN ipoib_port_t* const p_port, + IN int32_t shortage, + OUT cl_qlist_t* const p_done_list, + OUT int32_t* const p_discarded ); + +/****************************************************************************** +* +* Send manager operations. +* +******************************************************************************/ +static void +__send_mgr_construct( + IN ipoib_port_t* const p_port ); + +static void +__send_mgr_destroy( + IN ipoib_port_t* const p_port ); + +static NDIS_STATUS +__send_gen( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc, + IN SCATTER_GATHER_LIST *p_sgl, + IN INT lso_data_index); + +static NDIS_STATUS +__send_mgr_filter_ip( + IN ipoib_port_t* const p_port, + IN const eth_hdr_t* const p_eth_hdr, + IN MDL* p_mdl, + IN size_t buf_len, + IN SCATTER_GATHER_LIST *p_sgl, + IN OUT ipoib_send_desc_t* const p_desc ); + +static NDIS_STATUS +__send_mgr_filter_igmp_v2( + IN ipoib_port_t* const p_port, + IN const ip_hdr_t* const p_ip_hdr, + IN size_t iph_options_size, + IN MDL* p_mdl, + IN size_t buf_len ); + +static NDIS_STATUS +__send_mgr_filter_udp( + IN ipoib_port_t* const p_port, + IN const ip_hdr_t* const p_ip_hdr, + IN MDL* p_mdl, + IN size_t buf_len, + IN SCATTER_GATHER_LIST *p_sgl, + IN OUT ipoib_send_desc_t* const p_desc ); + +static NDIS_STATUS +__send_mgr_filter_dhcp( + IN ipoib_port_t* const p_port, + IN const udp_hdr_t* const p_udp_hdr, + IN MDL* p_mdl, + IN size_t buf_len, + IN OUT ipoib_send_desc_t* const p_desc ); + +static NDIS_STATUS +__send_mgr_filter_arp( + IN ipoib_port_t* const p_port, + IN const eth_hdr_t* const p_eth_hdr, + IN MDL* p_mdl, + IN size_t buf_len, + IN OUT ipoib_send_desc_t* const p_desc ); + +static void +__process_failed_send( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc, + IN const NDIS_STATUS status, + IN ULONG send_complete_flags ); + +static inline NDIS_STATUS +__send_mgr_queue( + IN ipoib_port_t* const p_port, + IN eth_hdr_t* const p_eth_hdr, + OUT ipoib_endpt_t** const pp_endpt ); + +static NDIS_STATUS +__build_send_desc( + IN ipoib_port_t* const p_port, + IN eth_hdr_t* const p_eth_hdr, + IN MDL* const p_mdl, + IN const size_t mdl_len, + IN SCATTER_GATHER_LIST *p_sgl, + IN OUT ipoib_send_desc_t* const p_desc ); + + +static void +__send_cb( + IN const ib_cq_handle_t h_cq, + IN void *cq_context ); + +static NDIS_STATUS +GetLsoHeaderSize( + IN PNET_BUFFER pNetBuffer, + IN LsoData *pLsoData, + OUT UINT *IndexOfData, + IN ipoib_hdr_t *ipoib_hdr ); + + +static NDIS_STATUS +__build_lso_desc( + IN ipoib_port_t* const p_port, + IN OUT ipoib_send_desc_t* const p_desc, + IN ULONG mss, + IN SCATTER_GATHER_LIST *p_sgl, + IN int32_t hdr_idx, + IN PNDIS_TCP_LARGE_SEND_OFFLOAD_NET_BUFFER_LIST_INFO p_lso_info ); + +static NDIS_STATUS +__send_fragments( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc, + IN eth_hdr_t* const p_eth_hdr, + IN ip_hdr_t* const p_ip_hdr, + IN uint32_t buf_len, + IN NDIS_BUFFER* p_ndis_buf ); + +static void +__update_fragment_ip_hdr( +IN ip_hdr_t* const p_ip_hdr, +IN uint16_t fragment_size, +IN uint16_t fragment_offset, +IN BOOLEAN more_fragments ); + +static void +__copy_ip_options( +IN uint8_t* p_buf, +IN uint8_t* p_options, +IN uint32_t options_len, +IN BOOLEAN copy_all ); +/****************************************************************************** +* +* Endpoint manager operations +* +******************************************************************************/ +static void +__endpt_mgr_construct( + IN ipoib_port_t* const p_port ); + +static ib_api_status_t +__endpt_mgr_init( + IN ipoib_port_t* const p_port ); + +static void +__endpt_mgr_destroy( + IN ipoib_port_t* const p_port ); + +/****f* IPoIB/__endpt_mgr_remove_all +* NAME +* __endpt_mgr_remove_all +* +* DESCRIPTION +* Removes all enpoints from the port, dereferencing them to initiate +* destruction. +* +* SYNOPSIS +*/ +static void +__endpt_mgr_remove_all( + IN ipoib_port_t* const p_port ); +/* +********/ + +static void +__endpt_mgr_remove( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt ); + +static void +__endpt_mgr_reset_all( + IN ipoib_port_t* const p_port ); + +static inline NDIS_STATUS +__endpt_mgr_ref( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ipoib_endpt_t** const pp_endpt ); + +static inline NDIS_STATUS +__endpt_mgr_get_gid_qpn( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ib_gid_t* const p_gid, + OUT UNALIGNED net32_t* const p_qpn ); + +static inline ipoib_endpt_t* +__endpt_mgr_get_by_gid( + IN ipoib_port_t* const p_port, + IN const ib_gid_t* const p_gid ); + +static inline ipoib_endpt_t* +__endpt_mgr_get_by_lid( + IN ipoib_port_t* const p_port, + IN const net16_t lid ); + +static inline ib_api_status_t +__endpt_mgr_insert_locked( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + IN ipoib_endpt_t* const p_endpt ); + +static inline ib_api_status_t +__endpt_mgr_insert( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + IN ipoib_endpt_t* const p_endpt ); + +static ib_api_status_t +__endpt_mgr_add_local( + IN ipoib_port_t* const p_port, + IN ib_port_info_t* const p_port_info ); + +static ib_api_status_t +__endpt_mgr_add_bcast( + IN ipoib_port_t* const p_port, + IN ib_mcast_rec_t *p_mcast_rec ); + +/****************************************************************************** +* +* MCast operations. +* +******************************************************************************/ +static ib_api_status_t +__port_get_bcast( + IN ipoib_port_t* const p_port ); + +static ib_api_status_t +__port_join_bcast( + IN ipoib_port_t* const p_port, + IN ib_member_rec_t* const p_member_rec ); + +static ib_api_status_t +__port_create_bcast( + IN ipoib_port_t* const p_port ); + + + +static void +__bcast_get_cb( + IN ib_query_rec_t *p_query_rec ); + + +static void +__bcast_cb( + IN ib_mcast_rec_t *p_mcast_rec ); + + +static void +__mcast_cb( + IN ib_mcast_rec_t *p_mcast_rec ); + +void +__leave_error_mcast_cb( + IN void *context ); + + +static intn_t +__gid_cmp( + IN const void* const p_key1, + IN const void* const p_key2 ) +{ + return cl_memcmp( p_key1, p_key2, sizeof(ib_gid_t) ); +} + + +inline void ipoib_port_ref( ipoib_port_t * p_port, int type ) +{ + cl_obj_ref( &p_port->obj ); +#if DBG + cl_atomic_inc( &p_port->ref[type % ref_mask] ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OBJ, + ("ref type %d ref_cnt %d\n", type, p_port->obj.ref_cnt) ); +#else + UNREFERENCED_PARAMETER(type); +#endif +} + + +inline void ipoib_port_deref(ipoib_port_t * p_port, int type) +{ +#if DBG + cl_atomic_dec( &p_port->ref[type % ref_mask] ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OBJ, + ("deref type %d ref_cnt %d\n", type, p_port->obj.ref_cnt) ); +#else + UNREFERENCED_PARAMETER(type); +#endif + cl_obj_deref( &p_port->obj ); + +} + +/* function returns pointer to payload that is going after IP header. +* asssuming that payload and IP header are in the same buffer +*/ +static void* GetIpPayloadPtr(const ip_hdr_t* const p_ip_hdr) +{ + return (void*)((uint8_t*)p_ip_hdr + IP_HEADER_LENGTH(p_ip_hdr)); +} + +/****************************************************************************** +* +* Implementation +* +******************************************************************************/ +ib_api_status_t +ipoib_create_port( + IN ipoib_adapter_t* const p_adapter, + IN ib_pnp_port_rec_t* const p_pnp_rec, + OUT ipoib_port_t** const pp_port ) +{ + ib_api_status_t status; + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( !p_adapter->p_port ); + + p_port = cl_zalloc( sizeof(ipoib_port_t) + + (sizeof(ipoib_hdr_t) * (p_adapter->params.sq_depth - 1)) ); + if( !p_port ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate ipoib_port_t (%d bytes)\n", + sizeof(ipoib_port_t)) ); + return IB_INSUFFICIENT_MEMORY; + } + +#ifdef _DEBUG_ + gp_ipoib_port = p_port; +#endif + + __port_construct( p_port ); + + status = __port_init( p_port, p_adapter, p_pnp_rec ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_port_init returned %s.\n", + p_adapter->p_ifc->get_err_str( status )) ); + __port_cleanup( &p_port->obj ); + __port_free( &p_port->obj ); + return status; + } + + *pp_port = p_port; + IPOIB_EXIT( IPOIB_DBG_INIT ); + return IB_SUCCESS; +} + + +void +ipoib_port_destroy( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( p_port ); + CL_ASSERT( p_port->p_adapter ); + CL_ASSERT( !p_port->p_adapter->p_port ); + + cl_obj_destroy( &p_port->obj ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +__port_construct( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_port->state = IB_QPS_RESET; + + cl_obj_construct( &p_port->obj, IPOIB_OBJ_PORT ); + cl_spinlock_construct( &p_port->send_lock ); + cl_spinlock_construct( &p_port->recv_lock ); + __ib_mgr_construct( p_port ); + __buf_mgr_construct( p_port ); + + __recv_mgr_construct( p_port ); + __send_mgr_construct( p_port ); + + __endpt_mgr_construct( p_port ); + + KeInitializeEvent( &p_port->sa_event, NotificationEvent, TRUE ); + KeInitializeEvent( &p_port->leave_mcast_event, NotificationEvent, TRUE ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static ib_api_status_t +__port_init( + IN ipoib_port_t* const p_port, + IN ipoib_adapter_t* const p_adapter, + IN ib_pnp_port_rec_t* const p_pnp_rec ) +{ + cl_status_t cl_status; + ib_api_status_t status; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_port->port_num = p_pnp_rec->p_port_attr->port_num; + p_port->p_adapter = p_adapter; + + cl_status = cl_spinlock_init( &p_port->send_lock ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_spinlock_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + cl_status = cl_spinlock_init( &p_port->recv_lock ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_spinlock_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + /* Initialize the IB resource manager. */ + status = __ib_mgr_init( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__ib_mgr_init returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Initialize the buffer manager. */ + status = __buf_mgr_init( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__buf_mgr_init returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Initialize the receive manager. */ + status = __recv_mgr_init( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__recv_mgr_init returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Initialize the endpoint manager. */ + status = __endpt_mgr_init( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__endpt_mgr_init returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + KeInitializeDpc(&p_port->recv_dpc,(PKDEFERRED_ROUTINE)__recv_cb_dpc,p_port); + + + /* Initialize multicast garbage collector timer and DPC object */ + KeInitializeDpc(&p_port->gc_dpc,(PKDEFERRED_ROUTINE)__port_mcast_garbage_dpc,p_port); + KeInitializeTimerEx(&p_port->gc_timer,SynchronizationTimer); + + /* We only ever destroy from the PnP callback thread. */ + cl_status = cl_obj_init( &p_port->obj, CL_DESTROY_SYNC, + __port_destroying, __port_cleanup, __port_free ); + +#if DBG + cl_atomic_inc( &p_port->ref[ref_init] ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OBJ, + ("ref type %d ref_cnt %d\n", ref_init, p_port->obj.ref_cnt) ); +#endif + + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_obj_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + cl_status = cl_obj_insert_rel( &p_port->rel, &p_adapter->obj, &p_port->obj ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_obj_insert_rel returned %#x\n", cl_status) ); + cl_obj_destroy( &p_port->obj ); + return IB_ERROR; + } + +#if DBG + cl_atomic_inc( &p_port->ref[ref_init] ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_OBJ, + ("ref type %d ref_cnt %d\n", ref_init, p_port->obj.ref_cnt) ); +#endif + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return IB_SUCCESS; +} + + +static void +__port_destroying( + IN cl_obj_t* const p_obj ) +{ + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( p_obj ); + + p_port = PARENT_STRUCT( p_obj, ipoib_port_t, obj ); + + ipoib_port_down( p_port ); + + __endpt_mgr_remove_all( p_port ); + +#if 0 + if( p_port->p_adapter->params.cm_enabled ) + { + endpt_cm_buf_mgr_destroy( p_port ); + ipoib_port_srq_destroy( p_port ); + p_port->endpt_mgr.thread_is_done = 1; + cl_event_signal( &p_port->endpt_mgr.event ); + } +#endif + ASSERT(FALSE); + //TODO NDIS6.0 + ipoib_port_resume( p_port, FALSE ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +__port_cleanup( + IN cl_obj_t* const p_obj ) +{ + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( p_obj ); + + p_port = PARENT_STRUCT( p_obj, ipoib_port_t, obj ); + + /* Wait for all sends and receives to get flushed. */ + while( p_port->send_mgr.depth || p_port->recv_mgr.depth ) + cl_thread_suspend( 0 ); + + /* Destroy the send and receive managers before closing the CA. */ + __ib_mgr_destroy( p_port ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +__port_free( + IN cl_obj_t* const p_obj ) +{ + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( p_obj ); + + p_port = PARENT_STRUCT( p_obj, ipoib_port_t, obj ); + + KeCancelTimer(&p_port->gc_timer); + KeFlushQueuedDpcs(); + __endpt_mgr_destroy( p_port ); + __recv_mgr_destroy( p_port ); + __send_mgr_destroy( p_port ); + __buf_mgr_destroy( p_port ); + + cl_spinlock_destroy( &p_port->send_lock ); + cl_spinlock_destroy( &p_port->recv_lock ); + + cl_obj_deinit( p_obj ); + if( p_port->p_ca_attrs ) + { + cl_free ( p_port->p_ca_attrs ); + } + cl_free( p_port ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + + +/****************************************************************************** +* +* IB resource manager implementation. +* +******************************************************************************/ +static void +__ib_mgr_construct( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + cl_memclr( &p_port->ib_mgr, sizeof(ipoib_ib_mgr_t) ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static ib_api_status_t +__ib_mgr_init( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t status; + ib_cq_create_t cq_create; + ib_qp_create_t qp_create; + ib_phys_create_t phys_create; + ib_phys_range_t phys_range; + uint64_t vaddr; + net32_t rkey; + ib_qp_attr_t qp_attr; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + /* Open the CA. */ + status = p_port->p_adapter->p_ifc->open_ca( + p_port->p_adapter->h_al, p_port->p_adapter->guids.ca_guid, + NULL, p_port, &p_port->ib_mgr.h_ca ); + if( status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_OPEN_CA, 1, status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_open_ca returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + status = __port_query_ca_attrs( p_port, &p_port->p_ca_attrs ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Query CA attributes failed\n" ) ); + return status; + } +#if 0 + if( p_port->p_adapter->params.cm_enabled ) + { + uint32_t payload_mtu = __port_attr_to_mtu_size( + p_port->p_ca_attrs->p_port_attr[p_port->port_num - 1].mtu ) + - sizeof(ipoib_hdr_t); + /* adjust ipoib UD payload MTU to actual port MTU size. */ + p_port->p_adapter->params.payload_mtu = + max( DEFAULT_PAYLOAD_MTU, payload_mtu ); + p_port->p_adapter->params.xfer_block_size = + (sizeof(eth_hdr_t) + p_port->p_adapter->params.payload_mtu); + } +#endif +#if IPOIB_USE_DMA + /* init DMA only once while running MiniportInitialize */ + if ( !p_port->p_adapter->reset ) + { + ULONG max_phys_mapping; + if( p_port->p_adapter->params.cm_enabled ) + { + max_phys_mapping = p_port->p_adapter->params.cm_xfer_block_size; + } + else if( p_port->p_adapter->params.lso ) + { + max_phys_mapping = LARGE_SEND_OFFLOAD_SIZE; + } + else + { + max_phys_mapping = p_port->p_adapter->params.xfer_block_size; + } + /*if( NdisMInitializeScatterGatherDma( p_port->p_adapter->h_adapter, + TRUE, max_phys_mapping )!= NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisMInitializeScatterGatherDma failed\n" ) ); + return IB_INSUFFICIENT_RESOURCES; + }*/ + } +#endif + + /* Allocate the PD. */ + status = p_port->p_adapter->p_ifc->alloc_pd( + p_port->ib_mgr.h_ca, IB_PDT_UD, p_port, &p_port->ib_mgr.h_pd ); + if( status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_ALLOC_PD, 1, status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_alloc_pd returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Allocate receive CQ. */ + cq_create.size = p_port->p_adapter->params.rq_depth; + cq_create.pfn_comp_cb = __recv_cb; + cq_create.h_wait_obj = NULL; + + status = p_port->p_adapter->p_ifc->create_cq( + p_port->ib_mgr.h_ca, &cq_create, p_port, + __cq_event, &p_port->ib_mgr.h_recv_cq ); + if( status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_CREATE_RECV_CQ, 1, status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_create_cq returned %s.\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Allocate send CQ. */ + cq_create.size = p_port->p_adapter->params.sq_depth; + cq_create.pfn_comp_cb = __send_cb; + + status = p_port->p_adapter->p_ifc->create_cq( + p_port->ib_mgr.h_ca, &cq_create, p_port, + __cq_event, &p_port->ib_mgr.h_send_cq ); + if( status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_CREATE_SEND_CQ, 1, status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_create_cq returned %s.\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Allocate the QP. */ + cl_memclr( &qp_create, sizeof(qp_create) ); + qp_create.qp_type = IB_QPT_UNRELIABLE_DGRM; + qp_create.rq_depth = p_port->p_adapter->params.rq_depth; + qp_create.rq_sge = 2; /* To support buffers spanning pages. */ + qp_create.h_rq_cq = p_port->ib_mgr.h_recv_cq; + qp_create.sq_depth = p_port->p_adapter->params.sq_depth; + +#define UD_QP_USED_SGE 3 + qp_create.sq_sge = MAX_SEND_SGE < p_port->p_ca_attrs->max_sges ? + MAX_SEND_SGE : ( p_port->p_ca_attrs->max_sges - UD_QP_USED_SGE ); + if ( !p_port->p_ca_attrs->ipoib_csum ) + { + /* checksum is not supported by device + user must specify BYPASS to explicitly cancel checksum calculation */ + if (p_port->p_adapter->params.send_chksum_offload == CSUM_ENABLED) + p_port->p_adapter->params.send_chksum_offload = CSUM_DISABLED; + if (p_port->p_adapter->params.recv_chksum_offload == CSUM_ENABLED) + p_port->p_adapter->params.recv_chksum_offload = CSUM_DISABLED; + } + + qp_create.h_sq_cq = p_port->ib_mgr.h_send_cq; + qp_create.sq_signaled = FALSE; + status = p_port->p_adapter->p_ifc->create_qp( + p_port->ib_mgr.h_pd, &qp_create, p_port, + __qp_event, &p_port->ib_mgr.h_qp ); + if( status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_CREATE_QP, 1, status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_create_qp returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + /* Query the QP so we can get our QPN. */ + status = p_port->p_adapter->p_ifc->query_qp( + p_port->ib_mgr.h_qp, &qp_attr ); + if( status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_QUERY_QP, 1, status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_query_qp returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + p_port->ib_mgr.qpn = qp_attr.num; + + /* Register all of physical memory */ + phys_create.length = MEM_REG_SIZE; + phys_create.num_ranges = 1; + phys_create.range_array = &phys_range; + phys_create.buf_offset = 0; + phys_create.hca_page_size = PAGE_SIZE; + phys_create.access_ctrl = IB_AC_LOCAL_WRITE; + phys_range.base_addr = 0; + phys_range.size = MEM_REG_SIZE; + vaddr = 0; + status = p_port->p_adapter->p_ifc->reg_phys( + p_port->ib_mgr.h_pd, &phys_create, &vaddr, + &p_port->ib_mgr.lkey, &rkey, &p_port->ib_mgr.h_mr ); + if( status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_REG_PHYS, 1, status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_reg_phys returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + status = ipoib_port_srq_init( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_port_srq_init failed %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + /* disable further CM initialization */ + p_port->p_adapter->params.cm_enabled = FALSE; + + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_CONNECTED_MODE_ERR, 1, 0xbadc0de1 ); + + } +//CM +#if 0 + if( p_port->p_adapter->params.cm_enabled ) + { + status = endpt_cm_buf_mgr_init( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("CM Init buf mgr failed status %#x\n", status ) ); + ipoib_port_srq_destroy( p_port ); + p_port->p_adapter->params.cm_enabled = FALSE; + + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_CONNECTED_MODE_ERR, 1, 0xbadc0de2 ); + } + else + { + if ( p_port->p_adapter->params.send_chksum_offload ) + p_port->p_adapter->params.send_chksum_offload = CSUM_DISABLED; + } + } +#endif + IPOIB_EXIT( IPOIB_DBG_INIT ); + return IB_SUCCESS; +} + +static void +__srq_async_event_cb( +IN ib_async_event_rec_t *p_event_rec ) +{ + ipoib_port_t* p_port = + (ipoib_port_t *)p_event_rec->context; + + switch( p_event_rec->code ) + { + case IB_AE_SRQ_LIMIT_REACHED: + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("SRQ ASYNC EVENT CODE %d: %s\n", + p_event_rec->code, "IB_AE_SRQ_LIMIT_REACHED" ) ); + break; + case IB_AE_SRQ_CATAS_ERROR: + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("SRQ ASYNC EVENT CODE %d: %s\n", + p_event_rec->code, "IB_AE_SRQ_CATAS_ERROR" ) ); + /*SRQ is in err state, must reinitialize */ + p_port->p_adapter->hung = TRUE; + break; + case IB_AE_SRQ_QP_LAST_WQE_REACHED: + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("SRQ ASYNC EVENT CODE %d: %s\n", + p_event_rec->code, "IB_AE_SRQ_QP_LAST_WQE_REACHED" ) ); + /*SRQ is in err state, must reinitialize */ + p_port->p_adapter->hung = TRUE; + break; + default: + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ASYNC EVENT CODE ARRIVED %d(%#x)\n", + p_event_rec->code, p_event_rec->code ) ); + } +} + +ib_api_status_t +ipoib_port_srq_init( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t ib_status; + ib_srq_handle_t h_srq; + ib_srq_attr_t srq_attr; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + if( !p_port->p_adapter->params.cm_enabled ) + return IB_SUCCESS; + + srq_attr.max_sge = min( 2, p_port->p_ca_attrs->max_srq_sges ); + srq_attr.srq_limit = 10; + srq_attr.max_wr = + min( (uint32_t)p_port->p_adapter->params.rq_depth * 8, + p_port->p_ca_attrs->max_srq_wrs/2 ); + + ib_status = p_port->p_adapter->p_ifc->create_srq( + p_port->ib_mgr.h_pd, + &srq_attr, + p_port, + __srq_async_event_cb, + &h_srq ); + if( ib_status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_CREATE_QP, 1, ib_status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_create_srq failed status %s\n", + p_port->p_adapter->p_ifc->get_err_str( ib_status )) ); + return ib_status; + } + p_port->ib_mgr.h_srq = h_srq; + + IPOIB_EXIT( IPOIB_DBG_INIT ); + + return ib_status; +} + +/* __port_query_ca_attrs() + * returns a pointer to allocated memory. + * must be released by caller. + */ +static ib_api_status_t +__port_query_ca_attrs( + IN ipoib_port_t* const p_port, + IN ib_ca_attr_t** pp_ca_attrs ) +{ + ib_api_status_t ib_status; + uint32_t attr_size; + ib_ca_attr_t* p_ca_attrs; + + *pp_ca_attrs = NULL; + + ib_status = + p_port->p_adapter->p_ifc->query_ca( p_port->ib_mgr.h_ca, NULL , &attr_size ); + if( ib_status != IB_INSUFFICIENT_MEMORY ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_query_ca failed status %s\n", + p_port->p_adapter->p_ifc->get_err_str( ib_status )) ); + goto done; + } + CL_ASSERT( attr_size ); + + p_ca_attrs = cl_zalloc( attr_size ); + if ( p_ca_attrs == NULL ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Allocate %d bytes failed for CA Attributes\n", attr_size )); + ib_status = IB_INSUFFICIENT_MEMORY; + goto done; + } + + ib_status = + p_port->p_adapter->p_ifc->query_ca( p_port->ib_mgr.h_ca, p_ca_attrs , &attr_size ); + if ( ib_status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("CA attributes query failed\n") ); + cl_free ( p_ca_attrs ); + goto done; + } + + *pp_ca_attrs = p_ca_attrs; +done: + return ib_status; +} + +void +ipoib_port_srq_destroy( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t status; + + if( p_port->ib_mgr.h_srq ) + { + status = + p_port->p_adapter->p_ifc->destroy_srq( p_port->ib_mgr.h_srq, NULL ); + CL_ASSERT( status == IB_SUCCESS ); + p_port->ib_mgr.h_srq = NULL; + } +} + +static void +__ib_mgr_destroy( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t status; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + if( p_port->ib_mgr.h_ca ) + { + status = + p_port->p_adapter->p_ifc->close_ca( p_port->ib_mgr.h_ca, NULL ); + CL_ASSERT( status == IB_SUCCESS ); + p_port->ib_mgr.h_ca = NULL; + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + + +/****************************************************************************** +* +* Buffer manager implementation. +* +******************************************************************************/ +static void +__buf_mgr_construct( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + cl_qpool_construct( &p_port->buf_mgr.recv_pool ); + + p_port->buf_mgr.h_packet_pool = NULL; + p_port->buf_mgr.h_buffer_pool = NULL; + + NdisInitializeNPagedLookasideList( &p_port->buf_mgr.send_buf_list, + NULL, NULL, 0, MAX_XFER_BLOCK_SIZE, 'bipi', 0 ); + + p_port->buf_mgr.h_send_pkt_pool = NULL; + p_port->buf_mgr.h_send_buf_pool = NULL; + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static ib_api_status_t +__buf_mgr_init( + IN ipoib_port_t* const p_port ) +{ + cl_status_t cl_status; + ipoib_params_t *p_params; + NET_BUFFER_LIST_POOL_PARAMETERS pool_parameters; + IPOIB_ENTER(IPOIB_DBG_INIT ); + + CL_ASSERT( p_port ); + CL_ASSERT( p_port->p_adapter ); + + p_params = &p_port->p_adapter->params; + + /* Allocate the receive descriptor pool */ + cl_status = cl_qpool_init( &p_port->buf_mgr.recv_pool, + p_params->rq_depth * p_params->recv_pool_ratio, +#if IPOIB_INLINE_RECV + 0, 0, sizeof(ipoib_recv_desc_t), __recv_ctor, NULL, p_port ); +#else /* IPOIB_INLINE_RECV */ + 0, 0, sizeof(ipoib_recv_desc_t), __recv_ctor, __recv_dtor, p_port ); +#endif /* IPOIB_INLINE_RECV */ + if( cl_status != CL_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_RECV_POOL, 1, cl_status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_qpool_init for recvs returned %#x\n", + cl_status) ); + return IB_INSUFFICIENT_MEMORY; + } + + /* Allocate the NET BUFFER list pools for receive indication. */ + NdisZeroMemory(&pool_parameters, sizeof(NET_BUFFER_LIST_POOL_PARAMETERS)); + pool_parameters.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + pool_parameters.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1; + pool_parameters.Header.Size = sizeof(pool_parameters); + pool_parameters.ProtocolId = 0; + pool_parameters.ContextSize = 0; + pool_parameters.fAllocateNetBuffer = TRUE; + pool_parameters.PoolTag = 'CRPI'; + + p_port->buf_mgr.h_packet_pool = NdisAllocateNetBufferListPool( + p_port->p_adapter->h_adapter, + &pool_parameters); + + if( !p_port->buf_mgr.h_packet_pool ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_RECV_PKT_POOL, 1, NDIS_STATUS_RESOURCES ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisAllocatePacketPool returned %08X\n", (UINT)NDIS_STATUS_RESOURCES) ); + return IB_INSUFFICIENT_RESOURCES; + } +/* + NdisAllocateBufferPool( &ndis_status, &p_port->buf_mgr.h_buffer_pool, + p_params->rq_depth ); + if( ndis_status != NDIS_STATUS_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_RECV_BUF_POOL, 1, ndis_status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisAllocateBufferPool returned %08X\n", ndis_status) ); + return IB_INSUFFICIENT_RESOURCES; + } +*/ + /* Allocate the NET buffer list pool for send formatting. */ + pool_parameters.PoolTag = 'XTPI'; + + p_port->buf_mgr.h_send_pkt_pool = NdisAllocateNetBufferListPool( + p_port->p_adapter->h_adapter, + &pool_parameters); + if( !p_port->buf_mgr.h_send_pkt_pool) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_SEND_PKT_POOL, 1, NDIS_STATUS_RESOURCES ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisAllocatePacketPool returned %08X\n", (UINT)NDIS_STATUS_RESOURCES) ); + return IB_INSUFFICIENT_RESOURCES; + } +/* + NdisAllocateBufferPool( &ndis_status, + &p_port->buf_mgr.h_send_buf_pool, 1 ); + if( ndis_status != NDIS_STATUS_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_SEND_BUF_POOL, 1, ndis_status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisAllocateBufferPool returned %08X\n", ndis_status) ); + return IB_INSUFFICIENT_RESOURCES; + } +*/ + IPOIB_EXIT( IPOIB_DBG_INIT ); + return IB_SUCCESS; +} + + +static void +__buf_mgr_destroy( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER(IPOIB_DBG_INIT ); + + CL_ASSERT( p_port ); + + /* Destroy the send packet and buffer pools. + if( p_port->buf_mgr.h_send_buf_pool ) + NdisFreeBufferPool( p_port->buf_mgr.h_send_buf_pool );*/ + if( p_port->buf_mgr.h_send_pkt_pool ) + NdisFreeNetBufferListPool ( p_port->buf_mgr.h_send_pkt_pool ); + + /* Destroy the receive packet and buffer pools. + if( p_port->buf_mgr.h_buffer_pool ) + NdisFreeBufferPool( p_port->buf_mgr.h_buffer_pool );*/ + if( p_port->buf_mgr.h_packet_pool ) + NdisFreeNetBufferListPool ( p_port->buf_mgr.h_packet_pool ); + + /* Free the receive and send descriptors. */ + cl_qpool_destroy( &p_port->buf_mgr.recv_pool ); + + /* Free the lookaside list of scratch buffers. */ + NdisDeleteNPagedLookasideList( &p_port->buf_mgr.send_buf_list ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static cl_status_t +__recv_ctor( + IN void* const p_object, + IN void* context, + OUT cl_pool_item_t** const pp_pool_item ) +{ + ipoib_recv_desc_t *p_desc; + ipoib_port_t *p_port; + +#if IPOIB_INLINE_RECV + uint32_t ds0_len; +#endif + + IPOIB_ENTER( IPOIB_DBG_ALLOC ); + + CL_ASSERT( p_object ); + CL_ASSERT( context ); + + p_desc = (ipoib_recv_desc_t*)p_object; + p_port = (ipoib_port_t*)context; + + /* Setup the work request. */ + p_desc->wr.ds_array = p_desc->local_ds; + p_desc->wr.wr_id = (uintn_t)p_desc; + +#if IPOIB_INLINE_RECV + /* Sanity check on the receive buffer layout */ + CL_ASSERT( (void*)&p_desc->buf.eth.pkt.type == + (void*)&p_desc->buf.ib.pkt.type ); + CL_ASSERT( sizeof(recv_buf_t) == sizeof(ipoib_pkt_t) + sizeof(ib_grh_t) ); + + /* Setup the local data segment. */ + p_desc->local_ds[0].vaddr = cl_get_physaddr( &p_desc->buf ); + p_desc->local_ds[0].lkey = p_port->ib_mgr.lkey; + ds0_len = + PAGE_SIZE - ((uint32_t)p_desc->local_ds[0].vaddr & (PAGE_SIZE - 1)); + if( ds0_len >= sizeof(recv_buf_t) ) + { + /* The whole buffer is within a page. */ + p_desc->local_ds[0].length = ds0_len; + p_desc->wr.num_ds = 1; + } + else + { + /* The buffer crosses page boundaries. */ + p_desc->local_ds[0].length = ds0_len; + p_desc->local_ds[1].vaddr = cl_get_physaddr( + ((uint8_t*)&p_desc->buf) + ds0_len ); + p_desc->local_ds[1].lkey = p_port->ib_mgr.lkey; + p_desc->local_ds[1].length = sizeof(recv_buf_t) - ds0_len; + p_desc->wr.num_ds = 2; + } +#else /* IPOIB_INLINE_RECV */ + /* Allocate the receive buffer. */ + p_desc->p_buf = (recv_buf_t*)cl_zalloc( sizeof(recv_buf_t) ); + if( !p_desc->p_buf ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate receive buffer.\n") ); + return CL_INSUFFICIENT_MEMORY; + } + + /* Sanity check on the receive buffer layout */ + CL_ASSERT( (void*)&p_desc->p_buf->eth.pkt.type == + (void*)&p_desc->p_buf->ib.pkt.type ); + + /* Setup the local data segment. */ + p_desc->local_ds[0].vaddr = cl_get_physaddr( p_desc->p_buf ); + p_desc->local_ds[0].length = sizeof(ipoib_pkt_t) + sizeof(ib_grh_t); + p_desc->local_ds[0].lkey = p_port->ib_mgr.lkey; + p_desc->wr.num_ds = 1; +#endif /* IPOIB_INLINE_RECV */ + + *pp_pool_item = &p_desc->item; + + IPOIB_EXIT( IPOIB_DBG_ALLOC ); + return CL_SUCCESS; +} + + +#if !IPOIB_INLINE_RECV +static void +__recv_dtor( + IN const cl_pool_item_t* const p_pool_item, + IN void *context ) +{ + ipoib_recv_desc_t *p_desc; + + IPOIB_ENTER( IPOIB_DBG_ALLOC ); + + UNUSED_PARAM( context ); + + p_desc = PARENT_STRUCT( p_pool_item, ipoib_recv_desc_t, item ); + + if( p_desc->p_buf ) + cl_free( p_desc->p_buf ); + + IPOIB_EXIT( IPOIB_DBG_ALLOC ); +} +#endif + + +static inline ipoib_recv_desc_t* +__buf_mgr_get_recv( + IN ipoib_port_t* const p_port ) +{ + ipoib_recv_desc_t *p_desc; + IPOIB_ENTER( IPOIB_DBG_RECV ); + p_desc = (ipoib_recv_desc_t*)cl_qpool_get( &p_port->buf_mgr.recv_pool ); + /* Reference the port object for the send. */ + if( p_desc ) + { + ipoib_port_ref( p_port, ref_get_recv ); + CL_ASSERT( p_desc->wr.wr_id == (uintn_t)p_desc ); +#if IPOIB_INLINE_RECV + CL_ASSERT( p_desc->local_ds[0].vaddr == + cl_get_physaddr( &p_desc->buf ) ); +#else /* IPOIB_INLINE_RECV */ + CL_ASSERT( p_desc->local_ds[0].vaddr == + cl_get_physaddr( p_desc->p_buf ) ); + CL_ASSERT( p_desc->local_ds[0].length == + (sizeof(ipoib_pkt_t) + sizeof(ib_grh_t)) ); +#endif /* IPOIB_INLINE_RECV */ + CL_ASSERT( p_desc->local_ds[0].lkey == p_port->ib_mgr.lkey ); + } + IPOIB_EXIT( IPOIB_DBG_RECV ); + return p_desc; +} + + +//NDIS60 +static inline void +__buf_mgr_put_recv( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc, + IN NET_BUFFER_LIST* const p_net_buffer_list OPTIONAL ) +{ + NET_BUFFER *p_buf = NULL; + MDL *p_mdl = NULL; + IPOIB_ENTER(IPOIB_DBG_RECV ); + + if( p_net_buffer_list ) + { + NET_BUFFER_LIST_NEXT_NBL(p_net_buffer_list) = NULL; + p_buf = NET_BUFFER_LIST_FIRST_NB(p_net_buffer_list); + CL_ASSERT( p_buf ); + p_mdl = NET_BUFFER_FIRST_MDL(p_buf); + CL_ASSERT( p_mdl ); + NdisFreeMdl(p_mdl); + NdisFreeNetBufferList(p_net_buffer_list); + } + + /* Return the descriptor to its pools. */ + cl_qpool_put( &p_port->buf_mgr.recv_pool, &p_desc->item ); + + /* + * Dereference the port object since the receive is no longer outstanding. + */ + ipoib_port_deref( p_port, ref_get_recv ); + IPOIB_EXIT( IPOIB_DBG_RECV ); +} + + +static inline void +__buf_mgr_put_recv_list( + IN ipoib_port_t* const p_port, + IN cl_qlist_t* const p_list ) +{ + //IPOIB_ENTER( IPOIB_DBG_RECV ); + cl_qpool_put_list( &p_port->buf_mgr.recv_pool, p_list ); + //IPOIB_EXIT( IPOIB_DBG_RECV ); +} + + +static inline NET_BUFFER_LIST* +__buf_mgr_get_ndis_pkt( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc ) +{ + NET_BUFFER_LIST *p_net_buffer_list; + MDL *p_mdl; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + p_mdl = NdisAllocateMdl(p_port->p_adapter->h_adapter, + &p_desc->buf.eth.pkt, + p_desc->len ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate MDL\n") ); + return NULL; + } + + p_net_buffer_list = NdisAllocateNetBufferAndNetBufferList( + p_port->buf_mgr.h_packet_pool, + 0, + 0, + p_mdl, + 0, + 0); + + if( !p_net_buffer_list ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate NET_BUFFER_LIST\n") ); + NdisFreeMdl(p_mdl); + return NULL; + } + + NET_BUFFER_LIST_NEXT_NBL(p_net_buffer_list) = NULL; + IPOIB_PORT_FROM_PACKET( p_net_buffer_list ) = p_port; + IPOIB_RECV_FROM_PACKET( p_net_buffer_list ) = p_desc; + p_net_buffer_list->SourceHandle = p_port->p_adapter->h_adapter; + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return p_net_buffer_list; +} + + +/****************************************************************************** +* +* Receive manager implementation. +* +******************************************************************************/ +static void +__recv_mgr_construct( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + cl_qlist_init( &p_port->recv_mgr.done_list ); + + p_port->recv_mgr.recv_pkt_array = NULL; + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static ib_api_status_t +__recv_mgr_init( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + /* Allocate the NDIS_PACKET pointer array for indicating receives. */ + p_port->recv_mgr.recv_pkt_array = cl_malloc( + sizeof(NET_BUFFER_LIST*) * p_port->p_adapter->params.rq_depth ); + if( !p_port->recv_mgr.recv_pkt_array ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_RECV_PKT_ARRAY, 0 ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_malloc for PNDIS_PACKET array failed.\n") ); + return IB_INSUFFICIENT_MEMORY; + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return IB_SUCCESS; +} + + +static void +__recv_mgr_destroy( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( cl_is_qlist_empty( &p_port->recv_mgr.done_list ) ); + CL_ASSERT( !p_port->recv_mgr.depth ); + + if( p_port->recv_mgr.recv_pkt_array ) + cl_free( p_port->recv_mgr.recv_pkt_array ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +/* + * Posts receive buffers to the receive queue and returns the number + * of receives needed to bring the RQ to its low water mark. Note + * that the value is signed, and can go negative. All tests must + * be for > 0. + */ +static int32_t +__recv_mgr_repost( + IN ipoib_port_t* const p_port ) +{ + ipoib_recv_desc_t *p_head = NULL, *p_tail = NULL, *p_next; + ib_api_status_t status; + ib_recv_wr_t *p_failed; + PERF_DECLARE( GetRecv ); + PERF_DECLARE( PostRecv ); + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + CL_ASSERT( p_port ); + cl_obj_lock( &p_port->obj ); + if( p_port->state != IB_QPS_RTS ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_RECV, + ("Port in invalid state. Not reposting.\n") ); + return 0; + } + ipoib_port_ref( p_port, ref_repost ); + cl_obj_unlock( &p_port->obj ); + + while( p_port->recv_mgr.depth < p_port->p_adapter->params.rq_depth ) + { + /* Pull receives out of the pool and chain them up. */ + cl_perf_start( GetRecv ); + p_next = __buf_mgr_get_recv( p_port ); + cl_perf_stop( &p_port->p_adapter->perf, GetRecv ); + if( !p_next ) + { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_RECV, + ("Out of receive descriptors! recv queue depth 0x%x\n",p_port->recv_mgr.depth) ); + break; + } + + if( !p_tail ) + { + p_tail = p_next; + p_next->wr.p_next = NULL; + } + else + { + p_next->wr.p_next = &p_head->wr; + } + + p_head = p_next; + + p_port->recv_mgr.depth++; + } + + if( p_head ) + { + cl_perf_start( PostRecv ); + status = p_port->p_adapter->p_ifc->post_recv( + p_port->ib_mgr.h_qp, &p_head->wr, &p_failed ); + cl_perf_stop( &p_port->p_adapter->perf, PostRecv ); + + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ip_post_recv returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + /* return the descriptors to the pool */ + while( p_failed ) + { + p_head = PARENT_STRUCT( p_failed, ipoib_recv_desc_t, wr ); + p_failed = p_failed->p_next; + + __buf_mgr_put_recv( p_port, p_head, NULL ); + p_port->recv_mgr.depth--; + } + } + } + + ipoib_port_deref( p_port, ref_repost ); + IPOIB_EXIT( IPOIB_DBG_RECV ); + return p_port->p_adapter->params.rq_low_watermark - p_port->recv_mgr.depth; +} + +void +ipoib_return_net_buffer_list( + IN NDIS_HANDLE adapter_context, + IN NET_BUFFER_LIST *p_net_buffer_lists, + IN ULONG return_flags) +{ + cl_list_item_t *p_item; + ipoib_port_t *p_port; + ipoib_recv_desc_t *p_desc; + NET_BUFFER_LIST *cur_net_buffer_list,*next_net_buffer_list; + ib_api_status_t status = IB_NOT_DONE; + int32_t shortage; + ULONG complete_flags = 0; + PERF_DECLARE( ReturnPacket ); + PERF_DECLARE( ReturnPutRecv ); + PERF_DECLARE( ReturnRepostRecv ); + PERF_DECLARE( ReturnPreparePkt ); + PERF_DECLARE( ReturnNdisIndicate ); + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + p_port = ((ipoib_adapter_t*)adapter_context)->p_port; + CL_ASSERT( p_net_buffer_lists ); + + cl_perf_start( ReturnPacket ); + cl_spinlock_acquire( &p_port->recv_lock ); + for (cur_net_buffer_list = p_net_buffer_lists; + cur_net_buffer_list != NULL; + cur_net_buffer_list = next_net_buffer_list) + { + next_net_buffer_list = NET_BUFFER_LIST_NEXT_NBL(cur_net_buffer_list); + + /* Get the port and descriptor from the packet. */ + CL_ASSERT(p_port == IPOIB_PORT_FROM_PACKET( cur_net_buffer_list )); + p_desc = IPOIB_RECV_FROM_PACKET( cur_net_buffer_list ); + + + //TODO: NDIS60, rewrite this block + /* Get descriptor from the packet. */ +#if 0 + if( p_desc->type == PKT_TYPE_CM_UCAST ) + { + NDIS_BUFFER *p_buf; + + /* Unchain the NDIS buffer. */ + NdisUnchainBufferAtFront( p_packet, &p_buf ); + CL_ASSERT( p_buf ); + /* Return the NDIS packet and NDIS buffer to their pools. */ + NdisDprFreePacketNonInterlocked( p_packet ); + NdisFreeBuffer( p_buf ); + + endpt_cm_buf_mgr_put_recv( &p_port->cm_buf_mgr, (ipoib_cm_desc_t *)p_desc ); + status = endpt_cm_post_recv( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Post Recv QP failed\n" ) ); + } + cl_spinlock_release( &p_port->recv_lock ); + return; + } +#endif + + cl_perf_start( ReturnPutRecv ); + __buf_mgr_put_recv( p_port, p_desc, cur_net_buffer_list ); + cl_perf_stop( &p_port->p_adapter->perf, ReturnPutRecv ); + } +#if 0 + /* Repost buffers. */ + cl_perf_start( ReturnRepostRecv ); + shortage = __recv_mgr_repost( p_port ); + cl_perf_stop( &p_port->p_adapter->perf, ReturnRepostRecv ); + + for( p_item = cl_qlist_remove_head( &p_port->recv_mgr.done_list ); + p_item != cl_qlist_end( &p_port->recv_mgr.done_list ); + p_item = cl_qlist_remove_head( &p_port->recv_mgr.done_list ) ) + { + p_desc = (ipoib_recv_desc_t*)p_item; + + cl_perf_start( ReturnPreparePkt ); + status = __recv_mgr_prepare_pkt( p_port, p_desc, &cur_net_buffer_list ); + cl_perf_stop( &p_port->p_adapter->perf, ReturnPreparePkt ); + if( status == IB_SUCCESS ) + { + if( shortage > 0 ) + NET_BUFFER_LIST_STATUS( cur_net_buffer_list) = NDIS_STATUS_RESOURCES; + else + NET_BUFFER_LIST_STATUS( cur_net_buffer_list) = NDIS_STATUS_SUCCESS; + + cl_spinlock_release( &p_port->recv_lock ); + NET_BUFFER_LIST_NEXT_NBL(cur_net_buffer_list) = NULL; + cl_perf_start( ReturnNdisIndicate ); + NdisMRecvIndicate( p_port->p_adapter->h_adapter, + cur_net_buffer_list, complete_flags ); + cl_perf_stop( &p_port->p_adapter->perf, ReturnNdisIndicate ); + cl_spinlock_acquire( &p_port->recv_lock ); + + if( shortage > 0 ) + { + cl_perf_start( ReturnPutRecv ); + __buf_mgr_put_recv( p_port, p_desc, cur_net_buffer_list ); + cl_perf_stop( &p_port->p_adapter->perf, ReturnPutRecv ); + + /* Repost buffers. */ + cl_perf_start( ReturnRepostRecv ); + shortage = __recv_mgr_repost( p_port ); + cl_perf_stop( &p_port->p_adapter->perf, ReturnRepostRecv ); + } + } + else if( status != IB_NOT_DONE ) + { + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_RECV, + ("__recv_mgr_prepare_pkt returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + /* Return the item to the head of the list. */ + cl_qlist_insert_head( &p_port->recv_mgr.done_list, p_item ); + break; + } + } + #endif + cl_spinlock_release( &p_port->recv_lock ); + cl_perf_stop( &p_port->p_adapter->perf, ReturnPacket ); + + IPOIB_EXIT( IPOIB_DBG_RECV ); +} + +static void __recv_cb_dpc(KDPC *p_gc_dpc,void *context,void * s_arg1 , void * s_arg2) +{ + + ipoib_port_t *p_port = context; + + UNREFERENCED_PARAMETER(p_gc_dpc); + UNREFERENCED_PARAMETER(s_arg1); + UNREFERENCED_PARAMETER(s_arg2); + + + __recv_cb(NULL, p_port); + ipoib_port_deref( p_port, ref_recv_cb ); + + +} + + +static void +__recv_cb( + IN const ib_cq_handle_t h_cq, + IN void *cq_context ) +{ + ipoib_port_t *p_port; + ib_api_status_t status; + ib_wc_t wc[MAX_RECV_WC], *p_free, *p_wc; + int32_t pkt_cnt, recv_cnt = 0, shortage, discarded; + cl_qlist_t done_list, bad_list; + size_t i; + ULONG recv_complete_flags = 0; + + PERF_DECLARE( RecvCompBundle ); + PERF_DECLARE( RecvCb ); + PERF_DECLARE( PollRecv ); + PERF_DECLARE( RepostRecv ); + PERF_DECLARE( FilterRecv ); + PERF_DECLARE( BuildPktArray ); + PERF_DECLARE( RecvNdisIndicate ); + PERF_DECLARE( RearmRecv ); + PERF_DECLARE( PutRecvList ); + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + cl_perf_clr( RecvCompBundle ); + + cl_perf_start( RecvCb ); +//return ; + UNUSED_PARAM( h_cq ); + + NDIS_SET_SEND_COMPLETE_FLAG(recv_complete_flags, NDIS_RECEIVE_FLAGS_DISPATCH_LEVEL ); + + p_port = (ipoib_port_t*)cq_context; + + cl_qlist_init( &done_list ); + cl_qlist_init( &bad_list ); + + ipoib_port_ref( p_port, ref_recv_cb ); + for( i = 0; i < MAX_RECV_WC; i++ ) + wc[i].p_next = &wc[i + 1]; + wc[MAX_RECV_WC - 1].p_next = NULL; + + /* + * We'll be accessing the endpoint map so take a reference + * on it to prevent modifications. + */ + cl_obj_lock( &p_port->obj ); + cl_atomic_inc( &p_port->endpt_rdr ); + cl_obj_unlock( &p_port->obj ); + + do + { + /* If we get here, then the list of WCs is intact. */ + p_free = wc; + + cl_perf_start( PollRecv ); + status = p_port->p_adapter->p_ifc->poll_cq( + p_port->ib_mgr.h_recv_cq, &p_free, &p_wc ); + cl_perf_stop( &p_port->p_adapter->perf, PollRecv ); + CL_ASSERT( status == IB_SUCCESS || status == IB_NOT_FOUND ); + + /* Look at the payload now and filter ARP and DHCP packets. */ + cl_perf_start( FilterRecv ); + recv_cnt += __recv_mgr_filter( p_port, p_wc, &done_list, &bad_list ); + cl_perf_stop( &p_port->p_adapter->perf, FilterRecv ); + + } while( (!p_free) && (recv_cnt < 128)); + + /* We're done looking at the endpoint map, release the reference. */ + cl_atomic_dec( &p_port->endpt_rdr ); + + cl_perf_log( &p_port->p_adapter->perf, RecvCompBundle, recv_cnt ); + + cl_spinlock_acquire( &p_port->recv_lock ); + + /* Update our posted depth. */ + p_port->recv_mgr.depth -= recv_cnt; + + /* Return any discarded receives to the pool */ + cl_perf_start( PutRecvList ); + __buf_mgr_put_recv_list( p_port, &bad_list ); + cl_perf_stop( &p_port->p_adapter->perf, PutRecvList ); + + do + { + int32_t cnt; + /* Repost ASAP so we don't starve the RQ. */ + cl_perf_start( RepostRecv ); + shortage = __recv_mgr_repost( p_port ); + cl_perf_stop( &p_port->p_adapter->perf, RepostRecv ); + + cl_perf_start( BuildPktArray ); + /* Notify NDIS of any and all possible receive buffers. */ + pkt_cnt = __recv_mgr_build_pkt_array( + p_port, shortage, &done_list, &discarded ); + cl_perf_stop( &p_port->p_adapter->perf, BuildPktArray ); + + /* Only indicate receives if we actually had any. */ + if( discarded && shortage > 0 ) + { + /* We may have thrown away packets, and have a shortage */ + cl_perf_start( RepostRecv ); + __recv_mgr_repost( p_port ); + cl_perf_stop( &p_port->p_adapter->perf, RepostRecv ); + } + + if( !pkt_cnt ) + break; + + cl_spinlock_release( &p_port->recv_lock ); + for( cnt = 0; cnt < pkt_cnt -1; cnt++) + { + NET_BUFFER_LIST_NEXT_NBL(p_port->recv_mgr.recv_pkt_array[cnt]) = + p_port->recv_mgr.recv_pkt_array[cnt + 1]; + } + cl_perf_start( RecvNdisIndicate ); +#ifndef NDIS_DEFAULT_PORT_NUMBER +#define NDIS_DEFAULT_PORT_NUMBER 0 +#endif + /*IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Indicate NDIS with %d received NBs\n", + pkt_cnt) );*/ + NdisMIndicateReceiveNetBufferLists( + p_port->p_adapter->h_adapter, + p_port->recv_mgr.recv_pkt_array[0], + NDIS_DEFAULT_PORT_NUMBER, + pkt_cnt, + recv_complete_flags); + + cl_perf_stop( &p_port->p_adapter->perf, RecvNdisIndicate ); + + /* + * Cap the number of receives to put back to what we just indicated + * with NDIS_STATUS_RESOURCES. + */ + if( shortage > 0 ) + { + if( pkt_cnt < shortage ) + shortage = pkt_cnt; + + /* Return all but the last packet to the pool. */ + cl_spinlock_acquire( &p_port->recv_lock ); + while( shortage-- > 1 ) + { + __buf_mgr_put_recv( p_port, + (ipoib_recv_desc_t *)IPOIB_RECV_FROM_PACKET( p_port->recv_mgr.recv_pkt_array[shortage] ), + p_port->recv_mgr.recv_pkt_array[shortage] ); + } + cl_spinlock_release( &p_port->recv_lock ); + + /* + * Return the last packet as if NDIS returned it, so that we repost + * and report any other pending receives. + */ + ipoib_return_net_buffer_list( NULL, p_port->recv_mgr.recv_pkt_array[0],recv_complete_flags ); + } + cl_spinlock_acquire( &p_port->recv_lock ); + + } while( pkt_cnt ); + cl_spinlock_release( &p_port->recv_lock ); + + if (p_free ) { + /* + * Rearm after filtering to prevent contention on the enpoint maps + * and eliminate the possibility of having a call to + * __endpt_mgr_insert find a duplicate. + */ + cl_perf_start( RearmRecv ); + status = p_port->p_adapter->p_ifc->rearm_cq( + p_port->ib_mgr.h_recv_cq, FALSE ); + cl_perf_stop( &p_port->p_adapter->perf, RearmRecv ); + CL_ASSERT( status == IB_SUCCESS ); + + ipoib_port_deref( p_port, ref_recv_cb ); + } else { + // Please note the reference is still up + KeInsertQueueDpc(&p_port->recv_dpc, NULL, NULL); + } + + cl_perf_stop( &p_port->p_adapter->perf, RecvCb ); + + IPOIB_EXIT( IPOIB_DBG_RECV ); +} + + +static void +__recv_get_endpts( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc, + IN ib_wc_t* const p_wc, + OUT ipoib_endpt_t** const pp_src, + OUT ipoib_endpt_t** const pp_dst ) +{ + ib_api_status_t status; + mac_addr_t mac; + PERF_DECLARE( GetEndptByGid ); + PERF_DECLARE( GetEndptByLid ); + PERF_DECLARE( EndptInsert ); + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + /* Setup our shortcut pointers based on whether GRH is valid. */ + if( p_wc->recv.ud.recv_opt & IB_RECV_OPT_GRH_VALID ) + { + /* Lookup the source endpoints based on GID. */ + cl_perf_start( GetEndptByGid ); + *pp_src = +#if IPOIB_INLINE_RECV + __endpt_mgr_get_by_gid( p_port, &p_desc->buf.ib.grh.src_gid ); +#else /* IPOIB_INLINE_RECV */ + __endpt_mgr_get_by_gid( p_port, &p_desc->p_buf->ib.grh.src_gid ); +#endif /* IPOIB_INLINE_RECV */ + cl_perf_stop( &p_port->p_adapter->perf, GetEndptByGid ); + + /* + * Lookup the destination endpoint based on GID. + * This is used along with the packet filter to determine + * whether to report this to NDIS. + */ + cl_perf_start( GetEndptByGid ); + *pp_dst = +#if IPOIB_INLINE_RECV + __endpt_mgr_get_by_gid( p_port, &p_desc->buf.ib.grh.dest_gid ); +#else /* IPOIB_INLINE_RECV */ + __endpt_mgr_get_by_gid( p_port, &p_desc->p_buf->ib.grh.dest_gid ); +#endif /* IPOIB_INLINE_RECV */ + cl_perf_stop( &p_port->p_adapter->perf, GetEndptByGid ); + + /* + * Create the source endpoint if it does not exist. Note that we + * can only do this for globally routed traffic since we need the + * information from the GRH to generate the MAC. + */ + if( !*pp_src ) + { + status = ipoib_mac_from_guid( +#if IPOIB_INLINE_RECV + p_desc->buf.ib.grh.src_gid.unicast.interface_id, p_port->p_adapter->params.guid_mask, &mac ); +#else /* IPOIB_INLINE_RECV */ + p_desc->p_buf->ib.grh.src_gid.unicast.interface_id, p_port->p_adapter->params.guid_mask, &mac ); +#endif /* IPOIB_INLINE_RECV */ + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_mac_from_guid returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return; + } + + /* Create the endpoint. */ +#if IPOIB_INLINE_RECV + *pp_src = ipoib_endpt_create( &p_desc->buf.ib.grh.src_gid, +#else /* IPOIB_INLINE_RECV */ + *pp_src = ipoib_endpt_create( &p_desc->p_buf->ib.grh.src_gid, +#endif /* IPOIB_INLINE_RECV */ + p_wc->recv.ud.remote_lid, p_wc->recv.ud.remote_qp ); + if( !*pp_src ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_endpt_create failed\n") ); + return; + } + cl_perf_start( EndptInsert ); + cl_obj_lock( &p_port->obj ); + status = __endpt_mgr_insert( p_port, mac, *pp_src ); + if( status != IB_SUCCESS ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__endpt_mgr_insert returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + *pp_src = NULL; + return; + } + cl_obj_unlock( &p_port->obj ); + cl_perf_stop( &p_port->p_adapter->perf, EndptInsert ); + } + } + else + { + /* + * Lookup the remote endpoint based on LID. Note that only + * unicast traffic can be LID routed. + */ + cl_perf_start( GetEndptByLid ); + *pp_src = __endpt_mgr_get_by_lid( p_port, p_wc->recv.ud.remote_lid ); + cl_perf_stop( &p_port->p_adapter->perf, GetEndptByLid ); + *pp_dst = p_port->p_local_endpt; + CL_ASSERT( *pp_dst ); + } + + if( *pp_src && !ipoib_is_voltaire_router_gid( &(*pp_src)->dgid ) && + (*pp_src)->qpn != p_wc->recv.ud.remote_qp ) + { + /* Update the QPN for the endpoint. */ + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_RECV, + ("Updating QPN for MAC: %02X-%02X-%02X-%02X-%02X-%02X\n", + (*pp_src )->mac.addr[0], (*pp_src )->mac.addr[1], + (*pp_src )->mac.addr[2], (*pp_src )->mac.addr[3], + (*pp_src )->mac.addr[4], (*pp_src )->mac.addr[5]) ); +// (*pp_src)->qpn = p_wc->recv.ud.remote_qp; + } + + if( *pp_src && *pp_dst ) + { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_RECV, + ("Recv:\n" + "\tsrc MAC: %02X-%02X-%02X-%02X-%02X-%02X\n" + "\tdst MAC: %02X-%02X-%02X-%02X-%02X-%02X\n", + (*pp_src )->mac.addr[0], (*pp_src )->mac.addr[1], + (*pp_src )->mac.addr[2], (*pp_src )->mac.addr[3], + (*pp_src )->mac.addr[4], (*pp_src )->mac.addr[5], + (*pp_dst )->mac.addr[0], (*pp_dst )->mac.addr[1], + (*pp_dst )->mac.addr[2], (*pp_dst )->mac.addr[3], + (*pp_dst )->mac.addr[4], (*pp_dst )->mac.addr[5]) ); + } + + IPOIB_EXIT( IPOIB_DBG_RECV ); +} + + +static int32_t +__recv_mgr_filter( + IN ipoib_port_t* const p_port, + IN ib_wc_t* const p_done_wc_list, + OUT cl_qlist_t* const p_done_list, + OUT cl_qlist_t* const p_bad_list ) +{ + ipoib_recv_desc_t *p_desc; + ib_wc_t *p_wc; + ipoib_pkt_t *p_ipoib; + eth_pkt_t *p_eth; + ipoib_endpt_t *p_src, *p_dst; + ib_api_status_t status; + uint32_t len; + int32_t recv_cnt = 0; + PERF_DECLARE( GetRecvEndpts ); + PERF_DECLARE( RecvGen ); + PERF_DECLARE( RecvTcp ); + PERF_DECLARE( RecvUdp ); + PERF_DECLARE( RecvDhcp ); + PERF_DECLARE( RecvArp ); + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + for( p_wc = p_done_wc_list; p_wc; p_wc = p_wc->p_next ) + { + CL_ASSERT( p_wc->status != IB_WCS_SUCCESS || p_wc->wc_type == IB_WC_RECV ); + p_desc = (ipoib_recv_desc_t*)(uintn_t)p_wc->wr_id; + recv_cnt++; + + if( p_wc->status != IB_WCS_SUCCESS ) + { + if( p_wc->status != IB_WCS_WR_FLUSHED_ERR ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed completion %s (vendor specific %#x)\n", + p_port->p_adapter->p_ifc->get_wc_status_str( p_wc->status ), + (int)p_wc->vendor_specific) ); + ipoib_inc_recv_stat( p_port->p_adapter, IP_STAT_ERROR, 0, 0 ); + } + else + { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_RECV, + ("Flushed completion %s\n", + p_port->p_adapter->p_ifc->get_wc_status_str( p_wc->status )) ); + ipoib_inc_recv_stat( p_port->p_adapter, IP_STAT_DROPPED, 0, 0 ); + } + cl_qlist_insert_tail( p_bad_list, &p_desc->item.list_item ); + /* Dereference the port object on behalf of the failed receive. */ + ipoib_port_deref( p_port, ref_failed_recv_wc ); + continue; + } + + len = p_wc->length - sizeof(ib_grh_t); + + if( len < sizeof(ipoib_hdr_t) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received ETH packet < min size\n") ); + ipoib_inc_recv_stat( p_port->p_adapter, IP_STAT_ERROR, 0, 0 ); + cl_qlist_insert_tail( p_bad_list, &p_desc->item.list_item ); + ipoib_port_deref( p_port, ref_recv_inv_len ); + continue; + } + + if((len - sizeof(ipoib_hdr_t)) > p_port->p_adapter->params.payload_mtu) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received ETH packet len %d > payload MTU (%d)\n", + (len - sizeof(ipoib_hdr_t)), + p_port->p_adapter->params.payload_mtu) ); + ipoib_inc_recv_stat( p_port->p_adapter, IP_STAT_ERROR, 0, 0 ); + cl_qlist_insert_tail( p_bad_list, &p_desc->item.list_item ); + ipoib_port_deref( p_port, ref_recv_inv_len ); + continue; + + } + /* Successful completion. Get the receive information. */ + p_desc->ndis_csum.Value = ( ( p_wc->recv.ud.recv_opt & IB_RECV_OPT_CSUM_MASK ) >> 8 ); + cl_perf_start( GetRecvEndpts ); + __recv_get_endpts( p_port, p_desc, p_wc, &p_src, &p_dst ); + cl_perf_stop( &p_port->p_adapter->perf, GetRecvEndpts ); + +#if IPOIB_INLINE_RECV + p_ipoib = &p_desc->buf.ib.pkt; + p_eth = &p_desc->buf.eth.pkt; +#else /* IPOIB_INLINE_RECV */ + p_ipoib = &p_desc->p_buf->ib.pkt; + p_eth = &p_desc->p_buf->eth.pkt; +#endif /*IPOIB_INLINE_RECV */ + + if( p_src ) + { + /* Don't report loopback traffic - we requested SW loopback. */ + if( !cl_memcmp( &p_port->p_adapter->params.conf_mac, + &p_src->mac, sizeof(p_port->p_adapter->params.conf_mac) ) ) + { + /* + * "This is not the packet you're looking for" - don't update + * receive statistics, the packet never happened. + */ + cl_qlist_insert_tail( p_bad_list, &p_desc->item.list_item ); + /* Dereference the port object on behalf of the failed recv. */ + ipoib_port_deref( p_port, ref_recv_loopback ); + continue; + } + } + + switch( p_ipoib->hdr.type ) + { + case ETH_PROT_TYPE_IP: + if( len < (sizeof(ipoib_hdr_t) + sizeof(ip_hdr_t)) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received IP packet < min size\n") ); + status = IB_INVALID_SETTING; + break; + } + + if( p_ipoib->type.ip.hdr.offset || + p_ipoib->type.ip.hdr.prot != IP_PROT_UDP ) + { + /* Unfiltered. Setup the ethernet header and report. */ + cl_perf_start( RecvTcp ); + status = __recv_gen( p_ipoib, p_eth, p_src, p_dst ); + cl_perf_stop( &p_port->p_adapter->perf, RecvTcp ); + break; + } + + /* First packet of a UDP transfer. */ + if( len < + (sizeof(ipoib_hdr_t) + sizeof(ip_hdr_t) + sizeof(udp_hdr_t)) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received UDP packet < min size\n") ); + status = IB_INVALID_SETTING; + break; + } + + /* Check if DHCP conversion is required. */ + if( (p_ipoib->type.ip.prot.udp.hdr.dst_port == DHCP_PORT_SERVER && + p_ipoib->type.ip.prot.udp.hdr.src_port == DHCP_PORT_CLIENT) || + (p_ipoib->type.ip.prot.udp.hdr.dst_port == DHCP_PORT_CLIENT && + p_ipoib->type.ip.prot.udp.hdr.src_port == DHCP_PORT_SERVER) ) + { + if( len < (sizeof(ipoib_hdr_t) + sizeof(ip_hdr_t) + + sizeof(udp_hdr_t) + DHCP_MIN_SIZE) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received DHCP < min size\n") ); + status = IB_INVALID_SETTING; + break; + } + if ((p_ipoib->type.ip.hdr.ver_hl & 0x0f) != 5 ) { + // If there are IP options in this message, we are in trouble in any case + status = IB_INVALID_SETTING; + break; + } + /* UDP packet with BOOTP ports in src/dst port numbers. */ + cl_perf_start( RecvDhcp ); + status = __recv_dhcp( p_port, p_ipoib, p_eth, p_src, p_dst ); + cl_perf_stop( &p_port->p_adapter->perf, RecvDhcp ); + } + else + { + /* Unfiltered. Setup the ethernet header and report. */ + cl_perf_start( RecvUdp ); + status = __recv_gen( p_ipoib, p_eth, p_src, p_dst ); + cl_perf_stop( &p_port->p_adapter->perf, RecvUdp ); + } + break; + + case ETH_PROT_TYPE_ARP: + if( len < (sizeof(ipoib_hdr_t) + sizeof(ipoib_arp_pkt_t)) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received ARP < min size\n") ); + status = IB_INVALID_SETTING; + break; + } + cl_perf_start( RecvArp ); + status = __recv_arp( p_port, p_wc, p_ipoib, p_eth, &p_src, p_dst ); + cl_perf_stop( &p_port->p_adapter->perf, RecvArp ); + len = sizeof(ipoib_hdr_t) + sizeof(arp_pkt_t); + break; + + default: + /* Unfiltered. Setup the ethernet header and report. */ + cl_perf_start( RecvGen ); + status = __recv_gen( p_ipoib, p_eth, p_src, p_dst ); + cl_perf_stop( &p_port->p_adapter->perf, RecvGen ); + } + + if( status != IB_SUCCESS ) + { + /* Update stats. */ + ipoib_inc_recv_stat( p_port->p_adapter, IP_STAT_ERROR, 0, 0 ); + cl_qlist_insert_tail( p_bad_list, &p_desc->item.list_item ); + /* Dereference the port object on behalf of the failed receive. */ + ipoib_port_deref( p_port, ref_recv_filter ); + } + else + { + ip_stat_sel_t ip_stat; + p_desc->len = + len + sizeof(eth_hdr_t) - sizeof(ipoib_hdr_t); + if( p_dst->h_mcast) + { + if( p_dst->dgid.multicast.raw_group_id[10] == 0xFF && + p_dst->dgid.multicast.raw_group_id[11] == 0xFF && + p_dst->dgid.multicast.raw_group_id[12] == 0xFF && + p_dst->dgid.multicast.raw_group_id[13] == 0xFF ) + { + p_desc->type = PKT_TYPE_BCAST; + ip_stat = IP_STAT_BCAST_BYTES; + } + else + { + p_desc->type = PKT_TYPE_MCAST; + ip_stat = IP_STAT_MCAST_BYTES; + } + } + else + { + p_desc->type = PKT_TYPE_UCAST; + ip_stat = IP_STAT_UCAST_BYTES; + + } + cl_qlist_insert_tail( p_done_list, &p_desc->item.list_item ); + ipoib_inc_recv_stat( p_port->p_adapter, ip_stat, len, 1 ); + } + } + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return recv_cnt; +} + + +static ib_api_status_t +__recv_gen( + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src, + IN ipoib_endpt_t* const p_dst ) +{ + IPOIB_ENTER( IPOIB_DBG_RECV ); + + if( !p_src || !p_dst ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received packet with no matching endpoints.\n") ); + return IB_NOT_DONE; + } + + /* + * Fill in the ethernet header. Note that doing so will overwrite + * the IPoIB header, so start by moving the information from the IPoIB + * header. + */ + p_eth->hdr.type = p_ipoib->hdr.type; + p_eth->hdr.src = p_src->mac; + p_eth->hdr.dst = p_dst->mac; + + if ( p_eth->hdr.dst.addr[0] == 1 && + p_eth->hdr.type == ETH_PROT_TYPE_IP && + p_eth->hdr.dst.addr[2] == 0x5E) + { + p_eth->hdr.dst.addr[1] = 0; + p_eth->hdr.dst.addr[3] = p_eth->hdr.dst.addr[3] & 0x7f; + } + if (p_dst->h_mcast) + p_dst->is_in_use = TRUE; + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return IB_SUCCESS; +} + + +static ib_api_status_t +__recv_dhcp( + IN ipoib_port_t* const p_port, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src, + IN ipoib_endpt_t* const p_dst ) +{ + ib_api_status_t status; + dhcp_pkt_t *p_dhcp; + uint8_t *p_option; + uint8_t *p_cid = NULL; + ib_gid_t gid; + uint8_t msg = 0; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + UNUSED_PARAM( p_port ); + + /* Create the ethernet header. */ + status = __recv_gen( p_ipoib, p_eth, p_src, p_dst ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__recv_gen returned %s.\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Fixup the payload. */ + p_dhcp = &p_eth->type.ip.prot.udp.dhcp; + if( p_dhcp->op != DHCP_REQUEST && p_dhcp->op != DHCP_REPLY ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid DHCP op code.\n") ); + return IB_INVALID_SETTING; + } + + /* + * Find the client identifier option, making sure to skip + * the "magic cookie". + */ + p_option = &p_dhcp->options[0]; + if ( *(uint32_t *)p_option != DHCP_COOKIE ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("DHCP cookie corrupted.\n") ); + return IB_INVALID_PARAMETER; + } + + p_option = &p_dhcp->options[4]; + while( *p_option != DHCP_OPT_END && p_option < &p_dhcp->options[312] ) + { + switch( *p_option ) + { + case DHCP_OPT_PAD: + p_option++; + break; + + case DHCP_OPT_MSG: + msg = p_option[2]; + p_option += 3; + break; + + case DHCP_OPT_CLIENT_ID: + p_cid = p_option; + /* Fall through. */ + + default: + /* + * All other options have a length byte following the option code. + * Offset by the length to get to the next option. + */ + p_option += (p_option[1] + 2); + } + } + + switch( msg ) + { + /* message from client */ + case DHCPDISCOVER: + case DHCPREQUEST: + case DHCPDECLINE: + case DHCPRELEASE: + case DHCPINFORM: + if( !p_cid ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to find required Client-identifier option.\n") ); + return IB_INVALID_SETTING; + } + if( p_dhcp->htype != DHCP_HW_TYPE_IB ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid hardware address type.\n") ); + return IB_INVALID_SETTING; + } + break; + /* message from DHCP server */ + case DHCPOFFER: + case DHCPACK: + case DHCPNAK: + break; + + default: + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalide message type.\n") ); + return IB_INVALID_PARAMETER; + } + p_eth->type.ip.prot.udp.hdr.chksum = 0; + p_dhcp->htype = DHCP_HW_TYPE_ETH; + p_dhcp->hlen = HW_ADDR_LEN; + + if( p_cid ) /* from client */ + { + /* Validate that the length and type of the option is as required. */ + if( p_cid[1] != 21 ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Client-identifier length not 21 as required.\n") ); + return IB_INVALID_SETTING; + } + if( p_cid[2] != DHCP_HW_TYPE_IB ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Client-identifier type is wrong.\n") ); + return IB_INVALID_SETTING; + } + /* + * Copy the GID value from the option so that we can make aligned + * accesses to the contents. + * Recover CID to standard type. + */ + cl_memcpy( &gid, &p_cid[7], sizeof(ib_gid_t) ); + p_cid[1] = HW_ADDR_LEN +1;// CID length + p_cid[2] = DHCP_HW_TYPE_ETH;// CID type + status = ipoib_mac_from_guid( gid.unicast.interface_id, p_port->p_adapter->params.guid_mask, (mac_addr_t*)&p_cid[3] ); + if (status == IB_INVALID_GUID_MASK) + { + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_ERROR, + ("Invalid GUID mask received, rejecting it") ); + ipoib_create_log(p_port->p_adapter->h_adapter, GUID_MASK_LOG_INDEX, EVENT_IPOIB_WRONG_PARAMETER_WRN); + status = IB_SUCCESS; + } + p_cid[HW_ADDR_LEN + 3] = DHCP_OPT_END; //terminate tag + } + IPOIB_EXIT( IPOIB_DBG_RECV ); + return status; +} + + +static ib_api_status_t +__recv_arp( + IN ipoib_port_t* const p_port, + IN ib_wc_t* const p_wc, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t** const pp_src, + IN ipoib_endpt_t* const p_dst ) +{ + ib_api_status_t status; + arp_pkt_t *p_arp; + const ipoib_arp_pkt_t *p_ib_arp; + ib_gid_t gid; + mac_addr_t mac; + ipoib_hw_addr_t null_hw = {0}; + uint8_t cm_capable = 0; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + if( !p_dst ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Unknown destination endpoint\n") ); + return IB_INVALID_SETTING; + } + + p_ib_arp = &p_ipoib->type.arp; + p_arp = &p_eth->type.arp; + + if( p_ib_arp->hw_type != ARP_HW_TYPE_IB ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ARP hardware type is not IB\n") ); + return IB_INVALID_SETTING; + } + + if( p_ib_arp->hw_size != sizeof(ipoib_hw_addr_t) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ARP hardware address size is not sizeof(ipoib_hw_addr_t)\n") ); + return IB_INVALID_SETTING; + } + + if( p_ib_arp->prot_type != ETH_PROT_TYPE_IP ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ARP protocal type not IP\n") ); + return IB_INVALID_SETTING; + } + + cm_capable = ipoib_addr_get_flags( &p_ib_arp->src_hw ); + + /* + * If we don't have a source, lookup the endpoint specified in the payload. + */ + if( !*pp_src ) + *pp_src = __endpt_mgr_get_by_gid( p_port, &p_ib_arp->src_hw.gid ); + + /* + * If the endpoint exists for the GID, make sure + * the dlid and qpn match the arp. + */ + if( *pp_src ) + { + if( cl_memcmp( &(*pp_src)->dgid, &p_ib_arp->src_hw.gid, + sizeof(ib_gid_t) ) ) + { + /* + * GIDs for the endpoint are different. The ARP must + * have been proxied. Dereference it. + */ + *pp_src = NULL; + } + else if( (*pp_src)->dlid && + (*pp_src)->dlid != p_wc->recv.ud.remote_lid ) + { + /* Out of date! Destroy the endpoint and replace it. */ + __endpt_mgr_remove( p_port, *pp_src ); + *pp_src = NULL; + } + else if ( ! ((*pp_src)->dlid)) { + /* Out of date! Destroy the endpoint and replace it. */ + __endpt_mgr_remove( p_port, *pp_src ); + *pp_src = NULL; + } + else if( ipoib_is_voltaire_router_gid( &(*pp_src)->dgid ) ) + { + if( (*pp_src)->qpn != ipoib_addr_get_qpn( &p_ib_arp->src_hw ) && + p_wc->recv.ud.remote_qp != ipoib_addr_get_qpn( &p_ib_arp->src_hw ) ) + { + /* Out of date! Destroy the endpoint and replace it. */ + __endpt_mgr_remove( p_port, *pp_src ); + *pp_src = NULL; + } + } + else if( (*pp_src)->qpn != p_wc->recv.ud.remote_qp ) + { + /* Out of date! Destroy the endpoint and replace it. */ + __endpt_mgr_remove( p_port, *pp_src ); + *pp_src = NULL; + } + } + + /* Do we need to create an endpoint for this GID? */ + if( !*pp_src ) + { + /* Copy the src GID to allow aligned access */ + cl_memcpy( &gid, &p_ib_arp->src_hw.gid, sizeof(ib_gid_t) ); + status = ipoib_mac_from_guid( gid.unicast.interface_id, p_port->p_adapter->params.guid_mask, &mac ); + if (status == IB_INVALID_GUID_MASK) + { + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_ERROR, + ("Invalid GUID mask received, rejecting it") ); + ipoib_create_log(p_port->p_adapter->h_adapter, GUID_MASK_LOG_INDEX, EVENT_IPOIB_WRONG_PARAMETER_WRN); + } + else if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_mac_from_guid returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + /* + * Create the endpoint. + */ + *pp_src = ipoib_endpt_create( &p_ib_arp->src_hw.gid, + p_wc->recv.ud.remote_lid, ipoib_addr_get_qpn( &p_ib_arp->src_hw ) ); + + if( !*pp_src ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_endpt_create failed\n") ); + return status; + } + + cl_obj_lock( &p_port->obj ); + status = __endpt_mgr_insert( p_port, mac, *pp_src ); + if( status != IB_SUCCESS ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__endpt_mgr_insert return %s \n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + cl_obj_unlock( &p_port->obj ); + } + + (*pp_src)->cm_flag = cm_capable; + + CL_ASSERT( !cl_memcmp( + &(*pp_src)->dgid, &p_ib_arp->src_hw.gid, sizeof(ib_gid_t) ) ); + CL_ASSERT( ipoib_is_voltaire_router_gid( &(*pp_src)->dgid ) || + (*pp_src)->qpn == ipoib_addr_get_qpn( &p_ib_arp->src_hw ) ); +#if 0 + if( p_port->p_adapter->params.cm_enabled && + p_ib_arp->op == ARP_OP_REQ && + cm_capable == IPOIB_CM_FLAG_RC ) + { + /* if we've got ARP request and RC flag is set, + save SID for connect REQ to be sent in ARP reply + when requestor's path get resolved */ + if( endpt_cm_get_state( (*pp_src) ) == IPOIB_CM_DISCONNECTED ) + { + (*pp_src)->cm_flag = cm_capable; + ipoib_addr_set_sid( + &(*pp_src)->conn.service_id, + ipoib_addr_get_qpn( &p_ib_arp->src_hw ) ); + } + } +#endif +#if 0 //DBG + if( p_port->p_adapter->params.cm_enabled ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + (" ARP %s from ENDPT[%p] state %d CM cap: %d QPN: %#x MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", + ((p_ib_arp->op == ARP_OP_REQ )? "REQUEST" : "REPLY"), + *pp_src, endpt_cm_get_state( *pp_src ), + ((cm_capable == IPOIB_CM_FLAG_RC)? 1: 0), + cl_ntoh32( ipoib_addr_get_qpn( &p_ib_arp->src_hw ) ), + (*pp_src)->mac.addr[0], (*pp_src)->mac.addr[1], + (*pp_src)->mac.addr[2], (*pp_src)->mac.addr[3], + (*pp_src)->mac.addr[4], (*pp_src)->mac.addr[5] )); + } +#endif + + /* Now swizzle the data. */ + p_arp->hw_type = ARP_HW_TYPE_ETH; + p_arp->hw_size = sizeof(mac_addr_t); + p_arp->src_hw = (*pp_src)->mac; + p_arp->src_ip = p_ib_arp->src_ip; + + if( cl_memcmp( &p_ib_arp->dst_hw, &null_hw, sizeof(ipoib_hw_addr_t) ) ) + { + if( cl_memcmp( &p_dst->dgid, &p_ib_arp->dst_hw.gid, sizeof(ib_gid_t) ) ) + { + /* + * We received bcast ARP packet that means + * remote port lets everyone know it was changed IP/MAC + * or just activated + */ + + /* Guy: TODO: Check why this check fails in case of Voltaire IPR */ + + if ( !ipoib_is_voltaire_router_gid( &(*pp_src)->dgid ) && + !ib_gid_is_multicast( (const ib_gid_t*)&p_dst->dgid ) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ARP: is not ARP MCAST\n") ); + return IB_INVALID_SETTING; + } + + p_arp->dst_hw = p_port->p_local_endpt->mac; + p_dst->mac = p_port->p_local_endpt->mac; + /* + * we don't care what receiver ip addr is, + * as long as OS' ARP table is global ??? + */ + p_arp->dst_ip = (net32_t)0; + } + else /* we've got reply to our ARP request */ + { + p_arp->dst_hw = p_dst->mac; + p_arp->dst_ip = p_ib_arp->dst_ip; + CL_ASSERT( p_dst->qpn == ipoib_addr_get_qpn( &p_ib_arp->dst_hw ) ); + } + } + else /* we got ARP reqeust */ + { + cl_memclr( &p_arp->dst_hw, sizeof(mac_addr_t) ); + p_arp->dst_ip = p_ib_arp->dst_ip; + } + + /* + * Create the ethernet header. Note that this is done last so that + * we have a chance to create a new endpoint. + */ + status = __recv_gen( p_ipoib, p_eth, *pp_src, p_dst ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__recv_gen returned %s.\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return IB_SUCCESS; +} + + +static ib_api_status_t +__recv_mgr_prepare_pkt( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc, + OUT NET_BUFFER_LIST** const pp_net_buffer_list ) +{ + NDIS_STATUS status; + uint32_t pkt_filter; + ip_stat_sel_t type; + //NDIS60 + NDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO chksum; + //NDIS_TCP_IP_CHECKSUM_PACKET_INFO chksum; + + PERF_DECLARE( GetNdisPkt ); + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + pkt_filter = p_port->p_adapter->packet_filter; + /* Check the packet filter. */ + switch( p_desc->type ) + { + default: + case PKT_TYPE_UCAST: + + if( pkt_filter & NDIS_PACKET_TYPE_PROMISCUOUS || + pkt_filter & NDIS_PACKET_TYPE_ALL_FUNCTIONAL || + pkt_filter & NDIS_PACKET_TYPE_SOURCE_ROUTING || + pkt_filter & NDIS_PACKET_TYPE_DIRECTED ) + { + /* OK to report. */ + type = IP_STAT_UCAST_BYTES; + status = NDIS_STATUS_SUCCESS; + IPOIB_PRINT( TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, + ("Received UCAST PKT.\n")); + } + else + { + type = IP_STAT_DROPPED; + status = NDIS_STATUS_FAILURE; + IPOIB_PRINT( TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, + ("Received UCAST PKT with ERROR !!!!\n")); + } + break; + case PKT_TYPE_BCAST: + if( pkt_filter & NDIS_PACKET_TYPE_PROMISCUOUS || + pkt_filter & NDIS_PACKET_TYPE_BROADCAST ) + { + /* OK to report. */ + type = IP_STAT_BCAST_BYTES; + status = NDIS_STATUS_SUCCESS; + IPOIB_PRINT( TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, + ("Received BCAST PKT.\n")); + } + else + { + type = IP_STAT_DROPPED; + status = NDIS_STATUS_FAILURE; + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received BCAST PKT with ERROR !!!!\n")); + } + break; + case PKT_TYPE_MCAST: + if( pkt_filter & NDIS_PACKET_TYPE_PROMISCUOUS || + pkt_filter & NDIS_PACKET_TYPE_ALL_MULTICAST || + pkt_filter & NDIS_PACKET_TYPE_MULTICAST ) + { + /* OK to report. */ + type = IP_STAT_MCAST_BYTES; + status = NDIS_STATUS_SUCCESS; + IPOIB_PRINT( TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, + ("Received UCAST PKT.\n")); + } + else + { + type = IP_STAT_DROPPED; + status = NDIS_STATUS_FAILURE; + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received MCAST PKT with ERROR !!!!\n")); + } + break; + } + + if( status != NDIS_STATUS_SUCCESS ) + { + ipoib_inc_recv_stat( p_port->p_adapter, type, 0, 0 ); + /* Return the receive descriptor to the pool. */ + __buf_mgr_put_recv( p_port, p_desc, NULL ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_RECV, + ("Packet filter doesn't match receive. Dropping.\n") ); + /* + * Return IB_NOT_DONE since the packet has been completed, + * but has not consumed an array entry. + */ + return IB_NOT_DONE; + } + + cl_perf_start( GetNdisPkt ); + *pp_net_buffer_list = __buf_mgr_get_ndis_pkt( p_port, p_desc ); + cl_perf_stop( &p_port->p_adapter->perf, GetNdisPkt ); + if( !*pp_net_buffer_list ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__buf_mgr_get_ndis_pkt failed\n") ); + return IB_INSUFFICIENT_RESOURCES; + } + + chksum.Value = 0; + switch( p_port->p_adapter->params.recv_chksum_offload ) + { + default: + CL_ASSERT( FALSE ); + case CSUM_DISABLED: + //NDIS60 + //NDIS_PER_PACKET_INFO_FROM_PACKET( *pp_packet, TcpIpChecksumPacketInfo ) = + //(void*)(uintn_t)chksum.Value; + NET_BUFFER_LIST_INFO(*pp_net_buffer_list, TcpIpChecksumNetBufferListInfo) = + (void*)(uintn_t)chksum.Value; + break; + case CSUM_ENABLED: + /* Get the checksums directly from packet information. */ + /* In this case, no one of cheksum's cat get false value */ + /* If hardware checksum failed or wasn't calculated, NDIS will recalculate it again */ + //NDIS60 + //NDIS_PER_PACKET_INFO_FROM_PACKET( *pp_packet, TcpIpChecksumPacketInfo ) = + NET_BUFFER_LIST_INFO(*pp_net_buffer_list, TcpIpChecksumNetBufferListInfo) = + (void*)(uintn_t)(p_desc->ndis_csum.Value); + break; + case CSUM_BYPASS: + /* Flag the checksums as having been calculated. */ + chksum.Receive.TcpChecksumSucceeded = TRUE; + chksum.Receive.UdpChecksumSucceeded = TRUE; + chksum.Receive.IpChecksumSucceeded = TRUE; + //NDIS60 + //NDIS_PER_PACKET_INFO_FROM_PACKET( *pp_packet, TcpIpChecksumPacketInfo ) = + NET_BUFFER_LIST_INFO(*pp_net_buffer_list, TcpIpChecksumNetBufferListInfo) = + (void*)(uintn_t)chksum.Value; + break; + } + ipoib_inc_recv_stat( p_port->p_adapter, type, p_desc->len, 1 ); + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return IB_SUCCESS; +} + + +static uint32_t +__recv_mgr_build_pkt_array( + IN ipoib_port_t* const p_port, + IN int32_t shortage, + OUT cl_qlist_t* const p_done_list, + OUT int32_t* const p_discarded ) +{ + cl_list_item_t *p_item; + ipoib_recv_desc_t *p_desc; + uint32_t i = 0; + ib_api_status_t status; + PERF_DECLARE( PreparePkt ); + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + *p_discarded = 0; + + /* Move any existing receives to the head to preserve ordering. */ + cl_qlist_insert_list_head( p_done_list, &p_port->recv_mgr.done_list ); + p_item = cl_qlist_remove_head( p_done_list ); + while( p_item != cl_qlist_end( p_done_list ) ) + { + p_desc = (ipoib_recv_desc_t*)p_item; + + cl_perf_start( PreparePkt ); + status = __recv_mgr_prepare_pkt( p_port, p_desc, + &p_port->recv_mgr.recv_pkt_array[i] ); + cl_perf_stop( &p_port->p_adapter->perf, PreparePkt ); + if( status == IB_SUCCESS ) + { + CL_ASSERT( p_port->recv_mgr.recv_pkt_array[i] ); + if( shortage-- > 0 ) + { + NET_BUFFER_LIST_STATUS(p_port->recv_mgr.recv_pkt_array[i])= NDIS_STATUS_RESOURCES; + } + else + { + NET_BUFFER_LIST_STATUS(p_port->recv_mgr.recv_pkt_array[i])= NDIS_STATUS_SUCCESS; + } + i++; + } + else if( status == IB_NOT_DONE ) + { + (*p_discarded)++; + } + else + { + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_RECV, + ("__recv_mgr_prepare_pkt returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + /* Put all completed receives on the port's done list. */ + cl_qlist_insert_tail( &p_port->recv_mgr.done_list, p_item ); + cl_qlist_insert_list_tail( &p_port->recv_mgr.done_list, p_done_list ); + break; + } + + p_item = cl_qlist_remove_head( p_done_list ); + } + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return i; +} + + + + +/****************************************************************************** +* +* Send manager implementation. +* +******************************************************************************/ +static void +__send_mgr_construct( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_SEND ); + p_port->send_mgr.depth = 0; + cl_qlist_init( &p_port->send_mgr.pending_list ); + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + + +static void +__pending_list_destroy( + IN ipoib_port_t* const p_port ) +{ + cl_list_item_t *p_item; + NET_BUFFER_LIST **pp_net_buffer_list, *p_head; + + p_head = NULL; + cl_spinlock_acquire( &p_port->send_lock ); + /* Complete any pending packets. */ + pp_net_buffer_list = &p_head; + for( p_item = cl_qlist_remove_head( &p_port->send_mgr.pending_list ); + p_item != cl_qlist_end( &p_port->send_mgr.pending_list ); + p_item = cl_qlist_remove_head( &p_port->send_mgr.pending_list ) ) + { + *pp_net_buffer_list = IPOIB_PACKET_FROM_LIST_ITEM( p_item ); + NET_BUFFER_LIST_STATUS(*pp_net_buffer_list) = NDIS_STATUS_RESET_IN_PROGRESS; + pp_net_buffer_list = &(NET_BUFFER_LIST_NEXT_NBL(*pp_net_buffer_list)); + } + cl_spinlock_release( &p_port->send_lock ); + if(p_head) + NdisMSendNetBufferListsComplete( + p_port->p_adapter->h_adapter, + p_head, + 0); +} + +static void +__send_mgr_destroy( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_SEND ); + __pending_list_destroy(p_port); + + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + + +static NDIS_STATUS +__send_mgr_filter( + IN ipoib_port_t* const p_port, + IN const eth_hdr_t* const p_eth_hdr, + IN MDL* const p_mdl, + IN size_t buf_len, + IN SCATTER_GATHER_LIST *p_sgl, + IN OUT ipoib_send_desc_t* const p_desc ) +{ + NDIS_STATUS status; + + PERF_DECLARE( FilterIp ); + PERF_DECLARE( FilterArp ); + PERF_DECLARE( SendGen ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + /* + * We already checked the ethernet header length, so we know it's safe + * to decrement the buf_len without underflowing. + */ + buf_len -= sizeof(eth_hdr_t); + + switch( p_eth_hdr->type ) + { + case ETH_PROT_TYPE_IP: + cl_perf_start( FilterIp ); + status = __send_mgr_filter_ip( + p_port, p_eth_hdr, p_mdl, buf_len, p_sgl, p_desc); + cl_perf_stop( &p_port->p_adapter->perf, FilterIp ); + break; + + case ETH_PROT_TYPE_ARP: + cl_perf_start( FilterArp ); + status = __send_mgr_filter_arp( + p_port, p_eth_hdr, p_mdl, buf_len, p_desc ); + p_desc->send_dir = SEND_UD_QP; + cl_perf_stop( &p_port->p_adapter->perf, FilterArp ); + break; + + default: + /* + * The IPoIB spec doesn't define how to send non IP or ARP packets. + * Just send the payload and hope for the best. + */ + + p_desc->send_dir = SEND_UD_QP; + cl_perf_start( SendGen ); + status = __send_gen( p_port, p_desc, p_sgl, 0 ); + cl_perf_stop( &p_port->p_adapter->perf, SendGen ); + break; + } + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return status; +} + + +static NDIS_STATUS +__send_copy( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc ) +{ + NET_BUFFER_LIST *p_net_buffer_list; + NET_BUFFER *p_netbuffer; + MDL *p_mdl; + UINT tot_len = 0; + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + UNREFERENCED_PARAMETER(p_port); + UNREFERENCED_PARAMETER(p_desc); + + p_desc->p_buf = + NdisAllocateFromNPagedLookasideList( &p_port->buf_mgr.send_buf_list ); + if( !p_desc->p_buf ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate buffer for packet copy.\n") ); + return NDIS_STATUS_RESOURCES; + } + + p_mdl = NdisAllocateMdl(p_port->p_adapter->h_adapter, + p_desc->p_buf, + p_port->p_adapter->params.xfer_block_size ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate MDL\n") ); + return NDIS_STATUS_RESOURCES; + } + + p_net_buffer_list = NdisAllocateNetBufferAndNetBufferList( + p_port->buf_mgr.h_send_buf_pool, + 0, + 0, + p_mdl, + 0, + 0); + + if( !p_net_buffer_list ) + { + NdisFreeMdl(p_mdl); + IPOIB_PRINT_EXIT( TRACE_LEVEL_WARNING, IPOIB_DBG_SEND, + ("Failed to allocate NDIS_PACKET for copy.\n") ); + return NDIS_STATUS_RESOURCES; + } + + for (p_netbuffer = NET_BUFFER_LIST_FIRST_NB(p_net_buffer_list); + p_netbuffer != NULL; + p_netbuffer = NET_BUFFER_NEXT_NB(p_netbuffer)) + { + tot_len +=NET_BUFFER_DATA_LENGTH(p_netbuffer); + } + + /* Setup the work request. */ + p_desc->send_wr[0].local_ds[1].vaddr = cl_get_physaddr( + ((uint8_t*)p_desc->p_buf) + sizeof(eth_hdr_t) ); + p_desc->send_wr[0].local_ds[1].length = tot_len - sizeof(eth_hdr_t); + p_desc->send_wr[0].local_ds[1].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[0].wr.num_ds = 2; + + /* Free our temp packet now that the data is copied. */ + NdisFreeMdl(p_mdl); + NdisFreeNetBufferList(p_net_buffer_list); + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return NDIS_STATUS_SUCCESS; +} + +static inline NDIS_STATUS +__send_mgr_get_eth_hdr( + IN PNET_BUFFER p_net_buffer, + OUT MDL** const pp_mdl, + OUT eth_hdr_t** const pp_eth_hdr, + OUT UINT* p_mdl_len) +{ + PUCHAR p_head = NULL; + IPOIB_ENTER( IPOIB_DBG_SEND ); + + *pp_mdl = NET_BUFFER_FIRST_MDL(p_net_buffer); + + NdisQueryMdl(*pp_mdl,&p_head,p_mdl_len,NormalPagePriority); + if( ! p_head ) + { + /* Failed to get first buffer. */ + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisQueryMdl failed.\n") ); + return NDIS_STATUS_FAILURE; + } + + if( *p_mdl_len < sizeof(eth_hdr_t) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("First buffer in packet smaller than eth_hdr_t: %d.\n", + *p_mdl_len) ); + return NDIS_STATUS_BUFFER_TOO_SHORT; + } + + *pp_eth_hdr = (eth_hdr_t*)(p_head + NET_BUFFER_CURRENT_MDL_OFFSET(p_net_buffer)); + + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_SEND, + ("Ethernet header:\n" + "\tsrc MAC: %02X-%02X-%02X-%02X-%02X-%02X\n" + "\tdst MAC: %02X-%02X-%02X-%02X-%02X-%02X\n" + "\tprotocol type: %04X\n", + (*pp_eth_hdr)->src.addr[0], (*pp_eth_hdr)->src.addr[1], + (*pp_eth_hdr)->src.addr[2], (*pp_eth_hdr)->src.addr[3], + (*pp_eth_hdr)->src.addr[4], (*pp_eth_hdr)->src.addr[5], + (*pp_eth_hdr)->dst.addr[0], (*pp_eth_hdr)->dst.addr[1], + (*pp_eth_hdr)->dst.addr[2], (*pp_eth_hdr)->dst.addr[3], + (*pp_eth_hdr)->dst.addr[4], (*pp_eth_hdr)->dst.addr[5], + cl_ntoh16( (*pp_eth_hdr)->type )) ); + + return NDIS_STATUS_SUCCESS; +} + + +#if !IPOIB_USE_DMA +/* Send using the MDL's page information rather than the SGL. */ +static ib_api_status_t +__send_gen( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc ) +{ + uint32_t i, j = 1; + ULONG offset; + MDL *p_mdl; + UINT num_pages, tot_len; + ULONG buf_len; + PPFN_NUMBER page_array; + boolean_t hdr_done = FALSE; + ib_api_status_t status; + PNET_BUFFER p_net_buf; + + IPOIB_ENTER( IPOIB_DBG_SEND ); + p_net_buf = NET_BUFFER_LIST_FIRST_NB(p_desc->p_netbuf_list); + NdisQueryBuffer( p_net_buf, &num_pages, NULL, &p_mdl, + &tot_len ); + + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("No buffers associated with packet.\n") ); + return IB_ERROR; + } + + /* Remember that one of the DS entries is reserved for the IPoIB header. */ + if( num_pages >= MAX_SEND_SGE ) + { + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_SEND, + ("Too many buffers to fit in WR ds_array. Copying data.\n") ); + status = __send_copy( p_port, p_desc ); + IPOIB_EXIT( IPOIB_DBG_SEND ); + return status; + } + + CL_ASSERT( tot_len > sizeof(eth_hdr_t) ); + CL_ASSERT( tot_len <= p_port->p_adapter->params.xfer_block_size ); + /* + * Assume that the ethernet header is always fully contained + * in the first page of the first MDL. This makes for much + * simpler code. + */ + offset = MmGetMdlByteOffset( p_mdl ) + sizeof(eth_hdr_t); + CL_ASSERT( offset <= PAGE_SIZE ); + + while( tot_len ) + { + buf_len = MmGetMdlByteCount( p_mdl ); + page_array = MmGetMdlPfnArray( p_mdl ); + CL_ASSERT( page_array ); + i = 0; + if( !hdr_done ) + { + CL_ASSERT( buf_len >= sizeof(eth_hdr_t) ); + /* Skip the ethernet header. */ + buf_len -= sizeof(eth_hdr_t); + CL_ASSERT( buf_len <= p_port->p_adapter->params.payload_mtu ); + if( buf_len ) + { + /* The ethernet header is a subset of this MDL. */ + CL_ASSERT( i == 0 ); + if( offset < PAGE_SIZE ) + { + p_desc->send_wr[0].local_ds[j].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[0].local_ds[j].vaddr = (page_array[i] << PAGE_SHIFT); + /* Add the byte offset since we're on the 1st page. */ + p_desc->send_wr[0].local_ds[j].vaddr += offset; + if( offset + buf_len > PAGE_SIZE ) + { + p_desc->send_wr[0].local_ds[j].length = PAGE_SIZE - offset; + buf_len -= p_desc->send_wr[0].local_ds[j].length; + } + else + { + p_desc->send_wr[0].local_ds[j].length = buf_len; + buf_len = 0; + } + /* This data segment is done. Move to the next. */ + j++; + } + /* This page is done. Move to the next. */ + i++; + } + /* Done handling the ethernet header. */ + hdr_done = TRUE; + } + + /* Finish this MDL */ + while( buf_len ) + { + p_desc->send_wr[0].local_ds[j].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[0].local_ds[j].vaddr = (page_array[i] << PAGE_SHIFT); + /* Add the first page's offset if we're on the first page. */ + if( i == 0 ) + p_desc->send_wr[0].local_ds[j].vaddr += MmGetMdlByteOffset( p_mdl ); + + if( i == 0 && (MmGetMdlByteOffset( p_mdl ) + buf_len) > PAGE_SIZE ) + { + /* Buffers spans pages. */ + p_desc->send_wr[0].local_ds[j].length = + PAGE_SIZE - MmGetMdlByteOffset( p_mdl ); + buf_len -= p_desc->send_wr[0].local_ds[j].length; + /* This page is done. Move to the next. */ + i++; + } + else + { + /* Last page of the buffer. */ + p_desc->send_wr[0].local_ds[j].length = buf_len; + buf_len = 0; + } + /* This data segment is done. Move to the next. */ + j++; + } + + tot_len -= MmGetMdlByteCount( p_mdl ); + if( !tot_len ) + break; + + NdisGetNextBuffer( p_mdl, &p_mdl ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get next buffer.\n") ); + return IB_ERROR; + } + } + + /* Set the number of data segments. */ + p_desc->send_wr[0].wr.num_ds = j; + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return IB_SUCCESS; +} + +#else + + +void +ipoib_process_sg_list1( + IN PDEVICE_OBJECT pDO, + IN PVOID pIrp, + IN PSCATTER_GATHER_LIST p_sgl, + IN PVOID context + ) +{ + int i; + char temp[200]; + for (i = 0 ; i < 1;i++) + temp[i] = 5; +} + + +void +ipoib_process_sg_list( + IN PDEVICE_OBJECT pDO, + IN PVOID pIrp, + IN PSCATTER_GATHER_LIST p_sgl, + IN PVOID context + ) +{ + NDIS_STATUS status; + ipoib_port_t *p_port; + MDL *p_mdl; + eth_hdr_t *p_eth_hdr; + UINT mdl_len; + static ipoib_send_desc_t *p_desc = NULL; + ib_send_wr_t *p_wr_failed; + NET_BUFFER_LIST *p_net_buffer_list; + NET_BUFFER *p_netbuf; + boolean_t from_queue; + ib_api_status_t ib_status; + ULONG complete_flags = 0; + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + UNREFERENCED_PARAMETER(pDO); + UNREFERENCED_PARAMETER(pIrp); + + PERF_DECLARE( SendCopy ); + PERF_DECLARE( BuildSendDesc ); + PERF_DECLARE( GetEthHdr ); + PERF_DECLARE( QueuePacket ); + PERF_DECLARE( SendMgrQueue ); + PERF_DECLARE( PostSend ); + PERF_DECLARE( ProcessFailedSends ); + PERF_DECLARE( GetEndpt ); + + + p_netbuf = (NET_BUFFER*)context; + p_net_buffer_list = (NET_BUFFER_LIST*)IPOIB_NET_BUFFER_LIST_FROM_NETBUFFER(p_netbuf); + p_port = (ipoib_port_t*)IPOIB_PORT_FROM_PACKET(p_net_buffer_list); + NDIS_SET_SEND_COMPLETE_FLAG(complete_flags, NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL); + + + cl_spinlock_acquire( &p_port->send_lock ); + if (p_desc == NULL) { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_SEND, ("Allocating send_desc First Time\n") ); + p_desc = ExAllocatePoolWithTag(NonPagedPool ,sizeof (ipoib_send_desc_t), 'XMXA'); + } + ASSERT(p_desc); + p_desc->p_netbuf_list = p_net_buffer_list; + p_desc->p_endpt = NULL; + p_desc->p_buf = NULL; + p_desc->num_wrs = 1; + + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_SEND, + ("\n*******\nRECEIVED NB= %x with SG= %x\n********\n", p_netbuf, p_sgl) ); + /* Get the ethernet header so we can find the endpoint. */ + cl_perf_start( GetEthHdr ); + status = __send_mgr_get_eth_hdr( + p_netbuf, &p_mdl, &p_eth_hdr, &mdl_len ); + cl_perf_stop( &p_port->p_adapter->perf, GetEthHdr ); + + if( status != NDIS_STATUS_SUCCESS ) + { + cl_perf_start( ProcessFailedSends ); + /* fail net buffer list */ + __process_failed_send( p_port, p_desc, status, complete_flags); + cl_perf_stop( &p_port->p_adapter->perf, ProcessFailedSends ); + goto send_end; + } + //from_queue = (boolean_t)(IPOIB_FROM_QUEUE(p_netbuf) == (void*)1); + from_queue = (boolean_t)(IPOIB_FROM_QUEUE(p_netbuf) != NULL); + if (from_queue) + { + cl_perf_start( GetEndpt ); + status = __endpt_mgr_ref( p_port, p_eth_hdr->dst, &p_desc->p_endpt ); + cl_perf_stop( &p_port->p_adapter->perf, GetEndpt ); + if( status == NDIS_STATUS_PENDING ) + { + IPOIB_FROM_QUEUE(p_netbuf) = p_sgl; + cl_qlist_insert_head( &p_port->send_mgr.pending_list, + IPOIB_LIST_ITEM_FROM_PACKET( p_desc->p_netbuf_list ) ); + goto send_end; + } + else if( status != NDIS_STATUS_SUCCESS ) + { + ASSERT( status == NDIS_STATUS_NO_ROUTE_TO_DESTINATION ); + + if( ETH_IS_MULTICAST( p_eth_hdr->dst.addr ) ) + { + if( ipoib_port_join_mcast( p_port, p_eth_hdr->dst, + IB_MC_REC_STATE_FULL_MEMBER) == IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_SEND, + ("Multicast Mac - trying to join.\n") ); + IPOIB_FROM_QUEUE(p_netbuf) = p_sgl; + cl_qlist_insert_head( &p_port->send_mgr.pending_list, + IPOIB_LIST_ITEM_FROM_PACKET( p_desc->p_netbuf_list ) ); + goto send_end; + } + } + /* + * Complete the send as if we sent it - WHQL tests don't like the + * sends to fail. + */ + cl_perf_start( ProcessFailedSends ); + __process_failed_send( p_port, p_desc, NDIS_STATUS_SUCCESS,complete_flags ); + cl_perf_stop( &p_port->p_adapter->perf, ProcessFailedSends ); + goto send_end; + } + } + else + { + cl_perf_start( SendMgrQueue ); + if ( ETH_IS_MULTICAST( p_eth_hdr->dst.addr ) && + p_eth_hdr->type == ETH_PROT_TYPE_IP && + !ETH_IS_BROADCAST( p_eth_hdr->dst.addr ) ) + { + ip_hdr_t *p_ip_hdr; + uint8_t *p_tmp; + MDL *p_ip_hdr_mdl; + UINT ip_hdr_mdl_len; + + if(mdl_len >= sizeof(ip_hdr_t) + sizeof(eth_hdr_t)) + { + p_ip_hdr = (ip_hdr_t*)(p_eth_hdr + 1); + } + else + { + NdisGetNextMdl(p_mdl,&p_ip_hdr_mdl); + // Extract the ip hdr + if( !p_ip_hdr_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get IP header buffer.\n") ); + goto mc_end; + } + NdisQueryMdl(p_ip_hdr_mdl,&p_tmp,&ip_hdr_mdl_len,NormalPagePriority); + if( !p_tmp ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get IP header.\n") ); + goto mc_end; + } + if( ip_hdr_mdl_len < sizeof(ip_hdr_t) ) + { + /* This buffer is done for. Get the next buffer. */ + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Buffer too small for IP packet.\n") ); + goto mc_end; + } + p_ip_hdr = (ip_hdr_t*)(p_tmp + NET_BUFFER_CURRENT_MDL_OFFSET(p_netbuf)); + p_eth_hdr->dst.addr[1] = ((unsigned char*)&p_ip_hdr->dst_ip)[0] & 0x0f; + p_eth_hdr->dst.addr[3] = ((unsigned char*)&p_ip_hdr->dst_ip)[1]; + } + } +mc_end: + status = __send_mgr_queue( p_port, p_eth_hdr, &p_desc->p_endpt ); + cl_perf_stop( &p_port->p_adapter->perf, SendMgrQueue ); + if( status == NDIS_STATUS_PENDING ) + { + /* Queue net buffer list. */ + cl_perf_start( QueuePacket ); + NET_BUFFER_LIST_NEXT_NBL(p_net_buffer_list) = NULL; + IPOIB_FROM_QUEUE(p_netbuf) = p_sgl; + cl_qlist_insert_tail( &p_port->send_mgr.pending_list, + IPOIB_LIST_ITEM_FROM_PACKET(p_net_buffer_list) ); + cl_perf_stop( &p_port->p_adapter->perf, QueuePacket ); + goto send_end; + } + if( status != NDIS_STATUS_SUCCESS ) + { + ASSERT( status == NDIS_STATUS_NO_ROUTE_TO_DESTINATION ); + /* + * Complete the send as if we sent it - WHQL tests don't like the + * sends to fail. + */ + cl_perf_start( ProcessFailedSends ); + __process_failed_send( p_port, p_desc, NDIS_STATUS_SUCCESS, complete_flags ); + cl_perf_stop( &p_port->p_adapter->perf, ProcessFailedSends ); + goto send_end; + } + } + cl_perf_start( BuildSendDesc ); + status = __build_send_desc( p_port, p_eth_hdr, p_mdl, mdl_len, p_sgl, p_desc ); + cl_perf_stop( &p_port->p_adapter->perf, BuildSendDesc ); + + if( status != NDIS_STATUS_SUCCESS ) + { + cl_perf_start( ProcessFailedSends ); + __process_failed_send( p_port, p_desc, status, complete_flags ); + cl_perf_stop( &p_port->p_adapter->perf, ProcessFailedSends ); + goto send_end; + } + + /* Post the WR. */ + cl_perf_start( PostSend ); + cl_msg_out("sending packet with wr-id =0x%x\n",&p_desc->send_wr[0].wr.wr_id ); + ib_status = p_port->p_adapter->p_ifc->post_send( p_port->ib_mgr.h_qp, &p_desc->send_wr[0].wr, &p_wr_failed ); + cl_perf_stop( &p_port->p_adapter->perf, PostSend ); + if( ib_status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_post_send returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( ib_status )) ); + cl_perf_start( ProcessFailedSends ); + __process_failed_send( p_port, p_desc, NDIS_STATUS_FAILURE, complete_flags ); + cl_perf_stop( &p_port->p_adapter->perf, ProcessFailedSends ); + /* Flag the adapter as hung since posting is busted. */ + p_port->p_adapter->hung = TRUE; + } + cl_atomic_inc( &p_port->send_mgr.depth ); + +send_end: + if (status != NDIS_STATUS_SUCCESS) { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_SEND, + ("Free S/G List: 0x%x.\n", p_sgl) ); + /*NdisMFreeNetBufferSGList( + p_port->p_adapter->NdisMiniportDmaHandle, + p_sgl, + p_netbuf);*/ + + } + + + cl_spinlock_release( &p_port->send_lock ); + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + +static NDIS_STATUS +__send_gen( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc, + IN SCATTER_GATHER_LIST *p_sgl, + IN INT lso_data_index + ) +{ + ib_api_status_t status; + uint32_t i, j = 1; + uint32_t offset = sizeof(eth_hdr_t); + PERF_DECLARE( SendCopy ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + if( !p_sgl ) + { + ASSERT( p_sgl ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get SGL from packet.\n") ); + return NDIS_STATUS_FAILURE; + } + + /* Remember that one of the DS entries is reserved for the IPoIB header. */ + if( ( p_sgl->NumberOfElements >= MAX_SEND_SGE || + p_sgl->Elements[0].Length < sizeof(eth_hdr_t)) ) + { + + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_SEND, + ("Too many buffers %d to fit in WR ds_array[%d] \ + Or buffer[0] length %d < Eth header. Copying data.\n", + p_sgl->NumberOfElements, MAX_SEND_SGE, p_sgl->Elements[0].Length ) ); + status = NDIS_STATUS_RESOURCES; + if( !p_port->p_adapter->params.cm_enabled ) + { + cl_perf_start( SendCopy ); + status = __send_copy( p_port, p_desc ); + cl_perf_stop( &p_port->p_adapter->perf, SendCopy ); + } + IPOIB_EXIT( IPOIB_DBG_SEND ); + return status; + } + + /* + * Skip the ethernet header. It is either the first element, + * or part of it. + */ + i = 0; + if( lso_data_index ) + { /* we have an LSO packet */ + i = lso_data_index; + j = 0; + } + else while( offset ) + { + if( p_sgl->Elements[i].Length <= offset ) + { + offset -= p_sgl->Elements[i++].Length; + } + else + { + p_desc->send_wr[0].local_ds[j].vaddr = + p_sgl->Elements[i].Address.QuadPart + offset; + p_desc->send_wr[0].local_ds[j].length = + p_sgl->Elements[i].Length - offset; + p_desc->send_wr[0].local_ds[j].lkey = p_port->ib_mgr.lkey; + i++; + j++; + break; + } + } + /* Now fill in the rest of the local data segments. */ + while( i < p_sgl->NumberOfElements ) + { + p_desc->send_wr[0].local_ds[j].vaddr = p_sgl->Elements[i].Address.QuadPart; + p_desc->send_wr[0].local_ds[j].length = p_sgl->Elements[i].Length; + p_desc->send_wr[0].local_ds[j].lkey = p_port->ib_mgr.lkey; + i++; + j++; + } + + /* Set the number of data segments. */ + p_desc->send_wr[0].wr.num_ds = j; + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return NDIS_STATUS_SUCCESS; +} +#endif + + +static NDIS_STATUS +__send_mgr_filter_ip( + IN ipoib_port_t* const p_port, + IN const eth_hdr_t* const p_eth_hdr, + IN MDL* p_mdl, + IN size_t buf_len, + IN SCATTER_GATHER_LIST *p_sgl, + IN OUT ipoib_send_desc_t* const p_desc ) +{ + NDIS_STATUS status; + ip_hdr_t *p_ip_hdr; + uint32_t ip_packet_len; + size_t iph_size_in_bytes; + size_t iph_options_size; + + PERF_DECLARE( QueryIp ); + PERF_DECLARE( SendTcp ); + PERF_DECLARE( FilterUdp ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + if( !buf_len ) + { + cl_perf_start( QueryIp ); + NdisGetNextMdl ( p_mdl, &p_mdl ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get IP header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + + NdisQueryMdl(p_mdl, &p_ip_hdr, &buf_len, NormalPagePriority); + if( !p_ip_hdr ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query IP header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + cl_perf_stop( &p_port->p_adapter->perf, QueryIp ); + } + else + { + p_ip_hdr = (ip_hdr_t*)(p_eth_hdr + 1); + } + if( buf_len < sizeof(ip_hdr_t) ) + { + /* This buffer is done for. Get the next buffer. */ + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Buffer too small for IP packet.\n") ); + return NDIS_STATUS_BUFFER_TOO_SHORT; + } + + switch( p_ip_hdr->prot ) + { + case IP_PROT_UDP: + + cl_perf_start( FilterUdp ); + status = __send_mgr_filter_udp( + p_port, p_ip_hdr, p_mdl, (buf_len - sizeof(ip_hdr_t)), p_sgl, p_desc ); + cl_perf_stop( &p_port->p_adapter->perf, FilterUdp ); + if( status == NDIS_STATUS_PENDING ) + { /* not DHCP packet, keep going */ + if( ETH_IS_MULTICAST( p_eth_hdr->dst.addr ) ) + p_desc->send_dir = SEND_UD_QP; + else + p_desc->send_dir = SEND_RC_QP; + break; + } + return status; + + case IP_PROT_TCP: + p_desc->send_dir = SEND_RC_QP; + break; + case IP_PROT_IGMP: + /* + In igmp packet I saw that iph arrive in 2 NDIS_BUFFERs: + 1. iph + 2. ip options + So to get the IGMP packet we need to skip the ip options NDIS_BUFFER + */ + iph_size_in_bytes = (p_ip_hdr->ver_hl & 0xf) * 4; + iph_options_size = iph_size_in_bytes - buf_len; + buf_len -= sizeof(ip_hdr_t);//without ipheader + + /* + Could be a case that arrived igmp packet not from type IGMPv2 , + but IGMPv1 or IGMPv3. + We anyway pass it to __send_mgr_filter_igmp_v2(). + */ + status = + __send_mgr_filter_igmp_v2( p_port, p_ip_hdr, iph_options_size, p_mdl, buf_len ); + if( status != NDIS_STATUS_SUCCESS ) + return status; + + case IP_PROT_ICMP: + p_desc->send_dir = SEND_UD_QP; + default: + break; + } + + if( !p_port->p_adapter->params.cm_enabled ) + { + p_desc->send_dir = SEND_UD_QP; + goto send_gen; + } + else if( endpt_cm_get_state( p_desc->p_endpt ) != IPOIB_CM_CONNECTED ) + { + p_desc->send_dir = SEND_UD_QP; + } + if( p_desc->send_dir == SEND_UD_QP ) + { + ip_packet_len = cl_ntoh16( p_ip_hdr->length ); + if( ip_packet_len > p_port->p_adapter->params.payload_mtu ) + { + //TODO: NDIS60 + #if 0 + status = __send_fragments( p_port, p_desc, (eth_hdr_t* const)p_eth_hdr, + (ip_hdr_t* const)p_ip_hdr, (uint32_t)buf_len, p_mdl ); + return status; + #endif + ASSERT(FALSE); + } + } + +send_gen: + cl_perf_start( SendTcp ); + status = __send_gen( p_port, p_desc, p_sgl, 0 ); + cl_perf_stop( &p_port->p_adapter->perf, SendTcp ); + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return status; +} + +static NDIS_STATUS +__send_mgr_filter_igmp_v2( + IN ipoib_port_t* const p_port, + IN const ip_hdr_t* const p_ip_hdr, + IN size_t iph_options_size, + IN MDL* p_mdl, + IN size_t buf_len ) +{ + igmp_v2_hdr_t *p_igmp_v2_hdr = NULL; + NDIS_STATUS endpt_status; + ipoib_endpt_t* p_endpt = NULL; + mac_addr_t fake_mcast_mac; + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_MCAST, + ("buf_len = %d,iph_options_size = %d\n",(int)buf_len,(int)iph_options_size ) ); + + if( !buf_len ) + { + // To get the IGMP packet we need to skip the ip options NDIS_BUFFER (if exists) + while ( iph_options_size ) + { + NdisGetNextMdl( p_mdl, &p_mdl ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get IGMPv2 header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + NdisQueryMdl( p_mdl, &p_igmp_v2_hdr, &buf_len, NormalPagePriority ); + if( !p_igmp_v2_hdr ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query IGMPv2 header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + iph_options_size-=buf_len; + } + + NdisGetNextMdl( p_mdl, &p_mdl ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get IGMPv2 header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + NdisQueryMdl( p_mdl, &p_igmp_v2_hdr, &buf_len, NormalPagePriority ); + if( !p_igmp_v2_hdr ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query IGMPv2 header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + } + else + { + /* assuming ip header and options are in the same packet */ + p_igmp_v2_hdr = GetIpPayloadPtr(p_ip_hdr); + } + /* Get the IGMP header length. */ + if( buf_len < sizeof(igmp_v2_hdr_t) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Buffer not large enough for IGMPv2 packet.\n") ); + return NDIS_STATUS_BUFFER_TOO_SHORT; + } + + // build fake mac from igmp packet group address + fake_mcast_mac.addr[0] = 1; + fake_mcast_mac.addr[1] = ((unsigned char*)&p_igmp_v2_hdr->group_address)[0] & 0x0f; + fake_mcast_mac.addr[2] = 0x5E; + fake_mcast_mac.addr[3] = ((unsigned char*)&p_igmp_v2_hdr->group_address)[1]; + fake_mcast_mac.addr[4] = ((unsigned char*)&p_igmp_v2_hdr->group_address)[2]; + fake_mcast_mac.addr[5] = ((unsigned char*)&p_igmp_v2_hdr->group_address)[3]; + + switch ( p_igmp_v2_hdr->type ) + { + case IGMP_V2_MEMBERSHIP_REPORT: + /* + This mean that some body open listener on this group + Change type of mcast endpt to SEND_RECV endpt. So mcast garbage collector + will not delete this mcast endpt. + */ + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_MCAST, + ("Catched IGMP_V2_MEMBERSHIP_REPORT message\n") ); + endpt_status = __endpt_mgr_ref( p_port, fake_mcast_mac, &p_endpt ); + if ( p_endpt ) + { + cl_obj_lock( &p_port->obj ); + p_endpt->is_mcast_listener = TRUE; + cl_obj_unlock( &p_port->obj ); + ipoib_endpt_deref( p_endpt ); + } + break; + + case IGMP_V2_LEAVE_GROUP: + /* + This mean that somebody CLOSE listener on this group . + Change type of mcast endpt to SEND_ONLY endpt. So mcast + garbage collector will delete this mcast endpt next time. + */ + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_MCAST, + ("Catched IGMP_V2_LEAVE_GROUP message\n") ); + endpt_status = __endpt_mgr_ref( p_port, fake_mcast_mac, &p_endpt ); + if ( p_endpt ) + { + cl_obj_lock( &p_port->obj ); + p_endpt->is_mcast_listener = FALSE; + p_endpt->is_in_use = FALSE; + cl_obj_unlock( &p_port->obj ); + ipoib_endpt_deref( p_endpt ); + } + + __port_do_mcast_garbage(p_port); + + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_MCAST, + ("Send Unknown IGMP message: 0x%x \n", p_igmp_v2_hdr->type ) ); + break; + } + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return NDIS_STATUS_SUCCESS; +} + +static NDIS_STATUS +__send_mgr_filter_udp( + IN ipoib_port_t* const p_port, + IN const ip_hdr_t* const p_ip_hdr, + IN MDL* p_mdl, + IN size_t buf_len, + IN SCATTER_GATHER_LIST *p_sgl, + IN OUT ipoib_send_desc_t* const p_desc ) +{ + ib_api_status_t status; + udp_hdr_t *p_udp_hdr; + PERF_DECLARE( QueryUdp ); + PERF_DECLARE( SendUdp ); + PERF_DECLARE( FilterDhcp ); + //TODO NDIS60 remove this param + UNUSED_PARAM(p_sgl); + IPOIB_ENTER( IPOIB_DBG_SEND ); + + if( !buf_len ) + { + cl_perf_start( QueryUdp ); + NdisGetNextMdl( p_mdl, &p_mdl ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get UDP header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + NdisQueryMdl( p_mdl, &p_udp_hdr, &buf_len, NormalPagePriority ); + if( !p_udp_hdr ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query UDP header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + cl_perf_stop( &p_port->p_adapter->perf, QueryUdp ); + } + else + { + p_udp_hdr = (udp_hdr_t*)GetIpPayloadPtr(p_ip_hdr); + } + /* Get the UDP header and check the destination port numbers. */ + + if (p_ip_hdr->offset > 0) { + /* This is a fragmented part of UDP packet + * Only first packet will contain UDP header in such case + * So, return if offset > 0 + */ + return NDIS_STATUS_PENDING; + } + + if( buf_len < sizeof(udp_hdr_t) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Buffer not large enough for UDP packet.\n") ); + return NDIS_STATUS_BUFFER_TOO_SHORT; + } + + if( (p_udp_hdr->src_port != DHCP_PORT_CLIENT || + p_udp_hdr->dst_port != DHCP_PORT_SERVER) && + (p_udp_hdr->src_port != DHCP_PORT_SERVER || + p_udp_hdr->dst_port != DHCP_PORT_CLIENT) ) + { + /* Not a DHCP packet. */ + return NDIS_STATUS_PENDING; + } + + buf_len -= sizeof(udp_hdr_t); + + /* Allocate our scratch buffer. */ + p_desc->p_buf = (send_buf_t*) + ExAllocateFromNPagedLookasideList( &p_port->buf_mgr.send_buf_list ); + if( !p_desc->p_buf ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query DHCP packet buffer.\n") ); + return NDIS_STATUS_RESOURCES; + } + /* Copy the IP and UDP headers. */ + cl_memcpy( &p_desc->p_buf->ip.hdr, p_ip_hdr , sizeof(ip_hdr_t) ); + cl_memcpy( + &p_desc->p_buf->ip.prot.udp.hdr, p_udp_hdr, sizeof(udp_hdr_t) ); + + cl_perf_start( FilterDhcp ); + status = __send_mgr_filter_dhcp( + p_port, p_udp_hdr, p_mdl, buf_len, p_desc ); + cl_perf_stop( &p_port->p_adapter->perf, FilterDhcp ); + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return status; +} + +unsigned short ipchksum(unsigned short *ip, int len) +{ + unsigned long sum = 0; + + len >>= 1; + while (len--) { + sum += *(ip++); + if (sum > 0xFFFF) + sum -= 0xFFFF; + } + return (unsigned short)((~sum) & 0x0000FFFF); +} + +static NDIS_STATUS +__send_mgr_filter_dhcp( + IN ipoib_port_t* const p_port, + IN const udp_hdr_t* const p_udp_hdr, + IN NDIS_BUFFER* p_mdl, + IN size_t buf_len, + IN OUT ipoib_send_desc_t* const p_desc ) +{ + dhcp_pkt_t *p_dhcp; + dhcp_pkt_t *p_ib_dhcp; + uint8_t *p_option, *p_cid = NULL; + uint8_t msg = 0; + size_t len; + ib_gid_t gid; + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + if( !buf_len ) + { + NdisGetNextMdl( p_mdl, &p_mdl ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get DHCP buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + NdisQueryMdl( p_mdl, &p_dhcp, &buf_len, NormalPagePriority ); + if( !p_dhcp ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query DHCP buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + } + else + { + p_dhcp = (dhcp_pkt_t*)(p_udp_hdr + 1); + } + + if( buf_len < DHCP_MIN_SIZE ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Buffer not large enough for DHCP packet.\n") ); + return NDIS_STATUS_BUFFER_TOO_SHORT; + } + + p_ib_dhcp = &p_desc->p_buf->ip.prot.udp.dhcp; + cl_memcpy( p_ib_dhcp, p_dhcp, buf_len ); + + /* Now scan through the options looking for the client identifier. */ + p_option = &p_ib_dhcp->options[4]; + while( *p_option != DHCP_OPT_END && p_option < &p_ib_dhcp->options[312] ) + { + switch( *p_option ) + { + case DHCP_OPT_PAD: + p_option++; + break; + + case DHCP_OPT_MSG: + msg = p_option[2]; + p_option += 3; + break; + + case DHCP_OPT_CLIENT_ID: + p_cid = p_option; + /* Fall through. */ + + default: + /* + * All other options have a length byte following the option code. + * Offset by the length to get to the next option. + */ + p_option += (p_option[1] + 2); + } + } + + switch( msg ) + { + /* Client messages */ + case DHCPDISCOVER: + case DHCPREQUEST: + p_ib_dhcp->flags |= DHCP_FLAGS_BROADCAST; + /* Fall through */ + case DHCPDECLINE: + case DHCPRELEASE: + case DHCPINFORM: + /* Fix up the client identifier option */ + if( p_cid ) + { + /* do we need to replace it ? len eq ETH MAC sz 'and' MAC is mine */ + if( p_cid[1] == HW_ADDR_LEN+1 && !cl_memcmp( &p_cid[3], + &p_port->p_adapter->params.conf_mac.addr, HW_ADDR_LEN ) ) + { + /* Make sure there's room to extend it. 23 is the size of + * the CID option for IPoIB. + */ + if( buf_len + 23 - p_cid[1] > sizeof(dhcp_pkt_t) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Can't convert CID to IPoIB format.\n") ); + return NDIS_STATUS_RESOURCES; + } + /* Move the existing options down, and add a new CID option */ + len = p_option - ( p_cid + p_cid[1] + 2 ); + p_option = p_cid + p_cid[1] + 2; + RtlMoveMemory( p_cid, p_option, len ); + + p_cid += len; + p_cid[0] = DHCP_OPT_CLIENT_ID; + p_cid[1] = 21; + p_cid[2] = DHCP_HW_TYPE_IB; + } + else + { + p_cid[2] = DHCP_HW_TYPE_IB; + } + } + else + { + /* + * Make sure there's room to extend it. 23 is the size of + * the CID option for IPoIB. + */ + if( buf_len + 23 > sizeof(dhcp_pkt_t) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Can't convert CID to IPoIB format.\n") ); + return NDIS_STATUS_RESOURCES; + } + + p_cid = p_option; + p_option = p_cid + 23; + p_option[0] = DHCP_OPT_END; + p_cid[0] = DHCP_OPT_CLIENT_ID; + p_cid[1] = 21; + p_cid[2] = DHCP_HW_TYPE_IB; + } + + CL_ASSERT( p_cid[1] == 21 ); + p_cid[23]= DHCP_OPT_END; + ib_gid_set_default( &gid, p_port->p_adapter->guids.port_guid.guid ); + cl_memcpy( &p_cid[7], &gid, sizeof(ib_gid_t) ); + cl_memcpy( &p_cid[3], &p_port->ib_mgr.qpn, sizeof(p_port->ib_mgr.qpn) ); + p_ib_dhcp->htype = DHCP_HW_TYPE_IB; + + /* update lengths to include any change we made */ + p_desc->p_buf->ip.hdr.length = cl_ntoh16( sizeof(ip_hdr_t) + sizeof(udp_hdr_t) + sizeof(dhcp_pkt_t) ); + p_desc->p_buf->ip.prot.udp.hdr.length = cl_ntoh16( sizeof(udp_hdr_t) + sizeof(dhcp_pkt_t) ); + + /* update crc in ip header */ + //if( !p_port->p_adapter->params.send_chksum_offload ) + //{ //TODO ? + p_desc->p_buf->ip.hdr.chksum = 0; + p_desc->p_buf->ip.hdr.chksum = ipchksum((unsigned short*) &p_desc->p_buf->ip.hdr, sizeof(ip_hdr_t)); + //} TODO ?? + break; + + /* Server messages. */ + case DHCPOFFER: + case DHCPACK: + case DHCPNAK: + /* don't touch server messages */ + break; + + default: + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalide message type.\n") ); + return NDIS_STATUS_INVALID_DATA; + } + /* no chksum for udp */ + p_desc->p_buf->ip.prot.udp.hdr.chksum = 0; + p_desc->send_wr[0].local_ds[1].vaddr = cl_get_physaddr( p_desc->p_buf ); + p_desc->send_wr[0].local_ds[1].length = sizeof(ip_hdr_t) + sizeof(udp_hdr_t) + sizeof(dhcp_pkt_t); + p_desc->send_wr[0].local_ds[1].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[0].wr.num_ds = 2; + p_desc->send_dir = SEND_UD_QP; + IPOIB_EXIT( IPOIB_DBG_SEND ); + return NDIS_STATUS_SUCCESS; +} + + +static NDIS_STATUS +__send_mgr_filter_arp( + IN ipoib_port_t* const p_port, + IN const eth_hdr_t* const p_eth_hdr, + IN MDL* p_mdl, + IN size_t buf_len, + IN OUT ipoib_send_desc_t* const p_desc ) +{ + arp_pkt_t *p_arp; + ipoib_arp_pkt_t *p_ib_arp; + NDIS_STATUS status; + mac_addr_t null_hw = {0}; + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + if( !buf_len ) + { + NdisGetNextMdl( p_mdl, &p_mdl ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get ARP buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + NdisQueryMdl( p_mdl, &p_arp, &buf_len, NormalPagePriority ); + if( !p_arp ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get query ARP buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + } + else + { + p_arp = (arp_pkt_t*)(p_eth_hdr + 1); + } + + /* Single buffer ARP packet. */ + if( buf_len < sizeof(arp_pkt_t) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Buffer too short for ARP.\n") ); + return NDIS_STATUS_BUFFER_TOO_SHORT; + } + + if( p_arp->prot_type != ETH_PROT_TYPE_IP ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Unsupported protocol type.\n") ); + return NDIS_STATUS_INVALID_DATA; + } + + /* Allocate our scratch buffer. */ + p_desc->p_buf = (send_buf_t*) + NdisAllocateFromNPagedLookasideList( &p_port->buf_mgr.send_buf_list ); + if( !p_desc->p_buf ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query ARP packet buffer.\n") ); + return NDIS_STATUS_RESOURCES; + } + p_ib_arp = (ipoib_arp_pkt_t*)p_desc->p_buf; + + /* Convert the ARP payload. */ + p_ib_arp->hw_type = ARP_HW_TYPE_IB; + p_ib_arp->prot_type = p_arp->prot_type; + p_ib_arp->hw_size = sizeof(ipoib_hw_addr_t); + p_ib_arp->prot_size = p_arp->prot_size; + p_ib_arp->op = p_arp->op; + + ipoib_addr_set_qpn( &p_ib_arp->src_hw, p_port->ib_mgr.qpn ); +#if 0 + + if( p_port->p_adapter->params.cm_enabled ) + { + ipoib_addr_set_flags( &p_ib_arp->src_hw, IPOIB_CM_FLAG_RC ); + } +#endif + + ib_gid_set_default( &p_ib_arp->src_hw.gid, + p_port->p_adapter->guids.port_guid.guid ); + p_ib_arp->src_ip = p_arp->src_ip; + if( cl_memcmp( &p_arp->dst_hw, &null_hw, sizeof(mac_addr_t) ) ) + { + /* Get the endpoint referenced by the dst_hw address. */ + net32_t qpn = 0; + status = __endpt_mgr_get_gid_qpn( p_port, p_arp->dst_hw, + &p_ib_arp->dst_hw.gid, &qpn ); + if( status != NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed lookup of destination HW address\n") ); + return status; + } + ipoib_addr_set_qpn( &p_ib_arp->dst_hw, qpn ); +#if 0 + if( p_arp->op == ARP_OP_REP && + p_port->p_adapter->params.cm_enabled && + p_desc->p_endpt->cm_flag == IPOIB_CM_FLAG_RC ) + { + cm_state_t cm_state; + cm_state = + ( cm_state_t )InterlockedCompareExchange( (volatile LONG *)&p_desc->p_endpt->conn.state, + IPOIB_CM_CONNECT, IPOIB_CM_DISCONNECTED ); + switch( cm_state ) + { + case IPOIB_CM_DISCONNECTED: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("ARP REPLY pending Endpt[%p] QPN %#x MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + p_desc->p_endpt, + cl_ntoh32( ipoib_addr_get_qpn( &p_ib_arp->dst_hw )), + p_desc->p_endpt->mac.addr[0], p_desc->p_endpt->mac.addr[1], + p_desc->p_endpt->mac.addr[2], p_desc->p_endpt->mac.addr[3], + p_desc->p_endpt->mac.addr[4], p_desc->p_endpt->mac.addr[5] ) ); + ipoib_addr_set_sid( &p_desc->p_endpt->conn.service_id, + ipoib_addr_get_qpn( &p_ib_arp->dst_hw ) ); + + NdisFreeToNPagedLookasideList( + &p_port->buf_mgr.send_buf_list, p_desc->p_buf ); + cl_qlist_insert_tail( &p_port->send_mgr.pending_list, + IPOIB_LIST_ITEM_FROM_PACKET( p_desc->p_buf ) ); + NdisInterlockedInsertTailList( &p_port->endpt_mgr.pending_conns, + &p_desc->p_endpt->list_item, + &p_port->endpt_mgr.conn_lock ); + cl_event_signal( &p_port->endpt_mgr.event ); + return NDIS_STATUS_PENDING; + + case IPOIB_CM_CONNECT: + /* queue ARP REP packet until connected */ + NdisFreeToNPagedLookasideList( + &p_port->buf_mgr.send_buf_list, p_desc->p_buf ); + cl_qlist_insert_tail( &p_port->send_mgr.pending_list, + IPOIB_LIST_ITEM_FROM_PACKET( p_desc->p_pkt ) ); + return NDIS_STATUS_PENDING; + default: + break; + } + } +#endif + } + else + { + cl_memclr( &p_ib_arp->dst_hw, sizeof(ipoib_hw_addr_t) ); + } + +#if 0 //DBG + if( p_port->p_adapter->params.cm_enabled ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + (" ARP %s SEND to ENDPT[%p] State: %d flag: %#x, QPN: %#x MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + ( p_ib_arp->op == ARP_OP_REP ? "REP": "REQ"), p_desc->p_endpt, + endpt_cm_get_state( p_desc->p_endpt ), + p_desc->p_endpt->cm_flag, + cl_ntoh32( ipoib_addr_get_qpn( &p_ib_arp->dst_hw )), + p_desc->p_endpt->mac.addr[0], p_desc->p_endpt->mac.addr[1], + p_desc->p_endpt->mac.addr[2], p_desc->p_endpt->mac.addr[3], + p_desc->p_endpt->mac.addr[4], p_desc->p_endpt->mac.addr[5] )); + } +#endif + + p_ib_arp->dst_ip = p_arp->dst_ip; + + p_desc->send_wr[0].local_ds[1].vaddr = cl_get_physaddr( p_ib_arp ); + p_desc->send_wr[0].local_ds[1].length = sizeof(ipoib_arp_pkt_t); + p_desc->send_wr[0].local_ds[1].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[0].wr.num_ds = 2; + p_desc->send_wr[0].wr.p_next = NULL; + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return NDIS_STATUS_SUCCESS; +} + +static inline NDIS_STATUS +__send_mgr_queue( + IN ipoib_port_t* const p_port, + IN eth_hdr_t* const p_eth_hdr, + OUT ipoib_endpt_t** const pp_endpt ) +{ + NDIS_STATUS status; + + PERF_DECLARE( GetEndpt ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + /* Check the send queue and pend the request if not empty. */ + if( cl_qlist_count( &p_port->send_mgr.pending_list ) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_WARNING, IPOIB_DBG_SEND, + ("Pending list not empty.\n") ); + return NDIS_STATUS_PENDING; + } + + /* Check the send queue and pend the request if not empty. */ + if( p_port->send_mgr.depth == p_port->p_adapter->params.sq_depth ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_WARNING, IPOIB_DBG_SEND, + ("No available WQEs.\n") ); + return NDIS_STATUS_PENDING; + } + + cl_perf_start( GetEndpt ); + status = __endpt_mgr_ref( p_port, p_eth_hdr->dst, pp_endpt ); + cl_perf_stop( &p_port->p_adapter->perf, GetEndpt ); + + if( status == NDIS_STATUS_NO_ROUTE_TO_DESTINATION && + ETH_IS_MULTICAST( p_eth_hdr->dst.addr ) ) + { + if( ipoib_port_join_mcast( p_port, p_eth_hdr->dst, + IB_MC_REC_STATE_FULL_MEMBER) == IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_SEND, + ("Multicast Mac - trying to join.\n") ); + return NDIS_STATUS_PENDING; + } + } + else if ( status == NDIS_STATUS_SUCCESS && + ETH_IS_MULTICAST( p_eth_hdr->dst.addr ) && + !ETH_IS_BROADCAST( p_eth_hdr->dst.addr ) ) + { + CL_ASSERT( (*pp_endpt) ); + CL_ASSERT((*pp_endpt)->h_mcast != NULL); + (*pp_endpt)->is_in_use = TRUE; + } + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return status; +} + + +static NDIS_STATUS +__build_send_desc( + IN ipoib_port_t* const p_port, + IN eth_hdr_t* const p_eth_hdr, + IN MDL* const p_mdl, + IN const size_t mdl_len, + IN SCATTER_GATHER_LIST *p_sgl, + IN OUT ipoib_send_desc_t* const p_desc ) +{ + NDIS_STATUS status; + int32_t hdr_idx; + uint32_t mss; + //PVOID* pp_tmp; + PNDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO p_checksum_list_info; + PNDIS_TCP_LARGE_SEND_OFFLOAD_NET_BUFFER_LIST_INFO p_lso_info; + PERF_DECLARE( SendMgrFilter ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + /* Format the send descriptor. */ + p_checksum_list_info = NET_BUFFER_LIST_INFO( p_desc->p_netbuf_list,TcpIpChecksumNetBufferListInfo); + //pp_tmp = &NET_BUFFER_LIST_INFO(p_desc->p_netbuf_list, TcpIpChecksumNetBufferListInfo); + //p_checksum_list_info = ( ) ((PULONG)pp_tmp); + p_lso_info = NET_BUFFER_LIST_INFO( p_desc->p_netbuf_list, TcpLargeSendNetBufferListInfo ); + + /* Format the send descriptor. */ + hdr_idx = cl_atomic_inc( &p_port->hdr_idx ); + hdr_idx &= (p_port->p_adapter->params.sq_depth - 1); + ASSERT( hdr_idx < p_port->p_adapter->params.sq_depth ); + p_port->hdr[hdr_idx].type = p_eth_hdr->type; + p_port->hdr[hdr_idx].resv = 0; + + p_desc->send_wr[0].local_ds[0].vaddr = cl_get_physaddr( &p_port->hdr[hdr_idx] ); + p_desc->send_wr[0].local_ds[0].length = sizeof(ipoib_hdr_t); + p_desc->send_wr[0].local_ds[0].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[0].wr.send_opt = 0; + + //TODO: first check params.lso, and thereafter calculate LSO + if( p_port->p_adapter->params.lso && + (mss = (p_lso_info->LsoV1Transmit.MSS | p_lso_info->LsoV2Transmit.MSS)) ) + { + ASSERT( mss == (p_lso_info->LsoV1Transmit.MSS & p_lso_info->LsoV2Transmit.MSS)); + ASSERT ( (mss & (1<<20)) == mss); + status = __build_lso_desc( p_port, p_desc, mss, p_sgl, hdr_idx, p_lso_info ); + if( status != NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__build_lso_desc returned 0x%08X.\n", status) ); + return status; + } + } + else + { + uint32_t i; + cl_perf_start( SendMgrFilter ); + status = __send_mgr_filter( + p_port, p_eth_hdr, p_mdl, mdl_len,p_sgl, p_desc ); + cl_perf_stop( &p_port->p_adapter->perf, SendMgrFilter ); + if( status != NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__send_mgr_filter returned 0x%08X.\n", status) ); + return status; + } + + if( p_desc->send_dir == SEND_UD_QP ) + { + p_desc->send_qp = p_port->ib_mgr.h_qp; // UD QP + for( i = 0; i < p_desc->num_wrs; i++ ) + { + p_desc->send_wr[i].wr.dgrm.ud.remote_qp = p_desc->p_endpt->qpn; + p_desc->send_wr[i].wr.dgrm.ud.remote_qkey = p_port->ib_mgr.bcast_rec.qkey; + p_desc->send_wr[i].wr.dgrm.ud.h_av = p_desc->p_endpt->h_av; + p_desc->send_wr[i].wr.dgrm.ud.pkey_index = p_port->pkey_index; + p_desc->send_wr[i].wr.dgrm.ud.rsvd = NULL; + p_desc->send_wr[i].wr.send_opt = 0; + + if( p_port->p_adapter->params.send_chksum_offload && + ( p_checksum_list_info->Transmit.IsIPv4 || + p_checksum_list_info->Transmit.IsIPv6 )) + { + // Set transimition checksum offloading + if( p_checksum_list_info->Transmit.IpHeaderChecksum ) + { + p_desc->send_wr[i].wr.send_opt |= IB_SEND_OPT_TX_IP_CSUM; + } + if( p_checksum_list_info->Transmit.TcpChecksum ) + { + p_desc->send_wr[i].wr.send_opt |= IB_SEND_OPT_TX_TCP_UDP_CSUM; + } + } + } + } + else // RC QP + { + CL_ASSERT( p_desc->send_dir == SEND_RC_QP ); + p_desc->send_qp = p_desc->p_endpt->conn.h_work_qp; + } + for( i = 0; i < p_desc->num_wrs; i++ ) + { + p_desc->send_wr[i].wr.wr_type = WR_SEND; + p_desc->send_wr[i].wr.wr_id = 0; + p_desc->send_wr[i].wr.ds_array = &p_desc->send_wr[i].local_ds[0]; + if( i ) + { + p_desc->send_wr[i-1].wr.p_next = &p_desc->send_wr[i].wr; + } + } + //TODO p_net_buf or p_buf +// p_desc->send_wr[p_desc->num_wrs - 1].wr.wr_id = (uintn_t)NET_BUFFER_LIST_FIRST_NB(p_desc->p_netbuf_list ); +//???? IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, ("WR_ID was set to NBL 0x%x \n",p_desc->p_netbuf_list )); + p_desc->send_wr[p_desc->num_wrs - 1].wr.wr_id = (uintn_t)p_desc->p_netbuf_list ; + + p_desc->send_wr[p_desc->num_wrs - 1].wr.send_opt |= IB_SEND_OPT_SIGNALED; + p_desc->send_wr[p_desc->num_wrs - 1].wr.p_next = NULL; + } + + /* Store context in our reserved area of the packet. */ + IPOIB_PORT_FROM_PACKET( p_desc->p_netbuf_list ) = p_port; + IPOIB_ENDPT_FROM_PACKET( p_desc->p_netbuf_list ) = p_desc->p_endpt; + IPOIB_SEND_FROM_NETBUFFER( NET_BUFFER_LIST_FIRST_NB(p_desc->p_netbuf_list ))= p_desc->p_buf; + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return NDIS_STATUS_SUCCESS; +} + +static NDIS_STATUS +__build_lso_desc( + IN ipoib_port_t* const p_port, + IN OUT ipoib_send_desc_t* const p_desc, + IN ULONG mss, + IN SCATTER_GATHER_LIST *p_sgl, + IN int32_t hdr_idx, + IN PNDIS_TCP_LARGE_SEND_OFFLOAD_NET_BUFFER_LIST_INFO p_lso_info) +{ + NDIS_STATUS status; + LsoData TheLsoData; + UINT IndexOfData = 0; + + PNET_BUFFER FirstBuffer = NET_BUFFER_LIST_FIRST_NB (p_desc->p_netbuf_list); + ULONG PacketLength = NET_BUFFER_DATA_LENGTH(FirstBuffer); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + + + memset(&TheLsoData, 0, sizeof TheLsoData ); + status = GetLsoHeaderSize( + FirstBuffer, + &TheLsoData, + &IndexOfData, + &p_port->hdr[hdr_idx] ); + + if ((status != NDIS_STATUS_SUCCESS ) || + (TheLsoData.FullBuffers != TheLsoData.UsedBuffers)) + { + ASSERT(FALSE); + + IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, ("<-- Throwing this packet\n")); + + if( status == NDIS_STATUS_SUCCESS ) + { + status = NDIS_STATUS_INVALID_PACKET; + } + return status; + } + ASSERT(TheLsoData.LsoHeaderSize> 0); + // Tell NDIS how much we will send. + //PktExt->NdisPacketInfo[TcpLargeSendPacketInfo] = UlongToPtr(PacketLength); + p_lso_info->LsoV1TransmitComplete.TcpPayload = PacketLength; + + p_desc->send_wr[0].wr.dgrm.ud.mss = mss; + p_desc->send_wr[0].wr.dgrm.ud.header = TheLsoData.LsoBuffers[0].pData; + p_desc->send_wr[0].wr.dgrm.ud.hlen = TheLsoData.LsoHeaderSize ;//lso_header_size; + p_desc->send_wr[0].wr.dgrm.ud.remote_qp = p_desc->p_endpt->qpn; + p_desc->send_wr[0].wr.dgrm.ud.remote_qkey = p_port->ib_mgr.bcast_rec.qkey; + p_desc->send_wr[0].wr.dgrm.ud.h_av = p_desc->p_endpt->h_av; + p_desc->send_wr[0].wr.dgrm.ud.pkey_index = p_port->pkey_index; + p_desc->send_wr[0].wr.dgrm.ud.rsvd = NULL; + + //TODO: Should be NBL or p_desc + p_desc->send_wr[0].wr.wr_id = (uintn_t)p_desc->p_netbuf_list; + p_desc->send_wr[0].wr.ds_array = p_desc->send_wr[0].local_ds; + p_desc->send_wr[0].wr.wr_type = WR_LSO; + p_desc->send_wr[0].wr.send_opt = + (IB_SEND_OPT_TX_IP_CSUM | IB_SEND_OPT_TX_TCP_UDP_CSUM) | IB_SEND_OPT_SIGNALED; + + p_desc->send_wr[0].wr.p_next = NULL; + p_desc->send_qp = p_port->ib_mgr.h_qp; + p_desc->send_dir = SEND_UD_QP; + status = __send_gen(p_port, p_desc, p_sgl, IndexOfData ); + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return status; +} + +static inline void +__process_failed_send( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc, + IN const NDIS_STATUS status, + IN ULONG compl_flags) +{ + IPOIB_ENTER( IPOIB_DBG_SEND ); + + /* Complete the packet. */ + NET_BUFFER_LIST_NEXT_NBL(p_desc->p_netbuf_list) = NULL; + NET_BUFFER_LIST_STATUS(p_desc->p_netbuf_list) = status; + NdisMSendNetBufferListsComplete( p_port->p_adapter->h_adapter, + p_desc->p_netbuf_list, compl_flags ); + ipoib_inc_send_stat( p_port->p_adapter, IP_STAT_ERROR, 0 ); + /* Deref the endpoint. */ + if( p_desc->p_endpt ) + ipoib_endpt_deref( p_desc->p_endpt ); + + if( p_desc->p_buf ) + { + NdisFreeToNPagedLookasideList( + &p_port->buf_mgr.send_buf_list, p_desc->p_buf ); + } + + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + +// max number of physical fragmented buffers +#define MAX_PHYS_BUF_FRAG_ELEMENTS 0x29 +#define MP_FRAG_ELEMENT SCATTER_GATHER_ELEMENT +#define PMP_FRAG_ELEMENT PSCATTER_GATHER_ELEMENT + + +typedef struct _MP_FRAG_LIST { + ULONG NumberOfElements; + ULONG_PTR Reserved; + SCATTER_GATHER_ELEMENT Elements[MAX_PHYS_BUF_FRAG_ELEMENTS]; +} MP_FRAG_LIST, *PMP_FRAG_LIST; + + +void +CreateFragList( + ULONG PhysBufCount, + PNET_BUFFER NetBuff, + ULONG PacketLength, + PMP_FRAG_LIST pFragList + ) +{ +// ETH_ENTER(ETH_SND); + + ULONG i = 0; + int j=0; + + UINT buf_len = NET_BUFFER_DATA_LENGTH(NetBuff); + PMDL pMdl = NET_BUFFER_CURRENT_MDL(NetBuff); + + ULONG CurrentMdlDataOffset = NET_BUFFER_CURRENT_MDL_OFFSET(NetBuff); + ASSERT(MmGetMdlByteCount(pMdl) >= CurrentMdlDataOffset); + + + ASSERT(NetBuff != NULL); + ASSERT(PhysBufCount <= MAX_PHYS_BUF_FRAG_ELEMENTS); + + ASSERT(buf_len > 0); + UNREFERENCED_PARAMETER(PacketLength); + + +// ETH_PRINT(TRACE_LEVEL_VERBOSE, ETH_SND, "CreateFragList: NetBuff %p, Length =0x%x\n", NetBuff, buf_len); + + while ( (pMdl != NULL) && (buf_len != 0) ) + { + PPFN_NUMBER page_array = MmGetMdlPfnArray(pMdl); + int MdlBufCount = ADDRESS_AND_SIZE_TO_SPAN_PAGES(MmGetMdlVirtualAddress(pMdl), MmGetMdlByteCount(pMdl)); + + ULONG offset = MmGetMdlByteOffset(pMdl) + CurrentMdlDataOffset ; + ULONG MdlBytesCount = MmGetMdlByteCount(pMdl) - CurrentMdlDataOffset; + CurrentMdlDataOffset = 0; + + if( MdlBytesCount == 0 ) + { + pMdl = pMdl->Next; + continue; + } + + ASSERT( (buf_len > 0) && (MdlBytesCount > 0) ); + +// ETH_PRINT(TRACE_LEVEL_VERBOSE, ETH_SND, "CreateFragList: pMdl=%p, MdlBytesCount=x%x, MdlBufCount=0x%x\n", pMdl, MdlBytesCount, MdlBufCount); + + if (MdlBytesCount > 0) + { + if( buf_len > MdlBytesCount) + { + buf_len -= MdlBytesCount; + } + else + { + MdlBytesCount = buf_len; + buf_len = 0; + } + // + // In some cases the mdlcount is greater than needed and in the last page + // there is 0 bytes + // + for (j=0; ((j< MdlBufCount) && (MdlBytesCount > 0)); j++) + { + ASSERT(MdlBytesCount > 0); + if (j ==0 ) + { + // + // First page + // + ULONG64 ul64PageNum = page_array[j]; + pFragList->Elements[i].Address.QuadPart = (ul64PageNum << PAGE_SHIFT)+ offset; + if( offset + MdlBytesCount > PAGE_SIZE ) + { + // + // the data slides behind the page boundry + // + ASSERT(PAGE_SIZE > offset); + pFragList->Elements[i].Length = PAGE_SIZE - offset; + MdlBytesCount -= pFragList->Elements[i].Length; + } + else + { + // + // All the data is hold in one page + // + pFragList->Elements[i].Length = MdlBytesCount; + MdlBytesCount = 0; + } + +// ETH_PRINT(TRACE_LEVEL_VERBOSE, ETH_SND, "CreateFragList: j == 0, MdlBytesCount=x%x, i = %d, element.length=0x%x \n", MdlBytesCount, i, pFragList->Elements[i].Length); + } + else + { + if (page_array[j] == (page_array[j-1] + 1)) + { + + ULONG size = min(PAGE_SIZE, MdlBytesCount); + i -= 1; + pFragList->Elements[i].Length += size; + MdlBytesCount -= size; + } + else + { + // + // Not first page. so the data always start at the begining of the page + // + ULONG64 ul64PageNum = page_array[j]; + pFragList->Elements[i].Address.QuadPart = (ul64PageNum << PAGE_SHIFT); + pFragList->Elements[i].Length = min(PAGE_SIZE, MdlBytesCount); + MdlBytesCount -= pFragList->Elements[i].Length; + } + +// ETH_PRINT(TRACE_LEVEL_VERBOSE, ETH_SND, "CreateFragList: j != 0, MdlBytesCount=x%x, i = %d, element.length=0x%x \n", MdlBytesCount, i, pFragList->Elements[i].Length); + } + i++; + ASSERT(i <= MAX_PHYS_BUF_FRAG_ELEMENTS); + } + } + + pMdl = pMdl->Next; + } + + if (buf_len != 0) + { + // + // In some cases the size in MDL isn't equal to the buffer size. In such + // a case we need to add the rest of packet to last chunk + // + ASSERT(i > 0); // To prevent array underflow + pFragList->Elements[i-1].Length += buf_len; +// ETH_PRINT(TRACE_LEVEL_ERROR, ETH_SND, "CreateFragList: buf_len != 0, i = %d, element.length=0x%x \n", i -1, pFragList->Elements[i-1].Length); + } + + ASSERT(i <= PhysBufCount); + pFragList->NumberOfElements = i; + +#ifdef DBG +{ + ULONG size = 0; + for (i = 0; i < pFragList->NumberOfElements; ++i) + { + size += pFragList->Elements[i].Length; + } + ASSERT(size == PacketLength); +} +#endif + +// ETH_EXIT(ETH_SND); +} + + + + +void +ipoib_port_send( + IN ipoib_port_t* const p_port, + IN NET_BUFFER_LIST *p_net_buffer_list, + IN ULONG send_flags) +{ + NDIS_STATUS status; + PNET_BUFFER p_netbuf; + UINT buf_cnt = 0; + //ipoib_send_desc_t *p_desc; + ULONG send_complete_flags = 0; + KIRQL old_irql; + PVOID p_sgl; + + PERF_DECLARE( GetEthHdr ); + PERF_DECLARE( BuildSendDesc ); + PERF_DECLARE( QueuePacket ); + PERF_DECLARE( SendMgrQueue ); + PERF_DECLARE( PostSend ); + PERF_DECLARE( ProcessFailedSends ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + if (NDIS_TEST_SEND_AT_DISPATCH_LEVEL(send_flags)) + { + //TODO Tzachid: make an assert here to validate your IRQL + //ASSERT (KeGetCurrentIRQL() == DISPATCH_LEVEL); + NDIS_SET_SEND_COMPLETE_FLAG(send_complete_flags, NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL); + } else { + //ASSERT (KeGetCurrentIRQL() == PASSIVE_LEVEL); + } + + cl_obj_lock( &p_port->obj ); + if( p_port->state != IB_QPS_RTS ) + { + + + cl_obj_unlock( &p_port->obj ); + + + NET_BUFFER_LIST_STATUS(p_net_buffer_list) = NDIS_STATUS_FAILURE; + NET_BUFFER_LIST_NEXT_NBL(p_net_buffer_list) = NULL; + ipoib_inc_send_stat( p_port->p_adapter, IP_STAT_DROPPED, 0 ); + + NdisMSendNetBufferListsComplete( + p_port->p_adapter->h_adapter, + p_net_buffer_list, + send_complete_flags); + + return; + } + cl_obj_unlock( &p_port->obj ); + + //IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + // ("Processing netbuffer list: %x\n", p_net_buffer_list)); + for (p_netbuf = NET_BUFFER_LIST_FIRST_NB(p_net_buffer_list); + p_netbuf != NULL; + p_netbuf = NET_BUFFER_NEXT_NB(p_netbuf)) + { + IPOIB_PORT_FROM_PACKET(p_net_buffer_list) = p_port; + IPOIB_NET_BUFFER_LIST_FROM_NETBUFFER(p_netbuf) = p_net_buffer_list; + IPOIB_FROM_QUEUE(p_netbuf) = NULL; + /*p_desc = &p_port->send_mgr.desc; + p_desc->p_buf = p_netbuf; + p_desc->p_endpt = NULL; + p_desc->p_buf = NULL; + p_desc->send_qp = NULL; + p_desc->num_wrs = 1; + p_desc->send_dir = 0;*/ + + old_irql = KeGetCurrentIrql(); + if (old_irql < DISPATCH_LEVEL) + { + KeRaiseIrqlToDpcLevel(); + } + ++buf_cnt; + //IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + // ("[%d] Netbuf = %x\n",buf_cnt, p_netbuf) ); + if (cl_is_item_in_qlist( &p_port->send_mgr.pending_list, + IPOIB_LIST_ITEM_FROM_PACKET( p_net_buffer_list ))) { + p_sgl = IPOIB_FROM_QUEUE(p_netbuf); + //IPOIB_FROM_QUEUE(p_net_buffer) = (void*)1; + ASSERT (p_sgl); + //IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + // ("[%d] FROM_QUEUE Netbuf = %x, found SGL = %x\n",buf_cnt, p_netbuf, p_sgl) ); + status = NDIS_STATUS_SUCCESS; + } else { + + + CHAR *pTemp = ExAllocatePoolWithTag(NonPagedPool , p_port->p_adapter->sg_list_size, 'abcd'); + CL_ASSERT(pTemp != NULL); + status = NDIS_STATUS_SUCCESS; + p_sgl = pTemp; + CreateFragList(NdisQueryNetBufferPhysicalCount(p_netbuf), p_netbuf, NET_BUFFER_DATA_LENGTH(p_netbuf), p_sgl); + IPOIB_FROM_QUEUE(p_netbuf) = NULL; + /*IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("[%d] Allocation from scratch: Netbuf = %x, found SGL = %x, PhysBufCnt=%ld, NB LEN = %ld, sg_list_size=%ld\n", + buf_cnt, p_netbuf, p_sgl,NdisQueryNetBufferPhysicalCount(p_netbuf) , + NET_BUFFER_DATA_LENGTH(p_netbuf),p_port->p_adapter->sg_list_size) ); + */ + ipoib_process_sg_list(NULL, NULL, p_sgl, p_netbuf); + status = NDIS_STATUS_SUCCESS; + +#if 0 + status = NdisMAllocateNetBufferSGList( + p_port->p_adapter->NdisMiniportDmaHandle, + p_netbuf, + p_netbuf, + NDIS_SG_LIST_WRITE_TO_DEVICE, + NULL, + 0); +#endif + } + KeLowerIrql (old_irql); + + if( status != NDIS_STATUS_SUCCESS ) + { + /* fail net buffer list */ + NET_BUFFER_LIST_NEXT_NBL(p_net_buffer_list) = NULL; + NET_BUFFER_LIST_STATUS(p_net_buffer_list) = NDIS_STATUS_RESOURCES; + NdisMSendNetBufferListsComplete( + p_port->p_adapter->h_adapter, + p_net_buffer_list, + send_complete_flags); + break; + } + ASSERT(buf_cnt); + IPOIB_GET_NET_BUFFER_LIST_REF_COUNT(p_net_buffer_list) = (PVOID)(ULONG_PTR)buf_cnt; + } + + /* Post the WR. * + cl_perf_start( PostSend ); + ib_status = p_port->p_adapter->p_ifc->post_send( p_desc->send_qp, &p_desc->send_wr[0].wr, &p_wr_failed ); + cl_perf_stop( &p_port->p_adapter->perf, PostSend ); + if( ib_status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_post_send returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( ib_status )) ); + cl_perf_start( ProcessFailedSends ); + __process_failed_send( p_port, p_desc, NDIS_STATUS_FAILURE ); + cl_perf_stop( &p_port->p_adapter->perf, ProcessFailedSends ); + * Flag the adapter as hung since posting is busted. * + p_port->p_adapter->hung = TRUE; + continue; + } + + cl_atomic_inc( &p_port->send_mgr.depth ); + } + cl_spinlock_release( &p_port->send_lock ); + + IPOIB_EXIT( IPOIB_DBG_SEND );*/ +} + + +void +ipoib_port_resume( + IN ipoib_port_t* const p_port, + IN boolean_t b_pending ) +{ + NDIS_STATUS status; + cl_list_item_t *p_item; + NET_BUFFER *p_net_buffer; + NET_BUFFER_LIST *p_net_buffer_list; + //ipoib_send_desc_t *p_desc; + KIRQL old_irql; + UINT buf_cnt = 0; + NET_BUFFER_LIST *p_prev_nbl = NULL; + PVOID p_sgl; + static PVOID p_prev_sgl = NULL; + + PERF_DECLARE( GetEndpt ); + PERF_DECLARE( BuildSendDesc ); + PERF_DECLARE( ProcessFailedSends ); + PERF_DECLARE( PostSend ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + cl_obj_lock( &p_port->obj ); + if( p_port->state != IB_QPS_RTS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_WARNING, IPOIB_DBG_SEND, + ("Invalid state - Aborting.\n") ); + cl_obj_unlock( &p_port->obj ); + return; + } + cl_obj_unlock( &p_port->obj ); + +//TODO NDIS60 +////?????????????? cl_spinlock_acquire( &p_port->send_lock ); + + for( p_item = cl_qlist_head( &p_port->send_mgr.pending_list ); + p_item != cl_qlist_end( &p_port->send_mgr.pending_list ); + p_item = cl_qlist_head( &p_port->send_mgr.pending_list ) ) + { + + + /* Check the send queue and pend the request if not empty. */ + if( p_port->send_mgr.depth == p_port->p_adapter->params.sq_depth ) + { + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_SEND, + ("No available WQEs.\n") ); + break; + } + + p_net_buffer_list = IPOIB_PACKET_FROM_LIST_ITEM( + cl_qlist_remove_head( &p_port->send_mgr.pending_list ) ); + if (p_prev_nbl == p_net_buffer_list) { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("TRYING TO PROCESS ONCE AGAIN, EXITING: %x\n", p_net_buffer_list)); + break; //TODO more sophisticated mechanism to avoid starvation + } + old_irql = KeGetCurrentIrql(); + if (old_irql < DISPATCH_LEVEL) + { + KeRaiseIrqlToDpcLevel(); + } + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Processing netbuffer list from queue: %x\n", p_net_buffer_list)); + + for( p_net_buffer = NET_BUFFER_LIST_FIRST_NB(p_net_buffer_list); + p_net_buffer != NULL; + p_net_buffer = NET_BUFFER_NEXT_NB(p_net_buffer), buf_cnt++) + { + + + p_sgl = IPOIB_FROM_QUEUE(p_net_buffer); + //IPOIB_FROM_QUEUE(p_net_buffer) = (void*)1; + ASSERT (p_sgl); + IPOIB_PORT_FROM_PACKET(p_net_buffer_list) = p_port; + IPOIB_NET_BUFFER_LIST_FROM_NETBUFFER(p_net_buffer) = p_net_buffer_list; + /*p_desc = &p_port->send_mgr.desc; + p_desc->p_buf = p_net_buffer; + p_desc->p_endpt = NULL; + p_desc->p_buf = NULL; + p_desc->send_qp = NULL; + p_desc->num_wrs = 1; + p_desc->send_dir = 0;*/ + + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("[%d] Netbuf = %x, p_sgl = %x\n",buf_cnt, p_net_buffer, p_sgl) ); + ASSERT(p_sgl); + if (p_sgl != (void*) 1) { + ipoib_process_sg_list(NULL, NULL, p_sgl, p_net_buffer); + status = NDIS_STATUS_SUCCESS; + } + else { + ASSERT(FALSE); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Getting strange flow\n") ); + NdisMFreeNetBufferSGList( + p_port->p_adapter->NdisMiniportDmaHandle, + p_sgl, + p_net_buffer ); + status = NdisMAllocateNetBufferSGList( + p_port->p_adapter->NdisMiniportDmaHandle, + p_net_buffer, + p_net_buffer, + NDIS_SG_LIST_WRITE_TO_DEVICE, + NULL, + 0 /*p_port->p_adapter->sg_list_size*/ ); + } + p_prev_sgl = p_sgl; + if( status != NDIS_STATUS_SUCCESS ) + { + /* fail net buffer list */ + NET_BUFFER_LIST_NEXT_NBL(p_net_buffer_list) = NULL; + NET_BUFFER_LIST_STATUS(p_net_buffer_list) = NDIS_STATUS_RESOURCES; + NdisMSendNetBufferListsComplete( + p_port->p_adapter->h_adapter, + p_net_buffer_list, + 0); + break; + } + } + + KeLowerIrql (old_irql); + + + p_prev_nbl = p_net_buffer_list; + + } + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + + + +static void +__send_cb( + IN const ib_cq_handle_t h_cq, + IN void *cq_context ) +{ + ipoib_port_t *p_port; + ib_api_status_t status; + ib_wc_t wc[MAX_SEND_WC], *p_wc, *p_free; + cl_qlist_t done_list; + NET_BUFFER_LIST *p_nbl; + uint32_t length; + ipoib_endpt_t *p_endpt; + send_buf_t *p_send_buf; + ip_stat_sel_t type; + size_t i; + NET_BUFFER *p_netbuffer = NULL; + + PERF_DECLARE( SendCompBundle ); + PERF_DECLARE( SendCb ); + PERF_DECLARE( PollSend ); + PERF_DECLARE( SendComp ); + PERF_DECLARE( FreeSendBuf ); + PERF_DECLARE( RearmSend ); + PERF_DECLARE( PortResume ); +//return;//??????????? + IPOIB_ENTER( IPOIB_DBG_SEND ); + + cl_perf_clr( SendCompBundle ); + + cl_perf_start( SendCb ); + + UNUSED_PARAM( h_cq ); + + cl_qlist_init( &done_list ); + + p_port = (ipoib_port_t*)cq_context; + + ipoib_port_ref( p_port, ref_send_cb ); + + for( i = 0; i < MAX_SEND_WC; i++ ) + wc[i].p_next = &wc[i + 1]; + wc[MAX_SEND_WC - 1].p_next = NULL; + + do + { + p_free = wc; + cl_perf_start( PollSend ); + status = p_port->p_adapter->p_ifc->poll_cq( p_port->ib_mgr.h_send_cq, &p_free, &p_wc ); + cl_perf_stop( &p_port->p_adapter->perf, PollSend ); + CL_ASSERT( status == IB_SUCCESS || status == IB_NOT_FOUND ); + + while( p_wc ) + { + cl_perf_start( SendComp ); + CL_ASSERT( p_wc->status != IB_WCS_SUCCESS || p_wc->wc_type == IB_WC_SEND ); + p_nbl = (NET_BUFFER_LIST*)(uintn_t)p_wc->wr_id; + IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_SEND, + ("[1]Successfull send completion for NBL=0x%x .\n", p_nbl )); + CL_ASSERT( p_nbl ); + CL_ASSERT( IPOIB_PORT_FROM_PACKET( p_nbl ) == p_port ); + length = 0; + p_endpt = IPOIB_ENDPT_FROM_PACKET( p_nbl ); + p_send_buf = IPOIB_SEND_FROM_NETBUFFER( NET_BUFFER_LIST_FIRST_NB (p_nbl )); + + switch( p_wc->status ) + { + case IB_WCS_SUCCESS: + if( p_endpt->h_mcast ) + { + if( p_endpt->dgid.multicast.raw_group_id[11] == 0xFF && + p_endpt->dgid.multicast.raw_group_id[10] == 0xFF && + p_endpt->dgid.multicast.raw_group_id[12] == 0xFF && + p_endpt->dgid.multicast.raw_group_id[13] == 0xFF ) + { + type = IP_STAT_BCAST_BYTES; + } + else + { + type = IP_STAT_MCAST_BYTES; + } + } + else + { + type = IP_STAT_UCAST_BYTES; + } + for (p_netbuffer = NET_BUFFER_LIST_FIRST_NB(p_nbl); + p_netbuffer != NULL; + p_netbuffer = NET_BUFFER_NEXT_NB(p_netbuffer)) + { + length += NET_BUFFER_DATA_LENGTH(p_netbuffer); + } + ipoib_inc_send_stat( p_port->p_adapter, type, length ); + IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_SEND, + ("Successfull send completion for NBL=0x%x .\n", p_nbl) ); + NET_BUFFER_LIST_STATUS(p_nbl) = NDIS_STATUS_SUCCESS; + IPOIB_DEC_NET_BUFFER_LIST_REF_COUNT(p_nbl); + if (IPOIB_GET_NET_BUFFER_LIST_REF_COUNT(p_nbl) == 0) + NdisMSendNetBufferListsComplete(p_port->p_adapter->h_adapter, + p_nbl, + 0); + break; + + case IB_WCS_WR_FLUSHED_ERR: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_SEND, + ("Flushed send completion.\n") ); + ipoib_inc_send_stat( p_port->p_adapter, IP_STAT_DROPPED, 0 ); + NET_BUFFER_LIST_STATUS(p_nbl) = NDIS_STATUS_RESET_IN_PROGRESS; + NdisMSendNetBufferListsComplete(p_port->p_adapter->h_adapter, + p_nbl, + 0); + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Send failed with %s (vendor specific %#x)\n", + p_port->p_adapter->p_ifc->get_wc_status_str( p_wc->status ), + (int)p_wc->vendor_specific) ); + ipoib_inc_send_stat( p_port->p_adapter, IP_STAT_ERROR, 0 ); + NET_BUFFER_LIST_STATUS(p_nbl) = NDIS_STATUS_FAILURE; + NdisMSendNetBufferListsComplete(p_port->p_adapter->h_adapter, + p_nbl, + 0); + break; + } + cl_perf_stop( &p_port->p_adapter->perf, SendComp ); + /* Dereference the enpoint used for the transfer. */ + ipoib_endpt_deref( p_endpt ); + + if( p_send_buf ) + { + cl_perf_start( FreeSendBuf ); + NdisFreeToNPagedLookasideList( &p_port->buf_mgr.send_buf_list, + p_send_buf ); + cl_perf_stop( &p_port->p_adapter->perf, FreeSendBuf ); + } + + cl_atomic_dec( &p_port->send_mgr.depth ); + + p_wc = p_wc->p_next; + cl_perf_inc( SendCompBundle ); + } + /* If we didn't use up every WC, break out. */ + } while( !p_free ); + + /* Rearm the CQ. */ + cl_perf_start( RearmSend ); + status = p_port->p_adapter->p_ifc->rearm_cq( p_port->ib_mgr.h_send_cq, FALSE ); + cl_perf_stop( &p_port->p_adapter->perf, RearmSend ); + CL_ASSERT( status == IB_SUCCESS ); + + /* Resume any sends awaiting resources. */ + cl_perf_start( PortResume ); + ipoib_port_resume( p_port, TRUE ); + cl_perf_stop( &p_port->p_adapter->perf, PortResume ); + + ipoib_port_deref( p_port, ref_send_cb ); + + cl_perf_stop( &p_port->p_adapter->perf, SendCb ); + cl_perf_update_ctr( &p_port->p_adapter->perf, SendCompBundle ); + + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + + +/****************************************************************************** +* +* Endpoint manager implementation +* +******************************************************************************/ +static void +__endpt_mgr_construct( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + cl_qmap_init( &p_port->endpt_mgr.mac_endpts ); + cl_qmap_init( &p_port->endpt_mgr.lid_endpts ); + cl_fmap_init( &p_port->endpt_mgr.gid_endpts, __gid_cmp ); + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + +static void +__endpt_cm_mgr_thread( +IN void* p_context ); + +static ib_api_status_t +__endpt_mgr_init( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); +#if 0 + if( p_port->p_adapter->params.cm_enabled ) + { + cl_fmap_init( &p_port->endpt_mgr.conn_endpts, __gid_cmp ); + + NdisInitializeListHead( &p_port->endpt_mgr.pending_conns ); + NdisAllocateSpinLock( &p_port->endpt_mgr.conn_lock ); + cl_event_init( &p_port->endpt_mgr.event, FALSE ); + + NdisInitializeListHead( &p_port->endpt_mgr.remove_conns ); + NdisAllocateSpinLock( &p_port->endpt_mgr.remove_lock ); + + cl_thread_init( &p_port->endpt_mgr.h_thread, + __endpt_cm_mgr_thread, + ( const void *)p_port, + "CmEndPtMgr" ); + } +#endif + UNUSED_PARAM(p_port); + IPOIB_EXIT( IPOIB_DBG_INIT ); + return IB_SUCCESS; +} + +static void +__endpt_cm_mgr_thread( +IN void* p_context ) +{ + ib_api_status_t ib_status; + LIST_ENTRY *p_item; + ipoib_endpt_t *p_endpt; + ipoib_port_t *p_port =( ipoib_port_t *)p_context; + + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Starting Port [%d] Endpt CM thread \n", p_port->port_num ) ); + + while( !p_port->endpt_mgr.thread_is_done ) + { + cl_event_wait_on( &p_port->endpt_mgr.event, EVENT_NO_TIMEOUT, FALSE ); + + while( ( p_item = NdisInterlockedRemoveHeadList( + &p_port->endpt_mgr.pending_conns, + &p_port->endpt_mgr.conn_lock) ) != NULL ) + { + + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, list_item ); + if( p_port->endpt_mgr.thread_is_done ) + { + endpt_cm_set_state( p_endpt, IPOIB_CM_DISCONNECTED ); + continue; + } + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Endpt[%p] CONNECT REQ to MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + p_endpt, + p_endpt->mac.addr[0], p_endpt->mac.addr[1], + p_endpt->mac.addr[2], p_endpt->mac.addr[3], + p_endpt->mac.addr[4], p_endpt->mac.addr[5] ) ); + + if( !p_endpt->conn.h_send_qp ) + { + ib_status = endpt_cm_create_qp( p_endpt, &p_endpt->conn.h_send_qp ); + if( ib_status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Endpt [%p ] CM create QP failed status %#x\n", p_endpt, ib_status ) ); + } + else + { + ib_status = ipoib_endpt_connect( p_endpt ); + if( ib_status != IB_SUCCESS && ib_status != IB_PENDING ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Endpt [ %p ] conn REQ failed status %#x\n", p_endpt, ib_status ) ); + } + } + if( ib_status != IB_SUCCESS && ib_status != IB_PENDING ) + { + endpt_cm_set_state( p_endpt, IPOIB_CM_DESTROY ); + endpt_cm_flush_recv( p_port, p_endpt ); + endpt_cm_set_state( p_endpt, IPOIB_CM_DISCONNECTED ); + } + } + + }//while( p_item != NULL ) + + while( ( p_item = NdisInterlockedRemoveHeadList( + &p_port->endpt_mgr.remove_conns, + &p_port->endpt_mgr.remove_lock ) ) != NULL ) + { + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, list_item ); + + endpt_cm_set_state( p_endpt, IPOIB_CM_DESTROY ); + + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_INIT, + ("\nDESTROYING Endpt[%p] MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + p_endpt, + p_endpt->mac.addr[0], p_endpt->mac.addr[1], + p_endpt->mac.addr[2], p_endpt->mac.addr[3], + p_endpt->mac.addr[4], p_endpt->mac.addr[5] ) ); + endpt_cm_flush_recv( p_port, p_endpt ); + endpt_cm_set_state( p_endpt, IPOIB_CM_DISCONNECTED ); + cl_obj_destroy( &p_endpt->obj ); + } + } + + p_port->endpt_mgr.thread_is_done++; + NdisFreeSpinLock( &p_port->endpt_mgr.conn_lock ); + + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + (" Port [%d] Endpt thread is done\n", p_port->port_num ) ); +} + +static void +__endpt_mgr_destroy( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + CL_ASSERT( cl_is_qmap_empty( &p_port->endpt_mgr.mac_endpts ) ); + CL_ASSERT( cl_is_qmap_empty( &p_port->endpt_mgr.lid_endpts ) ); + CL_ASSERT( cl_is_fmap_empty( &p_port->endpt_mgr.gid_endpts ) ); +#if 0 + if( p_port->p_adapter->params.cm_enabled ) + { + CL_ASSERT( cl_is_fmap_empty( &p_port->endpt_mgr.conn_endpts ) ); + } +#endif + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +__endpt_mgr_remove_all( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + cl_obj_lock( &p_port->obj ); + /* Wait for all readers to complete. */ + while( p_port->endpt_rdr ) + ; + /* + * We don't need to initiate destruction - this is called only + * from the __port_destroying function, and destruction cascades + * to all child objects. Just clear all the maps. + */ + cl_qmap_remove_all( &p_port->endpt_mgr.mac_endpts ); + cl_qmap_remove_all( &p_port->endpt_mgr.lid_endpts ); + cl_fmap_remove_all( &p_port->endpt_mgr.gid_endpts ); + cl_obj_unlock( &p_port->obj ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); +} + + +static void +__endpt_mgr_reset_all( + IN ipoib_port_t* const p_port ) +{ + cl_map_item_t *p_item; + cl_fmap_item_t *p_fmap_item; + ipoib_endpt_t *p_endpt; + cl_qlist_t mc_list; + cl_qlist_t conn_list; + uint32_t local_exist = 0; + NDIS_LINK_STATE link_state; + NDIS_STATUS_INDICATION status_indication; + + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + cl_qlist_init( &mc_list ); + cl_qlist_init( &conn_list ); +//??? cl_obj_lock( &p_port->obj ); + /* Wait for all readers to complete. */ + while( p_port->endpt_rdr ) + ; + +#if 0 + __endpt_mgr_remove_all(p_port); +#else + link_state.Header.Revision = NDIS_LINK_STATE_REVISION_1; + link_state.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + link_state.Header.Size = sizeof(NDIS_LINK_STATE); + link_state.MediaConnectState = MediaConnectStateDisconnected; + link_state.MediaDuplexState = MediaDuplexStateFull; + link_state.XmitLinkSpeed = link_state.RcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + + IPOIB_INIT_NDIS_STATUS_INDICATION(&status_indication, + p_port->p_adapter->h_adapter, + NDIS_STATUS_LINK_STATE, + (PVOID)&link_state, + sizeof(link_state)); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, ("Indicate DISCONNECT!\n") ); + NdisMIndicateStatusEx(p_port->p_adapter->h_adapter,&status_indication); + + link_state.MediaConnectState = MediaConnectStateConnected; + IPOIB_INIT_NDIS_STATUS_INDICATION(&status_indication, + p_port->p_adapter->h_adapter, + NDIS_STATUS_LINK_STATE, + (PVOID)&link_state, + sizeof(link_state)); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, ("Indicate Connect\n") ); + NdisMIndicateStatusEx(p_port->p_adapter->h_adapter,&status_indication); + + + // IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + // ("Link DOWN!\n") ); + + if( p_port->p_local_endpt ) + { + //TODO: CM RESTORE + //ipoib_port_cancel_listen( p_port, p_port->p_local_endpt ); + + cl_fmap_remove_item( &p_port->endpt_mgr.gid_endpts, + &p_port->p_local_endpt->gid_item ); + cl_qmap_remove_item( &p_port->endpt_mgr.mac_endpts, + &p_port->p_local_endpt->mac_item ); + cl_qmap_remove_item( &p_port->endpt_mgr.lid_endpts, + &p_port->p_local_endpt->lid_item ); + + cl_qlist_insert_head( + &mc_list, &p_port->p_local_endpt->mac_item.pool_item.list_item ); + local_exist = 1; + + p_port->p_local_endpt = NULL; + } + + p_item = cl_qmap_head( &p_port->endpt_mgr.mac_endpts ); + while( p_item != cl_qmap_end( &p_port->endpt_mgr.mac_endpts ) ) + { + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, mac_item ); + p_item = cl_qmap_next( p_item ); + if( p_endpt->h_mcast ) + { + /* + * We destroy MC endpoints since they will get recreated + * when the port comes back up and we rejoin the MC groups. + */ + cl_qmap_remove_item( &p_port->endpt_mgr.mac_endpts, + &p_endpt->mac_item ); + cl_fmap_remove_item( &p_port->endpt_mgr.gid_endpts, + &p_endpt->gid_item ); + + cl_qlist_insert_tail( + &mc_list, &p_endpt->mac_item.pool_item.list_item ); + } + /* destroy connected endpoints if any */ + else if( p_port->p_adapter->params.cm_enabled && + endpt_cm_get_state( p_endpt ) != IPOIB_CM_DISCONNECTED ) + { + p_fmap_item = cl_fmap_get( &p_port->endpt_mgr.conn_endpts, &p_endpt->dgid ); + if( p_fmap_item != cl_fmap_end( &p_port->endpt_mgr.conn_endpts ) ) + { + cl_fmap_remove_item( &p_port->endpt_mgr.conn_endpts, + &p_endpt->conn_item ); + } + cl_qmap_remove_item( &p_port->endpt_mgr.mac_endpts, + &p_endpt->mac_item ); + cl_fmap_remove_item( &p_port->endpt_mgr.gid_endpts, + &p_endpt->gid_item ); + + cl_qlist_insert_tail( + &conn_list, &p_endpt->mac_item.pool_item.list_item ); + } + if( p_endpt->h_av ) + { + /* Destroy the AV for all other endpoints. */ + p_port->p_adapter->p_ifc->destroy_av( p_endpt->h_av ); + p_endpt->h_av = NULL; + } + + if( p_endpt->dlid ) + { + cl_qmap_remove_item( &p_port->endpt_mgr.lid_endpts, + &p_endpt->lid_item ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("<__endptr_mgr_reset_all: setting p_endpt->dlid to 0\n")); + p_endpt->dlid = 0; + } + + } +#endif +//??? cl_obj_unlock( &p_port->obj ); + + //TODO CM + /*while( cl_qlist_count( &conn_list ) ) + { + endpt_cm_destroy_conn( p_port, + PARENT_STRUCT( cl_qlist_remove_head( &conn_list ), + ipoib_endpt_t, mac_item.pool_item.list_item ) ); + }*/ + + if(cl_qlist_count( &mc_list ) - local_exist) + { + p_port->mcast_cnt = (uint32_t)cl_qlist_count( &mc_list ) - local_exist; + } + else + { + p_port->mcast_cnt = 0; + KeSetEvent( &p_port->leave_mcast_event, EVENT_INCREMENT, FALSE ); + } + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT,("p_port->mcast_cnt = %d\n", p_port->mcast_cnt - local_exist)); + + /* Destroy all multicast endpoints now that we have released the lock. */ + while( cl_qlist_count( &mc_list ) ) + { + cl_list_item_t *p_item; + p_item = cl_qlist_remove_head( &mc_list ); + p_endpt = PARENT_STRUCT(p_item, ipoib_endpt_t, mac_item.pool_item.list_item); + cl_obj_destroy( &p_endpt->obj); + } + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); +} + + +/* + * Called when updating an endpoint entry in response to an ARP. + * Because receive processing is serialized, and holds a reference + * on the endpoint reader, we wait for all *other* readers to exit before + * removing the item. + */ +static void +__endpt_mgr_remove( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt ) +{ +#if 0 //CM + cl_fmap_item_t* p_fmap_item; +#endif + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + /* This function must be called from the receive path */ + CL_ASSERT(p_port->endpt_rdr > 0); + + cl_obj_lock( &p_port->obj ); + /* Wait for all readers to complete. */ + while( p_port->endpt_rdr > 1 ) + ; + + /* Remove the endpoint from the maps so further requests don't find it. */ + cl_qmap_remove_item( &p_port->endpt_mgr.mac_endpts, &p_endpt->mac_item ); + /* + * The enpoints are *ALWAYS* in both the MAC and GID maps. They are only + * in the LID map if the GID has the same subnet prefix as us. + */ + cl_fmap_remove_item( &p_port->endpt_mgr.gid_endpts, &p_endpt->gid_item ); +#if 0 + + if( p_port->p_adapter->params.cm_enabled ) + { + p_fmap_item = cl_fmap_get( &p_port->endpt_mgr.conn_endpts, &p_endpt->dgid ); + + if( p_fmap_item != cl_fmap_end( &p_port->endpt_mgr.conn_endpts ) ) + { + cl_fmap_remove_item( &p_port->endpt_mgr.conn_endpts, + &p_endpt->conn_item ); + } + } +#endif + if( p_endpt->dlid ) + { + cl_qmap_remove_item( &p_port->endpt_mgr.lid_endpts, + &p_endpt->lid_item ); + } + + cl_obj_unlock( &p_port->obj ); + + //TODO CM + //endpt_cm_destroy_conn( p_port, p_endpt ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); +} + + +NTSTATUS +ipoib_mac_to_gid( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ib_gid_t* p_gid ) +{ + ipoib_endpt_t* p_endpt; + cl_map_item_t *p_item; + uint64_t key = 0; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + cl_memcpy( &key, &mac, sizeof(mac_addr_t) ); + + cl_obj_lock( &p_port->obj ); + + p_item = cl_qmap_get( &p_port->endpt_mgr.mac_endpts, key ); + if( p_item == cl_qmap_end( &p_port->endpt_mgr.mac_endpts ) ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed endpoint lookup.\n") ); + return STATUS_INVALID_PARAMETER; + } + + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, mac_item ); + *p_gid = p_endpt->dgid; + + cl_obj_unlock( &p_port->obj ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return STATUS_SUCCESS; +} + + +NTSTATUS +ipoib_mac_to_path( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ib_path_rec_t* p_path ) +{ + ipoib_endpt_t* p_endpt; + cl_map_item_t *p_item; + uint64_t key = 0; + uint8_t sl; + net32_t flow_lbl; + uint8_t hop_limit; + uint8_t pkt_life; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + cl_memcpy( &key, &mac, sizeof(mac_addr_t) ); + + cl_obj_lock( &p_port->obj ); + + if( p_port->p_local_endpt == NULL ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("No local endpoint.\n") ); + return STATUS_INVALID_PARAMETER; + } + + if( mac.addr[0] == 0 && mac.addr[1] == 0 && mac.addr[2] == 0 && + mac.addr[3] == 0 && mac.addr[4] == 0 && mac.addr[5] == 0 ) + { + p_endpt = p_port->p_local_endpt; + } + else + { + p_item = cl_qmap_get( &p_port->endpt_mgr.mac_endpts, key ); + if( p_item == cl_qmap_end( &p_port->endpt_mgr.mac_endpts ) ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed endpoint lookup.\n") ); + return STATUS_INVALID_PARAMETER; + } + + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, mac_item ); + } + + p_path->resv0 = 0; + p_path->dgid = p_endpt->dgid; + p_path->sgid = p_port->p_local_endpt->dgid; + p_path->dlid = p_endpt->dlid; + p_path->slid = p_port->p_local_endpt->dlid; + + ib_member_get_sl_flow_hop( + p_port->ib_mgr.bcast_rec.sl_flow_hop, + &sl, + &flow_lbl, + &hop_limit + ); + if( p_path->slid == p_path->dlid ) + pkt_life = 0; + else + pkt_life = p_port->ib_mgr.bcast_rec.pkt_life; + + ib_path_rec_init_local( + p_path, + &p_endpt->dgid, + &p_port->p_local_endpt->dgid, + p_endpt->dlid, + p_port->p_local_endpt->dlid, + 1, + p_port->ib_mgr.bcast_rec.pkey, + sl, + IB_PATH_SELECTOR_EXACTLY, p_port->ib_mgr.bcast_rec.mtu, + IB_PATH_SELECTOR_EXACTLY, p_port->ib_mgr.bcast_rec.rate, + IB_PATH_SELECTOR_EXACTLY, pkt_life, + 0 ); + + /* Set global routing information. */ + ib_path_rec_set_hop_flow_raw( p_path, hop_limit, flow_lbl, FALSE ); + p_path->tclass = p_port->ib_mgr.bcast_rec.tclass; + + cl_obj_unlock( &p_port->obj ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return STATUS_SUCCESS; +} + + +static inline NDIS_STATUS +__endpt_mgr_ref( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ipoib_endpt_t** const pp_endpt ) +{ + NDIS_STATUS status; + cl_map_item_t *p_item; + uint64_t key; + + PERF_DECLARE( EndptQueue ); + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + if( !cl_memcmp( &mac, &p_port->p_adapter->params.conf_mac, sizeof(mac) ) ) + { + /* Discard loopback traffic. */ + IPOIB_PRINT(TRACE_LEVEL_WARNING, IPOIB_DBG_ENDPT, + ("Discarding loopback traffic\n") ); + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return NDIS_STATUS_NO_ROUTE_TO_DESTINATION; + } + + key = 0; + cl_memcpy( &key, &mac, sizeof(mac_addr_t) ); + + cl_obj_lock( &p_port->obj ); + + IPOIB_PRINT( TRACE_LEVEL_VERBOSE, IPOIB_DBG_ENDPT, + ("Look for :\t MAC: %02X-%02X-%02X-%02X-%02X-%02X\n", + mac.addr[0], mac.addr[1], mac.addr[2], + mac.addr[3], mac.addr[4], mac.addr[5]) ); + + p_item = cl_qmap_get( &p_port->endpt_mgr.mac_endpts, key ); + if( p_item == cl_qmap_end( &p_port->endpt_mgr.mac_endpts ) ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("Failed endpoint lookup.\n") ); + return NDIS_STATUS_NO_ROUTE_TO_DESTINATION; + } + + *pp_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, mac_item ); + ipoib_endpt_ref( *pp_endpt ); + + cl_obj_unlock( &p_port->obj ); + + cl_perf_start( EndptQueue ); + status = ipoib_endpt_queue( *pp_endpt ); + cl_perf_stop( &p_port->p_adapter->perf, EndptQueue ); + if( status != NDIS_STATUS_SUCCESS ) + *pp_endpt = NULL; + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return status; +} + + +static inline NDIS_STATUS +__endpt_mgr_get_gid_qpn( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ib_gid_t* const p_gid, + OUT UNALIGNED net32_t* const p_qpn ) +{ + UNALIGNED + cl_map_item_t *p_item; + ipoib_endpt_t *p_endpt; + uint64_t key; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + cl_obj_lock( &p_port->obj ); + + key = 0; + cl_memcpy( &key, &mac, sizeof(mac_addr_t) ); + p_item = cl_qmap_get( &p_port->endpt_mgr.mac_endpts, key ); + if( p_item == cl_qmap_end( &p_port->endpt_mgr.mac_endpts ) ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("Failed endpoint lookup.\n") ); + return NDIS_STATUS_FAILURE; + } + + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, mac_item ); + + *p_gid = p_endpt->dgid; + *p_qpn = p_endpt->qpn; + + cl_obj_unlock( &p_port->obj ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return NDIS_STATUS_SUCCESS; +} + + +static inline ipoib_endpt_t* +__endpt_mgr_get_by_gid( + IN ipoib_port_t* const p_port, + IN const ib_gid_t* const p_gid ) +{ + cl_fmap_item_t *p_item; + ipoib_endpt_t *p_endpt; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + p_item = cl_fmap_get( &p_port->endpt_mgr.gid_endpts, p_gid ); + if( p_item == cl_fmap_end( &p_port->endpt_mgr.gid_endpts ) ) + p_endpt = NULL; + else + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, gid_item ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return p_endpt; +} + + +static ipoib_endpt_t* +__endpt_mgr_get_by_lid( + IN ipoib_port_t* const p_port, + IN const net16_t lid ) +{ + cl_map_item_t *p_item; + ipoib_endpt_t *p_endpt; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + p_item = cl_qmap_get( &p_port->endpt_mgr.lid_endpts, lid ); + if( p_item == cl_qmap_end( &p_port->endpt_mgr.lid_endpts ) ) + p_endpt = NULL; + else + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, lid_item ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return p_endpt; +} + + +inline ib_api_status_t +__endpt_mgr_insert_locked( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + IN ipoib_endpt_t* const p_endpt ) +{ + ib_api_status_t status; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("insert :\t MAC: %02X-%02X-%02X-%02X-%02X-%02X\n", + mac.addr[0], mac.addr[1], mac.addr[2], + mac.addr[3], mac.addr[4], mac.addr[5]) ); + + cl_obj_lock( &p_port->obj ); + while( p_port->endpt_rdr ) + { + cl_obj_unlock( &p_port->obj ); + cl_obj_lock( &p_port->obj ); + } + /* __endpt_mgr_insert expects *one* reference to be held when being called. */ + cl_atomic_inc( &p_port->endpt_rdr ); + status= __endpt_mgr_insert( p_port, mac, p_endpt ); + cl_atomic_dec( &p_port->endpt_rdr ); + cl_obj_unlock( &p_port->obj ); + + return status; +} + + +inline ib_api_status_t +__endpt_mgr_insert( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + IN ipoib_endpt_t* const p_endpt ) +{ + uint64_t key; + cl_status_t cl_status; + cl_map_item_t *p_qitem; + cl_fmap_item_t *p_fitem; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + /* Wait for all accesses to the map to complete. */ + while( p_port->endpt_rdr > 1 ) + ; + + /* Link the endpoint to the port. */ + cl_status = cl_obj_insert_rel_parent_locked( + &p_endpt->rel, &p_port->obj, &p_endpt->obj ); + + if( cl_status != CL_SUCCESS ) + { + cl_obj_destroy( &p_endpt->obj ); + return IB_INVALID_STATE; + } + +#if DBG + cl_atomic_inc( &p_port->ref[ref_endpt_track] ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OBJ, + ("ref type %d ref_cnt %d\n", ref_endpt_track, p_port->obj.ref_cnt) ); +#endif + + p_endpt->mac = mac; + key = 0; + cl_memcpy( &key, &mac, sizeof(mac_addr_t) ); + p_qitem = cl_qmap_insert( + &p_port->endpt_mgr.mac_endpts, key, &p_endpt->mac_item ); + CL_ASSERT( p_qitem == &p_endpt->mac_item ); + p_fitem = cl_fmap_insert( + &p_port->endpt_mgr.gid_endpts, &p_endpt->dgid, &p_endpt->gid_item ); + CL_ASSERT( p_fitem == &p_endpt->gid_item ); + if( p_endpt->dlid ) + { + p_qitem = cl_qmap_insert( + &p_port->endpt_mgr.lid_endpts, p_endpt->dlid, &p_endpt->lid_item ); + CL_ASSERT( p_qitem == &p_endpt->lid_item ); + if (p_qitem != &p_endpt->lid_item) { + // Since we failed to insert into the list, make sure it is not removed + p_endpt->dlid =0; + } + } + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return IB_SUCCESS; +} + + +static ib_api_status_t +__endpt_mgr_add_bcast( + IN ipoib_port_t* const p_port, + IN ib_mcast_rec_t *p_mcast_rec ) +{ + ib_api_status_t status; + ipoib_endpt_t *p_endpt; + mac_addr_t bcast_mac; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + /* + * Cache the broadcast group properties for creating future mcast groups. + */ + p_port->ib_mgr.bcast_rec = *p_mcast_rec->p_member_rec; + + /* Allocate the broadcast endpoint. */ + p_endpt = ipoib_endpt_create( &p_mcast_rec->p_member_rec->mgid, + 0 , CL_HTON32(0x00FFFFFF) ); + if( !p_endpt ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_endpt_create failed.\n") ); + return IB_INSUFFICIENT_RESOURCES; + } + /* set reference to transport to be used while is not attached to the port */ + p_endpt->is_mcast_listener = TRUE; + p_endpt->p_ifc = p_port->p_adapter->p_ifc; + status = ipoib_endpt_set_mcast( p_endpt, p_port->ib_mgr.h_pd, + p_port->port_num, p_mcast_rec ); + if( status != IB_SUCCESS ) + { + cl_obj_destroy( &p_endpt->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_create_mcast_endpt returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Add the broadcast endpoint to the endpoint map. */ + cl_memset( &bcast_mac, 0xFF, sizeof(bcast_mac) ); + status = __endpt_mgr_insert_locked( p_port, bcast_mac, p_endpt ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +void +ipoib_port_remove_endpt( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac ) +{ + cl_map_item_t *p_item; + //TODO CM +// cl_fmap_item_t *p_fmap_item; + ipoib_endpt_t *p_endpt; + uint64_t key; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + key = 0; + cl_memcpy( &key, &mac, sizeof(mac_addr_t) ); + + /* Remove the endpoint from the maps so further requests don't find it. */ + cl_obj_lock( &p_port->obj ); + /* Wait for all readers to finish */ + while( p_port->endpt_rdr ) + ; + p_item = cl_qmap_remove( &p_port->endpt_mgr.mac_endpts, key ); + /* + * Dereference the endpoint. If the ref count goes to zero, it + * will get freed. + */ + if( p_item != cl_qmap_end( &p_port->endpt_mgr.mac_endpts ) ) + { + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, mac_item ); + /* + * The enpoints are *ALWAYS* in both the MAC and GID maps. They are only + * in the LID map if the GID has the same subnet prefix as us. + */ + cl_fmap_remove_item( + &p_port->endpt_mgr.gid_endpts, &p_endpt->gid_item ); +#if 0 + if( p_port->p_adapter->params.cm_enabled ) + { + p_fmap_item = cl_fmap_get( &p_port->endpt_mgr.conn_endpts, &p_endpt->dgid ); + + if( p_fmap_item != cl_fmap_end( &p_port->endpt_mgr.conn_endpts ) ) + { + cl_fmap_remove_item( &p_port->endpt_mgr.conn_endpts, + &p_endpt->conn_item ); + } + } +#endif + + if( p_endpt->dlid ) + { + cl_qmap_remove_item( + &p_port->endpt_mgr.lid_endpts, &p_endpt->lid_item ); + } + + cl_obj_unlock( &p_port->obj ); + //TODO CM + //endpt_cm_destroy_conn( p_port, p_endpt ); + +#if DBG + cl_atomic_dec( &p_port->ref[ref_endpt_track] ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("ref type %d ref_cnt %d\n", ref_endpt_track, p_port->obj.ref_cnt) ); +#endif + + } + else + { + cl_obj_unlock( &p_port->obj ); + } + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); +} + +/* + * The sequence for port up is as follows: + * 1. The port goes active. This allows the adapter to send SA queries + * and join the broadcast group (and other groups). + * + * 2. The adapter sends an SA query for the broadcast group. + * + * 3. Upon completion of the query, the adapter joins the broadcast group. + */ + + +/* + * Query the SA for the broadcast group. + */ +void +ipoib_port_up( + IN ipoib_port_t* const p_port, + IN const ib_pnp_port_rec_t* const p_pnp_rec ) +{ + ib_port_info_t *p_port_info; + ib_mad_t *mad_in = NULL; + ib_mad_t *mad_out = NULL; + ib_api_status_t status = IB_INSUFFICIENT_MEMORY; + static int cnt = 0; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_INIT, + ("[%d] Entering port_up.\n", ++cnt) ); + + cl_obj_lock( &p_port->obj ); + if ( p_port->state == IB_QPS_INIT ) + { + cl_obj_unlock( &p_port->obj ); + status = IB_SUCCESS; + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("p_port->state = %d - Aborting.\n", p_port->state) ); + goto up_done; + } + else if ( p_port->state == IB_QPS_RTS ) + { + cl_obj_unlock( &p_port->obj ); + cl_obj_lock( &p_port->p_adapter->obj ); + if( p_port->p_adapter->state == IB_PNP_PORT_INIT ) + { + p_port->p_adapter->state = IB_PNP_PORT_ACTIVE; + } + cl_obj_unlock( &p_port->p_adapter->obj ); + status = IB_SUCCESS; + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Port init is done. p_port->state = %d.\n", p_port->state ) ); + goto up_done; + } + p_port->state = IB_QPS_INIT; + cl_obj_unlock( &p_port->obj ); + + + /* Wait for all work requests to get flushed. */ + while( p_port->recv_mgr.depth || p_port->send_mgr.depth ) + cl_thread_suspend( 0 ); + + KeResetEvent( &p_port->sa_event ); + + mad_out = (ib_mad_t*)cl_zalloc(256); + if(! mad_out) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("failed to allocate mad mad_out\n")); + goto up_done; + } + mad_in = (ib_mad_t*)cl_zalloc(256); + if(! mad_in) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("failed to allocate mad mad_in\n")); + goto up_done; + } + + mad_in->attr_id = IB_MAD_ATTR_PORT_INFO; + mad_in->method = IB_MAD_METHOD_GET; + mad_in->base_ver = 1; + mad_in->class_ver =1; + mad_in->mgmt_class = IB_MCLASS_SUBN_LID; + + status = p_port->p_adapter->p_ifc->local_mad( + p_port->ib_mgr.h_ca ,p_port->port_num ,mad_in ,mad_out); + + if( status != IB_SUCCESS ) + { + ipoib_set_inactive( p_port->p_adapter ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_local_mad returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + goto up_done; + } + + p_port_info = (ib_port_info_t*)(((ib_smp_t*)mad_out)->data); + p_port->base_lid = p_pnp_rec->p_port_attr->lid; + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Received port info: link width = %d.\n", + p_port_info->link_width_active) ); + p_port->ib_mgr.rate = + ib_port_info_compute_rate( p_port_info ); + + ipoib_set_rate( p_port->p_adapter, + p_port_info->link_width_active, + ib_port_info_get_link_speed_active( p_port_info ) ); + + status = __port_get_bcast( p_port ); + if (status != IB_SUCCESS) + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + (" __port_get_bcast returned %s\n",p_port->p_adapter->p_ifc->get_err_str( status ))); + +up_done: + if( status != IB_SUCCESS ) + { + if( status != IB_CANCELED ) + { + ipoib_set_inactive( p_port->p_adapter ); + __endpt_mgr_reset_all( p_port ); + } + ASSERT(p_port->state == IB_QPS_INIT || p_port->state == IB_QPS_ERROR); + p_port->state = IB_QPS_ERROR; + KeSetEvent( &p_port->sa_event, EVENT_INCREMENT, FALSE ); + } + + if(mad_out) + cl_free(mad_out); + if(mad_in) + cl_free(mad_in); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static ib_api_status_t +__endpt_mgr_add_local( + IN ipoib_port_t* const p_port, + IN ib_port_info_t* const p_port_info ) +{ + ib_api_status_t status; + ib_gid_t gid; + ipoib_endpt_t *p_endpt; + ib_av_attr_t av_attr; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + ib_gid_set_default( &gid, p_port->p_adapter->guids.port_guid.guid ); + p_endpt = ipoib_endpt_create( + &gid, p_port_info->base_lid, p_port->ib_mgr.qpn ); + if( !p_endpt ) + { + p_port->p_adapter->hung = TRUE; + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to create local endpt\n") ); + return IB_INSUFFICIENT_MEMORY; + } + + cl_memclr( &av_attr, sizeof(ib_av_attr_t) ); + av_attr.port_num = p_port->port_num; + av_attr.sl = 0; + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + (" av_attr.dlid = p_port_info->base_lid = %d\n", cl_ntoh16( p_port_info->base_lid ) )); + av_attr.dlid = p_port_info->base_lid; + av_attr.static_rate = p_port->ib_mgr.rate; + av_attr.path_bits = 0; + status = p_port->p_adapter->p_ifc->create_av( + p_port->ib_mgr.h_pd, &av_attr, &p_endpt->h_av ); + if( status != IB_SUCCESS ) + { + cl_obj_destroy( &p_endpt->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_create_av for local endpoint returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* __endpt_mgr_insert expects *one* reference to be held. */ + cl_atomic_inc( &p_port->endpt_rdr ); + status = __endpt_mgr_insert( p_port, p_port->p_adapter->params.conf_mac, p_endpt ); + cl_atomic_dec( &p_port->endpt_rdr ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__endpt_mgr_insert for local endpoint returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + p_port->p_local_endpt = p_endpt; + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + + + +static ib_api_status_t +__port_get_bcast( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t status; + ib_query_req_t query; + ib_user_query_t info; + ib_member_rec_t member_rec; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + info.method = IB_MAD_METHOD_GETTABLE; + info.attr_id = IB_MAD_ATTR_MCMEMBER_RECORD; + info.attr_size = sizeof(ib_member_rec_t); + info.comp_mask = IB_MCR_COMPMASK_MGID; + info.p_attr = &member_rec; + + /* Query requires only the MGID. */ + cl_memclr( &member_rec, sizeof(ib_member_rec_t) ); + member_rec.mgid = bcast_mgid_template; + + member_rec.mgid.raw[4] = (uint8_t) (p_port->p_adapter->guids.port_guid.pkey >> 8) ; + member_rec.mgid.raw[5] = (uint8_t) p_port->p_adapter->guids.port_guid.pkey; + member_rec.pkey = cl_hton16(p_port->p_adapter->guids.port_guid.pkey); + cl_memclr( &query, sizeof(ib_query_req_t) ); + query.query_type = IB_QUERY_USER_DEFINED; + query.p_query_input = &info; + query.port_guid = p_port->p_adapter->guids.port_guid.guid; + query.timeout_ms = p_port->p_adapter->params.sa_timeout; + query.retry_cnt = p_port->p_adapter->params.sa_retry_cnt; + query.query_context = p_port; + query.pfn_query_cb = __bcast_get_cb; + + /* reference the object for the multicast query. */ + ipoib_port_ref( p_port, ref_get_bcast ); + + status = p_port->p_adapter->p_ifc->query( + p_port->p_adapter->h_al, &query, &p_port->ib_mgr.h_query ); + if( status != IB_SUCCESS ) + { + ipoib_port_deref( p_port, ref_get_bcast ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_query returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + } + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_query returned SUCCESS\n")); + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +/* Callback for the MCMemberRecord Get query for the IPv4 broadcast group. */ +static void +__bcast_get_cb( + IN ib_query_rec_t *p_query_rec ) +{ + ipoib_port_t *p_port; + ib_member_rec_t *p_mc_req; + ib_api_status_t status; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_port = (ipoib_port_t*)p_query_rec->query_context; + + cl_obj_lock( &p_port->obj ); + p_port->ib_mgr.h_query = NULL; + + CL_ASSERT(p_port->state == IB_QPS_INIT || p_port->state == IB_QPS_ERROR); + if( p_port->state != IB_QPS_INIT ) + { + status = IB_CANCELED; + goto done; + } + + status = p_query_rec->status; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("status of request %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + + switch( status ) + { + case IB_SUCCESS: + if( p_query_rec->result_cnt ) + { + p_mc_req = (ib_member_rec_t*) + ib_get_query_result( p_query_rec->p_result_mad, 0 ); + + /* Join the broadcast group. */ + status = __port_join_bcast( p_port, p_mc_req ); + break; + } + /* Fall through. */ + + case IB_REMOTE_ERROR: + /* SA failed the query. Broadcast group doesn't exist, create it. */ + status = __port_create_bcast( p_port ); + break; + + case IB_CANCELED: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Instance destroying - Aborting.\n") ); + break; + + default: + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_BCAST_GET, 1, p_query_rec->status ); + } +done: + cl_obj_unlock( &p_port->obj ); + + if( status != IB_SUCCESS ) + { + if( status != IB_CANCELED ) + { + ipoib_set_inactive( p_port->p_adapter ); + __endpt_mgr_reset_all( p_port ); + } + p_port->state = IB_QPS_ERROR; + KeSetEvent( &p_port->sa_event, EVENT_INCREMENT, FALSE ); + } + + /* Return the response MAD to AL. */ + if( p_query_rec->p_result_mad ) + p_port->p_adapter->p_ifc->put_mad( p_query_rec->p_result_mad ); + + /* Release the reference taken when issuing the member record query. */ + ipoib_port_deref( p_port, ref_bcast_get_cb ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static ib_api_status_t +__port_join_bcast( + IN ipoib_port_t* const p_port, + IN ib_member_rec_t* const p_member_rec ) +{ + ib_api_status_t status; + ib_mcast_req_t mcast_req; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + /* Check that the rate is realizable for our port. */ + if( p_port->ib_mgr.rate < (p_member_rec->rate & 0x3F) && + (g_ipoib.bypass_check_bcast_rate == 0)) + { + /* + * The MC group rate is higher than our port's rate. Log an error + * and stop. A port transition will drive the retry. + */ + IPOIB_PRINT(TRACE_LEVEL_WARNING, IPOIB_DBG_INIT, + ("Unrealizable join due to rate mismatch.\n") ); + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_BCAST_RATE, 2, + (uint32_t)(p_member_rec->rate & 0x3F), + (uint32_t)p_port->ib_mgr.rate ); + return IB_ERROR; + } + + /* Join the broadcast group. */ + cl_memclr( &mcast_req, sizeof(mcast_req) ); + /* Copy the results of the Get to use as parameters. */ + mcast_req.member_rec = *p_member_rec; + /* We specify our port GID for the join operation. */ + mcast_req.member_rec.port_gid.unicast.prefix = IB_DEFAULT_SUBNET_PREFIX; + mcast_req.member_rec.port_gid.unicast.interface_id = + p_port->p_adapter->guids.port_guid.guid; + + mcast_req.mcast_context = p_port; + mcast_req.pfn_mcast_cb = __bcast_cb; + mcast_req.timeout_ms = p_port->p_adapter->params.sa_timeout; + mcast_req.retry_cnt = p_port->p_adapter->params.sa_retry_cnt; + mcast_req.port_guid = p_port->p_adapter->guids.port_guid.guid; + mcast_req.pkey_index = p_port->pkey_index; + + if( ib_member_get_state( mcast_req.member_rec.scope_state ) != + IB_MC_REC_STATE_FULL_MEMBER ) + { + IPOIB_PRINT(TRACE_LEVEL_WARNING, IPOIB_DBG_INIT, + ("Incorrect MC member rec join state in query response.\n") ); + ib_member_set_state( &mcast_req.member_rec.scope_state, + IB_MC_REC_STATE_FULL_MEMBER ); + } + + /* reference the object for the multicast join request. */ + ipoib_port_ref( p_port, ref_join_bcast ); + + status = p_port->p_adapter->p_ifc->join_mcast( + p_port->ib_mgr.h_qp, &mcast_req ); + if( status != IB_SUCCESS ) + { + ipoib_port_deref( p_port, ref_bcast_join_failed ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_join_mcast returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + } + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +static ib_api_status_t +__port_create_bcast( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t status; + ib_mcast_req_t mcast_req; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + /* Join the broadcast group. */ + cl_memclr( &mcast_req, sizeof(mcast_req) ); + mcast_req.create = TRUE; + /* + * Create requires pkey, qkey, SL, flow label, traffic class, joing state + * and port GID. + * + * We specify the MGID since we don't want the SA to generate it for us. + */ + mcast_req.member_rec.mgid = bcast_mgid_template; + mcast_req.member_rec.mgid.raw[4] = (uint8_t) (p_port->p_adapter->guids.port_guid.pkey >> 8); + mcast_req.member_rec.mgid.raw[5] = (uint8_t) p_port->p_adapter->guids.port_guid.pkey; + ib_gid_set_default( &mcast_req.member_rec.port_gid, + p_port->p_adapter->guids.port_guid.guid ); + /* + * IPOIB spec requires that the QKEY have the MSb set so that the QKEY + * from the QP is used rather than the QKEY in the send WR. + */ + mcast_req.member_rec.qkey = + (uint32_t)(uintn_t)p_port | IB_QP_PRIVILEGED_Q_KEY; + mcast_req.member_rec.mtu = + (IB_PATH_SELECTOR_EXACTLY << 6) | IB_MTU_LEN_2048; + + mcast_req.member_rec.pkey = cl_hton16(p_port->p_adapter->guids.port_guid.pkey); + + mcast_req.member_rec.sl_flow_hop = ib_member_set_sl_flow_hop( 0, 0, 0 ); + mcast_req.member_rec.scope_state = + ib_member_set_scope_state( 2, IB_MC_REC_STATE_FULL_MEMBER ); + + mcast_req.mcast_context = p_port; + mcast_req.pfn_mcast_cb = __bcast_cb; + mcast_req.timeout_ms = p_port->p_adapter->params.sa_timeout; + mcast_req.retry_cnt = p_port->p_adapter->params.sa_retry_cnt; + mcast_req.port_guid = p_port->p_adapter->guids.port_guid.guid; + mcast_req.pkey_index = p_port->pkey_index; + + /* reference the object for the multicast join request. */ + ipoib_port_ref( p_port, ref_join_bcast ); + + status = p_port->p_adapter->p_ifc->join_mcast( p_port->ib_mgr.h_qp, &mcast_req ); + if( status != IB_SUCCESS ) + { + ipoib_port_deref( p_port, ref_bcast_create_failed ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_join_mcast returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + } + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +void +ipoib_port_down( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t status; + ib_qp_mod_t qp_mod; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + /* + * Mark our state. This causes all callbacks to abort. + * Note that we hold the receive lock so that we synchronize + * with reposting. We must take the receive lock before the + * object lock since that is the order taken when reposting. + */ + cl_spinlock_acquire( &p_port->recv_lock ); + cl_obj_lock( &p_port->obj ); + p_port->state = IB_QPS_ERROR; + + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_PORT_DOWN, 0 ); + + if( p_port->ib_mgr.h_query ) + { + p_port->p_adapter->p_ifc->cancel_query( + p_port->p_adapter->h_al, p_port->ib_mgr.h_query ); + p_port->ib_mgr.h_query = NULL; + } + cl_obj_unlock( &p_port->obj ); + cl_spinlock_release( &p_port->recv_lock ); + + KeWaitForSingleObject( + &p_port->sa_event, Executive, KernelMode, FALSE, NULL ); + + /* garbage collector timer is not needed when link is down */ + KeCancelTimer(&p_port->gc_timer); + KeFlushQueuedDpcs(); + + /* + * Put the QP in the error state. This removes the need to + * synchronize with send/receive callbacks. + */ + CL_ASSERT( p_port->ib_mgr.h_qp ); + cl_memclr( &qp_mod, sizeof(ib_qp_mod_t) ); + qp_mod.req_state = IB_QPS_ERROR; + status = p_port->p_adapter->p_ifc->modify_qp( p_port->ib_mgr.h_qp, &qp_mod ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_modify_qp to error state returned %s.\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + p_port->p_adapter->hung = TRUE; + return; + } + + KeResetEvent(&p_port->leave_mcast_event); + + /* Reset all endpoints so we don't flush our ARP cache. */ + __endpt_mgr_reset_all( p_port ); + + KeWaitForSingleObject( + &p_port->leave_mcast_event, Executive, KernelMode, FALSE, NULL ); + + __pending_list_destroy(p_port); + + cl_obj_lock( &p_port->p_adapter->obj ); + ipoib_dereg_addrs( p_port->p_adapter ); + cl_obj_unlock( &p_port->p_adapter->obj ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +__bcast_cb( + IN ib_mcast_rec_t *p_mcast_rec ) +{ + ipoib_port_t *p_port; + ib_api_status_t status; + LARGE_INTEGER gc_due_time; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_port = (ipoib_port_t*)p_mcast_rec->mcast_context; + + cl_obj_lock( &p_port->obj ); + + ASSERT(p_port->state == IB_QPS_INIT || p_port->state == IB_QPS_ERROR); + if( p_port->state != IB_QPS_INIT ) + { + cl_obj_unlock( &p_port->obj ); + if( p_mcast_rec->status == IB_SUCCESS ) + { + ipoib_port_ref(p_port, ref_leave_mcast); + p_port->p_adapter->p_ifc->leave_mcast( p_mcast_rec->h_mcast, __leave_error_mcast_cb ); + } + KeSetEvent( &p_port->sa_event, EVENT_INCREMENT, FALSE ); + ipoib_port_deref( p_port, ref_bcast_inv_state ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Invalid state - Aborting.\n") ); + return; + } + status = p_mcast_rec->status; + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Multicast join for broadcast group returned %s.\n", + p_port->p_adapter->p_ifc->get_err_str( p_mcast_rec->status )) ); + if( status == IB_REMOTE_ERROR ) + { + /* + * Either: + * - the join failed because the group no longer exists + * - the create failed because the group already exists + * + * Kick off a new Get query to the SA to restart the join process + * from the top. Note that as an optimization, it would be + * possible to distinguish between the join and the create. + * If the join fails, try the create. If the create fails, start + * over with the Get. + */ + /* TODO: Assert is a place holder. Can we ever get here if the + state isn't IB_PNP_PORT_ADD or PORT_DOWN or PORT_INIT? */ + CL_ASSERT( p_port->p_adapter->state == IB_PNP_PORT_ADD || + p_port->p_adapter->state == IB_PNP_PORT_DOWN || + p_port->p_adapter->state == IB_PNP_PORT_INIT ); + if(++p_port->bc_join_retry_cnt < p_port->p_adapter->params.bc_join_retry) + { + status = __port_get_bcast( p_port ); + } + else + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_BCAST_JOIN, 1, p_mcast_rec->status ); + p_port->bc_join_retry_cnt = 0; + } + } + else + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_BCAST_JOIN, 1, p_mcast_rec->status ); + } + + cl_obj_unlock( &p_port->obj ); + if( status != IB_SUCCESS ) + { + ipoib_set_inactive( p_port->p_adapter ); + __endpt_mgr_reset_all( p_port ); + ASSERT(p_port->state == IB_QPS_INIT || p_port->state == IB_QPS_ERROR); + p_port->state = IB_QPS_ERROR; + KeSetEvent( &p_port->sa_event, EVENT_INCREMENT, FALSE ); + } + ipoib_port_deref( p_port, ref_bcast_req_failed ); + IPOIB_EXIT( IPOIB_DBG_INIT ); + return; + } + p_port->bc_join_retry_cnt = 0; + + while( p_port->endpt_rdr ) + { + cl_obj_unlock( &p_port->obj ); + cl_obj_lock( &p_port->obj ); + } + + if( !p_port->p_local_endpt ) + { + ib_port_info_t port_info; + cl_memclr(&port_info, sizeof(port_info)); + port_info.base_lid = p_port->base_lid; + status = __endpt_mgr_add_local( p_port, &port_info ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__endpt_mgr_add_local returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + cl_obj_unlock( &p_port->obj ); + goto err; + } + } + + cl_obj_unlock( &p_port->obj ); + + status = __endpt_mgr_add_bcast( p_port, p_mcast_rec ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__endpt_mgr_add_bcast returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + ipoib_port_ref(p_port, ref_leave_mcast); + status = p_port->p_adapter->p_ifc->leave_mcast( p_mcast_rec->h_mcast, __leave_error_mcast_cb ); + CL_ASSERT( status == IB_SUCCESS ); + goto err; + } + + /* Get the QP ready for action. */ + status = __ib_mgr_activate( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__ib_mgr_activate returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + +err: + /* Flag the adapter as hung. */ + p_port->p_adapter->hung = TRUE; + ASSERT(p_port->state == IB_QPS_INIT || p_port->state == IB_QPS_ERROR); + p_port->state = IB_QPS_ERROR; + KeSetEvent( &p_port->sa_event, EVENT_INCREMENT, FALSE ); + ipoib_port_deref( p_port, ref_bcast_error ); + IPOIB_EXIT( IPOIB_DBG_INIT ); + return; + } + + cl_obj_lock( &p_port->obj ); + /* Only change the state if we're still in INIT. */ + ASSERT( p_port->state == IB_QPS_INIT || p_port->state == IB_QPS_ERROR); + if (p_port->state == IB_QPS_INIT) { + p_port->state = IB_QPS_RTS; + } + cl_obj_unlock( &p_port->obj ); + + /* Prepost receives. */ + cl_spinlock_acquire( &p_port->recv_lock ); + __recv_mgr_repost( p_port ); + cl_spinlock_release( &p_port->recv_lock ); + + /* Notify the adapter that we now have an active connection. */ + status = ipoib_set_active( p_port->p_adapter ); + if( status != IB_SUCCESS ) + { + ib_qp_mod_t qp_mod; + ipoib_set_inactive( p_port->p_adapter ); + KeSetEvent( &p_port->sa_event, EVENT_INCREMENT, FALSE ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("ipoib_set_active returned %s.\n",p_port->p_adapter->p_ifc->get_err_str( status ))); + cl_spinlock_acquire( &p_port->recv_lock ); + cl_obj_lock( &p_port->obj ); + p_port->state = IB_QPS_ERROR; + if( p_port->ib_mgr.h_query ) + { + p_port->p_adapter->p_ifc->cancel_query( + p_port->p_adapter->h_al, p_port->ib_mgr.h_query ); + p_port->ib_mgr.h_query = NULL; + } + cl_obj_unlock( &p_port->obj ); + cl_spinlock_release( &p_port->recv_lock ); + + CL_ASSERT( p_port->ib_mgr.h_qp ); + cl_memclr( &qp_mod, sizeof(ib_qp_mod_t) ); + qp_mod.req_state = IB_QPS_ERROR; + status = p_port->p_adapter->p_ifc->modify_qp( p_port->ib_mgr.h_qp, &qp_mod ); + __endpt_mgr_reset_all( p_port ); + + ipoib_port_deref( p_port, ref_join_bcast ); + return; + } +#if 0 //CM + if( p_port->p_adapter->params.cm_enabled && + !p_port->p_local_endpt->conn.h_cm_listen ) + { + if( ipoib_port_listen( p_port ) != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Port CM Listen failed\n" ) ); + /*keep going with UD only */ + p_port->p_adapter->params.cm_enabled = FALSE; + + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_CONNECTED_MODE_ERR, 1, 0xbadc0de3 ); + } + } +#endif + /* garbage collector timer is needed when link is active */ + gc_due_time.QuadPart = -(int64_t)(((uint64_t)p_port->p_adapter->params.mc_leave_rescan * 2000000) * 10); + KeSetTimerEx(&p_port->gc_timer,gc_due_time, + (LONG)p_port->p_adapter->params.mc_leave_rescan*1000,&p_port->gc_dpc); + + KeSetEvent( &p_port->sa_event, EVENT_INCREMENT, FALSE ); + ipoib_port_deref( p_port, ref_join_bcast ); + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +__qp_event( + IN ib_async_event_rec_t *p_event_rec ) +{ + UNUSED_PARAM( p_event_rec ); + CL_ASSERT( p_event_rec->context ); + ((ipoib_port_t*)p_event_rec->context)->p_adapter->hung = TRUE; +} + + +static void +__cq_event( + IN ib_async_event_rec_t *p_event_rec ) +{ + UNUSED_PARAM( p_event_rec ); + CL_ASSERT( p_event_rec->context ); + ((ipoib_port_t*)p_event_rec->context)->p_adapter->hung = TRUE; +} + + +static ib_api_status_t +__ib_mgr_activate( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t status; + ib_dgrm_info_t dgrm_info; + ib_qp_mod_t qp_mod; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + /* + * Move the QP to RESET. This allows us to reclaim any + * unflushed receives. + */ + cl_memclr( &qp_mod, sizeof(ib_qp_mod_t) ); + qp_mod.req_state = IB_QPS_RESET; + status = p_port->p_adapter->p_ifc->modify_qp( p_port->ib_mgr.h_qp, &qp_mod ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_modify_qp returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Move the QP to RTS. */ + dgrm_info.port_guid = p_port->p_adapter->guids.port_guid.guid; + dgrm_info.qkey = p_port->ib_mgr.bcast_rec.qkey; + dgrm_info.pkey_index = p_port->pkey_index; + status = p_port->p_adapter->p_ifc->init_dgrm_svc( p_port->ib_mgr.h_qp, &dgrm_info ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_init_dgrm_svc returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Rearm the CQs. */ + status = p_port->p_adapter->p_ifc->rearm_cq( p_port->ib_mgr.h_recv_cq, FALSE ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_rearm_cq for recv returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + status = p_port->p_adapter->p_ifc->rearm_cq( p_port->ib_mgr.h_send_cq, FALSE ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_rearm_cq for send returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return IB_SUCCESS; +} + + +/* Transition to a passive level thread. */ +ib_api_status_t +ipoib_port_join_mcast( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + IN const uint8_t state) +{ + ib_api_status_t status; + ib_mcast_req_t mcast_req; + ipoib_endpt_t *p_endpt; + + IPOIB_ENTER( IPOIB_DBG_MCAST ); + + switch( __endpt_mgr_ref( p_port, mac, &p_endpt ) ) + { + case NDIS_STATUS_NO_ROUTE_TO_DESTINATION: + break; + + case NDIS_STATUS_SUCCESS: + ipoib_endpt_deref( p_endpt ); + /* Fall through */ + + case NDIS_STATUS_PENDING: + return IB_SUCCESS; + } + + /* + * Issue the mcast request, using the parameters of the broadcast group. + * This allows us to do a create request that should always succeed since + * the required parameters are known. + */ + cl_memclr( &mcast_req, sizeof(mcast_req) ); + mcast_req.create = TRUE; + + /* Copy the settings from the broadcast group. */ + mcast_req.member_rec = p_port->ib_mgr.bcast_rec; + /* Clear fields that aren't specified in the join */ + mcast_req.member_rec.mlid = 0; + ib_member_set_state( &mcast_req.member_rec.scope_state,state); + + if( (mac.addr[0] == 1) && (mac.addr[2] == 0x5E )) + { + /* + * Update the address portion of the MGID with the 28 lower bits of the + * IP address. Since we're given a MAC address, we are using + * 24 lower bits of that network-byte-ordered value (assuming MSb + * is zero) and 4 lsb bits of the first byte of IP address. + */ + mcast_req.member_rec.mgid.raw[12] = mac.addr[1]; + mcast_req.member_rec.mgid.raw[13] = mac.addr[3]; + mcast_req.member_rec.mgid.raw[14] = mac.addr[4]; + mcast_req.member_rec.mgid.raw[15] = mac.addr[5]; + } + else + { + /* Handle non IP mutlicast MAC addresses. */ + /* Update the signature to use the lower 2 bytes of the OpenIB OUI. */ + mcast_req.member_rec.mgid.raw[2] = 0x14; + mcast_req.member_rec.mgid.raw[3] = 0x05; + /* Now copy the MAC address into the last 6 bytes of the GID. */ + cl_memcpy( &mcast_req.member_rec.mgid.raw[10], mac.addr, 6 ); + } + + mcast_req.mcast_context = p_port; + mcast_req.pfn_mcast_cb = __mcast_cb; + mcast_req.timeout_ms = p_port->p_adapter->params.sa_timeout; + mcast_req.retry_cnt = p_port->p_adapter->params.sa_retry_cnt; + mcast_req.port_guid = p_port->p_adapter->guids.port_guid.guid; + mcast_req.pkey_index = p_port->pkey_index; + mcast_req.member_rec.pkey = cl_hton16(p_port->p_adapter->guids.port_guid.pkey); + /* + * Create the endpoint and insert it in the port. Since we don't wait for + * the mcast SA operations to complete before returning from the multicast + * list set OID asynchronously, it is possible for the mcast entry to be + * cleared before the SA interaction completes. In this case, when the + * mcast callback is invoked, it would not find the corresponding endpoint + * and would be undone. + */ + p_endpt = ipoib_endpt_create( + &mcast_req.member_rec.mgid, 0, CL_HTON32(0x00FFFFFF) ); + if( !p_endpt ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_endpt_create failed.\n") ); + return IB_INSUFFICIENT_MEMORY; + } + + status = __endpt_mgr_insert_locked( p_port, mac, p_endpt ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__endpt_mgr_insert_locked returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* reference the object for the multicast join request. */ + ipoib_port_ref( p_port, ref_join_mcast ); + + status = p_port->p_adapter->p_ifc->join_mcast( p_port->ib_mgr.h_qp, &mcast_req ); + if( status != IB_SUCCESS ) + { + ipoib_port_deref( p_port, ref_mcast_join_failed ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_join_mcast returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + } + + IPOIB_EXIT( IPOIB_DBG_MCAST ); + return status; +} + + +static void +__mcast_cb( + IN ib_mcast_rec_t *p_mcast_rec ) +{ + ib_api_status_t status; + ipoib_port_t *p_port; + cl_fmap_item_t *p_item; + ipoib_endpt_t *p_endpt; + + IPOIB_ENTER( IPOIB_DBG_MCAST ); + + p_port = (ipoib_port_t*)p_mcast_rec->mcast_context; + + cl_obj_lock( &p_port->obj ); + while( p_port->endpt_rdr ) + { + cl_obj_unlock( &p_port->obj ); + cl_obj_lock( &p_port->obj ); + } + if( p_port->state != IB_QPS_RTS ) + { + cl_obj_unlock( &p_port->obj ); + if( p_mcast_rec->status == IB_SUCCESS ) + + { + ipoib_port_ref(p_port, ref_leave_mcast); + p_port->p_adapter->p_ifc->leave_mcast( p_mcast_rec->h_mcast, __leave_error_mcast_cb ); + } + ipoib_port_deref( p_port, ref_mcast_inv_state ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Invalid state - Aborting.\n") ); + return; + } + + if( p_mcast_rec->status != IB_SUCCESS ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Multicast join request failed with status %s.\n", + p_port->p_adapter->p_ifc->get_err_str( p_mcast_rec->status )) ); + /* Flag the adapter as hung. */ + p_port->p_adapter->hung =TRUE; + ipoib_port_deref( p_port, ref_mcast_req_failed ); + IPOIB_EXIT( IPOIB_DBG_MCAST ); + return; + } + + p_item = cl_fmap_get( + &p_port->endpt_mgr.gid_endpts, &p_mcast_rec->p_member_rec->mgid ); + if( p_item == cl_fmap_end( &p_port->endpt_mgr.gid_endpts ) ) + { + /* + * The endpoint must have been flushed while the join request + * was outstanding. Just leave the group and return. This + * is not an error. + */ + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT(TRACE_LEVEL_WARNING, IPOIB_DBG_ERROR, + ("Failed to find endpoint for update.\n") ); + + ipoib_port_ref(p_port, ref_leave_mcast); + p_port->p_adapter->p_ifc->leave_mcast( p_mcast_rec->h_mcast, __leave_error_mcast_cb ); + ipoib_port_deref( p_port, ref_mcast_no_endpt ); + IPOIB_EXIT( IPOIB_DBG_MCAST ); + return; + } + + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, gid_item ); + p_endpt->p_ifc = p_port->p_adapter->p_ifc; + + /* Setup the endpoint for use. */ + status = ipoib_endpt_set_mcast( + p_endpt, p_port->ib_mgr.h_pd, p_port->port_num, p_mcast_rec ); + if( status != IB_SUCCESS ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_MCAST, + ("ipoib_endpt_set_mcast returned %s.\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + /* Flag the adapter as hung. */ + p_port->p_adapter->hung = TRUE; + ipoib_port_deref( p_port, ref_mcast_av_failed ); + IPOIB_EXIT( IPOIB_DBG_MCAST ); + return; + } + + /* + * The endpoint is already in the GID and MAC maps. + * mast endpoint are not used in the LID map. + */ + CL_ASSERT(p_endpt->dlid == 0); + /* set flag that endpoint is use */ + p_endpt->is_in_use = TRUE; + cl_obj_unlock( &p_port->obj ); + + /* Try to send all pending sends. */ + ipoib_port_resume( p_port , FALSE); + + ipoib_port_deref( p_port, ref_join_mcast ); + + IPOIB_EXIT( IPOIB_DBG_MCAST ); +} + + +void +ipoib_leave_mcast_cb( + IN void *context ) +{ + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_MCAST ); + + p_port = (ipoib_port_t*)context; + + IPOIB_PRINT( TRACE_LEVEL_VERBOSE, IPOIB_DBG_MCAST,("p_port->mcast_cnt = %d\n", p_port->mcast_cnt)); + + ipoib_port_deref( p_port, ref_leave_mcast); + cl_atomic_dec( &p_port->mcast_cnt); + + if(0 == p_port->mcast_cnt) + { + KeSetEvent( &p_port->leave_mcast_event, EVENT_INCREMENT, FALSE ); + } + + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_MCAST, + ("Leave mcast callback deref ipoib_port \n") ); + + IPOIB_EXIT( IPOIB_DBG_MCAST ); +} + + + +void +__leave_error_mcast_cb( + IN void *context ) +{ + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_MCAST ); + + p_port = (ipoib_port_t*)context; + + ipoib_port_deref( p_port, ref_leave_mcast); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_MCAST, + ("Leave mcast callback deref ipoib_port \n") ); + + IPOIB_EXIT( IPOIB_DBG_MCAST ); +} + +/*++ +Routine Description: + The routine process the packet and returns LSO information + +Arguments: + pNetBuffer - a pointer to the first net buffer object of the packet + TcpHeaderOffset - offset to the begining of the TCP header in the packet + pLsoData - pointer to LsoData object in which the routine returns the LSO information + pHeaderSize - pointer to ULONG object in which the header size is returned + IndexOfData - + +Return Value: + NDIS_STATUS + +NOTE: + called at DISPATCH level +--*/ + +NDIS_STATUS GetLsoHeaderSize( + IN PNET_BUFFER pNetBuffer, + IN LsoData *pLsoData, + OUT UINT *IndexOfData, + IN ipoib_hdr_t *ipoib_hdr + ) +{ + UINT CurrLength; + PUCHAR pSrc; + PUCHAR pCopiedData = pLsoData->coppied_data; + ip_hdr_t UNALIGNED *IpHdr; + tcp_hdr_t UNALIGNED *TcpHdr; + uint16_t TcpHeaderLen; + uint16_t IpHeaderLen; + uint16_t IpOffset; + INT FullBuffers = 0; + PMDL pMDL; + NDIS_STATUS status = NDIS_STATUS_INVALID_PACKET; + + +#define IP_OFFSET 14; + // + // This Flag indicates the way we gets the headers + // RegularFlow = we get the headers (ETH+IP+TCP) in the same Buffer + // in sequence. + // + boolean_t IsRegularFlow = TRUE; + + const uint16_t ETH_OFFSET = IP_OFFSET; + + pLsoData->LsoHeaderSize = 0; + IpOffset = IP_OFFSET; //(uint16_t)pPort->EncapsulationFormat.EncapsulationHeaderSize; + *IndexOfData = 0; + + pMDL = NET_BUFFER_CURRENT_MDL(pNetBuffer); + NdisQueryMdl(pMDL, &pSrc, &CurrLength, NormalPagePriority); + //NdisQueryBufferSafe( CurrBuffer, &pSrc, &CurrLength, NormalPagePriority ); + if (pSrc == NULL) { + IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, ("Error processing packets\n")); + return status; + } + // We start by looking for the ethernet and the IP + if (CurrLength < ETH_OFFSET) { + IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, ("Error porcessing packets\n")); + return status; + } + //pLsoData->LsoHeaderSize = pLsoData->LsoHeaderSize + ETH_OFFSET; + if (CurrLength == ETH_OFFSET) { + ASSERT(FALSE); + IsRegularFlow = FALSE; + memcpy(pCopiedData, pSrc, ETH_OFFSET); + pCopiedData += ETH_OFFSET; + FullBuffers++; + // First buffer was only ethernet + pNetBuffer = NET_BUFFER_NEXT_NB(pNetBuffer); + NdisQueryMdl(NET_BUFFER_CURRENT_MDL(pNetBuffer), &pSrc, &CurrLength, NormalPagePriority); + if (pSrc == NULL) { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, ("Error porcessing packets\n")); + return status; + } + } else { + // This is ETH + IP together (at least) + pLsoData->LsoBuffers[0].pData = pSrc + (ETH_OFFSET - sizeof (ipoib_hdr_t)); + memcpy (pLsoData->LsoBuffers[0].pData, ipoib_hdr, sizeof (ipoib_hdr_t)); + CurrLength -= ETH_OFFSET; + pSrc = pSrc + ETH_OFFSET; + pLsoData->LsoHeaderSize = pLsoData->LsoHeaderSize + sizeof (ipoib_hdr_t); + } + // we should now be having at least the size of ethernet data + if (CurrLength < sizeof (ip_hdr_t)) { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, ("Error porcessing packets\n")); + return status; + } + IpHdr = (ip_hdr_t UNALIGNED*)pSrc; + IpHeaderLen = (uint16_t)IP_HEADER_LENGTH(IpHdr); + ASSERT(IpHdr->prot == IP_PROT_TCP); + if (CurrLength < IpHeaderLen) { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, ("Error processing packets\n")); + return status; + } + pLsoData->LsoHeaderSize = pLsoData->LsoHeaderSize + IpHeaderLen; + // We now start to find where the TCP header starts + if (CurrLength == IpHeaderLen) { + ASSERT(FALSE); + // two options : + // if(IsRegularFlow = FALSE) ==> ETH and IP seperated in two buffers + // if(IsRegularFlow = TRUE ) ==> ETH and IP in the same buffer + // TCP will start at next buffer + if(IsRegularFlow){ + memcpy(pCopiedData, pSrc-ETH_OFFSET ,ETH_OFFSET+IpHeaderLen); + pCopiedData += (ETH_OFFSET + IpHeaderLen); + } else { + memcpy(pCopiedData, pSrc,IpHeaderLen); + pCopiedData += IpHeaderLen; + } + + FullBuffers++; + IsRegularFlow = FALSE; + //NdisGetNextBuffer( CurrBuffer, &CurrBuffer); + //NdisQueryBufferSafe( CurrBuffer, &pSrc, &CurrLength, NormalPagePriority ); + pNetBuffer = NET_BUFFER_NEXT_NB(pNetBuffer); + NdisQueryMdl(NET_BUFFER_CURRENT_MDL(pNetBuffer), &pSrc, &CurrLength, NormalPagePriority); + if (pSrc == NULL) { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, ("Error porcessing packets\n")); + return status; + } + } else { + // if(IsRegularFlow = TRUE ) ==> the ETH and IP and TCP in the same buffer + // if(IsRegularFlow = FLASE ) ==> ETH in one buffer , IP+TCP together in the same buffer + if (IsRegularFlow) { + pLsoData->LsoBuffers[0].Len += IpHeaderLen; + } else { + memcpy(pCopiedData, pSrc, IpHeaderLen); + pCopiedData += IpHeaderLen; + } + + CurrLength -= IpHeaderLen; + pSrc = pSrc + IpHeaderLen; + } + if (CurrLength < sizeof (tcp_hdr_t)) { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, ("Error porcessing packets\n")); + return status; + } + // We have finaly found the TCP header + TcpHdr = (tcp_hdr_t UNALIGNED *)pSrc; + TcpHeaderLen = TCP_HEADER_LENGTH(TcpHdr); + + //ASSERT(TcpHeaderLen == 20); + + if (CurrLength < TcpHeaderLen) { + //IPOIB_PRINT(TRACE_LEVEL_VERBOSE, ETH, ("Error porcessing packets\n")); + return status; + } + pLsoData->LsoHeaderSize = pLsoData->LsoHeaderSize + TcpHeaderLen; + if(IsRegularFlow){ + pLsoData->LsoBuffers[0].Len += TcpHeaderLen; + } + else{ + memcpy(pCopiedData, pSrc, TcpHeaderLen); + pCopiedData += TcpHeaderLen; + } + if (CurrLength == TcpHeaderLen) { + FullBuffers++; + pLsoData->UsedBuffers = FullBuffers; + *IndexOfData = FullBuffers ; + } else { + pLsoData->UsedBuffers = FullBuffers + 1; + *IndexOfData = FullBuffers - 1; + } + pLsoData->FullBuffers = FullBuffers; + if (!IsRegularFlow){ + pLsoData->LsoBuffers[0].pData = pLsoData->coppied_data; + pLsoData->LsoBuffers[0].Len = ETH_OFFSET + IpHeaderLen + TcpHeaderLen; + ASSERT(pLsoData->LsoBuffers[0].Len <= LSO_MAX_HEADER); + } + return NDIS_STATUS_SUCCESS; +} + +static void __port_do_mcast_garbage(ipoib_port_t* const p_port) +{ + const mac_addr_t DEFAULT_MCAST_GROUP = {0x01, 0x00, 0x5E, 0x00, 0x00, 0x01}; + /* Do garbage collecting... */ + + cl_map_item_t *p_item; + ipoib_endpt_t *p_endpt; + cl_qlist_t destroy_mc_list; + uint8_t cnt; + const static GC_MAX_LEAVE_NUM = 80; + + cl_qlist_init( &destroy_mc_list ); + + cl_obj_lock( &p_port->obj ); + /* Wait for all readers to finish */ + while( p_port->endpt_rdr ) + { + cl_obj_unlock( &p_port->obj ); + cl_obj_lock( &p_port->obj ); + } + cnt = 0; + p_item = cl_qmap_head( &p_port->endpt_mgr.mac_endpts ); + while( (p_item != cl_qmap_end( &p_port->endpt_mgr.mac_endpts )) && (cnt < GC_MAX_LEAVE_NUM)) + { + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, mac_item ); + p_item = cl_qmap_next( p_item ); + + /* Check if the current endpoint is not a multicast listener */ + + if( p_endpt->h_mcast && + (!p_endpt->is_mcast_listener) && + ( cl_memcmp( &p_endpt->mac, &DEFAULT_MCAST_GROUP, sizeof(mac_addr_t) ) && + (!p_endpt->is_in_use) )) + { + cl_qmap_remove_item( &p_port->endpt_mgr.mac_endpts, + &p_endpt->mac_item ); + cl_fmap_remove_item( &p_port->endpt_mgr.gid_endpts, + &p_endpt->gid_item ); + + if( p_endpt->dlid ) + { + cl_qmap_remove_item( &p_port->endpt_mgr.lid_endpts, + &p_endpt->lid_item ); + p_endpt->dlid = 0; + } + + cl_qlist_insert_tail( + &destroy_mc_list, &p_endpt->mac_item.pool_item.list_item ); + cnt++; + } + else + p_endpt->is_in_use = FALSE; + } + cl_obj_unlock( &p_port->obj ); + + /* Destroy all multicast endpoints now that we have released the lock. */ + while( cl_qlist_count( &destroy_mc_list ) ) + { + p_endpt = PARENT_STRUCT( cl_qlist_remove_head( &destroy_mc_list ), + ipoib_endpt_t, mac_item.pool_item.list_item ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("mcast garbage collector: destroying endpoint %02x:%02x:%02x:%02x:%02x:%02x \n", + p_endpt->mac.addr[0], + p_endpt->mac.addr[1], + p_endpt->mac.addr[2], + p_endpt->mac.addr[3], + p_endpt->mac.addr[4], + p_endpt->mac.addr[5]) ); + cl_obj_destroy( &p_endpt->obj ); + } +} + +static void __port_mcast_garbage_dpc(KDPC *p_gc_dpc,void *context,void *s_arg1, void *s_arg2) +{ + ipoib_port_t *p_port = context; + + UNREFERENCED_PARAMETER(p_gc_dpc); + UNREFERENCED_PARAMETER(s_arg1); + UNREFERENCED_PARAMETER(s_arg2); + + __port_do_mcast_garbage(p_port); +} + +ipoib_endpt_t* +ipoib_endpt_get_by_gid( + IN ipoib_port_t* const p_port, + IN const ib_gid_t* const p_gid ) +{ + return __endpt_mgr_get_by_gid( p_port, p_gid ); +} + +ipoib_endpt_t* +ipoib_endpt_get_by_lid( + IN ipoib_port_t* const p_port, + IN const net16_t lid ) +{ + return __endpt_mgr_get_by_lid( p_port, lid ); +} + +ib_api_status_t +ipoib_recv_dhcp( + IN ipoib_port_t* const p_port, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src, + IN ipoib_endpt_t* const p_dst ) +{ + return __recv_dhcp( + p_port, p_ipoib, p_eth, p_src,p_dst ); +} + + +void +ipoib_port_cancel_xmit( + IN ipoib_port_t* const p_port, + IN PVOID cancel_id ) +{ + cl_list_item_t *p_item; + PNET_BUFFER_LIST p_nbl; + PVOID nbl_id; + cl_qlist_t cancel_list; + ULONG send_complete_flags = 0; + IPOIB_ENTER( IPOIB_DBG_SEND ); + + cl_qlist_init( &cancel_list ); + + cl_spinlock_acquire( &p_port->send_lock ); + + for( p_item = cl_qlist_head( &p_port->send_mgr.pending_list ); + p_item != cl_qlist_end( &p_port->send_mgr.pending_list ); + p_item = cl_qlist_next( p_item ) ) + { + p_nbl = IPOIB_PACKET_FROM_LIST_ITEM( p_item ); + nbl_id = NDIS_GET_NET_BUFFER_LIST_CANCEL_ID( p_nbl ); + if( nbl_id == cancel_id ) + { + cl_qlist_remove_item( &p_port->send_mgr.pending_list, p_item ); + NET_BUFFER_LIST_STATUS( p_nbl) = NDIS_STATUS_REQUEST_ABORTED ; + cl_qlist_insert_tail( &cancel_list, IPOIB_LIST_ITEM_FROM_PACKET( p_nbl ) ); + } + } + cl_spinlock_release( &p_port->send_lock ); + + if( cl_qlist_count( &cancel_list ) ) + { + while( ( p_item = cl_qlist_remove_head( &cancel_list )) + != cl_qlist_end( &cancel_list )) + { + p_nbl = IPOIB_PACKET_FROM_LIST_ITEM( p_item ); + NET_BUFFER_LIST_STATUS( p_nbl) = NDIS_STATUS_SEND_ABORTED; + send_complete_flags = 0; + if (NDIS_CURRENT_IRQL() == DISPATCH_LEVEL) + { + NDIS_SET_SEND_COMPLETE_FLAG(send_complete_flags, NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL); + } + NdisMSendNetBufferListsComplete( p_port->p_adapter->h_adapter, + p_nbl, send_complete_flags ); + } + } + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + +/* +* Put all fragments into separate WR and chain together. +* The last WR will be set to generate CQ Event. +* lookaside buffer is used for ipoib and ip headers attached to each WR. +* Buffer will be released on last WR send completion. +*/ +#if 0 +static NDIS_STATUS +__send_fragments( +IN ipoib_port_t* const p_port, +IN ipoib_send_desc_t* const p_desc, +IN eth_hdr_t* const p_eth_hdr, +IN ip_hdr_t* const p_ip_hdr, +IN uint32_t buf_len, +IN NDIS_BUFFER* p_ndis_buf ) +{ + uint32_t ds_idx = 1; + uint32_t wr_idx = 0; + uint32_t sgl_idx = 2; //skip eth hdr, ip hdr + uint32_t options_len = 0; + uint8_t* p_options = NULL; + uint8_t* p_buf; + uint32_t frag_offset = 0; + uint32_t next_sge; + uint32_t wr_size = 0; + uint32_t ip_hdr_len = IP_HEADER_LENGTH( p_ip_hdr ); + uint32_t total_ip_len = cl_ntoh16( p_ip_hdr->length ); + + SCATTER_GATHER_LIST *p_sgl; + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + if( IP_DONT_FRAGMENT(p_ip_hdr) ) + return NDIS_STATUS_INVALID_PACKET; + + p_sgl = NDIS_PER_PACKET_INFO_FROM_PACKET( p_desc->p_pkt, ScatterGatherListPacketInfo ); + if( !p_sgl ) + { + ASSERT( p_sgl ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get SGL from packet.\n") ); + return NDIS_STATUS_FAILURE; + } + if( ( p_sgl->NumberOfElements > MAX_SEND_SGE || + p_sgl->Elements[0].Length < sizeof(eth_hdr_t)) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Too many SG Elements in packet.\n") ); + return NDIS_STATUS_FAILURE; + } + p_buf = (uint8_t *) + ExAllocateFromNPagedLookasideList( &p_port->buf_mgr.send_buf_list ); + if( !p_buf ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate lookaside buffer.\n") ); + return NDIS_STATUS_RESOURCES; + } + p_desc->p_buf = (send_buf_t*)p_buf; + + if( buf_len < ip_hdr_len ) + { /* ip options in a separate buffer */ + CL_ASSERT( buf_len == sizeof( ip_hdr_t ) ); + NdisGetNextBuffer( p_ndis_buf, &p_ndis_buf ); + if( !p_ndis_buf ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get IP options buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + NdisQueryBufferSafe( p_ndis_buf, &p_options, &options_len, NormalPagePriority ); + if( !p_options ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query IP options buffer address.\n") ); + return NDIS_STATUS_FAILURE; + } + cl_memcpy( p_buf, p_ip_hdr, sizeof( ip_hdr_t ) ); + if( p_options && options_len ) + { + __copy_ip_options( &p_buf[sizeof(ip_hdr_t)], + p_options, options_len, TRUE ); + } + wr_size = buf_len + options_len; + sgl_idx++; + } + else + { /*options probably in the same buffer */ + cl_memcpy( p_buf, p_ip_hdr, buf_len ); + options_len = ip_hdr_len - sizeof( ip_hdr_t ); + if( options_len ) + { + p_options = p_buf + sizeof( ip_hdr_t ); + } + frag_offset += ( buf_len - ip_hdr_len ); + wr_size = buf_len; + } + + p_desc->send_wr[wr_idx].local_ds[ds_idx].vaddr = cl_get_physaddr( p_buf ); + p_desc->send_wr[wr_idx].local_ds[ds_idx].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[wr_idx].local_ds[ds_idx].length = wr_size; + + /* count how much data can be put into the first WR beside IP header. + * other protocols headers possibly supplied in subsequent buffers. + */ + for( sgl_idx; sgl_idx < p_sgl->NumberOfElements; sgl_idx++ ) + { + next_sge = p_sgl->Elements[sgl_idx].Length; + + /* add sgl if it can fit into the same WR + * Note: so far not going to split large SGE between WRs, + * so first fragment could be a smaller size. + */ + if( next_sge <= ( p_port->p_adapter->params.payload_mtu - wr_size ) ) + { + ++ds_idx; + wr_size += next_sge; + frag_offset += next_sge; + p_desc->send_wr[wr_idx].local_ds[ds_idx].vaddr = + p_sgl->Elements[sgl_idx].Address.QuadPart; + p_desc->send_wr[wr_idx].local_ds[ds_idx].length = next_sge; + p_desc->send_wr[wr_idx].local_ds[ds_idx].lkey = p_port->ib_mgr.lkey; + } + else + { + /* fix ip hdr for the first fragment and move on */ + __update_fragment_ip_hdr( (ip_hdr_t* const)p_buf, + (uint16_t)wr_size, IP_FRAGMENT_OFFSET(p_ip_hdr), TRUE ); + + p_desc->send_wr[wr_idx].wr.num_ds = ds_idx + 1; + p_buf += ip_hdr_len; + p_buf += (( buf_len > ip_hdr_len ) ? ( buf_len - ip_hdr_len ): 0); + frag_offset += ( (IP_FRAGMENT_OFFSET(p_ip_hdr)) << 3 ); + ++wr_idx; + ds_idx = 0; + break; + } + } + total_ip_len -= wr_size; + wr_size = 0; + + for( sgl_idx, wr_idx; sgl_idx < p_sgl->NumberOfElements; sgl_idx++ ) + { + uint32_t seg_len; + uint64_t next_sgl_addr; + + if( wr_idx >= ( MAX_WRS_PER_MSG - 1 ) ) + return NDIS_STATUS_RESOURCES; + + next_sge = p_sgl->Elements[sgl_idx].Length; + next_sgl_addr = p_sgl->Elements[sgl_idx].Address.QuadPart; + + while( next_sge ) + { + if( ds_idx == 0 ) + { /* new ipoib + ip header */ + ((ipoib_hdr_t*)p_buf)->type = p_eth_hdr->type; + ((ipoib_hdr_t*)p_buf)->resv = 0; + p_desc->send_wr[wr_idx].local_ds[ds_idx].vaddr = cl_get_physaddr( p_buf ); + p_desc->send_wr[wr_idx].local_ds[ds_idx].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[wr_idx].local_ds[ds_idx].length = sizeof( ipoib_hdr_t ); + p_buf += sizeof( ipoib_hdr_t ); + ++ds_idx; + + cl_memcpy( p_buf, p_ip_hdr, sizeof( ip_hdr_t ) ); + if( p_options && options_len ) + { + /* copy ip options if needed */ + __copy_ip_options( &p_buf[sizeof(ip_hdr_t)], + p_options, options_len, FALSE ); + } + wr_size = ip_hdr_len; + } + if( ds_idx == 1 ) + { + p_desc->send_wr[wr_idx].local_ds[ds_idx].length = ip_hdr_len; + p_desc->send_wr[wr_idx].local_ds[ds_idx].vaddr = cl_get_physaddr( p_buf ); + p_desc->send_wr[wr_idx].local_ds[ds_idx].lkey = p_port->ib_mgr.lkey; + ++ds_idx; + } + + seg_len = ( next_sge > ( p_port->p_adapter->params.payload_mtu - wr_size ) )? + ( p_port->p_adapter->params.payload_mtu - wr_size ) : next_sge; + + p_desc->send_wr[wr_idx].local_ds[ds_idx].vaddr = next_sgl_addr; + p_desc->send_wr[wr_idx].local_ds[ds_idx].length = seg_len; + p_desc->send_wr[wr_idx].local_ds[ds_idx].lkey = p_port->ib_mgr.lkey; + ++ds_idx; + + wr_size += seg_len; + total_ip_len -= seg_len; + + if( wr_size >= p_port->p_adapter->params.payload_mtu || total_ip_len == 0 ) + { /* fix ip hdr for that fragment */ + __update_fragment_ip_hdr( (ip_hdr_t* const)p_buf, (uint16_t)wr_size, + ((uint16_t)(frag_offset >> 3 )), + (BOOLEAN)(( total_ip_len > 0 ) || IP_MORE_FRAGMENTS( p_ip_hdr)) ); + p_desc->send_wr[wr_idx].wr.num_ds = ds_idx; + if( total_ip_len > 0 ) + { + ++wr_idx; + frag_offset += (wr_size - ip_hdr_len); + wr_size = 0; + ds_idx = 0; + p_buf += ip_hdr_len; + } + } + next_sge -= seg_len; + if( next_sge > 0 ) + { + next_sgl_addr += seg_len; + } + } + } + p_desc->num_wrs += wr_idx; + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return NDIS_STATUS_SUCCESS; +} +#endif + +static void +__update_fragment_ip_hdr( +IN ip_hdr_t* const p_ip_hdr, +IN uint16_t fragment_size, +IN uint16_t fragment_offset, +IN BOOLEAN more_fragments ) +{ + uint16_t* p_hdr = (uint16_t*)p_ip_hdr; + p_ip_hdr->length = cl_hton16( fragment_size ); // bytes + p_ip_hdr->offset = cl_hton16( fragment_offset ); // 8-byte units + if( more_fragments ) + { + IP_SET_MORE_FRAGMENTS( p_ip_hdr ); + } + else + { + IP_SET_LAST_FRAGMENT( p_ip_hdr ); + } + p_ip_hdr->chksum = 0; + p_ip_hdr->chksum = ipchksum( p_hdr, IP_HEADER_LENGTH(p_ip_hdr) ); +} + +static void +__copy_ip_options( +IN uint8_t* p_buf, +IN uint8_t* p_options, +IN uint32_t options_len, +IN BOOLEAN copy_all ) +{ + uint32_t option_length; + uint32_t total_length = 0; + uint32_t copied_length = 0; + uint8_t* p_src = p_options; + uint8_t* p_dst = p_buf; + + if( p_options == NULL || options_len == 0 ) + return; + if( copy_all ) + { + cl_memcpy( p_dst, p_src, options_len ); + return; + } + do + { + if( ( *p_src ) == 0 ) // end of options list + { + total_length++; + break; + } + if( ( *p_src ) == 0x1 ) // no op + { + p_src++; + total_length++; + continue; + } + /*from RFC791: + * This option may be used between options, for example, to align + * the beginning of a subsequent option on a 32 bit boundary. + */ + if( copied_length && (copied_length % 4) ) + { + uint32_t align = 4 - (copied_length % 4); + cl_memset( p_dst, 0x1, (size_t)align ); + p_dst += align; + copied_length += align; + } + option_length = *(p_src + 1); + + if( *p_src & 0x80 ) + { + cl_memcpy( p_dst, p_src, option_length ); + p_dst += option_length; + copied_length += option_length; + } + total_length += option_length; + p_src += option_length; + + }while( total_length < options_len ); + + CL_ASSERT( total_length == options_len ); + CL_ASSERT( copied_length <= 40 ); + + /* padding the rest */ + if( options_len > copied_length ) + { + cl_memclr( p_dst, ( options_len - copied_length ) ); + } + return; +} diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib_port.h b/branches/winverbs/ulp/ipoib/kernel6/ipoib_port.h new file mode 100644 index 00000000..602389b5 --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib_port.h @@ -0,0 +1,827 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_port.h 4226 2009-04-06 06:01:03Z xalex $ + */ + + + +#ifndef _IPOIB_PORT_H_ +#define _IPOIB_PORT_H_ + + +#include +#include +#include +#include +#include +#include "ipoib_xfr_mgr.h" +#include "ipoib_endpoint.h" + + +/* + * Define to place receive buffer inline in receive descriptor. + */ +#define IPOIB_INLINE_RECV 1 + +/* + * Invalid pkey index + */ +#define PKEY_INVALID_INDEX 0xFFFF + +/* + * Define to control how transfers are done. When defined as 1, causes + * packets to be sent using NDIS DMA facilities (getting the SGL from the + * packet). When defined as 0, uses the NDIS_BUFFER structures as MDLs + * to get the physical page mappings of the buffers. + */ +#define IPOIB_USE_DMA 1 + + +#define IPOIB_PORT_FROM_PACKET( P ) \ + (((ipoib_port_t**)NET_BUFFER_LIST_MINIPORT_RESERVED(P))[0]) +#define IPOIB_ENDPT_FROM_PACKET( P ) \ + (((ipoib_endpt_t**)NET_BUFFER_LIST_MINIPORT_RESERVED(P))[1]) +#define IPOIB_RECV_FROM_PACKET( P ) \ + (((ipoib_recv_desc_t**)NET_BUFFER_LIST_MINIPORT_RESERVED(P))[1]) + +//TODO to be renamed: IPOIB_NBL_FROM_LIST_ITEM +#define IPOIB_PACKET_FROM_LIST_ITEM( I ) \ + (PARENT_STRUCT( I, NET_BUFFER_LIST, MiniportReserved )) +#define IPOIB_LIST_ITEM_FROM_PACKET( P ) \ + ((cl_list_item_t*)NET_BUFFER_LIST_MINIPORT_RESERVED(P)) + +#define IPOIB_NET_BUFFER_LIST_FROM_NETBUFFER( P ) \ + (((NET_BUFFER_LIST**)NET_BUFFER_MINIPORT_RESERVED(P))[0]) +#define IPOIB_FROM_QUEUE( P ) \ + (((void**)NET_BUFFER_MINIPORT_RESERVED(P))[1]) +#define IPOIB_SEND_FROM_NETBUFFER( P ) \ + (((send_buf_t**)NET_BUFFER_MINIPORT_RESERVED(P))[2]) + + +#define IPOIB_GET_NET_BUFFER_LIST_REF_COUNT(_NetBufferList) ((_NetBufferList->FirstNetBuffer)->MiniportReserved[3]) +#define IPOIB_DEC_NET_BUFFER_LIST_REF_COUNT(_NetBufferList) (*(PULONG)&(_NetBufferList->FirstNetBuffer)->MiniportReserved[3])-- + + +typedef struct _ipoib_ib_mgr +{ + ib_ca_handle_t h_ca; + ib_pd_handle_t h_pd; + ib_cq_handle_t h_recv_cq; + ib_cq_handle_t h_send_cq; + ib_qp_handle_t h_qp; + ib_query_handle_t h_query; + ib_srq_handle_t h_srq; + net32_t qpn; + + ib_mr_handle_t h_mr; + net32_t lkey; + + uint8_t rate; + ib_member_rec_t bcast_rec; + +} ipoib_ib_mgr_t; +/* +* FIELDS +* h_ca +* CA handle for all IB resources. +* +* h_pd +* PD handle for all IB resources. +* +* h_recv_cq +* Recv CQ handle. +* +* h_send_cq +* Send CQ handle. +* +* h_qp +* QP handle for data transfers. +* +* h_query +* Query handle for cancelling SA queries. +* +* h_mr +* Registration handle for all of physical memory. Used for +* send/receive buffers to simplify growing the receive pool. +* +* lkey +* LKey for the memory region. +* +* bcast_rec +* Cached information about the broadcast group, used to specify +* parameters used to join other multicast groups. +*********/ + + +#include +/****s* IPoIB Driver/ipoib_hdr_t +* NAME +* ipoib_hdr_t +* +* DESCRIPTION +* IPoIB packet header. +* +* SYNOPSIS +*/ +typedef struct _ipoib_hdr +{ + net16_t type; + net16_t resv; + +} PACK_SUFFIX ipoib_hdr_t; +/* +* FIELDS +* type +* Protocol type. +* +* resv +* Reserved portion of IPoIB header. +*********/ + +typedef struct _ipoib_arp_pkt +{ + net16_t hw_type; + net16_t prot_type; + uint8_t hw_size; + uint8_t prot_size; + net16_t op; + ipoib_hw_addr_t src_hw; + net32_t src_ip; + ipoib_hw_addr_t dst_hw; + net32_t dst_ip; + +} PACK_SUFFIX ipoib_arp_pkt_t; + + +/****s* IPoIB Driver/ipoib_pkt_t +* NAME +* ipoib_pkt_t +* +* DESCRIPTION +* Represents an IPoIB packet with no GRH. +* +* SYNOPSIS +*/ +typedef struct _ipoib_pkt +{ + ipoib_hdr_t hdr; + union _payload + { + uint8_t data[MAX_UD_PAYLOAD_MTU]; + ipoib_arp_pkt_t arp; + ip_pkt_t ip; + + } PACK_SUFFIX type; + +} PACK_SUFFIX ipoib_pkt_t; +/* +* FIELDS +* hdr +* IPoIB header. +* +* type +* Union for different types of payloads. +* +* type.data +* raw packet. +* +* type.ib_arp +* IPoIB ARP packet. +* +* type.arp +* Ethernet ARP packet. +* +* type.ip +* IP packet. +*********/ + + +/****s* IPoIB Driver/recv_buf_t +* NAME +* recv_buf_t +* +* DESCRIPTION +* Represents a receive buffer, including the ethernet header +* used to indicate the receive to the OS. +* +* SYNOPSIS +*/ +typedef union _recv_buf +{ + struct _recv_buf_type_eth + { + uint8_t pad[sizeof(ib_grh_t) + + sizeof(ipoib_hdr_t) - + sizeof(eth_hdr_t)]; + eth_pkt_t pkt; /* data starts at sizeof(grh)+sizeof(eth_hdr) */ + + } PACK_SUFFIX eth; + + struct _recv_buf_type_ib + { + ib_grh_t grh; /* Must be same offset as lcl_rt.ib.pkt */ + ipoib_pkt_t pkt; /* data starts at 10+grh+4 */ + + } PACK_SUFFIX ib; + +} PACK_SUFFIX recv_buf_t; +/* +* FIELDS +* eth.pkt +* Ethernet packet, used to indicate the receive to the OS. +* +* ib.grh +* GRH for a globally routed received packet. +* +* ib.pkt +* IPOIB packet representing a globally routed received packet. +* +* NOTES +* When posting the work request, the address of ib.grh is used. +* +* TODO: Do we need a pad to offset the header so that the data ends up +* aligned on a pointer boundary? +*********/ + +/****s* IPoIB Driver/send_buf_t +* NAME +* send_buf_t +* +* DESCRIPTION +* Represents a send buffer, used to convert packets to IPoIB format. +* +* SYNOPSIS +*/ +typedef union _send_buf +{ + uint8_t data[MAX_UD_PAYLOAD_MTU]; + ipoib_arp_pkt_t arp; + ip_pkt_t ip; + +} PACK_SUFFIX send_buf_t; +/* +* FIELDS +* data +* IP/ARP packet. +* +* NOTES +* TODO: Do we need a pad to offset the header so that the data ends up +* aligned on a pointer boundary? +*********/ +#include + + +typedef struct _ipoib_buf_mgr +{ + cl_qpool_t recv_pool; + + NDIS_HANDLE h_packet_pool; + NDIS_HANDLE h_buffer_pool; + + NPAGED_LOOKASIDE_LIST send_buf_list; + NDIS_HANDLE h_send_pkt_pool; + NDIS_HANDLE h_send_buf_pool; + +} ipoib_buf_mgr_t; +/* +* FIELDS +* recv_pool +* Pool of ipoib_recv_desc_t structures. +* +* h_packet_pool +* NDIS packet pool, used to indicate receives to NDIS. +* +* h_buffer_pool +* NDIS buffer pool, used to indicate receives to NDIS. +* +* send_buf_list +* Lookaside list for dynamically allocating send buffers for send +* that require copies (ARP, DHCP, and any with more physical pages +* than can fit in the local data segments). +*********/ + + +typedef enum _ipoib_pkt_type +{ + PKT_TYPE_UCAST, + PKT_TYPE_BCAST, + PKT_TYPE_MCAST, + PKT_TYPE_CM_UCAST + +} ipoib_pkt_type_t; + +typedef struct _ipoib_cm_desc +{ + cl_pool_item_t item; /* Must be first. */ + uint32_t len; + ipoib_pkt_type_t type; + ib_recv_wr_t wr; + ib_local_ds_t local_ds[2]; + cl_list_item_t list_item; + uint8_t* p_alloc_buf; + uint8_t* p_buf; + uint32_t alloc_buf_size; + uint32_t buf_size; + net32_t lkey; + ib_mr_handle_t h_mr; + NDIS_TCP_IP_CHECKSUM_PACKET_INFO ndis_csum; + +} ipoib_cm_desc_t; + +typedef struct _ipoib_recv_desc +{ + cl_pool_item_t item; /* Must be first. */ + uint32_t len; + ipoib_pkt_type_t type; + ib_recv_wr_t wr; + ib_local_ds_t local_ds[2]; + NDIS_TCP_IP_CHECKSUM_PACKET_INFO ndis_csum; +#if IPOIB_INLINE_RECV + recv_buf_t buf; +#else + recv_buf_t *p_buf; +#endif + +} ipoib_recv_desc_t; +/* +* FIELDS +* item +* Pool item for storing descriptors in a pool. +* +* len +* Length to indicate to NDIS. This is different than the length of the +* received data as some data is IPoIB specific and filtered out. +* +* type +* Type of packet, used in filtering received packets against the packet +* filter. Also used to update stats. +* +* wr +* Receive work request. +* +* local_ds +* Local data segments. The second segment is only used if a buffer +* spans physical pages. +* +* buf +* Buffer for the receive. +* +* NOTES +* The pool item is always first to allow casting form a cl_pool_item_t or +* cl_list_item_t to the descriptor. +*********/ +typedef struct __ipoib_send_wr +{ + ib_send_wr_t wr; + ib_local_ds_t local_ds[MAX_SEND_SGE]; /* Must be last. */ +} ipoib_send_wr_t; + +typedef enum __send_dir +{ + SEND_UD_QP = 1, + SEND_RC_QP = 2 +} send_dir_t; + +typedef struct _ipoib_send_desc +{ + PNET_BUFFER_LIST p_netbuf_list; + ipoib_endpt_t *p_endpt; + send_buf_t *p_buf; + ib_qp_handle_t send_qp; + send_dir_t send_dir; + uint32_t num_wrs; + ipoib_send_wr_t send_wr[MAX_WRS_PER_MSG]; + +} ipoib_send_desc_t; +/* +* FIELDS +* p_pkt +* Pointer to the NDIS_PACKET associated with the send operation. +* +* p_endpt +* Endpoint for this send. +* +* p_buf +* Buffer for the send, if allocated. +* +* wr +* Send work request. +* +* pkt_hdr +* IPoIB packet header, pointed to by the first local datasegment. +* +* local_ds +* Local data segment array. Placed last to allow allocating beyond the +* end of the descriptor for additional datasegments. +* +* NOTES +* The pool item is always first to allow casting form a cl_pool_item_t or +* cl_list_item_t to the descriptor. +*********/ + + +typedef struct _ipoib_recv_mgr +{ + int32_t depth; + + NET_BUFFER_LIST **recv_pkt_array; + + cl_qlist_t done_list; + +} ipoib_recv_mgr_t; +/* +* FIELDS +* depth +* Current number of WRs posted. +* +* p_head +* Pointer to work completion in descriptor at the head of the QP. +* +* p_tail +* Pointer to the work completion in the descriptor at the tail of the QP. +* +* recv_pkt_array +* Array of pointers to NDIS_PACKET used to indicate receives. +* +* done_list +* List of receive descriptors that need to be indicated to NDIS. +*********/ + + +typedef struct _ipoib_send_mgr +{ + atomic32_t depth; + cl_qlist_t pending_list; + ipoib_send_desc_t desc; + +} ipoib_send_mgr_t; +/* +* FIELDS +* depth +* Current number of WRs posted, used to queue pending requests. +* +* pending_list +* List of NDIS_PACKET structures that are awaiting available WRs to send. +*********/ + + +typedef struct _ipoib_endpt_mgr +{ + cl_qmap_t mac_endpts; + cl_fmap_t gid_endpts; + cl_qmap_t lid_endpts; + cl_fmap_t conn_endpts; + LIST_ENTRY pending_conns; + LIST_ENTRY remove_conns; + NDIS_SPIN_LOCK conn_lock; + NDIS_SPIN_LOCK remove_lock; + cl_thread_t h_thread; + cl_event_t event; + uint32_t thread_is_done; +} ipoib_endpt_mgr_t; +/* +* FIELDS +* mac_endpts +* Map of enpoints, keyed by MAC address. +* +* gid_endpts +* Map of enpoints, keyed by GID. +* +* lid_endpts +* Map of enpoints, keyed by LID. Only enpoints on the same subnet +* are inserted in the LID map. +* +* conn_endpts +* Map of connected endpts, keyed by remote gid. +*********/ + + +typedef struct _ipoib_port +{ + cl_obj_t obj; + cl_obj_rel_t rel; + + ib_qp_state_t state; + + cl_spinlock_t recv_lock; + cl_spinlock_t send_lock; + + struct _ipoib_adapter *p_adapter; + uint8_t port_num; + + KEVENT sa_event; + + atomic32_t mcast_cnt; + KEVENT leave_mcast_event; + + ipoib_ib_mgr_t ib_mgr; + + ipoib_buf_mgr_t buf_mgr; + + ipoib_recv_mgr_t recv_mgr; + ipoib_send_mgr_t send_mgr; + + KDPC recv_dpc; + + ipoib_endpt_mgr_t endpt_mgr; + + endpt_buf_mgr_t cm_buf_mgr; + endpt_recv_mgr_t cm_recv_mgr; + + ipoib_endpt_t *p_local_endpt; + ib_ca_attr_t *p_ca_attrs; +#if DBG + atomic32_t ref[ref_array_size]; +#endif + + atomic32_t endpt_rdr; + + atomic32_t hdr_idx; + uint16_t pkey_index; + KDPC gc_dpc; + KTIMER gc_timer; + uint32_t bc_join_retry_cnt; + ib_net16_t base_lid; + ipoib_hdr_t hdr[1]; /* Must be last! */ + +} ipoib_port_t; +/* +* FIELDS +* obj +* Complib object for reference counting, relationships, +* and destruction synchronization. +* +* rel +* Relationship to associate the port with the adapter. +* +* state +* State of the port object. Tracks QP state fairly closely. +* +* recv_lock +* Spinlock to protect receive operations. +* +* send_lock +* Spinlock to protect send operations. +* +* p_adapter +* Parent adapter. Used to get AL handle. +* +* port_num +* Port number of this adapter. +* +* ib_mgr +* IB resource manager. +* +* recv_mgr +* Receive manager. +* +* send_mgr +* Send manager. +* +* endpt_mgr +* Endpoint manager. +*********/ +typedef struct _sgl_context +{ + MDL *p_mdl; + NET_BUFFER_LIST *p_netbuffer_list; + ipoib_port_t *p_port; +}sgl_context_t; + +ib_api_status_t +ipoib_create_port( + IN struct _ipoib_adapter* const p_adapter, + IN ib_pnp_port_rec_t* const p_pnp_rec, + OUT ipoib_port_t** const pp_port ); + +void +ipoib_port_destroy( + IN ipoib_port_t* const p_port ); + +void +ipoib_port_up( + IN ipoib_port_t* const p_port, + IN const ib_pnp_port_rec_t* const p_pnp_rec ); + +void +ipoib_port_down( + IN ipoib_port_t* const p_port ); + +ib_api_status_t +ipoib_port_join_mcast( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + IN const uint8_t state ); + + +void +ipoib_leave_mcast_cb( + IN void *context ); + + +void +ipoib_port_remove_endpt( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac ); + +void +ipoib_port_send( + IN ipoib_port_t* const p_port, + IN NET_BUFFER_LIST *net_buffer_list, + IN ULONG send_flags ); + +void +ipoib_return_net_buffer_list( + IN NDIS_HANDLE adapter_context, + IN PNET_BUFFER_LIST p_netbuffer_lists, + IN ULONG return_flags); + +void +ipoib_port_resume( + IN ipoib_port_t* const p_port, + IN boolean_t b_pending ); + +NTSTATUS +ipoib_mac_to_gid( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ib_gid_t* p_gid ); + +NTSTATUS +ipoib_mac_to_path( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ib_path_rec_t* p_path ); + +void +ipoib_process_sg_list( + IN PDEVICE_OBJECT pDO, + IN PVOID pIrp, + IN PSCATTER_GATHER_LIST pSGList, + IN PVOID Context); + +inline void ipoib_port_ref( + IN ipoib_port_t * p_port, + IN int type); + +inline void ipoib_port_deref( + IN ipoib_port_t * p_port, + IN int type); + +#if 0 +// This function is only used to monitor send failures +static inline VOID NdisMSendCompleteX( + IN NDIS_HANDLE MiniportAdapterHandle, + IN PNDIS_PACKET Packet, + IN NDIS_STATUS Status + ) { + if (Status != NDIS_STATUS_SUCCESS) { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Sending status other than Success to NDIS\n")); + } + NdisMSendComplete(MiniportAdapterHandle,Packet,Status); +} +#else +//#define NdisMSendCompleteX NdisMSendComplete +#endif + +ipoib_endpt_t* +ipoib_endpt_get_by_gid( + IN ipoib_port_t* const p_port, + IN const ib_gid_t* const p_gid ); + +ipoib_endpt_t* +ipoib_endpt_get_by_lid( + IN ipoib_port_t* const p_port, + IN const net16_t lid ); + +ib_api_status_t +ipoib_port_srq_init( + IN ipoib_port_t* const p_port ); + +void +ipoib_port_srq_destroy( + IN ipoib_port_t* const p_port ); + +#if 0 //CM +ib_api_status_t +ipoib_port_listen( + IN ipoib_port_t* const p_port ); + +ib_api_status_t +ipoib_port_cancel_listen( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt ) { + UNUSED_PARAM(p_port); + UNUSED_PARAM(p_endpt); + return IB_SUCCESS; +} +#endif + +ib_api_status_t +endpt_cm_buf_mgr_init( + IN ipoib_port_t* const p_port ); + +void +endpt_cm_buf_mgr_destroy( + IN ipoib_port_t* const p_port ); + +void +endpt_cm_buf_mgr_reset( + IN ipoib_port_t* const p_port ); + +void +endpt_cm_buf_mgr_put_recv( + IN endpt_buf_mgr_t * const p_buf_mgr, + IN ipoib_cm_desc_t* const p_desc ); + +void +endpt_cm_buf_mgr_put_recv_list( + IN endpt_buf_mgr_t * const p_buf_mgr, + IN cl_qlist_t* const p_list ); + +uint32_t +endpt_cm_recv_mgr_build_pkt_array( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt, + IN cl_qlist_t* const p_done_list, + IN OUT uint32_t* p_bytes_recv ); + +ib_api_status_t +endpt_cm_post_recv( + IN ipoib_port_t* const p_port ); + +/*void +endpt_cm_destroy_conn( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt ); +*/ +void +endpt_cm_disconnect( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt ); +void +endpt_cm_flush_recv( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt ); + +ib_api_status_t +ipoib_recv_dhcp( + IN ipoib_port_t* const p_port, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src, + IN ipoib_endpt_t* const p_dst ); + +void +ipoib_port_cancel_xmit( + IN ipoib_port_t* const p_port, + IN PVOID cancel_id ); + +static inline uint32_t +__port_attr_to_mtu_size(uint32_t value) +{ + switch (value) + { + default: + case IB_MTU_LEN_2048: + return 2048; + case IB_MTU_LEN_4096: + return 4096; + case IB_MTU_LEN_1024: + return 1024; + case IB_MTU_LEN_512: + return 512; + case IB_MTU_LEN_256: + return 256; + } +} +#endif /* _IPOIB_PORT_H_ */ diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib_xfr_mgr.c b/branches/winverbs/ulp/ipoib/kernel6/ipoib_xfr_mgr.c new file mode 100644 index 00000000..ce0650f1 --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib_xfr_mgr.c @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2008 Mellanox Technologies. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_xfr_mgr.c 3459 2008-11-12 16:48:21Z tzachid $ + */ + + +#include "ipoib_xfr_mgr.h" +#if defined(EVENT_TRACING) +#ifdef offsetof +#undef offsetof +#endif +#include "ipoib_xfr_mgr.tmh" +#endif + + +const ipoib_guid2mac_translation_t guid2mac_table[] = { + {0x30, 0x48, 0xE7}, + {0x05, 0xAD, 0xE7}, + {0x18, 0x8B, 0xE7}, + {0x1A, 0x4B, 0xE7}, + {0x17, 0x08, 0xE7}, + {0x1E, 0x0B, 0xE7}, + + {0x03, 0xBA, 0xE7}, + {0x05, 0xAD, 0xE7}, + {0x0D, 0x9D, 0xE7}, + {0x11, 0x0A, 0xE7}, + {0x11, 0x85, 0xE7}, + {0x12, 0x79, 0xE7}, + {0x13, 0x21, 0xE7}, + {0x14, 0x38, 0xE7}, + {0x16, 0x35, 0xE7}, + {0x17, 0x08, 0xE7}, + {0x17, 0xA4, 0xE7}, + {0x18, 0x8B, 0xE7}, + {0x18, 0xFE, 0xE7}, + {0x19, 0xBB, 0xE7}, + {0x1A, 0x4B, 0xE7}, + {0x1B, 0x78, 0xE7}, + {0x1E, 0x0B, 0xE7}, + {0x22, 0x64, 0xE7}, + {0x23, 0x7D, 0xE7}, + {0x30, 0x48, 0xE7}, + {0x80, 0x5F, 0xE7}, + + {0x00, 0x00, 0x00}, +}; + diff --git a/branches/winverbs/ulp/ipoib/kernel6/ipoib_xfr_mgr.h b/branches/winverbs/ulp/ipoib/kernel6/ipoib_xfr_mgr.h new file mode 100644 index 00000000..9e38b609 --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/ipoib_xfr_mgr.h @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_xfr_mgr.h 3719 2009-01-07 12:31:52Z reuven $ + */ + + +#ifndef _IPOIB_XFR_MGR_H_ +#define _IPOIB_XFR_MGR_H_ + + +#include +#include +#include +#include +#include +#include +#include + + +#include "ipoib_driver.h" +#include "ip_stats.h" +#include + + +#include +/****s* IPoIB Driver/ipoib_hw_addr_t +* NAME +* ipoib_hw_addr_t +* +* DESCRIPTION +* The ipoib_hw_addr_t structure defines an IPoIB compatible hardware +* address. Values in this structure are stored in network order. +* +* SYNOPSIS +*/ +typedef struct _ipoib_hw_addr +{ + uint32_t flags_qpn; + ib_gid_t gid; + +} PACK_SUFFIX ipoib_hw_addr_t; +/* +* FIELDS +* flags_qpn +* Flags and queue pair number. Use ipoib_addr_get_flags, +* ipoib_addr_set_flags, ipoib_addr_set_qpn, and ipoib_addr_get_qpn +* to manipulate the contents. +* +* gid +* IB GID value. +* +* SEE ALSO +* IPoIB, ipoib_addr_get_flags, ipoib_addr_set_flags, ipoib_addr_set_qpn, +* ipoib_addr_get_qpn +*********/ +#include + +/****s* IPoIB Driver/ipoib_guid2mac_translation_t +* NAME +* ipoib_guid2mac_translation_t +* +* DESCRIPTION +* The ipoib_guid2mac_translation_t structure defines a GUID to MAC translation. +* The structure holds map between known OUI to an appropriate GUID mask. +* +* SYNOPSIS +*/ +typedef struct _ipoib_guid2mac_translation_ +{ + uint8_t second_byte; + uint8_t third_byte; + uint8_t guid_mask; + +} ipoib_guid2mac_translation_t; +/* +* FIELDS +* second_byte +* second byte of OUI (located in lower three bytes of GUID). +* +* third_byte +* third byte of OUI (located in lower three bytes of GUID). +* +* guid_mask +* GUID mask that will be used to generate MAC from the GUID. +* +* SEE ALSO +* IPoIB, ipoib_mac_from_guid_mask +*********/ + +extern const ipoib_guid2mac_translation_t guid2mac_table[]; + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/* + * Address accessors + */ + +static inline uint8_t +ipoib_addr_get_flags( + IN const ipoib_hw_addr_t* const p_addr ) +{ + return (uint8_t)( p_addr->flags_qpn & 0x000000ff); +} + +static inline void +ipoib_addr_set_flags( + IN ipoib_hw_addr_t* const p_addr, + IN const uint8_t flags ) +{ + p_addr->flags_qpn &= ( 0xFFFFFF00 ); + p_addr->flags_qpn |= ( flags ); +} + +static inline net32_t +ipoib_addr_get_qpn( + IN const ipoib_hw_addr_t* const p_addr ) +{ + return( ( p_addr->flags_qpn ) & 0xffffff00 ); +} + +static inline void +ipoib_addr_set_qpn( + IN ipoib_hw_addr_t* const p_addr, + IN const net32_t qpn ) +{ + p_addr->flags_qpn &= ( 0x000000FF ); + p_addr->flags_qpn |= qpn ; +} + +static inline void +ipoib_addr_set_sid( + IN net64_t* const p_sid, + IN const net32_t qpn ) +{ + *p_sid = qpn; + *p_sid <<= 32; + *p_sid |= IPOIB_CM_FLAG_SVCID; +} + +/****f* IPOIB/ipoib_mac_from_sst_guid +* NAME +* ipoib_mac_from_sst_guid +* +* DESCRIPTION +* Generates an ethernet MAC address given a SilverStorm port GUID. +* +* SYNOPSIS +*/ +static inline ib_api_status_t +ipoib_mac_from_sst_guid( + IN const net64_t port_guid, + OUT mac_addr_t* const p_mac_addr ) +{ + const uint8_t *p_guid = (const uint8_t*)&port_guid; + uint32_t low24; + + /* Port guid is in network byte order. OUI is in lower 3 bytes. */ + ASSERT( p_guid[0] == 0x00 && p_guid[1] == 0x06 && p_guid[2] == 0x6a ); + + /* + * We end up using only the lower 23-bits of the GUID. Trap that + * the 24th (bit 23) through 27th (bit 26) bit aren't set. + */ + if( port_guid & CL_HTON64( 0x0000000007800000 ) ) + return IB_INVALID_GUID; + + low24 = 0x00FFF000 - + ((((uint32_t)cl_ntoh64( port_guid ) & 0x00FFFFFF) - 0x101) * 2); + low24 -= p_guid[3]; /* minus port number */ + + p_mac_addr->addr[0] = p_guid[0]; + p_mac_addr->addr[1] = p_guid[1]; + p_mac_addr->addr[2] = p_guid[2]; + p_mac_addr->addr[3] = (uint8_t)(low24 >> 16); + p_mac_addr->addr[4] = (uint8_t)(low24 >> 8); + p_mac_addr->addr[5] = (uint8_t)low24; + + return IB_SUCCESS; +} +/* +* PARAMETERS +* port_guid +* The port GUID, in network byte order, for which to generate a +* MAC address. +* +* p_mac_addr +* Pointer to a mac address in which to store the results. +* +* RETURN VALUES +* IB_SUCCESS +* The MAC address was successfully converted. +* +* IB_INVALID_GUID +* The port GUID provided was not a known GUID format. +* +* NOTES +* The algorithm to convert portGuid to MAC address is as per DN0074, and +* assumes a 2 port HCA. +* +* SEE ALSO +* IPOIB +*********/ + + +/****f* IPOIB/ipoib_mac_from_mlx_guid +* NAME +* ipoib_mac_from_mlx_guid +* +* DESCRIPTION +* Generates an ethernet MAC address given a Mellanox port GUID. +* +* SYNOPSIS +*/ +static inline ib_api_status_t +ipoib_mac_from_mlx_guid( + IN const net64_t port_guid, + OUT mac_addr_t* const p_mac_addr ) +{ + const uint8_t *p_guid = (const uint8_t*)&port_guid; + uint32_t low24; + net16_t guid_middle; + + /* Port guid is in network byte order. OUI is in lower 3 bytes. */ + ASSERT( p_guid[0] == 0x00 && p_guid[1] == 0x02 && p_guid[2] == 0xc9 ); + + guid_middle = (net16_t)((port_guid & CL_HTON64( 0x000000ffff000000 )) >>24); + + if (guid_middle == 2) { + p_mac_addr->addr[0] = 0; + } else if (guid_middle == 3) { + p_mac_addr->addr[0] = 2; + } else { + return IB_INVALID_GUID; + } + low24 = ((uint32_t)cl_ntoh64( port_guid ) & 0x00FFFFFF); + + p_mac_addr->addr[1] = p_guid[1]; + p_mac_addr->addr[2] = p_guid[2]; + p_mac_addr->addr[3] = (uint8_t)(low24 >> 16); + p_mac_addr->addr[4] = (uint8_t)(low24 >> 8); + p_mac_addr->addr[5] = (uint8_t)low24; + + return IB_SUCCESS; +} +/* +* PARAMETERS +* port_guid +* The port GUID, in network byte order, for which to generate a +* MAC address. +* +* p_mac_addr +* Pointer to a mac address in which to store the results. +* +* RETURN VALUES +* IB_SUCCESS +* The MAC address was successfully converted. +* +* IB_INVALID_GUID +* The port GUID provided was not a known GUID format. +* +*********/ + + +/****f* IPOIB/ipoib_mac_from_voltaire_guid +* NAME +* ipoib_mac_from_voltaire_guid +* +* DESCRIPTION +* Generates an ethernet MAC address given a Voltaire port GUID. +* +* SYNOPSIS +*/ +static inline ib_api_status_t +ipoib_mac_from_voltaire_guid( + IN const net64_t port_guid, + OUT mac_addr_t* const p_mac_addr ) +{ + const uint8_t *p_guid = (const uint8_t*)&port_guid; + + /* Port guid is in network byte order. OUI is in lower 3 bytes. */ + ASSERT( p_guid[0] == 0x00 && p_guid[1] == 0x08 && p_guid[2] == 0xf1 ); + + p_mac_addr->addr[0] = p_guid[0]; + p_mac_addr->addr[1] = p_guid[1]; + p_mac_addr->addr[2] = p_guid[2]; + p_mac_addr->addr[3] = p_guid[4] ^ p_guid[6]; + p_mac_addr->addr[4] = p_guid[5] ^ p_guid[7]; + p_mac_addr->addr[5] = p_guid[5] + p_guid[6] + p_guid[7]; + + return IB_SUCCESS; +} + + +/****f* IPOIB/ipoib_mac_from_guid_mask +* NAME +* ipoib_mac_from_guid_mask +* +* DESCRIPTION +* Generates an ethernet MAC address given general port GUID and a bitwise mask +* +* SYNOPSIS +*/ +static inline ib_api_status_t +ipoib_mac_from_guid_mask( + IN const uint8_t *p_guid, + IN uint32_t guid_mask, + OUT mac_addr_t* const p_mac_addr ) +{ + static const mac_addr_size = HW_ADDR_LEN; + uint8_t i; + int digit_counter = 0; + + // All non-zero bits of guid_mask indicates the number of an appropriate + // byte in port_guid, that will be used in MAC address construction + for (i = 7; guid_mask; guid_mask >>= 1, --i ) + { + if( guid_mask & 1 ) + { + ++digit_counter; + if( digit_counter > mac_addr_size ) + { + //to avoid negative index + return IB_INVALID_GUID_MASK; + } + p_mac_addr->addr[mac_addr_size - digit_counter] = p_guid [i]; + } + } + + // check for the mask validity: it should have 6 non-zero bits + if( digit_counter != mac_addr_size ) + return IB_INVALID_GUID_MASK; + + return IB_SUCCESS; +} +/* +* PARAMETERS +* port_guid +* The port GUID, in network byte order, for which to generate a +* MAC address. +* +* guid_mask +* Each BIT in the mask indicates whether to include the appropriate BYTE +* to the MAC address. Bit 0 corresponds to the less significant BYTE , i.e. +* highest index in the MAC array +* +* p_mac_addr +* Pointer to a mac address in which to store the results. +* +* RETURN VALUES +* IB_SUCCESS +* The MAC address was successfully converted. +* +* IB_INVALID_GUID +* The port GUID provided was not a known GUID format. +* +* SEE ALSO +* IPOIB +*********/ + + +/****f* IPOIB/ipoib_mac_from_guid +* NAME +* ipoib_mac_from_guid +* +* DESCRIPTION +* Generates an ethernet MAC address given a port GUID. +* +* SYNOPSIS +*/ +static inline ib_api_status_t +ipoib_mac_from_guid( + IN const net64_t port_guid, + IN uint32_t guid_mask, + OUT mac_addr_t* const p_mac_addr + ) +{ + ib_api_status_t status = IB_INVALID_GUID; + const uint8_t *p_guid = (const uint8_t*)&port_guid; + uint32_t laa, idx = 0; + + /* Port guid is in network byte order. OUI is in lower 3 bytes. */ + if( p_guid[0] == 0 ) + { + if( p_guid[1] == 0x02 && p_guid[2] == 0xc9 ) + { + status = ipoib_mac_from_mlx_guid( port_guid, p_mac_addr ); + } + else if( p_guid[1] == 0x08 && p_guid[2] == 0xf1 ) + { + status = ipoib_mac_from_voltaire_guid( port_guid, p_mac_addr ); + } + else if( p_guid[1] == 0x06 && p_guid[2] == 0x6a ) + { + status = ipoib_mac_from_sst_guid( port_guid, p_mac_addr ); + } + else + { + while( guid2mac_table[idx].second_byte != 0x00 || + guid2mac_table[idx].third_byte != 0x00 ) + { + if( p_guid[1] == guid2mac_table[idx].second_byte && + p_guid[2] == guid2mac_table[idx].third_byte ) + { + status = ipoib_mac_from_guid_mask(p_guid, guid2mac_table[idx].guid_mask, + p_mac_addr); + break; + } + ++idx; + } + } + + if( status == IB_SUCCESS ) + return status; + } + + if( guid_mask ) + return ipoib_mac_from_guid_mask( p_guid, guid_mask, p_mac_addr ); + + /* Value of zero is reserved. */ + laa = cl_atomic_inc( &g_ipoib.laa_idx ); + + if( !laa ) + return IB_INVALID_GUID; + + p_mac_addr->addr[0] = 2; /* LAA bit */ + p_mac_addr->addr[1] = 0; + p_mac_addr->addr[2] = (uint8_t)(laa >> 24); + p_mac_addr->addr[3] = (uint8_t)(laa >> 16); + p_mac_addr->addr[4] = (uint8_t)(laa >> 8); + p_mac_addr->addr[5] = (uint8_t)laa; + + return IB_SUCCESS; +} +/* +* PARAMETERS +* port_guid +* The port GUID, in network byte order, for which to generate a +* MAC address. +* +* p_mac_addr +* Pointer to a mac address in which to store the results. +* +* RETURN VALUES +* IB_SUCCESS +* The MAC address was successfully converted. +* +* IB_INVALID_GUID +* The port GUID provided was not a known GUID format. +* +* NOTES +* Creates a locally administered address using a global incrementing counter. +* +* SEE ALSO +* IPOIB +*********/ + + +/****f* IPOIB/ipoib_is_voltaire_router_gid +* NAME +* ipoib_is_voltaire_router_gid +* +* DESCRIPTION +* Checks whether the GID belongs to Voltaire IP router +* +* SYNOPSIS +*/ +boolean_t +static inline +ipoib_is_voltaire_router_gid( + IN const ib_gid_t *p_gid ) +{ + static const uint8_t VOLTAIRE_GUID_PREFIX[] = {0, 0x08, 0xf1, 0, 0x1}; + + return !cl_memcmp( &p_gid->unicast.interface_id, VOLTAIRE_GUID_PREFIX, + sizeof(VOLTAIRE_GUID_PREFIX) ); +} + + +#ifdef __cplusplus +} +#endif + +#endif /* _IPOIB_XFR_MGR_H_ */ diff --git a/branches/winverbs/ulp/ipoib/kernel6/makefile b/branches/winverbs/ulp/ipoib/kernel6/makefile new file mode 100644 index 00000000..bffacaa7 --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/makefile @@ -0,0 +1,7 @@ +# +# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source +# file to this component. This file merely indirects to the real make file +# that is shared by all the driver components of the OpenIB Windows project. +# + +!INCLUDE ..\..\..\inc\openib.def diff --git a/branches/winverbs/ulp/ipoib/kernel6/makefile.inc b/branches/winverbs/ulp/ipoib/kernel6/makefile.inc new file mode 100644 index 00000000..4f29f500 --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/makefile.inc @@ -0,0 +1,17 @@ + +# Transform .inx file to .inf file adding date + major,min & svn.version stamp +# Output .inf file is copied to the $(INF_TARGET) folder (commonly where .sys file resides). + +_LNG=$(LANGUAGE) + +!IF !DEFINED(_INX) +_INX=. +!ENDIF + +STAMP=stampinf -a $(_BUILDARCH) + +!INCLUDE mod_ver.def + +$(INF_TARGET) : $(_INX)\$(INF_NAME).inx + copy $(_INX)\$(@B).inx $@ + $(STAMP) -f $@ -d * -v $(IB_MAJORVERSION).$(IB_MINORVERSION).$(IB_BUILDVERSION).$(OPENIB_REV) diff --git a/branches/winverbs/ulp/ipoib/kernel6/netipoib-xp32.inf b/branches/winverbs/ulp/ipoib/kernel6/netipoib-xp32.inf new file mode 100644 index 00000000..fd29521f --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/netipoib-xp32.inf @@ -0,0 +1,280 @@ +; OpenFabrics Alliance Internet Protocol over InfiniBand Adapter +; Copyright 2005 SilverStorm Technologies all Rights Reserved. +; Copyright 2006 Mellanox Technologies all Rights Reserved. + +[Version] +Signature = "$Windows NT$" +Class = Net +ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318} +Provider = %MTL% +DriverVer=10/10/2008,2.0.0000.1630 +CatalogFile=ipoib.cat + +[Manufacturer] +%MTL% = MTL,ntx86,ntamd64,ntia64 + +[ControlFlags] +ExcludeFromSelect = IBA\IPoIB + +[MTL] +; empty since we don't support W9x/Me + +[MTL.ntx86] +%IpoibDesc% = Ipoib.DDInstall, IBA\IPoIB ; Internet Protocol over InfiniBand Adapter +%IpoibDescP% = Ipoib.DDInstall, IBA\IPoIBP ; Internet Protocol over InfiniBand Adapter with partition key + +[MTL.ntamd64] +%IpoibDesc% = Ipoib.DDInstall, IBA\IPoIB ; Internet Protocol over InfiniBand Adapter +%IpoibDescP% = Ipoib.DDInstall, IBA\IPoIBP ; Internet Protocol over InfiniBand Adapter with partition key + +[MTL.ntia64] +%IpoibDesc% = Ipoib.DDInstall, IBA\IPoIB ; Internet Protocol over InfiniBand Adapter +%IpoibDescP% = Ipoib.DDInstall, IBA\IPoIBP ; Internet Protocol over InfiniBand Adapter with partition key + +[Ipoib.DDInstall.ntx86] +Characteristics = 0x81 ; NCF_HAS_UI | NCF_VIRTUAL +AddReg = IpoibAddReg +CopyFiles = IpoibCopyFiles +CopyFiles = NdCopyFiles + +[Ipoib.DDInstall.ntamd64] +Characteristics = 0x81 ; NCF_HAS_UI | NCF_VIRTUAL +AddReg = IpoibAddReg +CopyFiles = IpoibCopyFiles +CopyFiles = NdCopyFiles +CopyFiles = WOW64CopyFiles + +[Ipoib.DDInstall.ntia64] +Characteristics = 0x81 ; NCF_HAS_UI | NCF_VIRTUAL +AddReg = IpoibAddReg +CopyFiles = IpoibCopyFiles +CopyFiles = NdCopyFiles +CopyFiles = WOW64CopyFiles + +[Ipoib.DDInstall.ntx86.Services] +AddService = ipoib, 2, IpoibService, IpoibEventLog + +[Ipoib.DDInstall.ntamd64.Services] +AddService = ipoib, 2, IpoibService, IpoibEventLog + +[Ipoib.DDInstall.ntia64.Services] +AddService = ipoib, 2, IpoibService, IpoibEventLog + +[IpoibAddReg] +HKR, ,RDMACapable, %REG_DWORD%, 1 +HKR, Ndi, Service, 0, "ipoib" +HKR, Ndi\Interfaces, UpperRange, 0, "ndis5" +HKR, Ndi\Interfaces, LowerRange, 0, "ethernet" + +HKR, Ndi\Params\RqDepth, ParamDesc, 0, %RQ_DEPTH_STR% +HKR, Ndi\Params\RqDepth, Type, 0, "dword" +HKR, Ndi\Params\RqDepth, Default, 0, "512" +HKR, Ndi\Params\RqDepth, Optional, 0, "0" +HKR, Ndi\Params\RqDepth, Min, 0, "128" +HKR, Ndi\Params\RqDepth, Max, 0, "1024" +HKR, Ndi\Params\RqDepth, Step, 0, "128" + +HKR, Ndi\Params\RqLowWatermark, ParamDesc, 0, %RQ_WATERMARK_STR% +HKR, Ndi\Params\RqLowWatermark, Type, 0, "dword" +HKR, Ndi\Params\RqLowWatermark, Default, 0, "4" +HKR, Ndi\Params\RqLowWatermark, Optional, 0, "0" +HKR, Ndi\Params\RqLowWatermark, Min, 0, "2" +HKR, Ndi\Params\RqLowWatermark, Max, 0, "8" +HKR, Ndi\Params\RqLowWatermark, Step, 0, "1" + +HKR, Ndi\Params\SqDepth, ParamDesc, 0, %SQ_DEPTH_STR% +HKR, Ndi\Params\SqDepth, Type, 0, "dword" +HKR, Ndi\Params\SqDepth, Default, 0, "512" +HKR, Ndi\Params\SqDepth, Optional, 0, "0" +HKR, Ndi\Params\SqDepth, Min, 0, "128" +HKR, Ndi\Params\SqDepth, Max, 0, "1024" +HKR, Ndi\Params\SqDepth, Step, 0, "128" + +HKR, Ndi\Params\SendChksum, ParamDesc, 0, %SQ_CSUM_STR% +HKR, Ndi\Params\SendChksum, Type, 0, "enum" +HKR, Ndi\Params\SendChksum, Default, 0, "1" +HKR, Ndi\Params\SendChksum, Optional, 0, "0" +HKR, Ndi\Params\SendChksum\enum, "0", 0, %DISABLED_STR% +HKR, Ndi\Params\SendChksum\enum, "1", 0, %ENABLED_IF_STR% +HKR, Ndi\Params\SendChksum\enum, "2", 0, %BYPASS_STR% + +HKR, Ndi\Params\RecvChksum, ParamDesc, 0, %RQ_CSUM_STR% +HKR, Ndi\Params\RecvChksum, Type, 0, "enum" +HKR, Ndi\Params\RecvChksum, Default, 0, "1" +HKR, Ndi\Params\RecvChksum, Optional, 0, "0" +HKR, Ndi\Params\RecvChksum\enum, "0", 0, %DISABLED_STR% +HKR, Ndi\Params\RecvChksum\enum, "1", 0, %ENABLED_IF_STR% +HKR, Ndi\Params\RecvChksum\enum, "2", 0, %BYPASS_STR% + +HKR, Ndi\Params\lso, ParamDesc, 0, %LSO_STR% +HKR, Ndi\Params\lso, Type, 0, "enum" +HKR, Ndi\Params\lso, Default, 0, "0" +HKR, Ndi\Params\lso, Optional, 0, "0" +HKR, Ndi\Params\lso\enum, "0", 0, %DISABLED_STR% +HKR, Ndi\Params\lso\enum, "1", 0, %ENABLED_STR% + + +HKR, Ndi\Params\SaTimeout, ParamDesc, 0, %SA_QUERY_TO_STR% +HKR, Ndi\Params\SaTimeout, Type, 0, "dword" +HKR, Ndi\Params\SaTimeout, Default, 0, "1000" +HKR, Ndi\Params\SaTimeout, Optional, 0, "0" +HKR, Ndi\Params\SaTimeout, Min, 0, "500" +HKR, Ndi\Params\SaTimeout, Step, 0, "250" + +HKR, Ndi\Params\SaRetries, ParamDesc, 0, %SA_QUERY_RETRY_STR% +HKR, Ndi\Params\SaRetries, Type, 0, "dword" +HKR, Ndi\Params\SaRetries, Default, 0, "10" +HKR, Ndi\Params\SaRetries, Optional, 0, "0" +HKR, Ndi\Params\SaRetries, Min, 0, "1" + +HKR, Ndi\Params\RecvRatio, ParamDesc, 0, %RECV_RATIO_STR% +HKR, Ndi\Params\RecvRatio, Type, 0, "dword" +HKR, Ndi\Params\RecvRatio, Default, 0, "1" +HKR, Ndi\Params\RecvRatio, Optional, 0, "0" +HKR, Ndi\Params\RecvRatio, Min, 0, "1" +HKR, Ndi\Params\RecvRatio, Max, 0, "10" + +HKR, Ndi\Params\PayloadMtu, ParamDesc, 0, %MTU_STR% +HKR, Ndi\Params\PayloadMtu, Type, 0, "dword" +HKR, Ndi\Params\PayloadMtu, Default, 0, "2044" +HKR, Ndi\Params\PayloadMtu, Min, 0, "512" +HKR, Ndi\Params\PayloadMtu, Max, 0, "4092" + +HKR, Ndi\Params\MCLeaveRescan, ParamDesc, 0, %MC_RESCAN_STR% +HKR, Ndi\Params\MCLeaveRescan, Type, 0, "dword" +HKR, Ndi\Params\MCLeaveRescan, Default, 0, "260" +HKR, Ndi\Params\MCLeaveRescan, Optional, 0, "0" +HKR, Ndi\Params\MCLeaveRescan, Min, 0, "1" +HKR, Ndi\Params\MCLeaveRescan, Max, 0, "3600" + +HKR, Ndi\Params\GUIDMask, ParamDesc, 0, %GUID_MASK_STR% +HKR, Ndi\Params\GUIDMask, Type, 0, "dword" +HKR, Ndi\Params\GUIDMask, Default, 0, "0" +HKR, Ndi\Params\GUIDMask, Optional, 0, "0" +HKR, Ndi\Params\GUIDMask, Min, 0, "0" +HKR, Ndi\Params\GUIDMask, Max, 0, "252" + +HKR, Ndi\Params\BCJoinRetry, ParamDesc, 0, %BC_JOIN_RETRY_STR% +HKR, Ndi\Params\BCJoinRetry, Type, 0, "dword" +HKR, Ndi\Params\BCJoinRetry, Default, 0, "50" +HKR, Ndi\Params\BCJoinRetry, Optional, 0, "0" +HKR, Ndi\Params\BCJoinRetry, Min, 0, "0" +HKR, Ndi\Params\BCJoinRetry, Max, 0, "1000" + +HKR, Ndi\Params\CmEnabled, ParamDesc, 0, %CONNECTED_MODE_STR% +HKR, Ndi\Params\CmEnabled, Type, 0, "enum" +HKR, Ndi\Params\CmEnabled, Default, 0, "1" +HKR, Ndi\Params\CmEnabled, Optional, 0, "0" +HKR, Ndi\Params\CmEnabled\enum, "0", 0, %DISABLED_STR% +HKR, Ndi\Params\CmEnabled\enum, "1", 0, %ENABLED_STR% + +HKR, Ndi\Params\CmPayloadMtu, ParamDesc, 0, %CONNECTED_MODE_MTU_STR% +HKR, Ndi\Params\CmPayloadMtu, Type, 0, "dword" +HKR, Ndi\Params\CmPayloadMtu, Default, 0, "65520" +HKR, Ndi\Params\CmPayloadMtu, Min, 0, "512" +HKR, Ndi\Params\CmPayloadMtu, Max, 0, "65520" + +[IpoibService] +DisplayName = %IpoibServiceDispName% +ServiceType = 1 ;%SERVICE_KERNEL_DRIVER% +StartType = 3 ;%SERVICE_DEMAND_START% +ErrorControl = 1 ;%SERVICE_ERROR_NORMAL% +ServiceBinary = %12%\ipoib.sys +LoadOrderGroup = NDIS +AddReg = Ipoib.ParamsReg + +[Ipoib.ParamsReg] +HKR,"Parameters","DebugLevel",%REG_DWORD_NO_CLOBBER%,0x00000002 +HKR,"Parameters","DebugFlags",%REG_DWORD_NO_CLOBBER%,0x00000fff +HKR,"Parameters","bypass_check_bcast_rate",%REG_DWORD_NO_CLOBBER%,0x00000000 + +[IpoibEventLog] +AddReg = IpoibAddEventLogReg + +[IpoibAddEventLogReg] +HKR, , EventMessageFile, 0x00020000, "%%SystemRoot%%\System32\netevent.dll;%%SystemRoot%%\System32\drivers\ipoib.sys" +HKR, , TypesSupported, 0x00010001, 7 + + +[IpoibCopyFiles] +ipoib.sys,,,2 + +[NdCopyFiles] +ibndprov.dll,,,0x00000002 +ndinstall.exe,,,0x00000002 + +[WOW64CopyFiles] +ibwsd.dll,ibwsd32.dll,,0x00000002 +ibndprov.dll,ibndprov32.dll,,0x00000002 + +[SourceDisksNames.x86] +1 = %IcsDisk1%,,,"" + +[SourceDisksNames.amd64] +1 = %IcsDisk1%,,,"" + +[SourceDisksNames.ia64] +1 = %IcsDisk1%,,,"" + +[SourceDisksFiles.x86] +ipoib.sys = 1 +ibndprov.dll = 1 +ndinstall.exe = 1 + +[SourceDisksFiles.amd64] +ipoib.sys = 1 +ibwsd.dll = 1 +ibwsd32.dll = 1 +ibndprov.dll = 1 +ibndprov32.dll = 1 +ndinstall.exe = 1 + +[SourceDisksFiles.ia64] +ipoib.sys = 1 +ibwsd.dll = 1 +ibwsd32.dll = 1 +ibndprov.dll = 1 +ibndprov32.dll = 1 +ndinstall.exe = 1 + +[DestinationDirs] +IpoibCopyFiles = %DIRID_DRIVERS% +WsdCopyFiles = %DIRID_SYSTEM% +NdCopyFiles = %DIRID_SYSTEM% +WOW64CopyFiles = %DIRID_SYSTEM_X86% +DefaultDestDir = %DIRID_SYSTEM% + +[Strings] +OPENIB = "OpenFabrics Alliance" +MTL = "Mellanox Technologies Ltd." +IpoibDesc = "Mellanox IPoIB Adapter" +IpoibDescP = "Mellanox IPoIB Adapter Partition" +IpoibServiceDispName = "IPoIB" +IcsDisk1 = "Mellanox IPoIB Disk #1" +DIRID_SYSTEM = 11 +DIRID_DRIVERS = 12 +DIRID_SYSTEM_X86 = 16425 +REG_DWORD = 0x00010001 +REG_DWORD_NO_CLOBBER = 0x00010003 + +RQ_DEPTH_STR = "Receive Queue depth" +RQ_WATERMARK_STR = "Receive Queue Low Watermark" +SQ_DEPTH_STR = "Send Queue Depth" +SQ_CSUM_STR = "Send Checksum Offload" +RQ_CSUM_STR = "Recv Checksum Offload" +LSO_STR = "Large Send Offload" +SA_QUERY_TO_STR = "SA Query Timeout (ms)" +SA_QUERY_RETRY_STR = "SA Query Retry Count" +RECV_RATIO_STR = "Receive Pool Ratio" +MTU_STR = "Payload Mtu size" +MC_RESCAN_STR = "MC leave rescan (sec)" +GUID_MASK_STR = "GUID bitwise mask" +BC_JOIN_RETRY_STR = "Number of retries connecting to bc" + +ENABLED_IF_STR = "Enabled (if supported by HW)" +ENABLED_STR = "Enabled" +DISABLED_STR = "Disabled" +BYPASS_STR = "Bypass" +CONNECTED_MODE_STR = "Connected mode" +CONNECTED_MODE_MTU_STR = "Connected Mode Payload Mtu size" + diff --git a/branches/winverbs/ulp/ipoib/kernel6/netipoib.inx b/branches/winverbs/ulp/ipoib/kernel6/netipoib.inx new file mode 100644 index 00000000..ca3126b5 --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/netipoib.inx @@ -0,0 +1,295 @@ +; OpenFabrics Alliance Internet Protocol over InfiniBand Adapter +; Copyright 2005 SilverStorm Technologies all Rights Reserved. +; Copyright 2006 Mellanox Technologies all Rights Reserved. + +[Version] +Signature = "$Windows NT$" +Class = Net +ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318} +Provider = %MTL% +DriverVer=06/11/2008,1.0.0000.1207 +CatalogFile=ipoib.cat + +[Manufacturer] +%MTL% = MTL,ntx86,ntamd64,ntia64 + +[ControlFlags] +ExcludeFromSelect = IBA\IPoIB + +[MTL] +; empty since we don't support W9x/Me + +[MTL.ntx86] +%IpoibDesc% = Ipoib.DDInstall, IBA\IPoIB ; Internet Protocol over InfiniBand Adapter +%IpoibDescP% = Ipoib.DDInstall, IBA\IPoIBP ; Internet Protocol over InfiniBand Adapter with partition key + +[MTL.ntamd64] +%IpoibDesc% = Ipoib.DDInstall, IBA\IPoIB ; Internet Protocol over InfiniBand Adapter +%IpoibDescP% = Ipoib.DDInstall, IBA\IPoIBP ; Internet Protocol over InfiniBand Adapter with partition key + +[MTL.ntia64] +%IpoibDesc% = Ipoib.DDInstall, IBA\IPoIB ; Internet Protocol over InfiniBand Adapter +%IpoibDescP% = Ipoib.DDInstall, IBA\IPoIBP ; Internet Protocol over InfiniBand Adapter with partition key + +[Ipoib.DDInstall.ntx86] +Characteristics = 0x81 ; NCF_HAS_UI | NCF_VIRTUAL +AddReg = IpoibAddReg +CopyFiles = IpoibCopyFiles +CopyFiles = WsdCopyFiles +CopyFiles = NdCopyFiles +*IfType = 6 ; IF_TYPE_ETHERNET_CSMACD +*MediaType = 0 ; NdisMedium802_3 +*PhysicalMediaType = 14 ; NdisPhysicalMedium802_3 + +[Ipoib.DDInstall.ntamd64] +Characteristics = 0x81 ; NCF_HAS_UI | NCF_VIRTUAL +AddReg = IpoibAddReg +CopyFiles = IpoibCopyFiles +CopyFiles = WsdCopyFiles +CopyFiles = NdCopyFiles +CopyFiles = WOW64CopyFiles +*IfType = 6 ; IF_TYPE_ETHERNET_CSMACD +*MediaType = 0 ; NdisMedium802_3 +*PhysicalMediaType = 14 ; NdisPhysicalMedium802_3 + +[Ipoib.DDInstall.ntia64] +Characteristics = 0x81 ; NCF_HAS_UI | NCF_VIRTUAL +AddReg = IpoibAddReg +CopyFiles = IpoibCopyFiles +CopyFiles = WsdCopyFiles +CopyFiles = NdCopyFiles +CopyFiles = WOW64CopyFiles +*IfType = 6 ; IF_TYPE_ETHERNET_CSMACD +*MediaType = 0 ; NdisMedium802_3 +*PhysicalMediaType = 14 ; NdisPhysicalMedium802_3 + +[Ipoib.DDInstall.ntx86.Services] +AddService = ipoib, 2, IpoibService, IpoibEventLog + +[Ipoib.DDInstall.ntamd64.Services] +AddService = ipoib, 2, IpoibService, IpoibEventLog + +[Ipoib.DDInstall.ntia64.Services] +AddService = ipoib, 2, IpoibService, IpoibEventLog + +[IpoibAddReg] +HKR, ,RDMACapable, %REG_DWORD%, 1 +HKR, Ndi, Service, 0, "ipoib" +HKR, Ndi\Interfaces, UpperRange, 0, "ndis5" +HKR, Ndi\Interfaces, LowerRange, 0, "ethernet" + +HKR, Ndi\Params\RqDepth, ParamDesc, 0, %RQ_DEPTH_STR% +HKR, Ndi\Params\RqDepth, Type, 0, "dword" +HKR, Ndi\Params\RqDepth, Default, 0, "512" +HKR, Ndi\Params\RqDepth, Optional, 0, "0" +HKR, Ndi\Params\RqDepth, Min, 0, "128" +HKR, Ndi\Params\RqDepth, Max, 0, "1024" +HKR, Ndi\Params\RqDepth, Step, 0, "128" + +HKR, Ndi\Params\RqLowWatermark, ParamDesc, 0, %RQ_WATERMARK_STR% +HKR, Ndi\Params\RqLowWatermark, Type, 0, "dword" +HKR, Ndi\Params\RqLowWatermark, Default, 0, "4" +HKR, Ndi\Params\RqLowWatermark, Optional, 0, "0" +HKR, Ndi\Params\RqLowWatermark, Min, 0, "2" +HKR, Ndi\Params\RqLowWatermark, Max, 0, "8" +HKR, Ndi\Params\RqLowWatermark, Step, 0, "1" + +HKR, Ndi\Params\SqDepth, ParamDesc, 0, %SQ_DEPTH_STR% +HKR, Ndi\Params\SqDepth, Type, 0, "dword" +HKR, Ndi\Params\SqDepth, Default, 0, "512" +HKR, Ndi\Params\SqDepth, Optional, 0, "0" +HKR, Ndi\Params\SqDepth, Min, 0, "128" +HKR, Ndi\Params\SqDepth, Max, 0, "1024" +HKR, Ndi\Params\SqDepth, Step, 0, "128" + +HKR, Ndi\Params\SendChksum, ParamDesc, 0, %SQ_CSUM_STR% +HKR, Ndi\Params\SendChksum, Type, 0, "enum" +HKR, Ndi\Params\SendChksum, Default, 0, "1" +HKR, Ndi\Params\SendChksum, Optional, 0, "0" +HKR, Ndi\Params\SendChksum\enum, "0", 0, %DISABLED_STR% +HKR, Ndi\Params\SendChksum\enum, "1", 0, %ENABLED_IF_STR% +HKR, Ndi\Params\SendChksum\enum, "2", 0, %BYPASS_STR% + +HKR, Ndi\Params\RecvChksum, ParamDesc, 0, %RQ_CSUM_STR% +HKR, Ndi\Params\RecvChksum, Type, 0, "enum" +HKR, Ndi\Params\RecvChksum, Default, 0, "1" +HKR, Ndi\Params\RecvChksum, Optional, 0, "0" +HKR, Ndi\Params\RecvChksum\enum, "0", 0, %DISABLED_STR% +HKR, Ndi\Params\RecvChksum\enum, "1", 0, %ENABLED_IF_STR% +HKR, Ndi\Params\RecvChksum\enum, "2", 0, %BYPASS_STR% + +HKR, Ndi\Params\lso, ParamDesc, 0, %LSO_STR% +HKR, Ndi\Params\lso, Type, 0, "enum" +HKR, Ndi\Params\lso, Default, 0, "0" +HKR, Ndi\Params\lso, Optional, 0, "0" +HKR, Ndi\Params\lso\enum, "0", 0, %DISABLED_STR% +HKR, Ndi\Params\lso\enum, "1", 0, %ENABLED_STR% + + +HKR, Ndi\Params\SaTimeout, ParamDesc, 0, %SA_QUERY_TO_STR% +HKR, Ndi\Params\SaTimeout, Type, 0, "dword" +HKR, Ndi\Params\SaTimeout, Default, 0, "1000" +HKR, Ndi\Params\SaTimeout, Optional, 0, "0" +HKR, Ndi\Params\SaTimeout, Min, 0, "500" +HKR, Ndi\Params\SaTimeout, Step, 0, "250" + +HKR, Ndi\Params\SaRetries, ParamDesc, 0, %SA_QUERY_RETRY_STR% +HKR, Ndi\Params\SaRetries, Type, 0, "dword" +HKR, Ndi\Params\SaRetries, Default, 0, "10" +HKR, Ndi\Params\SaRetries, Optional, 0, "0" +HKR, Ndi\Params\SaRetries, Min, 0, "1" + +HKR, Ndi\Params\RecvRatio, ParamDesc, 0, %RECV_RATIO_STR% +HKR, Ndi\Params\RecvRatio, Type, 0, "dword" +HKR, Ndi\Params\RecvRatio, Default, 0, "1" +HKR, Ndi\Params\RecvRatio, Optional, 0, "0" +HKR, Ndi\Params\RecvRatio, Min, 0, "1" +HKR, Ndi\Params\RecvRatio, Max, 0, "10" + +HKR, Ndi\Params\PayloadMtu, ParamDesc, 0, %MTU_STR% +HKR, Ndi\Params\PayloadMtu, Type, 0, "dword" +HKR, Ndi\Params\PayloadMtu, Default, 0, "2044" +HKR, Ndi\Params\PayloadMtu, Min, 0, "512" +HKR, Ndi\Params\PayloadMtu, Max, 0, "4092" + +HKR, Ndi\Params\MCLeaveRescan, ParamDesc, 0, %MC_RESCAN_STR% +HKR, Ndi\Params\MCLeaveRescan, Type, 0, "dword" +HKR, Ndi\Params\MCLeaveRescan, Default, 0, "260" +HKR, Ndi\Params\MCLeaveRescan, Optional, 0, "0" +HKR, Ndi\Params\MCLeaveRescan, Min, 0, "1" +HKR, Ndi\Params\MCLeaveRescan, Max, 0, "3600" + +HKR, Ndi\Params\GUIDMask, ParamDesc, 0, %GUID_MASK_STR% +HKR, Ndi\Params\GUIDMask, Type, 0, "dword" +HKR, Ndi\Params\GUIDMask, Default, 0, "0" +HKR, Ndi\Params\GUIDMask, Optional, 0, "0" +HKR, Ndi\Params\GUIDMask, Min, 0, "0" +HKR, Ndi\Params\GUIDMask, Max, 0, "252" + +HKR, Ndi\Params\BCJoinRetry, ParamDesc, 0, %BC_JOIN_RETRY_STR% +HKR, Ndi\Params\BCJoinRetry, Type, 0, "dword" +HKR, Ndi\Params\BCJoinRetry, Default, 0, "50" +HKR, Ndi\Params\BCJoinRetry, Optional, 0, "0" +HKR, Ndi\Params\BCJoinRetry, Min, 0, "0" +HKR, Ndi\Params\BCJoinRetry, Max, 0, "1000" + +HKR, Ndi\Params\CmEnabled, ParamDesc, 0, %CONNECTED_MODE_STR% +HKR, Ndi\Params\CmEnabled, Type, 0, "enum" +HKR, Ndi\Params\CmEnabled, Default, 0, "0" +HKR, Ndi\Params\CmEnabled, Optional, 0, "0" +HKR, Ndi\Params\CmEnabled\enum, "0", 0, %DISABLED_STR% +HKR, Ndi\Params\CmEnabled\enum, "1", 0, %ENABLED_STR% + +HKR, Ndi\Params\CmPayloadMtu, ParamDesc, 0, %CONNECTED_MODE_MTU_STR% +HKR, Ndi\Params\CmPayloadMtu, Type, 0, "dword" +HKR, Ndi\Params\CmPayloadMtu, Default, 0, "65520" +HKR, Ndi\Params\CmPayloadMtu, Min, 0, "512" +HKR, Ndi\Params\CmPayloadMtu, Max, 0, "65520" + +[IpoibService] +DisplayName = %IpoibServiceDispName% +ServiceType = 1 ;%SERVICE_KERNEL_DRIVER% +StartType = 3 ;%SERVICE_DEMAND_START% +ErrorControl = 1 ;%SERVICE_ERROR_NORMAL% +ServiceBinary = %12%\ipoib.sys +LoadOrderGroup = NDIS +AddReg = Ipoib.ParamsReg + +[Ipoib.ParamsReg] +HKR,"Parameters","DebugLevel",%REG_DWORD_NO_CLOBBER%,0x00000002 +HKR,"Parameters","DebugFlags",%REG_DWORD_NO_CLOBBER%,0x00000fff +HKR,"Parameters","bypass_check_bcast_rate",%REG_DWORD_NO_CLOBBER%,0x00000000 + +[IpoibEventLog] +AddReg = IpoibAddEventLogReg + +[IpoibAddEventLogReg] +HKR, , EventMessageFile, 0x00020000, "%%SystemRoot%%\System32\netevent.dll;%%SystemRoot%%\System32\drivers\ipoib.sys" +HKR, , TypesSupported, 0x00010001, 7 + + +[IpoibCopyFiles] +ipoib.sys,,,2 + +[WsdCopyFiles] +ibwsd.dll,,,0x00000002 + +[NdCopyFiles] +ibndprov.dll,,,0x00000002 +ndinstall.exe,,,0x00000002 + +[WOW64CopyFiles] +ibwsd.dll,ibwsd32.dll,,0x00000002 +ibndprov.dll,ibndprov32.dll,,0x00000002 + +[SourceDisksNames.x86] +1 = %IcsDisk1%,,,"" + +[SourceDisksNames.amd64] +1 = %IcsDisk1%,,,"" + +[SourceDisksNames.ia64] +1 = %IcsDisk1%,,,"" + +[SourceDisksFiles.x86] +ipoib.sys = 1 +ibwsd.dll = 1 +ibndprov.dll = 1 +ndinstall.exe = 1 + +[SourceDisksFiles.amd64] +ipoib.sys = 1 +ibwsd.dll = 1 +ibwsd32.dll = 1 +ibndprov.dll = 1 +ibndprov32.dll = 1 +ndinstall.exe = 1 + +[SourceDisksFiles.ia64] +ipoib.sys = 1 +ibwsd.dll = 1 +ibwsd32.dll = 1 +ibndprov.dll = 1 +ibndprov32.dll = 1 +ndinstall.exe = 1 + +[DestinationDirs] +IpoibCopyFiles = %DIRID_DRIVERS% +WsdCopyFiles = %DIRID_SYSTEM% +NdCopyFiles = %DIRID_SYSTEM% +WOW64CopyFiles = %DIRID_SYSTEM_X86% +DefaultDestDir = %DIRID_SYSTEM% + +[Strings] +OPENIB = "OpenFabrics Alliance" +MTL = "Mellanox Technologies Ltd." +IpoibDesc = "Mellanox IPoIB Adapter" +IpoibDescP = "Mellanox IPoIB Adapter Partition" +IpoibServiceDispName = "IPoIB" +IcsDisk1 = "Mellanox IPoIB Disk #1" +DIRID_SYSTEM = 11 +DIRID_DRIVERS = 12 +DIRID_SYSTEM_X86 = 16425 +REG_DWORD = 0x00010001 +REG_DWORD_NO_CLOBBER = 0x00010003 + +RQ_DEPTH_STR = "Receive Queue depth" +RQ_WATERMARK_STR = "Receive Queue Low Watermark" +SQ_DEPTH_STR = "Send Queue Depth" +SQ_CSUM_STR = "Send Checksum Offload" +RQ_CSUM_STR = "Recv Checksum Offload" +LSO_STR = "Large Send Offload" +SA_QUERY_TO_STR = "SA Query Timeout (ms)" +SA_QUERY_RETRY_STR = "SA Query Retry Count" +RECV_RATIO_STR = "Receive Pool Ratio" +MTU_STR = "Payload Mtu size" +MC_RESCAN_STR = "MC leave rescan (sec)" +GUID_MASK_STR = "GUID bitwise mask" +BC_JOIN_RETRY_STR = "Number of retries connecting to bc" + +ENABLED_IF_STR = "Enabled (if supported by HW)" +ENABLED_STR = "Enabled" +DISABLED_STR = "Disabled" +BYPASS_STR = "Bypass" +CONNECTED_MODE_STR = "Connected mode" +CONNECTED_MODE_MTU_STR = "Connected Mode Payload Mtu size" \ No newline at end of file diff --git a/branches/winverbs/ulp/ipoib/kernel6/offload.h b/branches/winverbs/ulp/ipoib/kernel6/offload.h new file mode 100644 index 00000000..de696c6a --- /dev/null +++ b/branches/winverbs/ulp/ipoib/kernel6/offload.h @@ -0,0 +1,47 @@ +/*++ + +Copyright (c) 2005-2008 Mellanox Technologies. All rights reserved. + +Module Name: + offload.h + +Abstract: + Task offloading header file + +Revision History: + +Notes: + +--*/ + +// +// Define the maximum size of large TCP packets the driver can offload. +// This sample driver uses shared memory to map the large packets, +// LARGE_SEND_OFFLOAD_SIZE is useless in this case, so we just define +// it as NIC_MAX_PACKET_SIZE. But shipping drivers should define +// LARGE_SEND_OFFLOAD_SIZE if they support LSO, and use it as +// MaximumPhysicalMapping when they call NdisMInitializeScatterGatherDma +// if they use ScatterGather method. If the drivers don't support +// LSO, then MaximumPhysicalMapping is NIC_MAX_PACKET_SIZE. +// + +#define LSO_MAX_HEADER 136 +#define LARGE_SEND_OFFLOAD_SIZE 60000 + +// This struct is being used in order to pass data about the GSO buffers if they +// are present +typedef struct LsoBuffer_ { + PUCHAR pData; + UINT Len; +} LsoBuffer; + +typedef struct LsoData_ { + LsoBuffer LsoBuffers[1]; + UINT UsedBuffers; + UINT FullBuffers; + UINT LsoHeaderSize; + UINT IndexOfData; + UCHAR coppied_data[LSO_MAX_HEADER]; +} LsoData; + + diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/dirs b/branches/winverbs/ulp/ipoib_NDIS6_CM/dirs new file mode 100644 index 00000000..ed41dcf4 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/dirs @@ -0,0 +1,2 @@ +DIRS=\ + kernel diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/ip_stats.h b/branches/winverbs/ulp/ipoib_NDIS6_CM/ip_stats.h new file mode 100644 index 00000000..2f93e41c --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/ip_stats.h @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ip_stats.h 1611 2006-08-20 14:48:55Z sleybo $ + */ + + +#ifndef _IP_STATS_H_ +#define _IP_STATS_H_ + + +#include + + +/****s* IB Network Drivers/ip_data_stats_t +* NAME +* ip_data_stats_t +* +* DESCRIPTION +* Defines data transfer statistic information for an IP device. +* +* SYNOPSIS +*/ +typedef struct _ip_data_stats +{ + uint64_t bytes; + uint64_t frames; + +} ip_data_stats_t; +/* +* FIELDS +* bytes +* Total number of bytes transfered. +* +* frames +* Total number of frames transfered. +* +* SEE ALSO +* IPoIB, INIC, ip_comp_stats_t, ip_stats_t +*********/ + + +/****s* IB Network Drivers/ip_comp_stats_t +* NAME +* ip_comp_stats_t +* +* DESCRIPTION +* Defines transfer completion statistic information for an IP device. +* +* SYNOPSIS +*/ +typedef struct _ip_comp_stats +{ + uint64_t success; + uint64_t error; + uint64_t dropped; + +} ip_comp_stats_t; +/* +* FIELDS +* success +* Total number of requests transfered successfully. +* +* error +* Total number of requests that failed being transfered. +* +* dropped +* Total number of requests that were dropped. +* +* SEE ALSO +* IPoIB, INIC, ip_data_stats_t, ip_stats_t +*********/ + + +/****s* IB Network Drivers/ip_stats_t +* NAME +* ip_stats_t +* +* DESCRIPTION +* Defines statistic information for an IP device. +* +* SYNOPSIS +*/ +typedef struct _ip_stats +{ + ip_comp_stats_t comp; + ip_data_stats_t ucast; + ip_data_stats_t bcast; + ip_data_stats_t mcast; + +} ip_stats_t; +/* +* FIELDS +* comp +* Request completion statistics. +* +* ucast +* Data statistics for unicast packets +* +* bcast +* Data statistics for broadcast packets +* +* mcast +* Data statistics for multicast packets +* +* SEE ALSO +* IPoIB, INIC, ip_data_stats_t, ip_comp_stats_t +*********/ + + +typedef enum _ip_stat_sel +{ + IP_STAT_SUCCESS, + IP_STAT_ERROR, + IP_STAT_DROPPED, + IP_STAT_UCAST_BYTES, + IP_STAT_UCAST_FRAMES, + IP_STAT_BCAST_BYTES, + IP_STAT_BCAST_FRAMES, + IP_STAT_MCAST_BYTES, + IP_STAT_MCAST_FRAMES + +} ip_stat_sel_t; + +#endif /* _IP_STATS_H_ */ diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/SOURCES b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/SOURCES new file mode 100644 index 00000000..a528904b --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/SOURCES @@ -0,0 +1,59 @@ +TARGETNAME=ipoib +TARGETPATH=..\..\..\bin\kernel\obj$(BUILD_ALT_DIR) +TARGETTYPE=DRIVER + +!if $(_NT_TOOLS_VERSION) != 0x700 +# WDK build only - transform .inx --> .inf adding date & version stamp. +# see .\makefile.inc +INF_NAME=netipoib +INF_TARGET=..\..\..\bin\kernel\$(O)\$(INF_NAME).inf +NTTARGETFILES=$(INF_TARGET) +!endif + +!if $(FREEBUILD) +ENABLE_EVENT_TRACING=1 +!else +#ENABLE_EVENT_TRACING=1 +!endif + + +SOURCES= ipoib_log.mc \ + ipoib.rc \ + ipoib_driver.c \ + ipoib_adapter.c \ + ipoib_endpoint.c \ + ipoib_port.c \ + ipoib_ibat.c \ +# ipoib_cm.c \ + ipoib_xfr_mgr.c + +INCLUDES=..;..\..\..\inc;..\..\..\inc\kernel; + +C_DEFINES=$(C_DEFINES) -DNDIS_MINIPORT_DRIVER -DNDIS_WDM=1 \ + -DDEPRECATE_DDK_FUNCTIONS -DNDIS60_MINIPORT=1 -DNEED_CL_OBJ -DBINARY_COMPATIBLE=0 -DVER_FILEREV=42 + +TARGETLIBS= \ + $(TARGETPATH)\*\complib.lib \ + $(DDK_LIB_PATH)\ndis.lib \ + $(DDK_LIB_PATH)\ntstrsafe.lib \ + $(DDK_LIB_PATH)\strsafe.lib + +!if !defined(DDK_TARGET_OS) || "$(DDK_TARGET_OS)"=="Win2K" +# +# The driver is built in the Win2K build environment +# - use the library version of safe strings +# +#TARGETLIBS= $(TARGETLIBS) $(DDK_LIB_PATH)\ntstrsafe.lib +!endif + +!IFDEF ENABLE_EVENT_TRACING + +C_DEFINES = $(C_DEFINES) -DEVENT_TRACING + +RUN_WPP = $(SOURCES) -km -ext: .c .h .C .H \ + -scan:ipoib_debug.h \ + -func:IPOIB_PRINT(LEVEL,FLAGS,(MSG,...)) \ + -func:IPOIB_PRINT_EXIT(LEVEL,FLAGS,(MSG,...)) +!ENDIF + +MSC_WARNING_LEVEL= /W4 diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib.cdf b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib.cdf new file mode 100644 index 00000000..eb21da98 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib.cdf @@ -0,0 +1,13 @@ +[CatalogHeader] +Name=ipoib.cat +PublicVersion=0x0000001 +EncodingType=0x00010001 +CATATTR1=0x10010001:OSAttr:2:6.0 +[CatalogFiles] +netipoib.inf=netipoib.inf +ipoib.sys=ipoib.sys +ibwsd.dll=ibwsd.dll +ibwsd32.dll=ibwsd32.dll +ibndprov.dll=ibndprov.dll +ibndprov32.dll=ibndprov32.dll +ndinstall.exe=ndinstall.exe diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib.rc b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib.rc new file mode 100644 index 00000000..330f19e7 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib.rc @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib.rc 1611 2006-08-20 14:48:55Z sleybo $ + */ + + +#include + +#define VER_FILETYPE VFT_DRV +#define VER_FILESUBTYPE VFT2_UNKNOWN + +#ifdef _DEBUG_ +#define VER_FILEDESCRIPTION_STR "IP over InfiniBand NDIS Miniport (Debug)" +#else +#define VER_FILEDESCRIPTION_STR "IP over InfiniBand NDIS Miniport" +#endif + +#define VER_INTERNALNAME_STR "ipoib.sys" +#define VER_ORIGINALFILENAME_STR "ipoib.sys" + +#include +#include "ipoib_log.rc" diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib32.cdf b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib32.cdf new file mode 100644 index 00000000..50225ba6 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib32.cdf @@ -0,0 +1,11 @@ +[CatalogHeader] +Name=ipoib.cat +PublicVersion=0x0000001 +EncodingType=0x00010001 +CATATTR1=0x10010001:OSAttr:2:6.0 +[CatalogFiles] +netipoib.inf=netipoib.inf +ipoib.sys=ipoib.sys +ibwsd.dll=ibwsd.dll +ibndprov.dll=ibndprov.dll +ndinstall.exe=ndinstall.exe diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_adapter.c b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_adapter.c new file mode 100644 index 00000000..92747097 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_adapter.c @@ -0,0 +1,1642 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_adapter.c 4506 2009-06-23 14:40:54Z xalex $ + */ + + + +#include "ipoib_adapter.h" +#include "ipoib_port.h" +#include "ipoib_driver.h" +#include "ipoib_debug.h" + +#if defined(EVENT_TRACING) +#ifdef offsetof +#undef offsetof +#endif +#include "ipoib_adapter.tmh" +#endif + + +#define ITEM_POOL_START 16 +#define ITEM_POOL_GROW 16 + + +/* IB Link speeds in 100bps */ +#define ONE_X_IN_100BPS 25000000 +#define FOUR_X_IN_100BPS 100000000 +#define TWELVE_X_IN_100BPS 300000000 + + +/* Declarations */ +static void +adapter_construct( + IN ipoib_adapter_t* const p_adapter ); + + +static ib_api_status_t +adapter_init( + IN ipoib_adapter_t* const p_adapter ); + + +static void +__adapter_destroying( + IN cl_obj_t* const p_obj ); + + +static void +__adapter_free( + IN cl_obj_t* const p_obj ); + + +static ib_api_status_t +__ipoib_pnp_reg( + IN ipoib_adapter_t* const p_adapter, + IN ib_pnp_class_t flags ); + + +static void +__ipoib_pnp_dereg( + IN void* context ); + + +static void +__ipoib_adapter_reset( + IN void* context); + + +static ib_api_status_t +__ipoib_pnp_cb( + IN ib_pnp_rec_t *p_pnp_rec ); + + +void +ipoib_join_mcast( + IN ipoib_adapter_t* const p_adapter ); + + +/* Leaves all mcast groups when port goes down. */ +static void +ipoib_clear_mcast( + IN ipoib_port_t* const p_port ); + +NDIS_STATUS +ipoib_get_adapter_guids( + IN NDIS_HANDLE* const h_adapter, + IN OUT ipoib_adapter_t *p_adapter ); + +NDIS_STATUS +ipoib_get_adapter_params( + IN NDIS_HANDLE* const wrapper_config_context, + IN OUT ipoib_adapter_t *p_adapter, + OUT PUCHAR *p_mac, + OUT UINT *p_len); + + +/* Implementation */ +ib_api_status_t +ipoib_create_adapter( + IN NDIS_HANDLE wrapper_config_context, + IN void* const h_adapter, + OUT ipoib_adapter_t** const pp_adapter ) +{ + ipoib_adapter_t *p_adapter; + ib_api_status_t status; + cl_status_t cl_status; + PUCHAR mac; + UINT len; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_adapter = cl_zalloc( sizeof(ipoib_adapter_t) ); + if( !p_adapter ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate ipoib_adapter_t (%d bytes)", + sizeof(ipoib_adapter_t)) ); + return IB_INSUFFICIENT_MEMORY; + } + + adapter_construct( p_adapter ); + + p_adapter->h_adapter = h_adapter; + + p_adapter->p_ifc = cl_zalloc( sizeof(ib_al_ifc_t) ); + if( !p_adapter->p_ifc ) + { + __adapter_free( &p_adapter->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_create_adapter failed to alloc ipoib_ifc_t %d bytes\n", + sizeof(ib_al_ifc_t)) ); + return IB_INSUFFICIENT_MEMORY; + } + + /* Get the CA and port GUID from the bus driver. */ + status = ipoib_get_adapter_guids( h_adapter, p_adapter ); + if( status != NDIS_STATUS_SUCCESS ) + { +ASSERT(FALSE); +//return NDIS_STATUS_SUCCESS; + __adapter_free( &p_adapter->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_get_adapter_guids returned 0x%.8X.\n", status) ); + return status; + } + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Port %016I64x (CA %016I64x port %d) initializing\n", + p_adapter->guids.port_guid.guid, p_adapter->guids.ca_guid, + p_adapter->guids.port_num) ); + + cl_status = cl_obj_init( &p_adapter->obj, CL_DESTROY_SYNC, + __adapter_destroying, NULL, __adapter_free ); + if( cl_status != CL_SUCCESS ) + { + __adapter_free( &p_adapter->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_obj_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + /* Read configuration parameters. */ + status = ipoib_get_adapter_params( wrapper_config_context, + p_adapter , &mac, &len); + if( status != NDIS_STATUS_SUCCESS ) + { + cl_obj_destroy( &p_adapter->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_get_adapter_params returned 0x%.8x.\n", status) ); + return status; + } + + status = adapter_init( p_adapter ); + if( status != IB_SUCCESS ) + { + cl_obj_destroy( &p_adapter->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("adapter_init returned %s.\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + ETH_COPY_NETWORK_ADDRESS( p_adapter->params.conf_mac.addr, p_adapter->mac.addr ); + /* If there is a NetworkAddress override in registry, use it */ + if( (status == NDIS_STATUS_SUCCESS) && (len == HW_ADDR_LEN) ) + { + if( ETH_IS_MULTICAST(mac) || ETH_IS_BROADCAST(mac) || + !ETH_IS_LOCALLY_ADMINISTERED(mac) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_INIT, + ("Overriding NetworkAddress is invalid - " + "%02x-%02x-%02x-%02x-%02x-%02x\n", + mac[0], mac[1], mac[2], + mac[3], mac[4], mac[5]) ); + } + else + { + ETH_COPY_NETWORK_ADDRESS( p_adapter->params.conf_mac.addr, mac ); + } + } + + *pp_adapter = p_adapter; + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +ib_api_status_t +ipoib_start_adapter( + IN ipoib_adapter_t* const p_adapter ) +{ + ib_api_status_t status; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + status = __ipoib_pnp_reg( p_adapter, + IB_PNP_FLAG_REG_SYNC | IB_PNP_FLAG_REG_COMPLETE ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +void +ipoib_destroy_adapter( + IN ipoib_adapter_t* const p_adapter ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( p_adapter ); + + /* + * Flag the adapter as being removed. We use the IB_PNP_PORT_REMOVE state + * for this purpose. Note that we protect this state change with both the + * mutex and the lock. The mutex provides synchronization as a whole + * between destruction and AL callbacks (PnP, Query, Destruction). + * The lock provides protection + */ + KeWaitForMutexObject( + &p_adapter->mutex, Executive, KernelMode, FALSE, NULL ); + cl_obj_lock( &p_adapter->obj ); + p_adapter->state = IB_PNP_PORT_REMOVE; + + /* + * Clear the pointer to the port object since the object destruction + * will cascade to child objects. This prevents potential duplicate + * destruction (or worse, stale pointer usage). + */ + p_adapter->p_port = NULL; + + cl_obj_unlock( &p_adapter->obj ); + + KeReleaseMutex( &p_adapter->mutex, FALSE ); + + cl_obj_destroy( &p_adapter->obj ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +adapter_construct( + IN ipoib_adapter_t* const p_adapter ) +{ + cl_obj_construct( &p_adapter->obj, IPOIB_OBJ_INSTANCE ); + cl_spinlock_construct( &p_adapter->send_stat_lock ); + cl_spinlock_construct( &p_adapter->recv_stat_lock ); + cl_qpool_construct( &p_adapter->item_pool ); + KeInitializeMutex( &p_adapter->mutex, 0 ); + + cl_thread_construct(&p_adapter->destroy_thread); + + cl_vector_construct( &p_adapter->ip_vector ); + + cl_perf_construct( &p_adapter->perf ); + + p_adapter->state = IB_PNP_PORT_ADD; + p_adapter->port_rate = FOUR_X_IN_100BPS; +} + + +static ib_api_status_t +adapter_init( + IN ipoib_adapter_t* const p_adapter ) +{ + cl_status_t cl_status; + ib_api_status_t status; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + cl_status = cl_perf_init( &p_adapter->perf, MaxPerf ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_perf_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + cl_status = cl_spinlock_init( &p_adapter->send_stat_lock ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_spinlock_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + cl_status = cl_spinlock_init( &p_adapter->recv_stat_lock ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_spinlock_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + cl_status = cl_qpool_init( &p_adapter->item_pool, ITEM_POOL_START, 0, + ITEM_POOL_GROW, sizeof(cl_pool_obj_t), NULL, NULL, NULL ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_qpool_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + + /* We manually manage the size and capacity of the vector. */ + cl_status = cl_vector_init( &p_adapter->ip_vector, 0, + 0, sizeof(net_address_item_t), NULL, NULL, p_adapter ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_vector_init for ip_vector returned %#x\n", + cl_status) ); + return IB_ERROR; + } + + /* Validate the port GUID and generate the MAC address. */ + status = + ipoib_mac_from_guid( p_adapter->guids.port_guid.guid, p_adapter->params.guid_mask, &p_adapter->mac); + if( status != IB_SUCCESS ) + { + if( status == IB_INVALID_GUID_MASK ) + { + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_ERROR, + ("Invalid GUID mask received, rejecting it") ); + ipoib_create_log(p_adapter->h_adapter, GUID_MASK_LOG_INDEX, EVENT_IPOIB_WRONG_PARAMETER_WRN); + } + + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_mac_from_guid returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Open AL. */ + status = p_adapter->p_ifc->open_al( &p_adapter->h_al ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_open_al returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +static ib_api_status_t +__ipoib_pnp_reg( + IN ipoib_adapter_t* const p_adapter, + IN ib_pnp_class_t flags ) +{ + ib_api_status_t status; + ib_pnp_req_t pnp_req; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( !p_adapter->h_pnp ); + CL_ASSERT( !p_adapter->registering ); + + p_adapter->registering = TRUE; + + /* Register for PNP events. */ + cl_memclr( &pnp_req, sizeof(pnp_req) ); + pnp_req.pnp_class = IB_PNP_PORT | flags; + /* + * Context is the cl_obj of the adapter to allow passing cl_obj_deref + * to ib_dereg_pnp. + */ + pnp_req.pnp_context = &p_adapter->obj; + pnp_req.pfn_pnp_cb = __ipoib_pnp_cb; + status = p_adapter->p_ifc->reg_pnp( p_adapter->h_al, &pnp_req, &p_adapter->h_pnp ); + if( status != IB_SUCCESS ) + { + ASSERT(FALSE); + p_adapter->registering = FALSE; + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_reg_pnp returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + /* + * Reference the adapter on behalf of the PNP registration. + * This allows the destruction to block until the PNP deregistration + * completes. + */ + cl_obj_ref( &p_adapter->obj ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +static void +__adapter_destroying( + IN cl_obj_t* const p_obj ) +{ + ipoib_adapter_t *p_adapter; + KLOCK_QUEUE_HANDLE hdl; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_adapter = PARENT_STRUCT( p_obj, ipoib_adapter_t, obj ); + + /* + * The adapter's object will be dereferenced when the deregistration + * completes. No need to lock here since all PnP related API calls + * are driven by NDIS (via the Init/Reset/Destroy paths). + */ + if( p_adapter->h_pnp ) + { + p_adapter->p_ifc->dereg_pnp( p_adapter->h_pnp, cl_obj_deref ); + p_adapter->h_pnp = NULL; + } + + if( p_adapter->packet_filter ) + { + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + cl_obj_lock( &p_adapter->obj ); + + ASSERT( cl_qlist_count( &g_ipoib.adapter_list ) ); + cl_qlist_remove_item( &g_ipoib.adapter_list, &p_adapter->entry ); + + p_adapter->packet_filter = 0; + + cl_obj_unlock( &p_adapter->obj ); + KeReleaseInStackQueuedSpinLock( &hdl ); + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +__adapter_free( + IN cl_obj_t* const p_obj ) +{ + ipoib_adapter_t *p_adapter; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_adapter = PARENT_STRUCT( p_obj, ipoib_adapter_t, obj ); + + if( p_adapter->p_ifc ) + { + if( p_adapter->h_al ) + p_adapter->p_ifc->close_al( p_adapter->h_al ); + + cl_free( p_adapter->p_ifc ); + p_adapter->p_ifc = NULL; + } + + cl_vector_destroy( &p_adapter->ip_vector ); + cl_qpool_destroy( &p_adapter->item_pool ); + cl_spinlock_destroy( &p_adapter->recv_stat_lock ); + cl_spinlock_destroy( &p_adapter->send_stat_lock ); + cl_obj_deinit( p_obj ); + + cl_perf_destroy( &p_adapter->perf, TRUE ); + + cl_free( p_adapter ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static ib_api_status_t +ipoib_query_pkey_index(ipoib_adapter_t *p_adapter) +{ + ib_api_status_t status; + ib_ca_attr_t *ca_attr; + uint32_t ca_size; + uint16_t index = 0; + + /* Query the CA for Pkey table */ + status = p_adapter->p_ifc->query_ca(p_adapter->p_port->ib_mgr.h_ca, NULL, &ca_size); + if(status != IB_INSUFFICIENT_MEMORY) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_query_ca failed\n")); + return status; + } + + ca_attr = (ib_ca_attr_t*)cl_zalloc(ca_size); + if (!ca_attr) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_zalloc can't allocate %d\n",ca_size)); + return IB_INSUFFICIENT_MEMORY; + } + + status = p_adapter->p_ifc->query_ca(p_adapter->p_port->ib_mgr.h_ca, ca_attr,&ca_size); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_query_ca returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + goto pkey_end; + } + CL_ASSERT(ca_attr->p_port_attr[p_adapter->p_port->port_num -1].p_pkey_table[0] == IB_DEFAULT_PKEY); + for(index = 0; index < ca_attr->p_port_attr[p_adapter->p_port->port_num -1].num_pkeys; index++) + { + if(cl_hton16(p_adapter->guids.port_guid.pkey) == ca_attr->p_port_attr[p_adapter->p_port->port_num -1].p_pkey_table[index]) + break; + } + if(index >= ca_attr->p_port_attr[p_adapter->p_port->port_num -1].num_pkeys) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Pkey table is invalid, index not found\n")); + NdisWriteErrorLogEntry( p_adapter->h_adapter, + EVENT_IPOIB_PARTITION_ERR, 1, p_adapter->guids.port_guid.pkey ); + status = IB_NOT_FOUND; + p_adapter->p_port->pkey_index = PKEY_INVALID_INDEX; + goto pkey_end; + } + + p_adapter->p_port->pkey_index = index; + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_IB, + ("for PKEY = 0x%04X got index = %d\n",p_adapter->guids.port_guid.pkey,index)); + +pkey_end: + if(ca_attr) + cl_free(ca_attr); + return status; +} + +static ib_api_status_t +__ipoib_pnp_cb( + IN ib_pnp_rec_t *p_pnp_rec ) +{ + ipoib_adapter_t *p_adapter; + ipoib_port_t *p_port; + ib_pnp_event_t old_state; + ib_pnp_port_rec_t *p_port_rec; + ib_api_status_t status = IB_SUCCESS; + NDIS_LINK_STATE link_state; + NDIS_STATUS_INDICATION status_indication; + + IPOIB_ENTER( IPOIB_DBG_PNP ); + + CL_ASSERT( p_pnp_rec ); + NdisZeroMemory(&link_state, sizeof(NDIS_LINK_STATE)); + p_adapter = + PARENT_STRUCT( p_pnp_rec->pnp_context, ipoib_adapter_t, obj ); + + CL_ASSERT( p_adapter ); + + /* Synchronize with destruction */ + KeWaitForMutexObject( + &p_adapter->mutex, Executive, KernelMode, FALSE, NULL ); + cl_obj_lock( &p_adapter->obj ); + old_state = p_adapter->state; + cl_obj_unlock( &p_adapter->obj ); + if( old_state == IB_PNP_PORT_REMOVE ) + { + KeReleaseMutex( &p_adapter->mutex, FALSE ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_PNP, + ("Aborting - Adapter destroying.\n") ); + return IB_NOT_DONE; + } + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_PNP, + ("p_pnp_rec->pnp_event = 0x%x (%s)\n", + p_pnp_rec->pnp_event, ib_get_pnp_event_str( p_pnp_rec->pnp_event )) ); + + p_port_rec = (ib_pnp_port_rec_t*)p_pnp_rec; + + switch( p_pnp_rec->pnp_event ) + { + case IB_PNP_PORT_ADD: + CL_ASSERT( !p_pnp_rec->context ); + /* Only process our port GUID. */ + if( p_pnp_rec->guid != p_adapter->guids.port_guid.guid ) + { + status = IB_NOT_DONE; + break; + } + + /* Don't process if we're destroying. */ + if( p_adapter->obj.state == CL_DESTROYING ) + { + status = IB_NOT_DONE; + break; + } + + CL_ASSERT( !p_adapter->p_port ); + /* Allocate all IB resources. */ + cl_obj_lock( &p_adapter->obj ); + p_adapter->state = IB_PNP_PORT_ADD; + cl_obj_unlock( &p_adapter->obj ); + status = ipoib_create_port( p_adapter, p_port_rec, &p_port ); + cl_obj_lock( &p_adapter->obj ); + if( status != IB_SUCCESS ) + { + p_adapter->state = old_state; + cl_obj_unlock( &p_adapter->obj ); + p_adapter->hung = TRUE; + break; + } + + p_pnp_rec->context = p_port; + + p_adapter->p_port = p_port; + cl_obj_unlock( &p_adapter->obj ); + break; + + case IB_PNP_PORT_REMOVE: + /* Release all IB resources. */ + CL_ASSERT( p_pnp_rec->context ); + + cl_obj_lock( &p_adapter->obj ); + p_adapter->state = IB_PNP_PORT_REMOVE; + p_port = p_adapter->p_port; + p_adapter->p_port = NULL; + cl_obj_unlock( &p_adapter->obj ); + ipoib_port_destroy( p_port ); + p_pnp_rec->context = NULL; + status = IB_SUCCESS; + break; + + case IB_PNP_PORT_ACTIVE: + /* Join multicast groups and put QP in RTS. */ + CL_ASSERT( p_pnp_rec->context ); + + cl_obj_lock( &p_adapter->obj ); + p_adapter->state = IB_PNP_PORT_INIT; + cl_obj_unlock( &p_adapter->obj ); + ipoib_port_up( p_adapter->p_port, p_port_rec ); + + status = IB_SUCCESS; + break; + + case IB_PNP_PORT_ARMED: + status = IB_SUCCESS; + break; + + case IB_PNP_PORT_INIT: + /* + * Init could happen if the SM brings the port down + * without changing the physical link. + */ + case IB_PNP_PORT_DOWN: + CL_ASSERT( p_pnp_rec->context ); + + cl_obj_lock( &p_adapter->obj ); + old_state = p_adapter->state; + p_adapter->state = IB_PNP_PORT_DOWN; + cl_obj_unlock( &p_adapter->obj ); + status = IB_SUCCESS; + + if( !p_adapter->registering && old_state != IB_PNP_PORT_DOWN ) + { + link_state.Header.Revision = NDIS_LINK_STATE_REVISION_1; + link_state.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + link_state.Header.Size = sizeof(NDIS_LINK_STATE); + link_state.MediaConnectState = MediaConnectStateDisconnected; + //link_state.MediaConnectState = MediaConnectStateConnected; + link_state.MediaDuplexState = MediaDuplexStateFull; + link_state.XmitLinkSpeed = link_state.RcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + + IPOIB_INIT_NDIS_STATUS_INDICATION(&status_indication, + p_adapter->h_adapter, + NDIS_STATUS_LINK_STATE, + (PVOID)&link_state, + sizeof(link_state)); + + NdisMIndicateStatusEx(p_adapter->h_adapter,&status_indication); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link DOWN!\n") ); + + ipoib_port_down( p_adapter->p_port ); + } + break; + + case IB_PNP_REG_COMPLETE: + if( p_adapter->registering ) + { + p_adapter->registering = FALSE; + cl_obj_lock( &p_adapter->obj ); + old_state = p_adapter->state; + cl_obj_unlock( &p_adapter->obj ); + + if( old_state == IB_PNP_PORT_DOWN ) + { + /* If we were initializing, we might have pended some OIDs. */ + ipoib_resume_oids( p_adapter ); + + link_state.Header.Revision = NDIS_LINK_STATE_REVISION_1; + link_state.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + link_state.Header.Size = sizeof(NDIS_LINK_STATE); + link_state.MediaConnectState = MediaConnectStateDisconnected; + //link_state.MediaConnectState = MediaConnectStateConnected; + link_state.MediaDuplexState = MediaDuplexStateFull; + link_state.XmitLinkSpeed = link_state.RcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + + IPOIB_INIT_NDIS_STATUS_INDICATION(&status_indication, + p_adapter->h_adapter, + NDIS_STATUS_LINK_STATE, + (PVOID)&link_state, + sizeof(link_state)); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_INIT, + ("Indicate DISCONNECT\n") ); + NdisMIndicateStatusEx(p_adapter->h_adapter,&status_indication); + } + } + + if( p_adapter->reset && p_adapter->state != IB_PNP_PORT_INIT ) + { + p_adapter->reset = FALSE; + NdisMResetComplete( + p_adapter->h_adapter, NDIS_STATUS_SUCCESS, TRUE ); + } + status = IB_SUCCESS; + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("IPOIB: Received unhandled PnP event 0x%x (%s)\n", + p_pnp_rec->pnp_event, ib_get_pnp_event_str( p_pnp_rec->pnp_event )) ); + /* Fall through. */ + + status = IB_SUCCESS; + + /* We ignore events below if the link is not active. */ + if( p_port_rec->p_port_attr->link_state != IB_LINK_ACTIVE ) + break; + + case IB_PNP_PKEY_CHANGE: + if(p_pnp_rec->pnp_event == IB_PNP_PKEY_CHANGE && + p_adapter->guids.port_guid.pkey != IB_DEFAULT_PKEY) + { + status = ipoib_query_pkey_index(p_adapter); + if(status != IB_SUCCESS) + { + cl_obj_lock( &p_adapter->obj ); + p_adapter->state = IB_PNP_PORT_INIT; + cl_obj_unlock( &p_adapter->obj ); + } + } + + case IB_PNP_SM_CHANGE: + case IB_PNP_GID_CHANGE: + case IB_PNP_LID_CHANGE: + + cl_obj_lock( &p_adapter->obj ); + old_state = p_adapter->state; + switch( old_state ) + { + case IB_PNP_PORT_DOWN: + p_adapter->state = IB_PNP_PORT_INIT; + break; + + default: + p_adapter->state = IB_PNP_PORT_DOWN; + } + cl_obj_unlock( &p_adapter->obj ); + + if( p_adapter->registering ) + break; + + switch( old_state ) + { + case IB_PNP_PORT_ACTIVE: + case IB_PNP_PORT_INIT: + link_state.Header.Revision = NDIS_LINK_STATE_REVISION_1; + link_state.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + link_state.Header.Size = sizeof(NDIS_LINK_STATE); + link_state.MediaConnectState = MediaConnectStateDisconnected; + //link_state.MediaConnectState = MediaConnectStateConnected; + link_state.MediaDuplexState = MediaDuplexStateFull; + link_state.XmitLinkSpeed = link_state.RcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + + IPOIB_INIT_NDIS_STATUS_INDICATION(&status_indication, + p_adapter->h_adapter, + NDIS_STATUS_LINK_STATE, + (PVOID)&link_state, + sizeof(link_state)); + + NdisMIndicateStatusEx(p_adapter->h_adapter,&status_indication); + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link DOWN!\n") ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_INIT, + ("Indicate DISCONNECT\n") ); + + ipoib_port_down( p_adapter->p_port ); + /* Fall through. */ + + case IB_PNP_PORT_DOWN: + cl_obj_lock( &p_adapter->obj ); + p_adapter->state = IB_PNP_PORT_INIT; + cl_obj_unlock( &p_adapter->obj ); + ipoib_port_up( p_adapter->p_port, (ib_pnp_port_rec_t*)p_pnp_rec ); + } + break; + } + + KeReleaseMutex( &p_adapter->mutex, FALSE ); + + IPOIB_EXIT( IPOIB_DBG_PNP ); + return status; +} + + +/* Joins/leaves mcast groups based on currently programmed mcast MACs. */ +void +ipoib_refresh_mcast( + IN ipoib_adapter_t* const p_adapter, + IN mac_addr_t* const p_mac_array, + IN const uint8_t num_macs ) +{ + uint8_t i, j; + ipoib_port_t *p_port = NULL; + + IPOIB_ENTER( IPOIB_DBG_MCAST ); + cl_obj_lock( &p_adapter->obj ); + if( p_adapter->state == IB_PNP_PORT_ACTIVE ) + { + p_port = p_adapter->p_port; + ipoib_port_ref( p_port, ref_refresh_mcast ); + } + cl_obj_unlock( &p_adapter->obj ); + + if( p_port ) + { + /* Purge old entries. */ + for( i = 0; i < p_adapter->mcast_array_size; i++ ) + { + for( j = 0; j < num_macs; j++ ) + { + if( !cl_memcmp( &p_adapter->mcast_array[i], &p_mac_array[j], + sizeof(mac_addr_t) ) ) + { + break; + } + } + if( j != num_macs ) + continue; + + ipoib_port_remove_endpt( p_port, p_adapter->mcast_array[i] ); + } + + /* Add new entries */ + for( i = 0; i < num_macs; i++ ) + { + for( j = 0; j < p_adapter->mcast_array_size; j++ ) + { + if( !cl_memcmp( &p_adapter->mcast_array[j], &p_mac_array[i], + sizeof(mac_addr_t) ) ) + { + break; + } + } + + if( j != p_adapter->mcast_array_size ) + continue; + if ( ( p_mac_array[i].addr[0] == 1 && p_mac_array[i].addr[1] == 0 && p_mac_array[i].addr[2] == 0x5e && + p_mac_array[i].addr[3] == 0 && p_mac_array[i].addr[4] == 0 && p_mac_array[i].addr[5] == 1 ) || + !( p_mac_array[i].addr[0] == 1 && p_mac_array[i].addr[1] == 0 && p_mac_array[i].addr[2] == 0x5e ) + ) + { + ipoib_port_join_mcast( p_port, p_mac_array[i], IB_MC_REC_STATE_FULL_MEMBER ); + } + } + } + + /* Copy the MAC array. */ + NdisMoveMemory( p_adapter->mcast_array, p_mac_array, + num_macs * sizeof(mac_addr_t) ); + p_adapter->mcast_array_size = num_macs; + + if( p_port ) + ipoib_port_deref( p_port, ref_refresh_mcast ); + + IPOIB_EXIT( IPOIB_DBG_MCAST ); +} + + +ib_api_status_t +ipoib_reset_adapter( + IN ipoib_adapter_t* const p_adapter ) +{ + ib_api_status_t status; + ib_pnp_handle_t h_pnp; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + if( p_adapter->reset ) + return IB_INVALID_STATE; + + p_adapter->hung = FALSE; + p_adapter->reset = TRUE; + + if( p_adapter->h_pnp ) + { + h_pnp = p_adapter->h_pnp; + p_adapter->h_pnp = NULL; + status = p_adapter->p_ifc->dereg_pnp( h_pnp, __ipoib_pnp_dereg ); + if( status == IB_SUCCESS ) + status = IB_NOT_DONE; + } + else + { + status = __ipoib_pnp_reg( p_adapter, IB_PNP_FLAG_REG_COMPLETE ); + if( status == IB_SUCCESS ) + p_adapter->hung = FALSE; + } + if (status == IB_NOT_DONE) { + p_adapter->reset = TRUE; + } + else { + p_adapter->reset = FALSE; + } + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +static void +__ipoib_pnp_dereg( + IN void* context ) +{ + ipoib_adapter_t* p_adapter; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_adapter = PARENT_STRUCT( context, ipoib_adapter_t, obj ); + + cl_thread_init(&p_adapter->destroy_thread, __ipoib_adapter_reset, (void*)p_adapter, "destroy_thread"); + + IPOIB_ENTER( IPOIB_DBG_INIT ); + +} + +static void +__ipoib_adapter_reset( + IN void* context) +{ + + ipoib_adapter_t *p_adapter; + ipoib_port_t *p_port; + ib_api_status_t status; + ib_pnp_event_t state; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Got RESET\n") ); +// return; + p_adapter = (ipoib_adapter_t*)context; + + /* Synchronize with destruction */ + KeWaitForMutexObject( + &p_adapter->mutex, Executive, KernelMode, FALSE, NULL ); + + cl_obj_lock( &p_adapter->obj ); + + CL_ASSERT( !p_adapter->h_pnp ); + + if( p_adapter->state != IB_PNP_PORT_REMOVE ) + p_adapter->state = IB_PNP_PORT_ADD; + + state = p_adapter->state; + + /* Destroy the current port instance if it still exists. */ + p_port = p_adapter->p_port; + p_adapter->p_port = NULL; + cl_obj_unlock( &p_adapter->obj ); + + if( p_port ) + ipoib_port_destroy( p_port ); + ASSERT(p_adapter->reset == TRUE); + if( state != IB_PNP_PORT_REMOVE ) + { + status = __ipoib_pnp_reg( p_adapter, IB_PNP_FLAG_REG_COMPLETE ); + if( status != IB_SUCCESS ) + { + p_adapter->reset = FALSE; + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__ipoib_pnp_reg returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + NdisMResetComplete( + p_adapter->h_adapter, NDIS_STATUS_HARD_ERRORS, TRUE ); + } + } + else + { + p_adapter->reset = FALSE; + NdisMResetComplete( + p_adapter->h_adapter, NDIS_STATUS_SUCCESS, TRUE ); + status = IB_SUCCESS; + } + + /* Dereference the adapter since the previous registration is now gone. */ + cl_obj_deref( &p_adapter->obj ); + + KeReleaseMutex( &p_adapter->mutex, FALSE ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +void +ipoib_set_rate( + IN ipoib_adapter_t* const p_adapter, + IN const uint8_t link_width, + IN const uint8_t link_speed ) +{ + uint32_t rate; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + /* Set the link speed based on the IB link speed (1x vs 4x, etc). */ + switch( link_speed ) + { + case IB_LINK_SPEED_ACTIVE_2_5: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link speed is 2.5Gs\n") ); + rate = IB_LINK_SPEED_ACTIVE_2_5; + break; + + case IB_LINK_SPEED_ACTIVE_5: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link speed is 5G\n") ); + rate = IB_LINK_SPEED_ACTIVE_5; + break; + + case IB_LINK_SPEED_ACTIVE_10: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link speed is 10G\n") ); + rate = IB_LINK_SPEED_ACTIVE_10; + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid link speed %d.\n", link_speed) ); + rate = 0; + } + + switch( link_width ) + { + case IB_LINK_WIDTH_ACTIVE_1X: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link width is 1X\n") ); + rate *= ONE_X_IN_100BPS; + break; + + case IB_LINK_WIDTH_ACTIVE_4X: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link width is 4X\n") ); + rate *= FOUR_X_IN_100BPS; + break; + + case IB_LINK_WIDTH_ACTIVE_12X: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Link width is 12X\n") ); + rate *= TWELVE_X_IN_100BPS; + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid link rate (%d).\n", link_width) ); + rate = 0; + } + + p_adapter->port_rate = rate; + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +ib_api_status_t +ipoib_set_active( + IN ipoib_adapter_t* const p_adapter ) +{ + ib_pnp_event_t old_state; + uint8_t i; + ib_api_status_t status = IB_SUCCESS; + NDIS_LINK_STATE link_state; + NDIS_STATUS_INDICATION status_indication; + IPOIB_ENTER( IPOIB_DBG_INIT ); + + NdisZeroMemory(&link_state, sizeof(NDIS_LINK_STATE)); + cl_obj_lock( &p_adapter->obj ); + old_state = p_adapter->state; + + /* Change the state to indicate that we are now connected and live. */ + if( old_state == IB_PNP_PORT_INIT ) + p_adapter->state = IB_PNP_PORT_ACTIVE; + + cl_obj_unlock( &p_adapter->obj ); + + /* + * If we had a pending OID request for OID_GEN_LINK_SPEED, + * complete it now. + */ + switch( old_state ) + { + case IB_PNP_PORT_ADD: + ipoib_reg_addrs( p_adapter ); + /* Fall through. */ + + case IB_PNP_PORT_REMOVE: + ipoib_resume_oids( p_adapter ); + break; + + default: + if (p_adapter->guids.port_guid.pkey != IB_DEFAULT_PKEY) + { + status = ipoib_query_pkey_index(p_adapter); + if( IB_SUCCESS != status) + { + break; + } + } + /* Join all programmed multicast groups. */ + for( i = 0; i < p_adapter->mcast_array_size; i++ ) + { + ipoib_port_join_mcast( + p_adapter->p_port, p_adapter->mcast_array[i] ,IB_MC_REC_STATE_FULL_MEMBER); + } + + /* Register all existing addresses. */ + ipoib_reg_addrs( p_adapter ); + + ipoib_resume_oids( p_adapter ); + + /* + * Now that we're in the broadcast group, notify that + * we have a link. + */ + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, ("Link UP!\n") ); + NdisWriteErrorLogEntry( p_adapter->h_adapter, + EVENT_IPOIB_PORT_UP + (p_adapter->port_rate/ONE_X_IN_100BPS), + 1, p_adapter->port_rate ); + + if( !p_adapter->reset ) + { + link_state.Header.Revision = NDIS_LINK_STATE_REVISION_1; + link_state.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + link_state.Header.Size = sizeof(NDIS_LINK_STATE); + link_state.MediaConnectState = MediaConnectStateConnected; + link_state.MediaDuplexState = MediaDuplexStateFull; + link_state.XmitLinkSpeed = + link_state.RcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + link_state.PauseFunctions = NdisPauseFunctionsSendAndReceive; + IPOIB_INIT_NDIS_STATUS_INDICATION(&status_indication, + p_adapter->h_adapter, + NDIS_STATUS_LINK_STATE, + (PVOID)&link_state, + sizeof(link_state)); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, ("***************Indicate connect!\n") ); + NdisMIndicateStatusEx(p_adapter->h_adapter,&status_indication); + } + } + + if( p_adapter->reset ) + { + ASSERT(FALSE); + p_adapter->reset = FALSE; + NdisMResetComplete( + p_adapter->h_adapter, NDIS_STATUS_SUCCESS, TRUE ); + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + +/* + * If something goes wrong after the port goes active, e.g. + * - PortInfo query failure + * - MC Join timeout + * - etc + * Mark the port state as down, resume any pended OIDS, etc. + */ +void +ipoib_set_inactive( + IN ipoib_adapter_t* const p_adapter ) +{ + ib_pnp_event_t old_state; + NDIS_LINK_STATE link_state; + NDIS_STATUS_INDICATION status_indication; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + NdisZeroMemory(&link_state, sizeof(NDIS_LINK_STATE)); + cl_obj_lock( &p_adapter->obj ); + old_state = p_adapter->state; + if( old_state != IB_PNP_PORT_REMOVE ) + p_adapter->state = IB_PNP_PORT_DOWN; + cl_obj_unlock( &p_adapter->obj ); + + /* + * If we had a pending OID request for OID_GEN_LINK_SPEED, + * complete it now. + */ + if( old_state == IB_PNP_PORT_INIT ) + { + link_state.Header.Revision = NDIS_LINK_STATE_REVISION_1; + link_state.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + link_state.Header.Size = sizeof(NDIS_LINK_STATE); + link_state.MediaConnectState = MediaConnectStateDisconnected; + //link_state.MediaConnectState = MediaConnectStateConnected; + + link_state.MediaDuplexState = MediaDuplexStateFull; + link_state.XmitLinkSpeed = link_state.RcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + + IPOIB_INIT_NDIS_STATUS_INDICATION(&status_indication, + p_adapter->h_adapter, + NDIS_STATUS_LINK_STATE, + (PVOID)&link_state, + sizeof(link_state)); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, ("Indicate Disconnect!\n") ); + NdisMIndicateStatusEx(p_adapter->h_adapter,&status_indication); + + ipoib_resume_oids( p_adapter ); + } + + if( p_adapter->reset ) + { + p_adapter->reset = FALSE; + NdisMResetComplete( + p_adapter->h_adapter, NDIS_STATUS_SUCCESS, TRUE ); + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + +NDIS_STATUS +ipoib_get_gen_stat( + IN ipoib_adapter_t* const p_adapter, + OUT pending_oid_t* const p_oid_info) +{ + PNDIS_STATISTICS_INFO StatisticsInfo; + IPOIB_ENTER( IPOIB_DBG_STAT ); + + if (p_oid_info->buf_len < sizeof(StatisticsInfo)) + { + *p_oid_info->p_bytes_needed = sizeof(NDIS_STATISTICS_INFO); + return NDIS_STATUS_INVALID_LENGTH; + } + + StatisticsInfo = (PNDIS_STATISTICS_INFO)p_oid_info->p_buf; + StatisticsInfo->Header.Revision = NDIS_OBJECT_REVISION_1; + StatisticsInfo->Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + StatisticsInfo->Header.Size = sizeof(NDIS_STATISTICS_INFO); + /*StatisticsInfo->SupportedStatistics = NDIS_STATISTICS_FLAGS_VALID_RCV_DISCARDS | + NDIS_STATISTICS_FLAGS_VALID_RCV_ERROR | + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_FRAMES_RCV | + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_FRAMES_RCV | + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_FRAMES_RCV | + NDIS_STATISTICS_FLAGS_VALID_BYTES_RCV | + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_BYTES_RCV | + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_BYTES_RCV | + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_BYTES_RCV | + + NDIS_STATISTICS_FLAGS_VALID_XMIT_DISCARDS | + NDIS_STATISTICS_FLAGS_VALID_XMIT_ERROR | + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_FRAMES_XMIT | + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_FRAMES_XMIT | + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_FRAMES_XMIT | + NDIS_STATISTICS_FLAGS_VALID_BYTES_XMIT | + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_BYTES_XMIT | + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_BYTES_XMIT | + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_BYTES_XMIT; */ + + + StatisticsInfo->SupportedStatistics = NDIS_STATISTICS_FLAGS_VALID_DIRECTED_FRAMES_RCV | + //The data in the ifHCInUcastPkts member is valid. + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_FRAMES_RCV | + //The data in the ifHCInMulticastPkts member is valid. + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_FRAMES_RCV | + //The data in the ifHCInBroadcastPkts member is valid. + NDIS_STATISTICS_FLAGS_VALID_BYTES_RCV | + //The data in the ifHCInOctets member is valid. + NDIS_STATISTICS_FLAGS_VALID_RCV_DISCARDS | + //The data in the ifInDiscards member is valid. + NDIS_STATISTICS_FLAGS_VALID_RCV_ERROR | + //The data in the ifInErrors member is valid. + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_FRAMES_XMIT | + //The data in the ifHCOutUcastPkts member is valid. + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_FRAMES_XMIT | + //The data in the ifHCOutMulticastPkts member is valid. + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_FRAMES_XMIT | + //The data in the ifHCOutBroadcastPkts member is valid. + NDIS_STATISTICS_FLAGS_VALID_BYTES_XMIT | + //The data in the ifHCOutOctets member is valid. + NDIS_STATISTICS_FLAGS_VALID_XMIT_ERROR | + //The data in the ifOutErrors member is valid. + NDIS_STATISTICS_FLAGS_VALID_XMIT_DISCARDS | + //The data in the ifOutDiscards member is valid. + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_BYTES_RCV | + //The data in the ifHCInUcastOctets member is valid. + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_BYTES_RCV | + //The data in the ifHCInMulticastOctets member is valid. + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_BYTES_RCV | + //The data in the ifHCInBroadcastOctets member is valid. + NDIS_STATISTICS_FLAGS_VALID_DIRECTED_BYTES_XMIT | + //The data in the ifHCOutUcastOctets member is valid. + NDIS_STATISTICS_FLAGS_VALID_MULTICAST_BYTES_XMIT | + //The data in the ifHCOutMulticastOctets member is valid. + NDIS_STATISTICS_FLAGS_VALID_BROADCAST_BYTES_XMIT ; + //The data in the ifHCOutBroadcastOctets member is valid + + cl_spinlock_acquire( &p_adapter->recv_stat_lock ); + StatisticsInfo->ifInDiscards = p_adapter->recv_stats.comp.dropped + + p_adapter->recv_stats.comp.error; + StatisticsInfo->ifInErrors = p_adapter->recv_stats.comp.error; + StatisticsInfo->ifHCInOctets = p_adapter->recv_stats.ucast.bytes + + p_adapter->recv_stats.bcast.bytes + + p_adapter->recv_stats.mcast.bytes; + StatisticsInfo->ifHCInUcastPkts = p_adapter->recv_stats.ucast.frames; + StatisticsInfo->ifHCInMulticastPkts = p_adapter->recv_stats.mcast.frames; + StatisticsInfo->ifHCInBroadcastPkts = p_adapter->recv_stats.bcast.frames; + StatisticsInfo->ifHCInMulticastOctets = p_adapter->recv_stats.mcast.bytes; + StatisticsInfo->ifHCInBroadcastOctets = p_adapter->recv_stats.bcast.bytes; + StatisticsInfo->ifHCInUcastOctets = p_adapter->recv_stats.ucast.bytes; + cl_spinlock_release( &p_adapter->recv_stat_lock ); + + cl_spinlock_acquire( &p_adapter->send_stat_lock ); + StatisticsInfo->ifHCOutOctets = p_adapter->send_stats.ucast.bytes + + p_adapter->send_stats.mcast.bytes + + p_adapter->send_stats.bcast.bytes; + StatisticsInfo->ifHCOutUcastPkts = p_adapter->send_stats.ucast.frames; + StatisticsInfo->ifHCOutMulticastPkts = p_adapter->send_stats.mcast.frames; + StatisticsInfo->ifHCOutBroadcastPkts = p_adapter->send_stats.bcast.frames; + StatisticsInfo->ifOutErrors = p_adapter->send_stats.comp.error; + StatisticsInfo->ifOutDiscards = p_adapter->send_stats.comp.dropped; + StatisticsInfo->ifHCOutUcastOctets = p_adapter->send_stats.ucast.bytes; + StatisticsInfo->ifHCOutMulticastOctets = p_adapter->send_stats.mcast.bytes; + StatisticsInfo->ifHCOutBroadcastOctets = p_adapter->send_stats.bcast.bytes; + cl_spinlock_release( &p_adapter->send_stat_lock ); + + *p_oid_info->p_bytes_used = sizeof(NDIS_STATISTICS_INFO); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_SUCCESS; +} + +NDIS_STATUS +ipoib_get_recv_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN pending_oid_t* const p_oid_info ) +{ + uint64_t stat; + + IPOIB_ENTER( IPOIB_DBG_STAT ); + + CL_ASSERT( p_adapter ); + + cl_spinlock_acquire( &p_adapter->recv_stat_lock ); + switch( stat_sel ) + { + case IP_STAT_SUCCESS: + stat = p_adapter->recv_stats.comp.success; + break; + + case IP_STAT_ERROR: + stat = p_adapter->recv_stats.comp.error; + break; + + case IP_STAT_DROPPED: + stat = p_adapter->recv_stats.comp.dropped; + break; + + case IP_STAT_UCAST_BYTES: + stat = p_adapter->recv_stats.ucast.bytes; + break; + + case IP_STAT_UCAST_FRAMES: + stat = p_adapter->recv_stats.ucast.frames; + break; + + case IP_STAT_BCAST_BYTES: + stat = p_adapter->recv_stats.bcast.bytes; + break; + + case IP_STAT_BCAST_FRAMES: + stat = p_adapter->recv_stats.bcast.frames; + break; + + case IP_STAT_MCAST_BYTES: + stat = p_adapter->recv_stats.mcast.bytes; + break; + + case IP_STAT_MCAST_FRAMES: + stat = p_adapter->recv_stats.mcast.frames; + break; + + default: + stat = 0; + } + cl_spinlock_release( &p_adapter->recv_stat_lock ); + + *p_oid_info->p_bytes_needed = sizeof(uint64_t); + + if( p_oid_info->buf_len >= sizeof(uint64_t) ) + { + *((uint64_t*)p_oid_info->p_buf) = stat; + *p_oid_info->p_bytes_used = sizeof(uint64_t); + } + else if( p_oid_info->buf_len >= sizeof(uint32_t) ) + { + *((uint32_t*)p_oid_info->p_buf) = (uint32_t)stat; + *p_oid_info->p_bytes_used = sizeof(uint32_t); + } + else + { + *p_oid_info->p_bytes_used = 0; + IPOIB_EXIT( IPOIB_DBG_STAT ); + return NDIS_STATUS_INVALID_LENGTH; + } + + IPOIB_EXIT( IPOIB_DBG_STAT ); + return NDIS_STATUS_SUCCESS; +} + + +void +ipoib_inc_recv_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN const size_t bytes OPTIONAL, + IN const size_t packets OPTIONAL ) +{ + IPOIB_ENTER( IPOIB_DBG_STAT ); + + cl_spinlock_acquire( &p_adapter->recv_stat_lock ); + switch( stat_sel ) + { + case IP_STAT_ERROR: + p_adapter->recv_stats.comp.error++; + break; + + case IP_STAT_DROPPED: + p_adapter->recv_stats.comp.dropped++; + break; + + case IP_STAT_UCAST_BYTES: + case IP_STAT_UCAST_FRAMES: + p_adapter->recv_stats.comp.success++; + p_adapter->recv_stats.ucast.frames += packets; + p_adapter->recv_stats.ucast.bytes += bytes; + break; + + case IP_STAT_BCAST_BYTES: + case IP_STAT_BCAST_FRAMES: + p_adapter->recv_stats.comp.success++; + p_adapter->recv_stats.bcast.frames += packets; + p_adapter->recv_stats.bcast.bytes += bytes; + break; + + case IP_STAT_MCAST_BYTES: + case IP_STAT_MCAST_FRAMES: + p_adapter->recv_stats.comp.success++; + p_adapter->recv_stats.mcast.frames += packets; + p_adapter->recv_stats.mcast.bytes += bytes; + break; + + default: + break; + } + cl_spinlock_release( &p_adapter->recv_stat_lock ); + + IPOIB_EXIT( IPOIB_DBG_STAT ); +} + +NDIS_STATUS +ipoib_get_send_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN pending_oid_t* const p_oid_info ) +{ + uint64_t stat; + + IPOIB_ENTER( IPOIB_DBG_STAT ); + + CL_ASSERT( p_adapter ); + + cl_spinlock_acquire( &p_adapter->send_stat_lock ); + switch( stat_sel ) + { + case IP_STAT_SUCCESS: + stat = p_adapter->send_stats.comp.success; + break; + + case IP_STAT_ERROR: + stat = p_adapter->send_stats.comp.error; + break; + + case IP_STAT_DROPPED: + stat = p_adapter->send_stats.comp.dropped; + break; + + case IP_STAT_UCAST_BYTES: + stat = p_adapter->send_stats.ucast.bytes; + break; + + case IP_STAT_UCAST_FRAMES: + stat = p_adapter->send_stats.ucast.frames; + break; + + case IP_STAT_BCAST_BYTES: + stat = p_adapter->send_stats.bcast.bytes; + break; + + case IP_STAT_BCAST_FRAMES: + stat = p_adapter->send_stats.bcast.frames; + break; + + case IP_STAT_MCAST_BYTES: + stat = p_adapter->send_stats.mcast.bytes; + break; + + case IP_STAT_MCAST_FRAMES: + stat = p_adapter->send_stats.mcast.frames; + break; + + default: + stat = 0; + } + cl_spinlock_release( &p_adapter->send_stat_lock ); + + *p_oid_info->p_bytes_needed = sizeof(uint64_t); + + if( p_oid_info->buf_len >= sizeof(uint64_t) ) + { + *((uint64_t*)p_oid_info->p_buf) = stat; + *p_oid_info->p_bytes_used = sizeof(uint64_t); + } + else if( p_oid_info->buf_len >= sizeof(uint32_t) ) + { + *((uint32_t*)p_oid_info->p_buf) = (uint32_t)stat; + *p_oid_info->p_bytes_used = sizeof(uint32_t); + } + else + { + *p_oid_info->p_bytes_used = 0; + IPOIB_EXIT( IPOIB_DBG_STAT ); + return NDIS_STATUS_INVALID_LENGTH; + } + + IPOIB_EXIT( IPOIB_DBG_STAT ); + return NDIS_STATUS_SUCCESS; +} + + +void +ipoib_inc_send_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN const size_t bytes OPTIONAL ) +{ + IPOIB_ENTER( IPOIB_DBG_STAT ); + + cl_spinlock_acquire( &p_adapter->send_stat_lock ); + switch( stat_sel ) + { + case IP_STAT_ERROR: + p_adapter->send_stats.comp.error++; + break; + + case IP_STAT_DROPPED: + p_adapter->send_stats.comp.dropped++; + break; + + case IP_STAT_UCAST_BYTES: + case IP_STAT_UCAST_FRAMES: + p_adapter->send_stats.comp.success++; + p_adapter->send_stats.ucast.frames++; + p_adapter->send_stats.ucast.bytes += bytes; + break; + + case IP_STAT_BCAST_BYTES: + case IP_STAT_BCAST_FRAMES: + p_adapter->send_stats.comp.success++; + p_adapter->send_stats.bcast.frames++; + p_adapter->send_stats.bcast.bytes += bytes; + break; + + case IP_STAT_MCAST_BYTES: + case IP_STAT_MCAST_FRAMES: + p_adapter->send_stats.comp.success++; + p_adapter->send_stats.mcast.frames++; + p_adapter->send_stats.mcast.bytes += bytes; + break; + + default: + break; + } + cl_spinlock_release( &p_adapter->send_stat_lock ); + + IPOIB_EXIT( IPOIB_DBG_STAT ); +} diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_adapter.h b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_adapter.h new file mode 100644 index 00000000..2254f399 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_adapter.h @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_adapter.h 4494 2009-06-22 14:31:08Z xalex $ + */ + + +#ifndef _IPOIB_ADAPTER_H_ +#define _IPOIB_ADAPTER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ip_stats.h" + + +/* + * Definitions + */ +#define MAX_MCAST 32 + +#define IPV4_ADDR_SIZE 4 + +#define PORT_NUM_INDEX_IN_GUID 3 /* 0 based index into big endian GUID to get port number */ + +/* + * Macros + */ +typedef enum +{ + CSUM_DISABLED = 0, + CSUM_ENABLED, + CSUM_BYPASS +} csum_flag_t; + +typedef enum _ipoib_state +{ + IPOIB_PAUSED, + IPOIB_PAUSING, + IPOIB_RUNNING +} ipoib_state_t; + +typedef struct _ipoib_params +{ + int32_t rq_depth; + int32_t rq_low_watermark; + int32_t sq_depth; + csum_flag_t send_chksum_offload; + csum_flag_t recv_chksum_offload; + uint32_t sa_timeout; + uint32_t sa_retry_cnt; + uint32_t recv_pool_ratio; + uint32_t payload_mtu; + boolean_t lso; + uint32_t xfer_block_size; + mac_addr_t conf_mac; + uint32_t mc_leave_rescan; + uint32_t guid_mask; + uint32_t bc_join_retry; + boolean_t cm_enabled; + uint32_t cm_payload_mtu; + uint32_t cm_xfer_block_size; +} ipoib_params_t; +/* +* FIELDS +* rq_depth +* Number of receive WQEs to allocate. +* +* rq_low_watermark +* Receives are indicated with NDIS_STATUS_RESOURCES when the number of +* receives posted to the RQ falls bellow this value. +* +* sq_depth +* Number of send WQEs to allocate. +* +* send_chksum_offload +* recv_chksum_offload +* Flags to indicate whether to offload send/recv checksums. +* 0 - No hardware cheksum +* 1 - Try to offload if the device support it +* 2 - Always report success (checksum bypass) +* +* wsdp_enabled +* Flag to indicate whether WSDP is enabled for an adapter adapter. +* +* static_lid +* LID to assign to the port if that port is down (not init) and has none. +* This feature allows a LID to be assigned, alowing locally targetted +* traffic to occur even on ports that are not plugged in. +* +* sa_timeout +* Time, in milliseconds, to wait for a response before retransmitting an +* SA query request. +* +* sa_retry_cnt +* Number of times to retry an SA query request. +* +* recv_pool_ratio +* Initial ratio of receive pool size to receive queue depth. +* +* grow_thresh +* Threshold at which to grow the receive pool. Valid values start are +* powers of 2, excluding 1. When zero, grows only when the pool is +* exhausted. Other values indicate fractional values +* (i.e. 2 indicates 1/2, 4 indicates 1/4, etc.) +* +* payload_mtu + The maximum available size of IPoIB transfer unit. + + If using UD mode: +* It should be decremented by size of IPoIB header (==4B) +* For example, if the HCA support 4K MTU, +* upper threshold for payload mtu is 4092B and not 4096B + + If using CM mode: + MTU will be not limited by 4K threshold. + UD QP still may be used for different protocols (like ARP). + For these situations the threshold for the UD QP will take the default value + +* +* lso +* It indicates if there's a support for hardware large/giant send offload +* +*********/ + + +typedef struct _pending_oid +{ + NDIS_OID oid; + PVOID p_buf; + ULONG buf_len; + PULONG p_bytes_used; + PULONG p_bytes_needed; + PNDIS_OID_REQUEST p_pending_oid; +} pending_oid_t; + + +typedef struct _ipoib_adapter +{ + cl_obj_t obj; + NDIS_HANDLE h_adapter; + ipoib_ifc_data_t guids; + + cl_list_item_t entry; + + ib_al_handle_t h_al; + ib_pnp_handle_t h_pnp; + + ib_pnp_event_t state; + boolean_t hung; + boolean_t reset; + boolean_t registering; + + boolean_t pending_query; + pending_oid_t query_oid; + boolean_t pending_set; + pending_oid_t set_oid; + + struct _ipoib_port *p_port; + + uint32_t port_rate; + + ipoib_params_t params; + cl_spinlock_t recv_stat_lock; + ip_stats_t recv_stats; + cl_spinlock_t send_stat_lock; + ip_stats_t send_stats; + + boolean_t is_primary; + struct _ipoib_adapter *p_primary; + + uint32_t packet_filter; + + mac_addr_t mac; + mac_addr_t mcast_array[MAX_MCAST]; + uint8_t mcast_array_size; + + cl_qpool_t item_pool; + + KMUTEX mutex; + + cl_thread_t destroy_thread; + cl_vector_t ip_vector; + + cl_perf_t perf; + NDIS_HANDLE NdisMiniportDmaHandle; + ipoib_state_t ipoib_state; + ib_al_ifc_t *p_ifc; + + ULONG sg_list_size; + +} ipoib_adapter_t; +/* +* FIELDS +* obj +* Complib object for reference counting and destruction synchronization. +* +* h_adapter +* NDIS adapter handle. +* +* guids +* CA and port GUIDs returned by the bus driver. +* +* entry +* List item for storing all adapters in a list for address translation. +* We add adapters when their packet filter is set to a non-zero value, +* and remove them when their packet filter is cleared. This is needed +* since user-mode removal events are generated after the packet filter +* is cleared, but before the adapter is destroyed. +* +* h_al +* AL handle for all IB resources. +* +* h_pnp +* PNP registration handle for port events. +* +* state +* State of the adapter. IB_PNP_PORT_ADD indicates that the adapter +* is ready to transfer data. +* +* hung +* Boolean flag used to return whether we are hung or not. +* +* p_port +* Pointer to an ipoib_port_t representing all resources for moving data +* on the IB fabric. +* +* rate +* Rate, in 100bps increments, of the link. +* +* params +* Configuration parameters. +* +* pending_query +* Indicates that an query OID request is being processed asynchronously. +* +* query_oid +* Information about the pended query OID request. +* Valid only if pending_query is TRUE. +* +* pending_set +* Indicates that an set OID request is being processed asynchronously. +* +* set_oid +* Information about the pended set OID request. +* Valid only if pending_set is TRUE. +* +* recv_lock +* Spinlock protecting receive processing. +* +* recv_stats +* Receive statistics. +* +* send_lock +* Spinlock protecting send processing. +* +* send_stats +* Send statistics. +* +* is_primary +* Boolean flag to indicate if an adapter is the primary adapter +* of a bundle. +* +* p_primary +* Pointer to the primary adapter for a bundle. +* +* packet_filter +* Packet filter set by NDIS. +* +* mac_addr +* Ethernet MAC address reported to NDIS. +* +* mcast_array +* List of multicast MAC addresses programmed by NDIS. +* +* mcast_array_size +* Number of entries in the multicat MAC address array; +* +* item_pool +* Pool of cl_pool_obj_t structures to use for queueing pending +* packets for transmission. +* +* mutex +* Mutex to synchronized PnP callbacks with destruction. +* +* ip_vector +* Vector of assigned IP addresses. +* +* p_ifc +* Pointer to transport interface. +* +*********/ + + +typedef struct _ats_reg +{ + ipoib_adapter_t *p_adapter; + ib_reg_svc_handle_t h_reg_svc; + +} ats_reg_t; +/* +* FIELDS +* p_adapter +* Pointer to the adapter to which this address is assigned. +* +* h_reg_svc +* Service registration handle. +*********/ + + +typedef struct _net_address_item +{ + ats_reg_t *p_reg; + union _net_address_item_address + { + ULONG as_ulong; + UCHAR as_bytes[IPV4_ADDR_SIZE]; + } address; + +} net_address_item_t; +/* +* FIELDS +* p_reg +* Pointer to the ATS registration assigned to this address. +* +* address +* Union representing the IP address as an unsigned long or as +* an array of bytes. +* +* as_ulong +* The IP address represented as an unsigned long. Windows stores +* IPs this way. +* +* as_bytes +* The IP address represented as an array of bytes. +*********/ + + +ib_api_status_t +ipoib_create_adapter( + IN NDIS_HANDLE wrapper_config_context, + IN void* const h_adapter, + OUT ipoib_adapter_t** const pp_adapter ); + + +ib_api_status_t +ipoib_start_adapter( + IN ipoib_adapter_t* const p_adapter ); + + +void +ipoib_destroy_adapter( + IN ipoib_adapter_t* const p_adapter ); + + +/* Joins/leaves mcast groups based on currently programmed mcast MACs. */ +void +ipoib_refresh_mcast( + IN ipoib_adapter_t* const p_adapter, + IN mac_addr_t* const p_mac_array, + IN const uint8_t num_macs ); +/* +* PARAMETERS +* p_adapter +* Instance whose multicast MAC address list to modify. +* +* p_mac_array +* Array of multicast MAC addresses assigned to the adapter. +* +* num_macs +* Number of MAC addresses in the array. +*********/ +NDIS_STATUS +ipoib_get_gen_stat( + IN ipoib_adapter_t* const p_adapter, + OUT pending_oid_t* const p_oid_info ); + +NDIS_STATUS +ipoib_get_recv_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN pending_oid_t* const p_oid_info ); + + +void +ipoib_inc_recv_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN const size_t bytes OPTIONAL, + IN const size_t packets OPTIONAL ); + + +NDIS_STATUS +ipoib_get_send_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN pending_oid_t* const p_oid_info ); + + +void +ipoib_inc_send_stat( + IN ipoib_adapter_t* const p_adapter, + IN const ip_stat_sel_t stat_sel, + IN const size_t bytes OPTIONAL ); + + +void +ipoib_set_rate( + IN ipoib_adapter_t* const p_adapter, + IN const uint8_t link_width, + IN const uint8_t link_speed ); + + +ib_api_status_t +ipoib_set_active( + IN ipoib_adapter_t* const p_adapter ); + +void +ipoib_set_inactive( + IN ipoib_adapter_t* const p_adapter ); + +ib_api_status_t +ipoib_reset_adapter( + IN ipoib_adapter_t* const p_adapter ); + +void +ipoib_reg_addrs( + IN ipoib_adapter_t* const p_adapter ); + +void +ipoib_dereg_addrs( + IN ipoib_adapter_t* const p_adapter ); + +#define IPOIB_INIT_NDIS_STATUS_INDICATION(_pStatusIndication, _M, _St, _Buf, _BufSize) \ + { \ + NdisZeroMemory(_pStatusIndication, sizeof(NDIS_STATUS_INDICATION)); \ + (_pStatusIndication)->Header.Type = NDIS_OBJECT_TYPE_STATUS_INDICATION; \ + (_pStatusIndication)->Header.Revision = NDIS_STATUS_INDICATION_REVISION_1; \ + (_pStatusIndication)->Header.Size = sizeof(NDIS_STATUS_INDICATION); \ + (_pStatusIndication)->SourceHandle = _M; \ + (_pStatusIndication)->StatusCode = _St; \ + (_pStatusIndication)->StatusBuffer = _Buf; \ + (_pStatusIndication)->StatusBufferSize = _BufSize; \ + } + +//TODO rename to 4 +#define IPOIB_MEDIA_MAX_SPEED 40000000000 + +#endif /* _IPOIB_ADAPTER_H_ */ diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_debug.h b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_debug.h new file mode 100644 index 00000000..69ec3c5a --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_debug.h @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_debug.h 3719 2009-01-07 12:31:52Z reuven $ + */ + + +#ifndef _IPOIB_DEBUG_H_ +#define _IPOIB_DEBUG_H_ + +#if defined __MODULE__ +#undef __MODULE__ +#endif + +#define __MODULE__ "[IPoIB]" + +#include + + +/* Object types for passing into complib. */ +#define IPOIB_OBJ_INSTANCE 1 +#define IPOIB_OBJ_PORT 2 +#define IPOIB_OBJ_ENDPOINT 3 + + +extern uint32_t g_ipoib_dbg_level; +extern uint32_t g_ipoib_dbg_flags; + + +#if defined(EVENT_TRACING) +// +// Software Tracing Definitions +// +#define WPP_CONTROL_GUIDS \ + WPP_DEFINE_CONTROL_GUID( \ + IPOIBCtlGuid,(3F9BC73D, EB03, 453a, B27B, 20F9A664211A), \ + WPP_DEFINE_BIT(IPOIB_DBG_ERROR) \ + WPP_DEFINE_BIT(IPOIB_DBG_INIT) \ + WPP_DEFINE_BIT(IPOIB_DBG_PNP) \ + WPP_DEFINE_BIT(IPOIB_DBG_SEND) \ + WPP_DEFINE_BIT(IPOIB_DBG_RECV) \ + WPP_DEFINE_BIT(IPOIB_DBG_ENDPT) \ + WPP_DEFINE_BIT(IPOIB_DBG_IB) \ + WPP_DEFINE_BIT(IPOIB_DBG_BUF) \ + WPP_DEFINE_BIT(IPOIB_DBG_MCAST) \ + WPP_DEFINE_BIT(IPOIB_DBG_ALLOC) \ + WPP_DEFINE_BIT(IPOIB_DBG_OID) \ + WPP_DEFINE_BIT(IPOIB_DBG_IOCTL) \ + WPP_DEFINE_BIT(IPOIB_DBG_STAT) \ + WPP_DEFINE_BIT(IPOIB_DBG_OBJ)) + + + +#define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) \ + (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl) +#define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) WPP_LEVEL_LOGGER(flags) +#define WPP_FLAG_ENABLED(flags) \ + (WPP_LEVEL_ENABLED(flags) && \ + WPP_CONTROL(WPP_BIT_ ## flags).Level >= TRACE_LEVEL_VERBOSE) +#define WPP_FLAG_LOGGER(flags) WPP_LEVEL_LOGGER(flags) + +// begin_wpp config +// IPOIB_ENTER(FLAG); +// IPOIB_EXIT(FLAG); +// USEPREFIX(IPOIB_PRINT, "%!STDPREFIX! [IPoIB] :%!FUNC!() :"); +// USEPREFIX(IPOIB_PRINT_EXIT, "%!STDPREFIX! [IPoIB] :%!FUNC!() :"); +// USESUFFIX(IPOIB_PRINT_EXIT, "[IpoIB] :%!FUNC!():]"); +// USESUFFIX(IPOIB_ENTER, " [IPoIB] :%!FUNC!():["); +// USESUFFIX(IPOIB_EXIT, " [IPoIB] :%!FUNC!():]"); +// end_wpp + +#else + +#include + + +/* + * Debug macros + */ +#define IPOIB_DBG_ERR (1 << 0) +#define IPOIB_DBG_INIT (1 << 1) +#define IPOIB_DBG_PNP (1 << 2) +#define IPOIB_DBG_SEND (1 << 3) +#define IPOIB_DBG_RECV (1 << 4) +#define IPOIB_DBG_ENDPT (1 << 5) +#define IPOIB_DBG_IB (1 << 6) +#define IPOIB_DBG_BUF (1 << 7) +#define IPOIB_DBG_MCAST (1 << 8) +#define IPOIB_DBG_ALLOC (1 << 9) +#define IPOIB_DBG_OID (1 << 10) +#define IPOIB_DBG_IOCTL (1 << 11) +#define IPOIB_DBG_STAT (1 << 12) +#define IPOIB_DBG_OBJ (1 << 13) + +#define IPOIB_DBG_ERROR (CL_DBG_ERROR | IPOIB_DBG_ERR) +#define IPOIB_DBG_ALL CL_DBG_ALL + + +#if DBG + +// assignment of _level_ is need to to overcome warning C4127 +#define IPOIB_PRINT(_level_,_flag_,_msg_) \ + { \ + __pragma(warning(suppress:6326)) \ + if( g_ipoib_dbg_level >= (_level_) ) \ + CL_TRACE( _flag_, g_ipoib_dbg_flags, _msg_ ); \ + } + +#define IPOIB_PRINT_EXIT(_level_,_flag_,_msg_) \ + { \ + __pragma(warning(suppress:6326)) \ + if( g_ipoib_dbg_level >= (_level_) ) \ + CL_TRACE( _flag_, g_ipoib_dbg_flags, _msg_ );\ + IPOIB_EXIT(_flag_);\ + } + +#define IPOIB_ENTER(_flag_) \ + { \ + __pragma(warning(suppress:6326)) \ + if( g_ipoib_dbg_level >= TRACE_LEVEL_VERBOSE ) \ + CL_ENTER( _flag_, g_ipoib_dbg_flags ); \ + } + +#define IPOIB_EXIT(_flag_)\ + { \ + __pragma(warning(suppress:6326)) \ + if( g_ipoib_dbg_level >= TRACE_LEVEL_VERBOSE ) \ + CL_EXIT( _flag_, g_ipoib_dbg_flags ); \ + } + +#define IPOIB_TRACE_BYTES( lvl, ptr, len ) \ + { \ + __pragma(warning(suppress:6326)) \ + if( g_ipoib_dbg_level >= (_level_) && \ + (g_ipoib_dbg_flags & (_flag_)) ) \ + { \ + size_t _loop_; \ + for( _loop_ = 0; _loop_ < (len); ++_loop_ ) \ + { \ + cl_dbg_out( "0x%.2X ", ((uint8_t*)(ptr))[_loop_] ); \ + if( (_loop_ + 1)% 16 == 0 ) \ + cl_dbg_out("\n"); \ + else if( (_loop_ % 4 + 1) == 0 ) \ + cl_dbg_out(" "); \ + } \ + cl_dbg_out("\n"); \ + } \ + } + +#else + +#define IPOIB_PRINT(lvl, flags, msg) + +#define IPOIB_PRINT_EXIT(_level_,_flag_,_msg_) + +#define IPOIB_ENTER(_flag_) + +#define IPOIB_EXIT(_flag_) + +#define IPOIB_TRACE_BYTES( lvl, ptr, len ) + +#endif + +#endif //EVENT_TRACING + + +enum ipoib_perf_counters +{ + SendBundle, + SendPackets, + PortSend, + GetEthHdr, + SendMgrQueue, + GetEndpt, + EndptQueue, + QueuePacket, + BuildSendDesc, + SendMgrFilter, + FilterIp, + QueryIp, + SendTcp, + FilterUdp, + QueryUdp, + SendUdp, + FilterDhcp, + FilterArp, + SendGen, + SendCopy, + PostSend, + ProcessFailedSends, + SendCompBundle, + SendCb, + PollSend, + SendComp, + FreeSendBuf, + RearmSend, + PortResume, + RecvCompBundle, + RecvCb, + PollRecv, + FilterRecv, + GetRecvEndpts, + GetEndptByGid, + GetEndptByLid, + EndptInsert, + RecvTcp, + RecvUdp, + RecvDhcp, + RecvArp, + RecvGen, + BuildPktArray, + PreparePkt, + GetNdisPkt, + RecvNdisIndicate, + PutRecvList, + RepostRecv, + GetRecv, + PostRecv, + RearmRecv, + ReturnPacket, + ReturnPutRecv, + ReturnRepostRecv, + ReturnPreparePkt, + ReturnNdisIndicate, + + /* Must be last! */ + MaxPerf + +}; + + +enum ref_cnt_buckets +{ + ref_init = 0, + ref_refresh_mcast, /* only used in refresh_mcast */ + ref_send_packets, /* only in send_packets */ + ref_get_recv, + ref_repost, /* only in __recv_mgr_repost */ + ref_recv_cb, /* only in __recv_cb */ + ref_send_cb, /* only in __send_cb */ + ref_port_up, + ref_get_bcast, + ref_bcast, /* join and create, used as base only */ + ref_join_mcast, + ref_leave_mcast, + ref_endpt_track, /* used when endpt is in port's child list. */ + + ref_array_size, /* Used to size the array of ref buckets. */ + ref_mask = 100, /* Used to differentiate derefs. */ + + ref_failed_recv_wc = 100 + ref_get_recv, + ref_recv_inv_len = 200 + ref_get_recv, + ref_recv_loopback = 300 + ref_get_recv, + ref_recv_filter = 400 + ref_get_recv, + + ref_bcast_get_cb = 100 + ref_get_bcast, + + ref_join_bcast = 100 + ref_bcast, + ref_create_bcast = 200 + ref_bcast, + ref_bcast_inv_state = 300 + ref_bcast, + ref_bcast_req_failed = 400 + ref_bcast, + ref_bcast_error = 500 + ref_bcast, + ref_bcast_join_failed = 600 + ref_bcast, + ref_bcast_create_failed = 700 + ref_bcast, + + ref_mcast_inv_state = 100 + ref_join_mcast, + ref_mcast_req_failed = 200 + ref_join_mcast, + ref_mcast_no_endpt = 300 + ref_join_mcast, + ref_mcast_av_failed = 400 + ref_join_mcast, + ref_mcast_join_failed = 500 + ref_join_mcast, + + ref_port_info_cb = 100 + ref_port_up + +}; + + +#endif /* _IPOIB_DEBUG_H_ */ diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_driver.c b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_driver.c new file mode 100644 index 00000000..0bc3ebd5 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_driver.c @@ -0,0 +1,4708 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_driver.c 4506 2009-06-23 14:40:54Z xalex $ + */ + +#include "limits.h" +#include "ipoib_driver.h" +#include "ipoib_debug.h" + +#if defined(EVENT_TRACING) +#ifdef offsetof +#undef offsetof +#endif +#include "ipoib_driver.tmh" +#endif + +#include "ipoib_port.h" +#include "ipoib_ibat.h" +#include +#include +#include +#include +#include "ntstrsafe.h" +#include "strsafe.h" +#include + + + +#define MAJOR_DRIVER_VERSION 2 +#define MINOR_DRIVER_VERSION 1 +#if defined(NDIS60_MINIPORT) +#define MAJOR_NDIS_VERSION 6 +#define MINOR_NDIS_VERSION 0 + +#else +#error NDIS Version not defined, try defining NDIS60_MINIPORT +#endif + +PDRIVER_OBJECT g_p_drv_obj; + + +#if 0 +static const NDIS_OID SUPPORTED_OIDS[] = +{ + OID_GEN_SUPPORTED_LIST, + OID_GEN_HARDWARE_STATUS, + OID_GEN_MEDIA_SUPPORTED, + OID_GEN_MEDIA_IN_USE, + OID_GEN_MAXIMUM_LOOKAHEAD, + OID_GEN_MAXIMUM_FRAME_SIZE, + OID_GEN_LINK_SPEED, + OID_GEN_TRANSMIT_BUFFER_SPACE, + OID_GEN_RECEIVE_BUFFER_SPACE, + OID_GEN_TRANSMIT_BLOCK_SIZE, + OID_GEN_RECEIVE_BLOCK_SIZE, + OID_GEN_VENDOR_ID, + OID_GEN_VENDOR_DESCRIPTION, + OID_GEN_CURRENT_PACKET_FILTER, + OID_GEN_CURRENT_LOOKAHEAD, + OID_GEN_DRIVER_VERSION, + OID_GEN_MAXIMUM_TOTAL_SIZE, + OID_GEN_PROTOCOL_OPTIONS, + OID_GEN_MAC_OPTIONS, + OID_GEN_MEDIA_CONNECT_STATUS, + OID_GEN_MAXIMUM_SEND_PACKETS, + OID_GEN_NETWORK_LAYER_ADDRESSES, + OID_GEN_VENDOR_DRIVER_VERSION, + OID_GEN_PHYSICAL_MEDIUM, + OID_GEN_XMIT_OK, + OID_GEN_RCV_OK, + OID_GEN_XMIT_ERROR, + OID_GEN_RCV_ERROR, + OID_GEN_RCV_NO_BUFFER, + OID_GEN_DIRECTED_BYTES_XMIT, + OID_GEN_DIRECTED_FRAMES_XMIT, + OID_GEN_MULTICAST_BYTES_XMIT, + OID_GEN_MULTICAST_FRAMES_XMIT, + OID_GEN_BROADCAST_BYTES_XMIT, + OID_GEN_BROADCAST_FRAMES_XMIT, + OID_GEN_DIRECTED_BYTES_RCV, + OID_GEN_DIRECTED_FRAMES_RCV, + OID_GEN_MULTICAST_BYTES_RCV, + OID_GEN_MULTICAST_FRAMES_RCV, + OID_GEN_BROADCAST_BYTES_RCV, + OID_GEN_BROADCAST_FRAMES_RCV, + OID_802_3_PERMANENT_ADDRESS, + OID_802_3_CURRENT_ADDRESS, + OID_802_3_MULTICAST_LIST, + OID_802_3_MAXIMUM_LIST_SIZE, + OID_802_3_MAC_OPTIONS, + OID_802_3_RCV_ERROR_ALIGNMENT, + OID_802_3_XMIT_ONE_COLLISION, + OID_802_3_XMIT_MORE_COLLISIONS, + OID_TCP_TASK_OFFLOAD +}; +#endif + +NDIS_OID NICSupportedOidsTest[] = +{ + OID_GEN_SUPPORTED_LIST, + OID_GEN_HARDWARE_STATUS, + OID_GEN_MEDIA_SUPPORTED, + OID_GEN_MEDIA_IN_USE, + OID_GEN_MAXIMUM_LOOKAHEAD, + OID_GEN_MAXIMUM_FRAME_SIZE, + OID_GEN_TRANSMIT_BUFFER_SPACE, + OID_GEN_RECEIVE_BUFFER_SPACE, + OID_GEN_TRANSMIT_BLOCK_SIZE, + OID_GEN_RECEIVE_BLOCK_SIZE, + OID_GEN_VENDOR_ID, + OID_GEN_VENDOR_DESCRIPTION, + OID_GEN_VENDOR_DRIVER_VERSION, + OID_GEN_CURRENT_PACKET_FILTER, + OID_GEN_CURRENT_LOOKAHEAD, + OID_GEN_DRIVER_VERSION, + OID_GEN_MAXIMUM_TOTAL_SIZE, + OID_GEN_MAC_OPTIONS, + OID_GEN_MAXIMUM_SEND_PACKETS, + OID_GEN_XMIT_OK, + OID_GEN_RCV_OK, + OID_GEN_XMIT_ERROR, + OID_GEN_RCV_ERROR, + OID_GEN_RCV_NO_BUFFER, + OID_GEN_RCV_CRC_ERROR, + OID_GEN_TRANSMIT_QUEUE_LENGTH, + OID_802_3_PERMANENT_ADDRESS, + OID_802_3_CURRENT_ADDRESS, + OID_802_3_MULTICAST_LIST, + OID_802_3_MAXIMUM_LIST_SIZE, + OID_802_3_RCV_ERROR_ALIGNMENT, + OID_802_3_XMIT_ONE_COLLISION, + OID_802_3_XMIT_MORE_COLLISIONS, + OID_802_3_XMIT_DEFERRED, + OID_802_3_XMIT_MAX_COLLISIONS, + OID_802_3_RCV_OVERRUN, + OID_802_3_XMIT_UNDERRUN, + OID_802_3_XMIT_HEARTBEAT_FAILURE, + OID_802_3_XMIT_TIMES_CRS_LOST, + OID_802_3_XMIT_LATE_COLLISIONS, + +#if !BUILD_W2K + OID_GEN_PHYSICAL_MEDIUM, +#endif + + OID_TCP_TASK_OFFLOAD, + +/* powermanagement */ + + OID_PNP_CAPABILITIES, + OID_PNP_SET_POWER, + OID_PNP_QUERY_POWER, + OID_PNP_ADD_WAKE_UP_PATTERN, + OID_PNP_REMOVE_WAKE_UP_PATTERN, + OID_PNP_ENABLE_WAKE_UP, + + +/* custom oid WMI support */ +// OID_CUSTOM_PERF_COUNTERS, + // OID_CUSTOM_STRING, + + OID_GEN_RECEIVE_SCALE_CAPABILITIES, + OID_GEN_RECEIVE_SCALE_PARAMETERS, + +// +// new and required for NDIS 6 miniports +// + OID_GEN_LINK_PARAMETERS, + OID_GEN_INTERRUPT_MODERATION, + OID_GEN_STATISTICS, + +/* Offload */ + OID_TCP_OFFLOAD_CURRENT_CONFIG, + OID_TCP_OFFLOAD_PARAMETERS, + OID_TCP_OFFLOAD_HARDWARE_CAPABILITIES, + OID_OFFLOAD_ENCAPSULATION, + +/* Header - Data seperation */ + // OID_GEN_HD_SPLIT_PARAMETERS, + // OID_GEN_HD_SPLIT_CURRENT_CONFIG, + +/* VLAN */ + // OID_ADD_VALN_ID, + // OID_DELETE_VLAN_ID, + +/* Set MAC */ + // OID_SET_MAC_ADDRESS + +}; + +static const NDIS_OID SUPPORTED_OIDS[] = +{ + OID_GEN_SUPPORTED_LIST, + OID_GEN_HARDWARE_STATUS, + OID_GEN_MEDIA_SUPPORTED, + OID_GEN_MEDIA_IN_USE, + OID_GEN_MAXIMUM_LOOKAHEAD, + OID_GEN_MAXIMUM_FRAME_SIZE, + OID_GEN_TRANSMIT_BUFFER_SPACE, + OID_GEN_RECEIVE_BUFFER_SPACE, + OID_GEN_TRANSMIT_BLOCK_SIZE, + OID_GEN_RECEIVE_BLOCK_SIZE, + OID_GEN_VENDOR_ID, + OID_GEN_VENDOR_DESCRIPTION, + OID_GEN_VENDOR_DRIVER_VERSION, + OID_GEN_CURRENT_PACKET_FILTER, + OID_GEN_CURRENT_LOOKAHEAD, + OID_GEN_DRIVER_VERSION, + OID_GEN_MAXIMUM_TOTAL_SIZE, + OID_GEN_MAC_OPTIONS, + OID_GEN_MAXIMUM_SEND_PACKETS, + OID_GEN_XMIT_OK, + OID_GEN_RCV_OK, + OID_GEN_XMIT_ERROR, + OID_GEN_RCV_ERROR, + OID_GEN_RCV_NO_BUFFER, + OID_GEN_RCV_CRC_ERROR, + OID_GEN_TRANSMIT_QUEUE_LENGTH, + OID_802_3_PERMANENT_ADDRESS, + OID_802_3_CURRENT_ADDRESS, + OID_802_3_MULTICAST_LIST, + OID_802_3_MAXIMUM_LIST_SIZE, + OID_802_3_RCV_ERROR_ALIGNMENT, + OID_802_3_XMIT_ONE_COLLISION, + OID_802_3_XMIT_MORE_COLLISIONS, + OID_802_3_XMIT_DEFERRED, + OID_802_3_XMIT_MAX_COLLISIONS, + OID_802_3_RCV_OVERRUN, + OID_802_3_XMIT_UNDERRUN, + OID_802_3_XMIT_HEARTBEAT_FAILURE, + OID_802_3_XMIT_TIMES_CRS_LOST, + OID_802_3_XMIT_LATE_COLLISIONS, + +#if !BUILD_W2K + OID_GEN_PHYSICAL_MEDIUM, +#endif + + OID_TCP_TASK_OFFLOAD, + +/* powermanagement */ + + OID_PNP_CAPABILITIES, + OID_PNP_SET_POWER, + OID_PNP_QUERY_POWER, + OID_PNP_ADD_WAKE_UP_PATTERN, + OID_PNP_REMOVE_WAKE_UP_PATTERN, + OID_PNP_ENABLE_WAKE_UP, + +#if 0 +/* custom oid WMI support */ + OID_CUSTOM_PERF_COUNTERS, + OID_CUSTOM_STRING, +#endif + + OID_GEN_RECEIVE_SCALE_CAPABILITIES, + OID_GEN_RECEIVE_SCALE_PARAMETERS, + + +// +// new and required for NDIS 6 miniports +// + OID_GEN_LINK_PARAMETERS, + OID_GEN_INTERRUPT_MODERATION, + OID_GEN_STATISTICS, + +/* Offload */ + OID_TCP_OFFLOAD_CURRENT_CONFIG, + OID_TCP_OFFLOAD_PARAMETERS, + OID_TCP_OFFLOAD_HARDWARE_CAPABILITIES, + OID_OFFLOAD_ENCAPSULATION, + +#if 0 + +/* Header - Data seperation */ + OID_GEN_HD_SPLIT_PARAMETERS, + OID_GEN_HD_SPLIT_CURRENT_CONFIG, + +/* VLAN */ + OID_ADD_VALN_ID, + OID_DELETE_VLAN_ID, + + +/* Set MAC */ + OID_SET_MAC_ADDRESS +#endif + +}; + +static const unsigned char VENDOR_ID[] = {0x00, 0x06, 0x6A, 0x00}; + +#define VENDOR_DESCRIPTION "Internet Protocol over InfiniBand" + +#define IB_INFINITE_SERVICE_LEASE 0xFFFFFFFF + +//The mask is 8 bit and can't contain more than 6 non-zero bits +#define MAX_GUID_MAX 0xFC + + +/* Global driver debug level */ +uint32_t g_ipoib_dbg_level = TRACE_LEVEL_ERROR; +uint32_t g_ipoib_dbg_flags = 0x00000fff; +ipoib_globals_t g_ipoib = {0}; +NDIS_HANDLE g_IpoibMiniportDriverHandle = NULL; +NDIS_HANDLE g_IpoibDriverContext = NULL; + + + +typedef struct _IPOIB_REG_ENTRY +{ + NDIS_STRING RegName; // variable name text + BOOLEAN bRequired; // 1 -> required, 0 -> optional + UINT FieldOffset; // offset in parent struct + UINT FieldSize; // size (in bytes) of the field + UINT Default; // default value to use + UINT Min; // minimum value allowed + UINT Max; // maximum value allowed +} IPOIB_REG_ENTRY, *PIPOIB_REG_ENTRY; + +IPOIB_REG_ENTRY HCARegTable[] = { + // reg value name If Required Offset in parentr struct Field size Default Min Max + {NDIS_STRING_CONST("GUIDMask"), 0, IPOIB_OFFSET(guid_mask), IPOIB_SIZE(guid_mask), 0, 0, MAX_GUID_MAX}, + /* GUIDMask should be the first element */ + {NDIS_STRING_CONST("RqDepth"), 1, IPOIB_OFFSET(rq_depth), IPOIB_SIZE(rq_depth), 512, 128, 1024}, + {NDIS_STRING_CONST("RqLowWatermark"), 0, IPOIB_OFFSET(rq_low_watermark), IPOIB_SIZE(rq_low_watermark), 4, 2, 8}, + {NDIS_STRING_CONST("SqDepth"), 1, IPOIB_OFFSET(sq_depth), IPOIB_SIZE(sq_depth), 512, 128, 1024}, + {NDIS_STRING_CONST("SendChksum"), 1, IPOIB_OFFSET(send_chksum_offload), IPOIB_SIZE(send_chksum_offload),CSUM_ENABLED,CSUM_DISABLED,CSUM_BYPASS}, + {NDIS_STRING_CONST("RecvChksum"), 1, IPOIB_OFFSET(recv_chksum_offload), IPOIB_SIZE(recv_chksum_offload),CSUM_ENABLED,CSUM_DISABLED,CSUM_BYPASS}, + {NDIS_STRING_CONST("SaTimeout"), 1, IPOIB_OFFSET(sa_timeout), IPOIB_SIZE(sa_timeout), 1000, 250, UINT_MAX}, + {NDIS_STRING_CONST("SaRetries"), 1, IPOIB_OFFSET(sa_retry_cnt), IPOIB_SIZE(sa_retry_cnt), 10, 1, UINT_MAX}, + {NDIS_STRING_CONST("RecvRatio"), 1, IPOIB_OFFSET(recv_pool_ratio), IPOIB_SIZE(recv_pool_ratio), 1, 1, 10}, + {NDIS_STRING_CONST("PayloadMtu"), 1, IPOIB_OFFSET(payload_mtu), IPOIB_SIZE(payload_mtu), 2044, 512, MAX_UD_PAYLOAD_MTU}, + {NDIS_STRING_CONST("lso"), 0, IPOIB_OFFSET(lso), IPOIB_SIZE(lso), 0, 0, 1}, + {NDIS_STRING_CONST("MCLeaveRescan"), 1, IPOIB_OFFSET(mc_leave_rescan), IPOIB_SIZE(mc_leave_rescan), 260, 1, 3600}, + {NDIS_STRING_CONST("BCJoinRetry"), 1, IPOIB_OFFSET(bc_join_retry), IPOIB_SIZE(bc_join_retry), 50, 0, 1000}, + {NDIS_STRING_CONST("CmEnabled"), 0, IPOIB_OFFSET(cm_enabled), IPOIB_SIZE(cm_enabled), FALSE, FALSE, TRUE}, + {NDIS_STRING_CONST("CmPayloadMtu"), 1, IPOIB_OFFSET(cm_payload_mtu), IPOIB_SIZE(cm_payload_mtu), MAX_CM_PAYLOAD_MTU, 512, MAX_CM_PAYLOAD_MTU} + +}; + +#define IPOIB_NUM_REG_PARAMS (sizeof (HCARegTable) / sizeof(IPOIB_REG_ENTRY)) + + +void +ipoib_create_log( + NDIS_HANDLE h_adapter, + UINT ind, + ULONG eventLogMsgId) + +{ +#define cMaxStrLen 40 +#define cArrLen 3 + + PWCHAR logMsgArray[cArrLen]; + WCHAR strVal[cMaxStrLen]; + NDIS_STRING AdapterInstanceName; + + IPOIB_INIT_NDIS_STRING(&AdapterInstanceName); + if (NdisMQueryAdapterInstanceName(&AdapterInstanceName, h_adapter)!= NDIS_STATUS_SUCCESS ){ + ASSERT(FALSE); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR,IPOIB_DBG_ERROR, ("[IPoIB] Init:Failed to retreive adapter name.\n")); + return; + } + logMsgArray[0] = AdapterInstanceName.Buffer; + + if (RtlStringCbPrintfW(strVal, sizeof(strVal), L"0x%x", HCARegTable[ind].Default) != STATUS_SUCCESS) { + ASSERT(FALSE); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR,IPOIB_DBG_ERROR, + ("[IPoIB] Init: Problem copying string value: exiting\n")); + return; + } + + logMsgArray[0] = AdapterInstanceName.Buffer; + logMsgArray[1] = HCARegTable[ind].RegName.Buffer; + logMsgArray[2] = strVal; + + NdisWriteEventLogEntry(g_p_drv_obj, eventLogMsgId, 0, cArrLen, &logMsgArray, 0, NULL); + +} + + + +NTSTATUS +DriverEntry( + IN PDRIVER_OBJECT p_drv_obj, + IN PUNICODE_STRING p_reg_path ); + +VOID +ipoib_unload( + IN PDRIVER_OBJECT p_drv_obj ); + +NDIS_STATUS +ipoib_initialize_ex( + IN NDIS_HANDLE h_adapter, + IN NDIS_HANDLE config_context, + IN PNDIS_MINIPORT_INIT_PARAMETERS MiniportInitParameters); + +NDIS_STATUS +MPInitializeTest( + IN NDIS_HANDLE MiniportAdapterHandle, + IN NDIS_HANDLE MiniportDriverContext, + IN PNDIS_MINIPORT_INIT_PARAMETERS MiniportInitParameters + ); + + + +BOOLEAN +ipoib_check_for_hang( + IN NDIS_HANDLE adapter_context ); + +void +ipoib_halt_ex( + IN NDIS_HANDLE adapter_context, + IN NDIS_HALT_ACTION HaltAction); + +NDIS_STATUS +ipoib_query_info( + IN NDIS_HANDLE adapter_context, + IN NDIS_OID oid, + IN PVOID info_buf, + IN ULONG info_buf_len, + OUT PULONG p_bytes_written, + OUT PULONG p_bytes_needed ); + + + +NDIS_STATUS +ipoib_reset( + IN NDIS_HANDLE adapter_context, + OUT PBOOLEAN p_addr_reset); + +NDIS_STATUS +ipoib_set_info( + IN NDIS_HANDLE adapter_context, + IN NDIS_OID oid, + IN PVOID info_buf, + IN ULONG info_buf_length, + OUT PULONG p_bytes_read, + OUT PULONG p_bytes_needed ); + +//NDIS60 +void +ipoib_send_net_buffer_list( + IN NDIS_HANDLE adapter_context, + IN PNET_BUFFER_LIST net_buffer_list, + IN NDIS_PORT_NUMBER port_num, + IN ULONG send_flags); + +void +ipoib_pnp_notify( + IN NDIS_HANDLE adapter_context, + IN PNET_DEVICE_PNP_EVENT pnp_event); + +VOID +ipoib_shutdown_ex( + IN NDIS_HANDLE adapter_context, + IN NDIS_SHUTDOWN_ACTION shutdown_action); + + +void +ipoib_cancel_xmit( + IN NDIS_HANDLE adapter_context, + IN PVOID cancel_id ); + + +static void +ipoib_complete_query( + IN ipoib_adapter_t* const p_adapter, + IN pending_oid_t* const p_oid_info, + IN const NDIS_STATUS status, + IN const void* const p_buf, + IN const ULONG buf_len ); + +static NDIS_STATUS +__ipoib_set_net_addr( + IN ipoib_adapter_t * p_adapter, + IN PVOID info_buf, + IN ULONG info_buf_len, + OUT PULONG p_bytes_read, + OUT PULONG p_bytes_needed ); + +static NDIS_STATUS +__ipoib_get_tcp_task_offload( + IN ipoib_adapter_t* p_adapter, + OUT pending_oid_t *pNdisRequest); + +static void +__ipoib_ats_reg_cb( + IN ib_reg_svc_rec_t *p_reg_svc_rec ); + +static void +__ipoib_ats_dereg_cb( + IN void *context ); + +static NTSTATUS +__ipoib_read_registry( + IN UNICODE_STRING* const p_registry_path ); + +static NDIS_STATUS +ipoib_set_options( + IN NDIS_HANDLE NdisMiniportDriverHandle, + IN NDIS_HANDLE MiniportDriverContext); + +static NDIS_STATUS +ipoib_oid_handler( + IN NDIS_HANDLE adapter_context, + IN PNDIS_OID_REQUEST pNdisRequest); + +static void +ipoib_cancel_oid_request( + IN NDIS_HANDLE adapter_context, + IN PVOID requestId); + +static NDIS_STATUS +ipoib_pause( + IN NDIS_HANDLE adapter_context, + IN PNDIS_MINIPORT_PAUSE_PARAMETERS pause_parameters); + +static NDIS_STATUS +ipoib_restart( + IN NDIS_HANDLE adapter_context, + IN PNDIS_MINIPORT_RESTART_PARAMETERS restart_parameters); + + + +//! Standard Windows Device Driver Entry Point +/*! DriverEntry is the first routine called after a driver is loaded, and +is responsible for initializing the driver. On W2k this occurs when the PnP +Manager matched a PnP ID to one in an INF file that references this driver. +Any not success return value will cause the driver to fail to load. +IRQL = PASSIVE_LEVEL + +@param p_drv_obj Pointer to Driver Object for this device driver +@param p_registry_path Pointer to unicode string containing path to this driver's registry area +@return STATUS_SUCCESS, NDIS_STATUS_BAD_CHARACTERISTICS, NDIS_STATUS_BAD_VERSION, +NDIS_STATUS_RESOURCES, or NDIS_STATUS_FAILURE +*/ +NTSTATUS +DriverEntry( + IN PDRIVER_OBJECT p_drv_obj, + IN PUNICODE_STRING p_registry_path ) +{ + NDIS_STATUS status; + NDIS_MINIPORT_DRIVER_CHARACTERISTICS characteristics; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + g_p_drv_obj = p_drv_obj; + +#ifdef _DEBUG_ + PAGED_CODE(); +#endif +#if defined(EVENT_TRACING) + WPP_INIT_TRACING(p_drv_obj, p_registry_path); +#endif + status = CL_INIT; + if( !NT_SUCCESS( status ) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_init failed.\n") ); + return status; + } + + __ipoib_read_registry(p_registry_path); + + KeInitializeSpinLock( &g_ipoib.lock ); + cl_qlist_init( &g_ipoib.adapter_list ); + + NdisZeroMemory(&characteristics, sizeof(characteristics)); + + characteristics.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_DRIVER_CHARACTERISTICS, + characteristics.Header.Size = sizeof(NDIS_MINIPORT_DRIVER_CHARACTERISTICS); + characteristics.Header.Revision = NDIS_MINIPORT_DRIVER_CHARACTERISTICS_REVISION_1; + + characteristics.MajorNdisVersion = MAJOR_NDIS_VERSION; + characteristics.MinorNdisVersion = MINOR_NDIS_VERSION; + characteristics.MajorDriverVersion = MAJOR_DRIVER_VERSION; + characteristics.MinorDriverVersion = MINOR_DRIVER_VERSION; + + + characteristics.CheckForHangHandlerEx = ipoib_check_for_hang; + characteristics.HaltHandlerEx = ipoib_halt_ex; + characteristics.InitializeHandlerEx = ipoib_initialize_ex;// MPInitializeTest + characteristics.OidRequestHandler = ipoib_oid_handler; + characteristics.CancelOidRequestHandler = ipoib_cancel_oid_request; + characteristics.ResetHandlerEx = ipoib_reset; + characteristics.DevicePnPEventNotifyHandler = ipoib_pnp_notify; + characteristics.ReturnNetBufferListsHandler = ipoib_return_net_buffer_list; + characteristics.SendNetBufferListsHandler = ipoib_send_net_buffer_list; + + characteristics.SetOptionsHandler = ipoib_set_options; + characteristics.PauseHandler = ipoib_pause; + characteristics.RestartHandler = ipoib_restart; + characteristics.UnloadHandler = ipoib_unload; + characteristics.CancelSendHandler = ipoib_cancel_xmit; + characteristics.ShutdownHandlerEx = ipoib_shutdown_ex; + + + +//TODO NDIS60 set g_ prefix to global variables + status = NdisMRegisterMiniportDriver( + p_drv_obj, p_registry_path,(PNDIS_HANDLE)&g_IpoibDriverContext, &characteristics,&g_IpoibMiniportDriverHandle ); + if( status != NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisMRegisterMiniportDriver failed with status of %d\n", status) ); + CL_DEINIT; + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + +static NDIS_STATUS +ipoib_set_options( + IN NDIS_HANDLE NdisMiniportDriverHandle, + IN NDIS_HANDLE MiniportDriverContext + ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + UNREFERENCED_PARAMETER(NdisMiniportDriverHandle); + UNREFERENCED_PARAMETER(MiniportDriverContext); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_SUCCESS; +} + +static NTSTATUS +__ipoib_read_registry( + IN UNICODE_STRING* const p_registry_path ) +{ + NTSTATUS status; + /* Remember the terminating entry in the table below. */ + RTL_QUERY_REGISTRY_TABLE table[4]; + UNICODE_STRING param_path; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + RtlInitUnicodeString( ¶m_path, NULL ); + param_path.MaximumLength = p_registry_path->Length + + sizeof(L"\\Parameters"); + param_path.Buffer = cl_zalloc( param_path.MaximumLength ); + if( !param_path.Buffer ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate parameters path buffer.\n") ); + return STATUS_INSUFFICIENT_RESOURCES; + } + + RtlAppendUnicodeStringToString( ¶m_path, p_registry_path ); + RtlAppendUnicodeToString( ¶m_path, L"\\Parameters" ); + + /* + * Clear the table. This clears all the query callback pointers, + * and sets up the terminating table entry. + */ + cl_memclr( table, sizeof(table) ); + + /* Setup the table entries. */ + table[0].Flags = RTL_QUERY_REGISTRY_DIRECT; + table[0].Name = L"DebugLevel"; + table[0].EntryContext = &g_ipoib_dbg_level; + table[0].DefaultType = REG_DWORD; + table[0].DefaultData = &g_ipoib_dbg_level; + table[0].DefaultLength = sizeof(ULONG); + + table[1].Flags = RTL_QUERY_REGISTRY_DIRECT; + table[1].Name = L"DebugFlags"; + table[1].EntryContext = &g_ipoib_dbg_flags; + table[1].DefaultType = REG_DWORD; + table[1].DefaultData = &g_ipoib_dbg_flags; + table[1].DefaultLength = sizeof(ULONG); + + table[2].Flags = RTL_QUERY_REGISTRY_DIRECT; + table[2].Name = L"bypass_check_bcast_rate"; + table[2].EntryContext = &g_ipoib.bypass_check_bcast_rate; + table[2].DefaultType = REG_DWORD; + table[2].DefaultData = &g_ipoib.bypass_check_bcast_rate; + table[2].DefaultLength = sizeof(ULONG); + + /* Have at it! */ + status = RtlQueryRegistryValues( RTL_REGISTRY_ABSOLUTE, + param_path.Buffer, table, NULL, NULL ); + + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("debug level %d debug flags 0x%.8x\n", + g_ipoib_dbg_level, + g_ipoib_dbg_flags)); + +#if DBG + if( g_ipoib_dbg_flags & IPOIB_DBG_ERR ) + g_ipoib_dbg_flags |= CL_DBG_ERROR; +#endif + + cl_free( param_path.Buffer ); + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +VOID +ipoib_unload( + IN PDRIVER_OBJECT p_drv_obj ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + UNREFERENCED_PARAMETER(p_drv_obj); + #if defined(EVENT_TRACING) + WPP_CLEANUP(p_drv_obj); + #endif + //NDIS6.0 + NdisMDeregisterMiniportDriver(g_IpoibMiniportDriverHandle); + UNREFERENCED_PARAMETER( p_drv_obj ); + CL_DEINIT; + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + + +NDIS_STATUS +ipoib_get_adapter_params( + IN NDIS_HANDLE* const wrapper_config_context, + IN OUT ipoib_adapter_t *p_adapter, + OUT PUCHAR *p_mac, + OUT UINT *p_len) +{ + NDIS_STATUS status; + NDIS_HANDLE h_config; + NDIS_CONFIGURATION_OBJECT config_obj; + NDIS_CONFIGURATION_PARAMETER *p_param; + UINT value; + PIPOIB_REG_ENTRY pRegEntry; + UINT i; + PUCHAR structPointer; + + int sq_depth_step = 128; + + UNUSED_PARAM(wrapper_config_context); + IPOIB_ENTER( IPOIB_DBG_INIT ); + + config_obj.Header.Type = NDIS_OBJECT_TYPE_CONFIGURATION_OBJECT; + config_obj.Header.Revision = NDIS_CONFIGURATION_OBJECT_REVISION_1; + config_obj.Header.Size = sizeof(NDIS_CONFIGURATION_OBJECT); + config_obj.NdisHandle = p_adapter->h_adapter; + config_obj.Flags = 0; + + status = NdisOpenConfigurationEx( &config_obj, &h_config); + if( status != NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisOpenConfigurationEx returned 0x%.8x\n", status) ); + return status; + } + + // read all the registry values + for (i = 0, pRegEntry = HCARegTable; i < IPOIB_NUM_REG_PARAMS; ++i) + { + // initialize pointer to appropriate place inside 'params' + structPointer = (PUCHAR) &p_adapter->params + pRegEntry[i].FieldOffset; + + // Get the configuration value for a specific parameter. Under NT the + // parameters are all read in as DWORDs. + NdisReadConfiguration( + &status, + &p_param, + h_config, + &pRegEntry[i].RegName, + NdisParameterInteger); + + // If the parameter was present, then check its value for validity. + if (status == NDIS_STATUS_SUCCESS) + { + // Check that param value is not too small or too large + if (p_param->ParameterData.IntegerData < pRegEntry[i].Min || + p_param->ParameterData.IntegerData > pRegEntry[i].Max) + { + value = pRegEntry[i].Default; + ipoib_create_log(p_adapter->h_adapter, i, EVENT_IPOIB_WRONG_PARAMETER_WRN); + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_INIT, ("Read configuration.Registry %S value is out of range, setting default value= 0x%x\n", pRegEntry[i].RegName.Buffer, value)); + + } + else + { + value = p_param->ParameterData.IntegerData; + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_INIT, ("Read configuration. Registry %S, Value= 0x%x\n", pRegEntry[i].RegName.Buffer, value)); + } + } + + else + { + value = pRegEntry[i].Default; + status = NDIS_STATUS_SUCCESS; + if (pRegEntry[i].bRequired) + { + ipoib_create_log(p_adapter->h_adapter, i, EVENT_IPOIB_WRONG_PARAMETER_ERR); + IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_INIT, ("Read configuration.Registry %S value not found, setting default value= 0x%x\n", pRegEntry[i].RegName.Buffer, value)); + } + else + { + ipoib_create_log(p_adapter->h_adapter, i, EVENT_IPOIB_WRONG_PARAMETER_INFO); + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_INIT, ("Read configuration. Registry %S value not found, Value= 0x%x\n", pRegEntry[i].RegName.Buffer, value)); + } + + } + // + // Store the value in the adapter structure. + // + switch(pRegEntry[i].FieldSize) + { + case 1: + *((PUCHAR) structPointer) = (UCHAR) value; + break; + + case 2: + *((PUSHORT) structPointer) = (USHORT) value; + break; + + case 4: + *((PULONG) structPointer) = (ULONG) value; + break; + + default: + IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, ("Bogus field size %d\n", pRegEntry[i].FieldSize)); + break; + } + } + + // Send queue depth needs to be a power of two + //static const INT sq_depth_step = 128; + + if (p_adapter->params.sq_depth % sq_depth_step) { + static const c_sq_ind = 2; + p_adapter->params.sq_depth = sq_depth_step *( + p_adapter->params.sq_depth / sq_depth_step + !!( (p_adapter->params.sq_depth % sq_depth_step) > (sq_depth_step/2) )); + ipoib_create_log(p_adapter->h_adapter, c_sq_ind, EVENT_IPOIB_WRONG_PARAMETER_WRN); + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_INIT, ("SQ DEPTH value was rounded to the closest acceptable value of 0x%x\n", p_adapter->params.sq_depth )); + + } + + + // Adjusting the low watermark parameter + p_adapter->params.rq_low_watermark = + p_adapter->params.rq_depth / p_adapter->params.rq_low_watermark; + + /* disable CM if LSO is active */ + if( p_adapter->params.cm_enabled ) + { + p_adapter->params.cm_enabled = !p_adapter->params.lso; + if( !p_adapter->params.cm_enabled ) + { + NdisWriteErrorLogEntry( p_adapter->h_adapter, + EVENT_IPOIB_CONNECTED_MODE_ERR, 1, 0xbadc0de0 ); + } + } + + if( p_adapter->params.cm_enabled ) + { + p_adapter->params.cm_xfer_block_size = + (sizeof(eth_hdr_t) + p_adapter->params.cm_payload_mtu); + } + + p_adapter->params.xfer_block_size = + (sizeof(eth_hdr_t) + p_adapter->params.payload_mtu); + + NdisReadNetworkAddress( &status, p_mac, p_len, h_config ); + + NdisCloseConfiguration( h_config ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_SUCCESS; +} + + +NDIS_STATUS +ipoib_get_adapter_guids( + IN NDIS_HANDLE* const h_adapter, + IN OUT ipoib_adapter_t *p_adapter ) +{ + NTSTATUS status; + ib_al_ifc_data_t data; + IO_STACK_LOCATION io_stack, *p_fwd_io_stack; + DEVICE_OBJECT *p_pdo; + IRP *p_irp; + KEVENT event; + IO_STATUS_BLOCK io_status; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + NdisMGetDeviceProperty( h_adapter, &p_pdo, NULL, NULL, NULL, NULL ); + + /* Query for our interface */ + data.size = sizeof(ipoib_ifc_data_t); + data.version = IPOIB_INTERFACE_DATA_VERSION; + data.type = &GUID_IPOIB_INTERFACE_DATA; + data.p_data = &p_adapter->guids; + + io_stack.MinorFunction = IRP_MN_QUERY_INTERFACE; + io_stack.Parameters.QueryInterface.Version = AL_INTERFACE_VERSION; + io_stack.Parameters.QueryInterface.Size = sizeof(ib_al_ifc_t); + io_stack.Parameters.QueryInterface.Interface = + (INTERFACE*)p_adapter->p_ifc; + io_stack.Parameters.QueryInterface.InterfaceSpecificData = &data; + io_stack.Parameters.QueryInterface.InterfaceType = + &GUID_IB_AL_INTERFACE; + + KeInitializeEvent( &event, NotificationEvent, FALSE ); + + /* Build the IRP for the HCA. */ + p_irp = IoBuildSynchronousFsdRequest( IRP_MJ_PNP, p_pdo, + NULL, 0, NULL, &event, &io_status ); + if( !p_irp ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate query interface IRP.\n") ); + return STATUS_INSUFFICIENT_RESOURCES; + } + + /* Copy the request query parameters. */ + p_fwd_io_stack = IoGetNextIrpStackLocation( p_irp ); + p_fwd_io_stack->MinorFunction = IRP_MN_QUERY_INTERFACE; + p_fwd_io_stack->Parameters.QueryInterface = + io_stack.Parameters.QueryInterface; + p_irp->IoStatus.Status = STATUS_NOT_SUPPORTED; + + /* Send the IRP. */ + status = IoCallDriver( p_pdo, p_irp ); + if( status == STATUS_PENDING ) + { + KeWaitForSingleObject( &event, Executive, KernelMode, + FALSE, NULL ); + status = io_status.Status; + } + + if( !NT_SUCCESS( status ) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Query interface for IPOIB interface returned %08x.\n", status) ); + return status; + } + + /* + * Dereference the interface now so that the bus driver doesn't fail a + * query remove IRP. We will always get unloaded before the bus driver + * since we're a child device. + */ + if (p_adapter->p_ifc) + p_adapter->p_ifc->wdm.InterfaceDereference( + p_adapter->p_ifc->wdm.Context ); + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_SUCCESS; +} + + +//! Initialization function called for each IOC discovered +/* The MiniportInitialize function is a required function that sets up a +NIC (or virtual NIC) for network I/O operations, claims all hardware +resources necessary to the NIC in the registry, and allocates resources +the driver needs to carry out network I/O operations. +IRQL = PASSIVE_LEVEL + +@param p_open_status Pointer to a status field set if this function returns NDIS_STATUS_OPEN_ERROR +@param p_selected_medium_index Pointer to unsigned integer noting index into medium_array for this NIC +@param medium_array Array of mediums for this NIC +@param medium_array_size Number of elements in medium_array +@param h_adapter Handle assigned by NDIS for this NIC +@param wrapper_config_context Handle used for Ndis initialization functions +@return NDIS_STATUS_SUCCESS, NDIS_STATUS_UNSUPPORTED_MEDIA, NDIS_STATUS_RESOURCES, +NDIS_STATUS_NOT_SUPPORTED +*/ + +/*void foo1(int i) +{ + char temp[5200]; + if (i ==0) return; + cl_msg_out("i = %d\n", i); + foo1(i-1); + +}*/ + +NDIS_STATUS +SetDeviceRegistrationAttributes( + ipoib_adapter_t *p_adapter, + NDIS_HANDLE h_adapter + ) +{ + NDIS_MINIPORT_ADD_DEVICE_REGISTRATION_ATTRIBUTES atr; + NTSTATUS Status; + + NdisZeroMemory(&atr, sizeof(NDIS_MINIPORT_ADD_DEVICE_REGISTRATION_ATTRIBUTES)); + + // + // setting registration attributes + // + atr.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADD_DEVICE_REGISTRATION_ATTRIBUTES; + atr.Header.Revision = NDIS_MINIPORT_ADD_DEVICE_REGISTRATION_ATTRIBUTES_REVISION_1; + atr.Header.Size = NDIS_SIZEOF_MINIPORT_ADD_DEVICE_REGISTRATION_ATTRIBUTES_REVISION_1; + + + atr.MiniportAddDeviceContext = (NDIS_HANDLE)p_adapter; + atr.Flags = 0; + + Status = NdisMSetMiniportAttributes(h_adapter, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&atr); + + return Status; +} + +//NDIS 6.1 +#if 0 +NDIS_STATUS +SetHardwareAssistAttributes( + ipoib_adapter_t *p_adapter, + NDIS_HANDLE h_adapter + ) +{ + NDIS_MINIPORT_ADAPTER_HARDWARE_ASSIST_ATTRIBUTES atr; + NTSTATUS Status; + + NdisZeroMemory(&atr, sizeof(NDIS_MINIPORT_ADAPTER_HARDWARE_ASSIST_ATTRIBUTES)); + + // + // setting registration attributes + // + atr.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_HARDWARE_ASSIST_ATTRIBUTES; + atr.Header.Revision = NDIS_MINIPORT_ADAPTER_HARDWARE_ASSIST_ATTRIBUTES_REVISION_1; + atr.Header.Size = NDIS_SIZEOF_MINIPORT_ADAPTER_HARDWARE_ASSIST_ATTRIBUTES_REVISION_1; + + NDIS_HD_SPLIT_ATTRIBUTES nhsa; + NdisZeroMemory(&nhsa, sizeof(nhsa)); + + nhsa.Header.Type = NDIS_OBJECT_TYPE_HD_SPLIT_ATTRIBUTES; + nhsa.Header.Revision = NDIS_OFFLOAD_REVISION_1; + nhsa.Header.Size = NDIS_SIZEOF_HD_SPLIT_ATTRIBUTES_REVISION_1; + + // BUGBUG: We are just cheating here ... + nhsa.HardwareCapabilities = NDIS_HD_SPLIT_CAPS_SUPPORTS_HEADER_DATA_SPLIT; +#if 0 + ... Only supported on B0 + + NDIS_HD_SPLIT_CAPS_SUPPORTS_IPV4_OPTIONS | + NDIS_HD_SPLIT_CAPS_SUPPORTS_IPV6_EXTENSION_HEADERS | + NDIS_HD_SPLIT_CAPS_SUPPORTS_TCP_OPTIONS; +#endif + + // The bellow should be left zero + if (pPort->Config.HeaderDataSplit) { + nhsa.CurrentCapabilities = NDIS_HD_SPLIT_CAPS_SUPPORTS_HEADER_DATA_SPLIT; + } else { + nhsa.CurrentCapabilities = 0; + } + + nhsa.HDSplitFlags = 0; + nhsa.BackfillSize = 0; + nhsa.MaxHeaderSize = 0; + + atr.HDSplitAttributes = &nhsa; + + Status = NdisMSetMiniportAttributes(h_adapter, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&atr); + + if (nhsa.HDSplitFlags & NDIS_HD_SPLIT_ENABLE_HEADER_DATA_SPLIT) { + ASSERT(pPort->Config.HeaderDataSplit == TRUE); + pPort->Config.HeaderDataSplit = TRUE; + } + else { + ASSERT(pPort->Config.HeaderDataSplit == FALSE); + pPort->Config.HeaderDataSplit = FALSE; + } + + return Status; +} +#endif + +/*++ +Routine Description: + the routine sets attributes that are associated with a miniport adapter. + +Arguments: + pPort - Pointer to port object + +Return Value: + NDIS_STATUS + +Note: + Should be called in PASSIVE_LEVEL + +--*/ +NDIS_STATUS +SetAdapterRegistrationAttributes( + ipoib_adapter_t *p_adapter, + NDIS_HANDLE h_adapter + ) + { + NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES atr; + NTSTATUS Status; + + NdisZeroMemory(&atr, sizeof(NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES)); + + /* setting registration attributes */ + + atr.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES; + atr.Header.Revision = NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1; + atr.Header.Size = NDIS_SIZEOF_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1; + //TODO NDIS60 Port or adapter + atr.MiniportAdapterContext = (NDIS_HANDLE)p_adapter; //(NDIS_HANDLE)pPort->p_adapter; + atr.AttributeFlags = NDIS_MINIPORT_ATTRIBUTES_BUS_MASTER; + atr.CheckForHangTimeInSeconds = 10; + atr.InterfaceType = NdisInterfacePci ; // ???? UH + //TODO NDIS60 PNP or PCI ? + //RegistrationAttributes.InterfaceType = NdisInterfacePNPBus; + + Status = NdisMSetMiniportAttributes(h_adapter, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&atr); + + return Status; +} + + +/*++ +Routine Description: + the routine sets generic attributes that are associated with a miniport + adapter. + +Arguments: + pPort - Pointer to port object + +Return Value: + NDIS_STATUS + +Note: + Should be called in PASSIVE_LEVEL + +--*/ +NDIS_STATUS +SetGenericAttributes( + ipoib_adapter_t *p_adapter, + NDIS_HANDLE h_adapter + ) +{ + NDIS_STATUS Status; + + NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES gat; + NdisZeroMemory(&gat, sizeof(NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES)); + + /* set up generic attributes */ + + gat.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES; + gat.Header.Revision = NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES_REVISION_1; + gat.Header.Size = sizeof(NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES); + + gat.MediaType = NdisMedium802_3; + gat.MaxXmitLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + gat.MaxRcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + gat.XmitLinkSpeed = IPOIB_MEDIA_MAX_SPEED; //TODO NDIS60 NDIS_LINK_SPEED_UNKNOWN + gat.RcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; // TODO NDIS60 NDIS_LINK_SPEED_UNKNOWN ??? + + gat.MediaConnectState = MediaConnectStateConnected; //TODO NDIS60 Check the current state + gat.MediaDuplexState = MediaDuplexStateFull; + + gat.MtuSize = MAX_IB_MTU; + gat.LookaheadSize = MAX_XFER_BLOCK_SIZE; + gat.MacOptions = NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA | + NDIS_MAC_OPTION_TRANSFERS_NOT_PEND | + NDIS_MAC_OPTION_NO_LOOPBACK | + NDIS_MAC_OPTION_FULL_DUPLEX; + //NDIS_MAC_OPTION_8021P_PRIORITY; //TODO NDIS60 + // DT: Enable for Header Data Split WHQL + // | NDIS_MAC_OPTION_8021Q_VLAN; + + gat.SupportedPacketFilters = NDIS_PACKET_TYPE_DIRECTED | + NDIS_PACKET_TYPE_MULTICAST | + //NDIS_PACKET_TYPE_ALL_MULTICAST | + NDIS_PACKET_TYPE_BROADCAST; + + gat.MaxMulticastListSize = MAX_MCAST; + + gat.MacAddressLength = HW_ADDR_LEN; + + NdisMoveMemory(gat.PermanentMacAddress, + p_adapter->mac.addr, + HW_ADDR_LEN); + + NdisMoveMemory(gat.CurrentMacAddress, + p_adapter->params.conf_mac.addr, + HW_ADDR_LEN); + + + gat.PhysicalMediumType = NdisPhysicalMedium802_3; + gat.AccessType = NET_IF_ACCESS_BROADCAST; + + gat.SupportedOidList = (PNDIS_OID)SUPPORTED_OIDS; + gat.SupportedOidListLength = sizeof(SUPPORTED_OIDS); + + + gat.DirectionType = NET_IF_DIRECTION_SENDRECEIVE; + gat.ConnectionType = NET_IF_CONNECTION_DEDICATED; + gat.IfType = IF_TYPE_ETHERNET_CSMACD; + gat.IfConnectorPresent = TRUE; + //TODO NDIS60 This value is absent for ETH driver + gat.AccessType = NET_IF_ACCESS_BROADCAST; // NET_IF_ACCESS_BROADCAST for a typical ethernet adapter + + + //TODO NDIS60 is it possible to reduce unsupported statistics + gat.SupportedStatistics = + NDIS_STATISTICS_XMIT_OK_SUPPORTED | + NDIS_STATISTICS_RCV_OK_SUPPORTED | + NDIS_STATISTICS_XMIT_ERROR_SUPPORTED | + NDIS_STATISTICS_RCV_ERROR_SUPPORTED | + NDIS_STATISTICS_RCV_CRC_ERROR_SUPPORTED | + NDIS_STATISTICS_RCV_NO_BUFFER_SUPPORTED | + NDIS_STATISTICS_TRANSMIT_QUEUE_LENGTH_SUPPORTED; + + //SupportedStatistics = NDIS_STATISTICS_XMIT_OK_SUPPORTED | + // NDIS_STATISTICS_GEN_STATISTICS_SUPPORTED; + + + // + // Set power management capabilities + // + gat.PowerManagementCapabilities = NULL; +#if 0 + NDIS_PNP_CAPABILITIES PowerManagementCapabilities; + NdisZeroMemory(&PowerManagementCapabilities, sizeof(NDIS_PNP_CAPABILITIES)); + if (MPIsPoMgmtSupported(pPort)) + { + MPFillPoMgmtCaps(pPort, &PowerManagementCapabilities, &Status, &unUsed); + ASSERT(NT_SUCCESS(Status)); + gat.PowerManagementCapabilities = &PowerManagementCapabilities; + } + else + { + + } +#endif + + // + // Set RSS attributes + // + gat.RecvScaleCapabilities = NULL; +#if 0 + NDIS_RECEIVE_SCALE_CAPABILITIES RssCapabilities; + NdisZeroMemory(&RssCapabilities, sizeof(PNDIS_RECEIVE_SCALE_CAPABILITIES)); + Status = MPFillRssCapabilities(pPort, &RssCapabilities, &unUsed); + if (NT_SUCCESS(Status)) + { + gat.RecvScaleCapabilities = &RssCapabilities; + } + else + { + // + // do not fail the call because of failure to get PM caps + // + Status = NDIS_STATUS_SUCCESS; + gat.RecvScaleCapabilities = NULL; + } +#endif + + Status = NdisMSetMiniportAttributes(h_adapter, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&gat); + + return Status; +} + + +/*++ +Routine Description: + The routine sets an NDIS_OFFLOAD structure indicates the current offload + capabilities that are provided by the miniport adapter + +Arguments: + pPort - a pointer to port object + offload - reference to NDIS_OFFLOAD object that should be filled + +Return Value: + None. + +--*/ +static +void +OffloadConfig( + ipoib_adapter_t *p_adapter, + NDIS_OFFLOAD *p_offload + ) +{ + + ULONG ulEncapsulation = NDIS_ENCAPSULATION_IEEE_802_3 | NDIS_ENCAPSULATION_IEEE_802_3_P_AND_Q; + + NdisZeroMemory(p_offload, NDIS_SIZEOF_NDIS_OFFLOAD_REVISION_1); + + p_offload->Header.Type = NDIS_OBJECT_TYPE_OFFLOAD; + p_offload->Header.Revision = NDIS_OFFLOAD_REVISION_1; // BUGBUG: do we need to support revision 2? UH 17-May-2008 + p_offload->Header.Size = NDIS_SIZEOF_NDIS_OFFLOAD_REVISION_1; + + p_offload->Checksum.IPv4Transmit.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv4Transmit.IpOptionsSupported = + p_offload->Checksum.IPv4Transmit.TcpOptionsSupported = + p_offload->Checksum.IPv4Transmit.TcpChecksum = + p_offload->Checksum.IPv4Transmit.UdpChecksum = + p_offload->Checksum.IPv4Transmit.IpChecksum =!!(p_adapter->params.send_chksum_offload); + + p_offload->Checksum.IPv4Receive.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv4Receive.IpOptionsSupported = + p_offload->Checksum.IPv4Receive.TcpOptionsSupported = + p_offload->Checksum.IPv4Receive.TcpChecksum = + p_offload->Checksum.IPv4Receive.UdpChecksum = + p_offload->Checksum.IPv4Receive.IpChecksum = !!(p_adapter->params.recv_chksum_offload); //TODO NDIS60 + + + p_offload->Checksum.IPv6Transmit.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv6Transmit.IpExtensionHeadersSupported = + p_offload->Checksum.IPv6Transmit.TcpOptionsSupported = + p_offload->Checksum.IPv6Transmit.TcpChecksum = + p_offload->Checksum.IPv6Transmit.UdpChecksum = FALSE; + + + p_offload->Checksum.IPv6Receive.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv6Receive.IpExtensionHeadersSupported = + p_offload->Checksum.IPv6Receive.TcpOptionsSupported = + p_offload->Checksum.IPv6Receive.TcpChecksum = + p_offload->Checksum.IPv6Receive.UdpChecksum = FALSE; + + if (p_adapter->params.lso) + { + p_offload->LsoV1.IPv4.Encapsulation = ulEncapsulation; + p_offload->LsoV1.IPv4.MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; +#define LSO_MIN_SEG_COUNT 2 + p_offload->LsoV1.IPv4.MinSegmentCount = LSO_MIN_SEG_COUNT; + + + p_offload->LsoV1.IPv4.TcpOptions = NDIS_OFFLOAD_SUPPORTED; + p_offload->LsoV1.IPv4.IpOptions = NDIS_OFFLOAD_SUPPORTED; + + p_offload->LsoV2.IPv4.Encapsulation = ulEncapsulation; + p_offload->LsoV2.IPv4.MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; + p_offload->LsoV2.IPv4.MinSegmentCount = LSO_MIN_SEG_COUNT; + + p_offload->LsoV2.IPv6.Encapsulation = ulEncapsulation; + p_offload->LsoV2.IPv6.MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; + p_offload->LsoV2.IPv6.MinSegmentCount = LSO_MIN_SEG_COUNT; + + p_offload->LsoV2.IPv6.IpExtensionHeadersSupported = NDIS_OFFLOAD_NOT_SUPPORTED; + p_offload->LsoV2.IPv6.TcpOptionsSupported = NDIS_OFFLOAD_SUPPORTED; + } + +} + + +/*++ +Routine Description: + The routine sets an NDIS_OFFLOAD structure that indicates all the task + offload capabilites that are supported by the NIC. These capabilities include + capabilities that are currently disabled by standardized keywords in the registry. + +Arguments: + offload - reference to NDIS_OFFLOAD object that should be filled + +Return Value: + None. + +--*/ +static +void +OffloadCapabilities( + NDIS_OFFLOAD *p_offload + ) +{ + ULONG ulEncapsulation = NDIS_ENCAPSULATION_IEEE_802_3 | NDIS_ENCAPSULATION_IEEE_802_3_P_AND_Q ; + NdisZeroMemory(p_offload, NDIS_SIZEOF_NDIS_OFFLOAD_REVISION_1); + + p_offload->Header.Type = NDIS_OBJECT_TYPE_OFFLOAD; + p_offload->Header.Revision = NDIS_OFFLOAD_REVISION_1; // BUGBUG: do we need to support revision 2? UH 17-May-2008 + p_offload->Header.Size = NDIS_SIZEOF_NDIS_OFFLOAD_REVISION_1; + + p_offload->Checksum.IPv4Transmit.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv4Transmit.IpOptionsSupported = TRUE; + p_offload->Checksum.IPv4Transmit.TcpOptionsSupported = TRUE; + p_offload->Checksum.IPv4Transmit.TcpChecksum = TRUE; + p_offload->Checksum.IPv4Transmit.UdpChecksum = TRUE; + p_offload->Checksum.IPv4Transmit.IpChecksum = TRUE; + + p_offload->Checksum.IPv4Receive.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv4Receive.IpOptionsSupported = TRUE; + p_offload->Checksum.IPv4Receive.TcpOptionsSupported = TRUE; + p_offload->Checksum.IPv4Receive.TcpChecksum = TRUE; + p_offload->Checksum.IPv4Receive.UdpChecksum = TRUE; + p_offload->Checksum.IPv4Receive.IpChecksum = TRUE; + + + // + // BUGBUG:: + // During a HW bug that didn't handle correctly packets with + // IPv6 Extension Headers -> we set IpExtensionHeadersSupported to TRUE + // + p_offload->Checksum.IPv6Transmit.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv6Transmit.IpExtensionHeadersSupported = TRUE; + p_offload->Checksum.IPv6Transmit.TcpOptionsSupported = TRUE; + p_offload->Checksum.IPv6Transmit.TcpChecksum = TRUE; + p_offload->Checksum.IPv6Transmit.UdpChecksum = TRUE; + + + p_offload->Checksum.IPv6Receive.Encapsulation = ulEncapsulation; + p_offload->Checksum.IPv6Receive.IpExtensionHeadersSupported = TRUE; + p_offload->Checksum.IPv6Receive.TcpOptionsSupported = TRUE; + p_offload->Checksum.IPv6Receive.TcpChecksum = TRUE; + p_offload->Checksum.IPv6Receive.UdpChecksum = TRUE; + + p_offload->LsoV1.IPv4.Encapsulation = ulEncapsulation; + p_offload->LsoV1.IPv4.MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; + p_offload->LsoV1.IPv4.MinSegmentCount = 2; + p_offload->LsoV1.IPv4.TcpOptions = NDIS_OFFLOAD_SUPPORTED; + p_offload->LsoV1.IPv4.IpOptions = NDIS_OFFLOAD_SUPPORTED; + + p_offload->LsoV2.IPv4.Encapsulation = ulEncapsulation; + p_offload->LsoV2.IPv4.MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; + p_offload->LsoV2.IPv4.MinSegmentCount = 2; + + p_offload->LsoV2.IPv6.Encapsulation = ulEncapsulation; + p_offload->LsoV2.IPv6.MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; + p_offload->LsoV2.IPv6.MinSegmentCount = 2; + + p_offload->LsoV2.IPv6.IpExtensionHeadersSupported = NDIS_OFFLOAD_NOT_SUPPORTED; + p_offload->LsoV2.IPv6.TcpOptionsSupported = NDIS_OFFLOAD_SUPPORTED; + + } + + +/*++ +Routine Description: + The routine sets offload attributes that are associated with a miniport + adapter. + +Arguments: + pPort - Pointer to port object + +Return Value: + NDIS_STATUS + +Note: + Should be called in PASSIVE_LEVEL + +--*/ +NDIS_STATUS +SetOffloadAttributes( + ipoib_adapter_t *p_adapter, + NDIS_HANDLE h_adapter + ) +{ + NDIS_STATUS Status; + NDIS_OFFLOAD offload,hwOffload; + //ULONG ulEncapsulation = NDIS_ENCAPSULATION_IEEE_802_3 | NDIS_ENCAPSULATION_IEEE_802_3_P_AND_Q; + + NDIS_MINIPORT_ADAPTER_OFFLOAD_ATTRIBUTES oat; + NdisZeroMemory(&oat, sizeof(NDIS_MINIPORT_ADAPTER_OFFLOAD_ATTRIBUTES)); + + oat.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_OFFLOAD_ATTRIBUTES; + oat.Header.Revision = NDIS_MINIPORT_ADAPTER_OFFLOAD_ATTRIBUTES_REVISION_1; + oat.Header.Size = NDIS_SIZEOF_MINIPORT_ADAPTER_OFFLOAD_ATTRIBUTES_REVISION_1; + + + OffloadConfig(p_adapter, &offload); + + + OffloadCapabilities(&hwOffload); + + oat.DefaultOffloadConfiguration = &offload; + oat.HardwareOffloadCapabilities = &hwOffload; + + Status = NdisMSetMiniportAttributes(h_adapter, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&oat); + + return Status; +} + + +/*++ + +Routine Description: + An NDIS 6.0 miniport driver must call NdisMSetMiniportAttributes + at least twice. The first call is to register itself with NDIS. + The second call is to register the miniport driver's general + attributes with NDIS. + + NdisMSetMiniportAttributes takes a parameter of type + NDIS_MINIPORT_ADAPTER_ATTRIBUTES, which is a union of several miniport + adapter attributes. Miniport drivers must first call + NdisMSetMiniportAttributes and pass in an + NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES structure + that contains the pointer to its own context area, attribute flags, + check-for-hang time, and interface type. + + All NDIS 6.0 miniport drivers are deserialized by default. + +Arguments: + pPort - Pointer to port object + +Return Value: + NDIS_STATUS + +Note: + Should be called in PASSIVE_LEVEL + +--*/ + NDIS_STATUS + SetAttributes( + ipoib_adapter_t *p_adapter, + NDIS_HANDLE h_adapter + ) + { + NTSTATUS Status; + + + Status = SetDeviceRegistrationAttributes(p_adapter, h_adapter); + if (Status != NDIS_STATUS_SUCCESS) + { + //ETH_PRINT(TRACE_LEVEL_ERROR, ETH_INIT, "Set device registration failed Error=0x%x\n", Status); + return Status; + } + + + Status = SetAdapterRegistrationAttributes(p_adapter, h_adapter); + if (Status != NDIS_STATUS_SUCCESS) + { + //ETH_PRINT(TRACE_LEVEL_ERROR, ETH_INIT, "Set adapter attributes failed Error=0x%x\n", Status); + return Status; + } + + Status = SetOffloadAttributes(p_adapter, h_adapter); + if (Status != NDIS_STATUS_SUCCESS) + { + //ETH_PRINT(TRACE_LEVEL_ERROR, ETH_INIT, "Set OFFLOAD attributes failed Error=0x%x\n", Status); + return Status; + } + +#if 0 + if(!pPort->Config.fWHQL) + { + Status = SetHardwareAssistAttributes(pPort); + if (Status != NDIS_STATUS_SUCCESS) + { + //ETH_PRINT(TRACE_LEVEL_ERROR, ETH_INIT, "Set Hardware Assist Attributes failed Error=0x%x\n", Status); + return Status; + } + } +#endif + + Status = SetGenericAttributes(p_adapter, h_adapter); + if (Status != NDIS_STATUS_SUCCESS) + { + //ETH_PRINT(TRACE_LEVEL_ERROR, ETH_INIT, "Set generic attributes failed Error=0x%x\n", Status); + return Status; + } + + return Status; +} + + + +NDIS_STATUS +InitNdisScatterGatherDma( + ipoib_adapter_t *p_adapter, + NDIS_HANDLE h_adapter + ) +{ + NDIS_STATUS status; + NDIS_SG_DMA_DESCRIPTION DmaDescription; + + NdisZeroMemory(&DmaDescription, sizeof(DmaDescription)); + + DmaDescription.Header.Type = NDIS_OBJECT_TYPE_SG_DMA_DESCRIPTION; + DmaDescription.Header.Revision = NDIS_SG_DMA_DESCRIPTION_REVISION_1; + DmaDescription.Header.Size = sizeof(NDIS_SG_DMA_DESCRIPTION); + DmaDescription.Flags = NDIS_SG_DMA_64_BIT_ADDRESS; + // + // Even if offload is enabled, the packet size for mapping shouldn't change + // + DmaDescription.MaximumPhysicalMapping = LARGE_SEND_OFFLOAD_SIZE + LSO_MAX_HEADER; + + DmaDescription.ProcessSGListHandler = ipoib_process_sg_list; + DmaDescription.SharedMemAllocateCompleteHandler = NULL; + + DmaDescription.Header.Type = NDIS_OBJECT_TYPE_SG_DMA_DESCRIPTION; + DmaDescription.Header.Revision = NDIS_SG_DMA_DESCRIPTION_REVISION_1; + DmaDescription.Header.Size = sizeof(NDIS_SG_DMA_DESCRIPTION);//NDIS_SIZEOF_SG_DMA_DESCRIPTION_REVISION_1; + + DmaDescription.Flags = NDIS_SG_DMA_64_BIT_ADDRESS; + //DmaDescription.MaximumPhysicalMapping = pPort->p_adapter->params.xfer_block_size; + + DmaDescription.ProcessSGListHandler = ipoib_process_sg_list; + DmaDescription.SharedMemAllocateCompleteHandler = NULL; + + status = NdisMRegisterScatterGatherDma( + h_adapter, + &DmaDescription, + &p_adapter->NdisMiniportDmaHandle); + + if( status != NDIS_STATUS_SUCCESS ) + { + //TODO NDIS60 + //ipoib_destroy_adapter( p_adapter ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisMRegisterScatterGatherDma returned 0x%.8x.\n", status) ); + + } + //NDIS sets this value before it returns from NdisMRegisterScatterGatherDma. + //Miniport drivers should use this size to preallocate memory for each scatter/gather list. + p_adapter->sg_list_size = DmaDescription.ScatterGatherListSize ; + + return status; +} + + +NDIS_STATUS +MPInitializeTest( + IN NDIS_HANDLE MiniportAdapterHandle, + IN NDIS_HANDLE MiniportDriverContext, + IN PNDIS_MINIPORT_INIT_PARAMETERS MiniportInitParameters + ) +/*++ +Routine Description: + + MiniportInitialize handler + +Arguments: + + MiniportAdapterHandle The handle NDIS uses to refer to us + MiniportDriverContext Handle passed to NDIS when we registered the driver + MiniportInitParameters Initialization parameters + +Return Value: + + NDIS_STATUS_SUCCESS unless something goes wrong + +--*/ +{ + + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + //PMP_PORT pPort = NULL; + ipoib_adapter_t *p_adapter; +// NDIS_MINIPORT_INTERRUPT_CHARACTERISTICS Interrupt; + NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES RegistrationAttributes; + NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES GeneralAttributes; + //NDIS_TIMER_CHARACTERISTICS Timer; + NDIS_PNP_CAPABILITIES PowerManagementCapabilities; + //PMP_ADAPTER Adapter = NULL; + //PVOID NetworkAddress; +// UINT index; +// UINT uiPnpCommandValue; +// ULONG ulInfoLen; + //ULONG InterruptVersion; +// LARGE_INTEGER liDueTime; + //BOOLEAN isTimerAlreadyInQueue = FALSE; +// uint8_t portId; + ib_api_status_t ib_status; +#if 0 +#if DBG + LARGE_INTEGER TS, TD, TE; +#endif +#endif + + cl_dbg_out ("====> MPInitialize\n"); + + UNREFERENCED_PARAMETER(MiniportDriverContext); + UNREFERENCED_PARAMETER(MiniportInitParameters); + + + + + + { + + ib_status = ipoib_create_adapter(MiniportDriverContext, MiniportAdapterHandle, &p_adapter ); + if( ib_status != IB_SUCCESS ) + { + ASSERT(FALSE); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_create_adapter returned status %d.\n", ib_status ) ); + return NDIS_STATUS_FAILURE; + } + + + + NdisZeroMemory(&RegistrationAttributes, sizeof(NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES)); + NdisZeroMemory(&GeneralAttributes, sizeof(NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES)); + + // + // setting registration attributes + // + RegistrationAttributes.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES; + RegistrationAttributes.Header.Revision = NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1; + RegistrationAttributes.Header.Size = sizeof(NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES); + + RegistrationAttributes.MiniportAdapterContext = (NDIS_HANDLE)p_adapter; + RegistrationAttributes.AttributeFlags = NDIS_MINIPORT_ATTRIBUTES_HARDWARE_DEVICE | + NDIS_MINIPORT_ATTRIBUTES_BUS_MASTER; + + RegistrationAttributes.CheckForHangTimeInSeconds = 2; + RegistrationAttributes.InterfaceType = NdisInterfacePci; + + Status = NdisMSetMiniportAttributes(MiniportAdapterHandle, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&RegistrationAttributes); + + if (Status != NDIS_STATUS_SUCCESS) + { + //break; + return Status; + } + +#if 0 + // + // Read the registry parameters + // + Status = NICReadRegParameters(Adapter); + + if (Status != NDIS_STATUS_SUCCESS) + { + break; + } + + + // + // Find the physical adapter + // + Status = MpFindAdapter(Adapter, MiniportInitParameters->AllocatedResources); + if (Status != NDIS_STATUS_SUCCESS) + { + break; + } + + // + // Map bus-relative IO range to system IO space + // + Status = NdisMRegisterIoPortRange( + (PVOID *)&Adapter->PortOffset, + Adapter->AdapterHandle, + Adapter->IoBaseAddress, + Adapter->IoRange); + if (Status != NDIS_STATUS_SUCCESS) + { + DBGPRINT(MP_ERROR, ("NdisMRegisterioPortRange failed\n")); + + NdisWriteErrorLogEntry( + Adapter->AdapterHandle, + NDIS_ERROR_CODE_BAD_IO_BASE_ADDRESS, + 0); + + break; + } + + // + // Read additional info from NIC such as MAC address + // + Status = NICReadAdapterInfo(Adapter); + if (Status != NDIS_STATUS_SUCCESS) + { + break; + } + +#endif + // + // set up generic attributes + // + + + GeneralAttributes.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES; + GeneralAttributes.Header.Revision = NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES_REVISION_1; + GeneralAttributes.Header.Size = sizeof(NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES); + + GeneralAttributes.MediaType = NdisMedium802_3; + + GeneralAttributes.MtuSize = DEFAULT_MTU; +#define LINE_SPEED_10_GBTS 10000000000 + GeneralAttributes.MaxXmitLinkSpeed = LINE_SPEED_10_GBTS; + GeneralAttributes.MaxRcvLinkSpeed = LINE_SPEED_10_GBTS; + GeneralAttributes.XmitLinkSpeed = NDIS_LINK_SPEED_UNKNOWN; + GeneralAttributes.RcvLinkSpeed = NDIS_LINK_SPEED_UNKNOWN; + GeneralAttributes.MediaConnectState = MediaConnectStateUnknown; + GeneralAttributes.MediaDuplexState = MediaDuplexStateUnknown; + GeneralAttributes.LookaheadSize = MAX_XFER_BLOCK_SIZE; +#if 0 + MPFillPoMgmtCaps (Adapter, + &PowerManagementCapabilities, + &Status, + &ulInfoLen); +#endif + NdisZeroMemory(&PowerManagementCapabilities, sizeof(NDIS_PNP_CAPABILITIES)); + Status = NDIS_STATUS_NOT_SUPPORTED; + // ulInfoLen = 0; + + if (Status == NDIS_STATUS_SUCCESS) + { + GeneralAttributes.PowerManagementCapabilities = &PowerManagementCapabilities; + } + else + { + GeneralAttributes.PowerManagementCapabilities = NULL; + } + + // + // do not fail the call because of failure to get PM caps + // + Status = NDIS_STATUS_SUCCESS; + + GeneralAttributes.MacOptions = NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA | + NDIS_MAC_OPTION_TRANSFERS_NOT_PEND | + NDIS_MAC_OPTION_NO_LOOPBACK; + + GeneralAttributes.SupportedPacketFilters = NDIS_PACKET_TYPE_DIRECTED | + NDIS_PACKET_TYPE_MULTICAST | + NDIS_PACKET_TYPE_ALL_MULTICAST | + NDIS_PACKET_TYPE_BROADCAST; + + GeneralAttributes.MaxMulticastListSize = MAX_MCAST; + GeneralAttributes.MacAddressLength = HW_ADDR_LEN; + NdisMoveMemory(GeneralAttributes.PermanentMacAddress, + p_adapter->mac.addr, + HW_ADDR_LEN); + + NdisMoveMemory(GeneralAttributes.CurrentMacAddress, + p_adapter->params.conf_mac.addr, + HW_ADDR_LEN); + GeneralAttributes.RecvScaleCapabilities = NULL; + GeneralAttributes.AccessType = NET_IF_ACCESS_BROADCAST; // NET_IF_ACCESS_BROADCAST for a typical ethernet adapter + GeneralAttributes.DirectionType = NET_IF_DIRECTION_SENDRECEIVE; // NET_IF_DIRECTION_SENDRECEIVE for a typical ethernet adapter + GeneralAttributes.ConnectionType = NET_IF_CONNECTION_DEDICATED; // NET_IF_CONNECTION_DEDICATED for a typical ethernet adapter + GeneralAttributes.IfType = IF_TYPE_ETHERNET_CSMACD; // IF_TYPE_ETHERNET_CSMACD for a typical ethernet adapter (regardless of speed) + GeneralAttributes.IfConnectorPresent = TRUE; // RFC 2665 TRUE if physical adapter + + GeneralAttributes.SupportedStatistics = NDIS_STATISTICS_XMIT_OK_SUPPORTED | + NDIS_STATISTICS_RCV_OK_SUPPORTED | + NDIS_STATISTICS_XMIT_ERROR_SUPPORTED | + NDIS_STATISTICS_RCV_ERROR_SUPPORTED | + NDIS_STATISTICS_RCV_CRC_ERROR_SUPPORTED | + NDIS_STATISTICS_RCV_NO_BUFFER_SUPPORTED | + NDIS_STATISTICS_TRANSMIT_QUEUE_LENGTH_SUPPORTED | + NDIS_STATISTICS_GEN_STATISTICS_SUPPORTED; + + GeneralAttributes.SupportedOidList = NICSupportedOidsTest; + GeneralAttributes.SupportedOidListLength = sizeof(NICSupportedOidsTest); + + Status = NdisMSetMiniportAttributes(MiniportAdapterHandle, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&GeneralAttributes); + + +#if 0 + // + // Allocate all other memory blocks including shared memory + // + Status = NICAllocAdapterMemory(Adapter); + if (Status != NDIS_STATUS_SUCCESS) + { + break; + } + // + // Init send data structures + // + NICInitSend(Adapter); + + // + // Init receive data structures + // + Status = NICInitRecv(Adapter); + if (Status != NDIS_STATUS_SUCCESS) + { + break; + } + // + // Map bus-relative registers to virtual system-space + // + Status = NdisMMapIoSpace( + (PVOID *) &(Adapter->CSRAddress), + Adapter->AdapterHandle, + Adapter->MemPhysAddress, + NIC_MAP_IOSPACE_LENGTH); + if (Status != NDIS_STATUS_SUCCESS) + { + DBGPRINT(MP_ERROR, ("NdisMMapIoSpace failed\n")); + + NdisWriteErrorLogEntry( + Adapter->AdapterHandle, + NDIS_ERROR_CODE_RESOURCE_CONFLICT, + 1, + ERRLOG_MAP_IO_SPACE); + + break; + } + + DBGPRINT(MP_INFO, ("CSRAddress="PTR_FORMAT"\n", Adapter->CSRAddress)); + + // + // Disable interrupts here which is as soon as possible + // + NICDisableInterrupt(Adapter); +#endif + + // + // Register the interrupt + // + // + + // + // the embeded NDIS interrupt structure is already zero'ed out + // as part of the adapter structure + // + #if 0 + NdisZeroMemory(&Interrupt, sizeof(NDIS_MINIPORT_INTERRUPT_CHARACTERISTICS)); + + Interrupt.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_INTERRUPT; + Interrupt.Header.Revision = NDIS_MINIPORT_INTERRUPT_REVISION_1; + Interrupt.Header.Size = sizeof(NDIS_MINIPORT_INTERRUPT_CHARACTERISTICS); + + Interrupt.InterruptHandler = MPIsr; + Interrupt.InterruptDpcHandler = MPHandleInterrupt; + Interrupt.DisableInterruptHandler = NULL; + Interrupt.EnableInterruptHandler = NULL; + + + + Status = NdisMRegisterInterruptEx(Adapter->AdapterHandle, + Adapter, + &Interrupt, + &Adapter->NdisInterruptHandle + ); + + + if (Status != NDIS_STATUS_SUCCESS) + { + DBGPRINT(MP_ERROR, ("NdisMRegisterInterrupt failed\n")); + + NdisWriteErrorLogEntry( + Adapter->AdapterHandle, + NDIS_ERROR_CODE_INTERRUPT_CONNECT, + 0); + + break; + } + + // + // If the driver support MSI + // + Adapter->InterruptType = Interrupt.InterruptType; + + if (Adapter->InterruptType == NDIS_CONNECT_MESSAGE_BASED) + { + Adapter->MessageInfoTable = Interrupt.MessageInfoTable; + } + + // + // If the driver supports MSI, here it should what kind of interrupt is granted. If MSI is granted, + // the driver can check Adapter->MessageInfoTable to get MSI information + // + + + MP_SET_FLAG(Adapter, fMP_ADAPTER_INTERRUPT_IN_USE); + + // + // Test our adapter hardware + // + Status = NICSelfTest(Adapter); + if (Status != NDIS_STATUS_SUCCESS) + { + break; + } + + // + // Init the hardware and set up everything + // + Status = NICInitializeAdapter(Adapter); + if (Status != NDIS_STATUS_SUCCESS) + { + break; + } + + // + // initial state is paused + // + Adapter->AdapterState = NicPaused; + + // + // Set the link detection flag + // + MP_SET_FLAG(Adapter, fMP_ADAPTER_LINK_DETECTION); + + // + // Increment the reference count so halt handler will wait + // + MP_INC_REF(Adapter); + + // + // Enable the interrupt + // + NICEnableInterrupt(Adapter); + + + NdisZeroMemory(&Timer, sizeof(NDIS_TIMER_CHARACTERISTICS)); + + Timer.Header.Type = NDIS_OBJECT_TYPE_TIMER_CHARACTERISTICS; + Timer.Header.Revision = NDIS_TIMER_CHARACTERISTICS_REVISION_1; + Timer.Header.Size = sizeof(NDIS_TIMER_CHARACTERISTICS); + + Timer.AllocationTag = NIC_TAG; + Timer.TimerFunction = MpLinkDetectionDpc; + Timer.FunctionContext = Adapter; + + // + // Minimize init-time + // + Status = NdisAllocateTimerObject( + Adapter->AdapterHandle, + &Timer, + &Adapter->LinkDetectionTimerHandle); + + if (Status != NDIS_STATUS_SUCCESS) + { + break; + } + + liDueTime.QuadPart = NIC_LINK_DETECTION_DELAY; + isTimerAlreadyInQueue =NdisSetTimerObject(Adapter->LinkDetectionTimerHandle, liDueTime, 0, NULL); + ASSERT(!isTimerAlreadyInQueue); +#endif + } +#if 0 + if (Adapter && (Status != NDIS_STATUS_SUCCESS)) + { + // + // Undo everything if it failed + // + MP_DEC_REF(Adapter); + MpFreeAdapter(Adapter); + } + + DBGPRINT_S(Status, ("<==== MPInitialize, Status=%x\n", Status)); +#endif + /* Create the adapter adapter */ + ib_status = ipoib_start_adapter( p_adapter ); + if( ib_status != IB_SUCCESS ) + { + ASSERT(FALSE); + + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_start_adapter returned status %d.\n", ib_status ) ); + return NDIS_STATUS_FAILURE; + } + + ipoib_ref_ibat(); + return NDIS_STATUS_SUCCESS; +} + + + +NDIS_STATUS +ipoib_initialize_ex( + IN NDIS_HANDLE h_adapter, + IN NDIS_HANDLE config_context, + IN PNDIS_MINIPORT_INIT_PARAMETERS MiniportInitParameters) + { + NDIS_STATUS status; + ib_api_status_t ib_status; + ipoib_adapter_t *p_adapter; + //NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES RegistrationAttributes; + //NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES GeneralAttributes; +#if IPOIB_USE_DMA + //NDIS_SG_DMA_DESCRIPTION DmaDescription; +#endif + IPOIB_ENTER( IPOIB_DBG_INIT ); + +#ifdef _DEBUG_ + PAGED_CODE(); +#endif + + UNUSED_PARAM( config_context ); + UNUSED_PARAM( MiniportInitParameters ); + + //foo1(100); + /* Create the adapter adapter */ + ib_status = ipoib_create_adapter(config_context, h_adapter, &p_adapter ); + if( ib_status != IB_SUCCESS ) + { + ASSERT(FALSE); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_create_adapter returned status %d.\n", ib_status ) ); + return NDIS_STATUS_FAILURE; + } + p_adapter->ipoib_state = IPOIB_PAUSED; + status = SetAttributes(p_adapter, h_adapter); + if (status != NDIS_STATUS_SUCCESS) { + ASSERT(FALSE); + } +#if 0 + NdisZeroMemory(&RegistrationAttributes, sizeof(NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES)); + NdisZeroMemory(&GeneralAttributes, sizeof(NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES)); + + /* setting registration attributes */ + RegistrationAttributes.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES; + RegistrationAttributes.Header.Revision = NDIS_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1; + RegistrationAttributes.Header.Size = NDIS_SIZEOF_MINIPORT_ADAPTER_REGISTRATION_ATTRIBUTES_REVISION_1; + + RegistrationAttributes.MiniportAdapterContext = (NDIS_HANDLE)p_adapter; + RegistrationAttributes.AttributeFlags = NDIS_MINIPORT_ATTRIBUTES_BUS_MASTER; + + RegistrationAttributes.CheckForHangTimeInSeconds = 10; + RegistrationAttributes.InterfaceType = NdisInterfacePNPBus; + + status = NdisMSetMiniportAttributes(h_adapter, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&RegistrationAttributes); + + if (status != NDIS_STATUS_SUCCESS) + { + ipoib_destroy_adapter( p_adapter ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisMSetMiniportAttributes returned 0x%.8x.\n", status) ); + return status; + } + + /* set up generic attributes */ + + GeneralAttributes.Header.Type = NDIS_OBJECT_TYPE_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES; + GeneralAttributes.Header.Revision = NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES_REVISION_1; + GeneralAttributes.Header.Size = sizeof(NDIS_MINIPORT_ADAPTER_GENERAL_ATTRIBUTES); + + GeneralAttributes.MediaType = NdisMedium802_3; + //TODO + GeneralAttributes.MtuSize = MAX_IB_MTU; + GeneralAttributes.MaxXmitLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + GeneralAttributes.MaxRcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + GeneralAttributes.XmitLinkSpeed = NDIS_LINK_SPEED_UNKNOWN; + GeneralAttributes.RcvLinkSpeed = NDIS_LINK_SPEED_UNKNOWN; + GeneralAttributes.MediaConnectState = MediaConnectStateUnknown; + GeneralAttributes.MediaDuplexState = MediaDuplexStateUnknown; + GeneralAttributes.LookaheadSize = MAX_XFER_BLOCK_SIZE; + + GeneralAttributes.PowerManagementCapabilities = NULL; + + GeneralAttributes.MacOptions = NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA | + NDIS_MAC_OPTION_TRANSFERS_NOT_PEND | + NDIS_MAC_OPTION_NO_LOOPBACK | + NDIS_MAC_OPTION_FULL_DUPLEX; + + GeneralAttributes.SupportedPacketFilters = NDIS_PACKET_TYPE_DIRECTED | + NDIS_PACKET_TYPE_MULTICAST | + NDIS_PACKET_TYPE_ALL_MULTICAST | + NDIS_PACKET_TYPE_BROADCAST; + + GeneralAttributes.MaxMulticastListSize = MAX_MCAST; + GeneralAttributes.MacAddressLength = HW_ADDR_LEN; + + NdisMoveMemory(GeneralAttributes.PermanentMacAddress, + p_adapter->mac.addr, + HW_ADDR_LEN); + + NdisMoveMemory(GeneralAttributes.CurrentMacAddress, + p_adapter->params.conf_mac.addr, + HW_ADDR_LEN); + + + GeneralAttributes.PhysicalMediumType = NdisPhysicalMediumUnspecified; + GeneralAttributes.RecvScaleCapabilities = NULL; + GeneralAttributes.AccessType = NET_IF_ACCESS_BROADCAST; // NET_IF_ACCESS_BROADCAST for a typical ethernet adapter + GeneralAttributes.DirectionType = NET_IF_DIRECTION_SENDRECEIVE; // NET_IF_DIRECTION_SENDRECEIVE for a typical ethernet adapter + GeneralAttributes.ConnectionType = NET_IF_CONNECTION_DEDICATED; // NET_IF_CONNECTION_DEDICATED for a typical ethernet adapter + GeneralAttributes.IfType = IF_TYPE_ETHERNET_CSMACD; // IF_TYPE_ETHERNET_CSMACD for a typical ethernet adapter (regardless of speed) + GeneralAttributes.IfConnectorPresent = TRUE; // RFC 2665 TRUE if physical adapter + + GeneralAttributes.SupportedStatistics = NDIS_STATISTICS_XMIT_OK_SUPPORTED | + NDIS_STATISTICS_RCV_OK_SUPPORTED | + NDIS_STATISTICS_XMIT_ERROR_SUPPORTED | + NDIS_STATISTICS_RCV_ERROR_SUPPORTED | + NDIS_STATISTICS_RCV_CRC_ERROR_SUPPORTED | + NDIS_STATISTICS_RCV_NO_BUFFER_SUPPORTED | + NDIS_STATISTICS_TRANSMIT_QUEUE_LENGTH_SUPPORTED | + NDIS_STATISTICS_GEN_STATISTICS_SUPPORTED; + + GeneralAttributes.SupportedOidList = (PNDIS_OID)SUPPORTED_OIDS; + GeneralAttributes.SupportedOidListLength = sizeof(SUPPORTED_OIDS); + + status = NdisMSetMiniportAttributes(h_adapter, + (PNDIS_MINIPORT_ADAPTER_ATTRIBUTES)&GeneralAttributes); + + if (status != NDIS_STATUS_SUCCESS) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisMSetMiniportAttributes returned 0x%.8x.\n", status) ); + } + +#if IPOIB_USE_DMA + + NdisZeroMemory(&DmaDescription, sizeof(DmaDescription)); + + DmaDescription.Header.Type = NDIS_OBJECT_TYPE_SG_DMA_DESCRIPTION; + DmaDescription.Header.Revision = NDIS_SG_DMA_DESCRIPTION_REVISION_1; + DmaDescription.Header.Size = sizeof(NDIS_SG_DMA_DESCRIPTION);//NDIS_SIZEOF_SG_DMA_DESCRIPTION_REVISION_1; + + DmaDescription.Flags = NDIS_SG_DMA_64_BIT_ADDRESS; + DmaDescription.MaximumPhysicalMapping = p_adapter->params.xfer_block_size; + + DmaDescription.ProcessSGListHandler = ipoib_process_sg_list; + DmaDescription.SharedMemAllocateCompleteHandler = NULL; + + status = NdisMRegisterScatterGatherDma( + p_adapter->h_adapter, + &DmaDescription, + &p_adapter->NdisMiniportDmaHandle); + + if( status != NDIS_STATUS_SUCCESS ) + { + ipoib_destroy_adapter( p_adapter ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisMRegisterScatterGatherDma returned 0x%.8x.\n", status) ); + return status; + } + + + +#endif +#endif //if 0 + + + +#if IPOIB_USE_DMA + + InitNdisScatterGatherDma(p_adapter, h_adapter); + + + +#endif + /* Create the adapter adapter */ + ib_status = ipoib_start_adapter( p_adapter ); + if( ib_status != IB_SUCCESS ) + { + ASSERT(FALSE); + NdisWriteErrorLogEntry( h_adapter, + NDIS_ERROR_CODE_HARDWARE_FAILURE, 0 ); +#if IPOIB_USE_DMA + NdisMDeregisterScatterGatherDma(p_adapter->NdisMiniportDmaHandle); +#endif + ipoib_destroy_adapter( p_adapter ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_start_adapter returned status %d.\n", ib_status ) ); + return NDIS_STATUS_FAILURE; + } + + ipoib_ref_ibat(); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; + } + + +//! Deallocates resources when the NIC is removed and halts the NIC.. +//TODO: Dispatch or Passive ? +/* IRQL = DISPATCH_LEVEL + +@param adapter_context The adapter context allocated at start +*/ +void +ipoib_halt_ex( + IN NDIS_HANDLE adapter_context, + IN NDIS_HALT_ACTION HaltAction ) +{ + ipoib_adapter_t *p_adapter; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + UNUSED_PARAM(HaltAction); +//return; + ipoib_deref_ibat(); + + CL_ASSERT( adapter_context ); + p_adapter = (ipoib_adapter_t*)adapter_context; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Port %016I64x (CA %016I64x port %d) halting\n", + p_adapter->guids.port_guid.guid, p_adapter->guids.ca_guid, + p_adapter->guids.port_num) ); + +#if IPOIB_USE_DMA + if (p_adapter->NdisMiniportDmaHandle != NULL) + { + NdisMDeregisterScatterGatherDma(p_adapter->NdisMiniportDmaHandle); + p_adapter->NdisMiniportDmaHandle = NULL; + } +#endif + ipoib_destroy_adapter( p_adapter ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +//! Reports the state of the NIC, or monitors the responsiveness of an underlying device driver. +/* IRQL = DISPATCH_LEVEL + +@param adapter_context The adapter context allocated at start +@return TRUE if the driver determines that its NIC is not operating +*/ +BOOLEAN +ipoib_check_for_hang( + IN NDIS_HANDLE adapter_context ) +{ + ipoib_adapter_t *p_adapter; + + IPOIB_ENTER( IPOIB_DBG_INIT ); +//return FALSE; + CL_ASSERT( adapter_context ); + p_adapter = (ipoib_adapter_t*)adapter_context; + + if( p_adapter->reset ) + { + IPOIB_EXIT( IPOIB_DBG_INIT ); + return FALSE; + } + if (p_adapter->hung) { + ipoib_resume_oids(p_adapter); + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return (p_adapter->hung? TRUE:FALSE); +} + + +/*++ +Routine Description: + The routine sets an NDIS_OFFLOAD structure indicates the current offload + capabilities that are provided by the miniport adapter + +Arguments: + pPort - a pointer to port object + offload - reference to NDIS_OFFLOAD object that should be filled + +Return Value: + None. + +--*/ +//TODO +#if 0 +static +void +__ipoib_get_offload_config( + ipoib_port_t *pPort, + NDIS_OFFLOAD *p_offload + ) +{ + NDIS_STATUS Status; + ULONG TxChksumOffload = ((MP_GET_PORT_CONFIG(pPort, TxChksumOffload) == TRUE) ? NDIS_OFFLOAD_SET_ON : NDIS_OFFLOAD_SET_OFF); + ULONG RxChksumOffload = ((MP_GET_PORT_CONFIG(pPort, RxChksumOffload) == TRUE) ? NDIS_OFFLOAD_SET_ON : NDIS_OFFLOAD_SET_OFF); + BOOLEAN fLargeSendOffload = MP_GET_PORT_CONFIG(pPort, LargeSendOffload); + ULONG ulEncapsulation = NDIS_ENCAPSULATION_IEEE_802_3 | NDIS_ENCAPSULATION_IEEE_802_3_P_AND_Q; + + NdisZeroMemory(&*p_offload, sizeof(NDIS_OFFLOAD)); + *p_offload.Header.Type = NDIS_OBJECT_TYPE_OFFLOAD; + *p_offload.Header.Revision = NDIS_OFFLOAD_REVISION_1; // BUGBUG: do we need to support revision 2? UH 17-May-2008 + *p_offload.Header.Size = NDIS_SIZEOF_NDIS_OFFLOAD_REVISION_1; + + *p_offload.Checksum.IPv4Transmit.Encapsulation = ulEncapsulation; + *p_offload.Checksum.IPv4Transmit.IpOptionsSupported = TxChksumOffload; + *p_offload.Checksum.IPv4Transmit.TcpOptionsSupported = TxChksumOffload; + *p_offload.Checksum.IPv4Transmit.TcpChecksum = TxChksumOffload; + *p_offload.Checksum.IPv4Transmit.UdpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + *p_offload.Checksum.IPv4Transmit.IpChecksum = TxChksumOffload; + + *p_offload.Checksum.IPv4Receive.Encapsulation = ulEncapsulation; + *p_offload.Checksum.IPv4Receive.IpOptionsSupported = RxChksumOffload; + *p_offload.Checksum.IPv4Receive.TcpOptionsSupported = RxChksumOffload; + *p_offload.Checksum.IPv4Receive.TcpChecksum = RxChksumOffload; + *p_offload.Checksum.IPv4Receive.UdpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + *p_offload.Checksum.IPv4Receive.IpChecksum = RxChksumOffload; + + *p_offload.Checksum.IPv6Transmit.Encapsulation = ulEncapsulation; + *p_offload.Checksum.IPv6Transmit.IpExtensionHeadersSupported = TxChksumOffload; + *p_offload.Checksum.IPv6Transmit.TcpOptionsSupported = TxChksumOffload; + *p_offload.Checksum.IPv6Transmit.TcpChecksum = TxChksumOffload; + *p_offload.Checksum.IPv6Transmit.UdpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + + + *p_offload.Checksum.IPv6Receive.Encapsulation = ulEncapsulation; + *p_offload.Checksum.IPv6Receive.IpExtensionHeadersSupported = RxChksumOffload; + *p_offload.Checksum.IPv6Receive.TcpOptionsSupported = RxChksumOffload; + *p_offload.Checksum.IPv6Receive.TcpChecksum = RxChksumOffload; + *p_offload.Checksum.IPv6Receive.UdpChecksum = NDIS_OFFLOAD_NOT_SUPPORTED; + + if (fLargeSendOffload) + { + *p_offload.LsoV1.IPv4.Encapsulation = ulEncapsulation; + *p_offload.LsoV1.IPv4.MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; + *p_offload.LsoV1.IPv4.MinSegmentCount = 1; + *p_offload.LsoV1.IPv4.TcpOptions = NDIS_OFFLOAD_SUPPORTED; + *p_offload.LsoV1.IPv4.IpOptions = NDIS_OFFLOAD_SUPPORTED; + } +} +#endif + +//! Returns information about the capabilities and status of the driver and/or its NIC. +/* IRQL = DISPATCH_LEVEL + +@param adapter_context The adapter context allocated at start +@param oid Object ID representing the query operation to be carried out +@param info_buf Buffer containing any input for this query and location for output +@param info_buf_len Number of bytes available in info_buf +@param p_bytes_written Pointer to number of bytes written into info_buf +@param p_bytes_needed Pointer to number of bytes needed to satisfy this oid +@return NDIS_STATUS_SUCCESS, NDIS_STATUS_PENDING, NDIS_STATUS_INVALID_OID, +NDIS_STATUS_INVALID_LENGTH, NDIS_STATUS_NOT_ACCEPTED, NDIS_STATUS_NOT_SUPPORTED, +NDIS_STATUS_RESOURCES +*/ + NDIS_STATUS +ipoib_query_info( + IN NDIS_HANDLE adapter_context, + IN NDIS_OID oid, + IN PVOID info_buf, + IN ULONG info_buf_len, + OUT PULONG p_bytes_written, + OUT PULONG p_bytes_needed ) + { + ipoib_adapter_t *p_adapter; + NDIS_STATUS status; + USHORT version; + ULONG info; + PVOID src_buf; + ULONG buf_len; + pending_oid_t oid_info; + uint8_t port_num; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + oid_info.oid = oid; + oid_info.p_buf = info_buf; + oid_info.buf_len = info_buf_len; + oid_info.p_bytes_used = p_bytes_written; + oid_info.p_bytes_needed = p_bytes_needed; + + CL_ASSERT( adapter_context ); + p_adapter = (ipoib_adapter_t*)adapter_context; + + CL_ASSERT( p_bytes_written ); + CL_ASSERT( p_bytes_needed ); + CL_ASSERT( !p_adapter->pending_query ); + + status = NDIS_STATUS_SUCCESS; + src_buf = &info; + buf_len = sizeof(info); + + port_num = p_adapter->guids.port_num; + + switch( oid ) + { + /* Required General */ + case OID_GEN_SUPPORTED_LIST: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_SUPPORTED_LIST\n", port_num) ); + src_buf = (PVOID)SUPPORTED_OIDS; + buf_len = sizeof(SUPPORTED_OIDS); + break; + + case OID_GEN_HARDWARE_STATUS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_HARDWARE_STATUS\n", port_num) ); + cl_obj_lock( &p_adapter->obj ); + switch( p_adapter->state ) + { + case IB_PNP_PORT_ADD: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NdisHardwareStatusInitializing\n", port_num) ); + info = NdisHardwareStatusInitializing; + break; + + case IB_PNP_PORT_ACTIVE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NdisHardwareStatusReady\n", port_num) ); + info = NdisHardwareStatusReady; + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NdisHardwareStatusNotReady\n", port_num) ); + info = NdisHardwareStatusNotReady; + } + cl_obj_unlock( &p_adapter->obj ); + break; + + case OID_GEN_MEDIA_SUPPORTED: + case OID_GEN_MEDIA_IN_USE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MEDIA_SUPPORTED " + "or OID_GEN_MEDIA_IN_USE\n", port_num) ); + info = NdisMedium802_3; + break; + + case OID_GEN_MAXIMUM_FRAME_SIZE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MAXIMUM_FRAME_SIZE\n", port_num) ); + if( p_adapter->params.cm_enabled ) + { + info = p_adapter->params.cm_payload_mtu; + } + else + { + info = p_adapter->params.payload_mtu; + } + break; + + case OID_GEN_LINK_SPEED: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_LINK_SPEED\n", port_num) ); + if (info_buf_len < buf_len) + { + break; + } + + cl_obj_lock( &p_adapter->obj ); + switch( p_adapter->state ) + { + case IB_PNP_PORT_ADD: + /* Mark the adapter as pending an OID */ + p_adapter->pending_query = TRUE; + + /* Save the request parameters. */ + p_adapter->query_oid = oid_info; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NDIS_STATUS_PENDING\n", port_num) ); + status = NDIS_STATUS_PENDING; + break; + + case IB_PNP_PORT_REMOVE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NDIS_STATUS_NOT_ACCEPTED\n", port_num) ); + status = NDIS_STATUS_NOT_ACCEPTED; + break; + + default: + CL_ASSERT( p_adapter->p_port ); + info = p_adapter->port_rate; + break; + } + cl_obj_unlock( &p_adapter->obj ); + break; + + case OID_GEN_TRANSMIT_BUFFER_SPACE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_TRANSMIT_BUFFER_SPACE\n", port_num) ); + if( p_adapter->params.cm_enabled ) + info = p_adapter->params.sq_depth * p_adapter->params.cm_xfer_block_size; + else + info = p_adapter->params.sq_depth * p_adapter->params.xfer_block_size; + break; + + case OID_GEN_RECEIVE_BUFFER_SPACE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_TRANSMIT_BUFFER_SPACE " + "or OID_GEN_RECEIVE_BUFFER_SPACE\n", port_num) ); + if( p_adapter->params.cm_enabled ) + info = p_adapter->params.rq_depth * p_adapter->params.cm_xfer_block_size; + else + info = p_adapter->params.rq_depth * p_adapter->params.xfer_block_size; + break; + + case OID_GEN_MAXIMUM_LOOKAHEAD: + case OID_GEN_CURRENT_LOOKAHEAD: + case OID_GEN_TRANSMIT_BLOCK_SIZE: + case OID_GEN_RECEIVE_BLOCK_SIZE: + case OID_GEN_MAXIMUM_TOTAL_SIZE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MAXIMUM_LOOKAHEAD " + "or OID_GEN_CURRENT_LOOKAHEAD or " + "OID_GEN_TRANSMIT_BLOCK_SIZE or " + "OID_GEN_RECEIVE_BLOCK_SIZE or " + "OID_GEN_MAXIMUM_TOTAL_SIZE\n", port_num) ); + if( p_adapter->params.cm_enabled ) + info = p_adapter->params.cm_xfer_block_size; + else + info = p_adapter->params.xfer_block_size; + break; + + case OID_GEN_VENDOR_ID: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_VENDOR_ID\n", port_num) ); + src_buf = (void*)VENDOR_ID; + buf_len = sizeof(VENDOR_ID); + break; + + case OID_GEN_VENDOR_DESCRIPTION: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_VENDOR_DESCRIPTION\n", port_num) ); + src_buf = VENDOR_DESCRIPTION; + buf_len = sizeof(VENDOR_DESCRIPTION); + break; + + case OID_GEN_VENDOR_DRIVER_VERSION: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_VENDOR_DRIVER_VERSION\n", port_num) ); + src_buf = &version; + buf_len = sizeof(version); + //TODO: Figure out what the right version is. + version = 1 << 8 | 1; + break; + + case OID_GEN_PHYSICAL_MEDIUM: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_PHYSICAL_MEDIUM\n", port_num) ); + info = NdisPhysicalMediumUnspecified; + break; + + case OID_GEN_CURRENT_PACKET_FILTER: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_CURRENT_PACKET_FILTER\n", port_num) ); + info = p_adapter->packet_filter; + break; + + case OID_GEN_DRIVER_VERSION: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_DRIVER_VERSION\n", port_num) ); + src_buf = &version; + buf_len = sizeof(version); + version = MAJOR_NDIS_VERSION << 8 | MINOR_NDIS_VERSION; + break; + + case OID_GEN_MAC_OPTIONS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MAC_OPTIONS\n", port_num) ); + info = NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA | + NDIS_MAC_OPTION_TRANSFERS_NOT_PEND | + NDIS_MAC_OPTION_NO_LOOPBACK | + NDIS_MAC_OPTION_FULL_DUPLEX; + //TODO: Figure out if we will support priority and VLANs. + // NDIS_MAC_OPTION_8021P_PRIORITY; + //#ifdef NDIS51_MINIPORT + // info |= NDIS_MAC_OPTION_8021Q_VLAN; + //#endif + break; + + case OID_GEN_MEDIA_CONNECT_STATUS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MEDIA_CONNECT_STATUS\n", port_num) ); + cl_obj_lock( &p_adapter->obj ); + switch( p_adapter->state ) + { + case IB_PNP_PORT_ADD: + case IB_PNP_PORT_INIT: + /* + * Delay reporting media state until we know whether the port is + * either up or down. + */ + p_adapter->pending_query = TRUE; + p_adapter->query_oid = oid_info; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NDIS_STATUS_PENDING\n", port_num) ); + status = NDIS_STATUS_PENDING; + break; + + case IB_PNP_PORT_ACTIVE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NdisMediaStateConnected\n", port_num) ); + info = NdisMediaStateConnected; + break; + + case IB_PNP_PORT_REMOVE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NDIS_STATUS_NOT_ACCEPTED\n", port_num) ); + status = NDIS_STATUS_NOT_ACCEPTED; + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d returning NdisMediaStateDisconnected\n", port_num) ); + info = NdisMediaStateDisconnected; + } + cl_obj_unlock( &p_adapter->obj ); + break; + + case OID_GEN_MAXIMUM_SEND_PACKETS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MAXIMUM_SEND_PACKETS\n", port_num) ); + info = MINIPORT_MAX_SEND_PACKETS; + break; + + /* Required General Statistics */ + case OID_GEN_STATISTICS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_STATISTICS\n", port_num) ); + src_buf = NULL; + buf_len = sizeof(NDIS_STATISTICS_INFO); + if (info_buf_len < buf_len) + { + break; + } + status = ipoib_get_gen_stat(p_adapter, &oid_info ); + break; + + case OID_GEN_XMIT_OK: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_XMIT_OK\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_SUCCESS, &oid_info ); + break; + + case OID_GEN_RCV_OK: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_RCV_OK\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_SUCCESS, &oid_info ); + break; + + case OID_GEN_XMIT_ERROR: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_XMIT_ERROR\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_ERROR, &oid_info ); + break; + + case OID_GEN_RCV_ERROR: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_RCV_ERROR\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_ERROR, &oid_info ); + break; + + case OID_GEN_RCV_NO_BUFFER: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_RCV_NO_BUFFER\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_DROPPED, &oid_info ); + break; + + case OID_GEN_DIRECTED_BYTES_XMIT: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_DIRECTED_BYTES_XMIT\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_UCAST_BYTES, &oid_info ); + break; + + case OID_GEN_DIRECTED_FRAMES_XMIT: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_DIRECTED_FRAMES_XMIT\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_UCAST_FRAMES, &oid_info ); + break; + + case OID_GEN_MULTICAST_BYTES_XMIT: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MULTICAST_BYTES_XMIT\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_MCAST_BYTES, &oid_info ); + break; + + case OID_GEN_MULTICAST_FRAMES_XMIT: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MULTICAST_FRAMES_XMIT\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_MCAST_FRAMES, &oid_info ); + break; + + case OID_GEN_BROADCAST_BYTES_XMIT: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_BROADCAST_BYTES_XMIT\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_BCAST_BYTES, &oid_info ); + break; + + case OID_GEN_BROADCAST_FRAMES_XMIT: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_BROADCAST_FRAMES_XMIT\n", port_num) ); + src_buf = NULL; + status = ipoib_get_send_stat( p_adapter, IP_STAT_BCAST_FRAMES, &oid_info ); + break; + + case OID_GEN_DIRECTED_BYTES_RCV: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_DIRECTED_BYTES_RCV\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_UCAST_BYTES, &oid_info ); + break; + + case OID_GEN_DIRECTED_FRAMES_RCV: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_DIRECTED_FRAMES_RCV\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_UCAST_FRAMES, &oid_info ); + break; + + case OID_GEN_MULTICAST_BYTES_RCV: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MULTICAST_BYTES_RCV\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_MCAST_BYTES, &oid_info ); + break; + + case OID_GEN_MULTICAST_FRAMES_RCV: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_MULTICAST_FRAMES_RCV\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_MCAST_FRAMES, &oid_info ); + break; + + case OID_GEN_BROADCAST_BYTES_RCV: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_BROADCAST_BYTES_RCV\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_BCAST_BYTES, &oid_info ); + break; + + case OID_GEN_BROADCAST_FRAMES_RCV: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_BROADCAST_FRAMES_RCV\n", port_num) ); + src_buf = NULL; + status = ipoib_get_recv_stat( p_adapter, IP_STAT_BCAST_FRAMES, &oid_info ); + break; + + /* Required Ethernet operational characteristics */ + case OID_802_3_PERMANENT_ADDRESS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_802_3_PERMANENT_ADDRESS\n", port_num) ); + src_buf = &p_adapter->mac; + buf_len = sizeof(p_adapter->mac); + break; + + case OID_802_3_CURRENT_ADDRESS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_802_3_CURRENT_ADDRESS\n", port_num) ); + src_buf = &p_adapter->params.conf_mac; + buf_len = sizeof(p_adapter->params.conf_mac); + break; + + case OID_802_3_MULTICAST_LIST: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_802_3_MULTICAST_LIST\n", port_num) ); + src_buf = p_adapter->mcast_array; + buf_len = p_adapter->mcast_array_size * sizeof(mac_addr_t); + break; + + case OID_802_3_MAXIMUM_LIST_SIZE: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_802_3_MAXIMUM_LIST_SIZE\n", port_num) ); + info = MAX_MCAST; + break; + + case OID_802_3_MAC_OPTIONS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_802_3_MAC_OPTIONS\n", port_num) ); + info = 0; + break; + + /* Required Ethernet stats */ + case OID_802_3_RCV_ERROR_ALIGNMENT: + case OID_802_3_XMIT_ONE_COLLISION: + case OID_802_3_XMIT_MORE_COLLISIONS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_802_3_RCV_ERROR_ALIGNMENT or " + "OID_802_3_XMIT_ONE_COLLISION or " + "OID_802_3_XMIT_MORE_COLLISIONS\n", port_num) ); + info = 0; + break; + + case OID_TCP_TASK_OFFLOAD: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_TCP_TASK_OFFLOAD\n", port_num) ); + src_buf = NULL; + status = __ipoib_get_tcp_task_offload( p_adapter, &oid_info ); + break; + + /* Optional General */ + case OID_GEN_SUPPORTED_GUIDS: +#ifdef NDIS51_MINIPORT + case OID_GEN_VLAN_ID: +#endif + + /* Optional General Stats */ + case OID_GEN_RCV_CRC_ERROR: + case OID_GEN_TRANSMIT_QUEUE_LENGTH: + + /* Optional Ethernet Stats */ + case OID_802_3_XMIT_DEFERRED: + case OID_802_3_XMIT_MAX_COLLISIONS: + case OID_802_3_RCV_OVERRUN: + case OID_802_3_XMIT_UNDERRUN: + case OID_802_3_XMIT_HEARTBEAT_FAILURE: + case OID_802_3_XMIT_TIMES_CRS_LOST: + case OID_802_3_XMIT_LATE_COLLISIONS: + case OID_PNP_CAPABILITIES: + status = NDIS_STATUS_NOT_SUPPORTED; + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received an unsupported oid of 0x%.8X!\n", port_num, oid) ); + break; + + case OID_GEN_PROTOCOL_OPTIONS: + case OID_GEN_NETWORK_LAYER_ADDRESSES: + case OID_GEN_TRANSPORT_HEADER_OFFSET: + case OID_PNP_ENABLE_WAKE_UP: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_GEN_PROTOCOL_OPTIONS or OID_GEN_NETWORK_LAYER_ADDRESSES or OID_GEN_TRANSPORT_HEADER_OFFSET OID_PNP_ENABLE_WAKE_UPn", port_num) ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Number of OID: 0x%.8X!\n", oid) ); + status = NDIS_STATUS_SUCCESS; + break; + + case OID_PNP_QUERY_POWER: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_TCP_TASK_OFFLOAD\n", port_num) ); + // Status is pre-set in this routine to Success + status = NDIS_STATUS_SUCCESS; + break; + + case OID_TCP_OFFLOAD_CURRENT_CONFIG: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_PNP_QUERY_POWER\n", port_num) ); + //ulBytesAvailable = ulInfoLen = sizeof(NDIS_OFFLOAD); + if (info_buf_len < sizeof(NDIS_OFFLOAD)) + { + status = NDIS_STATUS_BUFFER_TOO_SHORT; + *p_bytes_needed = sizeof(NDIS_OFFLOAD) ; + break; + } + + //ipoib_offload_config(pPort, &offload); + //pInfo = &offload; + break; + + default: + status = NDIS_STATUS_INVALID_OID; + // IPOIB_PRINT( TRACE_LEVEL_ERROR,IPOIB_DBG_OID, + // ("Port %d received an invalid oid of 0x%.8X!\n", port_num, oid) ); + break; + } + + /* + * Complete the request as if it was handled asynchronously to maximize + * code reuse for when we really handle the requests asynchronously. + * Note that this requires the QueryInformation entry point to always + * return NDIS_STATUS_PENDING + */ + if( status != NDIS_STATUS_PENDING ) + { + ipoib_complete_query( + p_adapter, &oid_info, status, src_buf, buf_len ); + return status; + } + + IPOIB_EXIT( IPOIB_DBG_OID ); + return NDIS_STATUS_PENDING; + } + + +static void +ipoib_complete_query( + IN ipoib_adapter_t* const p_adapter, + IN pending_oid_t* const p_oid_info, + IN const NDIS_STATUS status, + IN const void* const p_buf, + IN const ULONG buf_len ) +{ + NDIS_STATUS oid_status = status; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + CL_ASSERT( status != NDIS_STATUS_PENDING ); + + if( status == NDIS_STATUS_SUCCESS ) + { + if( p_oid_info->buf_len < buf_len ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Insufficient buffer space. " + "Returning NDIS_STATUS_INVALID_LENGTH.\n") ); + oid_status = NDIS_STATUS_INVALID_LENGTH; + *p_oid_info->p_bytes_needed = buf_len; + *p_oid_info->p_bytes_used = 0; + } + else if( p_oid_info->p_buf ) + { + /* Only copy if we have a distinct source buffer. */ + if( p_buf ) + { + NdisMoveMemory( p_oid_info->p_buf, p_buf, buf_len ); + *p_oid_info->p_bytes_used = buf_len; + } + } + else + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Returning NDIS_NOT_ACCEPTED") ); + oid_status = NDIS_STATUS_NOT_ACCEPTED; + } + } + else + { + *p_oid_info->p_bytes_used = 0; + } + + if (p_adapter->query_oid.p_pending_oid) + { + NdisMOidRequestComplete(p_adapter->h_adapter,p_adapter->query_oid.p_pending_oid,oid_status); + p_adapter->query_oid.p_pending_oid = NULL; + } + p_adapter->pending_query = FALSE; + + IPOIB_EXIT( IPOIB_DBG_OID ); +} + + +static NDIS_STATUS +__ipoib_get_tcp_task_offload( + IN ipoib_adapter_t* p_adapter, + OUT pending_oid_t *pNdisRequest ) +{ +#ifndef NDIS60_MINIPORT + NDIS_TASK_OFFLOAD_HEADER *p_offload_hdr; + NDIS_TASK_OFFLOAD *p_offload_task; + NDIS_TASK_TCP_IP_CHECKSUM *p_offload_chksum; + + NDIS_TASK_TCP_LARGE_SEND *p_offload_lso; + ULONG buf_len; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received query for OID_TCP_TASK_OFFLOAD\n", + p_adapter->guids.port_num) ); + + buf_len = sizeof(NDIS_TASK_OFFLOAD_HEADER) + + offsetof( NDIS_TASK_OFFLOAD, TaskBuffer ) + + sizeof(NDIS_TASK_TCP_IP_CHECKSUM) + + (p_adapter->params.lso ? + sizeof(NDIS_TASK_OFFLOAD) + sizeof(NDIS_TASK_TCP_LARGE_SEND) + : 0); + + pNdisRequest->DATA.QUERY_INFORMATION.BytesNeeded = buf_len; + + if( pNdisRequest->DATA.QUERY_INFORMATION.InformationBufferLength < buf_len ) + return NDIS_STATUS_INVALID_LENGTH; + + p_offload_hdr = (NDIS_TASK_OFFLOAD_HEADER*)pNdisRequest->DATA.QUERY_INFORMATION.InformationBuffer; + if( p_offload_hdr->Version != NDIS_TASK_OFFLOAD_VERSION ) + return NDIS_STATUS_INVALID_DATA; + + if( p_offload_hdr->EncapsulationFormat.Encapsulation != + IEEE_802_3_Encapsulation ) + { + return NDIS_STATUS_INVALID_DATA; + } + + p_offload_hdr->OffsetFirstTask = sizeof(NDIS_TASK_OFFLOAD_HEADER); + p_offload_task = (NDIS_TASK_OFFLOAD*)(p_offload_hdr + 1); + p_offload_task->Version = NDIS_TASK_OFFLOAD_VERSION; + p_offload_task->Size = sizeof(NDIS_TASK_OFFLOAD); + p_offload_task->Task = TcpIpChecksumNdisTask; + p_offload_task->OffsetNextTask = 0; + p_offload_task->TaskBufferLength = sizeof(NDIS_TASK_TCP_IP_CHECKSUM); + p_offload_chksum = + (NDIS_TASK_TCP_IP_CHECKSUM*)p_offload_task->TaskBuffer; + + p_offload_chksum->V4Transmit.IpOptionsSupported = + p_offload_chksum->V4Transmit.TcpOptionsSupported = + p_offload_chksum->V4Transmit.TcpChecksum = + p_offload_chksum->V4Transmit.UdpChecksum = + p_offload_chksum->V4Transmit.IpChecksum = + !!(p_adapter->params.send_chksum_offload); + + p_offload_chksum->V4Receive.IpOptionsSupported = + p_offload_chksum->V4Receive.TcpOptionsSupported = + p_offload_chksum->V4Receive.TcpChecksum = + p_offload_chksum->V4Receive.UdpChecksum = + p_offload_chksum->V4Receive.IpChecksum = + !!(p_adapter->params.recv_chksum_offload); + + p_offload_chksum->V6Transmit.IpOptionsSupported = FALSE; + p_offload_chksum->V6Transmit.TcpOptionsSupported = FALSE; + p_offload_chksum->V6Transmit.TcpChecksum = FALSE; + p_offload_chksum->V6Transmit.UdpChecksum = FALSE; + + p_offload_chksum->V6Receive.IpOptionsSupported = FALSE; + p_offload_chksum->V6Receive.TcpOptionsSupported = FALSE; + p_offload_chksum->V6Receive.TcpChecksum = FALSE; + p_offload_chksum->V6Receive.UdpChecksum = FALSE; + + + if (p_adapter->params.lso) { + // set the previous pointer to the correct place + p_offload_task->OffsetNextTask = FIELD_OFFSET(NDIS_TASK_OFFLOAD, TaskBuffer) + + p_offload_task->TaskBufferLength; + // set the LSO packet + p_offload_task = (PNDIS_TASK_OFFLOAD) + ((PUCHAR)p_offload_task + p_offload_task->OffsetNextTask); + + p_offload_task->Version = NDIS_TASK_OFFLOAD_VERSION; + p_offload_task->Size = sizeof(NDIS_TASK_OFFLOAD); + p_offload_task->Task = TcpLargeSendNdisTask; + p_offload_task->OffsetNextTask = 0; + p_offload_task->TaskBufferLength = sizeof(NDIS_TASK_TCP_LARGE_SEND); + + p_offload_lso = (PNDIS_TASK_TCP_LARGE_SEND) p_offload_task->TaskBuffer; + + p_offload_lso->Version = 0; + //TODO optimal size: 60000, 64000 or 65536 + //TODO LSO_MIN_SEG_COUNT to be 1 + p_offload_lso->MaxOffLoadSize = LARGE_SEND_OFFLOAD_SIZE; +#define LSO_MIN_SEG_COUNT 2 + p_offload_lso->MinSegmentCount = LSO_MIN_SEG_COUNT; + p_offload_lso->TcpOptions = TRUE; + p_offload_lso->IpOptions = TRUE; + } + + pNdisRequest->DATA.QUERY_INFORMATION.BytesWritten = buf_len + + return NDIS_STATUS_SUCCESS; +#endif + UNUSED_PARAM(p_adapter); + UNUSED_PARAM(pNdisRequest); + return NDIS_STATUS_NOT_SUPPORTED; + +} + + +static NDIS_STATUS +__ipoib_set_tcp_task_offload( + IN ipoib_adapter_t* p_adapter, + IN void* const p_info_buf, + IN ULONG* const p_info_len ) +{ +#ifndef NDIS60_MINIPORT + NDIS_TASK_OFFLOAD_HEADER *p_offload_hdr; + NDIS_TASK_OFFLOAD *p_offload_task; + NDIS_TASK_TCP_IP_CHECKSUM *p_offload_chksum; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_TCP_TASK_OFFLOAD\n", + p_adapter->guids.port_num) ); + + p_offload_hdr = (NDIS_TASK_OFFLOAD_HEADER*)p_info_buf; + + if( *p_info_len < sizeof(NDIS_TASK_OFFLOAD_HEADER) ) + return NDIS_STATUS_INVALID_LENGTH; + + if( p_offload_hdr->Version != NDIS_TASK_OFFLOAD_VERSION ) + return NDIS_STATUS_INVALID_DATA; + + if( p_offload_hdr->Size != sizeof(NDIS_TASK_OFFLOAD_HEADER) ) + return NDIS_STATUS_INVALID_LENGTH; + + if( !p_offload_hdr->OffsetFirstTask ) + return NDIS_STATUS_SUCCESS; + + if( p_offload_hdr->EncapsulationFormat.Encapsulation != + IEEE_802_3_Encapsulation ) + { + return NDIS_STATUS_INVALID_DATA; + } + + p_offload_task = (NDIS_TASK_OFFLOAD*) + (((UCHAR*)p_offload_hdr) + p_offload_hdr->OffsetFirstTask); + + if( *p_info_len < sizeof(NDIS_TASK_OFFLOAD_HEADER) + + offsetof( NDIS_TASK_OFFLOAD, TaskBuffer ) + + sizeof(NDIS_TASK_TCP_IP_CHECKSUM) ) + { + return NDIS_STATUS_INVALID_LENGTH; + } + + if( p_offload_task->Version != NDIS_TASK_OFFLOAD_VERSION ) + return NDIS_STATUS_INVALID_DATA; + p_offload_chksum = + (NDIS_TASK_TCP_IP_CHECKSUM*)p_offload_task->TaskBuffer; + + if( !p_adapter->params.send_chksum_offload && + (p_offload_chksum->V4Transmit.IpOptionsSupported || + p_offload_chksum->V4Transmit.TcpOptionsSupported || + p_offload_chksum->V4Transmit.TcpChecksum || + p_offload_chksum->V4Transmit.UdpChecksum || + p_offload_chksum->V4Transmit.IpChecksum) ) + { + return NDIS_STATUS_NOT_SUPPORTED; + } + + if( !p_adapter->params.recv_chksum_offload && + (p_offload_chksum->V4Receive.IpOptionsSupported || + p_offload_chksum->V4Receive.TcpOptionsSupported || + p_offload_chksum->V4Receive.TcpChecksum || + p_offload_chksum->V4Receive.UdpChecksum || + p_offload_chksum->V4Receive.IpChecksum) ) + { + return NDIS_STATUS_NOT_SUPPORTED; + } + + return NDIS_STATUS_SUCCESS; +#endif + UNUSED_PARAM(p_adapter); + UNUSED_PARAM(p_info_buf); + UNUSED_PARAM(p_info_len); + return NDIS_STATUS_NOT_SUPPORTED; +} + + +//! Issues a hardware reset to the NIC and/or resets the driver's software state. +/* Tear down the connection and start over again. This is only called when there is a problem. +For example, if a send, query info, or set info had a time out. MiniportCheckForHang will +be called first. +IRQL = DISPATCH_LEVEL + +@param p_addr_resetPointer to BOOLLEAN that is set to TRUE if the NDIS +library should call MiniportSetInformation to restore addressing information to the current values. +@param adapter_context The adapter context allocated at start +@return NDIS_STATUS_SUCCESS, NDIS_STATUS_PENDING, NDIS_STATUS_NOT_RESETTABLE, +NDIS_STATUS_RESET_IN_PROGRESS, NDIS_STATUS_SOFT_ERRORS, NDIS_STATUS_HARD_ERRORS +*/ +NDIS_STATUS +ipoib_reset( + IN NDIS_HANDLE adapter_context, + OUT PBOOLEAN p_addr_reset) +{ + ipoib_adapter_t* p_adapter; + + IPOIB_ENTER( IPOIB_DBG_INIT ); +//return NDIS_STATUS_SUCCESS; + CL_ASSERT( p_addr_reset ); + CL_ASSERT( adapter_context ); + p_adapter = (ipoib_adapter_t*)adapter_context; + + switch( ipoib_reset_adapter( p_adapter ) ) + { + case IB_NOT_DONE: + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_PENDING; + + case IB_SUCCESS: + IPOIB_EXIT( IPOIB_DBG_INIT ); + *p_addr_reset = TRUE; + return NDIS_STATUS_SUCCESS; + + case IB_INVALID_STATE: + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_RESET_IN_PROGRESS; + + default: + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_HARD_ERRORS; + } +} + + +//! Request changes in the state information that the miniport driver maintains +/* For example, this is used to set multicast addresses and the packet filter. +IRQL = DISPATCH_LEVEL + +@param adapter_context The adapter context allocated at start +@param oid Object ID representing the set operation to be carried out +@param info_buf Buffer containing input for this set and location for any output +@param info_buf_len Number of bytes available in info_buf +@param p_bytes_read Pointer to number of bytes read from info_buf +@param p_bytes_needed Pointer to number of bytes needed to satisfy this oid +@return NDIS_STATUS_SUCCESS, NDIS_STATUS_PENDING, NDIS_STATUS_INVALID_OID, +NDIS_STATUS_INVALID_LENGTH, NDIS_STATUS_INVALID_DATA, NDIS_STATUS_NOT_ACCEPTED, +NDIS_STATUS_NOT_SUPPORTED, NDIS_STATUS_RESOURCES +*/ +NDIS_STATUS +ipoib_set_info( + IN NDIS_HANDLE adapter_context, + IN NDIS_OID oid, + IN PVOID info_buf, + IN ULONG info_buf_len, + OUT PULONG p_bytes_read, + OUT PULONG p_bytes_needed ) +{ + ipoib_adapter_t* p_adapter; + NDIS_STATUS status; + + ULONG buf_len; + uint8_t port_num; + + KLOCK_QUEUE_HANDLE hdl; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + CL_ASSERT( adapter_context ); + p_adapter = (ipoib_adapter_t*)adapter_context; + + CL_ASSERT( p_bytes_read ); + CL_ASSERT( p_bytes_needed ); + CL_ASSERT( !p_adapter->pending_set ); + + status = NDIS_STATUS_SUCCESS; + *p_bytes_needed = 0; + buf_len = sizeof(ULONG); + + port_num = p_adapter->guids.port_num; + + cl_obj_lock( &p_adapter->obj ); + + if( p_adapter->state == IB_PNP_PORT_REMOVE ) + { + *p_bytes_read = 0; + cl_obj_unlock( &p_adapter->obj ); + return NDIS_STATUS_NOT_ACCEPTED; + } + + cl_obj_unlock( &p_adapter->obj ); + + switch( oid ) + { + /* Required General */ + case OID_GEN_CURRENT_PACKET_FILTER: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_CURRENT_PACKET_FILTER\n", port_num)); + if( info_buf_len < sizeof(p_adapter->packet_filter) ) + { + status = NDIS_STATUS_INVALID_LENGTH; + } + else if( !info_buf ) + { + status = NDIS_STATUS_INVALID_DATA; + } + else + { + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + cl_obj_lock( &p_adapter->obj ); + switch( p_adapter->state ) + { + case IB_PNP_PORT_ADD: + p_adapter->set_oid.oid = oid; + p_adapter->set_oid.p_buf = info_buf; + p_adapter->set_oid.buf_len = info_buf_len; + p_adapter->set_oid.p_bytes_used = p_bytes_read; + p_adapter->set_oid.p_bytes_needed = p_bytes_needed; + p_adapter->pending_set = TRUE; + status = NDIS_STATUS_PENDING; + break; + + case IB_PNP_PORT_REMOVE: + status = NDIS_STATUS_NOT_ACCEPTED; + break; + + default: + if( !p_adapter->packet_filter && (*(uint32_t*)info_buf) ) + { + cl_qlist_insert_tail( + &g_ipoib.adapter_list, &p_adapter->entry ); + + /* + * Filter was zero, now non-zero. Register IP addresses + * with SA. + */ + ipoib_reg_addrs( p_adapter ); + } + else if( p_adapter->packet_filter && !(*(uint32_t*)info_buf) ) + { + /* + * Filter was non-zero, now zero. Deregister IP addresses. + */ + ipoib_dereg_addrs( p_adapter ); + + ASSERT( cl_qlist_count( &g_ipoib.adapter_list ) ); + cl_qlist_remove_item( + &g_ipoib.adapter_list, &p_adapter->entry ); + } + + p_adapter->packet_filter = *(uint32_t*)info_buf; + } + cl_obj_unlock( &p_adapter->obj ); + KeReleaseInStackQueuedSpinLock( &hdl ); + } + break; + + case OID_GEN_CURRENT_LOOKAHEAD: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_CURRENT_LOOKAHEAD\n", port_num)); + if( info_buf_len < buf_len ) + status = NDIS_STATUS_INVALID_LENGTH; + break; + + case OID_GEN_PROTOCOL_OPTIONS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_PROTOCOL_OPTIONS\n", port_num)); + if( info_buf_len < buf_len ) + status = NDIS_STATUS_INVALID_LENGTH; + break; + + case OID_GEN_NETWORK_LAYER_ADDRESSES: + status = __ipoib_set_net_addr( p_adapter, info_buf, info_buf_len, p_bytes_read, p_bytes_needed); + break; + +#ifdef NDIS51_MINIPORT + case OID_GEN_MACHINE_NAME: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_MACHINE_NAME\n", port_num) ); + break; +#endif + + /* Required Ethernet operational characteristics */ + case OID_802_3_MULTICAST_LIST: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d received set for OID_802_3_MULTICAST_LIST\n", port_num) ); + if( info_buf_len > MAX_MCAST * sizeof(mac_addr_t) ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_802_3_MULTICAST_LIST - Multicast list full.\n", port_num) ); + status = NDIS_STATUS_MULTICAST_FULL; + *p_bytes_needed = MAX_MCAST * sizeof(mac_addr_t); + } + else if( info_buf_len % sizeof(mac_addr_t) ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_802_3_MULTICAST_LIST - Invalid input buffer.\n", port_num) ); + status = NDIS_STATUS_INVALID_DATA; + } + else if( !info_buf && info_buf_len ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_802_3_MULTICAST_LIST - Invalid input buffer.\n", port_num) ); + status = NDIS_STATUS_INVALID_DATA; + } + else + { + ipoib_refresh_mcast( p_adapter, (mac_addr_t*)info_buf, + (uint8_t)(info_buf_len / sizeof(mac_addr_t)) ); + + buf_len = info_buf_len; + /* + * Note that we don't return pending. It will likely take longer + * for our SA transactions to complete than NDIS will give us + * before reseting the adapter. If an SA failure is encountered, + * the adapter will be marked as hung and we will get reset. + */ + status = NDIS_STATUS_SUCCESS; + } + break; + + case OID_TCP_TASK_OFFLOAD: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_TCP_TASK_OFFLOAD\n", port_num) ); + + buf_len = info_buf_len; + status = + __ipoib_set_tcp_task_offload( p_adapter, info_buf, &buf_len ); + break; + + /* Optional General */ + case OID_GEN_TRANSPORT_HEADER_OFFSET: +#ifdef NDIS51_MINIPORT + case OID_GEN_RNDIS_CONFIG_PARAMETER: + case OID_GEN_VLAN_ID: +#endif + status = NDIS_STATUS_NOT_SUPPORTED; + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received an unsupported oid of 0x%.8X!\n", port_num, oid)); + break; + + case OID_GEN_SUPPORTED_LIST: + case OID_GEN_HARDWARE_STATUS: + case OID_GEN_MEDIA_SUPPORTED: + case OID_GEN_MEDIA_IN_USE: + case OID_GEN_MAXIMUM_FRAME_SIZE: + case OID_GEN_LINK_SPEED: + case OID_GEN_TRANSMIT_BUFFER_SPACE: + case OID_GEN_RECEIVE_BUFFER_SPACE: + case OID_GEN_MAXIMUM_LOOKAHEAD: + case OID_GEN_TRANSMIT_BLOCK_SIZE: + case OID_GEN_RECEIVE_BLOCK_SIZE: + case OID_GEN_MAXIMUM_TOTAL_SIZE: + case OID_GEN_VENDOR_ID: + case OID_GEN_VENDOR_DESCRIPTION: + case OID_GEN_VENDOR_DRIVER_VERSION: + case OID_GEN_DRIVER_VERSION: + case OID_GEN_MAC_OPTIONS: + case OID_GEN_MEDIA_CONNECT_STATUS: + case OID_GEN_MAXIMUM_SEND_PACKETS: + case OID_GEN_SUPPORTED_GUIDS: + case OID_GEN_PHYSICAL_MEDIUM: + default: + status = NDIS_STATUS_INVALID_OID; + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received an invalid oid of 0x%.8X!\n", port_num, oid)); + break; + } + + if( status == NDIS_STATUS_SUCCESS ) + { + *p_bytes_read = buf_len; + } + else + { + if( status == NDIS_STATUS_INVALID_LENGTH ) + { + if ( !*p_bytes_needed ) + { + *p_bytes_needed = buf_len; + } + } + + *p_bytes_read = 0; + } + + IPOIB_EXIT( IPOIB_DBG_OID ); + return status; +} + +#ifdef NNN +NDIS_STATUS +ipoib_set_info( + ipoib_adapter_t* p_adapter, + IN PNDIS_OID_REQUEST pNdisRequest) +{ + NDIS_STATUS status; + NDIS_OID oid; + UINT info_buf_len; + UINT buf_len; + uint8_t port_num; + PVOID info_buf; + UINT *p_bytes_needed; + KLOCK_QUEUE_HANDLE hdl; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + oid = pNdisRequest->DATA.SET_INFORMATION.Oid; + info_buf = pNdisRequest->DATA.QUERY_INFORMATION.InformationBuffer; + info_buf_len = pNdisRequest->DATA.QUERY_INFORMATION.InformationBufferLength; + p_bytes_needed = &pNdisRequest->DATA.QUERY_INFORMATION.BytesNeeded; + status = NDIS_STATUS_SUCCESS; + + buf_len = sizeof(UINT); + port_num = p_adapter->guids.port_num; + + switch( oid ) + { + /* Required General */ + case OID_GEN_CURRENT_PACKET_FILTER: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_CURRENT_PACKET_FILTER\n", port_num)); + if( info_buf_len < sizeof(p_adapter->packet_filter) ) + { + status = NDIS_STATUS_INVALID_LENGTH; + } + else if( !info_buf ) + { + status = NDIS_STATUS_INVALID_DATA; + } + else + { + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + cl_obj_lock( &p_adapter->obj ); + switch( p_adapter->state ) + { + case IB_PNP_PORT_ADD: + p_adapter->p_oid_request = pNdisRequest; + status = NDIS_STATUS_PENDING; + break; + + case IB_PNP_PORT_REMOVE: + status = NDIS_STATUS_NOT_ACCEPTED; + break; + + default: + if( !p_adapter->packet_filter && (*(uint32_t*)info_buf) ) + { + cl_qlist_insert_tail( + &g_ipoib.adapter_list, &p_adapter->entry ); + + /* + * Filter was zero, now non-zero. Register IP addresses + * with SA. + */ + ipoib_reg_addrs( p_adapter ); + } + else if( p_adapter->packet_filter && !(*(uint32_t*)info_buf) ) + { + /* + * Filter was non-zero, now zero. Deregister IP addresses. + */ + ipoib_dereg_addrs( p_adapter ); + + ASSERT( cl_qlist_count( &g_ipoib.adapter_list ) ); + cl_qlist_remove_item( + &g_ipoib.adapter_list, &p_adapter->entry ); + } + + p_adapter->packet_filter = *(uint32_t*)info_buf; + } + cl_obj_unlock( &p_adapter->obj ); + KeReleaseInStackQueuedSpinLock( &hdl ); + } + break; + + case OID_GEN_CURRENT_LOOKAHEAD: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_CURRENT_LOOKAHEAD\n", port_num)); + if( info_buf_len < buf_len ) + status = NDIS_STATUS_INVALID_LENGTH; + break; + + case OID_GEN_PROTOCOL_OPTIONS: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_PROTOCOL_OPTIONS\n", port_num)); + if( info_buf_len < buf_len ) + status = NDIS_STATUS_INVALID_LENGTH; + break; + + case OID_GEN_NETWORK_LAYER_ADDRESSES: + status = __ipoib_set_net_addr( p_adapter, pNdisRequest); + break; + + case OID_GEN_MACHINE_NAME: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_MACHINE_NAME\n", port_num) ); + break; + + + /* Required Ethernet operational characteristics */ + case OID_802_3_MULTICAST_LIST: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d received set for OID_802_3_MULTICAST_LIST\n", port_num) ); + if( info_buf_len > MAX_MCAST * sizeof(mac_addr_t) ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_802_3_MULTICAST_LIST - Multicast list full.\n", port_num) ); + status = NDIS_STATUS_MULTICAST_FULL; + *p_bytes_needed = MAX_MCAST * sizeof(mac_addr_t); + } + else if( info_buf_len % sizeof(mac_addr_t) ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_802_3_MULTICAST_LIST - Invalid input buffer.\n", port_num) ); + status = NDIS_STATUS_INVALID_DATA; + } + else if( !info_buf && info_buf_len ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_802_3_MULTICAST_LIST - Invalid input buffer.\n", port_num) ); + status = NDIS_STATUS_INVALID_DATA; + } + else + { + ipoib_refresh_mcast( p_adapter, (mac_addr_t*)info_buf, + (uint8_t)(info_buf_len / sizeof(mac_addr_t)) ); + + buf_len = info_buf_len; + /* + * Note that we don't return pending. It will likely take longer + * for our SA transactions to complete than NDIS will give us + * before reseting the adapter. If an SA failure is encountered, + * the adapter will be marked as hung and we will get reset. + */ + status = NDIS_STATUS_SUCCESS; + } + break; + + case OID_TCP_TASK_OFFLOAD: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_TCP_TASK_OFFLOAD\n", port_num) ); + + buf_len = info_buf_len; + status = + __ipoib_set_tcp_task_offload( p_adapter, pNdisRequest ); + break; + + /* Optional General */ + case OID_GEN_TRANSPORT_HEADER_OFFSET: +#ifdef NDIS51_MINIPORT + case OID_GEN_RNDIS_CONFIG_PARAMETER: + case OID_GEN_VLAN_ID: +#endif + status = NDIS_STATUS_NOT_SUPPORTED; + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received an unsupported oid of 0x%.8X!\n", port_num, oid)); + break; + + case OID_GEN_SUPPORTED_LIST: + case OID_GEN_HARDWARE_STATUS: + case OID_GEN_MEDIA_SUPPORTED: + case OID_GEN_MEDIA_IN_USE: + case OID_GEN_MAXIMUM_FRAME_SIZE: + case OID_GEN_LINK_SPEED: + case OID_GEN_TRANSMIT_BUFFER_SPACE: + case OID_GEN_RECEIVE_BUFFER_SPACE: + case OID_GEN_MAXIMUM_LOOKAHEAD: + case OID_GEN_TRANSMIT_BLOCK_SIZE: + case OID_GEN_RECEIVE_BLOCK_SIZE: + case OID_GEN_MAXIMUM_TOTAL_SIZE: + case OID_GEN_VENDOR_ID: + case OID_GEN_VENDOR_DESCRIPTION: + case OID_GEN_VENDOR_DRIVER_VERSION: + case OID_GEN_DRIVER_VERSION: + case OID_GEN_MAC_OPTIONS: + case OID_GEN_MEDIA_CONNECT_STATUS: + case OID_GEN_MAXIMUM_SEND_PACKETS: + case OID_GEN_SUPPORTED_GUIDS: + case OID_GEN_PHYSICAL_MEDIUM: + default: + status = NDIS_STATUS_INVALID_OID; + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received an invalid oid of 0x%.8X!\n", port_num, oid)); + break; + } + + if( status == NDIS_STATUS_SUCCESS ) + { + pNdisRequest->DATA.SET_INFORMATION.BytesRead = buf_len; + } + else + { + if( status == NDIS_STATUS_INVALID_LENGTH ) + { + if ( !*p_bytes_needed ) + { + *p_bytes_needed = buf_len; + } + } + + pNdisRequest->DATA.SET_INFORMATION.BytesRead = 0; + } + + IPOIB_EXIT( IPOIB_DBG_OID ); + return status; +} +#endif +static NDIS_STATUS +ipoib_oid_handler( + IN NDIS_HANDLE adapter_context, + IN PNDIS_OID_REQUEST pNdisRequest) +{ + NDIS_REQUEST_TYPE RequestType; + NDIS_STATUS status = NDIS_STATUS_SUCCESS; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + RequestType = pNdisRequest->RequestType; + + switch(RequestType) + { + case NdisRequestSetInformation: + status = ipoib_set_info(adapter_context, + pNdisRequest->DATA.SET_INFORMATION.Oid, + pNdisRequest->DATA.SET_INFORMATION.InformationBuffer, + pNdisRequest->DATA.SET_INFORMATION.InformationBufferLength, + (PULONG)&pNdisRequest->DATA.SET_INFORMATION.BytesRead, + (PULONG)&pNdisRequest->DATA.SET_INFORMATION.BytesNeeded); + break; + + case NdisRequestQueryInformation: + case NdisRequestQueryStatistics: + status = ipoib_query_info(adapter_context, + pNdisRequest->DATA.QUERY_INFORMATION.Oid, + pNdisRequest->DATA.QUERY_INFORMATION.InformationBuffer, + pNdisRequest->DATA.QUERY_INFORMATION.InformationBufferLength, + (PULONG)&pNdisRequest->DATA.QUERY_INFORMATION.BytesWritten, + (PULONG)&pNdisRequest->DATA.QUERY_INFORMATION.BytesNeeded); + + break; + + default: + status = NDIS_STATUS_NOT_SUPPORTED; + break; + } + IPOIB_EXIT( IPOIB_DBG_OID ); + return status; +} + +//! Transfers some number of packets, specified as an array of packet pointers, over the network. +/* For a deserialized driver, these packets are completed asynchronously +using NdisMSendComplete. +IRQL <= DISPATCH_LEVEL + +@param adapter_context Pointer to ipoib_adapter_t structure with per NIC state +@param packet_array Array of packets to send +@param numPackets Number of packets in the array +*/ +void +ipoib_send_net_buffer_list( + IN NDIS_HANDLE adapter_context, + IN PNET_BUFFER_LIST net_buffer_list, + IN NDIS_PORT_NUMBER port_num, + IN ULONG send_flags + ) +{ + ipoib_adapter_t *p_adapter; + ipoib_port_t *p_port; + ULONG send_complete_flags; + PNET_BUFFER_LIST curr_net_buffer_list; + PNET_BUFFER_LIST next_net_buffer_list; + NDIS_STATUS status = NDIS_STATUS_SUCCESS; + + UNREFERENCED_PARAMETER(port_num); + PERF_DECLARE( SendPackets ); + PERF_DECLARE( PortSend ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + cl_perf_start( SendPackets ); + + CL_ASSERT( adapter_context ); + p_adapter = (ipoib_adapter_t*)adapter_context; + p_port = p_adapter->p_port; + + cl_obj_lock( &p_adapter->obj ); + if( p_adapter->ipoib_state == IPOIB_PAUSING || + p_adapter->ipoib_state == IPOIB_PAUSED) + { + status = NDIS_STATUS_PAUSED; + cl_obj_unlock( &p_adapter->obj ); + goto compl_status; + } + + if( p_adapter->state != IB_PNP_PORT_ACTIVE || !p_adapter->p_port ) + { + cl_obj_unlock( &p_adapter->obj ); + status = NDIS_STATUS_FAILURE; + goto compl_status; + } + + p_port = p_adapter->p_port; + ipoib_port_ref( p_port, ref_send_packets ); + cl_obj_unlock( &p_adapter->obj ); + //IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + // ("Starting NET BUFFER LIST \n") ); + for (curr_net_buffer_list = net_buffer_list; + curr_net_buffer_list != NULL; + curr_net_buffer_list = next_net_buffer_list) + { + next_net_buffer_list = NET_BUFFER_LIST_NEXT_NBL(curr_net_buffer_list); + cl_perf_start( PortSend ); + + ipoib_port_send( p_port, curr_net_buffer_list, send_flags); + cl_perf_stop( &adapter->perf, PortSend ); + } + ipoib_port_deref( p_port, ref_send_packets ); + + cl_perf_stop( &p_adapter->perf, SendPackets ); + + cl_perf_log( &p_adapter->perf, SendBundle, num_packets ); + +compl_status: + if (status != NDIS_STATUS_SUCCESS) + { + //ASSERT(FALSE); //???? + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Got bad status \n") ); + send_complete_flags = 0; + + for (curr_net_buffer_list = net_buffer_list; + curr_net_buffer_list != NULL; + curr_net_buffer_list = next_net_buffer_list) + { + next_net_buffer_list = NET_BUFFER_LIST_NEXT_NBL(curr_net_buffer_list); + NET_BUFFER_LIST_STATUS(curr_net_buffer_list) = status; + ipoib_inc_send_stat( p_adapter, IP_STAT_DROPPED, 0 ); + } + + + if (NDIS_TEST_SEND_AT_DISPATCH_LEVEL(send_flags)) + { + NDIS_SET_SEND_COMPLETE_FLAG(send_complete_flags, NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL); + } + + NdisMSendNetBufferListsComplete( + p_adapter->h_adapter, + net_buffer_list, + send_complete_flags); + } + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + +void +ipoib_pnp_notify( + IN NDIS_HANDLE adapter_context, + IN PNET_DEVICE_PNP_EVENT pnp_event) +{ + ipoib_adapter_t *p_adapter; + + IPOIB_ENTER( IPOIB_DBG_PNP ); + + p_adapter = (ipoib_adapter_t*)adapter_context; + + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_PNP, ("Event %d\n", pnp_event->DevicePnPEvent) ); + if( pnp_event->DevicePnPEvent != NdisDevicePnPEventPowerProfileChanged ) + { + cl_obj_lock( &p_adapter->obj ); + p_adapter->state = IB_PNP_PORT_REMOVE; + cl_obj_unlock( &p_adapter->obj ); + + ipoib_resume_oids( p_adapter ); + } + + IPOIB_EXIT( IPOIB_DBG_PNP ); +} + + +VOID +ipoib_shutdown_ex( + IN NDIS_HANDLE adapter_context, + IN NDIS_SHUTDOWN_ACTION shutdown_action) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + UNUSED_PARAM( adapter_context ); + UNUSED_PARAM( shutdown_action ); + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +void +ipoib_resume_oids( + IN ipoib_adapter_t* const p_adapter ) +{ + ULONG info; + NDIS_STATUS status; + boolean_t pending_query, pending_set; + pending_oid_t query_oid = {0}; + pending_oid_t set_oid = {0}; + KLOCK_QUEUE_HANDLE hdl; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + cl_obj_lock( &p_adapter->obj ); + /* + * Set the status depending on our state. Fail OID requests that + * are pending while we reset the adapter. + */ + switch( p_adapter->state ) + { + case IB_PNP_PORT_ADD: + status = NDIS_STATUS_FAILURE; + break; + + case IB_PNP_PORT_REMOVE: + status = NDIS_STATUS_NOT_ACCEPTED; + break; + + default: + status = NDIS_STATUS_SUCCESS; + } + + pending_query = p_adapter->pending_query; + if( pending_query ) + { + query_oid = p_adapter->query_oid; + p_adapter->pending_query = FALSE; + } + pending_set = p_adapter->pending_set; + if( pending_set ) + { + set_oid = p_adapter->set_oid; + p_adapter->pending_set = FALSE; + } + cl_obj_unlock( &p_adapter->obj ); + + /* + * If we had a pending OID request for OID_GEN_LINK_SPEED, + * complete it now. Note that we hold the object lock since + * NdisMQueryInformationComplete is called at DISPATCH_LEVEL. + */ + if( pending_query ) + { + switch( query_oid.oid ) + { + case OID_GEN_LINK_SPEED: + ipoib_complete_query( p_adapter, &query_oid, + status, &p_adapter->port_rate, sizeof(p_adapter->port_rate) ); + break; + + case OID_GEN_MEDIA_CONNECT_STATUS: + info = NdisMediaStateConnected; + ipoib_complete_query( p_adapter, &query_oid, + status, &info, sizeof(info) ); + break; + + default: + CL_ASSERT( query_oid.oid == OID_GEN_LINK_SPEED || + query_oid.oid == OID_GEN_MEDIA_CONNECT_STATUS ); + break; + } + } + + if( pending_set ) + { + switch( set_oid.oid ) + { + case OID_GEN_CURRENT_PACKET_FILTER: + /* Validation already performed in the SetInformation path. */ + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + cl_obj_lock( &p_adapter->obj ); + if( !p_adapter->packet_filter && (*(PULONG)set_oid.p_buf) ) + { + cl_qlist_insert_tail( + &g_ipoib.adapter_list, &p_adapter->entry ); + /* + * Filter was zero, now non-zero. Register IP addresses + * with SA. + */ + ipoib_reg_addrs( p_adapter ); + } + else if( p_adapter->packet_filter && !(*(PULONG)set_oid.p_buf) ) + { + /* Filter was non-zero, now zero. Deregister IP addresses. */ + ipoib_dereg_addrs( p_adapter ); + + ASSERT( cl_qlist_count( &g_ipoib.adapter_list ) ); + cl_qlist_remove_item( + &g_ipoib.adapter_list, &p_adapter->entry ); + } + p_adapter->packet_filter = *(PULONG)set_oid.p_buf; + + cl_obj_unlock( &p_adapter->obj ); + KeReleaseInStackQueuedSpinLock( &hdl ); + p_adapter->set_oid.p_pending_oid = NULL; + NdisMOidRequestComplete( p_adapter->h_adapter, set_oid.p_pending_oid, status ); + break; + + case OID_GEN_NETWORK_LAYER_ADDRESSES: + status = __ipoib_set_net_addr( p_adapter, + p_adapter->set_oid.p_buf, + p_adapter->set_oid.buf_len, + p_adapter->set_oid.p_bytes_used, + p_adapter->set_oid.p_bytes_needed ); + + if( status != NDIS_STATUS_PENDING ) + { + p_adapter->set_oid.p_pending_oid = NULL; + NdisMOidRequestComplete( p_adapter->h_adapter, set_oid.p_pending_oid, status ); + } + break; + + default: + CL_ASSERT( set_oid.oid && 0 ); + break; + } + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static NDIS_STATUS +__ipoib_set_net_addr( + IN ipoib_adapter_t * p_adapter, + IN PVOID info_buf, + IN ULONG info_buf_len, + OUT PULONG p_bytes_read, + OUT PULONG p_bytes_needed ) +{ + NDIS_STATUS status; + PNETWORK_ADDRESS_LIST p_net_addrs; + PNETWORK_ADDRESS p_net_addr_oid; + PNETWORK_ADDRESS_IP p_ip_addr; + + net_address_item_t *p_addr_item; + + cl_status_t cl_status; + + size_t idx; + LONG i; + ULONG addr_size; + ULONG total_size; + + uint8_t port_num; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + status = NDIS_STATUS_SUCCESS; + port_num = p_adapter->guids.port_num; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d received set for OID_GEN_NETWORK_LAYER_ADDRESSES\n", + port_num) ); + + if( !info_buf ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Port %d - OID_GEN_NETWORK_LAYER_ADDRESSES - " + "NULL buffer\n", port_num) ); + IPOIB_EXIT( IPOIB_DBG_OID ); + return NDIS_STATUS_INVALID_DATA; + } + + /* + * Must use field offset because the structures define array's of size one + * of a the incorrect type for what is really stored. + */ + if( info_buf_len < FIELD_OFFSET(NETWORK_ADDRESS_LIST, Address) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - " + "bad length of %d, not enough " + "for NETWORK_ADDRESS_LIST (%d)\n", port_num, info_buf_len, + FIELD_OFFSET(NETWORK_ADDRESS_LIST, Address)) ); + *p_bytes_needed = FIELD_OFFSET(NETWORK_ADDRESS_LIST, Address); + IPOIB_EXIT( IPOIB_DBG_OID ); + return NDIS_STATUS_INVALID_LENGTH; + } + + p_net_addrs = (PNETWORK_ADDRESS_LIST)info_buf; + if( p_net_addrs->AddressCount == 0) + { + if( p_net_addrs->AddressType == NDIS_PROTOCOL_ID_TCP_IP ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - " + "clear TCP/IP addresses\n", port_num) ); + } + else + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - " + "Non TCP/IP address type of 0x%.4X on clear\n", + port_num, p_net_addrs->AddressType) ); + IPOIB_EXIT( IPOIB_DBG_OID ); + return NDIS_STATUS_SUCCESS; + } + } + + addr_size = FIELD_OFFSET(NETWORK_ADDRESS, Address) + + NETWORK_ADDRESS_LENGTH_IP; + total_size = FIELD_OFFSET(NETWORK_ADDRESS_LIST, Address) + + addr_size * p_net_addrs->AddressCount; + + if( info_buf_len < total_size ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - " + "bad length of %d, %d required for %d addresses\n", + port_num, info_buf_len, total_size, p_net_addrs->AddressCount) ); + *p_bytes_needed = total_size; + IPOIB_EXIT( IPOIB_DBG_OID ); + return NDIS_STATUS_INVALID_LENGTH; + } + + /* Lock lists for duration since SA callbacks can occur on other CPUs */ + cl_obj_lock( &p_adapter->obj ); + + /* Set the capacity of the vector to accomodate all assinged addresses. */ + cl_status = cl_vector_set_capacity( + &p_adapter->ip_vector, p_net_addrs->AddressCount ); + if( cl_status != CL_SUCCESS ) + { + cl_obj_unlock( &p_adapter->obj ); + IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Port %d - OID_GEN_NETWORK_LAYER_ADDRESSES - " + "Failed to set IP vector capacity: %#x\n", port_num, + cl_status) ); + IPOIB_EXIT( IPOIB_DBG_OID ); + return NDIS_STATUS_RESOURCES; + } + + *p_bytes_read = total_size; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - List contains %d addresses\n", + port_num, p_net_addrs->AddressCount)); + + /* First look for addresses we had that should be removed */ + for( idx = 0; idx != cl_vector_get_size( &p_adapter->ip_vector ); idx++ ) + { + p_addr_item = (net_address_item_t*) + cl_vector_get_ptr( &p_adapter->ip_vector, idx ); + p_net_addr_oid = (PNETWORK_ADDRESS)p_net_addrs->Address; + + for( i = 0; i < p_net_addrs->AddressCount; ++i, p_net_addr_oid = + (PNETWORK_ADDRESS)((uint8_t *)p_net_addr_oid + + FIELD_OFFSET(NETWORK_ADDRESS, Address) + + p_net_addr_oid->AddressLength) ) + { + + if( p_net_addr_oid->AddressType != NDIS_PROTOCOL_ID_TCP_IP ) + { + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Address %d is wrong type of 0x%.4X, " + "should be 0x%.4X\n", port_num, i, p_net_addr_oid->AddressType, + NDIS_PROTOCOL_ID_TCP_IP)); + continue; + } + + if( p_net_addr_oid->AddressLength != NETWORK_ADDRESS_LENGTH_IP) + { + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Address %d is wrong size of %d, " + "should be %d\n", port_num, i, p_net_addr_oid->AddressLength, + NETWORK_ADDRESS_LENGTH_IP)); + continue; + } + + p_ip_addr = (PNETWORK_ADDRESS_IP)p_net_addr_oid->Address; + if( !cl_memcmp( &p_ip_addr->in_addr, + &p_addr_item->address.as_ulong, sizeof(ULONG) ) ) + { + break; + } + } + + if( i == p_net_addrs->AddressCount ) + { + /* Didn't find a match, delete from SA */ + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Deleting Address %d.%d.%d.%d\n", + port_num, + p_addr_item->address.as_bytes[0], + p_addr_item->address.as_bytes[1], + p_addr_item->address.as_bytes[2], + p_addr_item->address.as_bytes[3])); + + if( p_addr_item->p_reg ) + { + if( p_addr_item->p_reg->h_reg_svc ) + { + p_adapter->p_ifc->dereg_svc( + p_addr_item->p_reg->h_reg_svc, __ipoib_ats_dereg_cb ); + } + else + { + cl_free( p_addr_item->p_reg ); + } + p_addr_item->p_reg = NULL; + } + p_addr_item->address.as_ulong = 0; + } + } + + /* Now look for new addresses */ + p_net_addr_oid = (NETWORK_ADDRESS *)p_net_addrs->Address; + idx = 0; + for( i = 0; i < p_net_addrs->AddressCount; i++, p_net_addr_oid = + (PNETWORK_ADDRESS)((uint8_t *)p_net_addr_oid + + FIELD_OFFSET(NETWORK_ADDRESS, Address) + p_net_addr_oid->AddressLength) ) + { + + if( p_net_addr_oid->AddressType != NDIS_PROTOCOL_ID_TCP_IP ) + { + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Address %d is wrong type of 0x%.4X, " + "should be 0x%.4X\n", port_num, i, p_net_addr_oid->AddressType, + NDIS_PROTOCOL_ID_TCP_IP)); + continue; + } + + if( p_net_addr_oid->AddressLength != NETWORK_ADDRESS_LENGTH_IP) + { + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Address %d is wrong size of %d, " + "should be %d\n", port_num, i, p_net_addr_oid->AddressLength, + NETWORK_ADDRESS_LENGTH_IP)); + continue; + } + + p_ip_addr = (PNETWORK_ADDRESS_IP)p_net_addr_oid->Address; + + /* Size the vector as needed. */ + if( cl_vector_get_size( &p_adapter->ip_vector ) <= idx ) + cl_vector_set_size( &p_adapter->ip_vector, idx + 1 ); + + p_addr_item = cl_vector_get_ptr( &p_adapter->ip_vector, idx ); + if( !cl_memcmp( &p_ip_addr->in_addr, &p_addr_item->address.as_ulong, + sizeof(ULONG) ) ) + { + idx++; + /* Already have this address - no change needed */ + continue; + } + + /* + * Copy the address information, but don't register yet - the port + * could be down. + */ + if( p_addr_item->p_reg ) + { + /* If in use by some other address, deregister. */ + if( p_addr_item->p_reg->h_reg_svc ) + { + p_adapter->p_ifc->dereg_svc( + p_addr_item->p_reg->h_reg_svc, __ipoib_ats_dereg_cb ); + } + else + { + cl_free( p_addr_item->p_reg ); + } + p_addr_item->p_reg = NULL; + } + memcpy ((void *)&p_addr_item->address.as_ulong, (const void *)&p_ip_addr->in_addr, sizeof(ULONG) ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Adding Address %d.%d.%d.%d\n", + port_num, + p_addr_item->address.as_bytes[0], + p_addr_item->address.as_bytes[1], + p_addr_item->address.as_bytes[2], + p_addr_item->address.as_bytes[3]) ); + idx++; + } + + /* Now clear any extra entries that shouldn't be there. */ + while( idx < cl_vector_get_size( &p_adapter->ip_vector ) ) + { + p_addr_item = (net_address_item_t*) + cl_vector_get_ptr( &p_adapter->ip_vector, + cl_vector_get_size( &p_adapter->ip_vector ) - 1 ); + + if( p_addr_item->p_reg ) + { + if( p_addr_item->p_reg->h_reg_svc ) + { + p_adapter->p_ifc->dereg_svc( + p_addr_item->p_reg->h_reg_svc, __ipoib_ats_dereg_cb ); + } + else + { + cl_free( p_addr_item->p_reg ); + } + p_addr_item->p_reg = NULL; + p_addr_item->address.as_ulong = 0; + } + + /* No need to check return value - shrinking always succeeds. */ + cl_vector_set_size( &p_adapter->ip_vector, + cl_vector_get_size( &p_adapter->ip_vector ) - 1 ); + } + + if( p_adapter->state == IB_PNP_PORT_ACTIVE && p_adapter->packet_filter ) + ipoib_reg_addrs( p_adapter ); + + cl_obj_unlock( &p_adapter->obj ); + + IPOIB_EXIT( IPOIB_DBG_OID ); + return NDIS_STATUS_SUCCESS; +} + + +/* Object lock is held when this function is called. */ +void +ipoib_reg_addrs( + IN ipoib_adapter_t* const p_adapter ) +{ + net_address_item_t *p_addr_item; + + size_t idx; + + uint8_t port_num; + + ib_api_status_t ib_status; + ib_reg_svc_req_t ib_service; + ib_gid_t port_gid; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + if(p_adapter->guids.port_guid.pkey != IB_DEFAULT_PKEY) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR,IPOIB_DBG_ERROR, + ("ATS Service available for default pkey only\n")); + return; + } + port_num = p_adapter->guids.port_num; + + /* Setup our service call with things common to all calls */ + cl_memset( &ib_service, 0, sizeof(ib_service) ); + + /* BUGBUG Only register local subnet GID prefix for now */ + ib_gid_set_default( &port_gid, p_adapter->guids.port_guid.guid ); + ib_service.svc_rec.service_gid = port_gid; + + ib_service.svc_rec.service_pkey = IB_DEFAULT_PKEY; + ib_service.svc_rec.service_lease = IB_INFINITE_SERVICE_LEASE; + + /* Must cast here because the service name is an array of unsigned chars but + * strcpy want a pointer to a signed char */ + if ( StringCchCopy( (char *)ib_service.svc_rec.service_name, + sizeof(ib_service.svc_rec.service_name) / sizeof(char), ATS_NAME ) != S_OK) { + ASSERT(FALSE); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR,IPOIB_DBG_ERROR, + ("Problem copying ATS name: exiting\n")); + return; + } + + /* IP Address in question will be put in below */ + ib_service.port_guid = p_adapter->guids.port_guid.guid; + ib_service.timeout_ms = p_adapter->params.sa_timeout; + ib_service.retry_cnt = p_adapter->params.sa_retry_cnt; + + /* Can't set IB_FLAGS_SYNC here because I can't wait at dispatch */ + ib_service.flags = 0; + + /* Service context will be put in below */ + + ib_service.svc_data_mask = IB_SR_COMPMASK_SID | + IB_SR_COMPMASK_SGID | + IB_SR_COMPMASK_SPKEY | + IB_SR_COMPMASK_SLEASE | + IB_SR_COMPMASK_SNAME | + IB_SR_COMPMASK_SDATA8_12 | + IB_SR_COMPMASK_SDATA8_13 | + IB_SR_COMPMASK_SDATA8_14 | + IB_SR_COMPMASK_SDATA8_15; + ib_service.pfn_reg_svc_cb = __ipoib_ats_reg_cb; + + for( idx = 0; idx < cl_vector_get_size( &p_adapter->ip_vector); idx++ ) + { + p_addr_item = (net_address_item_t*) + cl_vector_get_ptr( &p_adapter->ip_vector, idx ); + + if( p_addr_item->p_reg ) + continue; + + p_addr_item->p_reg = cl_zalloc( sizeof(ats_reg_t) ); + if( !p_addr_item->p_reg ) + break; + + p_addr_item->p_reg->p_adapter = p_adapter; + + ib_service.svc_context = p_addr_item->p_reg; + + ib_service.svc_rec.service_id = + ATS_SERVICE_ID & CL_HTON64(0xFFFFFFFFFFFFFF00); + /* ATS service IDs start at 0x10000CE100415453 */ + ib_service.svc_rec.service_id |= ((uint64_t)(idx + 0x53)) << 56; + + cl_memcpy( &ib_service.svc_rec.service_data8[ATS_IPV4_OFFSET], + p_addr_item->address.as_bytes, IPV4_ADDR_SIZE ); + + /* Take a reference for each service request. */ + cl_obj_ref(&p_adapter->obj); + ib_status = p_adapter->p_ifc->reg_svc( + p_adapter->h_al, &ib_service, &p_addr_item->p_reg->h_reg_svc ); + if( ib_status != IB_SUCCESS ) + { + if( ib_status == IB_INVALID_GUID ) + { + /* If this occurs, we log the error but do not fail the OID yet */ + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - " + "Failed to register IP Address " + "of %d.%d.%d.%d with error IB_INVALID_GUID\n", + port_num, + p_addr_item->address.as_bytes[0], + p_addr_item->address.as_bytes[1], + p_addr_item->address.as_bytes[2], + p_addr_item->address.as_bytes[3]) ); + } + else + { + /* Fatal error. */ + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Failed to register IP Address " + "of %d.%d.%d.%d with error %s\n", + port_num, + p_addr_item->address.as_bytes[0], + p_addr_item->address.as_bytes[1], + p_addr_item->address.as_bytes[2], + p_addr_item->address.as_bytes[3], + p_adapter->p_ifc->get_err_str( ib_status )) ); + p_adapter->hung = TRUE; + } + cl_obj_deref(&p_adapter->obj); + cl_free( p_addr_item->p_reg ); + p_addr_item->p_reg = NULL; + } + } + + IPOIB_EXIT( IPOIB_DBG_OID ); +} + + +/* Object lock is held when this function is called. */ +void +ipoib_dereg_addrs( + IN ipoib_adapter_t* const p_adapter ) +{ + net_address_item_t *p_addr_item; + + size_t idx; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + for( idx = 0; idx < cl_vector_get_size( &p_adapter->ip_vector); idx++ ) + { + p_addr_item = (net_address_item_t*) + cl_vector_get_ptr( &p_adapter->ip_vector, idx ); + + if( !p_addr_item->p_reg ) + continue; + + if( p_addr_item->p_reg->h_reg_svc ) + { + p_adapter->p_ifc->dereg_svc( + p_addr_item->p_reg->h_reg_svc, __ipoib_ats_dereg_cb ); + } + else + { + cl_free( p_addr_item->p_reg ); + } + p_addr_item->p_reg = NULL; + } + + IPOIB_EXIT( IPOIB_DBG_OID ); +} + + +void +ipoib_cancel_xmit( + IN NDIS_HANDLE adapter_context, + IN PVOID cancel_id ) +{ +/* ipoib_adapter_t* const p_adapter = + (ipoib_adapter_t* const )adapter_context; + + +if 0 + if( p_adapter && p_adapter->p_port ) + { + ipoib_port_cancel_xmit( p_adapter->p_port, cancel_id ); + } +endif +*/ + + UNUSED_PARAM(adapter_context); + UNUSED_PARAM(cancel_id); + + return; //TODO return this functionality + +} + + +static void +__ipoib_ats_reg_cb( + IN ib_reg_svc_rec_t *p_reg_svc_rec ) +{ + ats_reg_t *p_reg; + uint8_t port_num; + + IPOIB_ENTER( IPOIB_DBG_OID ); + + CL_ASSERT( p_reg_svc_rec ); + CL_ASSERT( p_reg_svc_rec->svc_context ); + + p_reg = (ats_reg_t*)p_reg_svc_rec->svc_context; + port_num = p_reg->p_adapter->guids.port_num; + + cl_obj_lock( &p_reg->p_adapter->obj ); + + if( p_reg_svc_rec->req_status == IB_SUCCESS && + !p_reg_svc_rec->resp_status ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION,IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Registered IP Address " + "of %d.%d.%d.%d\n", + port_num, + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET], + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET+1], + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET+2], + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET+3]) ); + } + else if( p_reg_svc_rec->req_status != IB_CANCELED ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_OID, + ("Port %d OID_GEN_NETWORK_LAYER_ADDRESSES - Failed to register IP Address " + "of %d.%d.%d.%d with error %s\n", + port_num, + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET], + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET+1], + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET+2], + p_reg_svc_rec->svc_rec.service_data8[ATS_IPV4_OFFSET+3], + p_reg->p_adapter->p_ifc->get_err_str( p_reg_svc_rec->resp_status )) ); + p_reg->p_adapter->hung = TRUE; + p_reg->h_reg_svc = NULL; + } + + cl_obj_unlock( &p_reg->p_adapter->obj ); + cl_obj_deref(&p_reg->p_adapter->obj); + + IPOIB_EXIT( IPOIB_DBG_OID ); +} + + +static void +__ipoib_ats_dereg_cb( + IN void *context ) +{ + cl_free( context ); +} + +static NDIS_STATUS +ipoib_pause( + IN NDIS_HANDLE adapter_context, + IN PNDIS_MINIPORT_PAUSE_PARAMETERS pause_parameters) +{ + ipoib_adapter_t *p_adapter; + KLOCK_QUEUE_HANDLE hdl; + + UNREFERENCED_PARAMETER(pause_parameters); + IPOIB_ENTER( IPOIB_DBG_INIT ); +//return NDIS_STATUS_SUCCESS; + CL_ASSERT(adapter_context); + p_adapter = (ipoib_adapter_t*)adapter_context; + CL_ASSERT(p_adapter->ipoib_state == IPOIB_RUNNING); + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + p_adapter->ipoib_state = IPOIB_PAUSING; + KeReleaseInStackQueuedSpinLock( &hdl ); + + //TODO: + ipoib_port_resume(p_adapter->p_port,FALSE); +// ASSERT(FALSE); + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + p_adapter->ipoib_state = IPOIB_PAUSED; + KeReleaseInStackQueuedSpinLock( &hdl ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_SUCCESS; +} + +static NDIS_STATUS +ipoib_restart( + IN NDIS_HANDLE adapter_context, + IN PNDIS_MINIPORT_RESTART_PARAMETERS restart_parameters) +{ + ipoib_adapter_t *p_adapter; + KLOCK_QUEUE_HANDLE hdl; + PNDIS_RESTART_ATTRIBUTES NdisRestartAttributes; + PNDIS_RESTART_GENERAL_ATTRIBUTES NdisGeneralAttributes; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + p_adapter = (ipoib_adapter_t*)adapter_context; + + NdisRestartAttributes = restart_parameters->RestartAttributes; + + if (NdisRestartAttributes != NULL) + { + CL_ASSERT(NdisRestartAttributes->Oid == OID_GEN_MINIPORT_RESTART_ATTRIBUTES); + NdisGeneralAttributes = (PNDIS_RESTART_GENERAL_ATTRIBUTES)NdisRestartAttributes->Data; + // + // Check to see if we need to change any attributes + } + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + p_adapter->ipoib_state = IPOIB_RUNNING; + KeReleaseInStackQueuedSpinLock( &hdl ); + + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return NDIS_STATUS_SUCCESS; +} + +/*++ +Routine Description: + + This function aborts the request pending in the miniport. + +Arguments: + + MiniportAdapterContext Pointer to the adapter structure + RequestId Specify the request to be cancelled. + +Return Value: + +--*/ +static void +ipoib_cancel_oid_request( + IN NDIS_HANDLE adapter_context, + IN PVOID requestId + ) +{ + PNDIS_OID_REQUEST pending_request; + ipoib_adapter_t *p_adapter; + + IPOIB_ENTER( IPOIB_DBG_OID ); + p_adapter = (ipoib_adapter_t*)adapter_context; + + cl_obj_lock( &p_adapter->obj ); + + if ( p_adapter->query_oid.p_pending_oid && + p_adapter->query_oid.p_pending_oid->RequestId == requestId) + { + pending_request = p_adapter->query_oid.p_pending_oid; + p_adapter->query_oid.p_pending_oid = NULL; + p_adapter->pending_query = FALSE; + } + else if(p_adapter->set_oid.p_pending_oid && + p_adapter->set_oid.p_pending_oid->RequestId == requestId) + { + pending_request = p_adapter->set_oid.p_pending_oid; + p_adapter->set_oid.p_pending_oid = NULL; + p_adapter->pending_set = FALSE; + } + else + { + cl_obj_unlock( &p_adapter->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("No Pending OID found\n") ); + return; + } + cl_obj_unlock( &p_adapter->obj ); + + NdisMOidRequestComplete(p_adapter->h_adapter, + pending_request, + NDIS_STATUS_REQUEST_ABORTED); + + IPOIB_EXIT( IPOIB_DBG_OID ); +} diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_driver.h b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_driver.h new file mode 100644 index 00000000..0e3b7f28 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_driver.h @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_driver.h 4494 2009-06-22 14:31:08Z xalex $ + */ + + +#ifndef _IPOIB_DRIVER_H_ +#define _IPOIB_DRIVER_H_ + + +#include "ipoib_log.h" +#include "ipoib_adapter.h" +#include +#include +#include "ipoib_debug.h" + + +/* + * Definitions + */ +#define MAX_BUNDLE_ID_LENGTH 32 + +/* The maximum number of send packets the MiniportSendPackets function can accept */ +#define MINIPORT_MAX_SEND_PACKETS 200 + +/* MLX4 supports 4K MTU */ +#define MAX_IB_MTU 4096 +#define DEFAULT_MTU 2048 +/* + * Header length as defined by IPoIB spec: + * http://www.ietf.org/internet-drafts/draft-ietf-ipoib-ip-over-infiniband-04.txt + */ + +#define MAX_UD_PAYLOAD_MTU (MAX_IB_MTU - sizeof(ipoib_hdr_t)) +#define DEFAULT_PAYLOAD_MTU (DEFAULT_MTU - sizeof(ipoib_hdr_t)) +#define MAX_CM_PAYLOAD_MTU (65520) +#define MAX_WRS_PER_MSG ((MAX_CM_PAYLOAD_MTU/DEFAULT_PAYLOAD_MTU)+1) +/* + * Only the protocol type is sent as part of the UD payload + * since the rest of the Ethernet header is encapsulated in the + * various IB headers. We report out buffer space as if we + * transmit the ethernet headers. + */ +#define MAX_XFER_BLOCK_SIZE (sizeof(eth_hdr_t) + MAX_UD_PAYLOAD_MTU) +#define DATA_OFFSET (sizeof(eth_hdr_t) - sizeof(ipoib_hdr_t)) + +#define IPOIB_CM_FLAG_RC (0x80) +#define IPOIB_CM_FLAG_UC (0x40) +#define IPOIB_CM_FLAG_SVCID (0x10) // OFED set IETF bit this way ( open OFED PR 1121 ) + +#define MAX_SEND_SGE (8) + +/* Amount of physical memory to register. */ +#define MEM_REG_SIZE 0xFFFFFFFFFFFFFFFF + +/* Number of work completions to chain for send and receive polling. */ +#define MAX_SEND_WC 5 +#define MAX_RECV_WC 16 + +typedef struct _ipoib_globals +{ + KSPIN_LOCK lock; + cl_qlist_t adapter_list; + cl_qlist_t bundle_list; + + atomic32_t laa_idx; + + NDIS_HANDLE h_ndis_wrapper; + PDEVICE_OBJECT h_ibat_dev; + NDIS_HANDLE h_ibat_dev_handle; //MSDN: this handle is a required parameter to the + //NdisDeregisterDeviceEx function that the driver calls subsequently + volatile LONG ibat_ref; + uint32_t bypass_check_bcast_rate; + +} ipoib_globals_t; +/* +* FIELDS +* lock +* Spinlock to protect list access. +* +* adapter_list +* List of all adapter instances. Used for address translation support. +* +* bundle_list +* List of all adapter bundles. +* +* laa_idx +* Global counter for generating LAA MACs +* +* h_ibat_dev +* Device handle returned by NdisMRegisterDevice. +*********/ + +extern ipoib_globals_t g_ipoib; +extern NDIS_HANDLE g_IpoibMiniportDriverHandle; + + + +typedef struct _ipoib_bundle +{ + cl_list_item_t list_item; + char bundle_id[MAX_BUNDLE_ID_LENGTH]; + cl_qlist_t adapter_list; + +} ipoib_bundle_t; +/* +* FIELDS +* list_item +* List item for storing the bundle in a quick list. +* +* bundle_id +* Bundle identifier. +* +* adapter_list +* List of adapters in the bundle. The adapter at the head is the +* primary adapter of the bundle. +*********/ +void +ipoib_create_log( + NDIS_HANDLE h_adapter, + UINT ind, + ULONG eventLogMsgId); + +#define GUID_MASK_LOG_INDEX 0 + +void +ipoib_resume_oids( + IN ipoib_adapter_t* const p_adapter ); + +#define IPOIB_OFFSET(field) ((UINT)FIELD_OFFSET(ipoib_params_t,field)) +#define IPOIB_SIZE(field) sizeof(((ipoib_params_t*)0)->field) +#define IPOIB_INIT_NDIS_STRING(str) \ + (str)->Length = 0; \ + (str)->MaximumLength = 0; \ + (str)->Buffer = NULL; + + + +#endif /* _IPOIB_DRIVER_H_ */ diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_endpoint.c b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_endpoint.c new file mode 100644 index 00000000..1e82e5a5 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_endpoint.c @@ -0,0 +1,1170 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_endpoint.c 4226 2009-04-06 06:01:03Z xalex $ + */ + + + +#include "ipoib_endpoint.h" +#include "ipoib_port.h" +#include "ipoib_debug.h" +#if defined(EVENT_TRACING) +#ifdef offsetof +#undef offsetof +#endif +#include "ipoib_endpoint.tmh" +#endif +#include +#include + + +static void +__endpt_destroying( + IN cl_obj_t* p_obj ); + +static void +__endpt_cleanup( + IN cl_obj_t* p_obj ); + +static void +__endpt_free( + IN cl_obj_t* p_obj ); + +static ib_api_status_t +__create_mcast_av( + IN ib_pd_handle_t h_pd, + IN uint8_t port_num, + IN ib_member_rec_t* const p_member_rec, + OUT ib_av_handle_t* const ph_av ); + +static inline ipoib_port_t* +__endpt_parent( + IN ipoib_endpt_t* const p_endpt ); + +static void +__path_query_cb( + IN ib_query_rec_t *p_query_rec ); + +static void +__endpt_resolve( + IN ipoib_endpt_t* const p_endpt ); + +static void +__endpt_cm_send_cb( + IN const ib_cq_handle_t h_cq, + IN void *cq_context ); +static void +__endpt_cm_recv_cb( + IN const ib_cq_handle_t h_cq, + IN void *cq_context ); + +static void +__endpt_cm_buf_mgr_construct( + IN endpt_buf_mgr_t * const p_buf_mgr ); +static void +__conn_reply_cb( + IN ib_cm_rep_rec_t *p_cm_rep ); + +static void +__conn_mra_cb( + IN ib_cm_mra_rec_t *p_mra_rec ); + +static void +__conn_rej_cb( + IN ib_cm_rej_rec_t *p_rej_rec ); + +static void +__conn_dreq_cb( + IN ib_cm_dreq_rec_t *p_dreq_rec ); + +#if 0 //CM +static cl_status_t +__cm_recv_desc_ctor( + IN void* const p_object, + IN void* context, + OUT cl_pool_item_t** const pp_pool_item ); + +static void +__cm_recv_desc_dtor( + IN const cl_pool_item_t* const p_pool_item, + IN void *context ); + +static NDIS_PACKET* +__endpt_cm_get_ndis_pkt( + IN ipoib_port_t* const p_port, + IN ipoib_cm_desc_t* const p_desc ); + +static inline ipoib_cm_desc_t* +__endpt_cm_buf_mgr_get_recv( + IN endpt_buf_mgr_t * const p_buf_mgr ); + +static boolean_t +__cm_recv_is_dhcp( + IN const ipoib_pkt_t* const p_ipoib ); + +static ib_api_status_t +__endpt_cm_recv_arp( + IN ipoib_port_t* const p_port, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src_endpt ); + +static ib_api_status_t +__endpt_cm_recv_udp( + IN ipoib_port_t* const p_port, + IN ib_wc_t* const p_wc, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src_endpt ); +#endif + +ipoib_endpt_t* +ipoib_endpt_create( + IN const ib_gid_t* const p_dgid, + IN const net16_t dlid, + IN const net32_t qpn ) +{ + ipoib_endpt_t *p_endpt; + cl_status_t status; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + p_endpt = cl_zalloc( sizeof(ipoib_endpt_t) ); + if( !p_endpt ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate endpoint (%d bytes)\n", + sizeof(ipoib_endpt_t)) ); + return NULL; + } + + cl_obj_construct( &p_endpt->obj, IPOIB_OBJ_ENDPOINT ); + + status = cl_obj_init( &p_endpt->obj, CL_DESTROY_ASYNC, + __endpt_destroying, __endpt_cleanup, __endpt_free ); + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("Created endpoint: [ %p ] DLID: %#x QPN: %#x \n", + p_endpt, cl_ntoh16(dlid), cl_ntoh32(qpn) ) ); + + p_endpt->dgid = *p_dgid; + p_endpt->dlid = dlid; + p_endpt->qpn = qpn; + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return p_endpt; +} + + +static ib_api_status_t +__create_mcast_av( + IN ib_pd_handle_t h_pd, + IN uint8_t port_num, + IN ib_member_rec_t* const p_member_rec, + OUT ib_av_handle_t* const ph_av ) +{ + ib_av_attr_t av_attr; + uint32_t flow_lbl; + uint8_t hop_lmt; + ib_api_status_t status; + ipoib_endpt_t *p_endpt; + + IPOIB_ENTER( IPOIB_DBG_MCAST ); + + p_endpt = PARENT_STRUCT(ph_av, ipoib_endpt_t, h_av ); + + cl_memclr( &av_attr, sizeof(ib_av_attr_t) ); + av_attr.port_num = port_num; + ib_member_get_sl_flow_hop( p_member_rec->sl_flow_hop, + &av_attr.sl, &flow_lbl, &hop_lmt ); + av_attr.dlid = p_member_rec->mlid; + av_attr.grh_valid = TRUE; + av_attr.grh.hop_limit = hop_lmt; + av_attr.grh.dest_gid = p_member_rec->mgid; + av_attr.grh.src_gid = p_member_rec->port_gid; + av_attr.grh.ver_class_flow = + ib_grh_set_ver_class_flow( 6, p_member_rec->tclass, flow_lbl ); + av_attr.static_rate = p_member_rec->rate & IB_PATH_REC_BASE_MASK; + av_attr.path_bits = 0; + /* port is not attached to endpoint at this point, so use endpt ifc reference */ + status = p_endpt->p_ifc->create_av( h_pd, &av_attr, ph_av ); + + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_create_av returned %s\n", + p_endpt->p_ifc->get_err_str( status )) ); + } + + IPOIB_EXIT( IPOIB_DBG_MCAST ); + return status; +} + + +ib_api_status_t +ipoib_endpt_set_mcast( + IN ipoib_endpt_t* const p_endpt, + IN ib_pd_handle_t h_pd, + IN uint8_t port_num, + IN ib_mcast_rec_t* const p_mcast_rec ) +{ + ib_api_status_t status; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("Create av for MAC: %02X-%02X-%02X-%02X-%02X-%02X\n", + p_endpt->mac.addr[0], p_endpt->mac.addr[1], + p_endpt->mac.addr[2], p_endpt->mac.addr[3], + p_endpt->mac.addr[4], p_endpt->mac.addr[5]) ); + + status = __create_mcast_av( h_pd, port_num, p_mcast_rec->p_member_rec, + &p_endpt->h_av ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__create_mcast_av returned %s\n", + p_endpt->p_ifc->get_err_str( status )) ); + return status; + } + p_endpt->h_mcast = p_mcast_rec->h_mcast; + CL_ASSERT(p_endpt->dlid == 0); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return IB_SUCCESS; +} + + +static void +__endpt_destroying( + IN cl_obj_t* p_obj ) +{ + ipoib_endpt_t *p_endpt; + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + p_endpt = PARENT_STRUCT( p_obj, ipoib_endpt_t, obj ); + p_port = __endpt_parent( p_endpt ); + + cl_obj_lock( p_obj ); + if( p_endpt->h_query ) + { + p_port->p_adapter->p_ifc->cancel_query( + p_port->p_adapter->h_al, p_endpt->h_query ); + p_endpt->h_query = NULL; + } + + /* Leave the multicast group if it exists. */ + if( p_endpt->h_mcast ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("Leaving MCast group\n") ); + ipoib_port_ref(p_port, ref_leave_mcast); + p_port->p_adapter->p_ifc->leave_mcast( p_endpt->h_mcast, ipoib_leave_mcast_cb ); + } +#if 0 + else if( p_port->p_adapter->params.cm_enabled ) + { + p_endpt->cm_flag = 0; + CL_ASSERT( endpt_cm_get_state( p_endpt ) == IPOIB_CM_DISCONNECTED ); + } +#endif + + cl_obj_unlock( p_obj ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); +} + + +static void +__endpt_cleanup( + IN cl_obj_t* p_obj ) +{ + ipoib_endpt_t *p_endpt; + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + p_endpt = PARENT_STRUCT( p_obj, ipoib_endpt_t, obj ); + p_port = __endpt_parent( p_endpt ); + + /* Destroy the AV if it exists. */ + if( p_endpt->h_av ) + p_port->p_adapter->p_ifc->destroy_av( p_endpt->h_av ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); +} + + +static void +__endpt_free( + IN cl_obj_t* p_obj ) +{ + ipoib_endpt_t *p_endpt; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + p_endpt = PARENT_STRUCT( p_obj, ipoib_endpt_t, obj ); + + cl_obj_deinit( p_obj ); + cl_free( p_endpt ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); +} + + +static inline ipoib_port_t* +__endpt_parent( + IN ipoib_endpt_t* const p_endpt ) +{ + return PARENT_STRUCT( p_endpt->rel.p_parent_obj, ipoib_port_t, obj ); +} + +ipoib_port_t* +ipoib_endpt_parent( + IN ipoib_endpt_t* const p_endpt ) +{ + return __endpt_parent( p_endpt ); +} + +/* + * This function is called with the port object's send lock held and + * a reference held on the endpoint. If we fail, we release the reference. + */ +NDIS_STATUS +ipoib_endpt_queue( + IN ipoib_endpt_t* const p_endpt ) +{ + ib_api_status_t status; + ipoib_port_t *p_port; + ib_av_attr_t av_attr; + net32_t flow_lbl; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + if( p_endpt->h_av ) + { + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return NDIS_STATUS_SUCCESS; + } + + if( p_endpt->qpn == CL_HTON32(0x00FFFFFF) ) + { + /* + * Handle a race between the mcast callback and a receive/send. The QP + * is joined to the MC group before the MC callback is received, so it + * can receive packets, and NDIS can try to respond. We need to delay + * a response until the MC callback runs and sets the AV. + */ + ipoib_endpt_deref( p_endpt ); + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return NDIS_STATUS_PENDING; + } + + /* This is the first packet for this endpoint. Create the AV. */ + p_port = __endpt_parent( p_endpt ); + + cl_memclr( &av_attr, sizeof(ib_av_attr_t) ); + + av_attr.port_num = p_port->port_num; + + ib_member_get_sl_flow_hop( + p_port->ib_mgr.bcast_rec.sl_flow_hop, + &av_attr.sl, + &flow_lbl, + &av_attr.grh.hop_limit + ); + + av_attr.dlid = p_endpt->dlid; + + /* + * We always send the GRH so that we preferably lookup endpoints + * by GID rather than by LID. This allows certain WHQL tests + * such as the 2c_MediaCheck test to succeed since they don't use + * IP. This allows endpoints to be created on the fly for requests + * for which there is no match, something that doesn't work when + * using LIDs only. + */ + av_attr.grh_valid = TRUE; + av_attr.grh.ver_class_flow = ib_grh_set_ver_class_flow( + 6, p_port->ib_mgr.bcast_rec.tclass, flow_lbl ); + av_attr.grh.resv1 = 0; + av_attr.grh.resv2 = 0; + ib_gid_set_default( &av_attr.grh.src_gid, p_port->p_adapter->guids.port_guid.guid ); + av_attr.grh.dest_gid = p_endpt->dgid; + + av_attr.static_rate = p_port->ib_mgr.bcast_rec.rate; + av_attr.path_bits = 0; + + /* Create the AV. */ + status = p_port->p_adapter->p_ifc->create_av( + p_port->ib_mgr.h_pd, &av_attr, &p_endpt->h_av ); + if( status != IB_SUCCESS ) + { + p_port->p_adapter->hung = TRUE; + ipoib_endpt_deref( p_endpt ); + cl_obj_unlock( &p_endpt->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_create_av failed with %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return NDIS_STATUS_FAILURE; + } + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return NDIS_STATUS_SUCCESS; +} + +#if 0 + +static void +__endpt_cm_buf_mgr_construct( + IN endpt_buf_mgr_t * const p_buf_mgr ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + cl_qpool_construct( &p_buf_mgr->recv_pool ); + + p_buf_mgr->h_packet_pool = NULL; + p_buf_mgr->h_buffer_pool = NULL; + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + +ib_api_status_t +endpt_cm_buf_mgr_init( + IN ipoib_port_t* const p_port ) +{ + cl_status_t cl_status; + NDIS_STATUS ndis_status; + ib_api_status_t ib_status = IB_SUCCESS; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + if( p_port->cm_buf_mgr.pool_init ) + return ib_status; + + cl_qlist_init( &p_port->cm_buf_mgr.posted_list ); + + __endpt_cm_buf_mgr_construct( &p_port->cm_buf_mgr ); + p_port->cm_recv_mgr.rq_depth = + min( (uint32_t)p_port->p_adapter->params.rq_depth * 8, + p_port->p_ca_attrs->max_srq_wrs/2 ); + p_port->cm_recv_mgr.depth = 0; + /* Allocate the receive descriptors pool */ + cl_status = cl_qpool_init( &p_port->cm_buf_mgr.recv_pool, + p_port->cm_recv_mgr.rq_depth , + 0, + 0, + sizeof( ipoib_cm_desc_t ), + __cm_recv_desc_ctor, + __cm_recv_desc_dtor, + p_port ); + + if( cl_status != CL_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_RECV_POOL, 1, cl_status ); + + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_qpool_init for cm recvs returned %#x\n", cl_status) ); + + return IB_INSUFFICIENT_MEMORY; + } + + /* Allocate the NDIS buffer and packet pools for receive indication. */ + NdisAllocatePacketPool( &ndis_status, + &p_port->cm_buf_mgr.h_packet_pool, + p_port->cm_recv_mgr.rq_depth, + PROTOCOL_RESERVED_SIZE_IN_PACKET ); + if( ndis_status != NDIS_STATUS_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_RECV_PKT_POOL, 1, ndis_status ); + + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisAllocatePacketPool returned %08X\n", ndis_status) ); + + ib_status = IB_INSUFFICIENT_RESOURCES; + goto pkt_pool_failed; + } + + NdisAllocateBufferPool( &ndis_status, + &p_port->cm_buf_mgr.h_buffer_pool, + p_port->cm_recv_mgr.rq_depth ); + if( ndis_status != NDIS_STATUS_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_RECV_BUF_POOL, 1, ndis_status ); + + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisAllocateBufferPool returned %08X\n", ndis_status) ); + + ib_status = IB_INSUFFICIENT_RESOURCES; + goto buf_pool_failed; + } + //NDIS60 + //p_port->cm_recv_mgr.recv_pkt_array = + //cl_zalloc( sizeof(NDIS_PACKET*) * p_port->cm_recv_mgr.rq_depth ); + p_port->cm_recv_mgr.recv_lst_array = + cl_zalloc( sizeof(NET_BUFFER_LIST*) * p_port->cm_recv_mgr.rq_depth ); + + + + if( !p_port->cm_recv_mgr.recv_pkt_array ) + { + //NDIS60 + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_zalloc for NET_BUFFER_LIST array failed.\n") ); + + ib_status = IB_INSUFFICIENT_MEMORY; + goto pkt_array_failed; + } + + p_port->cm_buf_mgr.pool_init = TRUE; + return IB_SUCCESS; + +pkt_array_failed: + if( p_port->cm_buf_mgr.h_buffer_pool ) + NdisFreeBufferPool( p_port->cm_buf_mgr.h_buffer_pool ); +buf_pool_failed: + if( p_port->cm_buf_mgr.h_packet_pool ) + NdisFreePacketPool( p_port->cm_buf_mgr.h_packet_pool ); +pkt_pool_failed: + cl_qpool_destroy( &p_port->cm_buf_mgr.recv_pool ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return ib_status; +} + +void +endpt_cm_buf_mgr_reset( + IN ipoib_port_t* const p_port ) +{ + cl_list_item_t *p_item; + + if( !p_port->cm_buf_mgr.pool_init ) + return; + + if( cl_qlist_count( &p_port->cm_buf_mgr.posted_list ) ) + { + for( p_item = cl_qlist_remove_head( &p_port->cm_buf_mgr.posted_list ); + p_item != cl_qlist_end( &p_port->cm_buf_mgr.posted_list ); + p_item = cl_qlist_remove_head( &p_port->cm_buf_mgr.posted_list ) ) + { + cl_qpool_put( &p_port->cm_buf_mgr.recv_pool, + &( PARENT_STRUCT( p_item, ipoib_cm_desc_t, list_item ))->item ); + } + } +} + +void +endpt_cm_buf_mgr_destroy( + IN ipoib_port_t* const p_port ) +{ + + IPOIB_ENTER(IPOIB_DBG_INIT ); + + CL_ASSERT( p_port ); + + /* Free the receive descriptors. */ + if( !p_port->cm_buf_mgr.pool_init ) + return; + + endpt_cm_buf_mgr_reset( p_port ); + + p_port->cm_buf_mgr.pool_init = FALSE; + + if( p_port->cm_recv_mgr.recv_pkt_array ) + { + cl_free( p_port->cm_recv_mgr.recv_pkt_array ); + } + + /* Destroy the receive packet and buffer pools. */ + if( p_port->cm_buf_mgr.h_buffer_pool ) + NdisFreeBufferPool( p_port->cm_buf_mgr.h_buffer_pool ); + if( p_port->cm_buf_mgr.h_packet_pool ) + NdisFreePacketPool( p_port->cm_buf_mgr.h_packet_pool ); + + cl_qpool_destroy( &p_port->cm_buf_mgr.recv_pool ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + +static cl_status_t +__cm_recv_desc_ctor( + IN void* const p_object, + IN void* context, + OUT cl_pool_item_t** const pp_pool_item ) +{ + ipoib_cm_desc_t* p_desc; + ipoib_port_t* p_port; + ib_mr_create_t create_mr; + net32_t rkey; + + CL_ASSERT( p_object ); + CL_ASSERT( context ); + + p_desc = (ipoib_cm_desc_t*)p_object; + p_port = (ipoib_port_t*)context; + +#define BUF_ALIGN (16) + + p_desc->alloc_buf_size = + ROUNDUP( p_port->p_adapter->params.cm_xfer_block_size, BUF_ALIGN ); + + p_desc->p_alloc_buf = (uint8_t *)ExAllocatePoolWithTag( + NonPagedPool, p_desc->alloc_buf_size, 'DOMC' ); + + if( p_desc->p_alloc_buf == NULL ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate receive buffer size %d bytes.\n", p_desc->alloc_buf_size ) ); + return CL_INSUFFICIENT_MEMORY; + } + + create_mr.vaddr = p_desc->p_alloc_buf; + create_mr.length = p_desc->alloc_buf_size; + create_mr.access_ctrl = IB_AC_LOCAL_WRITE; + + + if( p_port->p_adapter->p_ifc->reg_mem( + p_port->ib_mgr.h_pd, + &create_mr, + &p_desc->lkey, + &rkey, + &p_desc->h_mr ) != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to create Memory Region size %d bytes.\n", p_desc->alloc_buf_size ) ); + goto ctor_failed; + } + p_desc->p_buf = p_desc->p_alloc_buf + (BUF_ALIGN - sizeof( ipoib_hdr_t)); + p_desc->buf_size = p_desc->alloc_buf_size - (BUF_ALIGN - sizeof( ipoib_hdr_t)); + + /* Setup the local data segment. */ + p_desc->local_ds[0].vaddr = (uint64_t)(uintn_t)p_desc->p_buf; + p_desc->local_ds[0].length = p_desc->buf_size; + p_desc->local_ds[0].lkey = p_desc->lkey; + + /* Setup the work request. */ + p_desc->wr.wr_id = (uintn_t)p_desc; + p_desc->wr.ds_array = p_desc->local_ds; + p_desc->wr.num_ds = 1; + p_desc->type = PKT_TYPE_CM_UCAST; + + *pp_pool_item = &p_desc->item; + return CL_SUCCESS; + +ctor_failed: + ExFreePoolWithTag( p_desc->p_alloc_buf, 'DOMC' ); + return CL_INSUFFICIENT_MEMORY; +} + +static void +__cm_recv_desc_dtor( + IN const cl_pool_item_t* const p_pool_item, + IN void *context ) +{ + ipoib_cm_desc_t *p_desc; + ipoib_port_t* p_port; + + if( p_pool_item == NULL || context == NULL ) + return; + + p_port = (ipoib_port_t*)context; + p_desc = PARENT_STRUCT( p_pool_item, ipoib_cm_desc_t, item ); + + if( p_desc->h_mr ) + p_port->p_adapter->p_ifc->dereg_mr( p_desc->h_mr ); + + if( p_desc->p_alloc_buf ) + ExFreePoolWithTag( p_desc->p_alloc_buf, 'DOMC' ); +} + + +static NDIS_PACKET* +__endpt_cm_get_ndis_pkt( + IN ipoib_port_t* const p_port, + IN ipoib_cm_desc_t* const p_desc ) +{ + NDIS_STATUS status; + NDIS_PACKET *p_packet; + NDIS_BUFFER *p_buffer; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + NdisDprAllocatePacketNonInterlocked( &status, &p_packet, + p_port->cm_buf_mgr.h_packet_pool ); + if( status != NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate NDIS_PACKET: %08x\n", status) ); + return NULL; + } + + IPOIB_PORT_FROM_PACKET( p_packet ) = p_port; + IPOIB_RECV_FROM_PACKET( p_packet ) = p_desc; + + NdisAllocateBuffer( + &status, + &p_buffer, + p_port->cm_buf_mgr.h_buffer_pool, + (void *)(p_desc->p_buf - DATA_OFFSET), + p_desc->len + DATA_OFFSET ); + + if( status != NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate NDIS_BUFFER: %08x\n", status) ); + NdisDprFreePacketNonInterlocked( p_packet ); + return NULL; + } + + NdisChainBufferAtFront( p_packet, p_buffer ); + NDIS_SET_PACKET_HEADER_SIZE( p_packet, sizeof(eth_hdr_t) ); + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return p_packet; +} + +static inline ipoib_cm_desc_t* +__endpt_cm_buf_mgr_get_recv( + IN endpt_buf_mgr_t * const p_buf_mgr ) +{ + ipoib_cm_desc_t *p_desc; + + p_desc = (ipoib_cm_desc_t*)cl_qpool_get( &p_buf_mgr->recv_pool ); + if( p_desc ) + cl_qlist_insert_tail( &p_buf_mgr->posted_list, &p_desc->list_item ); + + return p_desc; +} + +void +endpt_cm_buf_mgr_put_recv( + IN endpt_buf_mgr_t * const p_buf_mgr, + IN ipoib_cm_desc_t* const p_desc ) +{ + + IPOIB_ENTER(IPOIB_DBG_RECV ); + + /* Return the descriptor to it's pool. */ + cl_qlist_remove_item( &p_buf_mgr->posted_list, &p_desc->list_item ); + cl_qpool_put( &p_buf_mgr->recv_pool, &p_desc->item ); + + IPOIB_EXIT( IPOIB_DBG_RECV ); +} + +void +endpt_cm_buf_mgr_put_recv_list( + IN endpt_buf_mgr_t * const p_buf_mgr, + IN cl_qlist_t* const p_list ) +{ + cl_qpool_put_list( &p_buf_mgr->recv_pool, p_list ); +} + +uint32_t +endpt_cm_recv_mgr_build_pkt_array( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt, + IN cl_qlist_t* const p_done_list, + IN OUT uint32_t* p_bytes_recv ) +{ + cl_list_item_t *p_item; + ipoib_cm_desc_t *p_desc; + uint32_t i = 0; + NDIS_PACKET *p_packet; + NDIS_TCP_IP_CHECKSUM_PACKET_INFO chksum; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + UNUSED_PARAM( p_endpt ); + + p_item = cl_qlist_remove_head( p_done_list ); + + *p_bytes_recv = 0; + + for( p_item; p_item != cl_qlist_end( p_done_list ); + p_item = cl_qlist_remove_head( p_done_list ) ) + { + p_desc = (ipoib_cm_desc_t*)p_item; + + p_packet = __endpt_cm_get_ndis_pkt( p_port, p_desc ); + if( !p_packet ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get Packet from descriptor\n" ) ); + endpt_cm_buf_mgr_put_recv( &p_port->cm_buf_mgr, p_desc ); + p_port->cm_recv_mgr.depth--; + continue; + } + chksum.Value = 0; + switch( p_port->p_adapter->params.recv_chksum_offload ) + { + default: + CL_ASSERT( FALSE ); + case CSUM_DISABLED: + case CSUM_ENABLED: + NDIS_PER_PACKET_INFO_FROM_PACKET( p_packet, TcpIpChecksumPacketInfo ) = + (void*)(uintn_t)chksum.Value; + break; + case CSUM_BYPASS: + /* Flag the checksums as having been calculated. */ + chksum.Receive.NdisPacketTcpChecksumSucceeded = TRUE; + chksum.Receive.NdisPacketUdpChecksumSucceeded = TRUE; + chksum.Receive.NdisPacketIpChecksumSucceeded = TRUE; + NDIS_PER_PACKET_INFO_FROM_PACKET( p_packet, TcpIpChecksumPacketInfo ) = + (void*)(uintn_t)chksum.Value; + break; + } + + NDIS_SET_PACKET_STATUS( p_packet, NDIS_STATUS_SUCCESS ); + p_port->cm_recv_mgr.recv_pkt_array[i] = p_packet; + i++; + *p_bytes_recv += p_desc->len; + } + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return i; +} +void +endpt_cm_flush_recv( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt ) +{ + ib_api_status_t ib_status = IB_SUCCESS; + ib_qp_mod_t mod_attr; + ib_wc_t wc[MAX_RECV_WC]; + ib_wc_t *p_free_wc; + ib_wc_t *p_done_wc; + ib_wc_t *p_wc; + ipoib_cm_desc_t *p_desc; + size_t i; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + CL_ASSERT( p_endpt ); + + if( p_endpt->conn.h_recv_qp ) + { + cl_memclr( &mod_attr, sizeof( mod_attr ) ); + mod_attr.req_state = IB_QPS_ERROR; + p_port->p_adapter->p_ifc->modify_qp( p_endpt->conn.h_send_qp, &mod_attr ); + p_port->p_adapter->p_ifc->modify_qp( p_endpt->conn.h_recv_qp, &mod_attr ); + + for( i = 0; i < MAX_RECV_WC; i++ ) + wc[i].p_next = &wc[i + 1]; + wc[MAX_RECV_WC - 1].p_next = NULL; + + do + { + p_free_wc = wc; + ib_status = + p_port->p_adapter->p_ifc->poll_cq( p_endpt->conn.h_recv_cq, + &p_free_wc, &p_done_wc ); + if( ib_status != IB_SUCCESS && + ib_status != IB_NOT_FOUND ) + { + /* connection CQ failed */ + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Poll Recv CQ failed status %#x\n", ib_status ) ); + break; + } + cl_spinlock_acquire( &p_port->recv_lock ); + for( p_wc = p_done_wc; p_wc; p_wc = p_wc->p_next ) + { + p_desc = (ipoib_cm_desc_t *)(uintn_t)p_wc->wr_id; + endpt_cm_buf_mgr_put_recv( &p_port->cm_buf_mgr, p_desc ); + p_port->cm_recv_mgr.depth--; + } + cl_spinlock_release( &p_port->recv_lock ); + } while( !p_free_wc ); + + ib_status = p_port->p_adapter->p_ifc->destroy_qp( p_endpt->conn.h_recv_qp, NULL ); + if( ib_status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Destroy Recv QP failed status %#x\n", ib_status ) ); + } + p_endpt->conn.h_recv_qp = NULL; + } + + if( p_endpt->conn.h_send_qp ) + { + ib_status = p_port->p_adapter->p_ifc->destroy_qp( p_endpt->conn.h_send_qp, NULL ); + if( ib_status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Destroy Send QP failed status %#x\n", ib_status ) ); + } + p_endpt->conn.h_send_qp = NULL; + } + + IPOIB_EXIT( IPOIB_DBG_RECV ); +} + +int32_t +endpt_cm_recv_mgr_filter( + IN ipoib_endpt_t* const p_endpt, + IN ib_wc_t* const p_done_wc_list, + OUT cl_qlist_t* const p_done_list, + OUT cl_qlist_t* const p_bad_list ) +{ + ib_api_status_t ib_status; + ipoib_cm_desc_t *p_desc; + ib_wc_t *p_wc; + ipoib_pkt_t *p_ipoib; + eth_pkt_t *p_eth; + ipoib_port_t* p_port; + int32_t recv_cnt; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + p_port = ipoib_endpt_parent( p_endpt ); + + for( p_wc = p_done_wc_list, recv_cnt = 0; p_wc; p_wc = p_wc->p_next ) + { + p_desc = (ipoib_cm_desc_t *)(uintn_t)p_wc->wr_id; + recv_cnt++; + if( p_wc->status != IB_WCS_SUCCESS ) + { + if( p_wc->status != IB_WCS_WR_FLUSHED_ERR ) + { + + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed completion %s (vendor specific %#x)\n", + p_port->p_adapter->p_ifc->get_wc_status_str( p_wc->status ), + (int)p_wc->vendor_specific) ); + } + else + { + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_RECV, + ("Flushed completion %s\n", + p_port->p_adapter->p_ifc->get_wc_status_str( p_wc->status )) ); + } + + ipoib_inc_recv_stat( p_port->p_adapter, IP_STAT_ERROR, 0, 0 ); + + cl_qlist_remove_item( &p_port->cm_buf_mgr.posted_list,&p_desc->list_item ); + cl_qlist_insert_tail( p_bad_list, &p_desc->item.list_item ); + continue; + } + + /* Successful completion + Setup the ethernet/ip/arp header and queue descriptor for report. */ + ib_status = IB_SUCCESS; + p_ipoib = (ipoib_pkt_t *)((uint8_t*)p_desc->p_buf ); + p_eth = (eth_pkt_t *)((uint8_t*)p_desc->p_buf - DATA_OFFSET ); + + switch( p_ipoib->hdr.type ) + { + case ETH_PROT_TYPE_ARP: + if( p_wc->length < (sizeof(ipoib_hdr_t) + sizeof(ipoib_arp_pkt_t)) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received ARP packet too short\n") ); + ib_status = IB_ERROR; + break; + } + ib_status = + __endpt_cm_recv_arp( p_port, p_ipoib, p_eth, p_endpt ); + break; + case ETH_PROT_TYPE_IP: + if( p_wc->length < (sizeof(ipoib_hdr_t) + sizeof(ip_hdr_t)) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received IP packet too short\n") ); + ib_status = IB_ERROR; + break; + } + if( p_ipoib->type.ip.hdr.prot == IP_PROT_UDP ) + { + ib_status = + __endpt_cm_recv_udp( p_port, p_wc, p_ipoib, p_eth, p_endpt ); + } + + break; + } + + if( ib_status != IB_SUCCESS ) + { + ipoib_inc_recv_stat( p_port->p_adapter, IP_STAT_ERROR, 0, 0 ); + cl_qlist_insert_tail( p_bad_list, &p_desc->item.list_item ); + continue; + } + + p_eth->hdr.type = p_ipoib->hdr.type; + p_eth->hdr.src = p_endpt->mac; + p_eth->hdr.dst = p_port->p_adapter->mac; + + /* save payload length */ + p_desc->len = p_wc->length; + + cl_qlist_insert_tail( p_done_list, &p_desc->item.list_item ); + } + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return recv_cnt; +} + +ib_api_status_t +endpt_cm_post_recv( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t ib_status = IB_SUCCESS; + ipoib_cm_desc_t *p_head_desc = NULL; + ipoib_cm_desc_t *p_tail_desc = NULL; + ipoib_cm_desc_t *p_next_desc; + ib_recv_wr_t *p_failed_wc = NULL; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + while( cl_qpool_count( &p_port->cm_buf_mgr.recv_pool ) > 1 ) + { + /* Pull receives out of the pool and chain them up. */ + p_next_desc = __endpt_cm_buf_mgr_get_recv( + &p_port->cm_buf_mgr ); + if( !p_next_desc ) + { + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_RECV, + ("Out of receive descriptors! Endpt recv queue depth 0x%x\n", + p_port->cm_recv_mgr.depth ) ); + break; + } + + if( !p_tail_desc ) + { + p_tail_desc = p_next_desc; + p_next_desc->wr.p_next = NULL; + } + else + { + p_next_desc->wr.p_next = &p_head_desc->wr; + } + + p_head_desc = p_next_desc; + + p_port->cm_recv_mgr.depth++; + } + + if( p_head_desc ) + { + ib_status = p_port->p_adapter->p_ifc->post_srq_recv( + p_port->ib_mgr.h_srq, &p_head_desc->wr, &p_failed_wc ); + + if( ib_status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ip_post_recv returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( ib_status )) ); + + /* put descriptors back to the pool */ + while( p_failed_wc ) + { + p_head_desc = PARENT_STRUCT( p_failed_wc, ipoib_cm_desc_t, wr ); + p_failed_wc = p_failed_wc->p_next; + endpt_cm_buf_mgr_put_recv( &p_port->cm_buf_mgr, p_head_desc ); + p_port->cm_recv_mgr.depth--; + } + } + } + + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return( ib_status ); +} + +static ib_api_status_t +__endpt_cm_recv_arp( + IN ipoib_port_t* const p_port, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src_endpt ) +{ + const ipoib_arp_pkt_t *p_ib_arp; + arp_pkt_t *p_arp; + + p_ib_arp = &p_ipoib->type.arp; + p_arp = &p_eth->type.arp; + + if( p_ib_arp->hw_type != ARP_HW_TYPE_IB || + p_ib_arp->hw_size != sizeof(ipoib_hw_addr_t) || + p_ib_arp->prot_type != ETH_PROT_TYPE_IP ) + { + return IB_ERROR; + } + + p_arp->hw_type = ARP_HW_TYPE_ETH; + p_arp->hw_size = sizeof(mac_addr_t); + p_arp->src_hw = p_src_endpt->mac; + p_arp->src_ip = p_ib_arp->src_ip; + p_arp->dst_hw = p_port->p_local_endpt->mac; + p_arp->dst_ip = p_ib_arp->dst_ip; + + return IB_SUCCESS; +} + +static ib_api_status_t +__endpt_cm_recv_udp( + IN ipoib_port_t* const p_port, + IN ib_wc_t* const p_wc, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src_endpt ) +{ + ib_api_status_t ib_status = IB_SUCCESS; + + if( p_wc->length < + (sizeof(ipoib_hdr_t) + sizeof(ip_hdr_t) + sizeof(udp_hdr_t)) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received UDP packet too short\n") ); + return IB_ERROR; + } + if( __cm_recv_is_dhcp( p_ipoib ) ) + { + ib_status = ipoib_recv_dhcp( + p_port, p_ipoib, p_eth, p_src_endpt, p_port->p_local_endpt ); + } + + return ib_status; +} + +static boolean_t +__cm_recv_is_dhcp( + IN const ipoib_pkt_t* const p_ipoib ) +{ + return( (p_ipoib->type.ip.prot.udp.hdr.dst_port == DHCP_PORT_SERVER && + p_ipoib->type.ip.prot.udp.hdr.src_port == DHCP_PORT_CLIENT) || + (p_ipoib->type.ip.prot.udp.hdr.dst_port == DHCP_PORT_CLIENT && + p_ipoib->type.ip.prot.udp.hdr.src_port == DHCP_PORT_SERVER) ); +} +#endif diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_endpoint.h b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_endpoint.h new file mode 100644 index 00000000..547d4652 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_endpoint.h @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_endpoint.h 4226 2009-04-06 06:01:03Z xalex $ + */ + + +#ifndef _IPOIB_ENDPOINT_H_ +#define _IPOIB_ENDPOINT_H_ + + +#include +#include +#include +#include +#include +#include "iba/ipoib_ifc.h" +#include +#include "ipoib_debug.h" + + +typedef struct _endpt_buf_mgr +{ + cl_qpool_t recv_pool; + NDIS_HANDLE h_packet_pool; + NDIS_HANDLE h_buffer_pool; + cl_qlist_t posted_list; + boolean_t pool_init; +} endpt_buf_mgr_t; + +typedef struct _endpt_recv_mgr +{ + int32_t depth; + int32_t rq_depth; + //NDIS60 + //NDIS_PACKET **recv_pkt_array; + NET_BUFFER_LIST *recv_lst_array; + +} endpt_recv_mgr_t; + + +typedef enum _cm_state +{ + IPOIB_CM_DISCONNECTED, + IPOIB_CM_INIT, + IPOIB_CM_CONNECT, + IPOIB_CM_CONNECTED, + IPOIB_CM_LISTEN, + IPOIB_CM_DREP_SENT, + IPOIB_CM_DREQ_SENT, + IPOIB_CM_REJ_RECVD, + IPOIB_CM_DESTROY +} cm_state_t; + +typedef struct _cm_private_data +{ + ib_net32_t ud_qpn; + ib_net32_t recv_mtu; +} cm_private_data_t; + +typedef struct _endpt_conn +{ + ib_net64_t service_id; + cm_private_data_t private_data; + ib_qp_handle_t h_send_qp; + ib_qp_handle_t h_recv_qp; + ib_qp_handle_t h_work_qp; + ib_cq_handle_t h_send_cq; + ib_cq_handle_t h_recv_cq; + ib_listen_handle_t h_cm_listen; + cm_state_t state; + +} endpt_conn_t; + +typedef struct _ipoib_endpt +{ + cl_obj_t obj; + cl_obj_rel_t rel; + cl_map_item_t mac_item; + cl_fmap_item_t gid_item; + cl_map_item_t lid_item; + cl_fmap_item_t conn_item; + LIST_ENTRY list_item; + ib_query_handle_t h_query; + ib_mcast_handle_t h_mcast; + mac_addr_t mac; + ib_gid_t dgid; + net16_t dlid; + net32_t qpn; + uint8_t cm_flag; + ib_av_handle_t h_av; + endpt_conn_t conn; + + ib_al_ifc_t *p_ifc; + boolean_t is_in_use; + boolean_t is_mcast_listener; +} ipoib_endpt_t; +/* +* FIELDS +* mac_item +* Map item for storing the endpoint in a map. The key is the +* destination MAC address. +* +* lid_item +* Map item for storing the endpoint in a map. The key is the +* destination LID. +* +* gid_item +* Map item for storing the endpoint in a map. The key is the +* destination GID. +* +* h_query +* Query handle for cancelling SA queries. +* +* h_mcast +* For multicast endpoints, the multicast handle. +* +* mac +* MAC address. +* +* dgid +* Destination GID. +* +* dlid +* Destination LID. The destination LID is only set for endpoints +* that are on the same subnet. It is used as key in the LID map. +* +* qpn +* Destination queue pair number. +* +* h_av +* Address vector for sending data. +* +* expired +* Flag to indicate that the endpoint should be flushed. +* +* connection +* for connected mode endpoints +* +* p_ifc +* Reference to transport functions, can be used +* while endpoint is not attached to port yet. +* +* NOTES +* If the h_mcast member is set, the endpoint is never expired. +*********/ + + +ipoib_endpt_t* +ipoib_endpt_create( + IN const ib_gid_t* const p_dgid, + IN const net16_t dlid, + IN const net32_t qpn ); + + +ib_api_status_t +ipoib_endpt_set_mcast( + IN ipoib_endpt_t* const p_endpt, + IN ib_pd_handle_t h_pd, + IN uint8_t port_num, + IN ib_mcast_rec_t* const p_mcast_rec ); + + +static inline void +ipoib_endpt_ref( + IN ipoib_endpt_t* const p_endpt ) +{ + CL_ASSERT( p_endpt ); + + cl_obj_ref( &p_endpt->obj ); + /* + * Anytime we reference the endpoint, we're either receiving data + * or trying to send data to that endpoint. Clear the expired flag + * to prevent the AV from being flushed. + */ +} + + +static inline void +ipoib_endpt_deref( + IN ipoib_endpt_t* const p_endpt ) +{ + cl_obj_deref( &p_endpt->obj ); +} + + +NDIS_STATUS +ipoib_endpt_queue( + IN ipoib_endpt_t* const p_endpt ); + +struct _ipoib_port * +ipoib_endpt_parent( + IN ipoib_endpt_t* const p_endpt ); + +inline cm_state_t +endpt_cm_set_state( + IN ipoib_endpt_t* const p_endpt, + IN cm_state_t state ) +{ + return(cm_state_t)InterlockedExchange( + (volatile LONG *)&p_endpt->conn.state, + (LONG)state ); +} + +inline cm_state_t +endpt_cm_get_state( + IN ipoib_endpt_t* const p_endpt ) +{ + return( cm_state_t )InterlockedCompareExchange( + (volatile LONG *)&p_endpt->conn.state, + IPOIB_CM_DISCONNECTED, IPOIB_CM_DISCONNECTED ); +} + +ib_api_status_t +endpt_cm_create_qp( + IN ipoib_endpt_t* const p_endpt, + IN ib_qp_handle_t* const p_h_qp ); + +ib_api_status_t +ipoib_endpt_connect( + IN ipoib_endpt_t* const p_endpt ); + +int32_t +endpt_cm_recv_mgr_filter( + IN ipoib_endpt_t* const p_endpt, + IN ib_wc_t* const p_done_wc_list, + OUT cl_qlist_t* const p_done_list, + OUT cl_qlist_t* const p_bad_list ); + +#endif /* _IPOIB_ENDPOINT_H_ */ diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_ibat.c b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_ibat.c new file mode 100644 index 00000000..a7703665 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_ibat.c @@ -0,0 +1,693 @@ +/* + * Copyright (c) 2005 Mellanox Technologies. All rights reserved. + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_ibat.c 4494 2009-06-22 14:31:08Z xalex $ + */ + + +#include "ipoib_driver.h" +#include "ipoib_adapter.h" +#include "ipoib_port.h" +#include "ipoib_debug.h" +#if defined(EVENT_TRACING) +#ifdef offsetof +#undef offsetof +#endif +#include "ipoib_ibat.tmh" +#endif +#include + +extern PDRIVER_OBJECT g_p_drv_obj; + +static NTSTATUS +__ipoib_create( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ); + +static NTSTATUS +__ipoib_cleanup( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ); + +static NTSTATUS +__ipoib_close( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ); + +static NTSTATUS +__ipoib_dispatch( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ); + + +static NTSTATUS +__ibat_get_ports( + IN IRP *pIrp, + IN IO_STACK_LOCATION *pIoStack ) +{ + IOCTL_IBAT_PORTS_IN *pIn; + IOCTL_IBAT_PORTS_OUT *pOut; + KLOCK_QUEUE_HANDLE hdl; + cl_list_item_t *pItem; + ipoib_adapter_t *pAdapter; + LONG nPorts; + + IPOIB_ENTER(IPOIB_DBG_IOCTL); + + if( pIoStack->Parameters.DeviceIoControl.InputBufferLength != + sizeof(IOCTL_IBAT_PORTS_IN) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid input buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + if( pIoStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(IOCTL_IBAT_PORTS_OUT) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid output buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + pIn = pIrp->AssociatedIrp.SystemBuffer; + pOut = pIrp->AssociatedIrp.SystemBuffer; + + if( pIn->Version != IBAT_IOCTL_VERSION ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid version.\n") ); + return STATUS_INVALID_PARAMETER; + } + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + nPorts = (LONG)cl_qlist_count( &g_ipoib.adapter_list ); + switch( nPorts ) + { + case 0: + cl_memclr( pOut->Ports, sizeof(pOut->Ports) ); + /* Fall through */ + case 1: + pOut->Size = sizeof(IOCTL_IBAT_PORTS_OUT); + break; + + default: + pOut->Size = sizeof(IOCTL_IBAT_PORTS_OUT) + + (sizeof(IBAT_PORT_RECORD) * (nPorts - 1)); + break; + } + + pIrp->IoStatus.Information = pOut->Size; + + if( pOut->Size > pIoStack->Parameters.DeviceIoControl.OutputBufferLength ) + { + nPorts = 1 + + (pIoStack->Parameters.DeviceIoControl.OutputBufferLength - + sizeof(IOCTL_IBAT_PORTS_OUT)) / sizeof(IBAT_PORT_RECORD); + + pIrp->IoStatus.Information = sizeof(IOCTL_IBAT_PORTS_OUT) + + ((nPorts - 1) * sizeof(IBAT_PORT_RECORD)); + } + + pOut->NumPorts = 0; + pItem = cl_qlist_head( &g_ipoib.adapter_list ); + while( pOut->NumPorts != nPorts ) + { + pAdapter = CONTAINING_RECORD( pItem, ipoib_adapter_t, entry ); + pOut->Ports[pOut->NumPorts].CaGuid = pAdapter->guids.ca_guid; + pOut->Ports[pOut->NumPorts].PortGuid = pAdapter->guids.port_guid.guid; + pOut->Ports[pOut->NumPorts].PKey = IB_DEFAULT_PKEY; + pOut->Ports[pOut->NumPorts].PortNum = pAdapter->guids.port_num; + pOut->NumPorts++; + + pItem = cl_qlist_next( pItem ); + } + + KeReleaseInStackQueuedSpinLock( &hdl ); + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return STATUS_SUCCESS; +} + + +static NTSTATUS +__ibat_get_ips( + IN IRP *pIrp, + IN IO_STACK_LOCATION *pIoStack ) +{ + IOCTL_IBAT_IP_ADDRESSES_IN *pIn; + IOCTL_IBAT_IP_ADDRESSES_OUT *pOut; + KLOCK_QUEUE_HANDLE hdl; + cl_list_item_t *pItem; + ipoib_adapter_t *pAdapter; + LONG nIps, maxIps; + size_t idx; + net_address_item_t *pAddr; + UINT64 PortGuid; + + IPOIB_ENTER(IPOIB_DBG_IOCTL); + + if( pIoStack->Parameters.DeviceIoControl.InputBufferLength != + sizeof(IOCTL_IBAT_IP_ADDRESSES_IN) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid input buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + if( pIoStack->Parameters.DeviceIoControl.OutputBufferLength < + sizeof(IOCTL_IBAT_IP_ADDRESSES_OUT) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid output buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + pIn = pIrp->AssociatedIrp.SystemBuffer; + pOut = pIrp->AssociatedIrp.SystemBuffer; + + if( pIn->Version != IBAT_IOCTL_VERSION ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid version.\n") ); + return STATUS_INVALID_PARAMETER; + } + + PortGuid = pIn->PortGuid; + + nIps = 0; + pOut->AddressCount = 0; + maxIps = 1 + + ((pIoStack->Parameters.DeviceIoControl.OutputBufferLength - + sizeof(IOCTL_IBAT_IP_ADDRESSES_OUT)) / sizeof(IP_ADDRESS)); + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + for( pItem = cl_qlist_head( &g_ipoib.adapter_list ); + pItem != cl_qlist_end( &g_ipoib.adapter_list ); + pItem = cl_qlist_next( pItem ) ) + { + pAdapter = CONTAINING_RECORD( pItem, ipoib_adapter_t, entry ); + if( PortGuid && pAdapter->guids.port_guid.guid != PortGuid ) + continue; + + cl_obj_lock( &pAdapter->obj ); + nIps += (LONG)cl_vector_get_size( &pAdapter->ip_vector ); + + for( idx = 0; + idx < cl_vector_get_size( &pAdapter->ip_vector ); + idx++ ) + { + if( pOut->AddressCount == maxIps ) + break; + + pAddr = (net_address_item_t*) + cl_vector_get_ptr( &pAdapter->ip_vector, idx ); + + pOut->Address[pOut->AddressCount].IpVersion = 4; + cl_memclr( &pOut->Address[pOut->AddressCount].Address, + sizeof(IP_ADDRESS) ); + cl_memcpy( &pOut->Address[pOut->AddressCount].Address[12], + pAddr->address.as_bytes, IPV4_ADDR_SIZE ); + + pOut->AddressCount++; + } + cl_obj_unlock( &pAdapter->obj ); + } + + pOut->Size = sizeof(IOCTL_IBAT_IP_ADDRESSES_OUT); + if( --nIps ) + pOut->Size += sizeof(IP_ADDRESS) * nIps; + + pIrp->IoStatus.Information = sizeof(IOCTL_IBAT_IP_ADDRESSES_OUT); + if( --maxIps < nIps ) + pIrp->IoStatus.Information += (sizeof(IP_ADDRESS) * maxIps); + else + pIrp->IoStatus.Information += (sizeof(IP_ADDRESS) * nIps); + + KeReleaseInStackQueuedSpinLock( &hdl ); + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return STATUS_SUCCESS; +} + + +static NTSTATUS +__ibat_mac_to_gid( + IN IRP *pIrp, + IN IO_STACK_LOCATION *pIoStack ) +{ + NTSTATUS status = STATUS_INVALID_PARAMETER; + IOCTL_IBAT_MAC_TO_GID_IN *pIn; + IOCTL_IBAT_MAC_TO_GID_OUT *pOut; + KLOCK_QUEUE_HANDLE hdl; + cl_list_item_t *pItem; + ipoib_adapter_t *pAdapter; + + IPOIB_ENTER(IPOIB_DBG_IOCTL); + + if( pIoStack->Parameters.DeviceIoControl.InputBufferLength != + sizeof(IOCTL_IBAT_MAC_TO_GID_IN) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid input buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + if( pIoStack->Parameters.DeviceIoControl.OutputBufferLength != + sizeof(IOCTL_IBAT_MAC_TO_GID_OUT) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid output buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + pIn = pIrp->AssociatedIrp.SystemBuffer; + pOut = pIrp->AssociatedIrp.SystemBuffer; + + if( pIn->Version != IBAT_IOCTL_VERSION ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid version.\n") ); + return STATUS_INVALID_PARAMETER; + } + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + + for( pItem = cl_qlist_head( &g_ipoib.adapter_list ); + pItem != cl_qlist_end( &g_ipoib.adapter_list ); + pItem = cl_qlist_next( pItem ) ) + { + pAdapter = CONTAINING_RECORD( pItem, ipoib_adapter_t, entry ); + if( pIn->PortGuid != pAdapter->guids.port_guid.guid ) + continue; + + /* Found the port - lookup the MAC. */ + cl_obj_lock( &pAdapter->obj ); + if( pAdapter->p_port ) + { + status = ipoib_mac_to_gid( + pAdapter->p_port, *(mac_addr_t*)pIn->DestMac, &pOut->DestGid ); + if( NT_SUCCESS( status ) ) + { + pIrp->IoStatus.Information = + sizeof(IOCTL_IBAT_MAC_TO_GID_OUT); + } + } + cl_obj_unlock( &pAdapter->obj ); + break; + } + + KeReleaseInStackQueuedSpinLock( &hdl ); + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return status; +} + + +static NTSTATUS +__ibat_mac_to_path( + IN IRP *pIrp, + IN IO_STACK_LOCATION *pIoStack ) +{ + NTSTATUS status = STATUS_INVALID_PARAMETER; + IOCTL_IBAT_MAC_TO_PATH_IN *pIn; + IOCTL_IBAT_MAC_TO_PATH_OUT *pOut; + KLOCK_QUEUE_HANDLE hdl; + cl_list_item_t *pItem; + ipoib_adapter_t *pAdapter; + + IPOIB_ENTER(IPOIB_DBG_IOCTL); + + if( pIoStack->Parameters.DeviceIoControl.InputBufferLength != + sizeof(IOCTL_IBAT_MAC_TO_PATH_IN) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid input buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + if( pIoStack->Parameters.DeviceIoControl.OutputBufferLength != + sizeof(IOCTL_IBAT_MAC_TO_PATH_OUT) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid output buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + pIn = pIrp->AssociatedIrp.SystemBuffer; + pOut = pIrp->AssociatedIrp.SystemBuffer; + + if( pIn->Version != IBAT_IOCTL_VERSION ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid version.\n") ); + return STATUS_INVALID_PARAMETER; + } + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + + for( pItem = cl_qlist_head( &g_ipoib.adapter_list ); + pItem != cl_qlist_end( &g_ipoib.adapter_list ); + pItem = cl_qlist_next( pItem ) ) + { + pAdapter = CONTAINING_RECORD( pItem, ipoib_adapter_t, entry ); + if( pIn->PortGuid != pAdapter->guids.port_guid.guid ) + continue; + + /* Found the port - lookup the MAC. */ + cl_obj_lock( &pAdapter->obj ); + if( pAdapter->p_port ) + { + status = ipoib_mac_to_path( + pAdapter->p_port, *(mac_addr_t*)pIn->DestMac, &pOut->Path ); + + if( NT_SUCCESS( status ) ) + { + pIrp->IoStatus.Information = + sizeof(IOCTL_IBAT_MAC_TO_PATH_OUT); + } + } + cl_obj_unlock( &pAdapter->obj ); + break; + } + + KeReleaseInStackQueuedSpinLock( &hdl ); + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return status; +} + + +static NTSTATUS +__ibat_ip_to_port( + IN IRP *pIrp, + IN IO_STACK_LOCATION *pIoStack ) +{ + IOCTL_IBAT_IP_TO_PORT_IN *pIn; + IOCTL_IBAT_IP_TO_PORT_OUT *pOut; + KLOCK_QUEUE_HANDLE hdl; + cl_list_item_t *pItem; + ipoib_adapter_t *pAdapter; + size_t idx; + net_address_item_t *pAddr; + NTSTATUS status = STATUS_NOT_FOUND; + + IPOIB_ENTER(IPOIB_DBG_IOCTL); + + if( pIoStack->Parameters.DeviceIoControl.InputBufferLength != + sizeof(IOCTL_IBAT_IP_TO_PORT_IN) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid input buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + if( pIoStack->Parameters.DeviceIoControl.OutputBufferLength != + sizeof(IOCTL_IBAT_IP_TO_PORT_OUT) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid output buffer size.\n") ); + return STATUS_INVALID_PARAMETER; + } + + pIn = pIrp->AssociatedIrp.SystemBuffer; + pOut = pIrp->AssociatedIrp.SystemBuffer; + + if( pIn->Version != IBAT_IOCTL_VERSION ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid version.\n") ); + return STATUS_INVALID_PARAMETER; + } + + if (pIn->Address.IpVersion != 4) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid IP version (%d). Supported only 4\n", pIn->Address.IpVersion) ); + return STATUS_INVALID_PARAMETER; + } + + KeAcquireInStackQueuedSpinLock( &g_ipoib.lock, &hdl ); + for( pItem = cl_qlist_head( &g_ipoib.adapter_list ); + pItem != cl_qlist_end( &g_ipoib.adapter_list ); + pItem = cl_qlist_next( pItem ) ) + { + pAdapter = CONTAINING_RECORD( pItem, ipoib_adapter_t, entry ); + + cl_obj_lock( &pAdapter->obj ); + + for( idx = 0; + idx < cl_vector_get_size( &pAdapter->ip_vector ); + idx++ ) + { + pAddr = (net_address_item_t*) + cl_vector_get_ptr( &pAdapter->ip_vector, idx ); + + if (!memcmp( &pIn->Address.Address[12], pAddr->address.as_bytes, IPV4_ADDR_SIZE)) + { + pOut->Port.CaGuid = pAdapter->guids.ca_guid; + pOut->Port.PortGuid = pAdapter->guids.port_guid.guid; + pOut->Port.PKey = IB_DEFAULT_PKEY; + pOut->Port.PortNum = pAdapter->guids.port_num; + pIrp->IoStatus.Information = sizeof(IOCTL_IBAT_IP_TO_PORT_OUT); + status = STATUS_SUCCESS; + break; + } + } + cl_obj_unlock( &pAdapter->obj ); + if (status == STATUS_SUCCESS) + break; + } + + KeReleaseInStackQueuedSpinLock( &hdl ); + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return status; +} + +void +ipoib_ref_ibat() +{ + UNICODE_STRING DeviceName; + UNICODE_STRING DeviceLinkUnicodeString; + NDIS_DEVICE_OBJECT_ATTRIBUTES DeviceObjectAttributes; + PDRIVER_DISPATCH DispatchTable[IRP_MJ_MAXIMUM_FUNCTION+1]; + + NDIS_STATUS Status = NDIS_STATUS_SUCCESS; + + IPOIB_ENTER( IPOIB_DBG_IOCTL ); + + if( InterlockedIncrement( &g_ipoib.ibat_ref ) == 1 ) + { + + NdisZeroMemory(DispatchTable, (IRP_MJ_MAXIMUM_FUNCTION+1) * sizeof(PDRIVER_DISPATCH)); + + DispatchTable[IRP_MJ_CREATE] = __ipoib_create; + DispatchTable[IRP_MJ_CLEANUP] = __ipoib_cleanup; + DispatchTable[IRP_MJ_CLOSE] = __ipoib_close; + DispatchTable[IRP_MJ_DEVICE_CONTROL] = __ipoib_dispatch; + DispatchTable[IRP_MJ_INTERNAL_DEVICE_CONTROL] = __ipoib_dispatch; + + + NdisInitUnicodeString( &DeviceName, IBAT_DEV_NAME ); + NdisInitUnicodeString( &DeviceLinkUnicodeString, IBAT_DOS_DEV_NAME ); + + + NdisZeroMemory(&DeviceObjectAttributes, sizeof(NDIS_DEVICE_OBJECT_ATTRIBUTES)); + + DeviceObjectAttributes.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; // type implicit from the context + DeviceObjectAttributes.Header.Revision = NDIS_DEVICE_OBJECT_ATTRIBUTES_REVISION_1; + DeviceObjectAttributes.Header.Size = sizeof(NDIS_DEVICE_OBJECT_ATTRIBUTES); + DeviceObjectAttributes.DeviceName = &DeviceName; + DeviceObjectAttributes.SymbolicName = &DeviceLinkUnicodeString; + DeviceObjectAttributes.MajorFunctions = &DispatchTable[0]; + DeviceObjectAttributes.ExtensionSize = 0; + DeviceObjectAttributes.DefaultSDDLString = NULL; + DeviceObjectAttributes.DeviceClassGuid = 0; + + Status = NdisRegisterDeviceEx( + g_IpoibMiniportDriverHandle, + &DeviceObjectAttributes, + &g_ipoib.h_ibat_dev, + &g_ipoib.h_ibat_dev_handle); + + + + if( Status != NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisRegisterDeviceEx failed with status of %d\n", Status) ); + } + } + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); +} + + +void +ipoib_deref_ibat() +{ + IPOIB_ENTER( IPOIB_DBG_IOCTL ); + + if( InterlockedDecrement( &g_ipoib.ibat_ref ) ) + { + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return; + } + + if( g_ipoib.h_ibat_dev ) + { + NdisDeregisterDeviceEx( g_ipoib.h_ibat_dev_handle ); + g_ipoib.h_ibat_dev = NULL; + g_ipoib.h_ibat_dev_handle = NULL; //TODO set here INVALID_HANDLE_VALUE + } + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); +} + + +static NTSTATUS +__ipoib_create( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ) +{ + IPOIB_ENTER( IPOIB_DBG_IOCTL ); + + UNREFERENCED_PARAMETER( pDevObj ); + + ipoib_ref_ibat(); + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = 0; + IoCompleteRequest( pIrp, IO_NO_INCREMENT ); + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return STATUS_SUCCESS; +} + + +static NTSTATUS +__ipoib_cleanup( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ) +{ + IPOIB_ENTER( IPOIB_DBG_IOCTL ); + + UNREFERENCED_PARAMETER( pDevObj ); + + ipoib_deref_ibat(); + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = 0; + IoCompleteRequest( pIrp, IO_NO_INCREMENT ); + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return STATUS_SUCCESS; +} + + +static NTSTATUS +__ipoib_close( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ) +{ + IPOIB_ENTER( IPOIB_DBG_IOCTL ); + + UNREFERENCED_PARAMETER( pDevObj ); + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = 0; + IoCompleteRequest( pIrp, IO_NO_INCREMENT ); + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return STATUS_SUCCESS; +} + + +static NTSTATUS +__ipoib_dispatch( + IN DEVICE_OBJECT* const pDevObj, + IN IRP* const pIrp ) +{ + IO_STACK_LOCATION *pIoStack; + NTSTATUS status = STATUS_SUCCESS; + + IPOIB_ENTER( IPOIB_DBG_IOCTL ); + + UNREFERENCED_PARAMETER( pDevObj ); + + pIoStack = IoGetCurrentIrpStackLocation( pIrp ); + + pIrp->IoStatus.Information = 0; + + switch( pIoStack->Parameters.DeviceIoControl.IoControlCode ) + { + case IOCTL_IBAT_PORTS: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_IOCTL, + ("IOCTL_IBAT_PORTS received\n") ); + status = __ibat_get_ports( pIrp, pIoStack ); + break; + + case IOCTL_IBAT_IP_ADDRESSES: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_IOCTL, + ("IOCTL_IBAT_IP_ADDRESSES received\n" )); + status = __ibat_get_ips( pIrp, pIoStack ); + break; + + case IOCTL_IBAT_MAC_TO_GID: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_IOCTL, + ("IOCTL_IBAT_MAC_TO_GID received\n" )); + status = __ibat_mac_to_gid( pIrp, pIoStack ); + break; + + case IOCTL_IBAT_IP_TO_PORT: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_IOCTL, + ("IOCTL_IBAT_IP_TO_PORT received\n" )); + status = __ibat_ip_to_port( pIrp, pIoStack ); + break; + + case IOCTL_IBAT_MAC_TO_PATH: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_IOCTL, + ("IOCTL_IBAT_MAC_TO_PATH received\n" )); + status = __ibat_mac_to_path( pIrp, pIoStack ); + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_VERBOSE, IPOIB_DBG_IOCTL, + ("unknow IOCTL code = 0x%x\n", + pIoStack->Parameters.DeviceIoControl.IoControlCode) ); + status = STATUS_INVALID_PARAMETER; + } + + pIrp->IoStatus.Status = status; + IoCompleteRequest( pIrp, IO_NO_INCREMENT ); + + IPOIB_EXIT( IPOIB_DBG_IOCTL ); + return status; +} + + diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_ibat.h b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_ibat.h new file mode 100644 index 00000000..efcb0a6b --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_ibat.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2005 Mellanox Technologies. All rights reserved. + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_ibat.h 1611 2006-08-20 14:48:55Z sleybo $ + */ + + +#ifndef _IPOIB_IBAT_H_ +#define _IPOIB_IBAT_H_ + + +void +ipoib_ref_ibat(); + +void +ipoib_deref_ibat(); + + +#endif /* _IPOIB_IBAT_H_ */ diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_log.mc b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_log.mc new file mode 100644 index 00000000..bb7d1c1d --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_log.mc @@ -0,0 +1,334 @@ +;/*++ +;============================================================================= +;Copyright (c) 2001 Mellanox Technologies +; +;Module Name: +; +; ipoiblog.mc +; +;Abstract: +; +; IPoIB Driver event log messages +; +;Authors: +; +; Yossi Leybovich +; +;Environment: +; +; Kernel Mode . +; +;============================================================================= +;--*/ +; +MessageIdTypedef = NDIS_ERROR_CODE + +SeverityNames = ( + Success = 0x0:STATUS_SEVERITY_SUCCESS + Informational = 0x1:STATUS_SEVERITY_INFORMATIONAL + Warning = 0x2:STATUS_SEVERITY_WARNING + Error = 0x3:STATUS_SEVERITY_ERROR + ) + +FacilityNames = ( + System = 0x0 + RpcRuntime = 0x2:FACILITY_RPC_RUNTIME + RpcStubs = 0x3:FACILITY_RPC_STUBS + Io = 0x4:FACILITY_IO_ERROR_CODE + IPoIB = 0x7:FACILITY_IPOIB_ERROR_CODE + ) + + +MessageId=0x0001 +Facility=IPoIB +Severity=Warning +SymbolicName=EVENT_IPOIB_PORT_DOWN +Language=English +%2: Network controller link is down. +. + +MessageId=0x0002 +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP +Language=English +%2: Network controller link is up. +. + + +MessageId=0x0003 +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP1 +Language=English +%2: Network controller link is up at 2.5Gbps. +. + +MessageId=0x0004 +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP2 +Language=English +%2: Network controller link is up at 5Gbps. +. + +MessageId=0x0006 +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP3 +Language=English +%2: Network controller link is up at 10Gbps. +. + +MessageId=0x000a +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP4 +Language=English +%2: Network controller link is up at 20Gps. +. + +MessageId=0x000e +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP5 +Language=English +%2: Network controller link is up at 30Gps. +. + +MessageId=0x0012 +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP6 +Language=English +%2: Network controller link is up at 40Gps. +. + +MessageId=0x001a +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP7 +Language=English +%2: Network controller link is up at 60Gps. +. + +MessageId=0x0032 +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_PORT_UP8 +Language=English +%2: Network controller link is up at 120Gps. +. + +MessageId=0x0040 +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_INIT_SUCCESS +Language=English +%2: Driver Initialized succesfully. +. + +MessageId=0x0041 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_OPEN_CA +Language=English +%2: Failed to open Channel Adapter. +. + +MessageId=0x0042 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_ALLOC_PD +Language=English +%2: Failed to allocate Protection Domain. +. + +MessageId=0x0043 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_CREATE_RECV_CQ +Language=English +%2: Failed to create receive Completion Queue. +. + +MessageId=0x0044 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_CREATE_SEND_CQ +Language=English +%2: Failed to create send Completion Queue. +. + +MessageId=0x0045 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_CREATE_QP +Language=English +%2: Failed to create Queue Pair. +. + +MessageId=0x0046 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_QUERY_QP +Language=English +%2: Failed to get Queue Pair number. +. + +MessageId=0x0047 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_REG_PHYS +Language=English +%2: Failed to create DMA Memory Region. +. + +MessageId=0x0048 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_RECV_POOL +Language=English +%2: Failed to create receive descriptor pool. +. + +MessageId=0x0049 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_RECV_PKT_POOL +Language=English +%2: Failed to create NDIS_PACKET pool for receive indications. +. + +MessageId=0x004A +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_RECV_BUF_POOL +Language=English +%2: Failed to create NDIS_BUFFER pool for receive indications. +. + +MessageId=0x004B +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_SEND_PKT_POOL +Language=English +%2: Failed to create NDIS_PACKET pool for send processing. +. + +MessageId=0x004C +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_SEND_BUF_POOL +Language=English +%2: Failed to create NDIS_BUFFER pool for send processing. +. + +MessageId=0x004D +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_RECV_PKT_ARRAY +Language=English +%2: Failed to allocate receive indication array. +. + +MessageId=0x004E +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_PORT_INFO_TIMEOUT +Language=English +%2: Subnet Administrator query for port information timed out. +Make sure the SA is functioning properly. Increasing the number +of retries and retry timeout adapter parameters may solve the +issue. +. + +MessageId=0x004F +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_PORT_INFO_REJECT +Language=English +%2: Subnet Administrator failed the query for port information. +Make sure the SA is functioning properly and compatible. +. + +MessageId=0x0050 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_QUERY_PORT_INFO +Language=English +%2: Subnet Administrator query for port information failed. +. + +MessageId=0x0055 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_BCAST_GET +Language=English +%2: Subnet Administrator failed query for broadcast group information. +. + +MessageId=0x0056 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_BCAST_JOIN +Language=English +%2: Subnet Administrator failed request to joing broadcast group. +. + +MessageId=0x0057 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_BCAST_RATE +Language=English +%2: The local port rate is too slow for the existing broadcast MC group. +. + +MessageId=0x0058 +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_WRONG_PARAMETER_ERR +Language=English +%2: Incorrect value or non-existing registry for the required IPoIB parameter %3, overriding it by default value: %4 +. + +MessageId=0x0059 +Facility=IPoIB +Severity=Warning +SymbolicName=EVENT_IPOIB_WRONG_PARAMETER_WRN +Language=English +%2: Incorrect value or non-existing registry entry for the required IPoIB parameter %3, overriding it by default value: %4 +. + +MessageId=0x005A +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_WRONG_PARAMETER_INFO +Language=English +%2: Incorrect value or non-existing registry for the optional IPoIB parameter %3, overriding it by default value: %4 +. + +MessageId=0x005B +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_PARTITION_ERR +Language=English +%2: Pkey index not found for partition , change switch pkey configuration. +. + +MessageId=0x005C +Facility=IPoIB +Severity=Error +SymbolicName=EVENT_IPOIB_CONNECTED_MODE_ERR +Language=English +%2: Connected Mode failed to initialize, disabled. Interface will use default UD QP transport. +. + +MessageId=0x005D +Facility=IPoIB +Severity=Informational +SymbolicName=EVENT_IPOIB_CONNECTED_MODE_UP +Language=English +%2: Connected Mode initialized and operational. +. + diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_port.c b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_port.c new file mode 100644 index 00000000..89e1a0c0 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_port.c @@ -0,0 +1,8123 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_port.c 4506 2009-06-23 14:40:54Z xalex $ + */ + + + +#include "ipoib_endpoint.h" +#include "ipoib_port.h" +#include "ipoib_adapter.h" +#include "ipoib_debug.h" +#if defined(EVENT_TRACING) +#ifdef offsetof +#undef offsetof +#endif +#include "ipoib_port.tmh" +#endif +#include + +#include "wdm.h" +#include + + + +ib_gid_t bcast_mgid_template = { + 0xff, /* multicast field */ + 0x12, /* scope (to be filled in) */ + 0x40, 0x1b, /* IPv4 signature */ + 0xff, 0xff, /* 16 bits of P_Key (to be filled in) */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 48 bits of zeros */ + 0xff, 0xff, 0xff, 0xff, /* 32 bit IPv4 broadcast address */ +}; + + +#ifdef _DEBUG_ +/* Handy pointer for debug use. */ +ipoib_port_t *gp_ipoib_port; +#endif + +static void __port_mcast_garbage_dpc(KDPC *p_gc_dpc,void *context,void *s_arg1, void *s_arg2); +static void __port_do_mcast_garbage(ipoib_port_t* const p_port ); + + +static void __recv_cb_dpc(KDPC *p_gc_dpc,void *context,void *s_arg1, void *s_arg2); + + +/****************************************************************************** +* +* Declarations +* +******************************************************************************/ +static void +__port_construct( + IN ipoib_port_t* const p_port ); + +static ib_api_status_t +__port_init( + IN ipoib_port_t* const p_port, + IN ipoib_adapter_t* const p_adapter, + IN ib_pnp_port_rec_t* const p_pnp_rec ); + +static void +__port_destroying( + IN cl_obj_t* const p_obj ); + +static void +__port_cleanup( + IN cl_obj_t* const p_obj ); + +static void +__port_free( + IN cl_obj_t* const p_obj ); + +static ib_api_status_t +__port_query_ca_attrs( + IN ipoib_port_t* const p_port, + IN ib_ca_attr_t** pp_ca_attrs ); + +static void +__srq_async_event_cb( +IN ib_async_event_rec_t *p_event_rec ); + +/****************************************************************************** +* +* IB resource manager operations +* +******************************************************************************/ +static void +__ib_mgr_construct( + IN ipoib_port_t* const p_port ); + +static ib_api_status_t +__ib_mgr_init( + IN ipoib_port_t* const p_port ); + +static void +__ib_mgr_destroy( + IN ipoib_port_t* const p_port ); + +static void +__qp_event( + IN ib_async_event_rec_t *p_event_rec ); + +static void +__cq_event( + IN ib_async_event_rec_t *p_event_rec ); + +static ib_api_status_t +__ib_mgr_activate( + IN ipoib_port_t* const p_port ); + +/****************************************************************************** +* +* Buffer manager operations. +* +******************************************************************************/ +static void +__buf_mgr_construct( + IN ipoib_port_t* const p_port ); + +static ib_api_status_t +__buf_mgr_init( + IN ipoib_port_t* const p_port ); + +static void +__buf_mgr_destroy( + IN ipoib_port_t* const p_port ); + +static cl_status_t +__recv_ctor( + IN void* const p_object, + IN void* context, + OUT cl_pool_item_t** const pp_pool_item ); + +#if !IPOIB_INLINE_RECV +static void +__recv_dtor( + IN const cl_pool_item_t* const p_pool_item, + IN void *context ); +#endif /* IPOIB_INLINE_RECV */ + +static inline ipoib_send_desc_t* +__buf_mgr_get_send( + IN ipoib_port_t* const p_port ); + +static inline void +__buf_mgr_put_send( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc ); + +static inline ipoib_recv_desc_t* +__buf_mgr_get_recv( + IN ipoib_port_t* const p_port ); + +static inline void +__buf_mgr_put_recv( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc, + IN NET_BUFFER_LIST* const p_net_buffer_list OPTIONAL ); + +static inline void +__buf_mgr_put_recv_list( + IN ipoib_port_t* const p_port, + IN cl_qlist_t* const p_list ); + +//NDIS60 +static inline NET_BUFFER_LIST* +__buf_mgr_get_ndis_pkt( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc ); + + +/****************************************************************************** +* +* Receive manager operations. +* +******************************************************************************/ +static void +__recv_mgr_construct( + IN ipoib_port_t* const p_port ); + +static ib_api_status_t +__recv_mgr_init( + IN ipoib_port_t* const p_port ); + +static void +__recv_mgr_destroy( + IN ipoib_port_t* const p_port ); + +/* Posts receive buffers to the receive queue. */ +static ib_api_status_t +__recv_mgr_repost( + IN ipoib_port_t* const p_port ); + +static void +__recv_cb( + IN const ib_cq_handle_t h_cq, + IN void *cq_context ); + +static void +__recv_get_endpts( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc, + IN ib_wc_t* const p_wc, + OUT ipoib_endpt_t** const pp_src, + OUT ipoib_endpt_t** const pp_dst ); + +static int32_t +__recv_mgr_filter( + IN ipoib_port_t* const p_port, + IN ib_wc_t* const p_done_wc_list, + OUT cl_qlist_t* const p_done_list, + OUT cl_qlist_t* const p_bad_list ); + +static ib_api_status_t +__recv_gen( + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src, + IN ipoib_endpt_t* const p_dst ); + +static ib_api_status_t +__recv_dhcp( + IN ipoib_port_t* const p_port, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src, + IN ipoib_endpt_t* const p_dst ); + +static ib_api_status_t +__recv_arp( + IN ipoib_port_t* const p_port, + IN ib_wc_t* const p_wc, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t** const p_src, + IN ipoib_endpt_t* const p_dst ); + +static ib_api_status_t +__recv_mgr_prepare_pkt( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc, + OUT NET_BUFFER_LIST** const pp_net_buffer_list ); + +static uint32_t +__recv_mgr_build_pkt_array( + IN ipoib_port_t* const p_port, + IN int32_t shortage, + OUT cl_qlist_t* const p_done_list, + OUT int32_t* const p_discarded ); + +/****************************************************************************** +* +* Send manager operations. +* +******************************************************************************/ +static void +__send_mgr_construct( + IN ipoib_port_t* const p_port ); + +static void +__send_mgr_destroy( + IN ipoib_port_t* const p_port ); + +static NDIS_STATUS +__send_gen( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc, + IN SCATTER_GATHER_LIST *p_sgl, + IN INT lso_data_index); + +static NDIS_STATUS +__send_mgr_filter_ip( + IN ipoib_port_t* const p_port, + IN const eth_hdr_t* const p_eth_hdr, + IN MDL* p_mdl, + IN size_t buf_len, + IN SCATTER_GATHER_LIST *p_sgl, + IN OUT ipoib_send_desc_t* const p_desc ); + +static NDIS_STATUS +__send_mgr_filter_igmp_v2( + IN ipoib_port_t* const p_port, + IN const ip_hdr_t* const p_ip_hdr, + IN size_t iph_options_size, + IN MDL* p_mdl, + IN size_t buf_len ); + +static NDIS_STATUS +__send_mgr_filter_udp( + IN ipoib_port_t* const p_port, + IN const ip_hdr_t* const p_ip_hdr, + IN MDL* p_mdl, + IN size_t buf_len, + IN SCATTER_GATHER_LIST *p_sgl, + IN OUT ipoib_send_desc_t* const p_desc ); + +static NDIS_STATUS +__send_mgr_filter_dhcp( + IN ipoib_port_t* const p_port, + IN const udp_hdr_t* const p_udp_hdr, + IN MDL* p_mdl, + IN size_t buf_len, + IN OUT ipoib_send_desc_t* const p_desc ); + +static NDIS_STATUS +__send_mgr_filter_arp( + IN ipoib_port_t* const p_port, + IN const eth_hdr_t* const p_eth_hdr, + IN MDL* p_mdl, + IN size_t buf_len, + IN OUT ipoib_send_desc_t* const p_desc ); + +static void +__process_failed_send( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc, + IN const NDIS_STATUS status, + IN ULONG send_complete_flags ); + +static inline NDIS_STATUS +__send_mgr_queue( + IN ipoib_port_t* const p_port, + IN eth_hdr_t* const p_eth_hdr, + OUT ipoib_endpt_t** const pp_endpt ); + +static NDIS_STATUS +__build_send_desc( + IN ipoib_port_t* const p_port, + IN eth_hdr_t* const p_eth_hdr, + IN MDL* const p_mdl, + IN const size_t mdl_len, + IN SCATTER_GATHER_LIST *p_sgl, + IN OUT ipoib_send_desc_t* const p_desc ); + + +static void +__send_cb( + IN const ib_cq_handle_t h_cq, + IN void *cq_context ); + +static NDIS_STATUS +GetLsoHeaderSize( + IN PNET_BUFFER pNetBuffer, + IN LsoData *pLsoData, + OUT UINT *IndexOfData, + IN ipoib_hdr_t *ipoib_hdr ); + + +static NDIS_STATUS +__build_lso_desc( + IN ipoib_port_t* const p_port, + IN OUT ipoib_send_desc_t* const p_desc, + IN ULONG mss, + IN SCATTER_GATHER_LIST *p_sgl, + IN int32_t hdr_idx, + IN PNDIS_TCP_LARGE_SEND_OFFLOAD_NET_BUFFER_LIST_INFO p_lso_info ); + +static NDIS_STATUS +__send_fragments( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc, + IN eth_hdr_t* const p_eth_hdr, + IN ip_hdr_t* const p_ip_hdr, + IN uint32_t buf_len, + IN NDIS_BUFFER* p_ndis_buf ); + +static void +__update_fragment_ip_hdr( +IN ip_hdr_t* const p_ip_hdr, +IN uint16_t fragment_size, +IN uint16_t fragment_offset, +IN BOOLEAN more_fragments ); + +static void +__copy_ip_options( +IN uint8_t* p_buf, +IN uint8_t* p_options, +IN uint32_t options_len, +IN BOOLEAN copy_all ); +/****************************************************************************** +* +* Endpoint manager operations +* +******************************************************************************/ +static void +__endpt_mgr_construct( + IN ipoib_port_t* const p_port ); + +static ib_api_status_t +__endpt_mgr_init( + IN ipoib_port_t* const p_port ); + +static void +__endpt_mgr_destroy( + IN ipoib_port_t* const p_port ); + +/****f* IPoIB/__endpt_mgr_remove_all +* NAME +* __endpt_mgr_remove_all +* +* DESCRIPTION +* Removes all enpoints from the port, dereferencing them to initiate +* destruction. +* +* SYNOPSIS +*/ +static void +__endpt_mgr_remove_all( + IN ipoib_port_t* const p_port ); +/* +********/ + +static void +__endpt_mgr_remove( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt ); + +static void +__endpt_mgr_reset_all( + IN ipoib_port_t* const p_port ); + +static inline NDIS_STATUS +__endpt_mgr_ref( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ipoib_endpt_t** const pp_endpt ); + +static inline NDIS_STATUS +__endpt_mgr_get_gid_qpn( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ib_gid_t* const p_gid, + OUT UNALIGNED net32_t* const p_qpn ); + +static inline ipoib_endpt_t* +__endpt_mgr_get_by_gid( + IN ipoib_port_t* const p_port, + IN const ib_gid_t* const p_gid ); + +static inline ipoib_endpt_t* +__endpt_mgr_get_by_lid( + IN ipoib_port_t* const p_port, + IN const net16_t lid ); + +static inline ib_api_status_t +__endpt_mgr_insert_locked( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + IN ipoib_endpt_t* const p_endpt ); + +static inline ib_api_status_t +__endpt_mgr_insert( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + IN ipoib_endpt_t* const p_endpt ); + +static ib_api_status_t +__endpt_mgr_add_local( + IN ipoib_port_t* const p_port, + IN ib_port_info_t* const p_port_info ); + +static ib_api_status_t +__endpt_mgr_add_bcast( + IN ipoib_port_t* const p_port, + IN ib_mcast_rec_t *p_mcast_rec ); + +/****************************************************************************** +* +* MCast operations. +* +******************************************************************************/ +static ib_api_status_t +__port_get_bcast( + IN ipoib_port_t* const p_port ); + +static ib_api_status_t +__port_join_bcast( + IN ipoib_port_t* const p_port, + IN ib_member_rec_t* const p_member_rec ); + +static ib_api_status_t +__port_create_bcast( + IN ipoib_port_t* const p_port ); + + + +static void +__bcast_get_cb( + IN ib_query_rec_t *p_query_rec ); + + +static void +__bcast_cb( + IN ib_mcast_rec_t *p_mcast_rec ); + + +static void +__mcast_cb( + IN ib_mcast_rec_t *p_mcast_rec ); + +void +__leave_error_mcast_cb( + IN void *context ); + + +static intn_t +__gid_cmp( + IN const void* const p_key1, + IN const void* const p_key2 ) +{ + return cl_memcmp( p_key1, p_key2, sizeof(ib_gid_t) ); +} + + +inline void ipoib_port_ref( ipoib_port_t * p_port, int type ) +{ + cl_obj_ref( &p_port->obj ); +#if DBG + cl_atomic_inc( &p_port->ref[type % ref_mask] ); + if ((p_port->obj.ref_cnt % 20)==0) + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OBJ, + ("ref type %d ref_cnt %d\n", type, p_port->obj.ref_cnt) ); + //TODO remove + //ASSERT (p_port->obj.ref_cnt < 100); +#else + UNREFERENCED_PARAMETER(type); +#endif +} + + +inline void ipoib_port_deref(ipoib_port_t * p_port, int type) +{ +#if DBG + cl_atomic_dec( &p_port->ref[type % ref_mask] ); + if ((p_port->obj.ref_cnt % 20) == 0) + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OBJ, + ("deref type %d ref_cnt %d\n", type, p_port->obj.ref_cnt) ); +#else + UNREFERENCED_PARAMETER(type); +#endif + cl_obj_deref( &p_port->obj ); + +} + +/* function returns pointer to payload that is going after IP header. +* asssuming that payload and IP header are in the same buffer +*/ +static void* GetIpPayloadPtr(const ip_hdr_t* const p_ip_hdr) +{ + return (void*)((uint8_t*)p_ip_hdr + IP_HEADER_LENGTH(p_ip_hdr)); +} + +/****************************************************************************** +* +* Implementation +* +******************************************************************************/ +ib_api_status_t +ipoib_create_port( + IN ipoib_adapter_t* const p_adapter, + IN ib_pnp_port_rec_t* const p_pnp_rec, + OUT ipoib_port_t** const pp_port ) +{ + ib_api_status_t status; + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( !p_adapter->p_port ); + + p_port = cl_zalloc( sizeof(ipoib_port_t) + + (sizeof(ipoib_hdr_t) * (p_adapter->params.sq_depth - 1)) ); + if( !p_port ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate ipoib_port_t (%d bytes)\n", + sizeof(ipoib_port_t)) ); + return IB_INSUFFICIENT_MEMORY; + } + +#ifdef _DEBUG_ + gp_ipoib_port = p_port; +#endif + + __port_construct( p_port ); + + status = __port_init( p_port, p_adapter, p_pnp_rec ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_port_init returned %s.\n", + p_adapter->p_ifc->get_err_str( status )) ); + __port_cleanup( &p_port->obj ); + __port_free( &p_port->obj ); + return status; + } + + *pp_port = p_port; + IPOIB_EXIT( IPOIB_DBG_INIT ); + return IB_SUCCESS; +} + + +void +ipoib_port_destroy( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( p_port ); + CL_ASSERT( p_port->p_adapter ); + CL_ASSERT( !p_port->p_adapter->p_port ); + + cl_obj_destroy( &p_port->obj ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +__port_construct( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_port->state = IB_QPS_RESET; + + cl_obj_construct( &p_port->obj, IPOIB_OBJ_PORT ); + cl_spinlock_construct( &p_port->send_lock ); + cl_spinlock_construct( &p_port->recv_lock ); + __ib_mgr_construct( p_port ); + __buf_mgr_construct( p_port ); + + __recv_mgr_construct( p_port ); + __send_mgr_construct( p_port ); + + __endpt_mgr_construct( p_port ); + + KeInitializeEvent( &p_port->sa_event, NotificationEvent, TRUE ); + KeInitializeEvent( &p_port->leave_mcast_event, NotificationEvent, TRUE ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static ib_api_status_t +__port_init( + IN ipoib_port_t* const p_port, + IN ipoib_adapter_t* const p_adapter, + IN ib_pnp_port_rec_t* const p_pnp_rec ) +{ + cl_status_t cl_status; + ib_api_status_t status; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_port->port_num = p_pnp_rec->p_port_attr->port_num; + p_port->p_adapter = p_adapter; + + cl_status = cl_spinlock_init( &p_port->send_lock ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_spinlock_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + cl_status = cl_spinlock_init( &p_port->recv_lock ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_spinlock_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + /* Initialize the IB resource manager. */ + status = __ib_mgr_init( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__ib_mgr_init returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Initialize the buffer manager. */ + status = __buf_mgr_init( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__buf_mgr_init returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Initialize the receive manager. */ + status = __recv_mgr_init( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__recv_mgr_init returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Initialize the endpoint manager. */ + status = __endpt_mgr_init( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__endpt_mgr_init returned %s\n", + p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + KeInitializeDpc(&p_port->recv_dpc,(PKDEFERRED_ROUTINE)__recv_cb_dpc,p_port); + + + /* Initialize multicast garbage collector timer and DPC object */ + KeInitializeDpc(&p_port->gc_dpc,(PKDEFERRED_ROUTINE)__port_mcast_garbage_dpc,p_port); + KeInitializeTimerEx(&p_port->gc_timer,SynchronizationTimer); + + /* We only ever destroy from the PnP callback thread. */ + cl_status = cl_obj_init( &p_port->obj, CL_DESTROY_SYNC, + __port_destroying, __port_cleanup, __port_free ); + +#if DBG + cl_atomic_inc( &p_port->ref[ref_init] ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OBJ, + ("ref type %d ref_cnt %d\n", ref_init, p_port->obj.ref_cnt) ); +#endif + + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_obj_init returned %#x\n", cl_status) ); + return IB_ERROR; + } + + cl_status = cl_obj_insert_rel( &p_port->rel, &p_adapter->obj, &p_port->obj ); + if( cl_status != CL_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_obj_insert_rel returned %#x\n", cl_status) ); + cl_obj_destroy( &p_port->obj ); + return IB_ERROR; + } + +#if DBG + cl_atomic_inc( &p_port->ref[ref_init] ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_OBJ, + ("ref type %d ref_cnt %d\n", ref_init, p_port->obj.ref_cnt) ); +#endif + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return IB_SUCCESS; +} + + +static void +__port_destroying( + IN cl_obj_t* const p_obj ) +{ + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( p_obj ); + + p_port = PARENT_STRUCT( p_obj, ipoib_port_t, obj ); + + ipoib_port_down( p_port ); + + __endpt_mgr_remove_all( p_port ); + +#if 0 + if( p_port->p_adapter->params.cm_enabled ) + { + endpt_cm_buf_mgr_destroy( p_port ); + ipoib_port_srq_destroy( p_port ); + p_port->endpt_mgr.thread_is_done = 1; + cl_event_signal( &p_port->endpt_mgr.event ); + } +#endif + ASSERT(FALSE); + //TODO NDIS6.0 + ipoib_port_resume( p_port, FALSE ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +__port_cleanup( + IN cl_obj_t* const p_obj ) +{ + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( p_obj ); + + p_port = PARENT_STRUCT( p_obj, ipoib_port_t, obj ); + + /* Wait for all sends and receives to get flushed. */ + while( p_port->send_mgr.depth || p_port->recv_mgr.depth ) + cl_thread_suspend( 0 ); + + /* Destroy the send and receive managers before closing the CA. */ + __ib_mgr_destroy( p_port ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +__port_free( + IN cl_obj_t* const p_obj ) +{ + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( p_obj ); + + p_port = PARENT_STRUCT( p_obj, ipoib_port_t, obj ); + + KeCancelTimer(&p_port->gc_timer); + KeFlushQueuedDpcs(); + __endpt_mgr_destroy( p_port ); + __recv_mgr_destroy( p_port ); + __send_mgr_destroy( p_port ); + __buf_mgr_destroy( p_port ); + + cl_spinlock_destroy( &p_port->send_lock ); + cl_spinlock_destroy( &p_port->recv_lock ); + + cl_obj_deinit( p_obj ); + if( p_port->p_ca_attrs ) + { + cl_free ( p_port->p_ca_attrs ); + } + cl_free( p_port ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + + +/****************************************************************************** +* +* IB resource manager implementation. +* +******************************************************************************/ +static void +__ib_mgr_construct( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + cl_memclr( &p_port->ib_mgr, sizeof(ipoib_ib_mgr_t) ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static ib_api_status_t +__ib_mgr_init( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t status; + ib_cq_create_t cq_create; + ib_qp_create_t qp_create; + ib_phys_create_t phys_create; + ib_phys_range_t phys_range; + uint64_t vaddr; + net32_t rkey; + ib_qp_attr_t qp_attr; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + /* Open the CA. */ + status = p_port->p_adapter->p_ifc->open_ca( + p_port->p_adapter->h_al, p_port->p_adapter->guids.ca_guid, + NULL, p_port, &p_port->ib_mgr.h_ca ); + if( status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_OPEN_CA, 1, status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_open_ca returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + status = __port_query_ca_attrs( p_port, &p_port->p_ca_attrs ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Query CA attributes failed\n" ) ); + return status; + } +#if 0 + if( p_port->p_adapter->params.cm_enabled ) + { + uint32_t payload_mtu = __port_attr_to_mtu_size( + p_port->p_ca_attrs->p_port_attr[p_port->port_num - 1].mtu ) + - sizeof(ipoib_hdr_t); + /* adjust ipoib UD payload MTU to actual port MTU size. */ + p_port->p_adapter->params.payload_mtu = + max( DEFAULT_PAYLOAD_MTU, payload_mtu ); + p_port->p_adapter->params.xfer_block_size = + (sizeof(eth_hdr_t) + p_port->p_adapter->params.payload_mtu); + } +#endif +#if IPOIB_USE_DMA + /* init DMA only once while running MiniportInitialize */ + if ( !p_port->p_adapter->reset ) + { + ULONG max_phys_mapping; + if( p_port->p_adapter->params.cm_enabled ) + { + max_phys_mapping = p_port->p_adapter->params.cm_xfer_block_size; + } + else if( p_port->p_adapter->params.lso ) + { + max_phys_mapping = LARGE_SEND_OFFLOAD_SIZE; + } + else + { + max_phys_mapping = p_port->p_adapter->params.xfer_block_size; + } + /*if( NdisMInitializeScatterGatherDma( p_port->p_adapter->h_adapter, + TRUE, max_phys_mapping )!= NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisMInitializeScatterGatherDma failed\n" ) ); + return IB_INSUFFICIENT_RESOURCES; + }*/ + } +#endif + + /* Allocate the PD. */ + status = p_port->p_adapter->p_ifc->alloc_pd( + p_port->ib_mgr.h_ca, IB_PDT_UD, p_port, &p_port->ib_mgr.h_pd ); + if( status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_ALLOC_PD, 1, status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_alloc_pd returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Allocate receive CQ. */ + cq_create.size = p_port->p_adapter->params.rq_depth; + cq_create.pfn_comp_cb = __recv_cb; + cq_create.h_wait_obj = NULL; + + status = p_port->p_adapter->p_ifc->create_cq( + p_port->ib_mgr.h_ca, &cq_create, p_port, + __cq_event, &p_port->ib_mgr.h_recv_cq ); + if( status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_CREATE_RECV_CQ, 1, status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_create_cq returned %s.\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Allocate send CQ. */ + cq_create.size = p_port->p_adapter->params.sq_depth; + cq_create.pfn_comp_cb = __send_cb; + + status = p_port->p_adapter->p_ifc->create_cq( + p_port->ib_mgr.h_ca, &cq_create, p_port, + __cq_event, &p_port->ib_mgr.h_send_cq ); + if( status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_CREATE_SEND_CQ, 1, status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_create_cq returned %s.\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Allocate the QP. */ + cl_memclr( &qp_create, sizeof(qp_create) ); + qp_create.qp_type = IB_QPT_UNRELIABLE_DGRM; + qp_create.rq_depth = p_port->p_adapter->params.rq_depth; + qp_create.rq_sge = 2; /* To support buffers spanning pages. */ + qp_create.h_rq_cq = p_port->ib_mgr.h_recv_cq; + qp_create.sq_depth = p_port->p_adapter->params.sq_depth; + +#define UD_QP_USED_SGE 3 + qp_create.sq_sge = MAX_SEND_SGE < p_port->p_ca_attrs->max_sges ? + MAX_SEND_SGE : ( p_port->p_ca_attrs->max_sges - UD_QP_USED_SGE ); + if ( !p_port->p_ca_attrs->ipoib_csum ) + { + /* checksum is not supported by device + user must specify BYPASS to explicitly cancel checksum calculation */ + if (p_port->p_adapter->params.send_chksum_offload == CSUM_ENABLED) + p_port->p_adapter->params.send_chksum_offload = CSUM_DISABLED; + if (p_port->p_adapter->params.recv_chksum_offload == CSUM_ENABLED) + p_port->p_adapter->params.recv_chksum_offload = CSUM_DISABLED; + } + + qp_create.h_sq_cq = p_port->ib_mgr.h_send_cq; + qp_create.sq_signaled = FALSE; + status = p_port->p_adapter->p_ifc->create_qp( + p_port->ib_mgr.h_pd, &qp_create, p_port, + __qp_event, &p_port->ib_mgr.h_qp ); + if( status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_CREATE_QP, 1, status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_create_qp returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + /* Query the QP so we can get our QPN. */ + status = p_port->p_adapter->p_ifc->query_qp( + p_port->ib_mgr.h_qp, &qp_attr ); + if( status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_QUERY_QP, 1, status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_query_qp returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + p_port->ib_mgr.qpn = qp_attr.num; + + /* Register all of physical memory */ + phys_create.length = MEM_REG_SIZE; + phys_create.num_ranges = 1; + phys_create.range_array = &phys_range; + phys_create.buf_offset = 0; + phys_create.hca_page_size = PAGE_SIZE; + phys_create.access_ctrl = IB_AC_LOCAL_WRITE; + phys_range.base_addr = 0; + phys_range.size = MEM_REG_SIZE; + vaddr = 0; + status = p_port->p_adapter->p_ifc->reg_phys( + p_port->ib_mgr.h_pd, &phys_create, &vaddr, + &p_port->ib_mgr.lkey, &rkey, &p_port->ib_mgr.h_mr ); + if( status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_REG_PHYS, 1, status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_reg_phys returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + status = ipoib_port_srq_init( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_port_srq_init failed %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + /* disable further CM initialization */ + p_port->p_adapter->params.cm_enabled = FALSE; + + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_CONNECTED_MODE_ERR, 1, 0xbadc0de1 ); + + } +//CM +#if 0 + if( p_port->p_adapter->params.cm_enabled ) + { + status = endpt_cm_buf_mgr_init( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("CM Init buf mgr failed status %#x\n", status ) ); + ipoib_port_srq_destroy( p_port ); + p_port->p_adapter->params.cm_enabled = FALSE; + + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_CONNECTED_MODE_ERR, 1, 0xbadc0de2 ); + } + else + { + if ( p_port->p_adapter->params.send_chksum_offload ) + p_port->p_adapter->params.send_chksum_offload = CSUM_DISABLED; + } + } +#endif + IPOIB_EXIT( IPOIB_DBG_INIT ); + return IB_SUCCESS; +} + +static void +__srq_async_event_cb( +IN ib_async_event_rec_t *p_event_rec ) +{ + ipoib_port_t* p_port = + (ipoib_port_t *)p_event_rec->context; + + switch( p_event_rec->code ) + { + case IB_AE_SRQ_LIMIT_REACHED: + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("SRQ ASYNC EVENT CODE %d: %s\n", + p_event_rec->code, "IB_AE_SRQ_LIMIT_REACHED" ) ); + break; + case IB_AE_SRQ_CATAS_ERROR: + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("SRQ ASYNC EVENT CODE %d: %s\n", + p_event_rec->code, "IB_AE_SRQ_CATAS_ERROR" ) ); + /*SRQ is in err state, must reinitialize */ + p_port->p_adapter->hung = TRUE; + break; + case IB_AE_SRQ_QP_LAST_WQE_REACHED: + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("SRQ ASYNC EVENT CODE %d: %s\n", + p_event_rec->code, "IB_AE_SRQ_QP_LAST_WQE_REACHED" ) ); + /*SRQ is in err state, must reinitialize */ + p_port->p_adapter->hung = TRUE; + break; + default: + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ASYNC EVENT CODE ARRIVED %d(%#x)\n", + p_event_rec->code, p_event_rec->code ) ); + } +} + +ib_api_status_t +ipoib_port_srq_init( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t ib_status; + ib_srq_handle_t h_srq; + ib_srq_attr_t srq_attr; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + if( !p_port->p_adapter->params.cm_enabled ) + return IB_SUCCESS; + + srq_attr.max_sge = min( 2, p_port->p_ca_attrs->max_srq_sges ); + srq_attr.srq_limit = 10; + srq_attr.max_wr = + min( (uint32_t)p_port->p_adapter->params.rq_depth * 8, + p_port->p_ca_attrs->max_srq_wrs/2 ); + + ib_status = p_port->p_adapter->p_ifc->create_srq( + p_port->ib_mgr.h_pd, + &srq_attr, + p_port, + __srq_async_event_cb, + &h_srq ); + if( ib_status != IB_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_CREATE_QP, 1, ib_status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_create_srq failed status %s\n", + p_port->p_adapter->p_ifc->get_err_str( ib_status )) ); + return ib_status; + } + p_port->ib_mgr.h_srq = h_srq; + + IPOIB_EXIT( IPOIB_DBG_INIT ); + + return ib_status; +} + +/* __port_query_ca_attrs() + * returns a pointer to allocated memory. + * must be released by caller. + */ +static ib_api_status_t +__port_query_ca_attrs( + IN ipoib_port_t* const p_port, + IN ib_ca_attr_t** pp_ca_attrs ) +{ + ib_api_status_t ib_status; + uint32_t attr_size; + ib_ca_attr_t* p_ca_attrs; + + *pp_ca_attrs = NULL; + + ib_status = + p_port->p_adapter->p_ifc->query_ca( p_port->ib_mgr.h_ca, NULL , &attr_size ); + if( ib_status != IB_INSUFFICIENT_MEMORY ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_query_ca failed status %s\n", + p_port->p_adapter->p_ifc->get_err_str( ib_status )) ); + goto done; + } + CL_ASSERT( attr_size ); + + p_ca_attrs = cl_zalloc( attr_size ); + if ( p_ca_attrs == NULL ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Allocate %d bytes failed for CA Attributes\n", attr_size )); + ib_status = IB_INSUFFICIENT_MEMORY; + goto done; + } + + ib_status = + p_port->p_adapter->p_ifc->query_ca( p_port->ib_mgr.h_ca, p_ca_attrs , &attr_size ); + if ( ib_status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("CA attributes query failed\n") ); + cl_free ( p_ca_attrs ); + goto done; + } + + *pp_ca_attrs = p_ca_attrs; +done: + return ib_status; +} + +void +ipoib_port_srq_destroy( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t status; + + if( p_port->ib_mgr.h_srq ) + { + status = + p_port->p_adapter->p_ifc->destroy_srq( p_port->ib_mgr.h_srq, NULL ); + CL_ASSERT( status == IB_SUCCESS ); + p_port->ib_mgr.h_srq = NULL; + } +} + +static void +__ib_mgr_destroy( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t status; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + if( p_port->ib_mgr.h_ca ) + { + status = + p_port->p_adapter->p_ifc->close_ca( p_port->ib_mgr.h_ca, NULL ); + CL_ASSERT( status == IB_SUCCESS ); + p_port->ib_mgr.h_ca = NULL; + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + + +/****************************************************************************** +* +* Buffer manager implementation. +* +******************************************************************************/ +static void +__buf_mgr_construct( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + cl_qpool_construct( &p_port->buf_mgr.recv_pool ); + + p_port->buf_mgr.h_packet_pool = NULL; + p_port->buf_mgr.h_buffer_pool = NULL; + + NdisInitializeNPagedLookasideList( &p_port->buf_mgr.send_buf_list, + NULL, NULL, 0, MAX_XFER_BLOCK_SIZE, 'bipi', 0 ); + + p_port->buf_mgr.h_send_pkt_pool = NULL; + p_port->buf_mgr.h_send_buf_pool = NULL; + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static ib_api_status_t +__buf_mgr_init( + IN ipoib_port_t* const p_port ) +{ + cl_status_t cl_status; + ipoib_params_t *p_params; + NET_BUFFER_LIST_POOL_PARAMETERS pool_parameters; + IPOIB_ENTER(IPOIB_DBG_INIT ); + + CL_ASSERT( p_port ); + CL_ASSERT( p_port->p_adapter ); + + p_params = &p_port->p_adapter->params; + + /* Allocate the receive descriptor pool */ + cl_status = cl_qpool_init( &p_port->buf_mgr.recv_pool, + p_params->rq_depth * p_params->recv_pool_ratio, +#if IPOIB_INLINE_RECV + 0, 0, sizeof(ipoib_recv_desc_t), __recv_ctor, NULL, p_port ); +#else /* IPOIB_INLINE_RECV */ + 0, 0, sizeof(ipoib_recv_desc_t), __recv_ctor, __recv_dtor, p_port ); +#endif /* IPOIB_INLINE_RECV */ + if( cl_status != CL_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_RECV_POOL, 1, cl_status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_qpool_init for recvs returned %#x\n", + cl_status) ); + return IB_INSUFFICIENT_MEMORY; + } + + /* Allocate the NET BUFFER list pools for receive indication. */ + NdisZeroMemory(&pool_parameters, sizeof(NET_BUFFER_LIST_POOL_PARAMETERS)); + pool_parameters.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + pool_parameters.Header.Revision = NET_BUFFER_LIST_POOL_PARAMETERS_REVISION_1; + pool_parameters.Header.Size = sizeof(pool_parameters); + pool_parameters.ProtocolId = 0; + pool_parameters.ContextSize = 0; + pool_parameters.fAllocateNetBuffer = TRUE; + pool_parameters.PoolTag = 'CRPI'; + + p_port->buf_mgr.h_packet_pool = NdisAllocateNetBufferListPool( + p_port->p_adapter->h_adapter, + &pool_parameters); + + if( !p_port->buf_mgr.h_packet_pool ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_RECV_PKT_POOL, 1, NDIS_STATUS_RESOURCES ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisAllocatePacketPool returned %08X\n", (UINT)NDIS_STATUS_RESOURCES) ); + return IB_INSUFFICIENT_RESOURCES; + } +/* + NdisAllocateBufferPool( &ndis_status, &p_port->buf_mgr.h_buffer_pool, + p_params->rq_depth ); + if( ndis_status != NDIS_STATUS_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_RECV_BUF_POOL, 1, ndis_status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisAllocateBufferPool returned %08X\n", ndis_status) ); + return IB_INSUFFICIENT_RESOURCES; + } +*/ + /* Allocate the NET buffer list pool for send formatting. */ + pool_parameters.PoolTag = 'XTPI'; + + p_port->buf_mgr.h_send_pkt_pool = NdisAllocateNetBufferListPool( + p_port->p_adapter->h_adapter, + &pool_parameters); + if( !p_port->buf_mgr.h_send_pkt_pool) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_SEND_PKT_POOL, 1, NDIS_STATUS_RESOURCES ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisAllocatePacketPool returned %08X\n", (UINT)NDIS_STATUS_RESOURCES) ); + return IB_INSUFFICIENT_RESOURCES; + } +/* + NdisAllocateBufferPool( &ndis_status, + &p_port->buf_mgr.h_send_buf_pool, 1 ); + if( ndis_status != NDIS_STATUS_SUCCESS ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_SEND_BUF_POOL, 1, ndis_status ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisAllocateBufferPool returned %08X\n", ndis_status) ); + return IB_INSUFFICIENT_RESOURCES; + } +*/ + IPOIB_EXIT( IPOIB_DBG_INIT ); + return IB_SUCCESS; +} + + +static void +__buf_mgr_destroy( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER(IPOIB_DBG_INIT ); + + CL_ASSERT( p_port ); + + /* Destroy the send packet and buffer pools. + if( p_port->buf_mgr.h_send_buf_pool ) + NdisFreeBufferPool( p_port->buf_mgr.h_send_buf_pool );*/ + if( p_port->buf_mgr.h_send_pkt_pool ) + NdisFreeNetBufferListPool ( p_port->buf_mgr.h_send_pkt_pool ); + + /* Destroy the receive packet and buffer pools. + if( p_port->buf_mgr.h_buffer_pool ) + NdisFreeBufferPool( p_port->buf_mgr.h_buffer_pool );*/ + if( p_port->buf_mgr.h_packet_pool ) + NdisFreeNetBufferListPool ( p_port->buf_mgr.h_packet_pool ); + + /* Free the receive and send descriptors. */ + cl_qpool_destroy( &p_port->buf_mgr.recv_pool ); + + /* Free the lookaside list of scratch buffers. */ + NdisDeleteNPagedLookasideList( &p_port->buf_mgr.send_buf_list ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static cl_status_t +__recv_ctor( + IN void* const p_object, + IN void* context, + OUT cl_pool_item_t** const pp_pool_item ) +{ + ipoib_recv_desc_t *p_desc; + ipoib_port_t *p_port; + +#if IPOIB_INLINE_RECV + uint32_t ds0_len; +#endif + + IPOIB_ENTER( IPOIB_DBG_ALLOC ); + + CL_ASSERT( p_object ); + CL_ASSERT( context ); + + p_desc = (ipoib_recv_desc_t*)p_object; + p_port = (ipoib_port_t*)context; + + /* Setup the work request. */ + p_desc->wr.ds_array = p_desc->local_ds; + p_desc->wr.wr_id = (uintn_t)p_desc; + +#if IPOIB_INLINE_RECV + /* Sanity check on the receive buffer layout */ + CL_ASSERT( (void*)&p_desc->buf.eth.pkt.type == + (void*)&p_desc->buf.ib.pkt.type ); + CL_ASSERT( sizeof(recv_buf_t) == sizeof(ipoib_pkt_t) + sizeof(ib_grh_t) ); + + /* Setup the local data segment. */ + p_desc->local_ds[0].vaddr = cl_get_physaddr( &p_desc->buf ); + p_desc->local_ds[0].lkey = p_port->ib_mgr.lkey; + ds0_len = + PAGE_SIZE - ((uint32_t)p_desc->local_ds[0].vaddr & (PAGE_SIZE - 1)); + if( ds0_len >= sizeof(recv_buf_t) ) + { + /* The whole buffer is within a page. */ + p_desc->local_ds[0].length = ds0_len; + p_desc->wr.num_ds = 1; + } + else + { + /* The buffer crosses page boundaries. */ + p_desc->local_ds[0].length = ds0_len; + p_desc->local_ds[1].vaddr = cl_get_physaddr( + ((uint8_t*)&p_desc->buf) + ds0_len ); + p_desc->local_ds[1].lkey = p_port->ib_mgr.lkey; + p_desc->local_ds[1].length = sizeof(recv_buf_t) - ds0_len; + p_desc->wr.num_ds = 2; + } +#else /* IPOIB_INLINE_RECV */ + /* Allocate the receive buffer. */ + p_desc->p_buf = (recv_buf_t*)cl_zalloc( sizeof(recv_buf_t) ); + if( !p_desc->p_buf ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate receive buffer.\n") ); + return CL_INSUFFICIENT_MEMORY; + } + + /* Sanity check on the receive buffer layout */ + CL_ASSERT( (void*)&p_desc->p_buf->eth.pkt.type == + (void*)&p_desc->p_buf->ib.pkt.type ); + + /* Setup the local data segment. */ + p_desc->local_ds[0].vaddr = cl_get_physaddr( p_desc->p_buf ); + p_desc->local_ds[0].length = sizeof(ipoib_pkt_t) + sizeof(ib_grh_t); + p_desc->local_ds[0].lkey = p_port->ib_mgr.lkey; + p_desc->wr.num_ds = 1; +#endif /* IPOIB_INLINE_RECV */ + + *pp_pool_item = &p_desc->item; + + IPOIB_EXIT( IPOIB_DBG_ALLOC ); + return CL_SUCCESS; +} + + +#if !IPOIB_INLINE_RECV +static void +__recv_dtor( + IN const cl_pool_item_t* const p_pool_item, + IN void *context ) +{ + ipoib_recv_desc_t *p_desc; + + IPOIB_ENTER( IPOIB_DBG_ALLOC ); + + UNUSED_PARAM( context ); + + p_desc = PARENT_STRUCT( p_pool_item, ipoib_recv_desc_t, item ); + + if( p_desc->p_buf ) + cl_free( p_desc->p_buf ); + + IPOIB_EXIT( IPOIB_DBG_ALLOC ); +} +#endif + + +static inline ipoib_recv_desc_t* +__buf_mgr_get_recv( + IN ipoib_port_t* const p_port ) +{ + ipoib_recv_desc_t *p_desc; + IPOIB_ENTER( IPOIB_DBG_RECV ); + p_desc = (ipoib_recv_desc_t*)cl_qpool_get( &p_port->buf_mgr.recv_pool ); + /* Reference the port object for the send. */ + if( p_desc ) + { + ipoib_port_ref( p_port, ref_get_recv ); + CL_ASSERT( p_desc->wr.wr_id == (uintn_t)p_desc ); +#if IPOIB_INLINE_RECV + CL_ASSERT( p_desc->local_ds[0].vaddr == + cl_get_physaddr( &p_desc->buf ) ); +#else /* IPOIB_INLINE_RECV */ + CL_ASSERT( p_desc->local_ds[0].vaddr == + cl_get_physaddr( p_desc->p_buf ) ); + CL_ASSERT( p_desc->local_ds[0].length == + (sizeof(ipoib_pkt_t) + sizeof(ib_grh_t)) ); +#endif /* IPOIB_INLINE_RECV */ + CL_ASSERT( p_desc->local_ds[0].lkey == p_port->ib_mgr.lkey ); + } + IPOIB_EXIT( IPOIB_DBG_RECV ); + return p_desc; +} + + +//NDIS60 +static inline void +__buf_mgr_put_recv( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc, + IN NET_BUFFER_LIST* const p_net_buffer_list OPTIONAL ) +{ + NET_BUFFER *p_buf = NULL; + MDL *p_mdl = NULL; + IPOIB_ENTER(IPOIB_DBG_RECV ); + + if( p_net_buffer_list ) + { + NET_BUFFER_LIST_NEXT_NBL(p_net_buffer_list) = NULL; + p_buf = NET_BUFFER_LIST_FIRST_NB(p_net_buffer_list); + CL_ASSERT( p_buf ); + p_mdl = NET_BUFFER_FIRST_MDL(p_buf); + CL_ASSERT( p_mdl ); + NdisFreeMdl(p_mdl); + NdisFreeNetBufferList(p_net_buffer_list); + } + + /* Return the descriptor to its pools. */ + cl_qpool_put( &p_port->buf_mgr.recv_pool, &p_desc->item ); + + /* + * Dereference the port object since the receive is no longer outstanding. + */ + ipoib_port_deref( p_port, ref_get_recv ); + IPOIB_EXIT( IPOIB_DBG_RECV ); +} + + +static inline void +__buf_mgr_put_recv_list( + IN ipoib_port_t* const p_port, + IN cl_qlist_t* const p_list ) +{ + //IPOIB_ENTER( IPOIB_DBG_RECV ); + cl_qpool_put_list( &p_port->buf_mgr.recv_pool, p_list ); + //IPOIB_EXIT( IPOIB_DBG_RECV ); +} + + +static inline NET_BUFFER_LIST* +__buf_mgr_get_ndis_pkt( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc ) +{ + NET_BUFFER_LIST *p_net_buffer_list; + MDL *p_mdl; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + p_mdl = NdisAllocateMdl(p_port->p_adapter->h_adapter, + &p_desc->buf.eth.pkt, + p_desc->len ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate MDL\n") ); + return NULL; + } + + p_net_buffer_list = NdisAllocateNetBufferAndNetBufferList( + p_port->buf_mgr.h_packet_pool, + 0, + 0, + p_mdl, + 0, + 0); + + if( !p_net_buffer_list ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate NET_BUFFER_LIST\n") ); + NdisFreeMdl(p_mdl); + return NULL; + } + + NET_BUFFER_LIST_NEXT_NBL(p_net_buffer_list) = NULL; + IPOIB_PORT_FROM_PACKET( p_net_buffer_list ) = p_port; + IPOIB_RECV_FROM_PACKET( p_net_buffer_list ) = p_desc; + p_net_buffer_list->SourceHandle = p_port->p_adapter->h_adapter; + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return p_net_buffer_list; +} + + +/****************************************************************************** +* +* Receive manager implementation. +* +******************************************************************************/ +static void +__recv_mgr_construct( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + cl_qlist_init( &p_port->recv_mgr.done_list ); + + p_port->recv_mgr.recv_pkt_array = NULL; + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static ib_api_status_t +__recv_mgr_init( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + /* Allocate the NDIS_PACKET pointer array for indicating receives. */ + p_port->recv_mgr.recv_pkt_array = cl_malloc( + sizeof(NET_BUFFER_LIST*) * p_port->p_adapter->params.rq_depth ); + if( !p_port->recv_mgr.recv_pkt_array ) + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_RECV_PKT_ARRAY, 0 ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("cl_malloc for PNDIS_PACKET array failed.\n") ); + return IB_INSUFFICIENT_MEMORY; + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return IB_SUCCESS; +} + + +static void +__recv_mgr_destroy( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + + CL_ASSERT( cl_is_qlist_empty( &p_port->recv_mgr.done_list ) ); + CL_ASSERT( !p_port->recv_mgr.depth ); + + if( p_port->recv_mgr.recv_pkt_array ) + cl_free( p_port->recv_mgr.recv_pkt_array ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +/* + * Posts receive buffers to the receive queue and returns the number + * of receives needed to bring the RQ to its low water mark. Note + * that the value is signed, and can go negative. All tests must + * be for > 0. + */ +static int32_t +__recv_mgr_repost( + IN ipoib_port_t* const p_port ) +{ + ipoib_recv_desc_t *p_head = NULL, *p_tail = NULL, *p_next; + ib_api_status_t status; + ib_recv_wr_t *p_failed; + PERF_DECLARE( GetRecv ); + PERF_DECLARE( PostRecv ); + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + CL_ASSERT( p_port ); + cl_obj_lock( &p_port->obj ); + if( p_port->state != IB_QPS_RTS ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_RECV, + ("Port in invalid state. Not reposting.\n") ); + return 0; + } + ipoib_port_ref( p_port, ref_repost ); + cl_obj_unlock( &p_port->obj ); + + while( p_port->recv_mgr.depth < p_port->p_adapter->params.rq_depth ) + { + /* Pull receives out of the pool and chain them up. */ + cl_perf_start( GetRecv ); + p_next = __buf_mgr_get_recv( p_port ); + cl_perf_stop( &p_port->p_adapter->perf, GetRecv ); + if( !p_next ) + { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_RECV, + ("Out of receive descriptors! recv queue depth 0x%x\n",p_port->recv_mgr.depth) ); + break; + } + + if( !p_tail ) + { + p_tail = p_next; + p_next->wr.p_next = NULL; + } + else + { + p_next->wr.p_next = &p_head->wr; + } + + p_head = p_next; + + p_port->recv_mgr.depth++; + } + + if( p_head ) + { + cl_perf_start( PostRecv ); + status = p_port->p_adapter->p_ifc->post_recv( + p_port->ib_mgr.h_qp, &p_head->wr, &p_failed ); + cl_perf_stop( &p_port->p_adapter->perf, PostRecv ); + + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ip_post_recv returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + /* return the descriptors to the pool */ + while( p_failed ) + { + p_head = PARENT_STRUCT( p_failed, ipoib_recv_desc_t, wr ); + p_failed = p_failed->p_next; + + __buf_mgr_put_recv( p_port, p_head, NULL ); + p_port->recv_mgr.depth--; + } + } + } + + ipoib_port_deref( p_port, ref_repost ); + IPOIB_EXIT( IPOIB_DBG_RECV ); + return p_port->p_adapter->params.rq_low_watermark - p_port->recv_mgr.depth; +} + +void +ipoib_return_net_buffer_list( + IN NDIS_HANDLE adapter_context, + IN NET_BUFFER_LIST *p_net_buffer_lists, + IN ULONG return_flags) +{ +// cl_list_item_t *p_item; + ipoib_port_t *p_port; + ipoib_recv_desc_t *p_desc; + NET_BUFFER_LIST *cur_net_buffer_list,*next_net_buffer_list; +// ib_api_status_t status = IB_NOT_DONE; +// int32_t shortage; +// ULONG complete_flags = 0; + PERF_DECLARE( ReturnPacket ); + PERF_DECLARE( ReturnPutRecv ); + PERF_DECLARE( ReturnRepostRecv ); + PERF_DECLARE( ReturnPreparePkt ); + PERF_DECLARE( ReturnNdisIndicate ); + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + UNUSED_PARAM( return_flags ); + + p_port = ((ipoib_adapter_t*)adapter_context)->p_port; + CL_ASSERT( p_net_buffer_lists ); + + cl_perf_start( ReturnPacket ); + cl_spinlock_acquire( &p_port->recv_lock ); + for (cur_net_buffer_list = p_net_buffer_lists; + cur_net_buffer_list != NULL; + cur_net_buffer_list = next_net_buffer_list) + { + next_net_buffer_list = NET_BUFFER_LIST_NEXT_NBL(cur_net_buffer_list); + + /* Get the port and descriptor from the packet. */ + CL_ASSERT(p_port == IPOIB_PORT_FROM_PACKET( cur_net_buffer_list )); + p_desc = IPOIB_RECV_FROM_PACKET( cur_net_buffer_list ); + + + //TODO: NDIS60, rewrite this block + /* Get descriptor from the packet. */ +#if 0 + if( p_desc->type == PKT_TYPE_CM_UCAST ) + { + NDIS_BUFFER *p_buf; + + /* Unchain the NDIS buffer. */ + NdisUnchainBufferAtFront( p_packet, &p_buf ); + CL_ASSERT( p_buf ); + /* Return the NDIS packet and NDIS buffer to their pools. */ + NdisDprFreePacketNonInterlocked( p_packet ); + NdisFreeBuffer( p_buf ); + + endpt_cm_buf_mgr_put_recv( &p_port->cm_buf_mgr, (ipoib_cm_desc_t *)p_desc ); + status = endpt_cm_post_recv( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Post Recv QP failed\n" ) ); + } + cl_spinlock_release( &p_port->recv_lock ); + return; + } +#endif + + cl_perf_start( ReturnPutRecv ); + __buf_mgr_put_recv( p_port, p_desc, cur_net_buffer_list ); + cl_perf_stop( &p_port->p_adapter->perf, ReturnPutRecv ); + } +#if 0 + /* Repost buffers. */ + cl_perf_start( ReturnRepostRecv ); + shortage = __recv_mgr_repost( p_port ); + cl_perf_stop( &p_port->p_adapter->perf, ReturnRepostRecv ); + + for( p_item = cl_qlist_remove_head( &p_port->recv_mgr.done_list ); + p_item != cl_qlist_end( &p_port->recv_mgr.done_list ); + p_item = cl_qlist_remove_head( &p_port->recv_mgr.done_list ) ) + { + p_desc = (ipoib_recv_desc_t*)p_item; + + cl_perf_start( ReturnPreparePkt ); + status = __recv_mgr_prepare_pkt( p_port, p_desc, &cur_net_buffer_list ); + cl_perf_stop( &p_port->p_adapter->perf, ReturnPreparePkt ); + if( status == IB_SUCCESS ) + { + if( shortage > 0 ) + NET_BUFFER_LIST_STATUS( cur_net_buffer_list) = NDIS_STATUS_RESOURCES; + else + NET_BUFFER_LIST_STATUS( cur_net_buffer_list) = NDIS_STATUS_SUCCESS; + + cl_spinlock_release( &p_port->recv_lock ); + NET_BUFFER_LIST_NEXT_NBL(cur_net_buffer_list) = NULL; + cl_perf_start( ReturnNdisIndicate ); + NdisMRecvIndicate( p_port->p_adapter->h_adapter, + cur_net_buffer_list, complete_flags ); + cl_perf_stop( &p_port->p_adapter->perf, ReturnNdisIndicate ); + cl_spinlock_acquire( &p_port->recv_lock ); + + if( shortage > 0 ) + { + cl_perf_start( ReturnPutRecv ); + __buf_mgr_put_recv( p_port, p_desc, cur_net_buffer_list ); + cl_perf_stop( &p_port->p_adapter->perf, ReturnPutRecv ); + + /* Repost buffers. */ + cl_perf_start( ReturnRepostRecv ); + shortage = __recv_mgr_repost( p_port ); + cl_perf_stop( &p_port->p_adapter->perf, ReturnRepostRecv ); + } + } + else if( status != IB_NOT_DONE ) + { + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_RECV, + ("__recv_mgr_prepare_pkt returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + /* Return the item to the head of the list. */ + cl_qlist_insert_head( &p_port->recv_mgr.done_list, p_item ); + break; + } + } + #endif + cl_spinlock_release( &p_port->recv_lock ); + cl_perf_stop( &p_port->p_adapter->perf, ReturnPacket ); + + IPOIB_EXIT( IPOIB_DBG_RECV ); +} + +static void __recv_cb_dpc(KDPC *p_gc_dpc,void *context,void * s_arg1 , void * s_arg2) +{ + + ipoib_port_t *p_port = context; + + UNREFERENCED_PARAMETER(p_gc_dpc); + UNREFERENCED_PARAMETER(s_arg1); + UNREFERENCED_PARAMETER(s_arg2); + + + __recv_cb(NULL, p_port); + ipoib_port_deref( p_port, ref_recv_cb ); + + +} + + +static void +__recv_cb( + IN const ib_cq_handle_t h_cq, + IN void *cq_context ) +{ + ipoib_port_t *p_port; + ib_api_status_t status; + ib_wc_t wc[MAX_RECV_WC], *p_free, *p_wc; + int32_t pkt_cnt, recv_cnt = 0, shortage, discarded; + cl_qlist_t done_list, bad_list; + size_t i; + ULONG recv_complete_flags = 0; + + PERF_DECLARE( RecvCompBundle ); + PERF_DECLARE( RecvCb ); + PERF_DECLARE( PollRecv ); + PERF_DECLARE( RepostRecv ); + PERF_DECLARE( FilterRecv ); + PERF_DECLARE( BuildPktArray ); + PERF_DECLARE( RecvNdisIndicate ); + PERF_DECLARE( RearmRecv ); + PERF_DECLARE( PutRecvList ); + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + cl_perf_clr( RecvCompBundle ); + + cl_perf_start( RecvCb ); +//return ; + UNUSED_PARAM( h_cq ); + + NDIS_SET_SEND_COMPLETE_FLAG(recv_complete_flags, NDIS_RECEIVE_FLAGS_DISPATCH_LEVEL ); + + p_port = (ipoib_port_t*)cq_context; + + cl_qlist_init( &done_list ); + cl_qlist_init( &bad_list ); + + ipoib_port_ref( p_port, ref_recv_cb ); + for( i = 0; i < MAX_RECV_WC; i++ ) + wc[i].p_next = &wc[i + 1]; + wc[MAX_RECV_WC - 1].p_next = NULL; + + /* + * We'll be accessing the endpoint map so take a reference + * on it to prevent modifications. + */ + cl_obj_lock( &p_port->obj ); + cl_atomic_inc( &p_port->endpt_rdr ); + cl_obj_unlock( &p_port->obj ); + + do + { + /* If we get here, then the list of WCs is intact. */ + p_free = wc; + + cl_perf_start( PollRecv ); + status = p_port->p_adapter->p_ifc->poll_cq( + p_port->ib_mgr.h_recv_cq, &p_free, &p_wc ); + cl_perf_stop( &p_port->p_adapter->perf, PollRecv ); + CL_ASSERT( status == IB_SUCCESS || status == IB_NOT_FOUND ); + + /* Look at the payload now and filter ARP and DHCP packets. */ + cl_perf_start( FilterRecv ); + recv_cnt += __recv_mgr_filter( p_port, p_wc, &done_list, &bad_list ); + cl_perf_stop( &p_port->p_adapter->perf, FilterRecv ); + + } while( (!p_free) && (recv_cnt < 128)); + + /* We're done looking at the endpoint map, release the reference. */ + cl_atomic_dec( &p_port->endpt_rdr ); + + cl_perf_log( &p_port->p_adapter->perf, RecvCompBundle, recv_cnt ); + + cl_spinlock_acquire( &p_port->recv_lock ); + + /* Update our posted depth. */ + p_port->recv_mgr.depth -= recv_cnt; + + /* Return any discarded receives to the pool */ + cl_perf_start( PutRecvList ); + __buf_mgr_put_recv_list( p_port, &bad_list ); + cl_perf_stop( &p_port->p_adapter->perf, PutRecvList ); + + do + { + int32_t cnt; + /* Repost ASAP so we don't starve the RQ. */ + cl_perf_start( RepostRecv ); + shortage = __recv_mgr_repost( p_port ); + cl_perf_stop( &p_port->p_adapter->perf, RepostRecv ); + + cl_perf_start( BuildPktArray ); + /* Notify NDIS of any and all possible receive buffers. */ + pkt_cnt = __recv_mgr_build_pkt_array( + p_port, shortage, &done_list, &discarded ); + cl_perf_stop( &p_port->p_adapter->perf, BuildPktArray ); + + /* Only indicate receives if we actually had any. */ + if( discarded && shortage > 0 ) + { + /* We may have thrown away packets, and have a shortage */ + cl_perf_start( RepostRecv ); + __recv_mgr_repost( p_port ); + cl_perf_stop( &p_port->p_adapter->perf, RepostRecv ); + } + + if( !pkt_cnt ) + break; + + cl_spinlock_release( &p_port->recv_lock ); + for( cnt = 0; cnt < pkt_cnt -1; cnt++) + { + NET_BUFFER_LIST_NEXT_NBL(p_port->recv_mgr.recv_pkt_array[cnt]) = + p_port->recv_mgr.recv_pkt_array[cnt + 1]; + } + cl_perf_start( RecvNdisIndicate ); +#ifndef NDIS_DEFAULT_PORT_NUMBER +#define NDIS_DEFAULT_PORT_NUMBER 0 +#endif + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Indicate NDIS with %d received NBs\n", + pkt_cnt) ); + NdisMIndicateReceiveNetBufferLists( + p_port->p_adapter->h_adapter, + p_port->recv_mgr.recv_pkt_array[0], + NDIS_DEFAULT_PORT_NUMBER, + pkt_cnt, + recv_complete_flags); + + cl_perf_stop( &p_port->p_adapter->perf, RecvNdisIndicate ); + + /* + * Cap the number of receives to put back to what we just indicated + * with NDIS_STATUS_RESOURCES. + */ + if( shortage > 0 ) + { + if( pkt_cnt < shortage ) + shortage = pkt_cnt; + + /* Return all but the last packet to the pool. */ + cl_spinlock_acquire( &p_port->recv_lock ); + while( shortage-- > 1 ) + { + __buf_mgr_put_recv( p_port, + (ipoib_recv_desc_t *)IPOIB_RECV_FROM_PACKET( p_port->recv_mgr.recv_pkt_array[shortage] ), + p_port->recv_mgr.recv_pkt_array[shortage] ); + } + cl_spinlock_release( &p_port->recv_lock ); + + /* + * Return the last packet as if NDIS returned it, so that we repost + * and report any other pending receives. + */ + ipoib_return_net_buffer_list( NULL, p_port->recv_mgr.recv_pkt_array[0],recv_complete_flags ); + } + cl_spinlock_acquire( &p_port->recv_lock ); + + } while( pkt_cnt ); + cl_spinlock_release( &p_port->recv_lock ); + + if (p_free ) { + /* + * Rearm after filtering to prevent contention on the enpoint maps + * and eliminate the possibility of having a call to + * __endpt_mgr_insert find a duplicate. + */ + cl_perf_start( RearmRecv ); + status = p_port->p_adapter->p_ifc->rearm_cq( + p_port->ib_mgr.h_recv_cq, FALSE ); + cl_perf_stop( &p_port->p_adapter->perf, RearmRecv ); + CL_ASSERT( status == IB_SUCCESS ); + + ipoib_port_deref( p_port, ref_recv_cb ); + } else { + // Please note the reference is still up + KeInsertQueueDpc(&p_port->recv_dpc, NULL, NULL); + } + + cl_perf_stop( &p_port->p_adapter->perf, RecvCb ); + + IPOIB_EXIT( IPOIB_DBG_RECV ); +} + + +static void +__recv_get_endpts( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc, + IN ib_wc_t* const p_wc, + OUT ipoib_endpt_t** const pp_src, + OUT ipoib_endpt_t** const pp_dst ) +{ + ib_api_status_t status; + mac_addr_t mac; + PERF_DECLARE( GetEndptByGid ); + PERF_DECLARE( GetEndptByLid ); + PERF_DECLARE( EndptInsert ); + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + /* Setup our shortcut pointers based on whether GRH is valid. */ + if( p_wc->recv.ud.recv_opt & IB_RECV_OPT_GRH_VALID ) + { + /* Lookup the source endpoints based on GID. */ + cl_perf_start( GetEndptByGid ); + *pp_src = +#if IPOIB_INLINE_RECV + __endpt_mgr_get_by_gid( p_port, &p_desc->buf.ib.grh.src_gid ); +#else /* IPOIB_INLINE_RECV */ + __endpt_mgr_get_by_gid( p_port, &p_desc->p_buf->ib.grh.src_gid ); +#endif /* IPOIB_INLINE_RECV */ + cl_perf_stop( &p_port->p_adapter->perf, GetEndptByGid ); + + /* + * Lookup the destination endpoint based on GID. + * This is used along with the packet filter to determine + * whether to report this to NDIS. + */ + cl_perf_start( GetEndptByGid ); + *pp_dst = +#if IPOIB_INLINE_RECV + __endpt_mgr_get_by_gid( p_port, &p_desc->buf.ib.grh.dest_gid ); +#else /* IPOIB_INLINE_RECV */ + __endpt_mgr_get_by_gid( p_port, &p_desc->p_buf->ib.grh.dest_gid ); +#endif /* IPOIB_INLINE_RECV */ + cl_perf_stop( &p_port->p_adapter->perf, GetEndptByGid ); + + /* + * Create the source endpoint if it does not exist. Note that we + * can only do this for globally routed traffic since we need the + * information from the GRH to generate the MAC. + */ + if( !*pp_src ) + { + status = ipoib_mac_from_guid( +#if IPOIB_INLINE_RECV + p_desc->buf.ib.grh.src_gid.unicast.interface_id, p_port->p_adapter->params.guid_mask, &mac ); +#else /* IPOIB_INLINE_RECV */ + p_desc->p_buf->ib.grh.src_gid.unicast.interface_id, p_port->p_adapter->params.guid_mask, &mac ); +#endif /* IPOIB_INLINE_RECV */ + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_mac_from_guid returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return; + } + + /* Create the endpoint. */ +#if IPOIB_INLINE_RECV + *pp_src = ipoib_endpt_create( &p_desc->buf.ib.grh.src_gid, +#else /* IPOIB_INLINE_RECV */ + *pp_src = ipoib_endpt_create( &p_desc->p_buf->ib.grh.src_gid, +#endif /* IPOIB_INLINE_RECV */ + p_wc->recv.ud.remote_lid, p_wc->recv.ud.remote_qp ); + if( !*pp_src ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_endpt_create failed\n") ); + return; + } + cl_perf_start( EndptInsert ); + cl_obj_lock( &p_port->obj ); + status = __endpt_mgr_insert( p_port, mac, *pp_src ); + if( status != IB_SUCCESS ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__endpt_mgr_insert returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + *pp_src = NULL; + return; + } + cl_obj_unlock( &p_port->obj ); + cl_perf_stop( &p_port->p_adapter->perf, EndptInsert ); + } + } + else + { + /* + * Lookup the remote endpoint based on LID. Note that only + * unicast traffic can be LID routed. + */ + cl_perf_start( GetEndptByLid ); + *pp_src = __endpt_mgr_get_by_lid( p_port, p_wc->recv.ud.remote_lid ); + cl_perf_stop( &p_port->p_adapter->perf, GetEndptByLid ); + *pp_dst = p_port->p_local_endpt; + CL_ASSERT( *pp_dst ); + } + + if( *pp_src && !ipoib_is_voltaire_router_gid( &(*pp_src)->dgid ) && + (*pp_src)->qpn != p_wc->recv.ud.remote_qp ) + { + /* Update the QPN for the endpoint. */ + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_RECV, + ("Updating QPN for MAC: %02X-%02X-%02X-%02X-%02X-%02X\n", + (*pp_src )->mac.addr[0], (*pp_src )->mac.addr[1], + (*pp_src )->mac.addr[2], (*pp_src )->mac.addr[3], + (*pp_src )->mac.addr[4], (*pp_src )->mac.addr[5]) ); +// (*pp_src)->qpn = p_wc->recv.ud.remote_qp; + } + + if( *pp_src && *pp_dst ) + { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_RECV, + ("Recv:\n" + "\tsrc MAC: %02X-%02X-%02X-%02X-%02X-%02X\n" + "\tdst MAC: %02X-%02X-%02X-%02X-%02X-%02X\n", + (*pp_src )->mac.addr[0], (*pp_src )->mac.addr[1], + (*pp_src )->mac.addr[2], (*pp_src )->mac.addr[3], + (*pp_src )->mac.addr[4], (*pp_src )->mac.addr[5], + (*pp_dst )->mac.addr[0], (*pp_dst )->mac.addr[1], + (*pp_dst )->mac.addr[2], (*pp_dst )->mac.addr[3], + (*pp_dst )->mac.addr[4], (*pp_dst )->mac.addr[5]) ); + } + + IPOIB_EXIT( IPOIB_DBG_RECV ); +} + + +static int32_t +__recv_mgr_filter( + IN ipoib_port_t* const p_port, + IN ib_wc_t* const p_done_wc_list, + OUT cl_qlist_t* const p_done_list, + OUT cl_qlist_t* const p_bad_list ) +{ + ipoib_recv_desc_t *p_desc; + ib_wc_t *p_wc; + ipoib_pkt_t *p_ipoib; + eth_pkt_t *p_eth; + ipoib_endpt_t *p_src, *p_dst; + ib_api_status_t status; + uint32_t len; + int32_t recv_cnt = 0; + PERF_DECLARE( GetRecvEndpts ); + PERF_DECLARE( RecvGen ); + PERF_DECLARE( RecvTcp ); + PERF_DECLARE( RecvUdp ); + PERF_DECLARE( RecvDhcp ); + PERF_DECLARE( RecvArp ); + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + for( p_wc = p_done_wc_list; p_wc; p_wc = p_wc->p_next ) + { + CL_ASSERT( p_wc->status != IB_WCS_SUCCESS || p_wc->wc_type == IB_WC_RECV ); + p_desc = (ipoib_recv_desc_t*)(uintn_t)p_wc->wr_id; + recv_cnt++; + + if( p_wc->status != IB_WCS_SUCCESS ) + { + if( p_wc->status != IB_WCS_WR_FLUSHED_ERR ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed completion %s (vendor specific %#x)\n", + p_port->p_adapter->p_ifc->get_wc_status_str( p_wc->status ), + (int)p_wc->vendor_specific) ); + ipoib_inc_recv_stat( p_port->p_adapter, IP_STAT_ERROR, 0, 0 ); + } + else + { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_RECV, + ("Flushed completion %s\n", + p_port->p_adapter->p_ifc->get_wc_status_str( p_wc->status )) ); + ipoib_inc_recv_stat( p_port->p_adapter, IP_STAT_DROPPED, 0, 0 ); + } + cl_qlist_insert_tail( p_bad_list, &p_desc->item.list_item ); + /* Dereference the port object on behalf of the failed receive. */ + ipoib_port_deref( p_port, ref_failed_recv_wc ); + continue; + } + + len = p_wc->length - sizeof(ib_grh_t); + + if( len < sizeof(ipoib_hdr_t) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received ETH packet < min size\n") ); + ipoib_inc_recv_stat( p_port->p_adapter, IP_STAT_ERROR, 0, 0 ); + cl_qlist_insert_tail( p_bad_list, &p_desc->item.list_item ); + ipoib_port_deref( p_port, ref_recv_inv_len ); + continue; + } + + if((len - sizeof(ipoib_hdr_t)) > p_port->p_adapter->params.payload_mtu) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received ETH packet len %d > payload MTU (%d)\n", + (len - sizeof(ipoib_hdr_t)), + p_port->p_adapter->params.payload_mtu) ); + ipoib_inc_recv_stat( p_port->p_adapter, IP_STAT_ERROR, 0, 0 ); + cl_qlist_insert_tail( p_bad_list, &p_desc->item.list_item ); + ipoib_port_deref( p_port, ref_recv_inv_len ); + continue; + + } + /* Successful completion. Get the receive information. */ + p_desc->ndis_csum.Value = ( ( p_wc->recv.ud.recv_opt & IB_RECV_OPT_CSUM_MASK ) >> 8 ); + p_desc->len = len + 14 - 4 ; + cl_perf_start( GetRecvEndpts ); + __recv_get_endpts( p_port, p_desc, p_wc, &p_src, &p_dst ); + cl_perf_stop( &p_port->p_adapter->perf, GetRecvEndpts ); + +#if IPOIB_INLINE_RECV + p_ipoib = &p_desc->buf.ib.pkt; + p_eth = &p_desc->buf.eth.pkt; +#else /* IPOIB_INLINE_RECV */ + p_ipoib = &p_desc->p_buf->ib.pkt; + p_eth = &p_desc->p_buf->eth.pkt; +#endif /*IPOIB_INLINE_RECV */ + + if( p_src ) + { + /* Don't report loopback traffic - we requested SW loopback. */ + if( !cl_memcmp( &p_port->p_adapter->params.conf_mac, + &p_src->mac, sizeof(p_port->p_adapter->params.conf_mac) ) ) + { + /* + * "This is not the packet you're looking for" - don't update + * receive statistics, the packet never happened. + */ + cl_qlist_insert_tail( p_bad_list, &p_desc->item.list_item ); + /* Dereference the port object on behalf of the failed recv. */ + ipoib_port_deref( p_port, ref_recv_loopback ); + continue; + } + } + + switch( p_ipoib->hdr.type ) + { + case ETH_PROT_TYPE_IP: + if( len < (sizeof(ipoib_hdr_t) + sizeof(ip_hdr_t)) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received IP packet < min size\n") ); + status = IB_INVALID_SETTING; + break; + } + + if( p_ipoib->type.ip.hdr.offset || + p_ipoib->type.ip.hdr.prot != IP_PROT_UDP ) + { + /* Unfiltered. Setup the ethernet header and report. */ + cl_perf_start( RecvTcp ); + status = __recv_gen( p_ipoib, p_eth, p_src, p_dst ); + cl_perf_stop( &p_port->p_adapter->perf, RecvTcp ); + break; + } + + /* First packet of a UDP transfer. */ + if( len < + (sizeof(ipoib_hdr_t) + sizeof(ip_hdr_t) + sizeof(udp_hdr_t)) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received UDP packet < min size\n") ); + status = IB_INVALID_SETTING; + break; + } + + /* Check if DHCP conversion is required. */ + if( (p_ipoib->type.ip.prot.udp.hdr.dst_port == DHCP_PORT_SERVER && + p_ipoib->type.ip.prot.udp.hdr.src_port == DHCP_PORT_CLIENT) || + (p_ipoib->type.ip.prot.udp.hdr.dst_port == DHCP_PORT_CLIENT && + p_ipoib->type.ip.prot.udp.hdr.src_port == DHCP_PORT_SERVER) ) + { + if( len < (sizeof(ipoib_hdr_t) + sizeof(ip_hdr_t) + + sizeof(udp_hdr_t) + DHCP_MIN_SIZE) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received DHCP < min size\n") ); + status = IB_INVALID_SETTING; + break; + } + if ((p_ipoib->type.ip.hdr.ver_hl & 0x0f) != 5 ) { + // If there are IP options in this message, we are in trouble in any case + status = IB_INVALID_SETTING; + break; + } + /* UDP packet with BOOTP ports in src/dst port numbers. */ + cl_perf_start( RecvDhcp ); + status = __recv_dhcp( p_port, p_ipoib, p_eth, p_src, p_dst ); + cl_perf_stop( &p_port->p_adapter->perf, RecvDhcp ); + } + else + { + /* Unfiltered. Setup the ethernet header and report. */ + cl_perf_start( RecvUdp ); + status = __recv_gen( p_ipoib, p_eth, p_src, p_dst ); + cl_perf_stop( &p_port->p_adapter->perf, RecvUdp ); + } + break; + + case ETH_PROT_TYPE_ARP: + if( len < (sizeof(ipoib_hdr_t) + sizeof(ipoib_arp_pkt_t)) ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received ARP < min size\n") ); + status = IB_INVALID_SETTING; + break; + } + cl_perf_start( RecvArp ); + status = __recv_arp( p_port, p_wc, p_ipoib, p_eth, &p_src, p_dst ); + cl_perf_stop( &p_port->p_adapter->perf, RecvArp ); + len = sizeof(ipoib_hdr_t) + sizeof(arp_pkt_t); + break; + + default: + /* Unfiltered. Setup the ethernet header and report. */ + cl_perf_start( RecvGen ); + status = __recv_gen( p_ipoib, p_eth, p_src, p_dst ); + cl_perf_stop( &p_port->p_adapter->perf, RecvGen ); + } + + if( status != IB_SUCCESS ) + { + /* Update stats. */ + ipoib_inc_recv_stat( p_port->p_adapter, IP_STAT_ERROR, 0, 0 ); + cl_qlist_insert_tail( p_bad_list, &p_desc->item.list_item ); + /* Dereference the port object on behalf of the failed receive. */ + ipoib_port_deref( p_port, ref_recv_filter ); + } + else + { + ip_stat_sel_t ip_stat; + p_desc->len = + len + sizeof(eth_hdr_t) - sizeof(ipoib_hdr_t); + if( p_dst->h_mcast) + { + if( p_dst->dgid.multicast.raw_group_id[10] == 0xFF && + p_dst->dgid.multicast.raw_group_id[11] == 0xFF && + p_dst->dgid.multicast.raw_group_id[12] == 0xFF && + p_dst->dgid.multicast.raw_group_id[13] == 0xFF ) + { + p_desc->type = PKT_TYPE_BCAST; + ip_stat = IP_STAT_BCAST_BYTES; + } + else + { + p_desc->type = PKT_TYPE_MCAST; + ip_stat = IP_STAT_MCAST_BYTES; + } + } + else + { + p_desc->type = PKT_TYPE_UCAST; + ip_stat = IP_STAT_UCAST_BYTES; + + } + cl_qlist_insert_tail( p_done_list, &p_desc->item.list_item ); + ipoib_inc_recv_stat( p_port->p_adapter, ip_stat, len, 1 ); + } + } + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return recv_cnt; +} + + +static ib_api_status_t +__recv_gen( + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src, + IN ipoib_endpt_t* const p_dst ) +{ + IPOIB_ENTER( IPOIB_DBG_RECV ); + + if( !p_src || !p_dst ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received packet with no matching endpoints.\n") ); + return IB_NOT_DONE; + } + + /* + * Fill in the ethernet header. Note that doing so will overwrite + * the IPoIB header, so start by moving the information from the IPoIB + * header. + */ + p_eth->hdr.type = p_ipoib->hdr.type; + p_eth->hdr.src = p_src->mac; + p_eth->hdr.dst = p_dst->mac; + + if ( p_eth->hdr.dst.addr[0] == 1 && + p_eth->hdr.type == ETH_PROT_TYPE_IP && + p_eth->hdr.dst.addr[2] == 0x5E) + { + p_eth->hdr.dst.addr[1] = 0; + p_eth->hdr.dst.addr[3] = p_eth->hdr.dst.addr[3] & 0x7f; + } + if (p_dst->h_mcast) + p_dst->is_in_use = TRUE; + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return IB_SUCCESS; +} + + +static ib_api_status_t +__recv_dhcp( + IN ipoib_port_t* const p_port, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src, + IN ipoib_endpt_t* const p_dst ) +{ + ib_api_status_t status; + dhcp_pkt_t *p_dhcp; + uint8_t *p_option; + uint8_t *p_cid = NULL; + ib_gid_t gid; + uint8_t msg = 0; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + UNUSED_PARAM( p_port ); + + /* Create the ethernet header. */ + status = __recv_gen( p_ipoib, p_eth, p_src, p_dst ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__recv_gen returned %s.\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Fixup the payload. */ + p_dhcp = &p_eth->type.ip.prot.udp.dhcp; + if( p_dhcp->op != DHCP_REQUEST && p_dhcp->op != DHCP_REPLY ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid DHCP op code.\n") ); + return IB_INVALID_SETTING; + } + + /* + * Find the client identifier option, making sure to skip + * the "magic cookie". + */ + p_option = &p_dhcp->options[0]; + if ( *(uint32_t *)p_option != DHCP_COOKIE ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("DHCP cookie corrupted.\n") ); + return IB_INVALID_PARAMETER; + } + + p_option = &p_dhcp->options[4]; + while( *p_option != DHCP_OPT_END && p_option < &p_dhcp->options[312] ) + { + switch( *p_option ) + { + case DHCP_OPT_PAD: + p_option++; + break; + + case DHCP_OPT_MSG: + msg = p_option[2]; + p_option += 3; + break; + + case DHCP_OPT_CLIENT_ID: + p_cid = p_option; + /* Fall through. */ + + default: + /* + * All other options have a length byte following the option code. + * Offset by the length to get to the next option. + */ + p_option += (p_option[1] + 2); + } + } + + switch( msg ) + { + /* message from client */ + case DHCPDISCOVER: + case DHCPREQUEST: + case DHCPDECLINE: + case DHCPRELEASE: + case DHCPINFORM: + if( !p_cid ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to find required Client-identifier option.\n") ); + return IB_INVALID_SETTING; + } + if( p_dhcp->htype != DHCP_HW_TYPE_IB ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalid hardware address type.\n") ); + return IB_INVALID_SETTING; + } + break; + /* message from DHCP server */ + case DHCPOFFER: + case DHCPACK: + case DHCPNAK: + break; + + default: + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalide message type.\n") ); + return IB_INVALID_PARAMETER; + } + p_eth->type.ip.prot.udp.hdr.chksum = 0; + p_dhcp->htype = DHCP_HW_TYPE_ETH; + p_dhcp->hlen = HW_ADDR_LEN; + + if( p_cid ) /* from client */ + { + /* Validate that the length and type of the option is as required. */ + if( p_cid[1] != 21 ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Client-identifier length not 21 as required.\n") ); + return IB_INVALID_SETTING; + } + if( p_cid[2] != DHCP_HW_TYPE_IB ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Client-identifier type is wrong.\n") ); + return IB_INVALID_SETTING; + } + /* + * Copy the GID value from the option so that we can make aligned + * accesses to the contents. + * Recover CID to standard type. + */ + cl_memcpy( &gid, &p_cid[7], sizeof(ib_gid_t) ); + p_cid[1] = HW_ADDR_LEN +1;// CID length + p_cid[2] = DHCP_HW_TYPE_ETH;// CID type + status = ipoib_mac_from_guid( gid.unicast.interface_id, p_port->p_adapter->params.guid_mask, (mac_addr_t*)&p_cid[3] ); + if (status == IB_INVALID_GUID_MASK) + { + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_ERROR, + ("Invalid GUID mask received, rejecting it") ); + ipoib_create_log(p_port->p_adapter->h_adapter, GUID_MASK_LOG_INDEX, EVENT_IPOIB_WRONG_PARAMETER_WRN); + status = IB_SUCCESS; + } + p_cid[HW_ADDR_LEN + 3] = DHCP_OPT_END; //terminate tag + } + IPOIB_EXIT( IPOIB_DBG_RECV ); + return status; +} + + +static ib_api_status_t +__recv_arp( + IN ipoib_port_t* const p_port, + IN ib_wc_t* const p_wc, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t** const pp_src, + IN ipoib_endpt_t* const p_dst ) +{ + ib_api_status_t status; + arp_pkt_t *p_arp; + const ipoib_arp_pkt_t *p_ib_arp; + ib_gid_t gid; + mac_addr_t mac; + ipoib_hw_addr_t null_hw = {0}; + uint8_t cm_capable = 0; + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + if( !p_dst ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Unknown destination endpoint\n") ); + return IB_INVALID_SETTING; + } + + p_ib_arp = &p_ipoib->type.arp; + p_arp = &p_eth->type.arp; + + if( p_ib_arp->hw_type != ARP_HW_TYPE_IB ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ARP hardware type is not IB\n") ); + return IB_INVALID_SETTING; + } + + if( p_ib_arp->hw_size != sizeof(ipoib_hw_addr_t) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ARP hardware address size is not sizeof(ipoib_hw_addr_t)\n") ); + return IB_INVALID_SETTING; + } + + if( p_ib_arp->prot_type != ETH_PROT_TYPE_IP ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ARP protocal type not IP\n") ); + return IB_INVALID_SETTING; + } + + cm_capable = ipoib_addr_get_flags( &p_ib_arp->src_hw ); + + /* + * If we don't have a source, lookup the endpoint specified in the payload. + */ + if( !*pp_src ) + *pp_src = __endpt_mgr_get_by_gid( p_port, &p_ib_arp->src_hw.gid ); + + /* + * If the endpoint exists for the GID, make sure + * the dlid and qpn match the arp. + */ + if( *pp_src ) + { + if( cl_memcmp( &(*pp_src)->dgid, &p_ib_arp->src_hw.gid, + sizeof(ib_gid_t) ) ) + { + /* + * GIDs for the endpoint are different. The ARP must + * have been proxied. Dereference it. + */ + *pp_src = NULL; + } + else if( (*pp_src)->dlid && + (*pp_src)->dlid != p_wc->recv.ud.remote_lid ) + { + /* Out of date! Destroy the endpoint and replace it. */ + __endpt_mgr_remove( p_port, *pp_src ); + *pp_src = NULL; + } + else if ( ! ((*pp_src)->dlid)) { + /* Out of date! Destroy the endpoint and replace it. */ + __endpt_mgr_remove( p_port, *pp_src ); + *pp_src = NULL; + } + else if( ipoib_is_voltaire_router_gid( &(*pp_src)->dgid ) ) + { + if( (*pp_src)->qpn != ipoib_addr_get_qpn( &p_ib_arp->src_hw ) && + p_wc->recv.ud.remote_qp != ipoib_addr_get_qpn( &p_ib_arp->src_hw ) ) + { + /* Out of date! Destroy the endpoint and replace it. */ + __endpt_mgr_remove( p_port, *pp_src ); + *pp_src = NULL; + } + } + else if( (*pp_src)->qpn != p_wc->recv.ud.remote_qp ) + { + /* Out of date! Destroy the endpoint and replace it. */ + __endpt_mgr_remove( p_port, *pp_src ); + *pp_src = NULL; + } + } + + /* Do we need to create an endpoint for this GID? */ + if( !*pp_src ) + { + /* Copy the src GID to allow aligned access */ + cl_memcpy( &gid, &p_ib_arp->src_hw.gid, sizeof(ib_gid_t) ); + status = ipoib_mac_from_guid( gid.unicast.interface_id, p_port->p_adapter->params.guid_mask, &mac ); + if (status == IB_INVALID_GUID_MASK) + { + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_ERROR, + ("Invalid GUID mask received, rejecting it") ); + ipoib_create_log(p_port->p_adapter->h_adapter, GUID_MASK_LOG_INDEX, EVENT_IPOIB_WRONG_PARAMETER_WRN); + } + else if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_mac_from_guid returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + /* + * Create the endpoint. + */ + *pp_src = ipoib_endpt_create( &p_ib_arp->src_hw.gid, + p_wc->recv.ud.remote_lid, ipoib_addr_get_qpn( &p_ib_arp->src_hw ) ); + + if( !*pp_src ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_endpt_create failed\n") ); + return status; + } + + cl_obj_lock( &p_port->obj ); + status = __endpt_mgr_insert( p_port, mac, *pp_src ); + if( status != IB_SUCCESS ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__endpt_mgr_insert return %s \n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + cl_obj_unlock( &p_port->obj ); + } + + (*pp_src)->cm_flag = cm_capable; + + CL_ASSERT( !cl_memcmp( + &(*pp_src)->dgid, &p_ib_arp->src_hw.gid, sizeof(ib_gid_t) ) ); + CL_ASSERT( ipoib_is_voltaire_router_gid( &(*pp_src)->dgid ) || + (*pp_src)->qpn == ipoib_addr_get_qpn( &p_ib_arp->src_hw ) ); +#if 0 + if( p_port->p_adapter->params.cm_enabled && + p_ib_arp->op == ARP_OP_REQ && + cm_capable == IPOIB_CM_FLAG_RC ) + { + /* if we've got ARP request and RC flag is set, + save SID for connect REQ to be sent in ARP reply + when requestor's path get resolved */ + if( endpt_cm_get_state( (*pp_src) ) == IPOIB_CM_DISCONNECTED ) + { + (*pp_src)->cm_flag = cm_capable; + ipoib_addr_set_sid( + &(*pp_src)->conn.service_id, + ipoib_addr_get_qpn( &p_ib_arp->src_hw ) ); + } + } +#endif +#if 0 //DBG + if( p_port->p_adapter->params.cm_enabled ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + (" ARP %s from ENDPT[%p] state %d CM cap: %d QPN: %#x MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", + ((p_ib_arp->op == ARP_OP_REQ )? "REQUEST" : "REPLY"), + *pp_src, endpt_cm_get_state( *pp_src ), + ((cm_capable == IPOIB_CM_FLAG_RC)? 1: 0), + cl_ntoh32( ipoib_addr_get_qpn( &p_ib_arp->src_hw ) ), + (*pp_src)->mac.addr[0], (*pp_src)->mac.addr[1], + (*pp_src)->mac.addr[2], (*pp_src)->mac.addr[3], + (*pp_src)->mac.addr[4], (*pp_src)->mac.addr[5] )); + } +#endif + + /* Now swizzle the data. */ + p_arp->hw_type = ARP_HW_TYPE_ETH; + p_arp->hw_size = sizeof(mac_addr_t); + p_arp->src_hw = (*pp_src)->mac; + p_arp->src_ip = p_ib_arp->src_ip; + + if( cl_memcmp( &p_ib_arp->dst_hw, &null_hw, sizeof(ipoib_hw_addr_t) ) ) + { + if( cl_memcmp( &p_dst->dgid, &p_ib_arp->dst_hw.gid, sizeof(ib_gid_t) ) ) + { + /* + * We received bcast ARP packet that means + * remote port lets everyone know it was changed IP/MAC + * or just activated + */ + + /* Guy: TODO: Check why this check fails in case of Voltaire IPR */ + + if ( !ipoib_is_voltaire_router_gid( &(*pp_src)->dgid ) && + !ib_gid_is_multicast( (const ib_gid_t*)&p_dst->dgid ) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ARP: is not ARP MCAST\n") ); + return IB_INVALID_SETTING; + } + + p_arp->dst_hw = p_port->p_local_endpt->mac; + p_dst->mac = p_port->p_local_endpt->mac; + /* + * we don't care what receiver ip addr is, + * as long as OS' ARP table is global ??? + */ + p_arp->dst_ip = (net32_t)0; + } + else /* we've got reply to our ARP request */ + { + p_arp->dst_hw = p_dst->mac; + p_arp->dst_ip = p_ib_arp->dst_ip; + CL_ASSERT( p_dst->qpn == ipoib_addr_get_qpn( &p_ib_arp->dst_hw ) ); + } + } + else /* we got ARP reqeust */ + { + cl_memclr( &p_arp->dst_hw, sizeof(mac_addr_t) ); + p_arp->dst_ip = p_ib_arp->dst_ip; + } + + /* + * Create the ethernet header. Note that this is done last so that + * we have a chance to create a new endpoint. + */ + status = __recv_gen( p_ipoib, p_eth, *pp_src, p_dst ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__recv_gen returned %s.\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return IB_SUCCESS; +} + + +static ib_api_status_t +__recv_mgr_prepare_pkt( + IN ipoib_port_t* const p_port, + IN ipoib_recv_desc_t* const p_desc, + OUT NET_BUFFER_LIST** const pp_net_buffer_list ) +{ + NDIS_STATUS status; + uint32_t pkt_filter; + ip_stat_sel_t type; + //NDIS60 + NDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO chksum; + //NDIS_TCP_IP_CHECKSUM_PACKET_INFO chksum; + + PERF_DECLARE( GetNdisPkt ); + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + pkt_filter = p_port->p_adapter->packet_filter; + /* Check the packet filter. */ + switch( p_desc->type ) + { + default: + case PKT_TYPE_UCAST: + + if( pkt_filter & NDIS_PACKET_TYPE_PROMISCUOUS || + pkt_filter & NDIS_PACKET_TYPE_ALL_FUNCTIONAL || + pkt_filter & NDIS_PACKET_TYPE_SOURCE_ROUTING || + pkt_filter & NDIS_PACKET_TYPE_DIRECTED ) + { + /* OK to report. */ + type = IP_STAT_UCAST_BYTES; + status = NDIS_STATUS_SUCCESS; + IPOIB_PRINT( TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, + ("Received UCAST PKT.\n")); + } + else + { + type = IP_STAT_DROPPED; + status = NDIS_STATUS_FAILURE; + IPOIB_PRINT( TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, + ("Received UCAST PKT with ERROR !!!!\n")); + } + break; + case PKT_TYPE_BCAST: + if( pkt_filter & NDIS_PACKET_TYPE_PROMISCUOUS || + pkt_filter & NDIS_PACKET_TYPE_BROADCAST ) + { + /* OK to report. */ + type = IP_STAT_BCAST_BYTES; + status = NDIS_STATUS_SUCCESS; + IPOIB_PRINT( TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, + ("Received BCAST PKT.\n")); + } + else + { + type = IP_STAT_DROPPED; + status = NDIS_STATUS_FAILURE; + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received BCAST PKT with ERROR !!!!\n")); + } + break; + case PKT_TYPE_MCAST: + if( pkt_filter & NDIS_PACKET_TYPE_PROMISCUOUS || + pkt_filter & NDIS_PACKET_TYPE_ALL_MULTICAST || + pkt_filter & NDIS_PACKET_TYPE_MULTICAST ) + { + /* OK to report. */ + type = IP_STAT_MCAST_BYTES; + status = NDIS_STATUS_SUCCESS; + IPOIB_PRINT( TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, + ("Received UCAST PKT.\n")); + } + else + { + type = IP_STAT_DROPPED; + status = NDIS_STATUS_FAILURE; + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Received MCAST PKT with ERROR !!!!\n")); + } + break; + } + + if( status != NDIS_STATUS_SUCCESS ) + { + ipoib_inc_recv_stat( p_port->p_adapter, type, 0, 0 ); + /* Return the receive descriptor to the pool. */ + __buf_mgr_put_recv( p_port, p_desc, NULL ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_RECV, + ("Packet filter doesn't match receive. Dropping.\n") ); + /* + * Return IB_NOT_DONE since the packet has been completed, + * but has not consumed an array entry. + */ + return IB_NOT_DONE; + } + + cl_perf_start( GetNdisPkt ); + *pp_net_buffer_list = __buf_mgr_get_ndis_pkt( p_port, p_desc ); + cl_perf_stop( &p_port->p_adapter->perf, GetNdisPkt ); + if( !*pp_net_buffer_list ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__buf_mgr_get_ndis_pkt failed\n") ); + return IB_INSUFFICIENT_RESOURCES; + } + + chksum.Value = 0; + +{ + PNET_BUFFER NetBuffer = NET_BUFFER_LIST_FIRST_NB(*pp_net_buffer_list); + NET_BUFFER_DATA_LENGTH(NetBuffer) = p_desc->len; +} + + switch( p_port->p_adapter->params.recv_chksum_offload ) + { + default: + CL_ASSERT( FALSE ); + case CSUM_DISABLED: + //NDIS60 + //NDIS_PER_PACKET_INFO_FROM_PACKET( *pp_packet, TcpIpChecksumPacketInfo ) = + //(void*)(uintn_t)chksum.Value; + NET_BUFFER_LIST_INFO(*pp_net_buffer_list, TcpIpChecksumNetBufferListInfo) = + (void*)(uintn_t)chksum.Value; + break; + case CSUM_ENABLED: + /* Get the checksums directly from packet information. */ + /* In this case, no one of cheksum's cat get false value */ + /* If hardware checksum failed or wasn't calculated, NDIS will recalculate it again */ + //NDIS60 + //NDIS_PER_PACKET_INFO_FROM_PACKET( *pp_packet, TcpIpChecksumPacketInfo ) = + NET_BUFFER_LIST_INFO(*pp_net_buffer_list, TcpIpChecksumNetBufferListInfo) = + (void*)(uintn_t)(p_desc->ndis_csum.Value); + break; + case CSUM_BYPASS: + /* Flag the checksums as having been calculated. */ + chksum.Receive.TcpChecksumSucceeded = TRUE; + chksum.Receive.UdpChecksumSucceeded = TRUE; + chksum.Receive.IpChecksumSucceeded = TRUE; + //NDIS60 + //NDIS_PER_PACKET_INFO_FROM_PACKET( *pp_packet, TcpIpChecksumPacketInfo ) = + NET_BUFFER_LIST_INFO(*pp_net_buffer_list, TcpIpChecksumNetBufferListInfo) = + (void*)(uintn_t)chksum.Value; + break; + } + ipoib_inc_recv_stat( p_port->p_adapter, type, p_desc->len, 1 ); + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return IB_SUCCESS; +} + + +static uint32_t +__recv_mgr_build_pkt_array( + IN ipoib_port_t* const p_port, + IN int32_t shortage, + OUT cl_qlist_t* const p_done_list, + OUT int32_t* const p_discarded ) +{ + cl_list_item_t *p_item; + ipoib_recv_desc_t *p_desc; + uint32_t i = 0; + ib_api_status_t status; + PERF_DECLARE( PreparePkt ); + + IPOIB_ENTER( IPOIB_DBG_RECV ); + + *p_discarded = 0; + + /* Move any existing receives to the head to preserve ordering. */ + cl_qlist_insert_list_head( p_done_list, &p_port->recv_mgr.done_list ); + p_item = cl_qlist_remove_head( p_done_list ); + while( p_item != cl_qlist_end( p_done_list ) ) + { + p_desc = (ipoib_recv_desc_t*)p_item; + + cl_perf_start( PreparePkt ); + status = __recv_mgr_prepare_pkt( p_port, p_desc, + &p_port->recv_mgr.recv_pkt_array[i] ); + cl_perf_stop( &p_port->p_adapter->perf, PreparePkt ); + if( status == IB_SUCCESS ) + { + CL_ASSERT( p_port->recv_mgr.recv_pkt_array[i] ); + if( shortage-- > 0 ) + { + NET_BUFFER_LIST_STATUS(p_port->recv_mgr.recv_pkt_array[i])= NDIS_STATUS_RESOURCES; + } + else + { + NET_BUFFER_LIST_STATUS(p_port->recv_mgr.recv_pkt_array[i])= NDIS_STATUS_SUCCESS; + } + i++; + } + else if( status == IB_NOT_DONE ) + { + (*p_discarded)++; + } + else + { + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_RECV, + ("__recv_mgr_prepare_pkt returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + /* Put all completed receives on the port's done list. */ + cl_qlist_insert_tail( &p_port->recv_mgr.done_list, p_item ); + cl_qlist_insert_list_tail( &p_port->recv_mgr.done_list, p_done_list ); + break; + } + + p_item = cl_qlist_remove_head( p_done_list ); + } + + IPOIB_EXIT( IPOIB_DBG_RECV ); + return i; +} + + + + +/****************************************************************************** +* +* Send manager implementation. +* +******************************************************************************/ +static void +__send_mgr_construct( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_SEND ); + p_port->send_mgr.depth = 0; + cl_qlist_init( &p_port->send_mgr.pending_list ); + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + + +static void +__pending_list_destroy( + IN ipoib_port_t* const p_port ) +{ + cl_list_item_t *p_item; + NET_BUFFER_LIST **pp_net_buffer_list, *p_head; + + p_head = NULL; + cl_spinlock_acquire( &p_port->send_lock ); + /* Complete any pending packets. */ + pp_net_buffer_list = &p_head; + for( p_item = cl_qlist_remove_head( &p_port->send_mgr.pending_list ); + p_item != cl_qlist_end( &p_port->send_mgr.pending_list ); + p_item = cl_qlist_remove_head( &p_port->send_mgr.pending_list ) ) + { + *pp_net_buffer_list = IPOIB_PACKET_FROM_LIST_ITEM( p_item ); + NET_BUFFER_LIST_STATUS(*pp_net_buffer_list) = NDIS_STATUS_RESET_IN_PROGRESS; + pp_net_buffer_list = &(NET_BUFFER_LIST_NEXT_NBL(*pp_net_buffer_list)); + } + cl_spinlock_release( &p_port->send_lock ); + if(p_head) + NdisMSendNetBufferListsComplete( + p_port->p_adapter->h_adapter, + p_head, + 0); +} + +static void +__send_mgr_destroy( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_SEND ); + __pending_list_destroy(p_port); + + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + + +static NDIS_STATUS +__send_mgr_filter( + IN ipoib_port_t* const p_port, + IN const eth_hdr_t* const p_eth_hdr, + IN MDL* const p_mdl, + IN size_t buf_len, + IN SCATTER_GATHER_LIST *p_sgl, + IN OUT ipoib_send_desc_t* const p_desc ) +{ + NDIS_STATUS status; + + PERF_DECLARE( FilterIp ); + PERF_DECLARE( FilterArp ); + PERF_DECLARE( SendGen ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + /* + * We already checked the ethernet header length, so we know it's safe + * to decrement the buf_len without underflowing. + */ + buf_len -= sizeof(eth_hdr_t); + + switch( p_eth_hdr->type ) + { + case ETH_PROT_TYPE_IP: + cl_perf_start( FilterIp ); + status = __send_mgr_filter_ip( + p_port, p_eth_hdr, p_mdl, buf_len, p_sgl, p_desc); + cl_perf_stop( &p_port->p_adapter->perf, FilterIp ); + break; + + case ETH_PROT_TYPE_ARP: + cl_perf_start( FilterArp ); + status = __send_mgr_filter_arp( + p_port, p_eth_hdr, p_mdl, buf_len, p_desc ); + p_desc->send_dir = SEND_UD_QP; + cl_perf_stop( &p_port->p_adapter->perf, FilterArp ); + break; + + default: + /* + * The IPoIB spec doesn't define how to send non IP or ARP packets. + * Just send the payload and hope for the best. + */ + + p_desc->send_dir = SEND_UD_QP; + cl_perf_start( SendGen ); + status = __send_gen( p_port, p_desc, p_sgl, 0 ); + cl_perf_stop( &p_port->p_adapter->perf, SendGen ); + break; + } + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return status; +} + + +static NDIS_STATUS +__send_copy( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc ) +{ + NET_BUFFER_LIST *p_net_buffer_list; + NET_BUFFER *p_netbuffer; + MDL *p_mdl; + UINT tot_len = 0; + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + UNREFERENCED_PARAMETER(p_port); + UNREFERENCED_PARAMETER(p_desc); + + p_desc->p_buf = + NdisAllocateFromNPagedLookasideList( &p_port->buf_mgr.send_buf_list ); + if( !p_desc->p_buf ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate buffer for packet copy.\n") ); + return NDIS_STATUS_RESOURCES; + } + + p_mdl = NdisAllocateMdl(p_port->p_adapter->h_adapter, + p_desc->p_buf, + p_port->p_adapter->params.xfer_block_size ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate MDL\n") ); + return NDIS_STATUS_RESOURCES; + } + + p_net_buffer_list = NdisAllocateNetBufferAndNetBufferList( + p_port->buf_mgr.h_send_buf_pool, + 0, + 0, + p_mdl, + 0, + 0); + + if( !p_net_buffer_list ) + { + NdisFreeMdl(p_mdl); + IPOIB_PRINT_EXIT( TRACE_LEVEL_WARNING, IPOIB_DBG_SEND, + ("Failed to allocate NDIS_PACKET for copy.\n") ); + return NDIS_STATUS_RESOURCES; + } + + for (p_netbuffer = NET_BUFFER_LIST_FIRST_NB(p_net_buffer_list); + p_netbuffer != NULL; + p_netbuffer = NET_BUFFER_NEXT_NB(p_netbuffer)) + { + tot_len +=NET_BUFFER_DATA_LENGTH(p_netbuffer); + } + + /* Setup the work request. */ + p_desc->send_wr[0].local_ds[1].vaddr = cl_get_physaddr( + ((uint8_t*)p_desc->p_buf) + sizeof(eth_hdr_t) ); + p_desc->send_wr[0].local_ds[1].length = tot_len - sizeof(eth_hdr_t); + p_desc->send_wr[0].local_ds[1].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[0].wr.num_ds = 2; + + /* Free our temp packet now that the data is copied. */ + NdisFreeMdl(p_mdl); + NdisFreeNetBufferList(p_net_buffer_list); + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return NDIS_STATUS_SUCCESS; +} + +static inline NDIS_STATUS +__send_mgr_get_eth_hdr( + IN PNET_BUFFER p_net_buffer, + OUT MDL** const pp_mdl, + OUT eth_hdr_t** const pp_eth_hdr, + OUT UINT* p_mdl_len) +{ + PUCHAR p_head = NULL; + IPOIB_ENTER( IPOIB_DBG_SEND ); + + *pp_mdl = NET_BUFFER_FIRST_MDL(p_net_buffer); + + NdisQueryMdl(*pp_mdl,&p_head,p_mdl_len,NormalPagePriority); + if( ! p_head ) + { + /* Failed to get first buffer. */ + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("NdisQueryMdl failed.\n") ); + return NDIS_STATUS_FAILURE; + } + + if( *p_mdl_len < sizeof(eth_hdr_t) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("First buffer in packet smaller than eth_hdr_t: %d.\n", + *p_mdl_len) ); + return NDIS_STATUS_BUFFER_TOO_SHORT; + } + + *pp_eth_hdr = (eth_hdr_t*)(p_head + NET_BUFFER_CURRENT_MDL_OFFSET(p_net_buffer)); + + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_SEND, + ("Ethernet header:\n" + "\tsrc MAC: %02X-%02X-%02X-%02X-%02X-%02X\n" + "\tdst MAC: %02X-%02X-%02X-%02X-%02X-%02X\n" + "\tprotocol type: %04X\n", + (*pp_eth_hdr)->src.addr[0], (*pp_eth_hdr)->src.addr[1], + (*pp_eth_hdr)->src.addr[2], (*pp_eth_hdr)->src.addr[3], + (*pp_eth_hdr)->src.addr[4], (*pp_eth_hdr)->src.addr[5], + (*pp_eth_hdr)->dst.addr[0], (*pp_eth_hdr)->dst.addr[1], + (*pp_eth_hdr)->dst.addr[2], (*pp_eth_hdr)->dst.addr[3], + (*pp_eth_hdr)->dst.addr[4], (*pp_eth_hdr)->dst.addr[5], + cl_ntoh16( (*pp_eth_hdr)->type )) ); + + return NDIS_STATUS_SUCCESS; +} + + +#if !IPOIB_USE_DMA +/* Send using the MDL's page information rather than the SGL. */ +static ib_api_status_t +__send_gen( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc ) +{ + uint32_t i, j = 1; + ULONG offset; + MDL *p_mdl; + UINT num_pages, tot_len; + ULONG buf_len; + PPFN_NUMBER page_array; + boolean_t hdr_done = FALSE; + ib_api_status_t status; + PNET_BUFFER p_net_buf; + + IPOIB_ENTER( IPOIB_DBG_SEND ); + p_net_buf = NET_BUFFER_LIST_FIRST_NB(p_desc->p_netbuf_list); + NdisQueryBuffer( p_net_buf, &num_pages, NULL, &p_mdl, + &tot_len ); + + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("No buffers associated with packet.\n") ); + return IB_ERROR; + } + + /* Remember that one of the DS entries is reserved for the IPoIB header. */ + if( num_pages >= MAX_SEND_SGE ) + { + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_SEND, + ("Too many buffers to fit in WR ds_array. Copying data.\n") ); + status = __send_copy( p_port, p_desc ); + IPOIB_EXIT( IPOIB_DBG_SEND ); + return status; + } + + CL_ASSERT( tot_len > sizeof(eth_hdr_t) ); + CL_ASSERT( tot_len <= p_port->p_adapter->params.xfer_block_size ); + /* + * Assume that the ethernet header is always fully contained + * in the first page of the first MDL. This makes for much + * simpler code. + */ + offset = MmGetMdlByteOffset( p_mdl ) + sizeof(eth_hdr_t); + CL_ASSERT( offset <= PAGE_SIZE ); + + while( tot_len ) + { + buf_len = MmGetMdlByteCount( p_mdl ); + page_array = MmGetMdlPfnArray( p_mdl ); + CL_ASSERT( page_array ); + i = 0; + if( !hdr_done ) + { + CL_ASSERT( buf_len >= sizeof(eth_hdr_t) ); + /* Skip the ethernet header. */ + buf_len -= sizeof(eth_hdr_t); + CL_ASSERT( buf_len <= p_port->p_adapter->params.payload_mtu ); + if( buf_len ) + { + /* The ethernet header is a subset of this MDL. */ + CL_ASSERT( i == 0 ); + if( offset < PAGE_SIZE ) + { + p_desc->send_wr[0].local_ds[j].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[0].local_ds[j].vaddr = (page_array[i] << PAGE_SHIFT); + /* Add the byte offset since we're on the 1st page. */ + p_desc->send_wr[0].local_ds[j].vaddr += offset; + if( offset + buf_len > PAGE_SIZE ) + { + p_desc->send_wr[0].local_ds[j].length = PAGE_SIZE - offset; + buf_len -= p_desc->send_wr[0].local_ds[j].length; + } + else + { + p_desc->send_wr[0].local_ds[j].length = buf_len; + buf_len = 0; + } + /* This data segment is done. Move to the next. */ + j++; + } + /* This page is done. Move to the next. */ + i++; + } + /* Done handling the ethernet header. */ + hdr_done = TRUE; + } + + /* Finish this MDL */ + while( buf_len ) + { + p_desc->send_wr[0].local_ds[j].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[0].local_ds[j].vaddr = (page_array[i] << PAGE_SHIFT); + /* Add the first page's offset if we're on the first page. */ + if( i == 0 ) + p_desc->send_wr[0].local_ds[j].vaddr += MmGetMdlByteOffset( p_mdl ); + + if( i == 0 && (MmGetMdlByteOffset( p_mdl ) + buf_len) > PAGE_SIZE ) + { + /* Buffers spans pages. */ + p_desc->send_wr[0].local_ds[j].length = + PAGE_SIZE - MmGetMdlByteOffset( p_mdl ); + buf_len -= p_desc->send_wr[0].local_ds[j].length; + /* This page is done. Move to the next. */ + i++; + } + else + { + /* Last page of the buffer. */ + p_desc->send_wr[0].local_ds[j].length = buf_len; + buf_len = 0; + } + /* This data segment is done. Move to the next. */ + j++; + } + + tot_len -= MmGetMdlByteCount( p_mdl ); + if( !tot_len ) + break; + + NdisGetNextBuffer( p_mdl, &p_mdl ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get next buffer.\n") ); + return IB_ERROR; + } + } + + /* Set the number of data segments. */ + p_desc->send_wr[0].wr.num_ds = j; + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return IB_SUCCESS; +} + +#else + +#if 0 +void +ipoib_process_sg_list1( + IN PDEVICE_OBJECT pDO, + IN PVOID pIrp, + IN PSCATTER_GATHER_LIST p_sgl, + IN PVOID context + ) +{ + int i; + char temp[200]; + for (i = 0 ; i < 1;i++) + temp[i] = 5; +} +#endif + +void +ipoib_process_sg_list( + IN PDEVICE_OBJECT pDO, + IN PVOID pIrp, + IN PSCATTER_GATHER_LIST p_sgl, + IN PVOID context + ) +{ + NDIS_STATUS status; + ipoib_port_t *p_port; + MDL *p_mdl; + eth_hdr_t *p_eth_hdr; + UINT mdl_len; + static ipoib_send_desc_t *p_desc = NULL; + ib_send_wr_t *p_wr_failed; + NET_BUFFER_LIST *p_net_buffer_list; + NET_BUFFER *p_netbuf; + boolean_t from_queue; + ib_api_status_t ib_status; + ULONG complete_flags = 0; + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + UNREFERENCED_PARAMETER(pDO); + UNREFERENCED_PARAMETER(pIrp); + + PERF_DECLARE( SendCopy ); + PERF_DECLARE( BuildSendDesc ); + PERF_DECLARE( GetEthHdr ); + PERF_DECLARE( QueuePacket ); + PERF_DECLARE( SendMgrQueue ); + PERF_DECLARE( PostSend ); + PERF_DECLARE( ProcessFailedSends ); + PERF_DECLARE( GetEndpt ); + + + p_netbuf = (NET_BUFFER*)context; + p_net_buffer_list = (NET_BUFFER_LIST*)IPOIB_NET_BUFFER_LIST_FROM_NETBUFFER(p_netbuf); + p_port = (ipoib_port_t*)IPOIB_PORT_FROM_PACKET(p_net_buffer_list); + NDIS_SET_SEND_COMPLETE_FLAG(complete_flags, NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL); + + + cl_spinlock_acquire( &p_port->send_lock ); + if (p_desc == NULL) { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_SEND, ("Allocating send_desc First Time\n") ); + p_desc = ExAllocatePoolWithTag(NonPagedPool ,sizeof (ipoib_send_desc_t), 'XMXA'); + } + ASSERT(p_desc); + p_desc->p_netbuf_list = p_net_buffer_list; + p_desc->p_endpt = NULL; + p_desc->p_buf = NULL; + p_desc->num_wrs = 1; + + //IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_SEND, + // ("\n*******\nRECEIVED NB= %x with SG= %x\n********\n", p_netbuf, p_sgl) ); + /* Get the ethernet header so we can find the endpoint. */ + cl_perf_start( GetEthHdr ); + status = __send_mgr_get_eth_hdr( + p_netbuf, &p_mdl, &p_eth_hdr, &mdl_len ); + cl_perf_stop( &p_port->p_adapter->perf, GetEthHdr ); + + if( status != NDIS_STATUS_SUCCESS ) + { + cl_perf_start( ProcessFailedSends ); + /* fail net buffer list */ + __process_failed_send( p_port, p_desc, status, complete_flags); + cl_perf_stop( &p_port->p_adapter->perf, ProcessFailedSends ); + goto send_end; + } + //from_queue = (boolean_t)(IPOIB_FROM_QUEUE(p_netbuf) == (void*)1); + from_queue = (boolean_t)(IPOIB_FROM_QUEUE(p_netbuf) != NULL); + if (from_queue) + { + cl_perf_start( GetEndpt ); + status = __endpt_mgr_ref( p_port, p_eth_hdr->dst, &p_desc->p_endpt ); + cl_perf_stop( &p_port->p_adapter->perf, GetEndpt ); + if( status == NDIS_STATUS_PENDING ) + { + IPOIB_FROM_QUEUE(p_netbuf) = p_sgl; + cl_qlist_insert_head( &p_port->send_mgr.pending_list, + IPOIB_LIST_ITEM_FROM_PACKET( p_desc->p_netbuf_list ) ); + goto send_end; + } + else if( status != NDIS_STATUS_SUCCESS ) + { + ASSERT( status == NDIS_STATUS_NO_ROUTE_TO_DESTINATION ); + + if( ETH_IS_MULTICAST( p_eth_hdr->dst.addr ) ) + { + if( ipoib_port_join_mcast( p_port, p_eth_hdr->dst, + IB_MC_REC_STATE_FULL_MEMBER) == IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_SEND, + ("Multicast Mac - trying to join.\n") ); + IPOIB_FROM_QUEUE(p_netbuf) = p_sgl; + cl_qlist_insert_head( &p_port->send_mgr.pending_list, + IPOIB_LIST_ITEM_FROM_PACKET( p_desc->p_netbuf_list ) ); + goto send_end; + } + } + /* + * Complete the send as if we sent it - WHQL tests don't like the + * sends to fail. + */ + cl_perf_start( ProcessFailedSends ); + __process_failed_send( p_port, p_desc, NDIS_STATUS_SUCCESS,complete_flags ); + cl_perf_stop( &p_port->p_adapter->perf, ProcessFailedSends ); + goto send_end; + } + } + else + { + cl_perf_start( SendMgrQueue ); + if ( ETH_IS_MULTICAST( p_eth_hdr->dst.addr ) && + p_eth_hdr->type == ETH_PROT_TYPE_IP && + !ETH_IS_BROADCAST( p_eth_hdr->dst.addr ) ) + { + ip_hdr_t *p_ip_hdr; + uint8_t *p_tmp; + MDL *p_ip_hdr_mdl; + UINT ip_hdr_mdl_len; + + if(mdl_len >= sizeof(ip_hdr_t) + sizeof(eth_hdr_t)) + { + p_ip_hdr = (ip_hdr_t*)(p_eth_hdr + 1); + } + else + { + NdisGetNextMdl(p_mdl,&p_ip_hdr_mdl); + // Extract the ip hdr + if( !p_ip_hdr_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get IP header buffer.\n") ); + goto mc_end; + } + NdisQueryMdl(p_ip_hdr_mdl,&p_tmp,&ip_hdr_mdl_len,NormalPagePriority); + if( !p_tmp ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get IP header.\n") ); + goto mc_end; + } + if( ip_hdr_mdl_len < sizeof(ip_hdr_t) ) + { + /* This buffer is done for. Get the next buffer. */ + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Buffer too small for IP packet.\n") ); + goto mc_end; + } + p_ip_hdr = (ip_hdr_t*)(p_tmp + NET_BUFFER_CURRENT_MDL_OFFSET(p_netbuf)); + p_eth_hdr->dst.addr[1] = ((unsigned char*)&p_ip_hdr->dst_ip)[0] & 0x0f; + p_eth_hdr->dst.addr[3] = ((unsigned char*)&p_ip_hdr->dst_ip)[1]; + } + } +mc_end: + status = __send_mgr_queue( p_port, p_eth_hdr, &p_desc->p_endpt ); + cl_perf_stop( &p_port->p_adapter->perf, SendMgrQueue ); + if( status == NDIS_STATUS_PENDING ) + { + /* Queue net buffer list. */ + cl_perf_start( QueuePacket ); + NET_BUFFER_LIST_NEXT_NBL(p_net_buffer_list) = NULL; + IPOIB_FROM_QUEUE(p_netbuf) = p_sgl; + cl_qlist_insert_tail( &p_port->send_mgr.pending_list, + IPOIB_LIST_ITEM_FROM_PACKET(p_net_buffer_list) ); + cl_perf_stop( &p_port->p_adapter->perf, QueuePacket ); + goto send_end; + } + if( status != NDIS_STATUS_SUCCESS ) + { + ASSERT( status == NDIS_STATUS_NO_ROUTE_TO_DESTINATION ); + /* + * Complete the send as if we sent it - WHQL tests don't like the + * sends to fail. + */ + cl_perf_start( ProcessFailedSends ); + __process_failed_send( p_port, p_desc, NDIS_STATUS_SUCCESS, complete_flags ); + cl_perf_stop( &p_port->p_adapter->perf, ProcessFailedSends ); + goto send_end; + } + } + cl_perf_start( BuildSendDesc ); + status = __build_send_desc( p_port, p_eth_hdr, p_mdl, mdl_len, p_sgl, p_desc ); + cl_perf_stop( &p_port->p_adapter->perf, BuildSendDesc ); + + if( status != NDIS_STATUS_SUCCESS ) + { + cl_perf_start( ProcessFailedSends ); + __process_failed_send( p_port, p_desc, status, complete_flags ); + cl_perf_stop( &p_port->p_adapter->perf, ProcessFailedSends ); + goto send_end; + } + + /* Post the WR. */ + cl_perf_start( PostSend ); + cl_msg_out("sending packet with wr-id =0x%x\n",&p_desc->send_wr[0].wr.wr_id ); + ib_status = p_port->p_adapter->p_ifc->post_send( p_port->ib_mgr.h_qp, &p_desc->send_wr[0].wr, &p_wr_failed ); + cl_perf_stop( &p_port->p_adapter->perf, PostSend ); + if( ib_status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_post_send returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( ib_status )) ); + cl_perf_start( ProcessFailedSends ); + __process_failed_send( p_port, p_desc, NDIS_STATUS_FAILURE, complete_flags ); + cl_perf_stop( &p_port->p_adapter->perf, ProcessFailedSends ); + /* Flag the adapter as hung since posting is busted. */ + p_port->p_adapter->hung = TRUE; + } + cl_atomic_inc( &p_port->send_mgr.depth ); + +send_end: + if (status != NDIS_STATUS_SUCCESS) { +// IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_SEND, + // ("Free S/G List: 0x%x.\n", (UINT) (PVOID) p_sgl) ); + /*NdisMFreeNetBufferSGList( + p_port->p_adapter->NdisMiniportDmaHandle, + p_sgl, + p_netbuf);*/ + + } + + + cl_spinlock_release( &p_port->send_lock ); + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + +static NDIS_STATUS +__send_gen( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc, + IN SCATTER_GATHER_LIST *p_sgl, + IN INT lso_data_index + ) +{ + ib_api_status_t status; + uint32_t i, j = 1; + uint32_t offset = sizeof(eth_hdr_t); + PERF_DECLARE( SendCopy ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + if( !p_sgl ) + { + ASSERT( p_sgl ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get SGL from packet.\n") ); + return NDIS_STATUS_FAILURE; + } + + /* Remember that one of the DS entries is reserved for the IPoIB header. */ + if( ( p_sgl->NumberOfElements >= MAX_SEND_SGE || + p_sgl->Elements[0].Length < sizeof(eth_hdr_t)) ) + { + + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_SEND, + ("Too many buffers %d to fit in WR ds_array[%d] \ + Or buffer[0] length %d < Eth header. Copying data.\n", + p_sgl->NumberOfElements, MAX_SEND_SGE, p_sgl->Elements[0].Length ) ); + status = NDIS_STATUS_RESOURCES; + if( !p_port->p_adapter->params.cm_enabled ) + { + cl_perf_start( SendCopy ); + status = __send_copy( p_port, p_desc ); + cl_perf_stop( &p_port->p_adapter->perf, SendCopy ); + } + IPOIB_EXIT( IPOIB_DBG_SEND ); + return status; + } + + /* + * Skip the ethernet header. It is either the first element, + * or part of it. + */ + i = 0; + if( lso_data_index ) + { /* we have an LSO packet */ + i = lso_data_index; + j = 0; + } + else while( offset ) + { + if( p_sgl->Elements[i].Length <= offset ) + { + offset -= p_sgl->Elements[i++].Length; + } + else + { + p_desc->send_wr[0].local_ds[j].vaddr = + p_sgl->Elements[i].Address.QuadPart + offset; + p_desc->send_wr[0].local_ds[j].length = + p_sgl->Elements[i].Length - offset; + p_desc->send_wr[0].local_ds[j].lkey = p_port->ib_mgr.lkey; + i++; + j++; + break; + } + } + /* Now fill in the rest of the local data segments. */ + while( i < p_sgl->NumberOfElements ) + { + p_desc->send_wr[0].local_ds[j].vaddr = p_sgl->Elements[i].Address.QuadPart; + p_desc->send_wr[0].local_ds[j].length = p_sgl->Elements[i].Length; + p_desc->send_wr[0].local_ds[j].lkey = p_port->ib_mgr.lkey; + i++; + j++; + } + + /* Set the number of data segments. */ + p_desc->send_wr[0].wr.num_ds = j; + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return NDIS_STATUS_SUCCESS; +} +#endif + + +static NDIS_STATUS +__send_mgr_filter_ip( + IN ipoib_port_t* const p_port, + IN const eth_hdr_t* const p_eth_hdr, + IN MDL* p_mdl, + IN size_t buf_len, + IN SCATTER_GATHER_LIST *p_sgl, + IN OUT ipoib_send_desc_t* const p_desc ) +{ + NDIS_STATUS status; + ip_hdr_t *p_ip_hdr; + uint32_t ip_packet_len; + size_t iph_size_in_bytes; + size_t iph_options_size; + + PERF_DECLARE( QueryIp ); + PERF_DECLARE( SendTcp ); + PERF_DECLARE( FilterUdp ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + if( !buf_len ) + { + cl_perf_start( QueryIp ); + NdisGetNextMdl ( p_mdl, &p_mdl ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get IP header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + + NdisQueryMdl(p_mdl, &p_ip_hdr, &buf_len, NormalPagePriority); + if( !p_ip_hdr ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query IP header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + cl_perf_stop( &p_port->p_adapter->perf, QueryIp ); + } + else + { + p_ip_hdr = (ip_hdr_t*)(p_eth_hdr + 1); + } + if( buf_len < sizeof(ip_hdr_t) ) + { + /* This buffer is done for. Get the next buffer. */ + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Buffer too small for IP packet.\n") ); + return NDIS_STATUS_BUFFER_TOO_SHORT; + } + + switch( p_ip_hdr->prot ) + { + case IP_PROT_UDP: + + cl_perf_start( FilterUdp ); + status = __send_mgr_filter_udp( + p_port, p_ip_hdr, p_mdl, (buf_len - sizeof(ip_hdr_t)), p_sgl, p_desc ); + cl_perf_stop( &p_port->p_adapter->perf, FilterUdp ); + if( status == NDIS_STATUS_PENDING ) + { /* not DHCP packet, keep going */ + if( ETH_IS_MULTICAST( p_eth_hdr->dst.addr ) ) + p_desc->send_dir = SEND_UD_QP; + else + p_desc->send_dir = SEND_RC_QP; + break; + } + return status; + + case IP_PROT_TCP: + p_desc->send_dir = SEND_RC_QP; + break; + case IP_PROT_IGMP: + /* + In igmp packet I saw that iph arrive in 2 NDIS_BUFFERs: + 1. iph + 2. ip options + So to get the IGMP packet we need to skip the ip options NDIS_BUFFER + */ + iph_size_in_bytes = (p_ip_hdr->ver_hl & 0xf) * 4; + iph_options_size = iph_size_in_bytes - buf_len; + buf_len -= sizeof(ip_hdr_t);//without ipheader + + /* + Could be a case that arrived igmp packet not from type IGMPv2 , + but IGMPv1 or IGMPv3. + We anyway pass it to __send_mgr_filter_igmp_v2(). + */ + status = + __send_mgr_filter_igmp_v2( p_port, p_ip_hdr, iph_options_size, p_mdl, buf_len ); + if( status != NDIS_STATUS_SUCCESS ) + return status; + + case IP_PROT_ICMP: + p_desc->send_dir = SEND_UD_QP; + default: + break; + } + + if( !p_port->p_adapter->params.cm_enabled ) + { + p_desc->send_dir = SEND_UD_QP; + goto send_gen; + } + else if( endpt_cm_get_state( p_desc->p_endpt ) != IPOIB_CM_CONNECTED ) + { + p_desc->send_dir = SEND_UD_QP; + } + if( p_desc->send_dir == SEND_UD_QP ) + { + ip_packet_len = cl_ntoh16( p_ip_hdr->length ); + if( ip_packet_len > p_port->p_adapter->params.payload_mtu ) + { + //TODO: NDIS60 + #if 0 + status = __send_fragments( p_port, p_desc, (eth_hdr_t* const)p_eth_hdr, + (ip_hdr_t* const)p_ip_hdr, (uint32_t)buf_len, p_mdl ); + return status; + #endif + ASSERT(FALSE); + } + } + +send_gen: + cl_perf_start( SendTcp ); + status = __send_gen( p_port, p_desc, p_sgl, 0 ); + cl_perf_stop( &p_port->p_adapter->perf, SendTcp ); + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return status; +} + +static NDIS_STATUS +__send_mgr_filter_igmp_v2( + IN ipoib_port_t* const p_port, + IN const ip_hdr_t* const p_ip_hdr, + IN size_t iph_options_size, + IN MDL* p_mdl, + IN size_t buf_len ) +{ + igmp_v2_hdr_t *p_igmp_v2_hdr = NULL; + NDIS_STATUS endpt_status; + ipoib_endpt_t* p_endpt = NULL; + mac_addr_t fake_mcast_mac; + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_MCAST, + ("buf_len = %d,iph_options_size = %d\n",(int)buf_len,(int)iph_options_size ) ); + + if( !buf_len ) + { + // To get the IGMP packet we need to skip the ip options NDIS_BUFFER (if exists) + while ( iph_options_size ) + { + NdisGetNextMdl( p_mdl, &p_mdl ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get IGMPv2 header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + NdisQueryMdl( p_mdl, &p_igmp_v2_hdr, &buf_len, NormalPagePriority ); + if( !p_igmp_v2_hdr ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query IGMPv2 header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + iph_options_size-=buf_len; + } + + NdisGetNextMdl( p_mdl, &p_mdl ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get IGMPv2 header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + NdisQueryMdl( p_mdl, &p_igmp_v2_hdr, &buf_len, NormalPagePriority ); + if( !p_igmp_v2_hdr ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query IGMPv2 header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + } + else + { + /* assuming ip header and options are in the same packet */ + p_igmp_v2_hdr = GetIpPayloadPtr(p_ip_hdr); + } + /* Get the IGMP header length. */ + if( buf_len < sizeof(igmp_v2_hdr_t) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Buffer not large enough for IGMPv2 packet.\n") ); + return NDIS_STATUS_BUFFER_TOO_SHORT; + } + + // build fake mac from igmp packet group address + fake_mcast_mac.addr[0] = 1; + fake_mcast_mac.addr[1] = ((unsigned char*)&p_igmp_v2_hdr->group_address)[0] & 0x0f; + fake_mcast_mac.addr[2] = 0x5E; + fake_mcast_mac.addr[3] = ((unsigned char*)&p_igmp_v2_hdr->group_address)[1]; + fake_mcast_mac.addr[4] = ((unsigned char*)&p_igmp_v2_hdr->group_address)[2]; + fake_mcast_mac.addr[5] = ((unsigned char*)&p_igmp_v2_hdr->group_address)[3]; + + switch ( p_igmp_v2_hdr->type ) + { + case IGMP_V2_MEMBERSHIP_REPORT: + /* + This mean that some body open listener on this group + Change type of mcast endpt to SEND_RECV endpt. So mcast garbage collector + will not delete this mcast endpt. + */ + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_MCAST, + ("Catched IGMP_V2_MEMBERSHIP_REPORT message\n") ); + endpt_status = __endpt_mgr_ref( p_port, fake_mcast_mac, &p_endpt ); + if ( p_endpt ) + { + cl_obj_lock( &p_port->obj ); + p_endpt->is_mcast_listener = TRUE; + cl_obj_unlock( &p_port->obj ); + ipoib_endpt_deref( p_endpt ); + } + break; + + case IGMP_V2_LEAVE_GROUP: + /* + This mean that somebody CLOSE listener on this group . + Change type of mcast endpt to SEND_ONLY endpt. So mcast + garbage collector will delete this mcast endpt next time. + */ + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_MCAST, + ("Catched IGMP_V2_LEAVE_GROUP message\n") ); + endpt_status = __endpt_mgr_ref( p_port, fake_mcast_mac, &p_endpt ); + if ( p_endpt ) + { + cl_obj_lock( &p_port->obj ); + p_endpt->is_mcast_listener = FALSE; + p_endpt->is_in_use = FALSE; + cl_obj_unlock( &p_port->obj ); + ipoib_endpt_deref( p_endpt ); + } + + __port_do_mcast_garbage(p_port); + + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_MCAST, + ("Send Unknown IGMP message: 0x%x \n", p_igmp_v2_hdr->type ) ); + break; + } + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return NDIS_STATUS_SUCCESS; +} + +static NDIS_STATUS +__send_mgr_filter_udp( + IN ipoib_port_t* const p_port, + IN const ip_hdr_t* const p_ip_hdr, + IN MDL* p_mdl, + IN size_t buf_len, + IN SCATTER_GATHER_LIST *p_sgl, + IN OUT ipoib_send_desc_t* const p_desc ) +{ + ib_api_status_t status; + udp_hdr_t *p_udp_hdr; + PERF_DECLARE( QueryUdp ); + PERF_DECLARE( SendUdp ); + PERF_DECLARE( FilterDhcp ); + //TODO NDIS60 remove this param + UNUSED_PARAM(p_sgl); + IPOIB_ENTER( IPOIB_DBG_SEND ); + + if( !buf_len ) + { + cl_perf_start( QueryUdp ); + NdisGetNextMdl( p_mdl, &p_mdl ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get UDP header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + NdisQueryMdl( p_mdl, &p_udp_hdr, &buf_len, NormalPagePriority ); + if( !p_udp_hdr ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query UDP header buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + cl_perf_stop( &p_port->p_adapter->perf, QueryUdp ); + } + else + { + p_udp_hdr = (udp_hdr_t*)GetIpPayloadPtr(p_ip_hdr); + } + /* Get the UDP header and check the destination port numbers. */ + + if (p_ip_hdr->offset > 0) { + /* This is a fragmented part of UDP packet + * Only first packet will contain UDP header in such case + * So, return if offset > 0 + */ + return NDIS_STATUS_PENDING; + } + + if( buf_len < sizeof(udp_hdr_t) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Buffer not large enough for UDP packet.\n") ); + return NDIS_STATUS_BUFFER_TOO_SHORT; + } + + if( (p_udp_hdr->src_port != DHCP_PORT_CLIENT || + p_udp_hdr->dst_port != DHCP_PORT_SERVER) && + (p_udp_hdr->src_port != DHCP_PORT_SERVER || + p_udp_hdr->dst_port != DHCP_PORT_CLIENT) ) + { + /* Not a DHCP packet. */ + return NDIS_STATUS_PENDING; + } + + buf_len -= sizeof(udp_hdr_t); + + /* Allocate our scratch buffer. */ + p_desc->p_buf = (send_buf_t*) + ExAllocateFromNPagedLookasideList( &p_port->buf_mgr.send_buf_list ); + if( !p_desc->p_buf ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query DHCP packet buffer.\n") ); + return NDIS_STATUS_RESOURCES; + } + /* Copy the IP and UDP headers. */ + cl_memcpy( &p_desc->p_buf->ip.hdr, p_ip_hdr , sizeof(ip_hdr_t) ); + cl_memcpy( + &p_desc->p_buf->ip.prot.udp.hdr, p_udp_hdr, sizeof(udp_hdr_t) ); + + cl_perf_start( FilterDhcp ); + status = __send_mgr_filter_dhcp( + p_port, p_udp_hdr, p_mdl, buf_len, p_desc ); + cl_perf_stop( &p_port->p_adapter->perf, FilterDhcp ); + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return status; +} + +unsigned short ipchksum(unsigned short *ip, int len) +{ + unsigned long sum = 0; + + len >>= 1; + while (len--) { + sum += *(ip++); + if (sum > 0xFFFF) + sum -= 0xFFFF; + } + return (unsigned short)((~sum) & 0x0000FFFF); +} + +static NDIS_STATUS +__send_mgr_filter_dhcp( + IN ipoib_port_t* const p_port, + IN const udp_hdr_t* const p_udp_hdr, + IN NDIS_BUFFER* p_mdl, + IN size_t buf_len, + IN OUT ipoib_send_desc_t* const p_desc ) +{ + dhcp_pkt_t *p_dhcp; + dhcp_pkt_t *p_ib_dhcp; + uint8_t *p_option, *p_cid = NULL; + uint8_t msg = 0; + size_t len; + ib_gid_t gid; + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + if( !buf_len ) + { + NdisGetNextMdl( p_mdl, &p_mdl ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get DHCP buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + NdisQueryMdl( p_mdl, &p_dhcp, &buf_len, NormalPagePriority ); + if( !p_dhcp ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query DHCP buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + } + else + { + p_dhcp = (dhcp_pkt_t*)(p_udp_hdr + 1); + } + + if( buf_len < DHCP_MIN_SIZE ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Buffer not large enough for DHCP packet.\n") ); + return NDIS_STATUS_BUFFER_TOO_SHORT; + } + + p_ib_dhcp = &p_desc->p_buf->ip.prot.udp.dhcp; + cl_memcpy( p_ib_dhcp, p_dhcp, buf_len ); + + /* Now scan through the options looking for the client identifier. */ + p_option = &p_ib_dhcp->options[4]; + while( *p_option != DHCP_OPT_END && p_option < &p_ib_dhcp->options[312] ) + { + switch( *p_option ) + { + case DHCP_OPT_PAD: + p_option++; + break; + + case DHCP_OPT_MSG: + msg = p_option[2]; + p_option += 3; + break; + + case DHCP_OPT_CLIENT_ID: + p_cid = p_option; + /* Fall through. */ + + default: + /* + * All other options have a length byte following the option code. + * Offset by the length to get to the next option. + */ + p_option += (p_option[1] + 2); + } + } + + switch( msg ) + { + /* Client messages */ + case DHCPDISCOVER: + case DHCPREQUEST: + p_ib_dhcp->flags |= DHCP_FLAGS_BROADCAST; + /* Fall through */ + case DHCPDECLINE: + case DHCPRELEASE: + case DHCPINFORM: + /* Fix up the client identifier option */ + if( p_cid ) + { + /* do we need to replace it ? len eq ETH MAC sz 'and' MAC is mine */ + if( p_cid[1] == HW_ADDR_LEN+1 && !cl_memcmp( &p_cid[3], + &p_port->p_adapter->params.conf_mac.addr, HW_ADDR_LEN ) ) + { + /* Make sure there's room to extend it. 23 is the size of + * the CID option for IPoIB. + */ + if( buf_len + 23 - p_cid[1] > sizeof(dhcp_pkt_t) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Can't convert CID to IPoIB format.\n") ); + return NDIS_STATUS_RESOURCES; + } + /* Move the existing options down, and add a new CID option */ + len = p_option - ( p_cid + p_cid[1] + 2 ); + p_option = p_cid + p_cid[1] + 2; + RtlMoveMemory( p_cid, p_option, len ); + + p_cid += len; + p_cid[0] = DHCP_OPT_CLIENT_ID; + p_cid[1] = 21; + p_cid[2] = DHCP_HW_TYPE_IB; + } + else + { + p_cid[2] = DHCP_HW_TYPE_IB; + } + } + else + { + /* + * Make sure there's room to extend it. 23 is the size of + * the CID option for IPoIB. + */ + if( buf_len + 23 > sizeof(dhcp_pkt_t) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Can't convert CID to IPoIB format.\n") ); + return NDIS_STATUS_RESOURCES; + } + + p_cid = p_option; + p_option = p_cid + 23; + p_option[0] = DHCP_OPT_END; + p_cid[0] = DHCP_OPT_CLIENT_ID; + p_cid[1] = 21; + p_cid[2] = DHCP_HW_TYPE_IB; + } + + CL_ASSERT( p_cid[1] == 21 ); + p_cid[23]= DHCP_OPT_END; + ib_gid_set_default( &gid, p_port->p_adapter->guids.port_guid.guid ); + cl_memcpy( &p_cid[7], &gid, sizeof(ib_gid_t) ); + cl_memcpy( &p_cid[3], &p_port->ib_mgr.qpn, sizeof(p_port->ib_mgr.qpn) ); + p_ib_dhcp->htype = DHCP_HW_TYPE_IB; + + /* update lengths to include any change we made */ + p_desc->p_buf->ip.hdr.length = cl_ntoh16( sizeof(ip_hdr_t) + sizeof(udp_hdr_t) + sizeof(dhcp_pkt_t) ); + p_desc->p_buf->ip.prot.udp.hdr.length = cl_ntoh16( sizeof(udp_hdr_t) + sizeof(dhcp_pkt_t) ); + + /* update crc in ip header */ + //if( !p_port->p_adapter->params.send_chksum_offload ) + //{ //TODO ? + p_desc->p_buf->ip.hdr.chksum = 0; + p_desc->p_buf->ip.hdr.chksum = ipchksum((unsigned short*) &p_desc->p_buf->ip.hdr, sizeof(ip_hdr_t)); + //} TODO ?? + break; + + /* Server messages. */ + case DHCPOFFER: + case DHCPACK: + case DHCPNAK: + /* don't touch server messages */ + break; + + default: + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Invalide message type.\n") ); + return NDIS_STATUS_INVALID_DATA; + } + /* no chksum for udp */ + p_desc->p_buf->ip.prot.udp.hdr.chksum = 0; + p_desc->send_wr[0].local_ds[1].vaddr = cl_get_physaddr( p_desc->p_buf ); + p_desc->send_wr[0].local_ds[1].length = sizeof(ip_hdr_t) + sizeof(udp_hdr_t) + sizeof(dhcp_pkt_t); + p_desc->send_wr[0].local_ds[1].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[0].wr.num_ds = 2; + p_desc->send_dir = SEND_UD_QP; + IPOIB_EXIT( IPOIB_DBG_SEND ); + return NDIS_STATUS_SUCCESS; +} + + +static NDIS_STATUS +__send_mgr_filter_arp( + IN ipoib_port_t* const p_port, + IN const eth_hdr_t* const p_eth_hdr, + IN MDL* p_mdl, + IN size_t buf_len, + IN OUT ipoib_send_desc_t* const p_desc ) +{ + arp_pkt_t *p_arp; + ipoib_arp_pkt_t *p_ib_arp; + NDIS_STATUS status; + mac_addr_t null_hw = {0}; + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + if( !buf_len ) + { + NdisGetNextMdl( p_mdl, &p_mdl ); + if( !p_mdl ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get ARP buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + NdisQueryMdl( p_mdl, &p_arp, &buf_len, NormalPagePriority ); + if( !p_arp ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get query ARP buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + } + else + { + p_arp = (arp_pkt_t*)(p_eth_hdr + 1); + } + + /* Single buffer ARP packet. */ + if( buf_len < sizeof(arp_pkt_t) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Buffer too short for ARP.\n") ); + return NDIS_STATUS_BUFFER_TOO_SHORT; + } + + if( p_arp->prot_type != ETH_PROT_TYPE_IP ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Unsupported protocol type.\n") ); + return NDIS_STATUS_INVALID_DATA; + } + + /* Allocate our scratch buffer. */ + p_desc->p_buf = (send_buf_t*) + NdisAllocateFromNPagedLookasideList( &p_port->buf_mgr.send_buf_list ); + if( !p_desc->p_buf ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query ARP packet buffer.\n") ); + return NDIS_STATUS_RESOURCES; + } + p_ib_arp = (ipoib_arp_pkt_t*)p_desc->p_buf; + + /* Convert the ARP payload. */ + p_ib_arp->hw_type = ARP_HW_TYPE_IB; + p_ib_arp->prot_type = p_arp->prot_type; + p_ib_arp->hw_size = sizeof(ipoib_hw_addr_t); + p_ib_arp->prot_size = p_arp->prot_size; + p_ib_arp->op = p_arp->op; + + ipoib_addr_set_qpn( &p_ib_arp->src_hw, p_port->ib_mgr.qpn ); +#if 0 + + if( p_port->p_adapter->params.cm_enabled ) + { + ipoib_addr_set_flags( &p_ib_arp->src_hw, IPOIB_CM_FLAG_RC ); + } +#endif + + ib_gid_set_default( &p_ib_arp->src_hw.gid, + p_port->p_adapter->guids.port_guid.guid ); + p_ib_arp->src_ip = p_arp->src_ip; + if( cl_memcmp( &p_arp->dst_hw, &null_hw, sizeof(mac_addr_t) ) ) + { + /* Get the endpoint referenced by the dst_hw address. */ + net32_t qpn = 0; + status = __endpt_mgr_get_gid_qpn( p_port, p_arp->dst_hw, + &p_ib_arp->dst_hw.gid, &qpn ); + if( status != NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed lookup of destination HW address\n") ); + return status; + } + ipoib_addr_set_qpn( &p_ib_arp->dst_hw, qpn ); +#if 0 + if( p_arp->op == ARP_OP_REP && + p_port->p_adapter->params.cm_enabled && + p_desc->p_endpt->cm_flag == IPOIB_CM_FLAG_RC ) + { + cm_state_t cm_state; + cm_state = + ( cm_state_t )InterlockedCompareExchange( (volatile LONG *)&p_desc->p_endpt->conn.state, + IPOIB_CM_CONNECT, IPOIB_CM_DISCONNECTED ); + switch( cm_state ) + { + case IPOIB_CM_DISCONNECTED: + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("ARP REPLY pending Endpt[%p] QPN %#x MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + p_desc->p_endpt, + cl_ntoh32( ipoib_addr_get_qpn( &p_ib_arp->dst_hw )), + p_desc->p_endpt->mac.addr[0], p_desc->p_endpt->mac.addr[1], + p_desc->p_endpt->mac.addr[2], p_desc->p_endpt->mac.addr[3], + p_desc->p_endpt->mac.addr[4], p_desc->p_endpt->mac.addr[5] ) ); + ipoib_addr_set_sid( &p_desc->p_endpt->conn.service_id, + ipoib_addr_get_qpn( &p_ib_arp->dst_hw ) ); + + NdisFreeToNPagedLookasideList( + &p_port->buf_mgr.send_buf_list, p_desc->p_buf ); + cl_qlist_insert_tail( &p_port->send_mgr.pending_list, + IPOIB_LIST_ITEM_FROM_PACKET( p_desc->p_buf ) ); + NdisInterlockedInsertTailList( &p_port->endpt_mgr.pending_conns, + &p_desc->p_endpt->list_item, + &p_port->endpt_mgr.conn_lock ); + cl_event_signal( &p_port->endpt_mgr.event ); + return NDIS_STATUS_PENDING; + + case IPOIB_CM_CONNECT: + /* queue ARP REP packet until connected */ + NdisFreeToNPagedLookasideList( + &p_port->buf_mgr.send_buf_list, p_desc->p_buf ); + cl_qlist_insert_tail( &p_port->send_mgr.pending_list, + IPOIB_LIST_ITEM_FROM_PACKET( p_desc->p_pkt ) ); + return NDIS_STATUS_PENDING; + default: + break; + } + } +#endif + } + else + { + cl_memclr( &p_ib_arp->dst_hw, sizeof(ipoib_hw_addr_t) ); + } + +#if 0 //DBG + if( p_port->p_adapter->params.cm_enabled ) + { + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + (" ARP %s SEND to ENDPT[%p] State: %d flag: %#x, QPN: %#x MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + ( p_ib_arp->op == ARP_OP_REP ? "REP": "REQ"), p_desc->p_endpt, + endpt_cm_get_state( p_desc->p_endpt ), + p_desc->p_endpt->cm_flag, + cl_ntoh32( ipoib_addr_get_qpn( &p_ib_arp->dst_hw )), + p_desc->p_endpt->mac.addr[0], p_desc->p_endpt->mac.addr[1], + p_desc->p_endpt->mac.addr[2], p_desc->p_endpt->mac.addr[3], + p_desc->p_endpt->mac.addr[4], p_desc->p_endpt->mac.addr[5] )); + } +#endif + + p_ib_arp->dst_ip = p_arp->dst_ip; + + p_desc->send_wr[0].local_ds[1].vaddr = cl_get_physaddr( p_ib_arp ); + p_desc->send_wr[0].local_ds[1].length = sizeof(ipoib_arp_pkt_t); + p_desc->send_wr[0].local_ds[1].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[0].wr.num_ds = 2; + p_desc->send_wr[0].wr.p_next = NULL; + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return NDIS_STATUS_SUCCESS; +} + +static inline NDIS_STATUS +__send_mgr_queue( + IN ipoib_port_t* const p_port, + IN eth_hdr_t* const p_eth_hdr, + OUT ipoib_endpt_t** const pp_endpt ) +{ + NDIS_STATUS status; + + PERF_DECLARE( GetEndpt ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + /* Check the send queue and pend the request if not empty. */ + if( cl_qlist_count( &p_port->send_mgr.pending_list ) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_WARNING, IPOIB_DBG_SEND, + ("Pending list not empty.\n") ); + return NDIS_STATUS_PENDING; + } + + /* Check the send queue and pend the request if not empty. */ + if( p_port->send_mgr.depth == p_port->p_adapter->params.sq_depth ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_WARNING, IPOIB_DBG_SEND, + ("No available WQEs.\n") ); + return NDIS_STATUS_PENDING; + } + + cl_perf_start( GetEndpt ); + status = __endpt_mgr_ref( p_port, p_eth_hdr->dst, pp_endpt ); + cl_perf_stop( &p_port->p_adapter->perf, GetEndpt ); + + if( status == NDIS_STATUS_NO_ROUTE_TO_DESTINATION && + ETH_IS_MULTICAST( p_eth_hdr->dst.addr ) ) + { + if( ipoib_port_join_mcast( p_port, p_eth_hdr->dst, + IB_MC_REC_STATE_FULL_MEMBER) == IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_SEND, + ("Multicast Mac - trying to join.\n") ); + return NDIS_STATUS_PENDING; + } + } + else if ( status == NDIS_STATUS_SUCCESS && + ETH_IS_MULTICAST( p_eth_hdr->dst.addr ) && + !ETH_IS_BROADCAST( p_eth_hdr->dst.addr ) ) + { + CL_ASSERT( (*pp_endpt) ); + CL_ASSERT((*pp_endpt)->h_mcast != NULL); + (*pp_endpt)->is_in_use = TRUE; + } + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return status; +} + + +static NDIS_STATUS +__build_send_desc( + IN ipoib_port_t* const p_port, + IN eth_hdr_t* const p_eth_hdr, + IN MDL* const p_mdl, + IN const size_t mdl_len, + IN SCATTER_GATHER_LIST *p_sgl, + IN OUT ipoib_send_desc_t* const p_desc ) +{ + NDIS_STATUS status; + int32_t hdr_idx; + uint32_t mss; + //PVOID* pp_tmp; + PNDIS_TCP_IP_CHECKSUM_NET_BUFFER_LIST_INFO p_checksum_list_info; + PNDIS_TCP_LARGE_SEND_OFFLOAD_NET_BUFFER_LIST_INFO p_lso_info; + PERF_DECLARE( SendMgrFilter ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + /* Format the send descriptor. */ + p_checksum_list_info = NET_BUFFER_LIST_INFO( p_desc->p_netbuf_list,TcpIpChecksumNetBufferListInfo); + //pp_tmp = &NET_BUFFER_LIST_INFO(p_desc->p_netbuf_list, TcpIpChecksumNetBufferListInfo); + //p_checksum_list_info = ( ) ((PULONG)pp_tmp); + p_lso_info = NET_BUFFER_LIST_INFO( p_desc->p_netbuf_list, TcpLargeSendNetBufferListInfo ); + + /* Format the send descriptor. */ + hdr_idx = cl_atomic_inc( &p_port->hdr_idx ); + hdr_idx &= (p_port->p_adapter->params.sq_depth - 1); + ASSERT( hdr_idx < p_port->p_adapter->params.sq_depth ); + p_port->hdr[hdr_idx].type = p_eth_hdr->type; + p_port->hdr[hdr_idx].resv = 0; + + p_desc->send_wr[0].local_ds[0].vaddr = cl_get_physaddr( &p_port->hdr[hdr_idx] ); + p_desc->send_wr[0].local_ds[0].length = sizeof(ipoib_hdr_t); + p_desc->send_wr[0].local_ds[0].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[0].wr.send_opt = 0; + + mss = (p_lso_info->LsoV1Transmit.MSS | p_lso_info->LsoV2Transmit.MSS); + //TODO: first check params.lso, and thereafter calculate LSO + if( p_port->p_adapter->params.lso && mss ) + + + { + ASSERT( mss == (p_lso_info->LsoV1Transmit.MSS & p_lso_info->LsoV2Transmit.MSS)); + ASSERT ( (mss & (1<<20)) == mss); + status = __build_lso_desc( p_port, p_desc, mss, p_sgl, hdr_idx, p_lso_info ); + if( status != NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__build_lso_desc returned 0x%08X.\n", status) ); + return status; + } + } + else + { + uint32_t i; + cl_perf_start( SendMgrFilter ); + status = __send_mgr_filter( + p_port, p_eth_hdr, p_mdl, mdl_len,p_sgl, p_desc ); + cl_perf_stop( &p_port->p_adapter->perf, SendMgrFilter ); + if( status != NDIS_STATUS_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__send_mgr_filter returned 0x%08X.\n", status) ); + return status; + } + + if( p_desc->send_dir == SEND_UD_QP ) + { + p_desc->send_qp = p_port->ib_mgr.h_qp; // UD QP + for( i = 0; i < p_desc->num_wrs; i++ ) + { + p_desc->send_wr[i].wr.dgrm.ud.remote_qp = p_desc->p_endpt->qpn; + p_desc->send_wr[i].wr.dgrm.ud.remote_qkey = p_port->ib_mgr.bcast_rec.qkey; + p_desc->send_wr[i].wr.dgrm.ud.h_av = p_desc->p_endpt->h_av; + p_desc->send_wr[i].wr.dgrm.ud.pkey_index = p_port->pkey_index; + p_desc->send_wr[i].wr.dgrm.ud.rsvd = NULL; + p_desc->send_wr[i].wr.send_opt = 0; + + if( p_port->p_adapter->params.send_chksum_offload && + ( p_checksum_list_info->Transmit.IsIPv4 || + p_checksum_list_info->Transmit.IsIPv6 )) + { + // Set transimition checksum offloading + if( p_checksum_list_info->Transmit.IpHeaderChecksum ) + { + p_desc->send_wr[i].wr.send_opt |= IB_SEND_OPT_TX_IP_CSUM; + } + if( p_checksum_list_info->Transmit.TcpChecksum ) + { + p_desc->send_wr[i].wr.send_opt |= IB_SEND_OPT_TX_TCP_UDP_CSUM; + } + } + } + } + else // RC QP + { + CL_ASSERT( p_desc->send_dir == SEND_RC_QP ); + p_desc->send_qp = p_desc->p_endpt->conn.h_work_qp; + } + for( i = 0; i < p_desc->num_wrs; i++ ) + { + p_desc->send_wr[i].wr.wr_type = WR_SEND; + p_desc->send_wr[i].wr.wr_id = 0; + p_desc->send_wr[i].wr.ds_array = &p_desc->send_wr[i].local_ds[0]; + if( i ) + { + p_desc->send_wr[i-1].wr.p_next = &p_desc->send_wr[i].wr; + } + } + //TODO p_net_buf or p_buf +// p_desc->send_wr[p_desc->num_wrs - 1].wr.wr_id = (uintn_t)NET_BUFFER_LIST_FIRST_NB(p_desc->p_netbuf_list ); +//???? IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, ("WR_ID was set to NBL 0x%x \n",p_desc->p_netbuf_list )); + p_desc->send_wr[p_desc->num_wrs - 1].wr.wr_id = (uintn_t)p_desc->p_netbuf_list ; + + p_desc->send_wr[p_desc->num_wrs - 1].wr.send_opt |= IB_SEND_OPT_SIGNALED; + p_desc->send_wr[p_desc->num_wrs - 1].wr.p_next = NULL; + } + + /* Store context in our reserved area of the packet. */ + IPOIB_PORT_FROM_PACKET( p_desc->p_netbuf_list ) = p_port; + IPOIB_ENDPT_FROM_PACKET( p_desc->p_netbuf_list ) = p_desc->p_endpt; + IPOIB_SEND_FROM_NETBUFFER( NET_BUFFER_LIST_FIRST_NB(p_desc->p_netbuf_list ))= p_desc->p_buf; + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return NDIS_STATUS_SUCCESS; +} + +static NDIS_STATUS +__build_lso_desc( + IN ipoib_port_t* const p_port, + IN OUT ipoib_send_desc_t* const p_desc, + IN ULONG mss, + IN SCATTER_GATHER_LIST *p_sgl, + IN int32_t hdr_idx, + IN PNDIS_TCP_LARGE_SEND_OFFLOAD_NET_BUFFER_LIST_INFO p_lso_info) +{ + NDIS_STATUS status; + LsoData TheLsoData; + UINT IndexOfData = 0; + + PNET_BUFFER FirstBuffer = NET_BUFFER_LIST_FIRST_NB (p_desc->p_netbuf_list); + ULONG PacketLength = NET_BUFFER_DATA_LENGTH(FirstBuffer); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + + + memset(&TheLsoData, 0, sizeof TheLsoData ); + status = GetLsoHeaderSize( + FirstBuffer, + &TheLsoData, + &IndexOfData, + &p_port->hdr[hdr_idx] ); + + if ((status != NDIS_STATUS_SUCCESS ) || + (TheLsoData.FullBuffers != TheLsoData.UsedBuffers)) + { + ASSERT(FALSE); + + IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, ("<-- Throwing this packet\n")); + + if( status == NDIS_STATUS_SUCCESS ) + { + status = NDIS_STATUS_INVALID_PACKET; + } + return status; + } + ASSERT(TheLsoData.LsoHeaderSize> 0); + // Tell NDIS how much we will send. + //PktExt->NdisPacketInfo[TcpLargeSendPacketInfo] = UlongToPtr(PacketLength); + p_lso_info->LsoV1TransmitComplete.TcpPayload = PacketLength; + + p_desc->send_wr[0].wr.dgrm.ud.mss = mss; + p_desc->send_wr[0].wr.dgrm.ud.header = TheLsoData.LsoBuffers[0].pData; + p_desc->send_wr[0].wr.dgrm.ud.hlen = TheLsoData.LsoHeaderSize ;//lso_header_size; + p_desc->send_wr[0].wr.dgrm.ud.remote_qp = p_desc->p_endpt->qpn; + p_desc->send_wr[0].wr.dgrm.ud.remote_qkey = p_port->ib_mgr.bcast_rec.qkey; + p_desc->send_wr[0].wr.dgrm.ud.h_av = p_desc->p_endpt->h_av; + p_desc->send_wr[0].wr.dgrm.ud.pkey_index = p_port->pkey_index; + p_desc->send_wr[0].wr.dgrm.ud.rsvd = NULL; + + //TODO: Should be NBL or p_desc + p_desc->send_wr[0].wr.wr_id = (uintn_t)p_desc->p_netbuf_list; + p_desc->send_wr[0].wr.ds_array = p_desc->send_wr[0].local_ds; + p_desc->send_wr[0].wr.wr_type = WR_LSO; + p_desc->send_wr[0].wr.send_opt = + (IB_SEND_OPT_TX_IP_CSUM | IB_SEND_OPT_TX_TCP_UDP_CSUM) | IB_SEND_OPT_SIGNALED; + + p_desc->send_wr[0].wr.p_next = NULL; + p_desc->send_qp = p_port->ib_mgr.h_qp; + p_desc->send_dir = SEND_UD_QP; + status = __send_gen(p_port, p_desc, p_sgl, IndexOfData ); + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return status; +} + +static inline void +__process_failed_send( + IN ipoib_port_t* const p_port, + IN ipoib_send_desc_t* const p_desc, + IN const NDIS_STATUS status, + IN ULONG compl_flags) +{ + IPOIB_ENTER( IPOIB_DBG_SEND ); + + /* Complete the packet. */ + NET_BUFFER_LIST_NEXT_NBL(p_desc->p_netbuf_list) = NULL; + NET_BUFFER_LIST_STATUS(p_desc->p_netbuf_list) = status; + NdisMSendNetBufferListsComplete( p_port->p_adapter->h_adapter, + p_desc->p_netbuf_list, compl_flags ); + ipoib_inc_send_stat( p_port->p_adapter, IP_STAT_ERROR, 0 ); + /* Deref the endpoint. */ + if( p_desc->p_endpt ) + ipoib_endpt_deref( p_desc->p_endpt ); + + if( p_desc->p_buf ) + { + NdisFreeToNPagedLookasideList( + &p_port->buf_mgr.send_buf_list, p_desc->p_buf ); + } + + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + +// max number of physical fragmented buffers +#define MAX_PHYS_BUF_FRAG_ELEMENTS 0x29 +#define MP_FRAG_ELEMENT SCATTER_GATHER_ELEMENT +#define PMP_FRAG_ELEMENT PSCATTER_GATHER_ELEMENT + + +typedef struct _MP_FRAG_LIST { + ULONG NumberOfElements; + ULONG_PTR Reserved; + SCATTER_GATHER_ELEMENT Elements[MAX_PHYS_BUF_FRAG_ELEMENTS]; +} MP_FRAG_LIST, *PMP_FRAG_LIST; + + +void +CreateFragList( + ULONG PhysBufCount, + PNET_BUFFER NetBuff, + ULONG PacketLength, + PMP_FRAG_LIST pFragList + ) +{ +// ETH_ENTER(ETH_SND); + + ULONG i = 0; + int j=0; + + UINT buf_len = NET_BUFFER_DATA_LENGTH(NetBuff); + PMDL pMdl = NET_BUFFER_CURRENT_MDL(NetBuff); + + ULONG CurrentMdlDataOffset = NET_BUFFER_CURRENT_MDL_OFFSET(NetBuff); + ASSERT(MmGetMdlByteCount(pMdl) >= CurrentMdlDataOffset); + + + ASSERT(NetBuff != NULL); +#ifdef DBG + ASSERT(PhysBufCount <= MAX_PHYS_BUF_FRAG_ELEMENTS); +#else + UNREFERENCED_PARAMETER(PhysBufCount); +#endif + + ASSERT(buf_len > 0); + UNREFERENCED_PARAMETER(PacketLength); + + +// ETH_PRINT(TRACE_LEVEL_VERBOSE, ETH_SND, "CreateFragList: NetBuff %p, Length =0x%x\n", NetBuff, buf_len); + + while ( (pMdl != NULL) && (buf_len != 0) ) + { + PPFN_NUMBER page_array = MmGetMdlPfnArray(pMdl); + int MdlBufCount = ADDRESS_AND_SIZE_TO_SPAN_PAGES(MmGetMdlVirtualAddress(pMdl), MmGetMdlByteCount(pMdl)); + + ULONG offset = MmGetMdlByteOffset(pMdl) + CurrentMdlDataOffset ; + ULONG MdlBytesCount = MmGetMdlByteCount(pMdl) - CurrentMdlDataOffset; + CurrentMdlDataOffset = 0; + + if( MdlBytesCount == 0 ) + { + pMdl = pMdl->Next; + continue; + } + + ASSERT( (buf_len > 0) && (MdlBytesCount > 0) ); + +// ETH_PRINT(TRACE_LEVEL_VERBOSE, ETH_SND, "CreateFragList: pMdl=%p, MdlBytesCount=x%x, MdlBufCount=0x%x\n", pMdl, MdlBytesCount, MdlBufCount); + + if (MdlBytesCount > 0) + { + if( buf_len > MdlBytesCount) + { + buf_len -= MdlBytesCount; + } + else + { + MdlBytesCount = buf_len; + buf_len = 0; + } + // + // In some cases the mdlcount is greater than needed and in the last page + // there is 0 bytes + // + for (j=0; ((j< MdlBufCount) && (MdlBytesCount > 0)); j++) + { + ASSERT(MdlBytesCount > 0); + if (j ==0 ) + { + // + // First page + // + ULONG64 ul64PageNum = page_array[j]; + pFragList->Elements[i].Address.QuadPart = (ul64PageNum << PAGE_SHIFT)+ offset; + if( offset + MdlBytesCount > PAGE_SIZE ) + { + // + // the data slides behind the page boundry + // + ASSERT(PAGE_SIZE > offset); + pFragList->Elements[i].Length = PAGE_SIZE - offset; + MdlBytesCount -= pFragList->Elements[i].Length; + } + else + { + // + // All the data is hold in one page + // + pFragList->Elements[i].Length = MdlBytesCount; + MdlBytesCount = 0; + } + +// ETH_PRINT(TRACE_LEVEL_VERBOSE, ETH_SND, "CreateFragList: j == 0, MdlBytesCount=x%x, i = %d, element.length=0x%x \n", MdlBytesCount, i, pFragList->Elements[i].Length); + } + else + { + if (page_array[j] == (page_array[j-1] + 1)) + { + + ULONG size = min(PAGE_SIZE, MdlBytesCount); + i -= 1; + pFragList->Elements[i].Length += size; + MdlBytesCount -= size; + } + else + { + // + // Not first page. so the data always start at the begining of the page + // + ULONG64 ul64PageNum = page_array[j]; + pFragList->Elements[i].Address.QuadPart = (ul64PageNum << PAGE_SHIFT); + pFragList->Elements[i].Length = min(PAGE_SIZE, MdlBytesCount); + MdlBytesCount -= pFragList->Elements[i].Length; + } + +// ETH_PRINT(TRACE_LEVEL_VERBOSE, ETH_SND, "CreateFragList: j != 0, MdlBytesCount=x%x, i = %d, element.length=0x%x \n", MdlBytesCount, i, pFragList->Elements[i].Length); + } + i++; + ASSERT(i <= MAX_PHYS_BUF_FRAG_ELEMENTS); + } + } + + pMdl = pMdl->Next; + } + + if (buf_len != 0) + { + // + // In some cases the size in MDL isn't equal to the buffer size. In such + // a case we need to add the rest of packet to last chunk + // + ASSERT(i > 0); // To prevent array underflow + pFragList->Elements[i-1].Length += buf_len; +// ETH_PRINT(TRACE_LEVEL_ERROR, ETH_SND, "CreateFragList: buf_len != 0, i = %d, element.length=0x%x \n", i -1, pFragList->Elements[i-1].Length); + } + + ASSERT(i <= PhysBufCount); + pFragList->NumberOfElements = i; + +#ifdef DBG +{ + ULONG size = 0; + for (i = 0; i < pFragList->NumberOfElements; ++i) + { + size += pFragList->Elements[i].Length; + } + ASSERT(size == PacketLength); +} +#endif + +// ETH_EXIT(ETH_SND); +} + + + + +void +ipoib_port_send( + IN ipoib_port_t* const p_port, + IN NET_BUFFER_LIST *p_net_buffer_list, + IN ULONG send_flags) +{ + NDIS_STATUS status; + PNET_BUFFER p_netbuf; + UINT buf_cnt = 0; + //ipoib_send_desc_t *p_desc; + ULONG send_complete_flags = 0; + KIRQL old_irql; + PVOID p_sgl; + + PERF_DECLARE( GetEthHdr ); + PERF_DECLARE( BuildSendDesc ); + PERF_DECLARE( QueuePacket ); + PERF_DECLARE( SendMgrQueue ); + PERF_DECLARE( PostSend ); + PERF_DECLARE( ProcessFailedSends ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + if (NDIS_TEST_SEND_AT_DISPATCH_LEVEL(send_flags)) + { + //TODO Tzachid: make an assert here to validate your IRQL + //ASSERT (KeGetCurrentIRQL() == DISPATCH_LEVEL); + NDIS_SET_SEND_COMPLETE_FLAG(send_complete_flags, NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL); + } else { + //ASSERT (KeGetCurrentIRQL() == PASSIVE_LEVEL); + } + + cl_obj_lock( &p_port->obj ); + if( p_port->state != IB_QPS_RTS ) + { + + + cl_obj_unlock( &p_port->obj ); + + + NET_BUFFER_LIST_STATUS(p_net_buffer_list) = NDIS_STATUS_FAILURE; + NET_BUFFER_LIST_NEXT_NBL(p_net_buffer_list) = NULL; + ipoib_inc_send_stat( p_port->p_adapter, IP_STAT_DROPPED, 0 ); + + NdisMSendNetBufferListsComplete( + p_port->p_adapter->h_adapter, + p_net_buffer_list, + send_complete_flags); + + return; + } + cl_obj_unlock( &p_port->obj ); + + //IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + // ("Processing netbuffer list: %x\n", p_net_buffer_list)); + for (p_netbuf = NET_BUFFER_LIST_FIRST_NB(p_net_buffer_list); + p_netbuf != NULL; + p_netbuf = NET_BUFFER_NEXT_NB(p_netbuf)) + { + IPOIB_PORT_FROM_PACKET(p_net_buffer_list) = p_port; + IPOIB_NET_BUFFER_LIST_FROM_NETBUFFER(p_netbuf) = p_net_buffer_list; + IPOIB_FROM_QUEUE(p_netbuf) = NULL; + /*p_desc = &p_port->send_mgr.desc; + p_desc->p_buf = p_netbuf; + p_desc->p_endpt = NULL; + p_desc->p_buf = NULL; + p_desc->send_qp = NULL; + p_desc->num_wrs = 1; + p_desc->send_dir = 0;*/ + + old_irql = KeGetCurrentIrql(); + if (old_irql < DISPATCH_LEVEL) + { + KeRaiseIrqlToDpcLevel(); + } + ++buf_cnt; + //IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + // ("[%d] Netbuf = %x\n",buf_cnt, p_netbuf) ); + if (cl_is_item_in_qlist( &p_port->send_mgr.pending_list, + IPOIB_LIST_ITEM_FROM_PACKET( p_net_buffer_list ))) { + p_sgl = IPOIB_FROM_QUEUE(p_netbuf); + //IPOIB_FROM_QUEUE(p_net_buffer) = (void*)1; + ASSERT (p_sgl); + //IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + // ("[%d] FROM_QUEUE Netbuf = %x, found SGL = %x\n",buf_cnt, p_netbuf, p_sgl) ); + status = NDIS_STATUS_SUCCESS; + } else { + +//#if 0 + CHAR *pTemp = ExAllocatePoolWithTag(NonPagedPool , p_port->p_adapter->sg_list_size, 'abcd'); + CL_ASSERT(pTemp != NULL); + status = NDIS_STATUS_SUCCESS; + p_sgl = pTemp; + CreateFragList(NdisQueryNetBufferPhysicalCount(p_netbuf), p_netbuf, NET_BUFFER_DATA_LENGTH(p_netbuf), p_sgl); + IPOIB_FROM_QUEUE(p_netbuf) = NULL; + /*IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("[%d] Allocation from scratch: Netbuf = %x, found SGL = %x, PhysBufCnt=%ld, NB LEN = %ld, sg_list_size=%ld\n", + buf_cnt, p_netbuf, p_sgl,NdisQueryNetBufferPhysicalCount(p_netbuf) , + NET_BUFFER_DATA_LENGTH(p_netbuf),p_port->p_adapter->sg_list_size) ); + */ + ipoib_process_sg_list(NULL, NULL, p_sgl, p_netbuf); + status = NDIS_STATUS_SUCCESS; +//#endif +#if 0 + status = NdisMAllocateNetBufferSGList( + p_port->p_adapter->NdisMiniportDmaHandle, + p_netbuf, + p_netbuf, + NDIS_SG_LIST_WRITE_TO_DEVICE, + NULL, + 0); +#endif + } + KeLowerIrql (old_irql); + + if( status != NDIS_STATUS_SUCCESS ) + { + /* fail net buffer list */ + NET_BUFFER_LIST_NEXT_NBL(p_net_buffer_list) = NULL; + NET_BUFFER_LIST_STATUS(p_net_buffer_list) = NDIS_STATUS_RESOURCES; + NdisMSendNetBufferListsComplete( + p_port->p_adapter->h_adapter, + p_net_buffer_list, + send_complete_flags); + break; + } + ASSERT(buf_cnt); + IPOIB_GET_NET_BUFFER_LIST_REF_COUNT(p_net_buffer_list) = (PVOID)(ULONG_PTR)buf_cnt; + } + + /* Post the WR. * + cl_perf_start( PostSend ); + ib_status = p_port->p_adapter->p_ifc->post_send( p_desc->send_qp, &p_desc->send_wr[0].wr, &p_wr_failed ); + cl_perf_stop( &p_port->p_adapter->perf, PostSend ); + if( ib_status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_post_send returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( ib_status )) ); + cl_perf_start( ProcessFailedSends ); + __process_failed_send( p_port, p_desc, NDIS_STATUS_FAILURE ); + cl_perf_stop( &p_port->p_adapter->perf, ProcessFailedSends ); + * Flag the adapter as hung since posting is busted. * + p_port->p_adapter->hung = TRUE; + continue; + } + + cl_atomic_inc( &p_port->send_mgr.depth ); + } + cl_spinlock_release( &p_port->send_lock ); + + IPOIB_EXIT( IPOIB_DBG_SEND );*/ +} + + +void +ipoib_port_resume( + IN ipoib_port_t* const p_port, + IN boolean_t b_pending ) +{ + NDIS_STATUS status; + cl_list_item_t *p_item; + NET_BUFFER *p_net_buffer; + NET_BUFFER_LIST *p_net_buffer_list; + //ipoib_send_desc_t *p_desc; + KIRQL old_irql; + UINT buf_cnt = 0; + NET_BUFFER_LIST *p_prev_nbl = NULL; + PVOID p_sgl; + static PVOID p_prev_sgl = NULL; + + PERF_DECLARE( GetEndpt ); + PERF_DECLARE( BuildSendDesc ); + PERF_DECLARE( ProcessFailedSends ); + PERF_DECLARE( PostSend ); + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + UNUSED_PARAM(b_pending); + + cl_obj_lock( &p_port->obj ); + if( p_port->state != IB_QPS_RTS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_WARNING, IPOIB_DBG_SEND, + ("Invalid state - Aborting.\n") ); + cl_obj_unlock( &p_port->obj ); + return; + } + cl_obj_unlock( &p_port->obj ); + +//TODO NDIS60 +////?????????????? cl_spinlock_acquire( &p_port->send_lock ); + + for( p_item = cl_qlist_head( &p_port->send_mgr.pending_list ); + p_item != cl_qlist_end( &p_port->send_mgr.pending_list ); + p_item = cl_qlist_head( &p_port->send_mgr.pending_list ) ) + { + + + /* Check the send queue and pend the request if not empty. */ + if( p_port->send_mgr.depth == p_port->p_adapter->params.sq_depth ) + { + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_SEND, + ("No available WQEs.\n") ); + break; + } + + p_net_buffer_list = IPOIB_PACKET_FROM_LIST_ITEM( + cl_qlist_remove_head( &p_port->send_mgr.pending_list ) ); + if (p_prev_nbl == p_net_buffer_list) { +// IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + // ("TRYING TO PROCESS ONCE AGAIN, EXITING: %x\n", p_net_buffer_list)); + break; //TODO more sophisticated mechanism to avoid starvation + } + old_irql = KeGetCurrentIrql(); + if (old_irql < DISPATCH_LEVEL) + { + KeRaiseIrqlToDpcLevel(); + } +// IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + // ("Processing netbuffer list from queue: %x\n", (UINT) (PVOID) p_net_buffer_list)); + + for( p_net_buffer = NET_BUFFER_LIST_FIRST_NB(p_net_buffer_list); + p_net_buffer != NULL; + p_net_buffer = NET_BUFFER_NEXT_NB(p_net_buffer), buf_cnt++) + { + + + p_sgl = IPOIB_FROM_QUEUE(p_net_buffer); + //IPOIB_FROM_QUEUE(p_net_buffer) = (void*)1; + ASSERT (p_sgl); + IPOIB_PORT_FROM_PACKET(p_net_buffer_list) = p_port; + IPOIB_NET_BUFFER_LIST_FROM_NETBUFFER(p_net_buffer) = p_net_buffer_list; + /*p_desc = &p_port->send_mgr.desc; + p_desc->p_buf = p_net_buffer; + p_desc->p_endpt = NULL; + p_desc->p_buf = NULL; + p_desc->send_qp = NULL; + p_desc->num_wrs = 1; + p_desc->send_dir = 0;*/ + +// IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + // ("[%d] Netbuf = %x, p_sgl = %x\n",buf_cnt, p_net_buffer, p_sgl) ); + ASSERT(p_sgl); + if (p_sgl != (void*) 1) { + ipoib_process_sg_list(NULL, NULL, p_sgl, p_net_buffer); + status = NDIS_STATUS_SUCCESS; + } + else { + ASSERT(FALSE); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Getting strange flow\n") ); + NdisMFreeNetBufferSGList( + p_port->p_adapter->NdisMiniportDmaHandle, + p_sgl, + p_net_buffer ); + status = NdisMAllocateNetBufferSGList( + p_port->p_adapter->NdisMiniportDmaHandle, + p_net_buffer, + p_net_buffer, + NDIS_SG_LIST_WRITE_TO_DEVICE, + NULL, + 0 /*p_port->p_adapter->sg_list_size*/ ); + } + p_prev_sgl = p_sgl; + if( status != NDIS_STATUS_SUCCESS ) + { + /* fail net buffer list */ + NET_BUFFER_LIST_NEXT_NBL(p_net_buffer_list) = NULL; + NET_BUFFER_LIST_STATUS(p_net_buffer_list) = NDIS_STATUS_RESOURCES; + NdisMSendNetBufferListsComplete( + p_port->p_adapter->h_adapter, + p_net_buffer_list, + 0); + break; + } + } + + KeLowerIrql (old_irql); + + + p_prev_nbl = p_net_buffer_list; + + } + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + + + +static void +__send_cb( + IN const ib_cq_handle_t h_cq, + IN void *cq_context ) +{ + ipoib_port_t *p_port; + ib_api_status_t status; + ib_wc_t wc[MAX_SEND_WC], *p_wc, *p_free; + cl_qlist_t done_list; + NET_BUFFER_LIST *p_nbl; + uint32_t length; + ipoib_endpt_t *p_endpt; + send_buf_t *p_send_buf; + ip_stat_sel_t type; + size_t i; + NET_BUFFER *p_netbuffer = NULL; + + PERF_DECLARE( SendCompBundle ); + PERF_DECLARE( SendCb ); + PERF_DECLARE( PollSend ); + PERF_DECLARE( SendComp ); + PERF_DECLARE( FreeSendBuf ); + PERF_DECLARE( RearmSend ); + PERF_DECLARE( PortResume ); +//return;//??????????? + IPOIB_ENTER( IPOIB_DBG_SEND ); + + cl_perf_clr( SendCompBundle ); + + cl_perf_start( SendCb ); + + UNUSED_PARAM( h_cq ); + + cl_qlist_init( &done_list ); + + p_port = (ipoib_port_t*)cq_context; + + ipoib_port_ref( p_port, ref_send_cb ); + + for( i = 0; i < MAX_SEND_WC; i++ ) + wc[i].p_next = &wc[i + 1]; + wc[MAX_SEND_WC - 1].p_next = NULL; + + do + { + p_free = wc; + cl_perf_start( PollSend ); + status = p_port->p_adapter->p_ifc->poll_cq( p_port->ib_mgr.h_send_cq, &p_free, &p_wc ); + cl_perf_stop( &p_port->p_adapter->perf, PollSend ); + CL_ASSERT( status == IB_SUCCESS || status == IB_NOT_FOUND ); + + while( p_wc ) + { + cl_perf_start( SendComp ); + CL_ASSERT( p_wc->status != IB_WCS_SUCCESS || p_wc->wc_type == IB_WC_SEND ); + p_nbl = (NET_BUFFER_LIST*)(uintn_t)p_wc->wr_id; + //IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_SEND, + //("[1]Successfull send completion for NBL=0x%x .\n", (UINT) (PVOID) p_nbl )); + CL_ASSERT( p_nbl ); + CL_ASSERT( IPOIB_PORT_FROM_PACKET( p_nbl ) == p_port ); + length = 0; + p_endpt = IPOIB_ENDPT_FROM_PACKET( p_nbl ); + p_send_buf = IPOIB_SEND_FROM_NETBUFFER( NET_BUFFER_LIST_FIRST_NB (p_nbl )); + + switch( p_wc->status ) + { + case IB_WCS_SUCCESS: + if( p_endpt->h_mcast ) + { + if( p_endpt->dgid.multicast.raw_group_id[11] == 0xFF && + p_endpt->dgid.multicast.raw_group_id[10] == 0xFF && + p_endpt->dgid.multicast.raw_group_id[12] == 0xFF && + p_endpt->dgid.multicast.raw_group_id[13] == 0xFF ) + { + type = IP_STAT_BCAST_BYTES; + } + else + { + type = IP_STAT_MCAST_BYTES; + } + } + else + { + type = IP_STAT_UCAST_BYTES; + } + for (p_netbuffer = NET_BUFFER_LIST_FIRST_NB(p_nbl); + p_netbuffer != NULL; + p_netbuffer = NET_BUFFER_NEXT_NB(p_netbuffer)) + { + length += NET_BUFFER_DATA_LENGTH(p_netbuffer); + } + ipoib_inc_send_stat( p_port->p_adapter, type, length ); + // IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_SEND, + //("Successfull send completion for NBL=0x%x .\n", (UINT) (PVOID) p_nbl) ); + NET_BUFFER_LIST_STATUS(p_nbl) = NDIS_STATUS_SUCCESS; + IPOIB_DEC_NET_BUFFER_LIST_REF_COUNT(p_nbl); + if (IPOIB_GET_NET_BUFFER_LIST_REF_COUNT(p_nbl) == 0) + NdisMSendNetBufferListsComplete(p_port->p_adapter->h_adapter, + p_nbl, + 0); + break; + + case IB_WCS_WR_FLUSHED_ERR: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_SEND, + ("Flushed send completion.\n") ); + ipoib_inc_send_stat( p_port->p_adapter, IP_STAT_DROPPED, 0 ); + NET_BUFFER_LIST_STATUS(p_nbl) = NDIS_STATUS_RESET_IN_PROGRESS; + NdisMSendNetBufferListsComplete(p_port->p_adapter->h_adapter, + p_nbl, + 0); + break; + + default: + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Send failed with %s (vendor specific %#x)\n", + p_port->p_adapter->p_ifc->get_wc_status_str( p_wc->status ), + (int)p_wc->vendor_specific) ); + ipoib_inc_send_stat( p_port->p_adapter, IP_STAT_ERROR, 0 ); + NET_BUFFER_LIST_STATUS(p_nbl) = NDIS_STATUS_FAILURE; + NdisMSendNetBufferListsComplete(p_port->p_adapter->h_adapter, + p_nbl, + 0); + break; + } + cl_perf_stop( &p_port->p_adapter->perf, SendComp ); + /* Dereference the enpoint used for the transfer. */ + ipoib_endpt_deref( p_endpt ); + + if( p_send_buf ) + { + cl_perf_start( FreeSendBuf ); + NdisFreeToNPagedLookasideList( &p_port->buf_mgr.send_buf_list, + p_send_buf ); + cl_perf_stop( &p_port->p_adapter->perf, FreeSendBuf ); + } + + cl_atomic_dec( &p_port->send_mgr.depth ); + + p_wc = p_wc->p_next; + cl_perf_inc( SendCompBundle ); + } + /* If we didn't use up every WC, break out. */ + } while( !p_free ); + + /* Rearm the CQ. */ + cl_perf_start( RearmSend ); + status = p_port->p_adapter->p_ifc->rearm_cq( p_port->ib_mgr.h_send_cq, FALSE ); + cl_perf_stop( &p_port->p_adapter->perf, RearmSend ); + CL_ASSERT( status == IB_SUCCESS ); + + /* Resume any sends awaiting resources. */ + cl_perf_start( PortResume ); + ipoib_port_resume( p_port, TRUE ); + cl_perf_stop( &p_port->p_adapter->perf, PortResume ); + + ipoib_port_deref( p_port, ref_send_cb ); + + cl_perf_stop( &p_port->p_adapter->perf, SendCb ); + cl_perf_update_ctr( &p_port->p_adapter->perf, SendCompBundle ); + + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + + +/****************************************************************************** +* +* Endpoint manager implementation +* +******************************************************************************/ +static void +__endpt_mgr_construct( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + cl_qmap_init( &p_port->endpt_mgr.mac_endpts ); + cl_qmap_init( &p_port->endpt_mgr.lid_endpts ); + cl_fmap_init( &p_port->endpt_mgr.gid_endpts, __gid_cmp ); + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + +static void +__endpt_cm_mgr_thread( +IN void* p_context ); + +static ib_api_status_t +__endpt_mgr_init( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); +#if 0 + if( p_port->p_adapter->params.cm_enabled ) + { + cl_fmap_init( &p_port->endpt_mgr.conn_endpts, __gid_cmp ); + + NdisInitializeListHead( &p_port->endpt_mgr.pending_conns ); + NdisAllocateSpinLock( &p_port->endpt_mgr.conn_lock ); + cl_event_init( &p_port->endpt_mgr.event, FALSE ); + + NdisInitializeListHead( &p_port->endpt_mgr.remove_conns ); + NdisAllocateSpinLock( &p_port->endpt_mgr.remove_lock ); + + cl_thread_init( &p_port->endpt_mgr.h_thread, + __endpt_cm_mgr_thread, + ( const void *)p_port, + "CmEndPtMgr" ); + } +#endif + UNUSED_PARAM(p_port); + IPOIB_EXIT( IPOIB_DBG_INIT ); + return IB_SUCCESS; +} + +static void +__endpt_cm_mgr_thread( +IN void* p_context ) +{ + ib_api_status_t ib_status; + LIST_ENTRY *p_item; + ipoib_endpt_t *p_endpt; + ipoib_port_t *p_port =( ipoib_port_t *)p_context; + + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Starting Port [%d] Endpt CM thread \n", p_port->port_num ) ); + + while( !p_port->endpt_mgr.thread_is_done ) + { + cl_event_wait_on( &p_port->endpt_mgr.event, EVENT_NO_TIMEOUT, FALSE ); + + while( ( p_item = NdisInterlockedRemoveHeadList( + &p_port->endpt_mgr.pending_conns, + &p_port->endpt_mgr.conn_lock) ) != NULL ) + { + + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, list_item ); + if( p_port->endpt_mgr.thread_is_done ) + { + endpt_cm_set_state( p_endpt, IPOIB_CM_DISCONNECTED ); + continue; + } + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Endpt[%p] CONNECT REQ to MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + p_endpt, + p_endpt->mac.addr[0], p_endpt->mac.addr[1], + p_endpt->mac.addr[2], p_endpt->mac.addr[3], + p_endpt->mac.addr[4], p_endpt->mac.addr[5] ) ); + + if( !p_endpt->conn.h_send_qp ) + { + ib_status = endpt_cm_create_qp( p_endpt, &p_endpt->conn.h_send_qp ); + if( ib_status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Endpt [%p ] CM create QP failed status %#x\n", p_endpt, ib_status ) ); + } + else + { + ib_status = ipoib_endpt_connect( p_endpt ); + if( ib_status != IB_SUCCESS && ib_status != IB_PENDING ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Endpt [ %p ] conn REQ failed status %#x\n", p_endpt, ib_status ) ); + } + } + if( ib_status != IB_SUCCESS && ib_status != IB_PENDING ) + { + endpt_cm_set_state( p_endpt, IPOIB_CM_DESTROY ); + endpt_cm_flush_recv( p_port, p_endpt ); + endpt_cm_set_state( p_endpt, IPOIB_CM_DISCONNECTED ); + } + } + + }//while( p_item != NULL ) + + while( ( p_item = NdisInterlockedRemoveHeadList( + &p_port->endpt_mgr.remove_conns, + &p_port->endpt_mgr.remove_lock ) ) != NULL ) + { + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, list_item ); + + endpt_cm_set_state( p_endpt, IPOIB_CM_DESTROY ); + + IPOIB_PRINT( TRACE_LEVEL_WARNING, IPOIB_DBG_INIT, + ("\nDESTROYING Endpt[%p] MAC %02x:%02x:%02x:%02x:%02x:%02x\n", + p_endpt, + p_endpt->mac.addr[0], p_endpt->mac.addr[1], + p_endpt->mac.addr[2], p_endpt->mac.addr[3], + p_endpt->mac.addr[4], p_endpt->mac.addr[5] ) ); + endpt_cm_flush_recv( p_port, p_endpt ); + endpt_cm_set_state( p_endpt, IPOIB_CM_DISCONNECTED ); + cl_obj_destroy( &p_endpt->obj ); + } + } + + p_port->endpt_mgr.thread_is_done++; + NdisFreeSpinLock( &p_port->endpt_mgr.conn_lock ); + + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + (" Port [%d] Endpt thread is done\n", p_port->port_num ) ); +} + +static void +__endpt_mgr_destroy( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_INIT ); + CL_ASSERT( cl_is_qmap_empty( &p_port->endpt_mgr.mac_endpts ) ); + CL_ASSERT( cl_is_qmap_empty( &p_port->endpt_mgr.lid_endpts ) ); + CL_ASSERT( cl_is_fmap_empty( &p_port->endpt_mgr.gid_endpts ) ); + UNUSED_PARAM(p_port); +#if 0 + if( p_port->p_adapter->params.cm_enabled ) + { + CL_ASSERT( cl_is_fmap_empty( &p_port->endpt_mgr.conn_endpts ) ); + } +#endif + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +__endpt_mgr_remove_all( + IN ipoib_port_t* const p_port ) +{ + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + cl_obj_lock( &p_port->obj ); + /* Wait for all readers to complete. */ + while( p_port->endpt_rdr ) + ; + /* + * We don't need to initiate destruction - this is called only + * from the __port_destroying function, and destruction cascades + * to all child objects. Just clear all the maps. + */ + cl_qmap_remove_all( &p_port->endpt_mgr.mac_endpts ); + cl_qmap_remove_all( &p_port->endpt_mgr.lid_endpts ); + cl_fmap_remove_all( &p_port->endpt_mgr.gid_endpts ); + cl_obj_unlock( &p_port->obj ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); +} + + +static void +__endpt_mgr_reset_all( + IN ipoib_port_t* const p_port ) +{ + cl_map_item_t *p_item; + cl_fmap_item_t *p_fmap_item; + ipoib_endpt_t *p_endpt; + cl_qlist_t mc_list; + cl_qlist_t conn_list; + uint32_t local_exist = 0; + NDIS_LINK_STATE link_state; + NDIS_STATUS_INDICATION status_indication; + + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + cl_qlist_init( &mc_list ); + cl_qlist_init( &conn_list ); +//??? cl_obj_lock( &p_port->obj ); + /* Wait for all readers to complete. */ + while( p_port->endpt_rdr ) + ; + +#if 0 + __endpt_mgr_remove_all(p_port); +#else + link_state.Header.Revision = NDIS_LINK_STATE_REVISION_1; + link_state.Header.Type = NDIS_OBJECT_TYPE_DEFAULT; + link_state.Header.Size = sizeof(NDIS_LINK_STATE); + link_state.MediaConnectState = MediaConnectStateDisconnected; + link_state.MediaDuplexState = MediaDuplexStateFull; + link_state.XmitLinkSpeed = link_state.RcvLinkSpeed = IPOIB_MEDIA_MAX_SPEED; + + IPOIB_INIT_NDIS_STATUS_INDICATION(&status_indication, + p_port->p_adapter->h_adapter, + NDIS_STATUS_LINK_STATE, + (PVOID)&link_state, + sizeof(link_state)); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, ("Indicate DISCONNECT!\n") ); + NdisMIndicateStatusEx(p_port->p_adapter->h_adapter,&status_indication); + + link_state.MediaConnectState = MediaConnectStateConnected; + IPOIB_INIT_NDIS_STATUS_INDICATION(&status_indication, + p_port->p_adapter->h_adapter, + NDIS_STATUS_LINK_STATE, + (PVOID)&link_state, + sizeof(link_state)); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, ("Indicate Connect\n") ); + NdisMIndicateStatusEx(p_port->p_adapter->h_adapter,&status_indication); + + + // IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + // ("Link DOWN!\n") ); + + if( p_port->p_local_endpt ) + { + //TODO: CM RESTORE + //ipoib_port_cancel_listen( p_port, p_port->p_local_endpt ); + + cl_fmap_remove_item( &p_port->endpt_mgr.gid_endpts, + &p_port->p_local_endpt->gid_item ); + cl_qmap_remove_item( &p_port->endpt_mgr.mac_endpts, + &p_port->p_local_endpt->mac_item ); + cl_qmap_remove_item( &p_port->endpt_mgr.lid_endpts, + &p_port->p_local_endpt->lid_item ); + + cl_qlist_insert_head( + &mc_list, &p_port->p_local_endpt->mac_item.pool_item.list_item ); + local_exist = 1; + + p_port->p_local_endpt = NULL; + } + + p_item = cl_qmap_head( &p_port->endpt_mgr.mac_endpts ); + while( p_item != cl_qmap_end( &p_port->endpt_mgr.mac_endpts ) ) + { + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, mac_item ); + p_item = cl_qmap_next( p_item ); + if( p_endpt->h_mcast ) + { + /* + * We destroy MC endpoints since they will get recreated + * when the port comes back up and we rejoin the MC groups. + */ + cl_qmap_remove_item( &p_port->endpt_mgr.mac_endpts, + &p_endpt->mac_item ); + cl_fmap_remove_item( &p_port->endpt_mgr.gid_endpts, + &p_endpt->gid_item ); + + cl_qlist_insert_tail( + &mc_list, &p_endpt->mac_item.pool_item.list_item ); + } + /* destroy connected endpoints if any */ + else if( p_port->p_adapter->params.cm_enabled && + endpt_cm_get_state( p_endpt ) != IPOIB_CM_DISCONNECTED ) + { + p_fmap_item = cl_fmap_get( &p_port->endpt_mgr.conn_endpts, &p_endpt->dgid ); + if( p_fmap_item != cl_fmap_end( &p_port->endpt_mgr.conn_endpts ) ) + { + cl_fmap_remove_item( &p_port->endpt_mgr.conn_endpts, + &p_endpt->conn_item ); + } + cl_qmap_remove_item( &p_port->endpt_mgr.mac_endpts, + &p_endpt->mac_item ); + cl_fmap_remove_item( &p_port->endpt_mgr.gid_endpts, + &p_endpt->gid_item ); + + cl_qlist_insert_tail( + &conn_list, &p_endpt->mac_item.pool_item.list_item ); + } + if( p_endpt->h_av ) + { + /* Destroy the AV for all other endpoints. */ + p_port->p_adapter->p_ifc->destroy_av( p_endpt->h_av ); + p_endpt->h_av = NULL; + } + + if( p_endpt->dlid ) + { + cl_qmap_remove_item( &p_port->endpt_mgr.lid_endpts, + &p_endpt->lid_item ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("<__endptr_mgr_reset_all: setting p_endpt->dlid to 0\n")); + p_endpt->dlid = 0; + } + + } +#endif +//??? cl_obj_unlock( &p_port->obj ); + + //TODO CM + /*while( cl_qlist_count( &conn_list ) ) + { + endpt_cm_destroy_conn( p_port, + PARENT_STRUCT( cl_qlist_remove_head( &conn_list ), + ipoib_endpt_t, mac_item.pool_item.list_item ) ); + }*/ + + if(cl_qlist_count( &mc_list ) - local_exist) + { + p_port->mcast_cnt = (uint32_t)cl_qlist_count( &mc_list ) - local_exist; + } + else + { + p_port->mcast_cnt = 0; + KeSetEvent( &p_port->leave_mcast_event, EVENT_INCREMENT, FALSE ); + } + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT,("p_port->mcast_cnt = %d\n", p_port->mcast_cnt - local_exist)); + + /* Destroy all multicast endpoints now that we have released the lock. */ + while( cl_qlist_count( &mc_list ) ) + { + cl_list_item_t *p_item; + p_item = cl_qlist_remove_head( &mc_list ); + p_endpt = PARENT_STRUCT(p_item, ipoib_endpt_t, mac_item.pool_item.list_item); + cl_obj_destroy( &p_endpt->obj); + } + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); +} + + +/* + * Called when updating an endpoint entry in response to an ARP. + * Because receive processing is serialized, and holds a reference + * on the endpoint reader, we wait for all *other* readers to exit before + * removing the item. + */ +static void +__endpt_mgr_remove( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt ) +{ +#if 0 //CM + cl_fmap_item_t* p_fmap_item; +#endif + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + /* This function must be called from the receive path */ + CL_ASSERT(p_port->endpt_rdr > 0); + + cl_obj_lock( &p_port->obj ); + /* Wait for all readers to complete. */ + while( p_port->endpt_rdr > 1 ) + ; + + /* Remove the endpoint from the maps so further requests don't find it. */ + cl_qmap_remove_item( &p_port->endpt_mgr.mac_endpts, &p_endpt->mac_item ); + /* + * The enpoints are *ALWAYS* in both the MAC and GID maps. They are only + * in the LID map if the GID has the same subnet prefix as us. + */ + cl_fmap_remove_item( &p_port->endpt_mgr.gid_endpts, &p_endpt->gid_item ); +#if 0 + + if( p_port->p_adapter->params.cm_enabled ) + { + p_fmap_item = cl_fmap_get( &p_port->endpt_mgr.conn_endpts, &p_endpt->dgid ); + + if( p_fmap_item != cl_fmap_end( &p_port->endpt_mgr.conn_endpts ) ) + { + cl_fmap_remove_item( &p_port->endpt_mgr.conn_endpts, + &p_endpt->conn_item ); + } + } +#endif + if( p_endpt->dlid ) + { + cl_qmap_remove_item( &p_port->endpt_mgr.lid_endpts, + &p_endpt->lid_item ); + } + + cl_obj_unlock( &p_port->obj ); + + //TODO CM + //endpt_cm_destroy_conn( p_port, p_endpt ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); +} + + +NTSTATUS +ipoib_mac_to_gid( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ib_gid_t* p_gid ) +{ + ipoib_endpt_t* p_endpt; + cl_map_item_t *p_item; + uint64_t key = 0; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + cl_memcpy( &key, &mac, sizeof(mac_addr_t) ); + + cl_obj_lock( &p_port->obj ); + + p_item = cl_qmap_get( &p_port->endpt_mgr.mac_endpts, key ); + if( p_item == cl_qmap_end( &p_port->endpt_mgr.mac_endpts ) ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed endpoint lookup.\n") ); + return STATUS_INVALID_PARAMETER; + } + + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, mac_item ); + *p_gid = p_endpt->dgid; + + cl_obj_unlock( &p_port->obj ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return STATUS_SUCCESS; +} + + +NTSTATUS +ipoib_mac_to_path( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ib_path_rec_t* p_path ) +{ + ipoib_endpt_t* p_endpt; + cl_map_item_t *p_item; + uint64_t key = 0; + uint8_t sl; + net32_t flow_lbl; + uint8_t hop_limit; + uint8_t pkt_life; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + cl_memcpy( &key, &mac, sizeof(mac_addr_t) ); + + cl_obj_lock( &p_port->obj ); + + if( p_port->p_local_endpt == NULL ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("No local endpoint.\n") ); + return STATUS_INVALID_PARAMETER; + } + + if( mac.addr[0] == 0 && mac.addr[1] == 0 && mac.addr[2] == 0 && + mac.addr[3] == 0 && mac.addr[4] == 0 && mac.addr[5] == 0 ) + { + p_endpt = p_port->p_local_endpt; + } + else + { + p_item = cl_qmap_get( &p_port->endpt_mgr.mac_endpts, key ); + if( p_item == cl_qmap_end( &p_port->endpt_mgr.mac_endpts ) ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed endpoint lookup.\n") ); + return STATUS_INVALID_PARAMETER; + } + + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, mac_item ); + } + + p_path->service_id = 0; + p_path->dgid = p_endpt->dgid; + p_path->sgid = p_port->p_local_endpt->dgid; + p_path->dlid = p_endpt->dlid; + p_path->slid = p_port->p_local_endpt->dlid; + + ib_member_get_sl_flow_hop( + p_port->ib_mgr.bcast_rec.sl_flow_hop, + &sl, + &flow_lbl, + &hop_limit + ); + + if( p_path->slid == p_path->dlid ) + pkt_life = 0; + else + pkt_life = p_port->ib_mgr.bcast_rec.pkt_life; + + ib_path_rec_init_local( + p_path, + &p_endpt->dgid, + &p_port->p_local_endpt->dgid, + p_endpt->dlid, + p_port->p_local_endpt->dlid, + 1, + p_port->ib_mgr.bcast_rec.pkey, + sl, 0, + IB_PATH_SELECTOR_EXACTLY, p_port->ib_mgr.bcast_rec.mtu, + IB_PATH_SELECTOR_EXACTLY, p_port->ib_mgr.bcast_rec.rate, + IB_PATH_SELECTOR_EXACTLY, pkt_life, + 0 ); + + /* Set global routing information. */ + ib_path_rec_set_hop_flow_raw( p_path, hop_limit, flow_lbl, FALSE ); + p_path->tclass = p_port->ib_mgr.bcast_rec.tclass; + + cl_obj_unlock( &p_port->obj ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return STATUS_SUCCESS; +} + + +static inline NDIS_STATUS +__endpt_mgr_ref( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ipoib_endpt_t** const pp_endpt ) +{ + NDIS_STATUS status; + cl_map_item_t *p_item; + uint64_t key; + + PERF_DECLARE( EndptQueue ); + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + if( !cl_memcmp( &mac, &p_port->p_adapter->params.conf_mac, sizeof(mac) ) ) + { + /* Discard loopback traffic. */ + IPOIB_PRINT(TRACE_LEVEL_WARNING, IPOIB_DBG_ENDPT, + ("Discarding loopback traffic\n") ); + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return NDIS_STATUS_NO_ROUTE_TO_DESTINATION; + } + + key = 0; + cl_memcpy( &key, &mac, sizeof(mac_addr_t) ); + + cl_obj_lock( &p_port->obj ); + + IPOIB_PRINT( TRACE_LEVEL_VERBOSE, IPOIB_DBG_ENDPT, + ("Look for :\t MAC: %02X-%02X-%02X-%02X-%02X-%02X\n", + mac.addr[0], mac.addr[1], mac.addr[2], + mac.addr[3], mac.addr[4], mac.addr[5]) ); + + p_item = cl_qmap_get( &p_port->endpt_mgr.mac_endpts, key ); + if( p_item == cl_qmap_end( &p_port->endpt_mgr.mac_endpts ) ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("Failed endpoint lookup.\n") ); + return NDIS_STATUS_NO_ROUTE_TO_DESTINATION; + } + + *pp_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, mac_item ); + ipoib_endpt_ref( *pp_endpt ); + + cl_obj_unlock( &p_port->obj ); + + cl_perf_start( EndptQueue ); + status = ipoib_endpt_queue( *pp_endpt ); + cl_perf_stop( &p_port->p_adapter->perf, EndptQueue ); + if( status != NDIS_STATUS_SUCCESS ) + *pp_endpt = NULL; + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return status; +} + + +static inline NDIS_STATUS +__endpt_mgr_get_gid_qpn( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ib_gid_t* const p_gid, + OUT UNALIGNED net32_t* const p_qpn ) +{ + UNALIGNED + cl_map_item_t *p_item; + ipoib_endpt_t *p_endpt; + uint64_t key; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + cl_obj_lock( &p_port->obj ); + + key = 0; + cl_memcpy( &key, &mac, sizeof(mac_addr_t) ); + p_item = cl_qmap_get( &p_port->endpt_mgr.mac_endpts, key ); + if( p_item == cl_qmap_end( &p_port->endpt_mgr.mac_endpts ) ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("Failed endpoint lookup.\n") ); + return NDIS_STATUS_FAILURE; + } + + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, mac_item ); + + *p_gid = p_endpt->dgid; + *p_qpn = p_endpt->qpn; + + cl_obj_unlock( &p_port->obj ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return NDIS_STATUS_SUCCESS; +} + + +static inline ipoib_endpt_t* +__endpt_mgr_get_by_gid( + IN ipoib_port_t* const p_port, + IN const ib_gid_t* const p_gid ) +{ + cl_fmap_item_t *p_item; + ipoib_endpt_t *p_endpt; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + p_item = cl_fmap_get( &p_port->endpt_mgr.gid_endpts, p_gid ); + if( p_item == cl_fmap_end( &p_port->endpt_mgr.gid_endpts ) ) + p_endpt = NULL; + else + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, gid_item ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return p_endpt; +} + + +static ipoib_endpt_t* +__endpt_mgr_get_by_lid( + IN ipoib_port_t* const p_port, + IN const net16_t lid ) +{ + cl_map_item_t *p_item; + ipoib_endpt_t *p_endpt; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + p_item = cl_qmap_get( &p_port->endpt_mgr.lid_endpts, lid ); + if( p_item == cl_qmap_end( &p_port->endpt_mgr.lid_endpts ) ) + p_endpt = NULL; + else + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, lid_item ); + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return p_endpt; +} + + +inline ib_api_status_t +__endpt_mgr_insert_locked( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + IN ipoib_endpt_t* const p_endpt ) +{ + ib_api_status_t status; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("insert :\t MAC: %02X-%02X-%02X-%02X-%02X-%02X\n", + mac.addr[0], mac.addr[1], mac.addr[2], + mac.addr[3], mac.addr[4], mac.addr[5]) ); + + cl_obj_lock( &p_port->obj ); + while( p_port->endpt_rdr ) + { + cl_obj_unlock( &p_port->obj ); + cl_obj_lock( &p_port->obj ); + } + /* __endpt_mgr_insert expects *one* reference to be held when being called. */ + cl_atomic_inc( &p_port->endpt_rdr ); + status= __endpt_mgr_insert( p_port, mac, p_endpt ); + cl_atomic_dec( &p_port->endpt_rdr ); + cl_obj_unlock( &p_port->obj ); + + return status; +} + + +inline ib_api_status_t +__endpt_mgr_insert( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + IN ipoib_endpt_t* const p_endpt ) +{ + uint64_t key; + cl_status_t cl_status; + cl_map_item_t *p_qitem; + cl_fmap_item_t *p_fitem; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + /* Wait for all accesses to the map to complete. */ + while( p_port->endpt_rdr > 1 ) + ; + + /* Link the endpoint to the port. */ + cl_status = cl_obj_insert_rel_parent_locked( + &p_endpt->rel, &p_port->obj, &p_endpt->obj ); + + if( cl_status != CL_SUCCESS ) + { + cl_obj_destroy( &p_endpt->obj ); + return IB_INVALID_STATE; + } + +#if DBG + cl_atomic_inc( &p_port->ref[ref_endpt_track] ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_OBJ, + ("ref type %d ref_cnt %d\n", ref_endpt_track, p_port->obj.ref_cnt) ); +#endif + + p_endpt->mac = mac; + key = 0; + cl_memcpy( &key, &mac, sizeof(mac_addr_t) ); + p_qitem = cl_qmap_insert( + &p_port->endpt_mgr.mac_endpts, key, &p_endpt->mac_item ); + CL_ASSERT( p_qitem == &p_endpt->mac_item ); + p_fitem = cl_fmap_insert( + &p_port->endpt_mgr.gid_endpts, &p_endpt->dgid, &p_endpt->gid_item ); + CL_ASSERT( p_fitem == &p_endpt->gid_item ); + if( p_endpt->dlid ) + { + p_qitem = cl_qmap_insert( + &p_port->endpt_mgr.lid_endpts, p_endpt->dlid, &p_endpt->lid_item ); + CL_ASSERT( p_qitem == &p_endpt->lid_item ); + if (p_qitem != &p_endpt->lid_item) { + // Since we failed to insert into the list, make sure it is not removed + p_endpt->dlid =0; + } + } + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); + return IB_SUCCESS; +} + + +static ib_api_status_t +__endpt_mgr_add_bcast( + IN ipoib_port_t* const p_port, + IN ib_mcast_rec_t *p_mcast_rec ) +{ + ib_api_status_t status; + ipoib_endpt_t *p_endpt; + mac_addr_t bcast_mac; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + /* + * Cache the broadcast group properties for creating future mcast groups. + */ + p_port->ib_mgr.bcast_rec = *p_mcast_rec->p_member_rec; + + /* Allocate the broadcast endpoint. */ + p_endpt = ipoib_endpt_create( &p_mcast_rec->p_member_rec->mgid, + 0 , CL_HTON32(0x00FFFFFF) ); + if( !p_endpt ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_endpt_create failed.\n") ); + return IB_INSUFFICIENT_RESOURCES; + } + /* set reference to transport to be used while is not attached to the port */ + p_endpt->is_mcast_listener = TRUE; + p_endpt->p_ifc = p_port->p_adapter->p_ifc; + status = ipoib_endpt_set_mcast( p_endpt, p_port->ib_mgr.h_pd, + p_port->port_num, p_mcast_rec ); + if( status != IB_SUCCESS ) + { + cl_obj_destroy( &p_endpt->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_create_mcast_endpt returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Add the broadcast endpoint to the endpoint map. */ + cl_memset( &bcast_mac, 0xFF, sizeof(bcast_mac) ); + status = __endpt_mgr_insert_locked( p_port, bcast_mac, p_endpt ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +void +ipoib_port_remove_endpt( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac ) +{ + cl_map_item_t *p_item; + //TODO CM +// cl_fmap_item_t *p_fmap_item; + ipoib_endpt_t *p_endpt; + uint64_t key; + + IPOIB_ENTER( IPOIB_DBG_ENDPT ); + + key = 0; + cl_memcpy( &key, &mac, sizeof(mac_addr_t) ); + + /* Remove the endpoint from the maps so further requests don't find it. */ + cl_obj_lock( &p_port->obj ); + /* Wait for all readers to finish */ + while( p_port->endpt_rdr ) + ; + p_item = cl_qmap_remove( &p_port->endpt_mgr.mac_endpts, key ); + /* + * Dereference the endpoint. If the ref count goes to zero, it + * will get freed. + */ + if( p_item != cl_qmap_end( &p_port->endpt_mgr.mac_endpts ) ) + { + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, mac_item ); + /* + * The enpoints are *ALWAYS* in both the MAC and GID maps. They are only + * in the LID map if the GID has the same subnet prefix as us. + */ + cl_fmap_remove_item( + &p_port->endpt_mgr.gid_endpts, &p_endpt->gid_item ); +#if 0 + if( p_port->p_adapter->params.cm_enabled ) + { + p_fmap_item = cl_fmap_get( &p_port->endpt_mgr.conn_endpts, &p_endpt->dgid ); + + if( p_fmap_item != cl_fmap_end( &p_port->endpt_mgr.conn_endpts ) ) + { + cl_fmap_remove_item( &p_port->endpt_mgr.conn_endpts, + &p_endpt->conn_item ); + } + } +#endif + + if( p_endpt->dlid ) + { + cl_qmap_remove_item( + &p_port->endpt_mgr.lid_endpts, &p_endpt->lid_item ); + } + + cl_obj_unlock( &p_port->obj ); + //TODO CM + //endpt_cm_destroy_conn( p_port, p_endpt ); + +#if DBG + cl_atomic_dec( &p_port->ref[ref_endpt_track] ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("ref type %d ref_cnt %d\n", ref_endpt_track, p_port->obj.ref_cnt) ); +#endif + + } + else + { + cl_obj_unlock( &p_port->obj ); + } + + IPOIB_EXIT( IPOIB_DBG_ENDPT ); +} + +/* + * The sequence for port up is as follows: + * 1. The port goes active. This allows the adapter to send SA queries + * and join the broadcast group (and other groups). + * + * 2. The adapter sends an SA query for the broadcast group. + * + * 3. Upon completion of the query, the adapter joins the broadcast group. + */ + + +/* + * Query the SA for the broadcast group. + */ +void +ipoib_port_up( + IN ipoib_port_t* const p_port, + IN const ib_pnp_port_rec_t* const p_pnp_rec ) +{ + ib_port_info_t *p_port_info; + ib_mad_t *mad_in = NULL; + ib_mad_t *mad_out = NULL; + ib_api_status_t status = IB_INSUFFICIENT_MEMORY; + static int cnt = 0; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_INIT, + ("[%d] Entering port_up.\n", ++cnt) ); + + cl_obj_lock( &p_port->obj ); + if ( p_port->state == IB_QPS_INIT ) + { + cl_obj_unlock( &p_port->obj ); + status = IB_SUCCESS; + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("p_port->state = %d - Aborting.\n", p_port->state) ); + goto up_done; + } + else if ( p_port->state == IB_QPS_RTS ) + { + cl_obj_unlock( &p_port->obj ); + cl_obj_lock( &p_port->p_adapter->obj ); + if( p_port->p_adapter->state == IB_PNP_PORT_INIT ) + { + p_port->p_adapter->state = IB_PNP_PORT_ACTIVE; + } + cl_obj_unlock( &p_port->p_adapter->obj ); + status = IB_SUCCESS; + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Port init is done. p_port->state = %d.\n", p_port->state ) ); + goto up_done; + } + p_port->state = IB_QPS_INIT; + cl_obj_unlock( &p_port->obj ); + + + /* Wait for all work requests to get flushed. */ + while( p_port->recv_mgr.depth || p_port->send_mgr.depth ) + cl_thread_suspend( 0 ); + + KeResetEvent( &p_port->sa_event ); + + mad_out = (ib_mad_t*)cl_zalloc(256); + if(! mad_out) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("failed to allocate mad mad_out\n")); + goto up_done; + } + mad_in = (ib_mad_t*)cl_zalloc(256); + if(! mad_in) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("failed to allocate mad mad_in\n")); + goto up_done; + } + + mad_in->attr_id = IB_MAD_ATTR_PORT_INFO; + mad_in->method = IB_MAD_METHOD_GET; + mad_in->base_ver = 1; + mad_in->class_ver =1; + mad_in->mgmt_class = IB_MCLASS_SUBN_LID; + + status = p_port->p_adapter->p_ifc->local_mad( + p_port->ib_mgr.h_ca ,p_port->port_num ,mad_in ,mad_out); + + if( status != IB_SUCCESS ) + { + ipoib_set_inactive( p_port->p_adapter ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_local_mad returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + goto up_done; + } + + p_port_info = (ib_port_info_t*)(((ib_smp_t*)mad_out)->data); + p_port->base_lid = p_pnp_rec->p_port_attr->lid; + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Received port info: link width = %d.\n", + p_port_info->link_width_active) ); + p_port->ib_mgr.rate = + ib_port_info_compute_rate( p_port_info ); + + ipoib_set_rate( p_port->p_adapter, + p_port_info->link_width_active, + ib_port_info_get_link_speed_active( p_port_info ) ); + + status = __port_get_bcast( p_port ); + if (status != IB_SUCCESS) + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + (" __port_get_bcast returned %s\n",p_port->p_adapter->p_ifc->get_err_str( status ))); + +up_done: + if( status != IB_SUCCESS ) + { + if( status != IB_CANCELED ) + { + ipoib_set_inactive( p_port->p_adapter ); + __endpt_mgr_reset_all( p_port ); + } + ASSERT(p_port->state == IB_QPS_INIT || p_port->state == IB_QPS_ERROR); + p_port->state = IB_QPS_ERROR; + KeSetEvent( &p_port->sa_event, EVENT_INCREMENT, FALSE ); + } + + if(mad_out) + cl_free(mad_out); + if(mad_in) + cl_free(mad_in); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static ib_api_status_t +__endpt_mgr_add_local( + IN ipoib_port_t* const p_port, + IN ib_port_info_t* const p_port_info ) +{ + ib_api_status_t status; + ib_gid_t gid; + ipoib_endpt_t *p_endpt; + ib_av_attr_t av_attr; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + ib_gid_set_default( &gid, p_port->p_adapter->guids.port_guid.guid ); + p_endpt = ipoib_endpt_create( + &gid, p_port_info->base_lid, p_port->ib_mgr.qpn ); + if( !p_endpt ) + { + p_port->p_adapter->hung = TRUE; + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to create local endpt\n") ); + return IB_INSUFFICIENT_MEMORY; + } + + cl_memclr( &av_attr, sizeof(ib_av_attr_t) ); + av_attr.port_num = p_port->port_num; + av_attr.sl = 0; + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + (" av_attr.dlid = p_port_info->base_lid = %d\n", cl_ntoh16( p_port_info->base_lid ) )); + av_attr.dlid = p_port_info->base_lid; + av_attr.static_rate = p_port->ib_mgr.rate; + av_attr.path_bits = 0; + status = p_port->p_adapter->p_ifc->create_av( + p_port->ib_mgr.h_pd, &av_attr, &p_endpt->h_av ); + if( status != IB_SUCCESS ) + { + cl_obj_destroy( &p_endpt->obj ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_create_av for local endpoint returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* __endpt_mgr_insert expects *one* reference to be held. */ + cl_atomic_inc( &p_port->endpt_rdr ); + status = __endpt_mgr_insert( p_port, p_port->p_adapter->params.conf_mac, p_endpt ); + cl_atomic_dec( &p_port->endpt_rdr ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__endpt_mgr_insert for local endpoint returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + p_port->p_local_endpt = p_endpt; + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + + + +static ib_api_status_t +__port_get_bcast( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t status; + ib_query_req_t query; + ib_user_query_t info; + ib_member_rec_t member_rec; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + info.method = IB_MAD_METHOD_GETTABLE; + info.attr_id = IB_MAD_ATTR_MCMEMBER_RECORD; + info.attr_size = sizeof(ib_member_rec_t); + info.comp_mask = IB_MCR_COMPMASK_MGID; + info.p_attr = &member_rec; + + /* Query requires only the MGID. */ + cl_memclr( &member_rec, sizeof(ib_member_rec_t) ); + member_rec.mgid = bcast_mgid_template; + + member_rec.mgid.raw[4] = (uint8_t) (p_port->p_adapter->guids.port_guid.pkey >> 8) ; + member_rec.mgid.raw[5] = (uint8_t) p_port->p_adapter->guids.port_guid.pkey; + member_rec.pkey = cl_hton16(p_port->p_adapter->guids.port_guid.pkey); + cl_memclr( &query, sizeof(ib_query_req_t) ); + query.query_type = IB_QUERY_USER_DEFINED; + query.p_query_input = &info; + query.port_guid = p_port->p_adapter->guids.port_guid.guid; + query.timeout_ms = p_port->p_adapter->params.sa_timeout; + query.retry_cnt = p_port->p_adapter->params.sa_retry_cnt; + query.query_context = p_port; + query.pfn_query_cb = __bcast_get_cb; + + /* reference the object for the multicast query. */ + ipoib_port_ref( p_port, ref_get_bcast ); + + status = p_port->p_adapter->p_ifc->query( + p_port->p_adapter->h_al, &query, &p_port->ib_mgr.h_query ); + if( status != IB_SUCCESS ) + { + ipoib_port_deref( p_port, ref_get_bcast ); + ASSERT(FALSE); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_query returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + } + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_query returned SUCCESS\n")); + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +/* Callback for the MCMemberRecord Get query for the IPv4 broadcast group. */ +static void +__bcast_get_cb( + IN ib_query_rec_t *p_query_rec ) +{ + ipoib_port_t *p_port; + ib_member_rec_t *p_mc_req; + ib_api_status_t status; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_port = (ipoib_port_t*)p_query_rec->query_context; + + cl_obj_lock( &p_port->obj ); + p_port->ib_mgr.h_query = NULL; + + CL_ASSERT(p_port->state == IB_QPS_INIT || p_port->state == IB_QPS_ERROR); + if( p_port->state != IB_QPS_INIT ) + { + status = IB_CANCELED; + goto done; + } + + status = p_query_rec->status; + + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("status of request %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + + switch( status ) + { + case IB_SUCCESS: + if( p_query_rec->result_cnt ) + { + p_mc_req = (ib_member_rec_t*) + ib_get_query_result( p_query_rec->p_result_mad, 0 ); + + /* Join the broadcast group. */ + status = __port_join_bcast( p_port, p_mc_req ); + break; + } + /* Fall through. */ + + case IB_REMOTE_ERROR: + /* SA failed the query. Broadcast group doesn't exist, create it. */ + status = __port_create_bcast( p_port ); + break; + + case IB_CANCELED: + IPOIB_PRINT(TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Instance destroying - Aborting.\n") ); + break; + + default: + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_BCAST_GET, 1, p_query_rec->status ); + } +done: + cl_obj_unlock( &p_port->obj ); + + if( status != IB_SUCCESS ) + { + if( status != IB_CANCELED ) + { + ipoib_set_inactive( p_port->p_adapter ); + __endpt_mgr_reset_all( p_port ); + } + p_port->state = IB_QPS_ERROR; + KeSetEvent( &p_port->sa_event, EVENT_INCREMENT, FALSE ); + } + + /* Return the response MAD to AL. */ + if( p_query_rec->p_result_mad ) + p_port->p_adapter->p_ifc->put_mad( p_query_rec->p_result_mad ); + + /* Release the reference taken when issuing the member record query. */ + ipoib_port_deref( p_port, ref_bcast_get_cb ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static ib_api_status_t +__port_join_bcast( + IN ipoib_port_t* const p_port, + IN ib_member_rec_t* const p_member_rec ) +{ + ib_api_status_t status; + ib_mcast_req_t mcast_req; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + /* Check that the rate is realizable for our port. */ + if( p_port->ib_mgr.rate < (p_member_rec->rate & 0x3F) && + (g_ipoib.bypass_check_bcast_rate == 0)) + { + /* + * The MC group rate is higher than our port's rate. Log an error + * and stop. A port transition will drive the retry. + */ + IPOIB_PRINT(TRACE_LEVEL_WARNING, IPOIB_DBG_INIT, + ("Unrealizable join due to rate mismatch.\n") ); + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_BCAST_RATE, 2, + (uint32_t)(p_member_rec->rate & 0x3F), + (uint32_t)p_port->ib_mgr.rate ); + return IB_ERROR; + } + + /* Join the broadcast group. */ + cl_memclr( &mcast_req, sizeof(mcast_req) ); + /* Copy the results of the Get to use as parameters. */ + mcast_req.member_rec = *p_member_rec; + /* We specify our port GID for the join operation. */ + mcast_req.member_rec.port_gid.unicast.prefix = IB_DEFAULT_SUBNET_PREFIX; + mcast_req.member_rec.port_gid.unicast.interface_id = + p_port->p_adapter->guids.port_guid.guid; + + mcast_req.mcast_context = p_port; + mcast_req.pfn_mcast_cb = __bcast_cb; + mcast_req.timeout_ms = p_port->p_adapter->params.sa_timeout; + mcast_req.retry_cnt = p_port->p_adapter->params.sa_retry_cnt; + mcast_req.port_guid = p_port->p_adapter->guids.port_guid.guid; + mcast_req.pkey_index = p_port->pkey_index; + + if( ib_member_get_state( mcast_req.member_rec.scope_state ) != + IB_MC_REC_STATE_FULL_MEMBER ) + { + IPOIB_PRINT(TRACE_LEVEL_WARNING, IPOIB_DBG_INIT, + ("Incorrect MC member rec join state in query response.\n") ); + ib_member_set_state( &mcast_req.member_rec.scope_state, + IB_MC_REC_STATE_FULL_MEMBER ); + } + + /* reference the object for the multicast join request. */ + ipoib_port_ref( p_port, ref_join_bcast ); + + status = p_port->p_adapter->p_ifc->join_mcast( + p_port->ib_mgr.h_qp, &mcast_req ); + if( status != IB_SUCCESS ) + { + ipoib_port_deref( p_port, ref_bcast_join_failed ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_join_mcast returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + } + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +static ib_api_status_t +__port_create_bcast( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t status; + ib_mcast_req_t mcast_req; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + /* Join the broadcast group. */ + cl_memclr( &mcast_req, sizeof(mcast_req) ); + mcast_req.create = TRUE; + /* + * Create requires pkey, qkey, SL, flow label, traffic class, joing state + * and port GID. + * + * We specify the MGID since we don't want the SA to generate it for us. + */ + mcast_req.member_rec.mgid = bcast_mgid_template; + mcast_req.member_rec.mgid.raw[4] = (uint8_t) (p_port->p_adapter->guids.port_guid.pkey >> 8); + mcast_req.member_rec.mgid.raw[5] = (uint8_t) p_port->p_adapter->guids.port_guid.pkey; + ib_gid_set_default( &mcast_req.member_rec.port_gid, + p_port->p_adapter->guids.port_guid.guid ); + /* + * IPOIB spec requires that the QKEY have the MSb set so that the QKEY + * from the QP is used rather than the QKEY in the send WR. + */ + mcast_req.member_rec.qkey = + (uint32_t)(uintn_t)p_port | IB_QP_PRIVILEGED_Q_KEY; + mcast_req.member_rec.mtu = + (IB_PATH_SELECTOR_EXACTLY << 6) | IB_MTU_LEN_2048; + + mcast_req.member_rec.pkey = cl_hton16(p_port->p_adapter->guids.port_guid.pkey); + + mcast_req.member_rec.sl_flow_hop = ib_member_set_sl_flow_hop( 0, 0, 0 ); + mcast_req.member_rec.scope_state = + ib_member_set_scope_state( 2, IB_MC_REC_STATE_FULL_MEMBER ); + + mcast_req.mcast_context = p_port; + mcast_req.pfn_mcast_cb = __bcast_cb; + mcast_req.timeout_ms = p_port->p_adapter->params.sa_timeout; + mcast_req.retry_cnt = p_port->p_adapter->params.sa_retry_cnt; + mcast_req.port_guid = p_port->p_adapter->guids.port_guid.guid; + mcast_req.pkey_index = p_port->pkey_index; + + /* reference the object for the multicast join request. */ + ipoib_port_ref( p_port, ref_join_bcast ); + + status = p_port->p_adapter->p_ifc->join_mcast( p_port->ib_mgr.h_qp, &mcast_req ); + if( status != IB_SUCCESS ) + { + ipoib_port_deref( p_port, ref_bcast_create_failed ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_join_mcast returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + } + IPOIB_EXIT( IPOIB_DBG_INIT ); + return status; +} + + +void +ipoib_port_down( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t status; + ib_qp_mod_t qp_mod; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + /* + * Mark our state. This causes all callbacks to abort. + * Note that we hold the receive lock so that we synchronize + * with reposting. We must take the receive lock before the + * object lock since that is the order taken when reposting. + */ + cl_spinlock_acquire( &p_port->recv_lock ); + cl_obj_lock( &p_port->obj ); + p_port->state = IB_QPS_ERROR; + + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_PORT_DOWN, 0 ); + + if( p_port->ib_mgr.h_query ) + { + p_port->p_adapter->p_ifc->cancel_query( + p_port->p_adapter->h_al, p_port->ib_mgr.h_query ); + p_port->ib_mgr.h_query = NULL; + } + cl_obj_unlock( &p_port->obj ); + cl_spinlock_release( &p_port->recv_lock ); + + KeWaitForSingleObject( + &p_port->sa_event, Executive, KernelMode, FALSE, NULL ); + + /* garbage collector timer is not needed when link is down */ + KeCancelTimer(&p_port->gc_timer); + KeFlushQueuedDpcs(); + + /* + * Put the QP in the error state. This removes the need to + * synchronize with send/receive callbacks. + */ + CL_ASSERT( p_port->ib_mgr.h_qp ); + cl_memclr( &qp_mod, sizeof(ib_qp_mod_t) ); + qp_mod.req_state = IB_QPS_ERROR; + status = p_port->p_adapter->p_ifc->modify_qp( p_port->ib_mgr.h_qp, &qp_mod ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_modify_qp to error state returned %s.\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + p_port->p_adapter->hung = TRUE; + return; + } + + KeResetEvent(&p_port->leave_mcast_event); + + /* Reset all endpoints so we don't flush our ARP cache. */ + __endpt_mgr_reset_all( p_port ); + + KeWaitForSingleObject( + &p_port->leave_mcast_event, Executive, KernelMode, FALSE, NULL ); + + __pending_list_destroy(p_port); + + cl_obj_lock( &p_port->p_adapter->obj ); + ipoib_dereg_addrs( p_port->p_adapter ); + cl_obj_unlock( &p_port->p_adapter->obj ); + + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +__bcast_cb( + IN ib_mcast_rec_t *p_mcast_rec ) +{ + ipoib_port_t *p_port; + ib_api_status_t status; + LARGE_INTEGER gc_due_time; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + + p_port = (ipoib_port_t*)p_mcast_rec->mcast_context; + + cl_obj_lock( &p_port->obj ); + + ASSERT(p_port->state == IB_QPS_INIT || p_port->state == IB_QPS_ERROR); + if( p_port->state != IB_QPS_INIT ) + { + cl_obj_unlock( &p_port->obj ); + if( p_mcast_rec->status == IB_SUCCESS ) + { + ipoib_port_ref(p_port, ref_leave_mcast); + p_port->p_adapter->p_ifc->leave_mcast( p_mcast_rec->h_mcast, __leave_error_mcast_cb ); + } + KeSetEvent( &p_port->sa_event, EVENT_INCREMENT, FALSE ); + ipoib_port_deref( p_port, ref_bcast_inv_state ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Invalid state - Aborting.\n") ); + return; + } + status = p_mcast_rec->status; + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Multicast join for broadcast group returned %s.\n", + p_port->p_adapter->p_ifc->get_err_str( p_mcast_rec->status )) ); + if( status == IB_REMOTE_ERROR ) + { + /* + * Either: + * - the join failed because the group no longer exists + * - the create failed because the group already exists + * + * Kick off a new Get query to the SA to restart the join process + * from the top. Note that as an optimization, it would be + * possible to distinguish between the join and the create. + * If the join fails, try the create. If the create fails, start + * over with the Get. + */ + /* TODO: Assert is a place holder. Can we ever get here if the + state isn't IB_PNP_PORT_ADD or PORT_DOWN or PORT_INIT? */ + CL_ASSERT( p_port->p_adapter->state == IB_PNP_PORT_ADD || + p_port->p_adapter->state == IB_PNP_PORT_DOWN || + p_port->p_adapter->state == IB_PNP_PORT_INIT ); + if(++p_port->bc_join_retry_cnt < p_port->p_adapter->params.bc_join_retry) + { + status = __port_get_bcast( p_port ); + } + else + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_BCAST_JOIN, 1, p_mcast_rec->status ); + p_port->bc_join_retry_cnt = 0; + } + } + else + { + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_BCAST_JOIN, 1, p_mcast_rec->status ); + } + + cl_obj_unlock( &p_port->obj ); + if( status != IB_SUCCESS ) + { + ipoib_set_inactive( p_port->p_adapter ); + __endpt_mgr_reset_all( p_port ); + ASSERT(p_port->state == IB_QPS_INIT || p_port->state == IB_QPS_ERROR); + p_port->state = IB_QPS_ERROR; + KeSetEvent( &p_port->sa_event, EVENT_INCREMENT, FALSE ); + } + ipoib_port_deref( p_port, ref_bcast_req_failed ); + IPOIB_EXIT( IPOIB_DBG_INIT ); + return; + } + p_port->bc_join_retry_cnt = 0; + + while( p_port->endpt_rdr ) + { + cl_obj_unlock( &p_port->obj ); + cl_obj_lock( &p_port->obj ); + } + + if( !p_port->p_local_endpt ) + { + ib_port_info_t port_info; + cl_memclr(&port_info, sizeof(port_info)); + port_info.base_lid = p_port->base_lid; + status = __endpt_mgr_add_local( p_port, &port_info ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__endpt_mgr_add_local returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + cl_obj_unlock( &p_port->obj ); + goto err; + } + } + + cl_obj_unlock( &p_port->obj ); + + status = __endpt_mgr_add_bcast( p_port, p_mcast_rec ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__endpt_mgr_add_bcast returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + ipoib_port_ref(p_port, ref_leave_mcast); + status = p_port->p_adapter->p_ifc->leave_mcast( p_mcast_rec->h_mcast, __leave_error_mcast_cb ); + CL_ASSERT( status == IB_SUCCESS ); + goto err; + } + + /* Get the QP ready for action. */ + status = __ib_mgr_activate( p_port ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__ib_mgr_activate returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + +err: + /* Flag the adapter as hung. */ + p_port->p_adapter->hung = TRUE; + ASSERT(p_port->state == IB_QPS_INIT || p_port->state == IB_QPS_ERROR); + p_port->state = IB_QPS_ERROR; + KeSetEvent( &p_port->sa_event, EVENT_INCREMENT, FALSE ); + ipoib_port_deref( p_port, ref_bcast_error ); + IPOIB_EXIT( IPOIB_DBG_INIT ); + return; + } + + cl_obj_lock( &p_port->obj ); + /* Only change the state if we're still in INIT. */ + ASSERT( p_port->state == IB_QPS_INIT || p_port->state == IB_QPS_ERROR); + if (p_port->state == IB_QPS_INIT) { + p_port->state = IB_QPS_RTS; + } + cl_obj_unlock( &p_port->obj ); + + /* Prepost receives. */ + cl_spinlock_acquire( &p_port->recv_lock ); + __recv_mgr_repost( p_port ); + cl_spinlock_release( &p_port->recv_lock ); + + /* Notify the adapter that we now have an active connection. */ + status = ipoib_set_active( p_port->p_adapter ); + if( status != IB_SUCCESS ) + { + ib_qp_mod_t qp_mod; + ipoib_set_inactive( p_port->p_adapter ); + KeSetEvent( &p_port->sa_event, EVENT_INCREMENT, FALSE ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("ipoib_set_active returned %s.\n",p_port->p_adapter->p_ifc->get_err_str( status ))); + cl_spinlock_acquire( &p_port->recv_lock ); + cl_obj_lock( &p_port->obj ); + p_port->state = IB_QPS_ERROR; + if( p_port->ib_mgr.h_query ) + { + p_port->p_adapter->p_ifc->cancel_query( + p_port->p_adapter->h_al, p_port->ib_mgr.h_query ); + p_port->ib_mgr.h_query = NULL; + } + cl_obj_unlock( &p_port->obj ); + cl_spinlock_release( &p_port->recv_lock ); + + CL_ASSERT( p_port->ib_mgr.h_qp ); + cl_memclr( &qp_mod, sizeof(ib_qp_mod_t) ); + qp_mod.req_state = IB_QPS_ERROR; + status = p_port->p_adapter->p_ifc->modify_qp( p_port->ib_mgr.h_qp, &qp_mod ); + __endpt_mgr_reset_all( p_port ); + + ipoib_port_deref( p_port, ref_join_bcast ); + return; + } +#if 0 //CM + if( p_port->p_adapter->params.cm_enabled && + !p_port->p_local_endpt->conn.h_cm_listen ) + { + if( ipoib_port_listen( p_port ) != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Port CM Listen failed\n" ) ); + /*keep going with UD only */ + p_port->p_adapter->params.cm_enabled = FALSE; + + NdisWriteErrorLogEntry( p_port->p_adapter->h_adapter, + EVENT_IPOIB_CONNECTED_MODE_ERR, 1, 0xbadc0de3 ); + } + } +#endif + /* garbage collector timer is needed when link is active */ + gc_due_time.QuadPart = -(int64_t)(((uint64_t)p_port->p_adapter->params.mc_leave_rescan * 2000000) * 10); + KeSetTimerEx(&p_port->gc_timer,gc_due_time, + (LONG)p_port->p_adapter->params.mc_leave_rescan*1000,&p_port->gc_dpc); + + KeSetEvent( &p_port->sa_event, EVENT_INCREMENT, FALSE ); + ipoib_port_deref( p_port, ref_join_bcast ); + IPOIB_EXIT( IPOIB_DBG_INIT ); +} + + +static void +__qp_event( + IN ib_async_event_rec_t *p_event_rec ) +{ + UNUSED_PARAM( p_event_rec ); + CL_ASSERT( p_event_rec->context ); + ((ipoib_port_t*)p_event_rec->context)->p_adapter->hung = TRUE; +} + + +static void +__cq_event( + IN ib_async_event_rec_t *p_event_rec ) +{ + UNUSED_PARAM( p_event_rec ); + CL_ASSERT( p_event_rec->context ); + ((ipoib_port_t*)p_event_rec->context)->p_adapter->hung = TRUE; +} + + +static ib_api_status_t +__ib_mgr_activate( + IN ipoib_port_t* const p_port ) +{ + ib_api_status_t status; + ib_dgrm_info_t dgrm_info; + ib_qp_mod_t qp_mod; + + IPOIB_ENTER( IPOIB_DBG_INIT ); + /* + * Move the QP to RESET. This allows us to reclaim any + * unflushed receives. + */ + cl_memclr( &qp_mod, sizeof(ib_qp_mod_t) ); + qp_mod.req_state = IB_QPS_RESET; + status = p_port->p_adapter->p_ifc->modify_qp( p_port->ib_mgr.h_qp, &qp_mod ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_modify_qp returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Move the QP to RTS. */ + dgrm_info.port_guid = p_port->p_adapter->guids.port_guid.guid; + dgrm_info.qkey = p_port->ib_mgr.bcast_rec.qkey; + dgrm_info.pkey_index = p_port->pkey_index; + status = p_port->p_adapter->p_ifc->init_dgrm_svc( p_port->ib_mgr.h_qp, &dgrm_info ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_init_dgrm_svc returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* Rearm the CQs. */ + status = p_port->p_adapter->p_ifc->rearm_cq( p_port->ib_mgr.h_recv_cq, FALSE ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_rearm_cq for recv returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + status = p_port->p_adapter->p_ifc->rearm_cq( p_port->ib_mgr.h_send_cq, FALSE ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_rearm_cq for send returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + IPOIB_EXIT( IPOIB_DBG_INIT ); + return IB_SUCCESS; +} + + +/* Transition to a passive level thread. */ +ib_api_status_t +ipoib_port_join_mcast( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + IN const uint8_t state) +{ + ib_api_status_t status; + ib_mcast_req_t mcast_req; + ipoib_endpt_t *p_endpt; + + IPOIB_ENTER( IPOIB_DBG_MCAST ); + + switch( __endpt_mgr_ref( p_port, mac, &p_endpt ) ) + { + case NDIS_STATUS_NO_ROUTE_TO_DESTINATION: + break; + + case NDIS_STATUS_SUCCESS: + ipoib_endpt_deref( p_endpt ); + /* Fall through */ + + case NDIS_STATUS_PENDING: + return IB_SUCCESS; + } + + /* + * Issue the mcast request, using the parameters of the broadcast group. + * This allows us to do a create request that should always succeed since + * the required parameters are known. + */ + cl_memclr( &mcast_req, sizeof(mcast_req) ); + mcast_req.create = TRUE; + + /* Copy the settings from the broadcast group. */ + mcast_req.member_rec = p_port->ib_mgr.bcast_rec; + /* Clear fields that aren't specified in the join */ + mcast_req.member_rec.mlid = 0; + ib_member_set_state( &mcast_req.member_rec.scope_state,state); + + if( (mac.addr[0] == 1) && (mac.addr[2] == 0x5E )) + { + /* + * Update the address portion of the MGID with the 28 lower bits of the + * IP address. Since we're given a MAC address, we are using + * 24 lower bits of that network-byte-ordered value (assuming MSb + * is zero) and 4 lsb bits of the first byte of IP address. + */ + mcast_req.member_rec.mgid.raw[12] = mac.addr[1]; + mcast_req.member_rec.mgid.raw[13] = mac.addr[3]; + mcast_req.member_rec.mgid.raw[14] = mac.addr[4]; + mcast_req.member_rec.mgid.raw[15] = mac.addr[5]; + } + else + { + /* Handle non IP mutlicast MAC addresses. */ + /* Update the signature to use the lower 2 bytes of the OpenIB OUI. */ + mcast_req.member_rec.mgid.raw[2] = 0x14; + mcast_req.member_rec.mgid.raw[3] = 0x05; + /* Now copy the MAC address into the last 6 bytes of the GID. */ + cl_memcpy( &mcast_req.member_rec.mgid.raw[10], mac.addr, 6 ); + } + + mcast_req.mcast_context = p_port; + mcast_req.pfn_mcast_cb = __mcast_cb; + mcast_req.timeout_ms = p_port->p_adapter->params.sa_timeout; + mcast_req.retry_cnt = p_port->p_adapter->params.sa_retry_cnt; + mcast_req.port_guid = p_port->p_adapter->guids.port_guid.guid; + mcast_req.pkey_index = p_port->pkey_index; + mcast_req.member_rec.pkey = cl_hton16(p_port->p_adapter->guids.port_guid.pkey); + /* + * Create the endpoint and insert it in the port. Since we don't wait for + * the mcast SA operations to complete before returning from the multicast + * list set OID asynchronously, it is possible for the mcast entry to be + * cleared before the SA interaction completes. In this case, when the + * mcast callback is invoked, it would not find the corresponding endpoint + * and would be undone. + */ + p_endpt = ipoib_endpt_create( + &mcast_req.member_rec.mgid, 0, CL_HTON32(0x00FFFFFF) ); + if( !p_endpt ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ipoib_endpt_create failed.\n") ); + return IB_INSUFFICIENT_MEMORY; + } + + status = __endpt_mgr_insert_locked( p_port, mac, p_endpt ); + if( status != IB_SUCCESS ) + { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("__endpt_mgr_insert_locked returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + return status; + } + + /* reference the object for the multicast join request. */ + ipoib_port_ref( p_port, ref_join_mcast ); + + status = p_port->p_adapter->p_ifc->join_mcast( p_port->ib_mgr.h_qp, &mcast_req ); + if( status != IB_SUCCESS ) + { + ipoib_port_deref( p_port, ref_mcast_join_failed ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("ib_join_mcast returned %s\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + } + + IPOIB_EXIT( IPOIB_DBG_MCAST ); + return status; +} + + +static void +__mcast_cb( + IN ib_mcast_rec_t *p_mcast_rec ) +{ + ib_api_status_t status; + ipoib_port_t *p_port; + cl_fmap_item_t *p_item; + ipoib_endpt_t *p_endpt; + + IPOIB_ENTER( IPOIB_DBG_MCAST ); + + p_port = (ipoib_port_t*)p_mcast_rec->mcast_context; + + cl_obj_lock( &p_port->obj ); + while( p_port->endpt_rdr ) + { + cl_obj_unlock( &p_port->obj ); + cl_obj_lock( &p_port->obj ); + } + if( p_port->state != IB_QPS_RTS ) + { + cl_obj_unlock( &p_port->obj ); + if( p_mcast_rec->status == IB_SUCCESS ) + + { + ipoib_port_ref(p_port, ref_leave_mcast); + p_port->p_adapter->p_ifc->leave_mcast( p_mcast_rec->h_mcast, __leave_error_mcast_cb ); + } + ipoib_port_deref( p_port, ref_mcast_inv_state ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_INIT, + ("Invalid state - Aborting.\n") ); + return; + } + + if( p_mcast_rec->status != IB_SUCCESS ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Multicast join request failed with status %s.\n", + p_port->p_adapter->p_ifc->get_err_str( p_mcast_rec->status )) ); + /* Flag the adapter as hung. */ + p_port->p_adapter->hung =TRUE; + ipoib_port_deref( p_port, ref_mcast_req_failed ); + IPOIB_EXIT( IPOIB_DBG_MCAST ); + return; + } + + p_item = cl_fmap_get( + &p_port->endpt_mgr.gid_endpts, &p_mcast_rec->p_member_rec->mgid ); + if( p_item == cl_fmap_end( &p_port->endpt_mgr.gid_endpts ) ) + { + /* + * The endpoint must have been flushed while the join request + * was outstanding. Just leave the group and return. This + * is not an error. + */ + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT(TRACE_LEVEL_WARNING, IPOIB_DBG_ERROR, + ("Failed to find endpoint for update.\n") ); + + ipoib_port_ref(p_port, ref_leave_mcast); + p_port->p_adapter->p_ifc->leave_mcast( p_mcast_rec->h_mcast, __leave_error_mcast_cb ); + ipoib_port_deref( p_port, ref_mcast_no_endpt ); + IPOIB_EXIT( IPOIB_DBG_MCAST ); + return; + } + + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, gid_item ); + p_endpt->p_ifc = p_port->p_adapter->p_ifc; + + /* Setup the endpoint for use. */ + status = ipoib_endpt_set_mcast( + p_endpt, p_port->ib_mgr.h_pd, p_port->port_num, p_mcast_rec ); + if( status != IB_SUCCESS ) + { + cl_obj_unlock( &p_port->obj ); + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_MCAST, + ("ipoib_endpt_set_mcast returned %s.\n", + p_port->p_adapter->p_ifc->get_err_str( status )) ); + /* Flag the adapter as hung. */ + p_port->p_adapter->hung = TRUE; + ipoib_port_deref( p_port, ref_mcast_av_failed ); + IPOIB_EXIT( IPOIB_DBG_MCAST ); + return; + } + + /* + * The endpoint is already in the GID and MAC maps. + * mast endpoint are not used in the LID map. + */ + CL_ASSERT(p_endpt->dlid == 0); + /* set flag that endpoint is use */ + p_endpt->is_in_use = TRUE; + cl_obj_unlock( &p_port->obj ); + + /* Try to send all pending sends. */ + ipoib_port_resume( p_port , FALSE); + + ipoib_port_deref( p_port, ref_join_mcast ); + + IPOIB_EXIT( IPOIB_DBG_MCAST ); +} + + +void +ipoib_leave_mcast_cb( + IN void *context ) +{ + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_MCAST ); + + p_port = (ipoib_port_t*)context; + + IPOIB_PRINT( TRACE_LEVEL_VERBOSE, IPOIB_DBG_MCAST,("p_port->mcast_cnt = %d\n", p_port->mcast_cnt)); + + ipoib_port_deref( p_port, ref_leave_mcast); + cl_atomic_dec( &p_port->mcast_cnt); + + if(0 == p_port->mcast_cnt) + { + KeSetEvent( &p_port->leave_mcast_event, EVENT_INCREMENT, FALSE ); + } + + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_MCAST, + ("Leave mcast callback deref ipoib_port \n") ); + + IPOIB_EXIT( IPOIB_DBG_MCAST ); +} + + + +void +__leave_error_mcast_cb( + IN void *context ) +{ + ipoib_port_t *p_port; + + IPOIB_ENTER( IPOIB_DBG_MCAST ); + + p_port = (ipoib_port_t*)context; + + ipoib_port_deref( p_port, ref_leave_mcast); + IPOIB_PRINT_EXIT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_MCAST, + ("Leave mcast callback deref ipoib_port \n") ); + + IPOIB_EXIT( IPOIB_DBG_MCAST ); +} + +/*++ +Routine Description: + The routine process the packet and returns LSO information + +Arguments: + pNetBuffer - a pointer to the first net buffer object of the packet + TcpHeaderOffset - offset to the begining of the TCP header in the packet + pLsoData - pointer to LsoData object in which the routine returns the LSO information + pHeaderSize - pointer to ULONG object in which the header size is returned + IndexOfData - + +Return Value: + NDIS_STATUS + +NOTE: + called at DISPATCH level +--*/ + +NDIS_STATUS GetLsoHeaderSize( + IN PNET_BUFFER pNetBuffer, + IN LsoData *pLsoData, + OUT UINT *IndexOfData, + IN ipoib_hdr_t *ipoib_hdr + ) +{ + UINT CurrLength; + PUCHAR pSrc; + PUCHAR pCopiedData = pLsoData->coppied_data; + ip_hdr_t UNALIGNED *IpHdr; + tcp_hdr_t UNALIGNED *TcpHdr; + uint16_t TcpHeaderLen; + uint16_t IpHeaderLen; + uint16_t IpOffset; + INT FullBuffers = 0; + PMDL pMDL; + NDIS_STATUS status = NDIS_STATUS_INVALID_PACKET; + + +#define IP_OFFSET 14; + // + // This Flag indicates the way we gets the headers + // RegularFlow = we get the headers (ETH+IP+TCP) in the same Buffer + // in sequence. + // + boolean_t IsRegularFlow = TRUE; + + const uint16_t ETH_OFFSET = IP_OFFSET; + + pLsoData->LsoHeaderSize = 0; + IpOffset = IP_OFFSET; //(uint16_t)pPort->EncapsulationFormat.EncapsulationHeaderSize; + *IndexOfData = 0; + + pMDL = NET_BUFFER_CURRENT_MDL(pNetBuffer); + NdisQueryMdl(pMDL, &pSrc, &CurrLength, NormalPagePriority); + //NdisQueryBufferSafe( CurrBuffer, &pSrc, &CurrLength, NormalPagePriority ); + if (pSrc == NULL) { + IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, ("Error processing packets\n")); + return status; + } + // We start by looking for the ethernet and the IP + if (CurrLength < ETH_OFFSET) { + IPOIB_PRINT(TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, ("Error porcessing packets\n")); + return status; + } + //pLsoData->LsoHeaderSize = pLsoData->LsoHeaderSize + ETH_OFFSET; + if (CurrLength == ETH_OFFSET) { + ASSERT(FALSE); + IsRegularFlow = FALSE; + memcpy(pCopiedData, pSrc, ETH_OFFSET); + pCopiedData += ETH_OFFSET; + FullBuffers++; + // First buffer was only ethernet + pNetBuffer = NET_BUFFER_NEXT_NB(pNetBuffer); + NdisQueryMdl(NET_BUFFER_CURRENT_MDL(pNetBuffer), &pSrc, &CurrLength, NormalPagePriority); + if (pSrc == NULL) { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, ("Error porcessing packets\n")); + return status; + } + } else { + // This is ETH + IP together (at least) + pLsoData->LsoBuffers[0].pData = pSrc + (ETH_OFFSET - sizeof (ipoib_hdr_t)); + memcpy (pLsoData->LsoBuffers[0].pData, ipoib_hdr, sizeof (ipoib_hdr_t)); + CurrLength -= ETH_OFFSET; + pSrc = pSrc + ETH_OFFSET; + pLsoData->LsoHeaderSize = pLsoData->LsoHeaderSize + sizeof (ipoib_hdr_t); + } + // we should now be having at least the size of ethernet data + if (CurrLength < sizeof (ip_hdr_t)) { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, ("Error porcessing packets\n")); + return status; + } + IpHdr = (ip_hdr_t UNALIGNED*)pSrc; + IpHeaderLen = (uint16_t)IP_HEADER_LENGTH(IpHdr); + ASSERT(IpHdr->prot == IP_PROT_TCP); + if (CurrLength < IpHeaderLen) { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, ("Error processing packets\n")); + return status; + } + pLsoData->LsoHeaderSize = pLsoData->LsoHeaderSize + IpHeaderLen; + // We now start to find where the TCP header starts + if (CurrLength == IpHeaderLen) { + ASSERT(FALSE); + // two options : + // if(IsRegularFlow = FALSE) ==> ETH and IP seperated in two buffers + // if(IsRegularFlow = TRUE ) ==> ETH and IP in the same buffer + // TCP will start at next buffer + if(IsRegularFlow){ + memcpy(pCopiedData, pSrc-ETH_OFFSET ,ETH_OFFSET+IpHeaderLen); + pCopiedData += (ETH_OFFSET + IpHeaderLen); + } else { + memcpy(pCopiedData, pSrc,IpHeaderLen); + pCopiedData += IpHeaderLen; + } + + FullBuffers++; + IsRegularFlow = FALSE; + //NdisGetNextBuffer( CurrBuffer, &CurrBuffer); + //NdisQueryBufferSafe( CurrBuffer, &pSrc, &CurrLength, NormalPagePriority ); + pNetBuffer = NET_BUFFER_NEXT_NB(pNetBuffer); + NdisQueryMdl(NET_BUFFER_CURRENT_MDL(pNetBuffer), &pSrc, &CurrLength, NormalPagePriority); + if (pSrc == NULL) { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, ("Error porcessing packets\n")); + return status; + } + } else { + // if(IsRegularFlow = TRUE ) ==> the ETH and IP and TCP in the same buffer + // if(IsRegularFlow = FLASE ) ==> ETH in one buffer , IP+TCP together in the same buffer + if (IsRegularFlow) { + pLsoData->LsoBuffers[0].Len += IpHeaderLen; + } else { + memcpy(pCopiedData, pSrc, IpHeaderLen); + pCopiedData += IpHeaderLen; + } + + CurrLength -= IpHeaderLen; + pSrc = pSrc + IpHeaderLen; + } + if (CurrLength < sizeof (tcp_hdr_t)) { + IPOIB_PRINT(TRACE_LEVEL_VERBOSE, IPOIB_DBG_ERROR, ("Error porcessing packets\n")); + return status; + } + // We have finaly found the TCP header + TcpHdr = (tcp_hdr_t UNALIGNED *)pSrc; + TcpHeaderLen = TCP_HEADER_LENGTH(TcpHdr); + + //ASSERT(TcpHeaderLen == 20); + + if (CurrLength < TcpHeaderLen) { + //IPOIB_PRINT(TRACE_LEVEL_VERBOSE, ETH, ("Error porcessing packets\n")); + return status; + } + pLsoData->LsoHeaderSize = pLsoData->LsoHeaderSize + TcpHeaderLen; + if(IsRegularFlow){ + pLsoData->LsoBuffers[0].Len += TcpHeaderLen; + } + else{ + memcpy(pCopiedData, pSrc, TcpHeaderLen); + pCopiedData += TcpHeaderLen; + } + if (CurrLength == TcpHeaderLen) { + FullBuffers++; + pLsoData->UsedBuffers = FullBuffers; + *IndexOfData = FullBuffers ; + } else { + pLsoData->UsedBuffers = FullBuffers + 1; + *IndexOfData = FullBuffers - 1; + } + pLsoData->FullBuffers = FullBuffers; + if (!IsRegularFlow){ + pLsoData->LsoBuffers[0].pData = pLsoData->coppied_data; + pLsoData->LsoBuffers[0].Len = ETH_OFFSET + IpHeaderLen + TcpHeaderLen; + ASSERT(pLsoData->LsoBuffers[0].Len <= LSO_MAX_HEADER); + } + return NDIS_STATUS_SUCCESS; +} + +static void __port_do_mcast_garbage(ipoib_port_t* const p_port) +{ + const mac_addr_t DEFAULT_MCAST_GROUP = {0x01, 0x00, 0x5E, 0x00, 0x00, 0x01}; + /* Do garbage collecting... */ + + cl_map_item_t *p_item; + ipoib_endpt_t *p_endpt; + cl_qlist_t destroy_mc_list; + uint8_t cnt; + const static GC_MAX_LEAVE_NUM = 80; + + cl_qlist_init( &destroy_mc_list ); + + cl_obj_lock( &p_port->obj ); + /* Wait for all readers to finish */ + while( p_port->endpt_rdr ) + { + cl_obj_unlock( &p_port->obj ); + cl_obj_lock( &p_port->obj ); + } + cnt = 0; + p_item = cl_qmap_head( &p_port->endpt_mgr.mac_endpts ); + while( (p_item != cl_qmap_end( &p_port->endpt_mgr.mac_endpts )) && (cnt < GC_MAX_LEAVE_NUM)) + { + p_endpt = PARENT_STRUCT( p_item, ipoib_endpt_t, mac_item ); + p_item = cl_qmap_next( p_item ); + + /* Check if the current endpoint is not a multicast listener */ + + if( p_endpt->h_mcast && + (!p_endpt->is_mcast_listener) && + ( cl_memcmp( &p_endpt->mac, &DEFAULT_MCAST_GROUP, sizeof(mac_addr_t) ) && + (!p_endpt->is_in_use) )) + { + cl_qmap_remove_item( &p_port->endpt_mgr.mac_endpts, + &p_endpt->mac_item ); + cl_fmap_remove_item( &p_port->endpt_mgr.gid_endpts, + &p_endpt->gid_item ); + + if( p_endpt->dlid ) + { + cl_qmap_remove_item( &p_port->endpt_mgr.lid_endpts, + &p_endpt->lid_item ); + p_endpt->dlid = 0; + } + + cl_qlist_insert_tail( + &destroy_mc_list, &p_endpt->mac_item.pool_item.list_item ); + cnt++; + } + else + p_endpt->is_in_use = FALSE; + } + cl_obj_unlock( &p_port->obj ); + + /* Destroy all multicast endpoints now that we have released the lock. */ + while( cl_qlist_count( &destroy_mc_list ) ) + { + p_endpt = PARENT_STRUCT( cl_qlist_remove_head( &destroy_mc_list ), + ipoib_endpt_t, mac_item.pool_item.list_item ); + IPOIB_PRINT( TRACE_LEVEL_INFORMATION, IPOIB_DBG_ENDPT, + ("mcast garbage collector: destroying endpoint %02x:%02x:%02x:%02x:%02x:%02x \n", + p_endpt->mac.addr[0], + p_endpt->mac.addr[1], + p_endpt->mac.addr[2], + p_endpt->mac.addr[3], + p_endpt->mac.addr[4], + p_endpt->mac.addr[5]) ); + cl_obj_destroy( &p_endpt->obj ); + } +} + +static void __port_mcast_garbage_dpc(KDPC *p_gc_dpc,void *context,void *s_arg1, void *s_arg2) +{ + ipoib_port_t *p_port = context; + + UNREFERENCED_PARAMETER(p_gc_dpc); + UNREFERENCED_PARAMETER(s_arg1); + UNREFERENCED_PARAMETER(s_arg2); + + __port_do_mcast_garbage(p_port); +} + +ipoib_endpt_t* +ipoib_endpt_get_by_gid( + IN ipoib_port_t* const p_port, + IN const ib_gid_t* const p_gid ) +{ + return __endpt_mgr_get_by_gid( p_port, p_gid ); +} + +ipoib_endpt_t* +ipoib_endpt_get_by_lid( + IN ipoib_port_t* const p_port, + IN const net16_t lid ) +{ + return __endpt_mgr_get_by_lid( p_port, lid ); +} + +ib_api_status_t +ipoib_recv_dhcp( + IN ipoib_port_t* const p_port, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src, + IN ipoib_endpt_t* const p_dst ) +{ + return __recv_dhcp( + p_port, p_ipoib, p_eth, p_src,p_dst ); +} + + +void +ipoib_port_cancel_xmit( + IN ipoib_port_t* const p_port, + IN PVOID cancel_id ) +{ + cl_list_item_t *p_item; + PNET_BUFFER_LIST p_nbl; + PVOID nbl_id; + cl_qlist_t cancel_list; + ULONG send_complete_flags = 0; + IPOIB_ENTER( IPOIB_DBG_SEND ); + + cl_qlist_init( &cancel_list ); + + cl_spinlock_acquire( &p_port->send_lock ); + + for( p_item = cl_qlist_head( &p_port->send_mgr.pending_list ); + p_item != cl_qlist_end( &p_port->send_mgr.pending_list ); + p_item = cl_qlist_next( p_item ) ) + { + p_nbl = IPOIB_PACKET_FROM_LIST_ITEM( p_item ); + nbl_id = NDIS_GET_NET_BUFFER_LIST_CANCEL_ID( p_nbl ); + if( nbl_id == cancel_id ) + { + cl_qlist_remove_item( &p_port->send_mgr.pending_list, p_item ); + NET_BUFFER_LIST_STATUS( p_nbl) = NDIS_STATUS_REQUEST_ABORTED ; + cl_qlist_insert_tail( &cancel_list, IPOIB_LIST_ITEM_FROM_PACKET( p_nbl ) ); + } + } + cl_spinlock_release( &p_port->send_lock ); + + if( cl_qlist_count( &cancel_list ) ) + { + while( ( p_item = cl_qlist_remove_head( &cancel_list )) + != cl_qlist_end( &cancel_list )) + { + p_nbl = IPOIB_PACKET_FROM_LIST_ITEM( p_item ); + NET_BUFFER_LIST_STATUS( p_nbl) = NDIS_STATUS_SEND_ABORTED; + send_complete_flags = 0; + if (NDIS_CURRENT_IRQL() == DISPATCH_LEVEL) + { + NDIS_SET_SEND_COMPLETE_FLAG(send_complete_flags, NDIS_SEND_COMPLETE_FLAGS_DISPATCH_LEVEL); + } + NdisMSendNetBufferListsComplete( p_port->p_adapter->h_adapter, + p_nbl, send_complete_flags ); + } + } + IPOIB_EXIT( IPOIB_DBG_SEND ); +} + +/* +* Put all fragments into separate WR and chain together. +* The last WR will be set to generate CQ Event. +* lookaside buffer is used for ipoib and ip headers attached to each WR. +* Buffer will be released on last WR send completion. +*/ +#if 0 +static NDIS_STATUS +__send_fragments( +IN ipoib_port_t* const p_port, +IN ipoib_send_desc_t* const p_desc, +IN eth_hdr_t* const p_eth_hdr, +IN ip_hdr_t* const p_ip_hdr, +IN uint32_t buf_len, +IN NDIS_BUFFER* p_ndis_buf ) +{ + uint32_t ds_idx = 1; + uint32_t wr_idx = 0; + uint32_t sgl_idx = 2; //skip eth hdr, ip hdr + uint32_t options_len = 0; + uint8_t* p_options = NULL; + uint8_t* p_buf; + uint32_t frag_offset = 0; + uint32_t next_sge; + uint32_t wr_size = 0; + uint32_t ip_hdr_len = IP_HEADER_LENGTH( p_ip_hdr ); + uint32_t total_ip_len = cl_ntoh16( p_ip_hdr->length ); + + SCATTER_GATHER_LIST *p_sgl; + + IPOIB_ENTER( IPOIB_DBG_SEND ); + + if( IP_DONT_FRAGMENT(p_ip_hdr) ) + return NDIS_STATUS_INVALID_PACKET; + + p_sgl = NDIS_PER_PACKET_INFO_FROM_PACKET( p_desc->p_pkt, ScatterGatherListPacketInfo ); + if( !p_sgl ) + { + ASSERT( p_sgl ); + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get SGL from packet.\n") ); + return NDIS_STATUS_FAILURE; + } + if( ( p_sgl->NumberOfElements > MAX_SEND_SGE || + p_sgl->Elements[0].Length < sizeof(eth_hdr_t)) ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Too many SG Elements in packet.\n") ); + return NDIS_STATUS_FAILURE; + } + p_buf = (uint8_t *) + ExAllocateFromNPagedLookasideList( &p_port->buf_mgr.send_buf_list ); + if( !p_buf ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to allocate lookaside buffer.\n") ); + return NDIS_STATUS_RESOURCES; + } + p_desc->p_buf = (send_buf_t*)p_buf; + + if( buf_len < ip_hdr_len ) + { /* ip options in a separate buffer */ + CL_ASSERT( buf_len == sizeof( ip_hdr_t ) ); + NdisGetNextBuffer( p_ndis_buf, &p_ndis_buf ); + if( !p_ndis_buf ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to get IP options buffer.\n") ); + return NDIS_STATUS_FAILURE; + } + NdisQueryBufferSafe( p_ndis_buf, &p_options, &options_len, NormalPagePriority ); + if( !p_options ) + { + IPOIB_PRINT_EXIT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Failed to query IP options buffer address.\n") ); + return NDIS_STATUS_FAILURE; + } + cl_memcpy( p_buf, p_ip_hdr, sizeof( ip_hdr_t ) ); + if( p_options && options_len ) + { + __copy_ip_options( &p_buf[sizeof(ip_hdr_t)], + p_options, options_len, TRUE ); + } + wr_size = buf_len + options_len; + sgl_idx++; + } + else + { /*options probably in the same buffer */ + cl_memcpy( p_buf, p_ip_hdr, buf_len ); + options_len = ip_hdr_len - sizeof( ip_hdr_t ); + if( options_len ) + { + p_options = p_buf + sizeof( ip_hdr_t ); + } + frag_offset += ( buf_len - ip_hdr_len ); + wr_size = buf_len; + } + + p_desc->send_wr[wr_idx].local_ds[ds_idx].vaddr = cl_get_physaddr( p_buf ); + p_desc->send_wr[wr_idx].local_ds[ds_idx].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[wr_idx].local_ds[ds_idx].length = wr_size; + + /* count how much data can be put into the first WR beside IP header. + * other protocols headers possibly supplied in subsequent buffers. + */ + for( sgl_idx; sgl_idx < p_sgl->NumberOfElements; sgl_idx++ ) + { + next_sge = p_sgl->Elements[sgl_idx].Length; + + /* add sgl if it can fit into the same WR + * Note: so far not going to split large SGE between WRs, + * so first fragment could be a smaller size. + */ + if( next_sge <= ( p_port->p_adapter->params.payload_mtu - wr_size ) ) + { + ++ds_idx; + wr_size += next_sge; + frag_offset += next_sge; + p_desc->send_wr[wr_idx].local_ds[ds_idx].vaddr = + p_sgl->Elements[sgl_idx].Address.QuadPart; + p_desc->send_wr[wr_idx].local_ds[ds_idx].length = next_sge; + p_desc->send_wr[wr_idx].local_ds[ds_idx].lkey = p_port->ib_mgr.lkey; + } + else + { + /* fix ip hdr for the first fragment and move on */ + __update_fragment_ip_hdr( (ip_hdr_t* const)p_buf, + (uint16_t)wr_size, IP_FRAGMENT_OFFSET(p_ip_hdr), TRUE ); + + p_desc->send_wr[wr_idx].wr.num_ds = ds_idx + 1; + p_buf += ip_hdr_len; + p_buf += (( buf_len > ip_hdr_len ) ? ( buf_len - ip_hdr_len ): 0); + frag_offset += ( (IP_FRAGMENT_OFFSET(p_ip_hdr)) << 3 ); + ++wr_idx; + ds_idx = 0; + break; + } + } + total_ip_len -= wr_size; + wr_size = 0; + + for( sgl_idx, wr_idx; sgl_idx < p_sgl->NumberOfElements; sgl_idx++ ) + { + uint32_t seg_len; + uint64_t next_sgl_addr; + + if( wr_idx >= ( MAX_WRS_PER_MSG - 1 ) ) + return NDIS_STATUS_RESOURCES; + + next_sge = p_sgl->Elements[sgl_idx].Length; + next_sgl_addr = p_sgl->Elements[sgl_idx].Address.QuadPart; + + while( next_sge ) + { + if( ds_idx == 0 ) + { /* new ipoib + ip header */ + ((ipoib_hdr_t*)p_buf)->type = p_eth_hdr->type; + ((ipoib_hdr_t*)p_buf)->resv = 0; + p_desc->send_wr[wr_idx].local_ds[ds_idx].vaddr = cl_get_physaddr( p_buf ); + p_desc->send_wr[wr_idx].local_ds[ds_idx].lkey = p_port->ib_mgr.lkey; + p_desc->send_wr[wr_idx].local_ds[ds_idx].length = sizeof( ipoib_hdr_t ); + p_buf += sizeof( ipoib_hdr_t ); + ++ds_idx; + + cl_memcpy( p_buf, p_ip_hdr, sizeof( ip_hdr_t ) ); + if( p_options && options_len ) + { + /* copy ip options if needed */ + __copy_ip_options( &p_buf[sizeof(ip_hdr_t)], + p_options, options_len, FALSE ); + } + wr_size = ip_hdr_len; + } + if( ds_idx == 1 ) + { + p_desc->send_wr[wr_idx].local_ds[ds_idx].length = ip_hdr_len; + p_desc->send_wr[wr_idx].local_ds[ds_idx].vaddr = cl_get_physaddr( p_buf ); + p_desc->send_wr[wr_idx].local_ds[ds_idx].lkey = p_port->ib_mgr.lkey; + ++ds_idx; + } + + seg_len = ( next_sge > ( p_port->p_adapter->params.payload_mtu - wr_size ) )? + ( p_port->p_adapter->params.payload_mtu - wr_size ) : next_sge; + + p_desc->send_wr[wr_idx].local_ds[ds_idx].vaddr = next_sgl_addr; + p_desc->send_wr[wr_idx].local_ds[ds_idx].length = seg_len; + p_desc->send_wr[wr_idx].local_ds[ds_idx].lkey = p_port->ib_mgr.lkey; + ++ds_idx; + + wr_size += seg_len; + total_ip_len -= seg_len; + + if( wr_size >= p_port->p_adapter->params.payload_mtu || total_ip_len == 0 ) + { /* fix ip hdr for that fragment */ + __update_fragment_ip_hdr( (ip_hdr_t* const)p_buf, (uint16_t)wr_size, + ((uint16_t)(frag_offset >> 3 )), + (BOOLEAN)(( total_ip_len > 0 ) || IP_MORE_FRAGMENTS( p_ip_hdr)) ); + p_desc->send_wr[wr_idx].wr.num_ds = ds_idx; + if( total_ip_len > 0 ) + { + ++wr_idx; + frag_offset += (wr_size - ip_hdr_len); + wr_size = 0; + ds_idx = 0; + p_buf += ip_hdr_len; + } + } + next_sge -= seg_len; + if( next_sge > 0 ) + { + next_sgl_addr += seg_len; + } + } + } + p_desc->num_wrs += wr_idx; + + IPOIB_EXIT( IPOIB_DBG_SEND ); + return NDIS_STATUS_SUCCESS; +} +#endif + +static void +__update_fragment_ip_hdr( +IN ip_hdr_t* const p_ip_hdr, +IN uint16_t fragment_size, +IN uint16_t fragment_offset, +IN BOOLEAN more_fragments ) +{ + uint16_t* p_hdr = (uint16_t*)p_ip_hdr; + p_ip_hdr->length = cl_hton16( fragment_size ); // bytes + p_ip_hdr->offset = cl_hton16( fragment_offset ); // 8-byte units + if( more_fragments ) + { + IP_SET_MORE_FRAGMENTS( p_ip_hdr ); + } + else + { + IP_SET_LAST_FRAGMENT( p_ip_hdr ); + } + p_ip_hdr->chksum = 0; + p_ip_hdr->chksum = ipchksum( p_hdr, IP_HEADER_LENGTH(p_ip_hdr) ); +} + +static void +__copy_ip_options( +IN uint8_t* p_buf, +IN uint8_t* p_options, +IN uint32_t options_len, +IN BOOLEAN copy_all ) +{ + uint32_t option_length; + uint32_t total_length = 0; + uint32_t copied_length = 0; + uint8_t* p_src = p_options; + uint8_t* p_dst = p_buf; + + if( p_options == NULL || options_len == 0 ) + return; + if( copy_all ) + { + cl_memcpy( p_dst, p_src, options_len ); + return; + } + do + { + if( ( *p_src ) == 0 ) // end of options list + { + total_length++; + break; + } + if( ( *p_src ) == 0x1 ) // no op + { + p_src++; + total_length++; + continue; + } + /*from RFC791: + * This option may be used between options, for example, to align + * the beginning of a subsequent option on a 32 bit boundary. + */ + if( copied_length && (copied_length % 4) ) + { + uint32_t align = 4 - (copied_length % 4); + cl_memset( p_dst, 0x1, (size_t)align ); + p_dst += align; + copied_length += align; + } + option_length = *(p_src + 1); + + if( *p_src & 0x80 ) + { + cl_memcpy( p_dst, p_src, option_length ); + p_dst += option_length; + copied_length += option_length; + } + total_length += option_length; + p_src += option_length; + + }while( total_length < options_len ); + + CL_ASSERT( total_length == options_len ); + CL_ASSERT( copied_length <= 40 ); + + /* padding the rest */ + if( options_len > copied_length ) + { + cl_memclr( p_dst, ( options_len - copied_length ) ); + } + return; +} diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_port.h b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_port.h new file mode 100644 index 00000000..602389b5 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_port.h @@ -0,0 +1,827 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * Copyright (c) 2006 Mellanox Technologies. All rights reserved. + * Portions Copyright (c) 2008 Microsoft Corporation. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_port.h 4226 2009-04-06 06:01:03Z xalex $ + */ + + + +#ifndef _IPOIB_PORT_H_ +#define _IPOIB_PORT_H_ + + +#include +#include +#include +#include +#include +#include "ipoib_xfr_mgr.h" +#include "ipoib_endpoint.h" + + +/* + * Define to place receive buffer inline in receive descriptor. + */ +#define IPOIB_INLINE_RECV 1 + +/* + * Invalid pkey index + */ +#define PKEY_INVALID_INDEX 0xFFFF + +/* + * Define to control how transfers are done. When defined as 1, causes + * packets to be sent using NDIS DMA facilities (getting the SGL from the + * packet). When defined as 0, uses the NDIS_BUFFER structures as MDLs + * to get the physical page mappings of the buffers. + */ +#define IPOIB_USE_DMA 1 + + +#define IPOIB_PORT_FROM_PACKET( P ) \ + (((ipoib_port_t**)NET_BUFFER_LIST_MINIPORT_RESERVED(P))[0]) +#define IPOIB_ENDPT_FROM_PACKET( P ) \ + (((ipoib_endpt_t**)NET_BUFFER_LIST_MINIPORT_RESERVED(P))[1]) +#define IPOIB_RECV_FROM_PACKET( P ) \ + (((ipoib_recv_desc_t**)NET_BUFFER_LIST_MINIPORT_RESERVED(P))[1]) + +//TODO to be renamed: IPOIB_NBL_FROM_LIST_ITEM +#define IPOIB_PACKET_FROM_LIST_ITEM( I ) \ + (PARENT_STRUCT( I, NET_BUFFER_LIST, MiniportReserved )) +#define IPOIB_LIST_ITEM_FROM_PACKET( P ) \ + ((cl_list_item_t*)NET_BUFFER_LIST_MINIPORT_RESERVED(P)) + +#define IPOIB_NET_BUFFER_LIST_FROM_NETBUFFER( P ) \ + (((NET_BUFFER_LIST**)NET_BUFFER_MINIPORT_RESERVED(P))[0]) +#define IPOIB_FROM_QUEUE( P ) \ + (((void**)NET_BUFFER_MINIPORT_RESERVED(P))[1]) +#define IPOIB_SEND_FROM_NETBUFFER( P ) \ + (((send_buf_t**)NET_BUFFER_MINIPORT_RESERVED(P))[2]) + + +#define IPOIB_GET_NET_BUFFER_LIST_REF_COUNT(_NetBufferList) ((_NetBufferList->FirstNetBuffer)->MiniportReserved[3]) +#define IPOIB_DEC_NET_BUFFER_LIST_REF_COUNT(_NetBufferList) (*(PULONG)&(_NetBufferList->FirstNetBuffer)->MiniportReserved[3])-- + + +typedef struct _ipoib_ib_mgr +{ + ib_ca_handle_t h_ca; + ib_pd_handle_t h_pd; + ib_cq_handle_t h_recv_cq; + ib_cq_handle_t h_send_cq; + ib_qp_handle_t h_qp; + ib_query_handle_t h_query; + ib_srq_handle_t h_srq; + net32_t qpn; + + ib_mr_handle_t h_mr; + net32_t lkey; + + uint8_t rate; + ib_member_rec_t bcast_rec; + +} ipoib_ib_mgr_t; +/* +* FIELDS +* h_ca +* CA handle for all IB resources. +* +* h_pd +* PD handle for all IB resources. +* +* h_recv_cq +* Recv CQ handle. +* +* h_send_cq +* Send CQ handle. +* +* h_qp +* QP handle for data transfers. +* +* h_query +* Query handle for cancelling SA queries. +* +* h_mr +* Registration handle for all of physical memory. Used for +* send/receive buffers to simplify growing the receive pool. +* +* lkey +* LKey for the memory region. +* +* bcast_rec +* Cached information about the broadcast group, used to specify +* parameters used to join other multicast groups. +*********/ + + +#include +/****s* IPoIB Driver/ipoib_hdr_t +* NAME +* ipoib_hdr_t +* +* DESCRIPTION +* IPoIB packet header. +* +* SYNOPSIS +*/ +typedef struct _ipoib_hdr +{ + net16_t type; + net16_t resv; + +} PACK_SUFFIX ipoib_hdr_t; +/* +* FIELDS +* type +* Protocol type. +* +* resv +* Reserved portion of IPoIB header. +*********/ + +typedef struct _ipoib_arp_pkt +{ + net16_t hw_type; + net16_t prot_type; + uint8_t hw_size; + uint8_t prot_size; + net16_t op; + ipoib_hw_addr_t src_hw; + net32_t src_ip; + ipoib_hw_addr_t dst_hw; + net32_t dst_ip; + +} PACK_SUFFIX ipoib_arp_pkt_t; + + +/****s* IPoIB Driver/ipoib_pkt_t +* NAME +* ipoib_pkt_t +* +* DESCRIPTION +* Represents an IPoIB packet with no GRH. +* +* SYNOPSIS +*/ +typedef struct _ipoib_pkt +{ + ipoib_hdr_t hdr; + union _payload + { + uint8_t data[MAX_UD_PAYLOAD_MTU]; + ipoib_arp_pkt_t arp; + ip_pkt_t ip; + + } PACK_SUFFIX type; + +} PACK_SUFFIX ipoib_pkt_t; +/* +* FIELDS +* hdr +* IPoIB header. +* +* type +* Union for different types of payloads. +* +* type.data +* raw packet. +* +* type.ib_arp +* IPoIB ARP packet. +* +* type.arp +* Ethernet ARP packet. +* +* type.ip +* IP packet. +*********/ + + +/****s* IPoIB Driver/recv_buf_t +* NAME +* recv_buf_t +* +* DESCRIPTION +* Represents a receive buffer, including the ethernet header +* used to indicate the receive to the OS. +* +* SYNOPSIS +*/ +typedef union _recv_buf +{ + struct _recv_buf_type_eth + { + uint8_t pad[sizeof(ib_grh_t) + + sizeof(ipoib_hdr_t) - + sizeof(eth_hdr_t)]; + eth_pkt_t pkt; /* data starts at sizeof(grh)+sizeof(eth_hdr) */ + + } PACK_SUFFIX eth; + + struct _recv_buf_type_ib + { + ib_grh_t grh; /* Must be same offset as lcl_rt.ib.pkt */ + ipoib_pkt_t pkt; /* data starts at 10+grh+4 */ + + } PACK_SUFFIX ib; + +} PACK_SUFFIX recv_buf_t; +/* +* FIELDS +* eth.pkt +* Ethernet packet, used to indicate the receive to the OS. +* +* ib.grh +* GRH for a globally routed received packet. +* +* ib.pkt +* IPOIB packet representing a globally routed received packet. +* +* NOTES +* When posting the work request, the address of ib.grh is used. +* +* TODO: Do we need a pad to offset the header so that the data ends up +* aligned on a pointer boundary? +*********/ + +/****s* IPoIB Driver/send_buf_t +* NAME +* send_buf_t +* +* DESCRIPTION +* Represents a send buffer, used to convert packets to IPoIB format. +* +* SYNOPSIS +*/ +typedef union _send_buf +{ + uint8_t data[MAX_UD_PAYLOAD_MTU]; + ipoib_arp_pkt_t arp; + ip_pkt_t ip; + +} PACK_SUFFIX send_buf_t; +/* +* FIELDS +* data +* IP/ARP packet. +* +* NOTES +* TODO: Do we need a pad to offset the header so that the data ends up +* aligned on a pointer boundary? +*********/ +#include + + +typedef struct _ipoib_buf_mgr +{ + cl_qpool_t recv_pool; + + NDIS_HANDLE h_packet_pool; + NDIS_HANDLE h_buffer_pool; + + NPAGED_LOOKASIDE_LIST send_buf_list; + NDIS_HANDLE h_send_pkt_pool; + NDIS_HANDLE h_send_buf_pool; + +} ipoib_buf_mgr_t; +/* +* FIELDS +* recv_pool +* Pool of ipoib_recv_desc_t structures. +* +* h_packet_pool +* NDIS packet pool, used to indicate receives to NDIS. +* +* h_buffer_pool +* NDIS buffer pool, used to indicate receives to NDIS. +* +* send_buf_list +* Lookaside list for dynamically allocating send buffers for send +* that require copies (ARP, DHCP, and any with more physical pages +* than can fit in the local data segments). +*********/ + + +typedef enum _ipoib_pkt_type +{ + PKT_TYPE_UCAST, + PKT_TYPE_BCAST, + PKT_TYPE_MCAST, + PKT_TYPE_CM_UCAST + +} ipoib_pkt_type_t; + +typedef struct _ipoib_cm_desc +{ + cl_pool_item_t item; /* Must be first. */ + uint32_t len; + ipoib_pkt_type_t type; + ib_recv_wr_t wr; + ib_local_ds_t local_ds[2]; + cl_list_item_t list_item; + uint8_t* p_alloc_buf; + uint8_t* p_buf; + uint32_t alloc_buf_size; + uint32_t buf_size; + net32_t lkey; + ib_mr_handle_t h_mr; + NDIS_TCP_IP_CHECKSUM_PACKET_INFO ndis_csum; + +} ipoib_cm_desc_t; + +typedef struct _ipoib_recv_desc +{ + cl_pool_item_t item; /* Must be first. */ + uint32_t len; + ipoib_pkt_type_t type; + ib_recv_wr_t wr; + ib_local_ds_t local_ds[2]; + NDIS_TCP_IP_CHECKSUM_PACKET_INFO ndis_csum; +#if IPOIB_INLINE_RECV + recv_buf_t buf; +#else + recv_buf_t *p_buf; +#endif + +} ipoib_recv_desc_t; +/* +* FIELDS +* item +* Pool item for storing descriptors in a pool. +* +* len +* Length to indicate to NDIS. This is different than the length of the +* received data as some data is IPoIB specific and filtered out. +* +* type +* Type of packet, used in filtering received packets against the packet +* filter. Also used to update stats. +* +* wr +* Receive work request. +* +* local_ds +* Local data segments. The second segment is only used if a buffer +* spans physical pages. +* +* buf +* Buffer for the receive. +* +* NOTES +* The pool item is always first to allow casting form a cl_pool_item_t or +* cl_list_item_t to the descriptor. +*********/ +typedef struct __ipoib_send_wr +{ + ib_send_wr_t wr; + ib_local_ds_t local_ds[MAX_SEND_SGE]; /* Must be last. */ +} ipoib_send_wr_t; + +typedef enum __send_dir +{ + SEND_UD_QP = 1, + SEND_RC_QP = 2 +} send_dir_t; + +typedef struct _ipoib_send_desc +{ + PNET_BUFFER_LIST p_netbuf_list; + ipoib_endpt_t *p_endpt; + send_buf_t *p_buf; + ib_qp_handle_t send_qp; + send_dir_t send_dir; + uint32_t num_wrs; + ipoib_send_wr_t send_wr[MAX_WRS_PER_MSG]; + +} ipoib_send_desc_t; +/* +* FIELDS +* p_pkt +* Pointer to the NDIS_PACKET associated with the send operation. +* +* p_endpt +* Endpoint for this send. +* +* p_buf +* Buffer for the send, if allocated. +* +* wr +* Send work request. +* +* pkt_hdr +* IPoIB packet header, pointed to by the first local datasegment. +* +* local_ds +* Local data segment array. Placed last to allow allocating beyond the +* end of the descriptor for additional datasegments. +* +* NOTES +* The pool item is always first to allow casting form a cl_pool_item_t or +* cl_list_item_t to the descriptor. +*********/ + + +typedef struct _ipoib_recv_mgr +{ + int32_t depth; + + NET_BUFFER_LIST **recv_pkt_array; + + cl_qlist_t done_list; + +} ipoib_recv_mgr_t; +/* +* FIELDS +* depth +* Current number of WRs posted. +* +* p_head +* Pointer to work completion in descriptor at the head of the QP. +* +* p_tail +* Pointer to the work completion in the descriptor at the tail of the QP. +* +* recv_pkt_array +* Array of pointers to NDIS_PACKET used to indicate receives. +* +* done_list +* List of receive descriptors that need to be indicated to NDIS. +*********/ + + +typedef struct _ipoib_send_mgr +{ + atomic32_t depth; + cl_qlist_t pending_list; + ipoib_send_desc_t desc; + +} ipoib_send_mgr_t; +/* +* FIELDS +* depth +* Current number of WRs posted, used to queue pending requests. +* +* pending_list +* List of NDIS_PACKET structures that are awaiting available WRs to send. +*********/ + + +typedef struct _ipoib_endpt_mgr +{ + cl_qmap_t mac_endpts; + cl_fmap_t gid_endpts; + cl_qmap_t lid_endpts; + cl_fmap_t conn_endpts; + LIST_ENTRY pending_conns; + LIST_ENTRY remove_conns; + NDIS_SPIN_LOCK conn_lock; + NDIS_SPIN_LOCK remove_lock; + cl_thread_t h_thread; + cl_event_t event; + uint32_t thread_is_done; +} ipoib_endpt_mgr_t; +/* +* FIELDS +* mac_endpts +* Map of enpoints, keyed by MAC address. +* +* gid_endpts +* Map of enpoints, keyed by GID. +* +* lid_endpts +* Map of enpoints, keyed by LID. Only enpoints on the same subnet +* are inserted in the LID map. +* +* conn_endpts +* Map of connected endpts, keyed by remote gid. +*********/ + + +typedef struct _ipoib_port +{ + cl_obj_t obj; + cl_obj_rel_t rel; + + ib_qp_state_t state; + + cl_spinlock_t recv_lock; + cl_spinlock_t send_lock; + + struct _ipoib_adapter *p_adapter; + uint8_t port_num; + + KEVENT sa_event; + + atomic32_t mcast_cnt; + KEVENT leave_mcast_event; + + ipoib_ib_mgr_t ib_mgr; + + ipoib_buf_mgr_t buf_mgr; + + ipoib_recv_mgr_t recv_mgr; + ipoib_send_mgr_t send_mgr; + + KDPC recv_dpc; + + ipoib_endpt_mgr_t endpt_mgr; + + endpt_buf_mgr_t cm_buf_mgr; + endpt_recv_mgr_t cm_recv_mgr; + + ipoib_endpt_t *p_local_endpt; + ib_ca_attr_t *p_ca_attrs; +#if DBG + atomic32_t ref[ref_array_size]; +#endif + + atomic32_t endpt_rdr; + + atomic32_t hdr_idx; + uint16_t pkey_index; + KDPC gc_dpc; + KTIMER gc_timer; + uint32_t bc_join_retry_cnt; + ib_net16_t base_lid; + ipoib_hdr_t hdr[1]; /* Must be last! */ + +} ipoib_port_t; +/* +* FIELDS +* obj +* Complib object for reference counting, relationships, +* and destruction synchronization. +* +* rel +* Relationship to associate the port with the adapter. +* +* state +* State of the port object. Tracks QP state fairly closely. +* +* recv_lock +* Spinlock to protect receive operations. +* +* send_lock +* Spinlock to protect send operations. +* +* p_adapter +* Parent adapter. Used to get AL handle. +* +* port_num +* Port number of this adapter. +* +* ib_mgr +* IB resource manager. +* +* recv_mgr +* Receive manager. +* +* send_mgr +* Send manager. +* +* endpt_mgr +* Endpoint manager. +*********/ +typedef struct _sgl_context +{ + MDL *p_mdl; + NET_BUFFER_LIST *p_netbuffer_list; + ipoib_port_t *p_port; +}sgl_context_t; + +ib_api_status_t +ipoib_create_port( + IN struct _ipoib_adapter* const p_adapter, + IN ib_pnp_port_rec_t* const p_pnp_rec, + OUT ipoib_port_t** const pp_port ); + +void +ipoib_port_destroy( + IN ipoib_port_t* const p_port ); + +void +ipoib_port_up( + IN ipoib_port_t* const p_port, + IN const ib_pnp_port_rec_t* const p_pnp_rec ); + +void +ipoib_port_down( + IN ipoib_port_t* const p_port ); + +ib_api_status_t +ipoib_port_join_mcast( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + IN const uint8_t state ); + + +void +ipoib_leave_mcast_cb( + IN void *context ); + + +void +ipoib_port_remove_endpt( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac ); + +void +ipoib_port_send( + IN ipoib_port_t* const p_port, + IN NET_BUFFER_LIST *net_buffer_list, + IN ULONG send_flags ); + +void +ipoib_return_net_buffer_list( + IN NDIS_HANDLE adapter_context, + IN PNET_BUFFER_LIST p_netbuffer_lists, + IN ULONG return_flags); + +void +ipoib_port_resume( + IN ipoib_port_t* const p_port, + IN boolean_t b_pending ); + +NTSTATUS +ipoib_mac_to_gid( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ib_gid_t* p_gid ); + +NTSTATUS +ipoib_mac_to_path( + IN ipoib_port_t* const p_port, + IN const mac_addr_t mac, + OUT ib_path_rec_t* p_path ); + +void +ipoib_process_sg_list( + IN PDEVICE_OBJECT pDO, + IN PVOID pIrp, + IN PSCATTER_GATHER_LIST pSGList, + IN PVOID Context); + +inline void ipoib_port_ref( + IN ipoib_port_t * p_port, + IN int type); + +inline void ipoib_port_deref( + IN ipoib_port_t * p_port, + IN int type); + +#if 0 +// This function is only used to monitor send failures +static inline VOID NdisMSendCompleteX( + IN NDIS_HANDLE MiniportAdapterHandle, + IN PNDIS_PACKET Packet, + IN NDIS_STATUS Status + ) { + if (Status != NDIS_STATUS_SUCCESS) { + IPOIB_PRINT( TRACE_LEVEL_ERROR, IPOIB_DBG_ERROR, + ("Sending status other than Success to NDIS\n")); + } + NdisMSendComplete(MiniportAdapterHandle,Packet,Status); +} +#else +//#define NdisMSendCompleteX NdisMSendComplete +#endif + +ipoib_endpt_t* +ipoib_endpt_get_by_gid( + IN ipoib_port_t* const p_port, + IN const ib_gid_t* const p_gid ); + +ipoib_endpt_t* +ipoib_endpt_get_by_lid( + IN ipoib_port_t* const p_port, + IN const net16_t lid ); + +ib_api_status_t +ipoib_port_srq_init( + IN ipoib_port_t* const p_port ); + +void +ipoib_port_srq_destroy( + IN ipoib_port_t* const p_port ); + +#if 0 //CM +ib_api_status_t +ipoib_port_listen( + IN ipoib_port_t* const p_port ); + +ib_api_status_t +ipoib_port_cancel_listen( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt ) { + UNUSED_PARAM(p_port); + UNUSED_PARAM(p_endpt); + return IB_SUCCESS; +} +#endif + +ib_api_status_t +endpt_cm_buf_mgr_init( + IN ipoib_port_t* const p_port ); + +void +endpt_cm_buf_mgr_destroy( + IN ipoib_port_t* const p_port ); + +void +endpt_cm_buf_mgr_reset( + IN ipoib_port_t* const p_port ); + +void +endpt_cm_buf_mgr_put_recv( + IN endpt_buf_mgr_t * const p_buf_mgr, + IN ipoib_cm_desc_t* const p_desc ); + +void +endpt_cm_buf_mgr_put_recv_list( + IN endpt_buf_mgr_t * const p_buf_mgr, + IN cl_qlist_t* const p_list ); + +uint32_t +endpt_cm_recv_mgr_build_pkt_array( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt, + IN cl_qlist_t* const p_done_list, + IN OUT uint32_t* p_bytes_recv ); + +ib_api_status_t +endpt_cm_post_recv( + IN ipoib_port_t* const p_port ); + +/*void +endpt_cm_destroy_conn( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt ); +*/ +void +endpt_cm_disconnect( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt ); +void +endpt_cm_flush_recv( + IN ipoib_port_t* const p_port, + IN ipoib_endpt_t* const p_endpt ); + +ib_api_status_t +ipoib_recv_dhcp( + IN ipoib_port_t* const p_port, + IN const ipoib_pkt_t* const p_ipoib, + OUT eth_pkt_t* const p_eth, + IN ipoib_endpt_t* const p_src, + IN ipoib_endpt_t* const p_dst ); + +void +ipoib_port_cancel_xmit( + IN ipoib_port_t* const p_port, + IN PVOID cancel_id ); + +static inline uint32_t +__port_attr_to_mtu_size(uint32_t value) +{ + switch (value) + { + default: + case IB_MTU_LEN_2048: + return 2048; + case IB_MTU_LEN_4096: + return 4096; + case IB_MTU_LEN_1024: + return 1024; + case IB_MTU_LEN_512: + return 512; + case IB_MTU_LEN_256: + return 256; + } +} +#endif /* _IPOIB_PORT_H_ */ diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_xfr_mgr.c b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_xfr_mgr.c new file mode 100644 index 00000000..ce0650f1 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_xfr_mgr.c @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2008 Mellanox Technologies. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_xfr_mgr.c 3459 2008-11-12 16:48:21Z tzachid $ + */ + + +#include "ipoib_xfr_mgr.h" +#if defined(EVENT_TRACING) +#ifdef offsetof +#undef offsetof +#endif +#include "ipoib_xfr_mgr.tmh" +#endif + + +const ipoib_guid2mac_translation_t guid2mac_table[] = { + {0x30, 0x48, 0xE7}, + {0x05, 0xAD, 0xE7}, + {0x18, 0x8B, 0xE7}, + {0x1A, 0x4B, 0xE7}, + {0x17, 0x08, 0xE7}, + {0x1E, 0x0B, 0xE7}, + + {0x03, 0xBA, 0xE7}, + {0x05, 0xAD, 0xE7}, + {0x0D, 0x9D, 0xE7}, + {0x11, 0x0A, 0xE7}, + {0x11, 0x85, 0xE7}, + {0x12, 0x79, 0xE7}, + {0x13, 0x21, 0xE7}, + {0x14, 0x38, 0xE7}, + {0x16, 0x35, 0xE7}, + {0x17, 0x08, 0xE7}, + {0x17, 0xA4, 0xE7}, + {0x18, 0x8B, 0xE7}, + {0x18, 0xFE, 0xE7}, + {0x19, 0xBB, 0xE7}, + {0x1A, 0x4B, 0xE7}, + {0x1B, 0x78, 0xE7}, + {0x1E, 0x0B, 0xE7}, + {0x22, 0x64, 0xE7}, + {0x23, 0x7D, 0xE7}, + {0x30, 0x48, 0xE7}, + {0x80, 0x5F, 0xE7}, + + {0x00, 0x00, 0x00}, +}; + diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_xfr_mgr.h b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_xfr_mgr.h new file mode 100644 index 00000000..9e38b609 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/ipoib_xfr_mgr.h @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2005 SilverStorm Technologies. All rights reserved. + * + * This software is available to you under the OpenIB.org BSD license + * below: + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * $Id: ipoib_xfr_mgr.h 3719 2009-01-07 12:31:52Z reuven $ + */ + + +#ifndef _IPOIB_XFR_MGR_H_ +#define _IPOIB_XFR_MGR_H_ + + +#include +#include +#include +#include +#include +#include +#include + + +#include "ipoib_driver.h" +#include "ip_stats.h" +#include + + +#include +/****s* IPoIB Driver/ipoib_hw_addr_t +* NAME +* ipoib_hw_addr_t +* +* DESCRIPTION +* The ipoib_hw_addr_t structure defines an IPoIB compatible hardware +* address. Values in this structure are stored in network order. +* +* SYNOPSIS +*/ +typedef struct _ipoib_hw_addr +{ + uint32_t flags_qpn; + ib_gid_t gid; + +} PACK_SUFFIX ipoib_hw_addr_t; +/* +* FIELDS +* flags_qpn +* Flags and queue pair number. Use ipoib_addr_get_flags, +* ipoib_addr_set_flags, ipoib_addr_set_qpn, and ipoib_addr_get_qpn +* to manipulate the contents. +* +* gid +* IB GID value. +* +* SEE ALSO +* IPoIB, ipoib_addr_get_flags, ipoib_addr_set_flags, ipoib_addr_set_qpn, +* ipoib_addr_get_qpn +*********/ +#include + +/****s* IPoIB Driver/ipoib_guid2mac_translation_t +* NAME +* ipoib_guid2mac_translation_t +* +* DESCRIPTION +* The ipoib_guid2mac_translation_t structure defines a GUID to MAC translation. +* The structure holds map between known OUI to an appropriate GUID mask. +* +* SYNOPSIS +*/ +typedef struct _ipoib_guid2mac_translation_ +{ + uint8_t second_byte; + uint8_t third_byte; + uint8_t guid_mask; + +} ipoib_guid2mac_translation_t; +/* +* FIELDS +* second_byte +* second byte of OUI (located in lower three bytes of GUID). +* +* third_byte +* third byte of OUI (located in lower three bytes of GUID). +* +* guid_mask +* GUID mask that will be used to generate MAC from the GUID. +* +* SEE ALSO +* IPoIB, ipoib_mac_from_guid_mask +*********/ + +extern const ipoib_guid2mac_translation_t guid2mac_table[]; + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/* + * Address accessors + */ + +static inline uint8_t +ipoib_addr_get_flags( + IN const ipoib_hw_addr_t* const p_addr ) +{ + return (uint8_t)( p_addr->flags_qpn & 0x000000ff); +} + +static inline void +ipoib_addr_set_flags( + IN ipoib_hw_addr_t* const p_addr, + IN const uint8_t flags ) +{ + p_addr->flags_qpn &= ( 0xFFFFFF00 ); + p_addr->flags_qpn |= ( flags ); +} + +static inline net32_t +ipoib_addr_get_qpn( + IN const ipoib_hw_addr_t* const p_addr ) +{ + return( ( p_addr->flags_qpn ) & 0xffffff00 ); +} + +static inline void +ipoib_addr_set_qpn( + IN ipoib_hw_addr_t* const p_addr, + IN const net32_t qpn ) +{ + p_addr->flags_qpn &= ( 0x000000FF ); + p_addr->flags_qpn |= qpn ; +} + +static inline void +ipoib_addr_set_sid( + IN net64_t* const p_sid, + IN const net32_t qpn ) +{ + *p_sid = qpn; + *p_sid <<= 32; + *p_sid |= IPOIB_CM_FLAG_SVCID; +} + +/****f* IPOIB/ipoib_mac_from_sst_guid +* NAME +* ipoib_mac_from_sst_guid +* +* DESCRIPTION +* Generates an ethernet MAC address given a SilverStorm port GUID. +* +* SYNOPSIS +*/ +static inline ib_api_status_t +ipoib_mac_from_sst_guid( + IN const net64_t port_guid, + OUT mac_addr_t* const p_mac_addr ) +{ + const uint8_t *p_guid = (const uint8_t*)&port_guid; + uint32_t low24; + + /* Port guid is in network byte order. OUI is in lower 3 bytes. */ + ASSERT( p_guid[0] == 0x00 && p_guid[1] == 0x06 && p_guid[2] == 0x6a ); + + /* + * We end up using only the lower 23-bits of the GUID. Trap that + * the 24th (bit 23) through 27th (bit 26) bit aren't set. + */ + if( port_guid & CL_HTON64( 0x0000000007800000 ) ) + return IB_INVALID_GUID; + + low24 = 0x00FFF000 - + ((((uint32_t)cl_ntoh64( port_guid ) & 0x00FFFFFF) - 0x101) * 2); + low24 -= p_guid[3]; /* minus port number */ + + p_mac_addr->addr[0] = p_guid[0]; + p_mac_addr->addr[1] = p_guid[1]; + p_mac_addr->addr[2] = p_guid[2]; + p_mac_addr->addr[3] = (uint8_t)(low24 >> 16); + p_mac_addr->addr[4] = (uint8_t)(low24 >> 8); + p_mac_addr->addr[5] = (uint8_t)low24; + + return IB_SUCCESS; +} +/* +* PARAMETERS +* port_guid +* The port GUID, in network byte order, for which to generate a +* MAC address. +* +* p_mac_addr +* Pointer to a mac address in which to store the results. +* +* RETURN VALUES +* IB_SUCCESS +* The MAC address was successfully converted. +* +* IB_INVALID_GUID +* The port GUID provided was not a known GUID format. +* +* NOTES +* The algorithm to convert portGuid to MAC address is as per DN0074, and +* assumes a 2 port HCA. +* +* SEE ALSO +* IPOIB +*********/ + + +/****f* IPOIB/ipoib_mac_from_mlx_guid +* NAME +* ipoib_mac_from_mlx_guid +* +* DESCRIPTION +* Generates an ethernet MAC address given a Mellanox port GUID. +* +* SYNOPSIS +*/ +static inline ib_api_status_t +ipoib_mac_from_mlx_guid( + IN const net64_t port_guid, + OUT mac_addr_t* const p_mac_addr ) +{ + const uint8_t *p_guid = (const uint8_t*)&port_guid; + uint32_t low24; + net16_t guid_middle; + + /* Port guid is in network byte order. OUI is in lower 3 bytes. */ + ASSERT( p_guid[0] == 0x00 && p_guid[1] == 0x02 && p_guid[2] == 0xc9 ); + + guid_middle = (net16_t)((port_guid & CL_HTON64( 0x000000ffff000000 )) >>24); + + if (guid_middle == 2) { + p_mac_addr->addr[0] = 0; + } else if (guid_middle == 3) { + p_mac_addr->addr[0] = 2; + } else { + return IB_INVALID_GUID; + } + low24 = ((uint32_t)cl_ntoh64( port_guid ) & 0x00FFFFFF); + + p_mac_addr->addr[1] = p_guid[1]; + p_mac_addr->addr[2] = p_guid[2]; + p_mac_addr->addr[3] = (uint8_t)(low24 >> 16); + p_mac_addr->addr[4] = (uint8_t)(low24 >> 8); + p_mac_addr->addr[5] = (uint8_t)low24; + + return IB_SUCCESS; +} +/* +* PARAMETERS +* port_guid +* The port GUID, in network byte order, for which to generate a +* MAC address. +* +* p_mac_addr +* Pointer to a mac address in which to store the results. +* +* RETURN VALUES +* IB_SUCCESS +* The MAC address was successfully converted. +* +* IB_INVALID_GUID +* The port GUID provided was not a known GUID format. +* +*********/ + + +/****f* IPOIB/ipoib_mac_from_voltaire_guid +* NAME +* ipoib_mac_from_voltaire_guid +* +* DESCRIPTION +* Generates an ethernet MAC address given a Voltaire port GUID. +* +* SYNOPSIS +*/ +static inline ib_api_status_t +ipoib_mac_from_voltaire_guid( + IN const net64_t port_guid, + OUT mac_addr_t* const p_mac_addr ) +{ + const uint8_t *p_guid = (const uint8_t*)&port_guid; + + /* Port guid is in network byte order. OUI is in lower 3 bytes. */ + ASSERT( p_guid[0] == 0x00 && p_guid[1] == 0x08 && p_guid[2] == 0xf1 ); + + p_mac_addr->addr[0] = p_guid[0]; + p_mac_addr->addr[1] = p_guid[1]; + p_mac_addr->addr[2] = p_guid[2]; + p_mac_addr->addr[3] = p_guid[4] ^ p_guid[6]; + p_mac_addr->addr[4] = p_guid[5] ^ p_guid[7]; + p_mac_addr->addr[5] = p_guid[5] + p_guid[6] + p_guid[7]; + + return IB_SUCCESS; +} + + +/****f* IPOIB/ipoib_mac_from_guid_mask +* NAME +* ipoib_mac_from_guid_mask +* +* DESCRIPTION +* Generates an ethernet MAC address given general port GUID and a bitwise mask +* +* SYNOPSIS +*/ +static inline ib_api_status_t +ipoib_mac_from_guid_mask( + IN const uint8_t *p_guid, + IN uint32_t guid_mask, + OUT mac_addr_t* const p_mac_addr ) +{ + static const mac_addr_size = HW_ADDR_LEN; + uint8_t i; + int digit_counter = 0; + + // All non-zero bits of guid_mask indicates the number of an appropriate + // byte in port_guid, that will be used in MAC address construction + for (i = 7; guid_mask; guid_mask >>= 1, --i ) + { + if( guid_mask & 1 ) + { + ++digit_counter; + if( digit_counter > mac_addr_size ) + { + //to avoid negative index + return IB_INVALID_GUID_MASK; + } + p_mac_addr->addr[mac_addr_size - digit_counter] = p_guid [i]; + } + } + + // check for the mask validity: it should have 6 non-zero bits + if( digit_counter != mac_addr_size ) + return IB_INVALID_GUID_MASK; + + return IB_SUCCESS; +} +/* +* PARAMETERS +* port_guid +* The port GUID, in network byte order, for which to generate a +* MAC address. +* +* guid_mask +* Each BIT in the mask indicates whether to include the appropriate BYTE +* to the MAC address. Bit 0 corresponds to the less significant BYTE , i.e. +* highest index in the MAC array +* +* p_mac_addr +* Pointer to a mac address in which to store the results. +* +* RETURN VALUES +* IB_SUCCESS +* The MAC address was successfully converted. +* +* IB_INVALID_GUID +* The port GUID provided was not a known GUID format. +* +* SEE ALSO +* IPOIB +*********/ + + +/****f* IPOIB/ipoib_mac_from_guid +* NAME +* ipoib_mac_from_guid +* +* DESCRIPTION +* Generates an ethernet MAC address given a port GUID. +* +* SYNOPSIS +*/ +static inline ib_api_status_t +ipoib_mac_from_guid( + IN const net64_t port_guid, + IN uint32_t guid_mask, + OUT mac_addr_t* const p_mac_addr + ) +{ + ib_api_status_t status = IB_INVALID_GUID; + const uint8_t *p_guid = (const uint8_t*)&port_guid; + uint32_t laa, idx = 0; + + /* Port guid is in network byte order. OUI is in lower 3 bytes. */ + if( p_guid[0] == 0 ) + { + if( p_guid[1] == 0x02 && p_guid[2] == 0xc9 ) + { + status = ipoib_mac_from_mlx_guid( port_guid, p_mac_addr ); + } + else if( p_guid[1] == 0x08 && p_guid[2] == 0xf1 ) + { + status = ipoib_mac_from_voltaire_guid( port_guid, p_mac_addr ); + } + else if( p_guid[1] == 0x06 && p_guid[2] == 0x6a ) + { + status = ipoib_mac_from_sst_guid( port_guid, p_mac_addr ); + } + else + { + while( guid2mac_table[idx].second_byte != 0x00 || + guid2mac_table[idx].third_byte != 0x00 ) + { + if( p_guid[1] == guid2mac_table[idx].second_byte && + p_guid[2] == guid2mac_table[idx].third_byte ) + { + status = ipoib_mac_from_guid_mask(p_guid, guid2mac_table[idx].guid_mask, + p_mac_addr); + break; + } + ++idx; + } + } + + if( status == IB_SUCCESS ) + return status; + } + + if( guid_mask ) + return ipoib_mac_from_guid_mask( p_guid, guid_mask, p_mac_addr ); + + /* Value of zero is reserved. */ + laa = cl_atomic_inc( &g_ipoib.laa_idx ); + + if( !laa ) + return IB_INVALID_GUID; + + p_mac_addr->addr[0] = 2; /* LAA bit */ + p_mac_addr->addr[1] = 0; + p_mac_addr->addr[2] = (uint8_t)(laa >> 24); + p_mac_addr->addr[3] = (uint8_t)(laa >> 16); + p_mac_addr->addr[4] = (uint8_t)(laa >> 8); + p_mac_addr->addr[5] = (uint8_t)laa; + + return IB_SUCCESS; +} +/* +* PARAMETERS +* port_guid +* The port GUID, in network byte order, for which to generate a +* MAC address. +* +* p_mac_addr +* Pointer to a mac address in which to store the results. +* +* RETURN VALUES +* IB_SUCCESS +* The MAC address was successfully converted. +* +* IB_INVALID_GUID +* The port GUID provided was not a known GUID format. +* +* NOTES +* Creates a locally administered address using a global incrementing counter. +* +* SEE ALSO +* IPOIB +*********/ + + +/****f* IPOIB/ipoib_is_voltaire_router_gid +* NAME +* ipoib_is_voltaire_router_gid +* +* DESCRIPTION +* Checks whether the GID belongs to Voltaire IP router +* +* SYNOPSIS +*/ +boolean_t +static inline +ipoib_is_voltaire_router_gid( + IN const ib_gid_t *p_gid ) +{ + static const uint8_t VOLTAIRE_GUID_PREFIX[] = {0, 0x08, 0xf1, 0, 0x1}; + + return !cl_memcmp( &p_gid->unicast.interface_id, VOLTAIRE_GUID_PREFIX, + sizeof(VOLTAIRE_GUID_PREFIX) ); +} + + +#ifdef __cplusplus +} +#endif + +#endif /* _IPOIB_XFR_MGR_H_ */ diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/makefile b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/makefile new file mode 100644 index 00000000..bffacaa7 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/makefile @@ -0,0 +1,7 @@ +# +# DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source +# file to this component. This file merely indirects to the real make file +# that is shared by all the driver components of the OpenIB Windows project. +# + +!INCLUDE ..\..\..\inc\openib.def diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/makefile.inc b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/makefile.inc new file mode 100644 index 00000000..4f29f500 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/makefile.inc @@ -0,0 +1,17 @@ + +# Transform .inx file to .inf file adding date + major,min & svn.version stamp +# Output .inf file is copied to the $(INF_TARGET) folder (commonly where .sys file resides). + +_LNG=$(LANGUAGE) + +!IF !DEFINED(_INX) +_INX=. +!ENDIF + +STAMP=stampinf -a $(_BUILDARCH) + +!INCLUDE mod_ver.def + +$(INF_TARGET) : $(_INX)\$(INF_NAME).inx + copy $(_INX)\$(@B).inx $@ + $(STAMP) -f $@ -d * -v $(IB_MAJORVERSION).$(IB_MINORVERSION).$(IB_BUILDVERSION).$(OPENIB_REV) diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/netipoib.inx b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/netipoib.inx new file mode 100644 index 00000000..ca3126b5 --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/netipoib.inx @@ -0,0 +1,295 @@ +; OpenFabrics Alliance Internet Protocol over InfiniBand Adapter +; Copyright 2005 SilverStorm Technologies all Rights Reserved. +; Copyright 2006 Mellanox Technologies all Rights Reserved. + +[Version] +Signature = "$Windows NT$" +Class = Net +ClassGUID = {4d36e972-e325-11ce-bfc1-08002be10318} +Provider = %MTL% +DriverVer=06/11/2008,1.0.0000.1207 +CatalogFile=ipoib.cat + +[Manufacturer] +%MTL% = MTL,ntx86,ntamd64,ntia64 + +[ControlFlags] +ExcludeFromSelect = IBA\IPoIB + +[MTL] +; empty since we don't support W9x/Me + +[MTL.ntx86] +%IpoibDesc% = Ipoib.DDInstall, IBA\IPoIB ; Internet Protocol over InfiniBand Adapter +%IpoibDescP% = Ipoib.DDInstall, IBA\IPoIBP ; Internet Protocol over InfiniBand Adapter with partition key + +[MTL.ntamd64] +%IpoibDesc% = Ipoib.DDInstall, IBA\IPoIB ; Internet Protocol over InfiniBand Adapter +%IpoibDescP% = Ipoib.DDInstall, IBA\IPoIBP ; Internet Protocol over InfiniBand Adapter with partition key + +[MTL.ntia64] +%IpoibDesc% = Ipoib.DDInstall, IBA\IPoIB ; Internet Protocol over InfiniBand Adapter +%IpoibDescP% = Ipoib.DDInstall, IBA\IPoIBP ; Internet Protocol over InfiniBand Adapter with partition key + +[Ipoib.DDInstall.ntx86] +Characteristics = 0x81 ; NCF_HAS_UI | NCF_VIRTUAL +AddReg = IpoibAddReg +CopyFiles = IpoibCopyFiles +CopyFiles = WsdCopyFiles +CopyFiles = NdCopyFiles +*IfType = 6 ; IF_TYPE_ETHERNET_CSMACD +*MediaType = 0 ; NdisMedium802_3 +*PhysicalMediaType = 14 ; NdisPhysicalMedium802_3 + +[Ipoib.DDInstall.ntamd64] +Characteristics = 0x81 ; NCF_HAS_UI | NCF_VIRTUAL +AddReg = IpoibAddReg +CopyFiles = IpoibCopyFiles +CopyFiles = WsdCopyFiles +CopyFiles = NdCopyFiles +CopyFiles = WOW64CopyFiles +*IfType = 6 ; IF_TYPE_ETHERNET_CSMACD +*MediaType = 0 ; NdisMedium802_3 +*PhysicalMediaType = 14 ; NdisPhysicalMedium802_3 + +[Ipoib.DDInstall.ntia64] +Characteristics = 0x81 ; NCF_HAS_UI | NCF_VIRTUAL +AddReg = IpoibAddReg +CopyFiles = IpoibCopyFiles +CopyFiles = WsdCopyFiles +CopyFiles = NdCopyFiles +CopyFiles = WOW64CopyFiles +*IfType = 6 ; IF_TYPE_ETHERNET_CSMACD +*MediaType = 0 ; NdisMedium802_3 +*PhysicalMediaType = 14 ; NdisPhysicalMedium802_3 + +[Ipoib.DDInstall.ntx86.Services] +AddService = ipoib, 2, IpoibService, IpoibEventLog + +[Ipoib.DDInstall.ntamd64.Services] +AddService = ipoib, 2, IpoibService, IpoibEventLog + +[Ipoib.DDInstall.ntia64.Services] +AddService = ipoib, 2, IpoibService, IpoibEventLog + +[IpoibAddReg] +HKR, ,RDMACapable, %REG_DWORD%, 1 +HKR, Ndi, Service, 0, "ipoib" +HKR, Ndi\Interfaces, UpperRange, 0, "ndis5" +HKR, Ndi\Interfaces, LowerRange, 0, "ethernet" + +HKR, Ndi\Params\RqDepth, ParamDesc, 0, %RQ_DEPTH_STR% +HKR, Ndi\Params\RqDepth, Type, 0, "dword" +HKR, Ndi\Params\RqDepth, Default, 0, "512" +HKR, Ndi\Params\RqDepth, Optional, 0, "0" +HKR, Ndi\Params\RqDepth, Min, 0, "128" +HKR, Ndi\Params\RqDepth, Max, 0, "1024" +HKR, Ndi\Params\RqDepth, Step, 0, "128" + +HKR, Ndi\Params\RqLowWatermark, ParamDesc, 0, %RQ_WATERMARK_STR% +HKR, Ndi\Params\RqLowWatermark, Type, 0, "dword" +HKR, Ndi\Params\RqLowWatermark, Default, 0, "4" +HKR, Ndi\Params\RqLowWatermark, Optional, 0, "0" +HKR, Ndi\Params\RqLowWatermark, Min, 0, "2" +HKR, Ndi\Params\RqLowWatermark, Max, 0, "8" +HKR, Ndi\Params\RqLowWatermark, Step, 0, "1" + +HKR, Ndi\Params\SqDepth, ParamDesc, 0, %SQ_DEPTH_STR% +HKR, Ndi\Params\SqDepth, Type, 0, "dword" +HKR, Ndi\Params\SqDepth, Default, 0, "512" +HKR, Ndi\Params\SqDepth, Optional, 0, "0" +HKR, Ndi\Params\SqDepth, Min, 0, "128" +HKR, Ndi\Params\SqDepth, Max, 0, "1024" +HKR, Ndi\Params\SqDepth, Step, 0, "128" + +HKR, Ndi\Params\SendChksum, ParamDesc, 0, %SQ_CSUM_STR% +HKR, Ndi\Params\SendChksum, Type, 0, "enum" +HKR, Ndi\Params\SendChksum, Default, 0, "1" +HKR, Ndi\Params\SendChksum, Optional, 0, "0" +HKR, Ndi\Params\SendChksum\enum, "0", 0, %DISABLED_STR% +HKR, Ndi\Params\SendChksum\enum, "1", 0, %ENABLED_IF_STR% +HKR, Ndi\Params\SendChksum\enum, "2", 0, %BYPASS_STR% + +HKR, Ndi\Params\RecvChksum, ParamDesc, 0, %RQ_CSUM_STR% +HKR, Ndi\Params\RecvChksum, Type, 0, "enum" +HKR, Ndi\Params\RecvChksum, Default, 0, "1" +HKR, Ndi\Params\RecvChksum, Optional, 0, "0" +HKR, Ndi\Params\RecvChksum\enum, "0", 0, %DISABLED_STR% +HKR, Ndi\Params\RecvChksum\enum, "1", 0, %ENABLED_IF_STR% +HKR, Ndi\Params\RecvChksum\enum, "2", 0, %BYPASS_STR% + +HKR, Ndi\Params\lso, ParamDesc, 0, %LSO_STR% +HKR, Ndi\Params\lso, Type, 0, "enum" +HKR, Ndi\Params\lso, Default, 0, "0" +HKR, Ndi\Params\lso, Optional, 0, "0" +HKR, Ndi\Params\lso\enum, "0", 0, %DISABLED_STR% +HKR, Ndi\Params\lso\enum, "1", 0, %ENABLED_STR% + + +HKR, Ndi\Params\SaTimeout, ParamDesc, 0, %SA_QUERY_TO_STR% +HKR, Ndi\Params\SaTimeout, Type, 0, "dword" +HKR, Ndi\Params\SaTimeout, Default, 0, "1000" +HKR, Ndi\Params\SaTimeout, Optional, 0, "0" +HKR, Ndi\Params\SaTimeout, Min, 0, "500" +HKR, Ndi\Params\SaTimeout, Step, 0, "250" + +HKR, Ndi\Params\SaRetries, ParamDesc, 0, %SA_QUERY_RETRY_STR% +HKR, Ndi\Params\SaRetries, Type, 0, "dword" +HKR, Ndi\Params\SaRetries, Default, 0, "10" +HKR, Ndi\Params\SaRetries, Optional, 0, "0" +HKR, Ndi\Params\SaRetries, Min, 0, "1" + +HKR, Ndi\Params\RecvRatio, ParamDesc, 0, %RECV_RATIO_STR% +HKR, Ndi\Params\RecvRatio, Type, 0, "dword" +HKR, Ndi\Params\RecvRatio, Default, 0, "1" +HKR, Ndi\Params\RecvRatio, Optional, 0, "0" +HKR, Ndi\Params\RecvRatio, Min, 0, "1" +HKR, Ndi\Params\RecvRatio, Max, 0, "10" + +HKR, Ndi\Params\PayloadMtu, ParamDesc, 0, %MTU_STR% +HKR, Ndi\Params\PayloadMtu, Type, 0, "dword" +HKR, Ndi\Params\PayloadMtu, Default, 0, "2044" +HKR, Ndi\Params\PayloadMtu, Min, 0, "512" +HKR, Ndi\Params\PayloadMtu, Max, 0, "4092" + +HKR, Ndi\Params\MCLeaveRescan, ParamDesc, 0, %MC_RESCAN_STR% +HKR, Ndi\Params\MCLeaveRescan, Type, 0, "dword" +HKR, Ndi\Params\MCLeaveRescan, Default, 0, "260" +HKR, Ndi\Params\MCLeaveRescan, Optional, 0, "0" +HKR, Ndi\Params\MCLeaveRescan, Min, 0, "1" +HKR, Ndi\Params\MCLeaveRescan, Max, 0, "3600" + +HKR, Ndi\Params\GUIDMask, ParamDesc, 0, %GUID_MASK_STR% +HKR, Ndi\Params\GUIDMask, Type, 0, "dword" +HKR, Ndi\Params\GUIDMask, Default, 0, "0" +HKR, Ndi\Params\GUIDMask, Optional, 0, "0" +HKR, Ndi\Params\GUIDMask, Min, 0, "0" +HKR, Ndi\Params\GUIDMask, Max, 0, "252" + +HKR, Ndi\Params\BCJoinRetry, ParamDesc, 0, %BC_JOIN_RETRY_STR% +HKR, Ndi\Params\BCJoinRetry, Type, 0, "dword" +HKR, Ndi\Params\BCJoinRetry, Default, 0, "50" +HKR, Ndi\Params\BCJoinRetry, Optional, 0, "0" +HKR, Ndi\Params\BCJoinRetry, Min, 0, "0" +HKR, Ndi\Params\BCJoinRetry, Max, 0, "1000" + +HKR, Ndi\Params\CmEnabled, ParamDesc, 0, %CONNECTED_MODE_STR% +HKR, Ndi\Params\CmEnabled, Type, 0, "enum" +HKR, Ndi\Params\CmEnabled, Default, 0, "0" +HKR, Ndi\Params\CmEnabled, Optional, 0, "0" +HKR, Ndi\Params\CmEnabled\enum, "0", 0, %DISABLED_STR% +HKR, Ndi\Params\CmEnabled\enum, "1", 0, %ENABLED_STR% + +HKR, Ndi\Params\CmPayloadMtu, ParamDesc, 0, %CONNECTED_MODE_MTU_STR% +HKR, Ndi\Params\CmPayloadMtu, Type, 0, "dword" +HKR, Ndi\Params\CmPayloadMtu, Default, 0, "65520" +HKR, Ndi\Params\CmPayloadMtu, Min, 0, "512" +HKR, Ndi\Params\CmPayloadMtu, Max, 0, "65520" + +[IpoibService] +DisplayName = %IpoibServiceDispName% +ServiceType = 1 ;%SERVICE_KERNEL_DRIVER% +StartType = 3 ;%SERVICE_DEMAND_START% +ErrorControl = 1 ;%SERVICE_ERROR_NORMAL% +ServiceBinary = %12%\ipoib.sys +LoadOrderGroup = NDIS +AddReg = Ipoib.ParamsReg + +[Ipoib.ParamsReg] +HKR,"Parameters","DebugLevel",%REG_DWORD_NO_CLOBBER%,0x00000002 +HKR,"Parameters","DebugFlags",%REG_DWORD_NO_CLOBBER%,0x00000fff +HKR,"Parameters","bypass_check_bcast_rate",%REG_DWORD_NO_CLOBBER%,0x00000000 + +[IpoibEventLog] +AddReg = IpoibAddEventLogReg + +[IpoibAddEventLogReg] +HKR, , EventMessageFile, 0x00020000, "%%SystemRoot%%\System32\netevent.dll;%%SystemRoot%%\System32\drivers\ipoib.sys" +HKR, , TypesSupported, 0x00010001, 7 + + +[IpoibCopyFiles] +ipoib.sys,,,2 + +[WsdCopyFiles] +ibwsd.dll,,,0x00000002 + +[NdCopyFiles] +ibndprov.dll,,,0x00000002 +ndinstall.exe,,,0x00000002 + +[WOW64CopyFiles] +ibwsd.dll,ibwsd32.dll,,0x00000002 +ibndprov.dll,ibndprov32.dll,,0x00000002 + +[SourceDisksNames.x86] +1 = %IcsDisk1%,,,"" + +[SourceDisksNames.amd64] +1 = %IcsDisk1%,,,"" + +[SourceDisksNames.ia64] +1 = %IcsDisk1%,,,"" + +[SourceDisksFiles.x86] +ipoib.sys = 1 +ibwsd.dll = 1 +ibndprov.dll = 1 +ndinstall.exe = 1 + +[SourceDisksFiles.amd64] +ipoib.sys = 1 +ibwsd.dll = 1 +ibwsd32.dll = 1 +ibndprov.dll = 1 +ibndprov32.dll = 1 +ndinstall.exe = 1 + +[SourceDisksFiles.ia64] +ipoib.sys = 1 +ibwsd.dll = 1 +ibwsd32.dll = 1 +ibndprov.dll = 1 +ibndprov32.dll = 1 +ndinstall.exe = 1 + +[DestinationDirs] +IpoibCopyFiles = %DIRID_DRIVERS% +WsdCopyFiles = %DIRID_SYSTEM% +NdCopyFiles = %DIRID_SYSTEM% +WOW64CopyFiles = %DIRID_SYSTEM_X86% +DefaultDestDir = %DIRID_SYSTEM% + +[Strings] +OPENIB = "OpenFabrics Alliance" +MTL = "Mellanox Technologies Ltd." +IpoibDesc = "Mellanox IPoIB Adapter" +IpoibDescP = "Mellanox IPoIB Adapter Partition" +IpoibServiceDispName = "IPoIB" +IcsDisk1 = "Mellanox IPoIB Disk #1" +DIRID_SYSTEM = 11 +DIRID_DRIVERS = 12 +DIRID_SYSTEM_X86 = 16425 +REG_DWORD = 0x00010001 +REG_DWORD_NO_CLOBBER = 0x00010003 + +RQ_DEPTH_STR = "Receive Queue depth" +RQ_WATERMARK_STR = "Receive Queue Low Watermark" +SQ_DEPTH_STR = "Send Queue Depth" +SQ_CSUM_STR = "Send Checksum Offload" +RQ_CSUM_STR = "Recv Checksum Offload" +LSO_STR = "Large Send Offload" +SA_QUERY_TO_STR = "SA Query Timeout (ms)" +SA_QUERY_RETRY_STR = "SA Query Retry Count" +RECV_RATIO_STR = "Receive Pool Ratio" +MTU_STR = "Payload Mtu size" +MC_RESCAN_STR = "MC leave rescan (sec)" +GUID_MASK_STR = "GUID bitwise mask" +BC_JOIN_RETRY_STR = "Number of retries connecting to bc" + +ENABLED_IF_STR = "Enabled (if supported by HW)" +ENABLED_STR = "Enabled" +DISABLED_STR = "Disabled" +BYPASS_STR = "Bypass" +CONNECTED_MODE_STR = "Connected mode" +CONNECTED_MODE_MTU_STR = "Connected Mode Payload Mtu size" \ No newline at end of file diff --git a/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/offload.h b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/offload.h new file mode 100644 index 00000000..de696c6a --- /dev/null +++ b/branches/winverbs/ulp/ipoib_NDIS6_CM/kernel/offload.h @@ -0,0 +1,47 @@ +/*++ + +Copyright (c) 2005-2008 Mellanox Technologies. All rights reserved. + +Module Name: + offload.h + +Abstract: + Task offloading header file + +Revision History: + +Notes: + +--*/ + +// +// Define the maximum size of large TCP packets the driver can offload. +// This sample driver uses shared memory to map the large packets, +// LARGE_SEND_OFFLOAD_SIZE is useless in this case, so we just define +// it as NIC_MAX_PACKET_SIZE. But shipping drivers should define +// LARGE_SEND_OFFLOAD_SIZE if they support LSO, and use it as +// MaximumPhysicalMapping when they call NdisMInitializeScatterGatherDma +// if they use ScatterGather method. If the drivers don't support +// LSO, then MaximumPhysicalMapping is NIC_MAX_PACKET_SIZE. +// + +#define LSO_MAX_HEADER 136 +#define LARGE_SEND_OFFLOAD_SIZE 60000 + +// This struct is being used in order to pass data about the GSO buffers if they +// are present +typedef struct LsoBuffer_ { + PUCHAR pData; + UINT Len; +} LsoBuffer; + +typedef struct LsoData_ { + LsoBuffer LsoBuffers[1]; + UINT UsedBuffers; + UINT FullBuffers; + UINT LsoHeaderSize; + UINT IndexOfData; + UCHAR coppied_data[LSO_MAX_HEADER]; +} LsoData; + + -- 2.41.0