Private/Ftdi.PInvoke.ps1
|
# Ftdi.PInvoke.ps1 # Native P/Invoke wrappers for libftd2xx.so (Linux/macOS). # # When $script:FtdiNativeAvailable is $true (set by Initialize-FtdiNative), # these wrappers let any code in the module call FT_Open / FT_Close / # FT_SetBitMode / FT_ReadEE / FT_WriteEE directly against the native library # without needing FTD2XX_NET.dll (which is Windows-only managed code). # # Usage (called from Initialize-FtdiAssembly after NativeLibrary.Load succeeds): # Initialize-FtdiNative -LibraryPath '/path/to/libftd2xx.so' # # Then any module function can call: # $handle = Invoke-FtdiNativeOpen -Index 0 # Invoke-FtdiNativeSetBitMode -Handle $handle -Mask 0x11 -Mode 0x20 # Invoke-FtdiNativeClose -Handle $handle #Requires -Version 5.1 $script:FtdiNativeTypeDefined = $false $script:FtdiNativeAvailable = $false $script:FtdiNativeLibPath = '' function Initialize-FtdiNative { <# .SYNOPSIS Registers C# P/Invoke declarations for the native libftd2xx.so. .DESCRIPTION Calls Add-Type to define the [FtdiNative] class with DllImport attributes pointing at the supplied absolute library path. Safe to call multiple times- returns immediately (success) if the type is already defined. Sets $script:FtdiNativeAvailable = $true on success. .PARAMETER LibraryPath Absolute path to libftd2xx.so (or libftd2xx.so.x.y.z). #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory = $true)] [string]$LibraryPath ) # Already defined in this session if ($script:FtdiNativeTypeDefined) { $script:FtdiNativeAvailable = $true return $true } # Already registered under a different call (type exists in AppDomain) if ('FtdiNative' -as [type]) { $script:FtdiNativeTypeDefined = $true $script:FtdiNativeAvailable = $true $script:FtdiNativeLibPath = $LibraryPath Write-Verbose "FtdiNative type already registered in AppDomain" return $true } if (-not [System.IO.File]::Exists($LibraryPath)) { Write-Warning "Initialize-FtdiNative: library not found at '$LibraryPath'" return $false } # Escape backslashes for the C# string literal (Linux paths never have them, # but be safe in case this runs on Windows with a UNC path). $escapedPath = $LibraryPath.Replace('\', '\\') $csharp = @" using System; using System.Runtime.InteropServices; public static class FtdiNative { // FT_STATUS values public const int FT_OK = 0; public const int FT_INVALID_HANDLE = 1; public const int FT_DEVICE_NOT_FOUND = 2; public const int FT_DEVICE_NOT_OPENED = 3; public const int FT_IO_ERROR = 4; public const int FT_INSUFFICIENT_RESOURCES = 5; public const int FT_INVALID_PARAMETER = 6; public const int FT_OTHER_ERROR = 7; // SetBitMode modes public const byte MODE_RESET = 0x00; public const byte MODE_BITBANG = 0x01; public const byte MODE_MPSSE = 0x02; public const byte MODE_SYNC_BITBANG = 0x04; public const byte MODE_CBUS_BITBANG = 0x20; public const byte MODE_FAST_SERIAL = 0x40; public const byte MODE_SYNC_245 = 0x40; // CBUS EEPROM option codes (FT_CBUS_OPTIONS enum) public const byte CBUS_TXDEN = 0; public const byte CBUS_PWREN = 1; public const byte CBUS_RXLED = 2; public const byte CBUS_TXLED = 3; public const byte CBUS_TXRXLED = 4; public const byte CBUS_SLEEP = 5; public const byte CBUS_CLK48 = 6; public const byte CBUS_CLK24 = 7; public const byte CBUS_CLK12 = 8; public const byte CBUS_CLK6 = 9; public const byte CBUS_IOMODE = 10; public const byte CBUS_BITBANG_WR = 11; public const byte CBUS_BITBANG_RD = 12; // FT232R EEPROM word addresses for CBUS pin mode public const uint EE_WORD_CBUS01 = 7; // bits 3:0 = CBUS0, bits 7:4 = CBUS1 public const uint EE_WORD_CBUS23 = 8; // bits 3:0 = CBUS2, bits 7:4 = CBUS3 [DllImport("$escapedPath", EntryPoint = "FT_Open")] public static extern int FT_Open(int deviceNumber, out IntPtr pHandle); [DllImport("$escapedPath", EntryPoint = "FT_Close")] public static extern int FT_Close(IntPtr ftHandle); [DllImport("$escapedPath", EntryPoint = "FT_SetBitMode")] public static extern int FT_SetBitMode(IntPtr ftHandle, byte ucMask, byte ucEnable); [DllImport("$escapedPath", EntryPoint = "FT_ReadEE")] public static extern int FT_ReadEE(IntPtr ftHandle, uint dwWordOffset, out ushort lpwValue); [DllImport("$escapedPath", EntryPoint = "FT_WriteEE")] public static extern int FT_WriteEE(IntPtr ftHandle, uint dwWordOffset, ushort wValue); [DllImport("$escapedPath", EntryPoint = "FT_EE_UASize")] public static extern int FT_EE_UASize(IntPtr ftHandle, out uint lpdwSize); [DllImport("$escapedPath", EntryPoint = "FT_GetDeviceInfo")] public static extern int FT_GetDeviceInfo( IntPtr ftHandle, out int lpftDevice, out uint lpdwID, byte[] pcSerialNumber, byte[] pcDescription, IntPtr pvDummy); } "@ try { Add-Type -TypeDefinition $csharp -ErrorAction Stop $script:FtdiNativeTypeDefined = $true $script:FtdiNativeAvailable = $true $script:FtdiNativeLibPath = $LibraryPath Write-Verbose "FtdiNative P/Invoke type registered (lib: $LibraryPath)" return $true } catch { Write-Warning "Initialize-FtdiNative: Add-Type failed: $_" return $false } } # --------------------------------------------------------------------------- # Wrapper helpers (thin PowerShell wrappers around the static C# methods) # --------------------------------------------------------------------------- function Invoke-FtdiNativeOpen { <# .SYNOPSIS Opens an FTDI device by zero-based index using the native D2XX library. Returns an IntPtr handle, or IntPtr.Zero on failure. #> [CmdletBinding()] [OutputType([IntPtr])] param( [Parameter(Mandatory = $true)] [int]$Index ) if (-not $script:FtdiNativeAvailable) { throw "FtdiNative not initialised. Call Initialize-FtdiNative first." } $handle = [IntPtr]::Zero $status = [FtdiNative]::FT_Open($Index, [ref]$handle) if ($status -ne [FtdiNative]::FT_OK) { $msg = switch ($status) { ([FtdiNative]::FT_DEVICE_NOT_FOUND) { "Device not found (index $Index)" } ([FtdiNative]::FT_DEVICE_NOT_OPENED) { "Device could not be opened (already in use?)" } default { "FT_Open returned status $status" } } throw "Invoke-FtdiNativeOpen: $msg" } Write-Verbose "Invoke-FtdiNativeOpen: device $Index opened, handle=0x$('{0:X}' -f $handle.ToInt64())" return $handle } function Invoke-FtdiNativeClose { <# .SYNOPSIS Closes a native D2XX device handle. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [IntPtr]$Handle ) if ($Handle -eq [IntPtr]::Zero) { return } $status = [FtdiNative]::FT_Close($Handle) if ($status -ne [FtdiNative]::FT_OK) { Write-Warning "Invoke-FtdiNativeClose: FT_Close returned $status" } else { Write-Verbose "Invoke-FtdiNativeClose: handle closed" } } function Invoke-FtdiNativeSetBitMode { <# .SYNOPSIS Calls FT_SetBitMode on an open native handle. Returns $true on success, throws on error. #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory = $true)] [IntPtr]$Handle, [Parameter(Mandatory = $true)] [byte]$Mask, [Parameter(Mandatory = $true)] [byte]$Mode ) $status = [FtdiNative]::FT_SetBitMode($Handle, $Mask, $Mode) if ($status -ne [FtdiNative]::FT_OK) { $desc = switch ($status) { ([FtdiNative]::FT_OTHER_ERROR) { "FT_OTHER_ERROR - CBUS pins may not be programmed as FT_CBUS_IOMODE in the " + "device EEPROM. Run: Set-PsGadgetFt232rCbusMode -Index <n> first, then replug." } default { "FT_SetBitMode returned status $status" } } throw "Invoke-FtdiNativeSetBitMode: $desc" } Write-Verbose ("Invoke-FtdiNativeSetBitMode: mode=0x{0:X2} mask=0x{1:X2} OK" -f $Mode, $Mask) return $true } function Invoke-FtdiNativeReadEE { <# .SYNOPSIS Reads a single 16-bit word from the device EEPROM at the given word offset. Returns the word value as [ushort]. #> [CmdletBinding()] [OutputType([ushort])] param( [Parameter(Mandatory = $true)] [IntPtr]$Handle, [Parameter(Mandatory = $true)] [uint]$WordOffset ) [ushort]$value = 0 $status = [FtdiNative]::FT_ReadEE($Handle, $WordOffset, [ref]$value) if ($status -ne [FtdiNative]::FT_OK) { throw "Invoke-FtdiNativeReadEE: FT_ReadEE(offset=$WordOffset) returned status $status" } Write-Verbose ("Invoke-FtdiNativeReadEE: word[{0}] = 0x{1:X4}" -f $WordOffset, $value) return $value } function Invoke-FtdiNativeWriteEE { <# .SYNOPSIS Writes a 16-bit word to the device EEPROM at the given word offset. CAUTION: EEPROM writes are persistent across power cycles. Verify values offline before writing. #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory = $true)] [IntPtr]$Handle, [Parameter(Mandatory = $true)] [uint]$WordOffset, [Parameter(Mandatory = $true)] [ushort]$Value ) $status = [FtdiNative]::FT_WriteEE($Handle, $WordOffset, $Value) if ($status -ne [FtdiNative]::FT_OK) { throw "Invoke-FtdiNativeWriteEE: FT_WriteEE(offset=$WordOffset, value=0x$('{0:X4}' -f $Value)) returned status $status" } Write-Verbose ("Invoke-FtdiNativeWriteEE: word[{0}] written 0x{1:X4}" -f $WordOffset, $Value) return $true } function Get-FtdiNativeCbusEepromInfo { <# .SYNOPSIS Reads the FT232R EEPROM CBUS pin configuration using native P/Invoke. Returns a PSCustomObject with Cbus0..Cbus3 mode names (e.g. FT_CBUS_IOMODE). #> [CmdletBinding()] [OutputType([System.Object])] param( [Parameter(Mandatory = $true)] [int]$Index ) if (-not $script:FtdiNativeAvailable) { throw "FtdiNative not initialised." } $handle = Invoke-FtdiNativeOpen -Index $Index try { $word7 = Invoke-FtdiNativeReadEE -Handle $handle -WordOffset ([FtdiNative]::EE_WORD_CBUS01) $word8 = Invoke-FtdiNativeReadEE -Handle $handle -WordOffset ([FtdiNative]::EE_WORD_CBUS23) $cbus0 = $word7 -band 0x0F $cbus1 = ($word7 -shr 4) -band 0x0F $cbus2 = $word8 -band 0x0F $cbus3 = ($word8 -shr 4) -band 0x0F $nameOf = { param([int]$v) if ($script:FT_CBUS_NAMES.ContainsKey($v)) { $script:FT_CBUS_NAMES[$v] } else { "UNKNOWN_$v" } } return [PSCustomObject]@{ Cbus0 = & $nameOf $cbus0 Cbus1 = & $nameOf $cbus1 Cbus2 = & $nameOf $cbus2 Cbus3 = & $nameOf $cbus3 Cbus0Byte = [byte]$cbus0 Cbus1Byte = [byte]$cbus1 Cbus2Byte = [byte]$cbus2 Cbus3Byte = [byte]$cbus3 } } finally { Invoke-FtdiNativeClose -Handle $handle } } function Set-FtdiNativeCbusEeprom { <# .SYNOPSIS Programs FT232R EEPROM CBUS pin modes using native P/Invoke. Pins not listed keep their current EEPROM value. .DESCRIPTION Reads the current EEPROM words for the CBUS pins, patches in the new values for the requested pins, and writes back. Only modified words are written. CAUTION: This directly alters non-volatile EEPROM. The device must be unplugged and replugged for the new modes to take effect. .PARAMETER Index Zero-based device index. .PARAMETER Pins Which CBUS pin numbers to reconfigure (0-3). .PARAMETER Mode EEPROM mode name or byte value. Use 'FT_CBUS_IOMODE' (10) to enable GPIO. #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory = $true)] [int]$Index, [Parameter(Mandatory = $true)] [ValidateRange(0, 3)] [int[]]$Pins, [Parameter(Mandatory = $false)] [string]$Mode = 'FT_CBUS_IOMODE' ) if (-not $script:FtdiNativeAvailable) { throw "FtdiNative not initialised." } # Resolve mode byte if ($script:FT_CBUS_VALUES.ContainsKey($Mode)) { [byte]$modeByte = $script:FT_CBUS_VALUES[$Mode] } elseif ([byte]::TryParse($Mode, [ref]([byte]0))) { [byte]$modeByte = [byte]$Mode } else { throw "Unknown CBUS mode '$Mode'. Valid names: $($script:FT_CBUS_VALUES.Keys -join ', ')" } $handle = Invoke-FtdiNativeOpen -Index $Index try { # Read current EEPROM words [ushort]$word7 = Invoke-FtdiNativeReadEE -Handle $handle -WordOffset ([FtdiNative]::EE_WORD_CBUS01) [ushort]$word8 = Invoke-FtdiNativeReadEE -Handle $handle -WordOffset ([FtdiNative]::EE_WORD_CBUS23) $origWord7 = $word7 $origWord8 = $word8 foreach ($pin in $Pins) { switch ($pin) { 0 { $word7 = [ushort](($word7 -band 0xFFF0) -bor ($modeByte -band 0x0F)) } 1 { $word7 = [ushort](($word7 -band 0xFF0F) -bor (($modeByte -band 0x0F) -shl 4)) } 2 { $word8 = [ushort](($word8 -band 0xFFF0) -bor ($modeByte -band 0x0F)) } 3 { $word8 = [ushort](($word8 -band 0xFF0F) -bor (($modeByte -band 0x0F) -shl 4)) } } } if ($word7 -ne $origWord7) { Invoke-FtdiNativeWriteEE -Handle $handle -WordOffset ([FtdiNative]::EE_WORD_CBUS01) -Value $word7 Write-Verbose ("EEPROM word7: 0x{0:X4} -> 0x{1:X4}" -f $origWord7, $word7) } if ($word8 -ne $origWord8) { Invoke-FtdiNativeWriteEE -Handle $handle -WordOffset ([FtdiNative]::EE_WORD_CBUS23) -Value $word8 Write-Verbose ("EEPROM word8: 0x{0:X4} -> 0x{1:X4}" -f $origWord8, $word8) } Write-Host "EEPROM updated. Unplug and replug the device for the new CBUS mode to take effect." return $true } finally { Invoke-FtdiNativeClose -Handle $handle } } |