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. // Verified from FT_Prog hex dump (AN_107 / 93C46 EEPROM layout): // Word 0x0A: bits[3:0]=CBUS0, bits[7:4]=CBUS1, bits[11:8]=CBUS2, bits[15:12]=CBUS3 // Word 0x0B: bits[3:0]=CBUS4 // Prior constants EE_WORD_CBUS01=7, EE_WORD_CBUS23=8 were wrong (those are config words). public const uint EE_WORD_CBUS0123 = 10; // 0x0A: all of CBUS0-3 packed into one word public const uint EE_WORD_CBUS4 = 11; // 0x0B: bits[3:0]=CBUS4 [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_GetBitMode")] public static extern int FT_GetBitMode(IntPtr ftHandle, out byte pucMode); [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); [DllImport("$escapedPath", EntryPoint = "FT_EE_Read")] public static extern int FT_EE_Read(IntPtr ftHandle, ref FtProgramData pData); [DllImport("$escapedPath", EntryPoint = "FT_EE_Program")] public static extern int FT_EE_Program(IntPtr ftHandle, ref FtProgramData pData); } // FT_PROGRAM_DATA struct -- full layout through Version 5 (FT232H extensions). // String fields are char* pointers -- callers must allocate and pin buffers before use. // Field layout follows the D2XX Programmer's Guide §4.4 / §4.6 exactly. // All Version 3/4/5 fields zero-initialise automatically; safe to use with Version=5 // even when programming an FT232R (libftd2xx ignores inapplicable fields). [System.Runtime.InteropServices.StructLayout( System.Runtime.InteropServices.LayoutKind.Sequential)] public struct FtProgramData { // Header -- must be set by caller public uint Signature1; // 0x00000000 public uint Signature2; // 0xFFFFFFFF public uint Version; // 2 for FT232R public ushort VendorId; public ushort ProductId; public IntPtr Manufacturer; // char* -- allocate >= 32 bytes public IntPtr ManufacturerId; // char* -- allocate >= 16 bytes public IntPtr Description; // char* -- allocate >= 64 bytes public IntPtr SerialNumber; // char* -- allocate >= 16 bytes public ushort MaxPower; public ushort PnP; public ushort SelfPowered; public ushort RemoteWakeup; // BM extensions public byte Rev4; public byte IsoIn; public byte IsoOut; public byte PullDownEnable; public byte SerNumEnable; public byte USBVersionEnable; public ushort USBVersion; // FT2232 extensions (Version >= 1) public byte Rev5; public byte IsoInA; public byte IsoInB; public byte IsoOutA; public byte IsoOutB; public byte PullDownEnable5; public byte SerNumEnable5; public byte USBVersionEnable5; public ushort USBVersion5; public byte AIsHighCurrent; public byte BIsHighCurrent; public byte IFAIsFifo; public byte IFAIsFifoTar; public byte IFAIsFastSer; public byte AIsVCP; public byte IFBIsFifo; public byte IFBIsFifoTar; public byte IFBIsFastSer; public byte BIsVCP; // FT232R extensions (Version >= 2) public byte UseExtOsc; public byte HighDriveIOs; public byte EndpointSize; public byte PullDownEnableR; public byte SerNumEnableR; public byte InvertTXD; public byte InvertRXD; public byte InvertRTS; public byte InvertCTS; public byte InvertDTR; public byte InvertDSR; public byte InvertDCD; public byte InvertRI; public byte Cbus0; public byte Cbus1; public byte Cbus2; public byte Cbus3; public byte Cbus4; public byte RIsD2XX; // Rev 7 (FT2232H) Extensions [Version >= 3] public byte PullDownEnable7; public byte SerNumEnable7; public byte ALSlowSlew; public byte ALSchmittInput; public byte ALDriveCurrent; public byte AHSlowSlew; public byte AHSchmittInput; public byte AHDriveCurrent; public byte BLSlowSlew; public byte BLSchmittInput; public byte BLDriveCurrent; public byte BHSlowSlew; public byte BHSchmittInput; public byte BHDriveCurrent; public byte IFAIsFifo7; public byte IFAIsFifoTar7; public byte IFAIsFastSer7; public byte AIsVCP7; public byte IFBIsFifo7; public byte IFBIsFifoTar7; public byte IFBIsFastSer7; public byte BIsVCP7; public byte PowerSaveEnable; // Rev 8 (FT4232H) Extensions [Version >= 4] public byte PullDownEnable8; public byte SerNumEnable8; public byte ASlowSlew; public byte ASchmittInput; public byte ADriveCurrent; public byte BSlowSlew; public byte BSchmittInput; public byte BDriveCurrent; public byte CSlowSlew; public byte CSchmittInput; public byte CDriveCurrent; public byte DSlowSlew; public byte DSchmittInput; public byte DDriveCurrent; public byte ARIIsTXDEN; public byte BRIIsTXDEN; public byte CRIIsTXDEN; public byte DRIIsTXDEN; public byte AIsVCP8; public byte BIsVCP8; public byte CIsVCP8; public byte DIsVCP8; // Rev 9 (FT232H) Extensions [Version >= 5] public byte PullDownEnableH; public byte SerNumEnableH; public byte ACSlowSlewH; public byte ACSchmittInputH; public byte ACDriveCurrentH; public byte ADSlowSlewH; public byte ADSchmittInputH; public byte ADDriveCurrentH; public byte Cbus0H; public byte Cbus1H; public byte Cbus2H; public byte Cbus3H; public byte Cbus4H; public byte Cbus5H; public byte Cbus6H; public byte Cbus7H; public byte Cbus8H; public byte Cbus9H; public byte IsFifoH; public byte IsFifoTarH; public byte IsFastSerH; public byte IsFT1248H; public byte FT1248CpolH; public byte FT1248LsbH; public byte FT1248FlowControlH; public byte IsVCPH; public byte PowerSaveEnableH; } "@ 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-FtdiNativeGetBitMode { <# .SYNOPSIS Reads the instantaneous pin state byte via FT_GetBitMode. For CBUS mode (0x20), bits 0-3 = current logic level of CBUS0-CBUS3. #> [CmdletBinding()] [OutputType([byte])] param( [Parameter(Mandatory = $true)] [IntPtr]$Handle ) [byte]$mode = 0 $status = [FtdiNative]::FT_GetBitMode($Handle, [ref]$mode) if ($status -ne [FtdiNative]::FT_OK) { throw "Invoke-FtdiNativeGetBitMode: FT_GetBitMode returned status $status" } Write-Verbose ("Invoke-FtdiNativeGetBitMode: pinState=0x{0:X2}" -f $mode) return $mode } 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-FtdiNativeFt232rEeprom { <# .SYNOPSIS Reads FT232R EEPROM fields via native P/Invoke (macOS/Linux). .DESCRIPTION Returns a PSCustomObject matching the shape of Get-FtdiFt232rEeprom. Uses FT_EE_Read with the full FtProgramData struct so all fields including string descriptors (Manufacturer, Description, SerialNumber) are populated. The device must not be open (no active New-PsGadgetFtdi connection) when this is called -- FT_Open does not allow a second handle on the same device. .PARAMETER Index Zero-based device index (from Get-FtdiDevice). .OUTPUTS PSCustomObject with EEPROM fields, or throws on P/Invoke error. #> [CmdletBinding()] [OutputType([System.Object])] param( [Parameter(Mandatory = $true)] [int]$Index ) if (-not $script:FtdiNativeAvailable) { throw "FtdiNative not initialised. Call Initialize-FtdiNative first." } $handle = Invoke-FtdiNativeOpen -Index $Index try { $manufBuf = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(32) $manufIdBuf = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(16) $descBuf = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(64) $serialBuf = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(16) try { $data = [FtProgramData]::new() $data.Signature1 = [uint32]0x00000000 $data.Signature2 = [uint32]4294967295 # 0xFFFFFFFF $data.Version = [uint32]5 $data.Manufacturer = $manufBuf $data.ManufacturerId = $manufIdBuf $data.Description = $descBuf $data.SerialNumber = $serialBuf $status = [FtdiNative]::FT_EE_Read($handle, [ref]$data) if ($status -ne [FtdiNative]::FT_OK) { throw "FT_EE_Read failed: status=$status" } $resolveCbus = { param([int]$v) if ($script:FT_CBUS_NAMES.ContainsKey($v)) { $script:FT_CBUS_NAMES[$v] } else { "UNKNOWN($v)" } } return [PSCustomObject]@{ VendorID = '0x{0:X4}' -f $data.VendorId ProductID = '0x{0:X4}' -f $data.ProductId Manufacturer = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($data.Manufacturer) ManufacturerID = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($data.ManufacturerId) Description = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($data.Description) SerialNumber = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($data.SerialNumber) MaxPower = $data.MaxPower SelfPowered = [bool]$data.SelfPowered RemoteWakeup = [bool]$data.RemoteWakeup UseExtOsc = [bool]$data.UseExtOsc HighDriveIOs = [bool]$data.HighDriveIOs EndpointSize = $data.EndpointSize PullDownEnable = [bool]$data.PullDownEnableR SerNumEnable = [bool]$data.SerNumEnableR InvertTXD = [bool]$data.InvertTXD InvertRXD = [bool]$data.InvertRXD InvertRTS = [bool]$data.InvertRTS InvertCTS = [bool]$data.InvertCTS InvertDTR = [bool]$data.InvertDTR InvertDSR = [bool]$data.InvertDSR InvertDCD = [bool]$data.InvertDCD InvertRI = [bool]$data.InvertRI Cbus0 = & $resolveCbus $data.Cbus0 Cbus1 = & $resolveCbus $data.Cbus1 Cbus2 = & $resolveCbus $data.Cbus2 Cbus3 = & $resolveCbus $data.Cbus3 Cbus4 = & $resolveCbus $data.Cbus4 RIsD2XX = [bool]$data.RIsD2XX _NativeRead = $true } } finally { [System.Runtime.InteropServices.Marshal]::FreeHGlobal($manufBuf) [System.Runtime.InteropServices.Marshal]::FreeHGlobal($manufIdBuf) [System.Runtime.InteropServices.Marshal]::FreeHGlobal($descBuf) [System.Runtime.InteropServices.Marshal]::FreeHGlobal($serialBuf) } } finally { Invoke-FtdiNativeClose -Handle $handle } } function Get-FtdiNativeFt232hEeprom { <# .SYNOPSIS Reads FT232H EEPROM fields via native P/Invoke (macOS/Linux). .DESCRIPTION Uses FT_EE_Read with the full FtProgramData struct (Version 5) to populate all FT232H fields including string descriptors, drive settings, and CBUS pin modes. Returns a PSCustomObject matching the shape of Get-FtdiFt232hEeprom. The device must not be open (no active New-PsGadgetFtdi connection) when this is called -- FT_Open does not allow a second handle on the same device. .PARAMETER Index Zero-based device index (from Get-FtdiDevice). .OUTPUTS PSCustomObject with EEPROM fields, or throws on P/Invoke error. #> [CmdletBinding()] [OutputType([System.Object])] param( [Parameter(Mandatory = $true)] [int]$Index ) if (-not $script:FtdiNativeAvailable) { throw "FtdiNative not initialised. Call Initialize-FtdiNative first." } $handle = Invoke-FtdiNativeOpen -Index $Index try { $manufBuf = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(32) $manufIdBuf = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(16) $descBuf = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(64) $serialBuf = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(16) try { $data = [FtProgramData]::new() $data.Signature1 = [uint32]0x00000000 $data.Signature2 = [uint32]4294967295 # 0xFFFFFFFF $data.Version = [uint32]5 $data.Manufacturer = $manufBuf $data.ManufacturerId = $manufIdBuf $data.Description = $descBuf $data.SerialNumber = $serialBuf $status = [FtdiNative]::FT_EE_Read($handle, [ref]$data) if ($status -ne [FtdiNative]::FT_OK) { throw "FT_EE_Read failed: status=$status" } $resolveCbus = { param([int]$v) if ($script:FT_232H_CBUS_NAMES.ContainsKey($v)) { $script:FT_232H_CBUS_NAMES[$v] } else { "UNKNOWN($v)" } } return [PSCustomObject]@{ VendorID = '0x{0:X4}' -f $data.VendorId ProductID = '0x{0:X4}' -f $data.ProductId Manufacturer = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($data.Manufacturer) ManufacturerID = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($data.ManufacturerId) Description = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($data.Description) SerialNumber = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($data.SerialNumber) MaxPower = $data.MaxPower SelfPowered = [bool]$data.SelfPowered RemoteWakeup = [bool]$data.RemoteWakeup PullDownEnable = [bool]$data.PullDownEnableH SerNumEnable = [bool]$data.SerNumEnableH ACSlowSlew = [bool]$data.ACSlowSlewH ACSchmittInput = [bool]$data.ACSchmittInputH ACDriveCurrent = $data.ACDriveCurrentH ADSlowSlew = [bool]$data.ADSlowSlewH ADSchmittInput = [bool]$data.ADSchmittInputH ADDriveCurrent = $data.ADDriveCurrentH Cbus0 = & $resolveCbus $data.Cbus0H Cbus1 = & $resolveCbus $data.Cbus1H Cbus2 = & $resolveCbus $data.Cbus2H Cbus3 = & $resolveCbus $data.Cbus3H Cbus4 = & $resolveCbus $data.Cbus4H Cbus5 = & $resolveCbus $data.Cbus5H Cbus6 = & $resolveCbus $data.Cbus6H Cbus7 = & $resolveCbus $data.Cbus7H Cbus8 = & $resolveCbus $data.Cbus8H Cbus9 = & $resolveCbus $data.Cbus9H IsFifo = [bool]$data.IsFifoH IsFifoTar = [bool]$data.IsFifoTarH IsFastSer = [bool]$data.IsFastSerH IsFT1248 = [bool]$data.IsFT1248H FT1248Cpol = [bool]$data.FT1248CpolH FT1248Lsb = [bool]$data.FT1248LsbH FT1248FlowControl = [bool]$data.FT1248FlowControlH IsVCP = [bool]$data.IsVCPH PowerSaveEnable = [bool]$data.PowerSaveEnableH _NativeRead = $true } } finally { [System.Runtime.InteropServices.Marshal]::FreeHGlobal($manufBuf) [System.Runtime.InteropServices.Marshal]::FreeHGlobal($manufIdBuf) [System.Runtime.InteropServices.Marshal]::FreeHGlobal($descBuf) [System.Runtime.InteropServices.Marshal]::FreeHGlobal($serialBuf) } } finally { Invoke-FtdiNativeClose -Handle $handle } } 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 { $wordCbus0123 = Invoke-FtdiNativeReadEE -Handle $handle -WordOffset ([FtdiNative]::EE_WORD_CBUS0123) $wordCbus4 = Invoke-FtdiNativeReadEE -Handle $handle -WordOffset ([FtdiNative]::EE_WORD_CBUS4) $cbus0 = $wordCbus0123 -band 0x000F $cbus1 = ($wordCbus0123 -shr 4) -band 0x000F $cbus2 = ($wordCbus0123 -shr 8) -band 0x000F $cbus3 = ($wordCbus0123 -shr 12) -band 0x000F $cbus4 = $wordCbus4 -band 0x000F $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 Cbus4 = & $nameOf $cbus4 Cbus0Byte = [byte]$cbus0 Cbus1Byte = [byte]$cbus1 Cbus2Byte = [byte]$cbus2 Cbus3Byte = [byte]$cbus3 Cbus4Byte = [byte]$cbus4 } } 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 { # Use FT_EE_Read / FT_EE_Program -- the official D2XX EEPROM programming API. # FT_WriteEE only buffers words in libftd2xx RAM; it does not flush to the # physical chip on the FT232R internal EEPROM. FT_EE_Program does a full # atomic write-verify cycle that commits to the physical EEPROM. # Allocate string buffers for FT_EE_Read output (sizes per D2XX guide §4.4) $manufBuf = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(32) $manufIdBuf = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(16) $descBuf = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(64) $serialBuf = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(16) try { $data = [FtProgramData]::new() $data.Signature1 = [uint32]0x00000000 $data.Signature2 = [uint32]4294967295 # 0xFFFFFFFF $data.Version = [uint32]5 # Version 5 = full struct through FT232H (Rev9) $data.Manufacturer = $manufBuf $data.ManufacturerId = $manufIdBuf $data.Description = $descBuf $data.SerialNumber = $serialBuf $status = [FtdiNative]::FT_EE_Read($handle, [ref]$data) if ($status -ne [FtdiNative]::FT_OK) { throw "FT_EE_Read failed: status=$status" } Write-Verbose ("FT_EE_Read: Cbus0={0} Cbus1={1} Cbus2={2} Cbus3={3} Cbus4={4}" -f $data.Cbus0, $data.Cbus1, $data.Cbus2, $data.Cbus3, $data.Cbus4) # Patch only the requested CBUS pins foreach ($pin in $Pins) { switch ($pin) { 0 { $data.Cbus0 = $modeByte } 1 { $data.Cbus1 = $modeByte } 2 { $data.Cbus2 = $modeByte } 3 { $data.Cbus3 = $modeByte } 4 { $data.Cbus4 = $modeByte } } } $status = [FtdiNative]::FT_EE_Program($handle, [ref]$data) if ($status -ne [FtdiNative]::FT_OK) { throw "FT_EE_Program failed: status=$status" } Write-Verbose ("FT_EE_Program: Cbus0={0} Cbus1={1} Cbus2={2} Cbus3={3} Cbus4={4}" -f $data.Cbus0, $data.Cbus1, $data.Cbus2, $data.Cbus3, $data.Cbus4) } finally { [System.Runtime.InteropServices.Marshal]::FreeHGlobal($manufBuf) [System.Runtime.InteropServices.Marshal]::FreeHGlobal($manufIdBuf) [System.Runtime.InteropServices.Marshal]::FreeHGlobal($descBuf) [System.Runtime.InteropServices.Marshal]::FreeHGlobal($serialBuf) } Write-Host "EEPROM updated. Unplug and replug the device for the new CBUS mode to take effect." return $true } finally { Invoke-FtdiNativeClose -Handle $handle } } |