From 52d2c9f2ba43b17e5decd9fb5bfc35fc4a468dcf Mon Sep 17 00:00:00 2001 From: ufrisk Date: Thu, 12 Oct 2017 11:05:25 +0200 Subject: [PATCH] Version 2.3 - FPGA support. --- pcileech/Android.mk | 2 +- pcileech/Makefile | 2 +- pcileech/device.c | 102 +++-- pcileech/device.h | 33 ++ pcileech/device3380.c | 44 +- pcileech/device3380.h | 26 -- pcileech/device605.h | 59 --- pcileech/device605_601.c | 488 +++++++++++++++++++++ pcileech/device605_601.h | 17 + pcileech/{device605.c => device605_uart.c} | 268 +++++------ pcileech/device605_uart.h | 17 + pcileech/executor.c | 6 +- pcileech/extra.c | 14 +- pcileech/extra.h | 6 + pcileech/help.c | 36 +- pcileech/kmd.c | 32 +- pcileech/memdump.c | 4 +- pcileech/oscompatibility.c | 21 + pcileech/oscompatibility.h | 4 + pcileech/pcileech.c | 67 +-- pcileech/pcileech.h | 34 +- pcileech/pcileech.vcxproj | 6 +- pcileech/pcileech.vcxproj.filters | 18 +- pcileech/statistics.c | 2 +- pcileech/statistics.h | 2 +- pcileech/tlp.c | 63 ++- pcileech/tlp.h | 30 +- pcileech/util.c | 25 +- pcileech/util.h | 7 + pcileech/vfs.c | 2 +- pcileech_files/pcileech | Bin 146752 -> 151224 bytes pcileech_files/pcileech.exe | Bin 288256 -> 299520 bytes readme.md | 36 +- 33 files changed, 1066 insertions(+), 407 deletions(-) delete mode 100644 pcileech/device605.h create mode 100644 pcileech/device605_601.c create mode 100644 pcileech/device605_601.h rename pcileech/{device605.c => device605_uart.c} (63%) create mode 100644 pcileech/device605_uart.h diff --git a/pcileech/Android.mk b/pcileech/Android.mk index 41d47ab..71afc67 100644 --- a/pcileech/Android.mk +++ b/pcileech/Android.mk @@ -14,7 +14,7 @@ LOCAL_CFLAGS := -D ANDROID LOCAL_LDLIBS := -L$(LOCAL_PATH)/lib -llog -g LOCAL_C_INCLUDES := bionic -LOCAL_SRC_FILES:= pcileech.c oscompatibility.c device.c device3380.c device605.c executor.c extra.c help.c kmd.c memdump.c mempatch.c statistics.c tlp.c util.c vfs.c +LOCAL_SRC_FILES:= pcileech.c oscompatibility.c device.c device3380.c device605_uart.c device605_601.c executor.c extra.c help.c kmd.c memdump.c mempatch.c statistics.c tlp.c util.c vfs.c LOCAL_MODULE := pcileech LOCAL_SHARED_LIBRARIES += libusb1.0 diff --git a/pcileech/Makefile b/pcileech/Makefile index 103eac7..b0aef7d 100644 --- a/pcileech/Makefile +++ b/pcileech/Makefile @@ -1,7 +1,7 @@ CC=gcc CFLAGS=-I. -D LINUX -pthread `pkg-config libusb-1.0 --libs --cflags` DEPS = pcileech.h -OBJ = pcileech oscompatibility.o pcileech.o device.o device3380.o device605.o executor.o extra.o help.o kmd.o memdump.o mempatch.o statistics.o tlp.o util.o vfs.o +OBJ = pcileech oscompatibility.o pcileech.o device.o device3380.o device605_uart.o device605_601.o executor.o extra.o help.o kmd.o memdump.o mempatch.o statistics.o tlp.o util.o vfs.o %.o: %.c $(DEPS) $(CC) -c -o $@ $< $(CFLAGS) diff --git a/pcileech/device.c b/pcileech/device.c index f808cdf..afc560b 100644 --- a/pcileech/device.c +++ b/pcileech/device.c @@ -5,20 +5,57 @@ // #include "device.h" #include "kmd.h" +#include "statistics.h" #include "device3380.h" -#include "device605.h" +#include "device605_uart.h" +#include "device605_601.h" BOOL DeviceReadDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb, _In_ QWORD flags) { if(flags & PCILEECH_MEM_FLAG_RETRYONFAIL) { return DeviceReadDMA(ctx, qwAddr, pb, cb, 0) || DeviceReadDMA(ctx, qwAddr, pb, cb, 0); } - if(PCILEECH_DEVICE_USB3380 == ctx->cfg->tpDevice) { - return Device3380_ReadDMA(ctx, qwAddr, pb, cb); - } else if(PCILEECH_DEVICE_SP605 == ctx->cfg->tpDevice) { - return Device605_ReadDMA(ctx, qwAddr, pb, cb); + return ctx->cfg->dev.pfnReadDMA ? ctx->cfg->dev.pfnReadDMA(ctx, qwAddr, pb, cb) : FALSE; +} + +#define CHUNK_FAIL_DIVISOR 16 +DWORD DeviceReadDMAEx_DoWork(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb, _Inout_opt_ PPAGE_STATISTICS pPageStat, _In_ DWORD cbMaxSizeIo) +{ + DWORD cbRd, cbRdOff; + DWORD cbChunk, cChunkTotal, cChunkSuccess = 0; + DWORD i, cbSuccess = 0; + // calculate current chunk sizes + cbChunk = ~0xfff & min(cb, cbMaxSizeIo); + cbChunk = (cbChunk > 0x3000) ? cbChunk : 0x1000; + cChunkTotal = (cb / cbChunk) + ((cb % cbChunk) ? 1 : 0); + // try read memory + memset(pb, 0, cb); + for(i = 0; i < cChunkTotal; i++) { + cbRdOff = i * cbChunk; + cbRd = ((i == cChunkTotal - 1) && (cb % cbChunk)) ? (cb % cbChunk) : cbChunk; // (last chunk may be smaller) + if(DeviceReadDMA(ctx, qwAddr + cbRdOff, pb + cbRdOff, cbRd, 0)) { + cbSuccess += cbRd; + PageStatUpdate(pPageStat, qwAddr + cbRdOff + cbRd, cbRd / 0x1000, 0); + } else if(cbRd == 0x1000) { + PageStatUpdate(pPageStat, qwAddr + cbRdOff + cbRd, 0, 1); + } else { + cbSuccess += DeviceReadDMAEx_DoWork(ctx, qwAddr + cbRdOff, pb + cbRdOff, cbRd, pPageStat, cbRd / CHUNK_FAIL_DIVISOR); + } + } + return cbSuccess; +} + +DWORD DeviceReadDMAEx(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb, _Inout_opt_ PPAGE_STATISTICS pPageStat) +{ + BYTE pbWorkaround[4096]; + DWORD cbWorkaround; + if(cb != 0x1000) { + return DeviceReadDMAEx_DoWork(ctx, qwAddr, pb, cb, pPageStat, (DWORD)ctx->cfg->qwMaxSizeDmaIo); } - return FALSE; + // why is this working ??? if not here console is screwed up... (threading issue?) + cbWorkaround = DeviceReadDMAEx_DoWork(ctx, qwAddr, pbWorkaround, 0x1000, pPageStat, (DWORD)ctx->cfg->qwMaxSizeDmaIo); + memcpy(pb, pbWorkaround, 0x1000); + return cbWorkaround; } BOOL DeviceWriteDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ PBYTE pb, _In_ DWORD cb, _In_ QWORD flags) @@ -28,11 +65,7 @@ BOOL DeviceWriteDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ PBYTE if(flags & PCILEECH_MEM_FLAG_RETRYONFAIL) { return DeviceWriteDMA(ctx, qwAddr, pb, cb, 0) || DeviceWriteDMA(ctx, qwAddr, pb, cb, 0); } - if(PCILEECH_DEVICE_USB3380 == ctx->cfg->tpDevice) { - result = Device3380_WriteDMA(ctx, qwAddr, pb, cb); - } else if(PCILEECH_DEVICE_SP605 == ctx->cfg->tpDevice) { - result = Device605_WriteDMA(ctx, qwAddr, pb, cb); - } + result = ctx->cfg->dev.pfnWriteDMA ? ctx->cfg->dev.pfnWriteDMA(ctx, qwAddr, pb, cb) : FALSE; if(!result) { return FALSE; } if(flags & PCILEECH_MEM_FLAG_VERIFYWRITE) { pbV = LocalAlloc(0, cb + 0x2000); @@ -47,32 +80,43 @@ BOOL DeviceWriteDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ PBYTE BOOL DeviceProbeDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ DWORD cPages, _Out_ __bcount(cPages) PBYTE pbResultMap) { - if(PCILEECH_DEVICE_SP605 == ctx->cfg->tpDevice) { - Device605_ProbeDMA(ctx, qwAddr, cPages, pbResultMap); - return TRUE; - } - return FALSE; + if(!ctx->cfg->dev.pfnProbeDMA) { return FALSE; } + ctx->cfg->dev.pfnProbeDMA(ctx, qwAddr, cPages, pbResultMap); + return TRUE; +} + +BOOL DeviceWriteTlp(_Inout_ PPCILEECH_CONTEXT ctx, _In_ PBYTE pb, _In_ DWORD cb) +{ + if(!ctx->cfg->dev.pfnWriteTlp) { return FALSE; } + return ctx->cfg->dev.pfnWriteTlp(ctx, pb, cb); +} + +BOOL DeviceListenTlp(_Inout_ PPCILEECH_CONTEXT ctx, _In_ DWORD dwTime) +{ + if(!ctx->cfg->dev.pfnListenTlp) { return FALSE; } + return ctx->cfg->dev.pfnListenTlp(ctx, dwTime); } VOID DeviceClose(_Inout_ PPCILEECH_CONTEXT ctx) { - if(ctx->hDevice) { - if(PCILEECH_DEVICE_USB3380 == ctx->cfg->tpDevice) { - Device3380_Close(ctx); - } else if(PCILEECH_DEVICE_SP605 == ctx->cfg->tpDevice) { - Device605_Close(ctx); - } + if(ctx->hDevice && ctx->cfg->dev.pfnClose) { + ctx->cfg->dev.pfnClose(ctx); } } BOOL DeviceOpen(_Inout_ PPCILEECH_CONTEXT ctx) { - if(PCILEECH_DEVICE_USB3380 == ctx->cfg->tpDevice) { - return Device3380_Open(ctx); - } else if(PCILEECH_DEVICE_SP605 == ctx->cfg->tpDevice) { - return Device605_Open(ctx); + BOOL result = FALSE; + if(PCILEECH_DEVICE_USB3380 == ctx->cfg->dev.tp || PCILEECH_DEVICE_NA == ctx->cfg->dev.tp) { + result = Device3380_Open(ctx); } - return FALSE; + if(PCILEECH_DEVICE_SP605_FT601 == ctx->cfg->dev.tp || PCILEECH_DEVICE_NA == ctx->cfg->dev.tp) { + result = Device605_601_Open(ctx); + } + if(PCILEECH_DEVICE_SP605_UART == ctx->cfg->dev.tp) { + result = Device605_UART_Open(ctx); + } + return result; } BOOL DeviceWriteMEM(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ PBYTE pb, _In_ DWORD cb, _In_ QWORD flags) @@ -88,7 +132,9 @@ BOOL DeviceReadMEM(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ PBYTE { if(ctx->phKMD) { return KMDReadMemory(ctx, qwAddr, pb, cb); - } else { + } else if(flags || cb == 0x1000) { return DeviceReadDMA(ctx, qwAddr, pb, cb, flags); + } else { + return cb == DeviceReadDMAEx(ctx, qwAddr, pb, cb, NULL); } } diff --git a/pcileech/device.h b/pcileech/device.h index 17d8099..416dec8 100644 --- a/pcileech/device.h +++ b/pcileech/device.h @@ -6,6 +6,7 @@ #ifndef __DEVICE_H__ #define __DEVICE_H__ #include "pcileech.h" +#include "statistics.h" #define PCILEECH_MEM_FLAG_RETRYONFAIL 0x01 #define PCILEECH_MEM_FLAG_VERIFYWRITE 0x02 @@ -34,6 +35,20 @@ VOID DeviceClose(_Inout_ PPCILEECH_CONTEXT ctx); */ BOOL DeviceReadDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb, _In_ QWORD flags); +/* +* Try read memory with DMA in a fairly optimal way considering device limits. +* The number of total successfully read bytes is returned. Failed reads will +* be zeroed out the he returned memory. +* -- ctx +* -- qwAddr +* -- pb +* -- cb +* -- pPageStat = optional page statistics +* -- return = the number of bytes successfully read. +* +*/ +DWORD DeviceReadDMAEx(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb, _Inout_opt_ PPAGE_STATISTICS pPageStat); + /* * Write data to the target system using DMA. * -- ctx @@ -82,4 +97,22 @@ BOOL DeviceWriteMEM(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ PBYTE */ BOOL DeviceReadMEM(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb, _In_ QWORD flags); +/* +* Write PCIe Transaction Layer Packets (TLPs) to the device. +* -- ctx +* -- pb = PCIe TLP/TLPs to send. +* -- cb = +* -- return +*/ +BOOL DeviceWriteTlp(_Inout_ PPCILEECH_CONTEXT ctx, _In_ PBYTE pb, _In_ DWORD cb); + +/* +* Listen for incoming PCIe Transaction Layer Packets (TLPs) for a specific +* amount of time. +* -- ctx +* -- dwTime = time in ms +* -- return +*/ +BOOL DeviceListenTlp(_Inout_ PPCILEECH_CONTEXT ctx, _In_ DWORD dwTime); + #endif /* __DEVICE_H__ */ \ No newline at end of file diff --git a/pcileech/device3380.c b/pcileech/device3380.c index c46fdec..be6f16b 100644 --- a/pcileech/device3380.c +++ b/pcileech/device3380.c @@ -296,7 +296,7 @@ BOOL Device3380_FlashEEPROM(_Inout_ PPCILEECH_CONTEXT ctx, _In_ PBYTE pbEEPROM, VOID Action_Device3380_Flash(_Inout_ PPCILEECH_CONTEXT ctx) { BOOL result; - if(ctx->cfg->tpDevice != PCILEECH_DEVICE_USB3380) { + if(ctx->cfg->dev.tp != PCILEECH_DEVICE_USB3380) { printf("Flash failed: unsupported device.\n"); return; } @@ -320,7 +320,7 @@ VOID Action_Device3380_Flash(_Inout_ PPCILEECH_CONTEXT ctx) VOID Action_Device3380_8051Start(_Inout_ PPCILEECH_CONTEXT ctx) { BOOL result; - if(ctx->cfg->tpDevice != PCILEECH_DEVICE_USB3380) { + if(ctx->cfg->dev.tp != PCILEECH_DEVICE_USB3380) { printf("8051 startup failed: unsupported device.\n"); return; } @@ -339,7 +339,7 @@ VOID Action_Device3380_8051Start(_Inout_ PPCILEECH_CONTEXT ctx) VOID Action_Device3380_8051Stop(_Inout_ PPCILEECH_CONTEXT ctx) { - if(ctx->cfg->tpDevice != PCILEECH_DEVICE_USB3380) { + if(ctx->cfg->dev.tp != PCILEECH_DEVICE_USB3380) { printf("Stopping 8051 failed: unsupported device.\n"); return; } @@ -376,6 +376,18 @@ BOOL DevicePciInReadDma(_In_ PDEVICE_DATA pDeviceData, _In_ QWORD qwAddr, _Out_ BOOL Device3380_Open2(_Inout_ PPCILEECH_CONTEXT ctx); +VOID Device3380_Close(_Inout_ PPCILEECH_CONTEXT ctx) +{ + PDEVICE_DATA pDeviceData = (PDEVICE_DATA)ctx->hDevice; + if(!pDeviceData) { return; } + if(!pDeviceData->HandlesOpen) { return; } + WinUsb_Free(pDeviceData->WinusbHandle); + if(pDeviceData->DeviceHandle) { CloseHandle(pDeviceData->DeviceHandle); } + pDeviceData->HandlesOpen = FALSE; + LocalFree(ctx->hDevice); + ctx->hDevice = 0; +} + BOOL Device3380_Open(_Inout_ PPCILEECH_CONTEXT ctx) { BOOL result; @@ -384,7 +396,7 @@ BOOL Device3380_Open(_Inout_ PPCILEECH_CONTEXT ctx) if(!result) { return FALSE; } Device3380_ReadCsr((PDEVICE_DATA)ctx->hDevice, REG_USBSTAT, &dwReg, CSR_CONFIGSPACE_MEMM | CSR_BYTEALL); if(ctx->cfg->fForceUsb2 && (dwReg & 0x0100 /* Super-Speed(USB3) */)) { - printf("Device Info: Device running at USB3 speed; downgrading to USB2 ...\n"); + printf("Device Info: USB3380 running at USB3 speed; downgrading to USB2 ...\n"); dwReg = 0x04; // USB2=ENABLE, USB3=DISABLE Device3380_WriteCsr((PDEVICE_DATA)ctx->hDevice, REG_USBCTL2, dwReg, CSR_CONFIGSPACE_MEMM | CSR_BYTE0); Device3380_Close(ctx); @@ -394,26 +406,22 @@ BOOL Device3380_Open(_Inout_ PPCILEECH_CONTEXT ctx) Device3380_ReadCsr((PDEVICE_DATA)ctx->hDevice, REG_USBSTAT, &dwReg, CSR_CONFIGSPACE_MEMM | CSR_BYTEALL); } if(dwReg & 0xc0 /* Full-Speed(USB1)|High-Speed(USB2) */) { - printf("Device Info: Device running at USB2 speed.\n"); + printf("Device Info: USB330 running at USB2 speed.\n"); } else if(ctx->cfg->fVerbose) { - printf("Device Info: Device running at USB3 speed.\n"); + printf("Device Info: USB330 running at USB3 speed.\n"); } if(ctx->cfg->fVerbose) { printf("Device Info: USB3380.\n"); } + // set callback functions and fix up config + ctx->cfg->dev.tp = PCILEECH_DEVICE_USB3380; + ctx->cfg->dev.qwMaxSizeDmaIo = 0x01000000; + ctx->cfg->dev.qwAddrMaxNative = 0x00000000ffffffff; + ctx->cfg->dev.fPartialPageReadSupported = TRUE; + ctx->cfg->dev.pfnClose = Device3380_Close; + ctx->cfg->dev.pfnReadDMA = Device3380_ReadDMA; + ctx->cfg->dev.pfnWriteDMA = Device3380_WriteDMA; return TRUE; } -VOID Device3380_Close(_Inout_ PPCILEECH_CONTEXT ctx) -{ - PDEVICE_DATA pDeviceData = (PDEVICE_DATA)ctx->hDevice; - if(!pDeviceData) { return; } - if(!pDeviceData->HandlesOpen) { return; } - WinUsb_Free(pDeviceData->WinusbHandle); - if(pDeviceData->DeviceHandle) { CloseHandle(pDeviceData->DeviceHandle); } - pDeviceData->HandlesOpen = FALSE; - LocalFree(ctx->hDevice); - ctx->hDevice = 0; -} - #ifdef WIN32 #include diff --git a/pcileech/device3380.h b/pcileech/device3380.h index 95abc42..fa028bc 100644 --- a/pcileech/device3380.h +++ b/pcileech/device3380.h @@ -14,32 +14,6 @@ */ BOOL Device3380_Open(_Inout_ PPCILEECH_CONTEXT ctx); -/* -* Clean up various device related stuff and deallocate memory buffers. -* -- ctx -*/ -VOID Device3380_Close(_Inout_ PPCILEECH_CONTEXT ctx); - -/* -* Read data from the target system using DMA. -* -- ctx -* -- qwAddr - max supported address = 0x100000000 - cb - (32-bit address space) -* -- pb -* -- cb -* -- return -*/ -BOOL Device3380_ReadDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb); - -/* -* Write data to the target system using DMA. -* -- ctx -* -- qwAddr - max supported address = 0x100000000 - cb - (32-bit address space) -* -- pb -* -- cb -* -- return -*/ -BOOL Device3380_WriteDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ PBYTE pb, _In_ DWORD cb); - /* * Flash a new firmware into the onboard memory of the USB3380 card. * This may be dangerious and the device may stop working after a reflash! diff --git a/pcileech/device605.h b/pcileech/device605.h deleted file mode 100644 index 78dab3f..0000000 --- a/pcileech/device605.h +++ /dev/null @@ -1,59 +0,0 @@ -// device605.h : definitions related to the Xilinx SP605 dev board flashed with @d_olex early access bitstream. (UART communication). -// -// (c) Ulf Frisk, 2017 -// Author: Ulf Frisk, pcileech@frizk.net -// -#ifndef __DEVICE605_H__ -#define __DEVICE605_H__ -#include "pcileech.h" - -/* -* Open a connection to the SP605 PCILeech flashed device. -* -- ctx -* -- result -*/ -BOOL Device605_Open(_Inout_ PPCILEECH_CONTEXT ctx); - -/* -* Clean up various device related stuff and deallocate memory buffers. -* -- ctx -*/ -VOID Device605_Close(_Inout_ PPCILEECH_CONTEXT ctx); - -/* -* Read data from the target system using DMA. -* -- ctx -* -- qwAddr -* -- pb -* -- cb -* -- return -*/ -BOOL Device605_ReadDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb); - -/* -* Write data to the target system using DMA. -* -- ctx -* -- qwAddr -* -- pb -* -- cb -* -- return -*/ -BOOL Device605_WriteDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ PBYTE pb, _In_ DWORD cb); - -/* -* Probe the memory of the target system to check whether it's readable or not. -* -- ctx -* -- qwAddr = address to start probe from. -* -- cPages = number of 4kB pages to probe. -* -- pbResultMap = result map, 1 byte represents 1 page, 0 = fail, 1 = success. -* -- return = FALSE if not supported by underlying hardware, TRUE if supported. -*/ -VOID Device605_ProbeDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ DWORD cPages, _Out_ __bcount(cPages) PBYTE pbResultMap); - -/* -* Transmit a raw PCIe TLP. -* -- ctx -*/ -VOID Action_Device605_TlpTx(_Inout_ PPCILEECH_CONTEXT ctx); - -#endif /* __DEVICE605_H__ */ diff --git a/pcileech/device605_601.c b/pcileech/device605_601.c new file mode 100644 index 0000000..3172a53 --- /dev/null +++ b/pcileech/device605_601.c @@ -0,0 +1,488 @@ +// device605_601.c : implementation related to the Xilinx SP605 dev board flashed with bitstream for FTDI UMFT601X-B addon-board. +// +// (c) Ulf Frisk, 2017 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifdef WIN32 + +#include "device605_601.h" +#include "device.h" +#include "tlp.h" +#include "util.h" + +//------------------------------------------------------------------------------- +// FPGA/SP605/FT601 defines below. +//------------------------------------------------------------------------------- + +#define FPGA_CFG_RX_VALID 0x77000000 +#define FPGA_CFG_RX_VALID_MASK 0xff070000 +#define FPGA_CMD_RX_VALID 0x77020000 +#define FPGA_CMD_RX_VALID_MASK 0xff070000 + +#define FPGA_TLP_RX_VALID 0x77030000 +#define FPGA_TLP_RX_VALID_MASK 0xff030000 +#define FPGA_TLP_RX_VALID_LAST 0x77070000 +#define FPGA_TLP_RX_VALID_LAST_MASK 0xff070000 + +#define FPGA_TLP_TX_VALID 0x77030000 +#define FPGA_TLP_TX_VALID_LAST 0x77070000 +#define FPGA_LOOP_TX_VALID 0x77010000 + +#define SP605_601_PROBE_MAXPAGES 0x400 +#define SP601_601_MAX_SIZE_RX 0x001e000 // in data bytes (excl. overhead/TLP headers) +#define SP601_601_MAX_SIZE_TX 0x0002000 // in total data (incl. overhead/TLP headers) + +#define ENDIAN_SWAP_WORD(x) (x = (x << 8) | (x >> 8)) +#define ENDIAN_SWAP_DWORD(x) (x = (x << 24) | ((x >> 8) & 0xff00) | ((x << 8) & 0xff0000) | (x >> 24)) + +typedef struct tdDEVICE_CONTEXT_SP605_601 { + WORD wDeviceId; + WORD wFpgaVersion; + BOOL isPrintTlp; + PTLP_CALLBACK_BUF_MRd pMRdBuffer; + struct { + PBYTE pb; + DWORD cb; + DWORD cbMax; + } rxbuf; + struct { + PBYTE pb; + DWORD cb; + DWORD cbMax; + } txbuf; + struct { + HMODULE hModule; + HANDLE hFTDI; + ULONG(*pfnFT_Create)( + PVOID pvArg, + DWORD dwFlags, + HANDLE *pftHandle + ); + ULONG(*pfnFT_Close)( + HANDLE ftHandle + ); + ULONG(*pfnFT_WritePipe)( + HANDLE ftHandle, + UCHAR ucPipeID, + PUCHAR pucBuffer, + ULONG ulBufferLength, + PULONG pulBytesTransferred, + LPOVERLAPPED pOverlapped + ); + ULONG(*pfnFT_ReadPipe)( + HANDLE ftHandle, + UCHAR ucPipeID, + PUCHAR pucBuffer, + ULONG ulBufferLength, + PULONG pulBytesTransferred, + LPOVERLAPPED pOverlapped + ); + ULONG(*pfnFT_AbortPipe)( + HANDLE ftHandle, + UCHAR ucPipeID + ); + + } dev; + BOOL(*hRxTlpCallbackFn)(_Inout_ PTLP_CALLBACK_BUF_MRd pBufferMrd, _In_ PBYTE pb, _In_ DWORD cb, _In_opt_ HANDLE hEventCompleted); + QWORD dbg_qwLastTx[8]; + DWORD dbg_cbLastTx; +} DEVICE_CONTEXT_SP605_601, *PDEVICE_CONTEXT_SP605_601; + +//------------------------------------------------------------------------------- +// FPGA/SP605/FT601 implementation below. +//------------------------------------------------------------------------------- + +VOID Device601_601_InitializeFTDI(_In_ PDEVICE_CONTEXT_SP605_601 ctx) +{ + DWORD status; + // Load FTDI Library + ctx->dev.hModule = LoadLibrary(L"FTD3XX.dll"); + if(!ctx->dev.hModule) { return; } + ctx->dev.pfnFT_AbortPipe = (ULONG(*)(HANDLE, UCHAR)) + GetProcAddress(ctx->dev.hModule, "FT_AbortPipe"); + ctx->dev.pfnFT_Close = (ULONG(*)(HANDLE)) + GetProcAddress(ctx->dev.hModule, "FT_Close"); + ctx->dev.pfnFT_Create = (ULONG(*)(PVOID, DWORD, HANDLE*)) + GetProcAddress(ctx->dev.hModule, "FT_Create"); + ctx->dev.pfnFT_ReadPipe = (ULONG(*)(HANDLE, UCHAR, PUCHAR, ULONG, PULONG, LPOVERLAPPED)) + GetProcAddress(ctx->dev.hModule, "FT_ReadPipe"); + ctx->dev.pfnFT_WritePipe = (ULONG(*)(HANDLE, UCHAR, PUCHAR, ULONG, PULONG, LPOVERLAPPED)) + GetProcAddress(ctx->dev.hModule, "FT_WritePipe"); + // Open FTDI + status = ctx->dev.pfnFT_Create(NULL, 0x10 /*FT_OPEN_BY_INDEX*/, &ctx->dev.hFTDI); + if(status || !ctx->dev.hFTDI) { return; } + ctx->dev.pfnFT_AbortPipe(ctx->dev.hFTDI, 0x02); + ctx->dev.pfnFT_AbortPipe(ctx->dev.hFTDI, 0x82); +} + +VOID Device605_601_Close(_Inout_ PPCILEECH_CONTEXT ctxPcileech) +{ + PDEVICE_CONTEXT_SP605_601 ctx = (PDEVICE_CONTEXT_SP605_601)ctxPcileech->hDevice; + if(!ctx) { return; } + if(ctx->dev.hFTDI) { ctx->dev.pfnFT_Close(ctx->dev.hFTDI); } + if(ctx->dev.hModule) { FreeLibrary(ctx->dev.hModule); } + if(ctx->rxbuf.pb) { LocalFree(ctx->rxbuf.pb); } + if(ctx->txbuf.pb) { LocalFree(ctx->txbuf.pb); } + LocalFree(ctx); + ctxPcileech->hDevice = 0; +} + +VOID Device605_601_GetDeviceID_FpgaVersion(_In_ PDEVICE_CONTEXT_SP605_601 ctx) +{ + DWORD status; + DWORD cbTX, cbRX, i, dwStatus, dwData; + PBYTE pbRX = LocalAlloc(0, 0x01000000); + BYTE pbTX[24] = { + // cfg read addr 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, + // cmd msg: version + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x77, + // mirror msg -> at least something to read back -> no ft601 freeze. + 0xff, 0xee, 0xdd, 0xcc, 0x00, 0x00, 0x01, 0x77 + }; + status = ctx->dev.pfnFT_WritePipe(ctx->dev.hFTDI, 0x02, pbTX, 24, &cbTX, NULL); + if(status) { goto fail; } + status = ctx->dev.pfnFT_ReadPipe(ctx->dev.hFTDI, 0x82, pbRX, 0x01000000, &cbRX, NULL); + if(status || cbRX < 16) { goto fail; } + for(i = 0; i < cbRX - 7; i += 8) { + dwData = *(PDWORD)(pbRX + i); + dwStatus = *(PDWORD)(pbRX + i + 4); + if(!ctx->wDeviceId && (FPGA_CFG_RX_VALID == (FPGA_CFG_RX_VALID_MASK & dwStatus))) { + ctx->wDeviceId = dwStatus & 0xffff; + } + if(!ctx->wFpgaVersion && (FPGA_CMD_RX_VALID == (FPGA_CMD_RX_VALID_MASK & dwStatus))) { + ctx->wFpgaVersion = dwData & 0xffff; + ENDIAN_SWAP_WORD(ctx->wFpgaVersion); + } + } +fail: + LocalFree(pbRX); +} + +BOOL Device605_601_TxTlp(_In_ PDEVICE_CONTEXT_SP605_601 ctx, _In_ PBYTE pbTlp, _In_ DWORD cbTlp, BOOL fRdKeepalive, BOOL fFlush) +{ + DWORD status; + PBYTE pbTx; + DWORD i, cbTx, cbTxed = 0; + if(cbTlp & 0x3) { return FALSE; } + if(cbTlp > 2048) { return FALSE; } + if(ctx->isPrintTlp) { + TLP_Print(pbTlp, cbTlp, TRUE); + } + // prepare transmit buffer + pbTx = ctx->txbuf.pb + ctx->txbuf.cb; + cbTx = 2 * cbTlp; + for(i = 0; i < cbTlp; i += 4) { + *(PDWORD)(pbTx + (i << 1)) = *(PDWORD)(pbTlp + i); + *(PDWORD)(pbTx + ((i << 1) + 4)) = FPGA_TLP_TX_VALID; + } + if(cbTlp) { + *(PDWORD)(pbTx + ((i << 1) - 4)) = FPGA_TLP_TX_VALID_LAST; + } + if(fRdKeepalive) { + cbTx += 8; + *(PDWORD)(pbTx + (i << 1)) = 0xffeeddcc; + *(PDWORD)(pbTx + ((i << 1) + 4)) = FPGA_LOOP_TX_VALID; + } + ctx->txbuf.cb += cbTx; + // transmit + if((ctx->txbuf.cb > SP601_601_MAX_SIZE_TX) || (fFlush && ctx->txbuf.cb)) { + status = ctx->dev.pfnFT_WritePipe(ctx->dev.hFTDI, 0x02, ctx->txbuf.pb, ctx->txbuf.cb, &cbTxed, NULL); + ctx->txbuf.cb = 0; + return (0 == status); + } + return TRUE; +} + +#define TLP_RX_MAX_SIZE 1024 +VOID Device605_601_RxTlpSynchronous(_In_ PDEVICE_CONTEXT_SP605_601 ctx) +{ + DWORD status; + DWORD dwTlp, dwStatus; + DWORD i, cdwTlp = 0; + BYTE pbTlp[TLP_RX_MAX_SIZE]; + PDWORD pdwTlp = (PDWORD)pbTlp; + PDWORD pdwRx = (PDWORD)ctx->rxbuf.pb; + + status = ctx->dev.pfnFT_ReadPipe(ctx->dev.hFTDI, 0x82, ctx->rxbuf.pb, ctx->rxbuf.cbMax, &ctx->rxbuf.cb, NULL); + if(status) { + ctx->dev.pfnFT_AbortPipe(ctx->dev.hFTDI, 0x82); + return; + } + if(ctx->rxbuf.cb % 8) { + printf("Device Info: SP605 / FT601: Bad read from device. Should not happen!\n"); + return; + } + for(i = 0; i < ctx->rxbuf.cb / sizeof(QWORD); i++) { // index in 64-bit (QWORD) + dwTlp = pdwRx[i << 1]; + dwStatus = pdwRx[1 + (i << 1)]; + if(FPGA_TLP_RX_VALID == (FPGA_TLP_RX_VALID_MASK & dwStatus)) { + pdwTlp[cdwTlp] = dwTlp; + cdwTlp++; + if(cdwTlp >= TLP_RX_MAX_SIZE / sizeof(DWORD)) { return; } + } + if(FPGA_TLP_RX_VALID_LAST == (FPGA_TLP_RX_VALID_LAST_MASK & dwStatus)) { + if(cdwTlp >= 3) { + if(ctx->isPrintTlp) { + TLP_Print(pbTlp, cdwTlp << 2, FALSE); + } + if(ctx->hRxTlpCallbackFn) { + ctx->hRxTlpCallbackFn(ctx->pMRdBuffer, pbTlp, cdwTlp << 2, NULL); + } + } else { + printf("Device Info: SP605 / FT601: Bad PCIe TLP received! Should not happen!\n"); + } + cdwTlp = 0; + } + } +} + +BOOL Device605_601_ReadDMA(_Inout_ PPCILEECH_CONTEXT ctxPcileech, _In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb) +{ + PDEVICE_CONTEXT_SP605_601 ctx = (PDEVICE_CONTEXT_SP605_601)ctxPcileech->hDevice; + TLP_CALLBACK_BUF_MRd rxbuf; + DWORD tx[4], o, i; + BOOL is32; + PTLP_HDR_MRdWr64 hdrRd64 = (PTLP_HDR_MRdWr64)tx; + PTLP_HDR_MRdWr32 hdrRd32 = (PTLP_HDR_MRdWr32)tx; + if(cb > SP601_601_MAX_SIZE_RX) { return FALSE; } + if(qwAddr % 0x1000) { return FALSE; } + if((cb >= 0x1000) && (cb % 0x1000)) { return FALSE; } + if((cb < 0x1000) && (cb % 0x8)) { return FALSE; } + // prepare + rxbuf.cb = 0; + rxbuf.pb = pb; + rxbuf.cbMax = cb; + ctx->pMRdBuffer = &rxbuf; + ctx->hRxTlpCallbackFn = TLP_CallbackMRd; + // transmit TLPs + for(o = 0; o < cb; o += 0x1000) { + memset(tx, 0, 16); + is32 = qwAddr + o < 0x100000000; + if(is32) { + hdrRd32->h.TypeFmt = TLP_MRd32; + hdrRd32->h.Length = (WORD)((cb < 0x1000) ? cb >> 2 : 0); + hdrRd32->RequesterID = ctx->wDeviceId; + hdrRd32->Tag = (BYTE)(o >> 12); + hdrRd32->FirstBE = 0xf; + hdrRd32->LastBE = 0xf; + hdrRd32->Address = (DWORD)(qwAddr + o); + } + else { + hdrRd64->h.TypeFmt = TLP_MRd64; + hdrRd64->h.Length = (WORD)((cb < 0x1000) ? cb >> 2 : 0); + hdrRd64->RequesterID = ctx->wDeviceId; + hdrRd64->Tag = (BYTE)(o >> 12); + hdrRd64->FirstBE = 0xf; + hdrRd64->LastBE = 0xf; + hdrRd64->AddressHigh = (DWORD)((qwAddr + o) >> 32); + hdrRd64->AddressLow = (DWORD)(qwAddr + o); + } + for(i = 0; i < 4; i++) { + ENDIAN_SWAP_DWORD(tx[i]); + } + Device605_601_TxTlp(ctx, (PBYTE)tx, is32 ? 12 : 16, TRUE, (o % 0x8000 == 0x7000)); + } + Device605_601_TxTlp(ctx, NULL, 0, TRUE, TRUE); + usleep(300); + Device605_601_RxTlpSynchronous(ctx); + ctx->pMRdBuffer = NULL; + return rxbuf.cb >= rxbuf.cbMax; +} + +VOID Device605_601_ProbeDMA(_Inout_ PPCILEECH_CONTEXT ctxPcileech, _In_ QWORD qwAddr, _In_ DWORD cPages, _Out_ __bcount(cPages) PBYTE pbResultMap) +{ + DWORD i, j; + PDEVICE_CONTEXT_SP605_601 ctx = (PDEVICE_CONTEXT_SP605_601)ctxPcileech->hDevice; + TLP_CALLBACK_BUF_MRd bufMRd; + DWORD tx[4]; + BOOL is32; + PTLP_HDR_MRdWr64 hdrRd64 = (PTLP_HDR_MRdWr64)tx; + PTLP_HDR_MRdWr32 hdrRd32 = (PTLP_HDR_MRdWr32)tx; + // split probe into processing chunks if too large... + while(cPages > SP605_601_PROBE_MAXPAGES) { + Device605_601_ProbeDMA(ctxPcileech, qwAddr, SP605_601_PROBE_MAXPAGES, pbResultMap); + cPages -= SP605_601_PROBE_MAXPAGES; + pbResultMap += SP605_601_PROBE_MAXPAGES; + qwAddr += SP605_601_PROBE_MAXPAGES << 12; + } + memset(pbResultMap, 0, cPages); + // prepare + bufMRd.cb = 0; + bufMRd.pb = pbResultMap; + bufMRd.cbMax = cPages; + ctx->pMRdBuffer = &bufMRd; + ctx->hRxTlpCallbackFn = TLP_CallbackMRdProbe; + // transmit TLPs + for(i = 0; i < cPages; i++) { + memset(tx, 0, 16); + is32 = qwAddr + (i << 12) < 0x100000000; + if(is32) { + hdrRd32->h.TypeFmt = TLP_MRd32; + hdrRd32->h.Length = 1; + hdrRd32->RequesterID = ctx->wDeviceId; + hdrRd32->FirstBE = 0xf; + hdrRd32->LastBE = 0; + hdrRd32->Address = (DWORD)(qwAddr + (i << 12) + ((i & 0x1f) << 2)); // 5 low address bits coded into the dword read. + hdrRd32->Tag = (BYTE)((i >> 5) & 0x1f); // 5 high address bits coded into tag. + } else { + hdrRd64->h.TypeFmt = TLP_MRd64; + hdrRd64->h.Length = 1; + hdrRd64->RequesterID = ctx->wDeviceId; + hdrRd64->FirstBE = 0xf; + hdrRd64->LastBE = 0; + hdrRd64->AddressHigh = (DWORD)((qwAddr + (i << 12)) >> 32); + hdrRd64->AddressLow = (DWORD)(qwAddr + (i << 12) + ((i & 0x1f) << 2)); // 5 low address bits coded into the dword read. + hdrRd64->Tag = (BYTE)((i >> 5) & 0x1f); // 5 high address bits coded into tag. + } + for(j = 0; j < 4; j++) { + ENDIAN_SWAP_DWORD(tx[j]); + } + Device605_601_TxTlp(ctx, (PBYTE)tx, is32 ? 12 : 16, FALSE, (i % 24 == 0)); + } + Device605_601_TxTlp(ctx, NULL, 0, TRUE, TRUE); + usleep(300); + Device605_601_RxTlpSynchronous(ctx); + ctx->hRxTlpCallbackFn = NULL; + ctx->pMRdBuffer = NULL; +} + +// write max 128 byte packets. +BOOL Device605_601_WriteDMA_TXP(_Inout_ PDEVICE_CONTEXT_SP605_601 ctx, _In_ QWORD qwA, _In_ BYTE bFirstBE, _In_ BYTE bLastBE, _In_ PBYTE pb, _In_ DWORD cb) +{ + DWORD txbuf[36], i, cbTlp; + PBYTE pbTlp = (PBYTE)txbuf; + PTLP_HDR_MRdWr32 hdrWr32 = (PTLP_HDR_MRdWr32)txbuf; + PTLP_HDR_MRdWr64 hdrWr64 = (PTLP_HDR_MRdWr64)txbuf; + memset(pbTlp, 0, 16); + if(qwA < 0x100000000) { + hdrWr32->h.TypeFmt = TLP_MWr32; + hdrWr32->h.Length = (WORD)(cb + 3) >> 2; + hdrWr32->FirstBE = bFirstBE; + hdrWr32->LastBE = bLastBE; + hdrWr32->RequesterID = ctx->wDeviceId; + hdrWr32->Address = (DWORD)qwA; + for(i = 0; i < 3; i++) { + ENDIAN_SWAP_DWORD(txbuf[i]); + } + memcpy(pbTlp + 12, pb, cb); + cbTlp = (12 + cb + 3) & ~0x3; + } else { + hdrWr64->h.TypeFmt = TLP_MWr64; + hdrWr64->h.Length = (WORD)(cb + 3) >> 2; + hdrWr64->FirstBE = bFirstBE; + hdrWr64->LastBE = bLastBE; + hdrWr64->RequesterID = ctx->wDeviceId; + hdrWr64->AddressHigh = (DWORD)(qwA >> 32); + hdrWr64->AddressLow = (DWORD)qwA; + for(i = 0; i < 4; i++) { + ENDIAN_SWAP_DWORD(txbuf[i]); + } + memcpy(pbTlp + 16, pb, cb); + cbTlp = (16 + cb + 3) & ~0x3; + } + return Device605_601_TxTlp(ctx, pbTlp, cbTlp, FALSE, FALSE); +} + +BOOL Device605_601_WriteDMA(_Inout_ PPCILEECH_CONTEXT ctxPcileech, _In_ QWORD qwA, _In_ PBYTE pb, _In_ DWORD cb) +{ + PDEVICE_CONTEXT_SP605_601 ctx = (PDEVICE_CONTEXT_SP605_601)ctxPcileech->hDevice; + BOOL result = TRUE; + BYTE be, pbb[4]; + DWORD cbtx; + // TX 1st dword if not aligned + if(cb && (qwA & 0x3)) { + be = (cb < 3) ? (0xf >> (4 - cb)) : 0xf; + be <<= qwA & 0x3; + cbtx = min(cb, 4 - (qwA & 0x3)); + memcpy(pbb + (qwA & 0x3), pb, cbtx); + result = Device605_601_WriteDMA_TXP(ctx, qwA & ~0x3, be, 0, pbb, 4); + pb += cbtx; + cb -= cbtx; + qwA += cbtx; + } + // TX as 128-byte packets (aligned to 128-byte boundaries) + while(result && cb) { + cbtx = min(128 - (qwA & 0x7f), cb); + be = (cbtx & 0x3) ? (0xf >> (4 - (cbtx & 0x3))) : 0xf; + result = (cbtx <= 4) ? + Device605_601_WriteDMA_TXP(ctx, qwA, be, 0, pb, 4) : + Device605_601_WriteDMA_TXP(ctx, qwA, 0xf, be, pb, cbtx); + pb += cbtx; + cb -= cbtx; + qwA += cbtx; + } + return Device605_601_TxTlp(ctx, NULL, 0, FALSE, TRUE) && result; // Flush and Return. +} + +BOOL Device605_601_ListenTlp(_Inout_ PPCILEECH_CONTEXT ctxPcileech, _In_ DWORD dwTime) +{ + PDEVICE_CONTEXT_SP605_601 ctx = (PDEVICE_CONTEXT_SP605_601)ctxPcileech->hDevice; + QWORD tmStart = GetTickCount64(); + while(GetTickCount64() - tmStart < dwTime) { + Device605_601_TxTlp(ctx, NULL, 0, TRUE, TRUE); + Sleep(10); + Device605_601_RxTlpSynchronous(ctx); + } + return TRUE; +} + +BOOL Device605_601_WriteTlp(_Inout_ PPCILEECH_CONTEXT ctxPcileech, _In_ PBYTE pbTlp, _In_ DWORD cbTlp) +{ + PDEVICE_CONTEXT_SP605_601 ctx = (PDEVICE_CONTEXT_SP605_601)ctxPcileech->hDevice; + return Device605_601_TxTlp(ctx, pbTlp, cbTlp, FALSE, TRUE); +} + +BOOL Device605_601_Open(_Inout_ PPCILEECH_CONTEXT ctxPcileech) +{ + PDEVICE_CONTEXT_SP605_601 ctx; + ctx = LocalAlloc(LMEM_ZEROINIT, sizeof(DEVICE_CONTEXT_SP605_601)); + if(!ctx) { return FALSE; } + ctxPcileech->hDevice = (HANDLE)ctx; + Device601_601_InitializeFTDI(ctx); + if(!ctx->dev.hModule && ctxPcileech->cfg->fVerbose) { printf("Device Info: SP605 / FT601: Could not load FTD3XX.dll.\n"); } + if(!ctx->dev.hModule) { goto fail; } + if(!ctx->dev.hFTDI && ctxPcileech->cfg->fVerbose) { printf("Device Info: SP605 / FT601: Could not connect to device.\n"); } + if(!ctx->dev.hFTDI) { goto fail; } + Device605_601_GetDeviceID_FpgaVersion(ctx); + if(!ctx->wDeviceId) { goto fail; } + ctx->rxbuf.cbMax = (DWORD)(2.3 * SP601_601_MAX_SIZE_RX); // buffer size tuned to lowest possible (+margin) for performance. + ctx->rxbuf.pb = LocalAlloc(0, ctx->rxbuf.cbMax); + if(!ctx->rxbuf.pb) { goto fail; } + ctx->txbuf.cbMax = SP601_601_MAX_SIZE_TX + 0x10000; + ctx->txbuf.pb = LocalAlloc(0, ctx->txbuf.cbMax); + if(!ctx->txbuf.pb) { goto fail; } + ctx->isPrintTlp = ctxPcileech->cfg->fVerboseExtra; + // set callback functions and fix up config + ctxPcileech->cfg->dev.tp = PCILEECH_DEVICE_SP605_FT601; + ctxPcileech->cfg->dev.qwMaxSizeDmaIo = SP601_601_MAX_SIZE_RX; + ctxPcileech->cfg->dev.qwAddrMaxNative = 0x0000ffffffffffff; + ctxPcileech->cfg->dev.fPartialPageReadSupported = TRUE; + ctxPcileech->cfg->dev.pfnClose = Device605_601_Close; + ctxPcileech->cfg->dev.pfnProbeDMA = Device605_601_ProbeDMA; + ctxPcileech->cfg->dev.pfnReadDMA = Device605_601_ReadDMA; + ctxPcileech->cfg->dev.pfnWriteDMA = Device605_601_WriteDMA; + ctxPcileech->cfg->dev.pfnWriteTlp = Device605_601_WriteTlp; + ctxPcileech->cfg->dev.pfnListenTlp = Device605_601_ListenTlp; + // return + if(ctxPcileech->cfg->fVerbose) { printf("Device Info: SP605 / FT601.\n"); } + return TRUE; +fail: + Device605_601_Close(ctxPcileech); + return FALSE; +} + +#endif /* WIN32 */ +#if defined(LINUX) || defined(ANDROID) + +#include "device605_601.h" + +BOOL Device605_601_Open(_Inout_ PPCILEECH_CONTEXT ctx) +{ + if(ctx->cfg->dev.tp == PCILEECH_DEVICE_SP605_UART) { + printf("SP605 / FT601: Failed. Device currently only supported in PCILeech for Windows."); + } + return FALSE; +} + +#endif /* LINUX || ANDROID */ diff --git a/pcileech/device605_601.h b/pcileech/device605_601.h new file mode 100644 index 0000000..cf730be --- /dev/null +++ b/pcileech/device605_601.h @@ -0,0 +1,17 @@ +// device605_601.h : definitions related to the Xilinx SP605 dev board flashed with bitstream for FTDI UMFT601X-B addon-board. +// +// (c) Ulf Frisk, 2017 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __DEVICE605_601_H__ +#define __DEVICE605_601_H__ +#include "pcileech.h" + +/* +* Open a connection to the SP605/FT601 PCILeech flashed device. +* -- ctx +* -- result +*/ +BOOL Device605_601_Open(_Inout_ PPCILEECH_CONTEXT ctx); + +#endif /* __DEVICE605_601_H__ */ diff --git a/pcileech/device605.c b/pcileech/device605_uart.c similarity index 63% rename from pcileech/device605.c rename to pcileech/device605_uart.c index 2fb6c88..312d3e6 100644 --- a/pcileech/device605.c +++ b/pcileech/device605_uart.c @@ -1,11 +1,11 @@ -// device.c : implementation related to the Xilinx SP605 dev board flashed with @d_olex early access bitstream. (UART communication). +// device605_uart.c : implementation related to the Xilinx SP605 dev board flashed with @d_olex early access bitstream. (UART communication). // // (c) Ulf Frisk, 2017 // Author: Ulf Frisk, pcileech@frizk.net // #ifdef WIN32 -#include "device605.h" +#include "device605_uart.h" #include "device.h" #include "tlp.h" @@ -26,12 +26,6 @@ #define ENDIAN_SWAP_DWORD(x) (x = (x << 24) | ((x >> 8) & 0xff00) | ((x << 8) & 0xff0000) | (x >> 24)) -typedef struct tdDEVICE_CONTEXT_SP605_RXBUF { - DWORD cbMax; - DWORD cb; - PBYTE pb; -} DEVICE_CONTEXT_SP605_RXBUF, *PDEVICE_CONTEXT_SP605_RXBUF; - typedef struct tdDEVICE_CONTEXT_SP605 { HANDLE hCommCfg; HANDLE hCommPcie; @@ -43,17 +37,15 @@ typedef struct tdDEVICE_CONTEXT_SP605 { OVERLAPPED oRx; OVERLAPPED oCfg; HANDLE hRxBufferEvent; - PDEVICE_CONTEXT_SP605_RXBUF pRxBuffer; - VOID(*hRxTlpCallbackFn)(_Inout_ struct tdDEVICE_CONTEXT_SP605 *ctx605, _In_ PBYTE pb, _In_ DWORD cb); + PTLP_CALLBACK_BUF_MRd pMRdBuffer; + BOOL(*hRxTlpCallbackFn)(_Inout_ PTLP_CALLBACK_BUF_MRd pBufferMrd, _In_ PBYTE pb, _In_ DWORD cb, _In_opt_ HANDLE hEventCompleted); } DEVICE_CONTEXT_SP605, *PDEVICE_CONTEXT_SP605; -VOID Device605_RxTlp_Thread(PDEVICE_CONTEXT_SP605 ctx605); - //------------------------------------------------------------------------------- // SP605 implementation below. //------------------------------------------------------------------------------- -VOID Device605_Close(_Inout_ PPCILEECH_CONTEXT ctx) +VOID Device605_UART_Close(_Inout_ PPCILEECH_CONTEXT ctx) { PDEVICE_CONTEXT_SP605 ctx605 = (PDEVICE_CONTEXT_SP605)ctx->hDevice; if(!ctx605) { return; } @@ -63,7 +55,7 @@ VOID Device605_Close(_Inout_ PPCILEECH_CONTEXT ctx) } if(ctx605->hRxBufferEvent) { WaitForSingleObject(ctx605->hRxBufferEvent, INFINITE); - while(ctx605->pRxBuffer) { SwitchToThread(); } + while(ctx605->pMRdBuffer) { SwitchToThread(); } CloseHandle(ctx605->hRxBufferEvent); } if(ctx605->hCommCfg) { CloseHandle(ctx605->hCommCfg); } @@ -75,7 +67,7 @@ VOID Device605_Close(_Inout_ PPCILEECH_CONTEXT ctx) ctx->hDevice = 0; } -HANDLE Device605_Open_COM(_In_ LPSTR szCOM) +HANDLE Device605_UART_Open_COM(_In_ LPSTR szCOM) { DCB dcb = { 0 }; HANDLE hComm; @@ -90,7 +82,7 @@ HANDLE Device605_Open_COM(_In_ LPSTR szCOM) return hComm; } -WORD Device605_GetDeviceID(_In_ PDEVICE_CONTEXT_SP605 ctx605) +WORD Device605_UART_GetDeviceID(_In_ PDEVICE_CONTEXT_SP605 ctx605) { DWORD dw, txrx[] = { 0x00000000, 0x00000000 }; if(!WriteFile(ctx605->hCommCfg, txrx, sizeof(txrx), &dw, &ctx605->oCfg)) { @@ -100,52 +92,12 @@ WORD Device605_GetDeviceID(_In_ PDEVICE_CONTEXT_SP605 ctx605) if(!ReadFile(ctx605->hCommCfg, txrx, sizeof(txrx), &dw, &ctx605->oCfg)) { if(ERROR_IO_PENDING != GetLastError()) { return 0; } if(WAIT_TIMEOUT == WaitForSingleObject(ctx605->oCfg.hEvent, SP605_COM_TIMEOUT)) { return 0; } - if(!GetOverlappedResult(ctx605->hCommPcie, &ctx605->oCfg, &dw, FALSE)) { return 0; } + if(!GetOverlappedResult(ctx605->hCommCfg, &ctx605->oCfg, &dw, FALSE)) { return 0; } } return (WORD)_byteswap_ulong(txrx[0]); } -BOOL Device605_Open(_Inout_ PPCILEECH_CONTEXT ctx) -{ - DWORD i; - CHAR szCOM[] = { 'C', 'O', 'M', 'x', 0 }; - PDEVICE_CONTEXT_SP605 ctx605; - ctx605 = LocalAlloc(LMEM_ZEROINIT, sizeof(DEVICE_CONTEXT_SP605)); - if(!ctx605) { return FALSE; } - ctx->hDevice = (HANDLE)ctx605; - // open COM ports - for(i = 1; i <= 9; i++) { - szCOM[3] = (CHAR)('0' + i); - if(!ctx605->hCommPcie) { - ctx605->hCommPcie = Device605_Open_COM(szCOM); - } else { - ctx605->hCommCfg = Device605_Open_COM(szCOM); - if(ctx605->hCommCfg) { break; } - } - } - if(!ctx605->hCommPcie || !ctx605->hCommCfg) { goto fail; } - SetupComm(ctx605->hCommPcie, 0x8000, 0x8000); - ctx605->oTx.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - if(!ctx605->oTx.hEvent) { goto fail; } - ctx605->oRx.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - if(!ctx605->oRx.hEvent) { goto fail; } - ctx605->oCfg.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - if(!ctx605->oCfg.hEvent) { goto fail; } - ctx605->hRxBufferEvent = CreateEvent(NULL, TRUE, TRUE, NULL); - if(!ctx605->hRxBufferEvent) { goto fail; } - ctx605->wDeviceId = Device605_GetDeviceID(ctx605); - if(!ctx605->wDeviceId) { goto fail; } - ctx605->isPrintTlp = ctx->cfg->fVerboseExtra; - ctx605->hThreadRx = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Device605_RxTlp_Thread, ctx605, 0, NULL); // start rx thread, must be last in open - if(!ctx605->hThreadRx) { goto fail; } - if(ctx->cfg->fVerbose) { printf("Device Info: SP605.\n"); } - return TRUE; -fail: - Device605_Close(ctx); - return FALSE; -} - -BOOL Device605_TxTlp(_In_ PDEVICE_CONTEXT_SP605 ctx605, _In_ PBYTE pbTlp, _In_ DWORD cbTlp) +BOOL Device605_UART_TxTlp(_In_ PDEVICE_CONTEXT_SP605 ctx605, _In_ PBYTE pbTlp, _In_ DWORD cbTlp) { DWORD pdwTx[1024], cTx, i, dwTxed; if(!cbTlp) { return TRUE; } @@ -166,30 +118,7 @@ BOOL Device605_TxTlp(_In_ PDEVICE_CONTEXT_SP605 ctx605, _In_ PBYTE pbTlp, _In_ D (GetLastError() == ERROR_IO_PENDING && GetOverlappedResult(ctx605->hCommPcie, &ctx605->oTx, &dwTxed, TRUE)); } -VOID Device605_RxTlp_CallbackMRd(_Inout_ PDEVICE_CONTEXT_SP605 ctx605, _In_ PBYTE pb, _In_ DWORD cb) -{ - PDEVICE_CONTEXT_SP605_RXBUF prxbuf = ctx605->pRxBuffer; - PTLP_HDR_CplD hdrC = (PTLP_HDR_CplD)pb; - PTLP_HDR hdr = (PTLP_HDR)pb; - PDWORD buf = (PDWORD)pb; - DWORD o, c; - buf[0] = _byteswap_ulong(buf[0]); - if(cb < ((DWORD)hdr->Length << 2) - 12) { return; } - if((hdr->TypeFmt == TLP_CplD) && prxbuf) { - buf[1] = _byteswap_ulong(buf[1]); - buf[2] = _byteswap_ulong(buf[2]); - // NB! read algorithm below only support reading full 4kB pages _or_ - // partial page if starting at page boundry and read is less than 4kB. - o = (hdrC->Tag << 12) + min(0x1000, prxbuf->cbMax) - (hdrC->ByteCount ? hdrC->ByteCount : 0x1000); - c = (DWORD)hdr->Length << 2; - memcpy(prxbuf->pb + o, pb + 12, c); - if(prxbuf->cbMax <= (DWORD)InterlockedAdd(&prxbuf->cb, c)) { - SetEvent(ctx605->hRxBufferEvent); - } - } -} - -VOID Device605_RxTlp_Thread(_In_ PDEVICE_CONTEXT_SP605 ctx605) +VOID Device605_UART_RxTlp_Thread(_In_ PDEVICE_CONTEXT_SP605 ctx605) { DWORD rx[2], dwTlp[1024], cbRead, dwResult, cdwTlp = 0; while(!ctx605->isTerminateThreadRx) { @@ -213,8 +142,8 @@ VOID Device605_RxTlp_Thread(_In_ PDEVICE_CONTEXT_SP605 ctx605) if(ctx605->isPrintTlp) { TLP_Print((PBYTE)dwTlp, cdwTlp << 2, FALSE); } - if(ctx605->hRxTlpCallbackFn) { - ctx605->hRxTlpCallbackFn(ctx605, (PBYTE)dwTlp, cdwTlp << 2); + if(ctx605->hRxTlpCallbackFn && ctx605->pMRdBuffer) { + ctx605->hRxTlpCallbackFn(ctx605->pMRdBuffer, (PBYTE)dwTlp, cdwTlp << 2, ctx605->hRxBufferEvent); } } cdwTlp = 0; @@ -225,10 +154,10 @@ VOID Device605_RxTlp_Thread(_In_ PDEVICE_CONTEXT_SP605 ctx605) ctx605->isTerminateThreadRx = TRUE; } -BOOL Device605_ReadDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb) +BOOL Device605_UART_ReadDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb) { PDEVICE_CONTEXT_SP605 ctx605 = (PDEVICE_CONTEXT_SP605)ctx->hDevice; - DEVICE_CONTEXT_SP605_RXBUF rxbuf; + TLP_CALLBACK_BUF_MRd rxbuf; DWORD tx[4], o, i; BOOL is32; PTLP_HDR_MRdWr64 hdrRd64 = (PTLP_HDR_MRdWr64)tx; @@ -241,9 +170,9 @@ BOOL Device605_ReadDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ P rxbuf.cb = 0; rxbuf.pb = pb; rxbuf.cbMax = cb; - ctx605->pRxBuffer = &rxbuf; + ctx605->pMRdBuffer = &rxbuf; ResetEvent(ctx605->hRxBufferEvent); - ctx605->hRxTlpCallbackFn = Device605_RxTlp_CallbackMRd; + ctx605->hRxTlpCallbackFn = TLP_CallbackMRd; // transmit TLPs for(o = 0; o < cb; o += 0x1000) { memset(tx, 0, 16); @@ -258,7 +187,7 @@ BOOL Device605_ReadDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ P hdrRd32->Address = (DWORD)(qwAddr + o); } else { hdrRd64->h.TypeFmt = TLP_MRd64; - hdrRd32->h.Length = (WORD)((cb < 0x1000) ? cb >> 2 : 0); + hdrRd64->h.Length = (WORD)((cb < 0x1000) ? cb >> 2 : 0); hdrRd64->RequesterID = ctx605->wDeviceId; hdrRd64->Tag = (BYTE)(o >> 12); hdrRd64->FirstBE = 0xf; @@ -269,48 +198,28 @@ BOOL Device605_ReadDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ P for(i = 0; i < 4; i++) { ENDIAN_SWAP_DWORD(tx[i]); } - Device605_TxTlp(ctx605, (PBYTE)tx, is32 ? 12 : 16); + Device605_UART_TxTlp(ctx605, (PBYTE)tx, is32 ? 12 : 16); } // wait for result WaitForSingleObject(ctx605->hRxBufferEvent, SP605_READ_TIMEOUT); ctx605->hRxTlpCallbackFn = NULL; - ctx605->pRxBuffer = NULL; + ctx605->pMRdBuffer = NULL; SetEvent(ctx605->hRxBufferEvent); return rxbuf.cb >= rxbuf.cbMax; } -VOID Device605_RxTlp_CallbackProbeDMA(_Inout_ PDEVICE_CONTEXT_SP605 ctx605, _In_ PBYTE pb, _In_ DWORD cb) -{ - PDEVICE_CONTEXT_SP605_RXBUF prxbuf = ctx605->pRxBuffer; - PTLP_HDR_CplD hdrC = (PTLP_HDR_CplD)pb; - PDWORD buf = (PDWORD)pb; - DWORD i; - if(cb < 16) { return; } // min size CplD = 16 bytes. - buf[0] = _byteswap_ulong(buf[0]); - buf[1] = _byteswap_ulong(buf[1]); - buf[2] = _byteswap_ulong(buf[2]); - if((hdrC->h.TypeFmt == TLP_CplD) && prxbuf) { - // 5 low address bits coded into the dword read, 5 high address bits coded into tag. - i = ((hdrC->Tag & 0x1f) << 5) + ((hdrC->LowerAddress >> 2) & 0x1f); - prxbuf->pb[i] = 1; - if(prxbuf->cbMax <= (DWORD)InterlockedAdd(&prxbuf->cb, 1)) { - SetEvent(ctx605->hRxBufferEvent); - } - } -} - -VOID Device605_ProbeDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ DWORD cPages, _Out_ __bcount(cPages) PBYTE pbResultMap) +VOID Device605_UART_ProbeDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ DWORD cPages, _Out_ __bcount(cPages) PBYTE pbResultMap) { DWORD i, j; PDEVICE_CONTEXT_SP605 ctx605 = (PDEVICE_CONTEXT_SP605)ctx->hDevice; - DEVICE_CONTEXT_SP605_RXBUF rxbuf; + TLP_CALLBACK_BUF_MRd rxbuf; DWORD tx[4]; BOOL is32; PTLP_HDR_MRdWr64 hdrRd64 = (PTLP_HDR_MRdWr64)tx; PTLP_HDR_MRdWr32 hdrRd32 = (PTLP_HDR_MRdWr32)tx; // split probe into processing chunks if too large... while(cPages > SP605_PROBE_MAXPAGES) { - Device605_ProbeDMA(ctx, qwAddr, SP605_PROBE_MAXPAGES, pbResultMap); + Device605_UART_ProbeDMA(ctx, qwAddr, SP605_PROBE_MAXPAGES, pbResultMap); cPages -= SP605_PROBE_MAXPAGES; pbResultMap += SP605_PROBE_MAXPAGES; qwAddr += SP605_PROBE_MAXPAGES << 12; @@ -320,9 +229,9 @@ VOID Device605_ProbeDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ D rxbuf.cb = 0; rxbuf.pb = pbResultMap; rxbuf.cbMax = cPages; - ctx605->pRxBuffer = &rxbuf; + ctx605->pMRdBuffer = &rxbuf; ResetEvent(ctx605->hRxBufferEvent); - ctx605->hRxTlpCallbackFn = Device605_RxTlp_CallbackProbeDMA; + ctx605->hRxTlpCallbackFn = TLP_CallbackMRdProbe; // transmit TLPs for(i = 0; i < cPages; i++) { memset(tx, 0, 16); @@ -337,27 +246,27 @@ VOID Device605_ProbeDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ D hdrRd32->Tag = (BYTE)((i >> 5) & 0x1f); // 5 high address bits coded into tag. } else { hdrRd64->h.TypeFmt = TLP_MRd64; - hdrRd32->h.Length = 1; - hdrRd32->RequesterID = ctx605->wDeviceId; - hdrRd32->FirstBE = 0xf; - hdrRd32->LastBE = 0; + hdrRd64->h.Length = 1; + hdrRd64->RequesterID = ctx605->wDeviceId; + hdrRd64->FirstBE = 0xf; + hdrRd64->LastBE = 0; hdrRd64->AddressHigh = (DWORD)((qwAddr + (i << 12)) >> 32); - hdrRd32->Address = (DWORD)(qwAddr + (i << 12) + ((i & 0x1f) << 2)); // 5 low address bits coded into the dword read. - hdrRd32->Tag = (BYTE)((i >> 5) & 0x1f); // 5 high address bits coded into tag. + hdrRd64->AddressLow = (DWORD)(qwAddr + (i << 12) + ((i & 0x1f) << 2)); // 5 low address bits coded into the dword read. + hdrRd64->Tag = (BYTE)((i >> 5) & 0x1f); // 5 high address bits coded into tag. } for(j = 0; j < 4; j++) { ENDIAN_SWAP_DWORD(tx[j]); } - Device605_TxTlp(ctx605, (PBYTE)tx, is32 ? 12 : 16); + Device605_UART_TxTlp(ctx605, (PBYTE)tx, is32 ? 12 : 16); } // wait for result WaitForSingleObject(ctx605->hRxBufferEvent, SP605_PROBE_TIMEOUT); ctx605->hRxTlpCallbackFn = NULL; - ctx605->pRxBuffer = NULL; + ctx605->pMRdBuffer = NULL; SetEvent(ctx605->hRxBufferEvent); } -BOOL Device605_WriteDMA_TXP(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwA, _In_ BYTE bFirstBE, _In_ BYTE bLastBE, _In_ PBYTE pb, _In_ DWORD cb) +BOOL Device605_UART_WriteDMA_TXP(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwA, _In_ BYTE bFirstBE, _In_ BYTE bLastBE, _In_ PBYTE pb, _In_ DWORD cb) { PDEVICE_CONTEXT_SP605 ctx605 = (PDEVICE_CONTEXT_SP605)ctx->hDevice; DWORD txbuf[36], i, cbTlp; @@ -391,10 +300,10 @@ BOOL Device605_WriteDMA_TXP(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwA, _In_ memcpy(pbTlp + 16, pb, cb); cbTlp = (16 + cb + 3) & ~0x3; } - return Device605_TxTlp(ctx605, pbTlp, cbTlp); + return Device605_UART_TxTlp(ctx605, pbTlp, cbTlp); } -BOOL Device605_WriteDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwA, _In_ PBYTE pb, _In_ DWORD cb) +BOOL Device605_UART_WriteDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwA, _In_ PBYTE pb, _In_ DWORD cb) { BOOL result = TRUE; BYTE be, pbb[4]; @@ -406,7 +315,7 @@ BOOL Device605_WriteDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwA, _In_ PBYT be <<= qwA & 0x3; cbtx = min(cb, 4 - (qwA & 0x3)); memcpy(pbb + (qwA & 0x3), pb, cbtx); - result = Device605_WriteDMA_TXP(ctx, qwA & ~0x3, be, 0, pbb, 4); + result = Device605_UART_WriteDMA_TXP(ctx, qwA & ~0x3, be, 0, pbb, 4); pb += cbtx; cb -= cbtx; qwA += cbtx; @@ -416,8 +325,8 @@ BOOL Device605_WriteDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwA, _In_ PBYT cbtx = min(128 - (qwA & 0x7f), cb); be = (cbtx & 0x3) ? (0xf >> (4 - (cbtx & 0x3))) : 0xf; result = (cbtx <= 4) ? - Device605_WriteDMA_TXP(ctx, qwA, be, 0, pb, 4) : - Device605_WriteDMA_TXP(ctx, qwA, 0xf, be, pb, cbtx); + Device605_UART_WriteDMA_TXP(ctx, qwA, be, 0, pb, 4) : + Device605_UART_WriteDMA_TXP(ctx, qwA, 0xf, be, pb, cbtx); pb += cbtx; cb -= cbtx; qwA += cbtx; @@ -425,57 +334,80 @@ BOOL Device605_WriteDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwA, _In_ PBYT return result; } -VOID Action_Device605_TlpTx(_Inout_ PPCILEECH_CONTEXT ctx) +BOOL Device605_UART_ListenTlp(_Inout_ PPCILEECH_CONTEXT ctx, _In_ DWORD dwTime) { - if(ctx->cfg->tpDevice != PCILEECH_DEVICE_SP605) { - printf("TLP: Failed. unsupported device.\n"); - return; - } - if(Device605_TxTlp((PDEVICE_CONTEXT_SP605)ctx->hDevice, ctx->cfg->pbIn, (DWORD)ctx->cfg->cbIn)) { - printf("TLP: Success.\n"); - // If no custom exit timeout is set wait 500ms to receive any TLP responses. - if(ctx->cfg->qwWaitBeforeExit == 0) { - Sleep(500); - } - } else { - printf("TLP: Failed. TX error.\n"); - } + Sleep(dwTime); + return TRUE; } -#endif /* WIN32 */ -#if defined(LINUX) || defined(ANDROID) - -#include "device605.h" - -BOOL Device605_Open(_Inout_ PPCILEECH_CONTEXT ctx) +BOOL Device605_UART_WriteTlp(_Inout_ PPCILEECH_CONTEXT ctx, _In_ PBYTE pbTlp, _In_ DWORD cbTlp) { - printf("SP605: Failed. Device only supported in PCILeech for Windows."); - return FALSE; + return Device605_UART_TxTlp((PDEVICE_CONTEXT_SP605)ctx->hDevice, pbTlp, cbTlp); } -VOID Device605_Close(_Inout_ PPCILEECH_CONTEXT ctx) +BOOL Device605_UART_Open(_Inout_ PPCILEECH_CONTEXT ctx) { - return; + DWORD i; + CHAR szCOM[] = { 'C', 'O', 'M', 'x', 0 }; + PDEVICE_CONTEXT_SP605 ctx605; + ctx605 = LocalAlloc(LMEM_ZEROINIT, sizeof(DEVICE_CONTEXT_SP605)); + if(!ctx605) { return FALSE; } + ctx->hDevice = (HANDLE)ctx605; + // open COM ports + for(i = 1; i <= 9; i++) { + szCOM[3] = (CHAR)('0' + i); + if(!ctx605->hCommPcie) { + ctx605->hCommPcie = Device605_UART_Open_COM(szCOM); + } else { + ctx605->hCommCfg = Device605_UART_Open_COM(szCOM); + if(ctx605->hCommCfg) { break; } + } + } + if(!ctx605->hCommPcie || !ctx605->hCommCfg) { goto fail; } + SetupComm(ctx605->hCommPcie, 0x8000, 0x8000); + ctx605->oTx.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if(!ctx605->oTx.hEvent) { goto fail; } + ctx605->oRx.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if(!ctx605->oRx.hEvent) { goto fail; } + ctx605->oCfg.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if(!ctx605->oCfg.hEvent) { goto fail; } + ctx605->hRxBufferEvent = CreateEvent(NULL, TRUE, TRUE, NULL); + if(!ctx605->hRxBufferEvent) { goto fail; } + ctx605->wDeviceId = Device605_UART_GetDeviceID(ctx605); + if(!ctx605->wDeviceId) { goto fail; } + ctx605->isPrintTlp = ctx->cfg->fVerboseExtra; + ctx605->hThreadRx = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Device605_UART_RxTlp_Thread, ctx605, 0, NULL); // start rx thread, must be last in open + if(!ctx605->hThreadRx) { goto fail; } + // set callback functions and fix up config + ctx->cfg->dev.tp = PCILEECH_DEVICE_SP605_UART; + ctx->cfg->dev.qwMaxSizeDmaIo = 0x4000; + ctx->cfg->dev.qwAddrMaxNative = 0x0000ffffffffffff; + ctx->cfg->dev.fPartialPageReadSupported = TRUE; + ctx->cfg->dev.pfnClose = Device605_UART_Close; + ctx->cfg->dev.pfnProbeDMA = Device605_UART_ProbeDMA; + ctx->cfg->dev.pfnReadDMA = Device605_UART_ReadDMA; + ctx->cfg->dev.pfnWriteDMA = Device605_UART_WriteDMA; + ctx->cfg->dev.pfnWriteTlp = Device605_UART_WriteTlp; + ctx->cfg->dev.pfnListenTlp = Device605_UART_ListenTlp; + // return + if(ctx->cfg->fVerbose) { printf("Device Info: SP605 / UART.\n"); } + return TRUE; +fail: + Device605_UART_Close(ctx); + return FALSE; } -VOID Action_Device605_TlpTx(_Inout_ PPCILEECH_CONTEXT ctx) -{ - printf("TLP: Failed. Operation only supported in PCILeech for Windows."); -} +#endif /* WIN32 */ +#if defined(LINUX) || defined(ANDROID) -BOOL Device605_ReadDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb) -{ - return FALSE; -} +#include "device605_uart.h" -BOOL Device605_WriteDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ PBYTE pb, _In_ DWORD cb) +BOOL Device605_UART_Open(_Inout_ PPCILEECH_CONTEXT ctx) { + if(ctx->cfg->dev.tp == PCILEECH_DEVICE_SP605_UART) { + printf("SP605 / UART: Failed. Device currently only supported in PCILeech for Windows."); + } return FALSE; } -VOID Device605_ProbeDMA(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ DWORD cPages, _Out_ __bcount(cPages) PBYTE pbResultMap) -{ - ; -} - #endif /* LINUX || ANDROID */ diff --git a/pcileech/device605_uart.h b/pcileech/device605_uart.h new file mode 100644 index 0000000..9496bc7 --- /dev/null +++ b/pcileech/device605_uart.h @@ -0,0 +1,17 @@ +// device605_uart.h : definitions related to the Xilinx SP605 dev board flashed with @d_olex early access bitstream. (UART communication). +// +// (c) Ulf Frisk, 2017 +// Author: Ulf Frisk, pcileech@frizk.net +// +#ifndef __DEVICE605_UART_H__ +#define __DEVICE605_UART_H__ +#include "pcileech.h" + +/* +* Open a connection to the SP605/UART PCILeech flashed device. +* -- ctx +* -- result +*/ +BOOL Device605_UART_Open(_Inout_ PPCILEECH_CONTEXT ctx); + +#endif /* __DEVICE605_UART_H__ */ diff --git a/pcileech/executor.c b/pcileech/executor.c index 955cb2e..7713f40 100644 --- a/pcileech/executor.c +++ b/pcileech/executor.c @@ -161,7 +161,7 @@ VOID Exec_Callback(_Inout_ PPCILEECH_CONTEXT ctx, _Inout_ PHANDLE phCallback) if(ph->is.bin.seqAck >= ph->os.bin.seq) { return; } cbLength = 0; result = - DeviceReadDMA(ctx, ctx->pk->DMAAddrPhysical + ctx->pk->dataOutExtraOffset, ph->pbDMA, (DWORD)SIZE_PAGE_ALIGN_4K(ctx->pk->dataOutExtraLength), 0) && + DeviceReadDMAEx(ctx, ctx->pk->DMAAddrPhysical + ctx->pk->dataOutExtraOffset, ph->pbDMA, (DWORD)SIZE_PAGE_ALIGN_4K(ctx->pk->dataOutExtraLength), NULL) && (cbLength = fwrite(ph->pbDMA, 1, ctx->pk->dataOutExtraLength, ph->pFileOutput)) && (ctx->pk->dataOutExtraLength == cbLength); ph->qwFileWritten += cbLength; @@ -237,7 +237,7 @@ BOOL Exec_ExecSilent(_Inout_ PPCILEECH_CONTEXT ctx, _In_ LPSTR szShellcodeName, *pcbOut = pk->dataOutExtraLength; *ppbOut = (PBYTE)LocalAlloc(0, SIZE_PAGE_ALIGN_4K(*pcbOut)); if(!*ppbOut) { result = FALSE; goto fail; } - result = DeviceReadDMA(ctx, pk->DMAAddrPhysical + pk->dataOutExtraOffset, *ppbOut, SIZE_PAGE_ALIGN_4K(*pcbOut), 0); + result = SIZE_PAGE_ALIGN_4K(*pcbOut) == DeviceReadDMAEx(ctx, pk->DMAAddrPhysical + pk->dataOutExtraOffset, *ppbOut, SIZE_PAGE_ALIGN_4K(*pcbOut), NULL); } fail: LocalFree(pKmdExec); @@ -337,7 +337,7 @@ VOID ActionExecShellcode(_Inout_ PPCILEECH_CONTEXT ctx) if(cbLength > 0) { // read extra output buffer if(!(pbBuffer = LocalAlloc(LMEM_ZEROINIT, SIZE_PAGE_ALIGN_4K(cbLength))) || - !DeviceReadDMA(ctx, pk->DMAAddrPhysical + pk->dataOutExtraOffset, pbBuffer, SIZE_PAGE_ALIGN_4K(cbLength), 0)) { + !DeviceReadDMAEx(ctx, pk->DMAAddrPhysical + pk->dataOutExtraOffset, pbBuffer, SIZE_PAGE_ALIGN_4K(cbLength), NULL)) { printf("EXEC: Error reading output.\n"); goto fail; } diff --git a/pcileech/extra.c b/pcileech/extra.c index 45b43b2..9014fc1 100644 --- a/pcileech/extra.c +++ b/pcileech/extra.c @@ -16,7 +16,7 @@ VOID Extra_MacFVRecover_ReadMemory_Optimized(_Inout_ PPCILEECH_CONTEXT ctx, _Ino 0x88000000, 0x89000000, 0x8a000000, 0x8b000000, 0x8c000000, 0x8d000000, 0x8e000000, 0x8f000000 }; for(i = 0; i < sizeof(dwOffsets) / sizeof(DWORD); i++) { - DeviceReadDMA(ctx, dwOffsets[i], pb512M + dwOffsets[i] - 0x70000000, 0x01000000, PCILEECH_MEM_FLAG_RETRYONFAIL); + DeviceReadDMAEx(ctx, dwOffsets[i], pb512M + dwOffsets[i] - 0x70000000, 0x01000000, NULL); } } @@ -156,7 +156,7 @@ VOID Action_MacDisableVtd(_Inout_ PPCILEECH_CONTEXT ctx) // DMAR table assumed to be on page boundary. This doesn't have to be true, // but it seems like it is on the MACs. for(i = 0; i < sizeof(dwOffsets) / sizeof(DWORD); i++) { - if(DeviceReadDMA(ctx, dwOffsets[i], pb16M, 0x01000000, 0)) { + if(DeviceReadDMAEx(ctx, dwOffsets[i], pb16M, 0x01000000, NULL)) { for(j = 0; j < 0x01000000; j += 0x1000) { if(*(PQWORD)(pb16M + j) == 0x0000008852414d44) { dwAddress = dwOffsets[i] + j; @@ -193,3 +193,13 @@ VOID Action_PT_Phys2Virt(_Inout_ PPCILEECH_CONTEXT ctx) printf("PT_PHYS2VIRT: Failed.\n"); } } + +VOID Action_TlpTx(_Inout_ PPCILEECH_CONTEXT ctx) +{ + if(ctx->cfg->cbIn < 12) { + printf("Action_TlpTx: Invalid TLP (too short).\n"); + } + printf("TLP: Transmitting PCIe TLP.%s\n", ctx->cfg->fVerboseExtra ? "" : " (use -vv option for detailed info)."); + DeviceWriteTlp(ctx, ctx->cfg->pbIn, (DWORD)ctx->cfg->cbIn); + DeviceListenTlp(ctx, 100); +} diff --git a/pcileech/extra.h b/pcileech/extra.h index f1e2f92..b5b319c 100644 --- a/pcileech/extra.h +++ b/pcileech/extra.h @@ -33,4 +33,10 @@ VOID Action_MacDisableVtd(_Inout_ PPCILEECH_CONTEXT ctx); */ VOID Action_PT_Phys2Virt(_Inout_ PPCILEECH_CONTEXT ctx); +/* +* Transmit the TLP data specified in the -in parameter. +* -- ctx +*/ +VOID Action_TlpTx(_Inout_ PPCILEECH_CONTEXT ctx); + #endif /* __EXTRA_H__ */ diff --git a/pcileech/help.c b/pcileech/help.c index 0e9e68b..8619204 100644 --- a/pcileech/help.c +++ b/pcileech/help.c @@ -32,8 +32,9 @@ VOID Help_ShowGeneral() " erted KMD. The already inserted KMD will be left intact upon exit. If the KMD\n" \ " contains a kernel mode signature the kernel module will be loaded and then un-\n" \ " loaded on program exit ( except for the kmdload command ). \n" \ - " KMD mode may access all memory. DMA mode may only access memory below 4GB if\n" \ - " USB3380 hardware is used. Commands marked 'W' are only available on Windows.\n" \ + " KMD mode may access all memory (available to the kernel of the target system).\n" \ + " DMA mode may only access lower 4GB of memory if USB3380 hardware is used. \n" \ + " DMA mode may access all memory if FPGA based hardware such as SP605 is used. \n" \ " For detailed help about a specific command type: pcileech.exe -help\n" \ " General syntax: pcileech.exe [- ] ... \n" \ " Valid commands and valid MODEs [ and options ] \n" \ @@ -45,17 +46,17 @@ VOID Help_ShowGeneral() " [implant] KMD [ in, out, s, 0..9 ] \n" \ " kmdload DMA [ pt, cr3 ] \n" \ " kmdexit KMD \n" \ - " mount W KMD [ s ] \n" \ + " mount KMD [ s ] (Windows only feature) \n" \ " pagedisplay DMA,KMD [ min ] \n" \ " pt_phys2virt DMA,KMD [ cr3, 0 ] \n" \ " testmemread DMA [ min ] \n" \ " testmemreadwrite DMA [ min ] \n" \ " Device specific commands and valid MODEs [ and options ] (and device): \n" \ - " usb3380_flash DMA,KMD [ in ] (USB3380) \n" \ - " usb3380_8051start DMA,KMD [ in ] (USB3380) \n" \ - " usb3380_8051stop DMA,KMD (USB3380) \n" \ - " tlp W DMA [ in ] (SP605) \n" \ - " probe W DMA [ in ] (SP605) \n" \ + " usb3380_flash DMA,KMD [ in ] (USB3380) \n" \ + " usb3380_8051start DMA,KMD [ in ] (USB3380) \n" \ + " usb3380_8051stop DMA,KMD (USB3380) \n" \ + " tlp DMA [ in ] (FPGA) \n" \ + " probe DMA [ in ] (FPGA) \n" \ " System specific commands and valid MODEs [ and options ]: \n" \ " mac_fvrecover DMA \n" \ " mac_fvrecover2 DMA \n" \ @@ -72,6 +73,7 @@ VOID Help_ShowGeneral() " -all : search all memory for signature - do not stop at first occurrence. \n" \ " Option has no value. Example: -all \n" \ " -vv : extra verbose option. Same as -v but even more detailed output. \n" \ + " Option shows raw PCIe TLPs received/sent if FPGA is used. \n" \ " -v : verbose option. Additional information is displayed in the output. \n" \ " The memory map is shown when searching/dumping memory as an example. \n" \ " Affects all modes and commands. \n" \ @@ -85,11 +87,11 @@ VOID Help_ShowGeneral() " Option has no value. Example: -usb2 \n" \ " -iosize: max DMA i/o size. Hardware DMA requests larger than iosize will \n" \ " be discarded. Affects all modes and commands. \n" \ - " -wait: wait in seconds before exit. Useful when viewing received PCIe TLPs. \n" \ + " -tlpwait: Wait in seconds while listening for PCIe TLPs. \n" \ + " Wait occurs after any other actions have been completed. \n" \ + " -device: force the use of a specific hardware device instead of auto-detect.\n" \ " Affects all modes and commands. \n" \ - " -device: specify a hardware device other than the USB3380 to use. \n" \ - " Affects all modes and commands. \n" \ - " Valid options: USB3380, SP605 \n" \ + " Valid options: USB3380, SP605_UART, SP605_FT601 \n" \ " -help: show help about the selected command or implant and then exit \n" \ " without running the command. Affects all modes and commands. \n" \ " Option has no value. Example: -help \n" \ @@ -136,7 +138,7 @@ VOID Help_ShowInfo() printf( " PCILEECH INFORMATION \n" \ " PCILeech (c) 2016, 2017 Ulf Frisk \n" \ - " Version: 2.2 \n" \ + " Version: 2.3 \n" \ " License: GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 \n" \ " Contact information: pcileech@frizk.net \n" \ " System requirements: 64-bit Windows 7, 10 or Linux. \n" \ @@ -145,9 +147,11 @@ VOID Help_ShowInfo() " Slotscreamer - https://github.com/NSAPlayset/SLOTSCREAMER \n" \ " Inception - https://github.com/carmaa/inception \n" \ " Google USB driver - https://developer.android.com/sdk/win-usb.html#download \n" \ + " FTDI FT601: - http://www.ftdichip.com/Drivers/D3XX.htm \n" \ " Dokany - https://github.com/dokan-dev/dokany/releases/latest \n" \ " ---------------- \n" \ " Use with USB3380 hardware programmed as a PCILeech device. \n" \ + " Use with SP605/FT601 harware programmed as a PCILeech FPGA device. \n" \ " Use with SP605 hardware / 'PCI Express DIY hacking toolkit' by cr4sh/@d_olex. \n\n" \ " ---------------- \n" \ " Driver information (USB3380/Windows): \n" \ @@ -155,6 +159,12 @@ VOID Help_ShowInfo() " device masks as a Google Glass. Please download and install the Google USB \n" \ " driver before proceeding by using the USB3380 device. USB3 is recommended \n" \ " to performance reasons (USB2 will work but impact performance). \n" \ + " Driver information (SP605/FT601/Windows): \n" \ + " The PCILeech programmed SP605 FPGA development board with the FT601 USB3 \n" \ + " addon board requires drivers for Windows. The drivers are on Windows update \n" \ + " and is installed automatically at first use. PCILeech also requires the FTDI\n" \ + " application library (DLL). Download this library from the FTDI web site and \n" \ + " place the 64-bit FTD3XX.dll alongside pcileech.exe. \n" \ " Driver information (Dokany/Windows): \n" \ " To be able to use the 'mount' functionality for filesystem browsing and live\n" \ " memory file access PCILeech requires Dokany to be installed for virtual file\n" \ diff --git a/pcileech/kmd.c b/pcileech/kmd.c index 4917f68..7123c3c 100644 --- a/pcileech/kmd.c +++ b/pcileech/kmd.c @@ -92,12 +92,11 @@ BOOL KMD_FindSignature1(_Inout_ PPCILEECH_CONTEXT ctx, _Inout_ PSIGNATURE pSigna return FALSE; } // loop kmd-find - qwAddrMax = min(ctx->cfg->qwAddrMax, ((ctx->cfg->tpDevice == PCILEECH_DEVICE_USB3380) ? 0xffffffffUL : 0x0000ffffffffffffULL)); + qwAddrMax = min(ctx->cfg->qwAddrMax, ctx->cfg->dev.qwAddrMaxNative); PageStatInitialize(&pageStat, qwAddrCurrent, qwAddrMax, "Searching for KMD location", FALSE, FALSE); while(qwAddrCurrent < qwAddrMax) { pageStat.qwAddr = qwAddrCurrent; - if(DeviceReadDMA(ctx, qwAddrCurrent, pbBuffer8M, 0x800000, 0)) { - pageStat.cPageSuccess += 2048; + if(DeviceReadDMAEx(ctx, qwAddrCurrent, pbBuffer8M, 0x800000, &pageStat)) { result = KMD_FindSignature2(pbBuffer8M, 2048, qwAddrCurrent, pSignatures, cSignatures, pdwSignatureMatchIdx); if(result) { LocalFree(pbBuffer8M); @@ -105,8 +104,6 @@ BOOL KMD_FindSignature1(_Inout_ PPCILEECH_CONTEXT ctx, _Inout_ PSIGNATURE pSigna PageStatClose(&pageStat); return TRUE; } - } else { - pageStat.cPageFail += 2048; } qwAddrCurrent += 0x800000; } @@ -210,7 +207,7 @@ BOOL KMD_MacOSKernelSeekSignature(_Inout_ PPCILEECH_CONTEXT ctx, _Out_ PSIGNATUR cbTextHIB = (cbTextHIB + 0xfff) & 0xfffff000; pbTextHIB = LocalAlloc(0, cbTextHIB); if(!pbTextHIB) { return FALSE; } - if(!DeviceReadDMA(ctx, dwTextHIB, pbTextHIB, cbTextHIB, PCILEECH_MEM_FLAG_RETRYONFAIL)) { + if(!DeviceReadDMAEx(ctx, dwTextHIB, pbTextHIB, cbTextHIB, NULL)) { LocalFree(pbTextHIB); return FALSE; } @@ -235,7 +232,7 @@ BOOL KMD_FreeBSDKernelSeekSignature(_Inout_ PPCILEECH_CONTEXT ctx, _Out_ PSIGNAT PBYTE pb64M = LocalAlloc(LMEM_ZEROINIT, 0x04000000); if(!pb64M) { return FALSE; } for(i = 0x01000000; i < 0x04000000; i += 0x01000000) { - DeviceReadDMA(ctx, i, pb64M + i, 0x01000000, PCILEECH_MEM_FLAG_RETRYONFAIL); + DeviceReadDMAEx(ctx, i, pb64M + i, 0x01000000, NULL); } // 1: search for string 'vn_open' i = 0; @@ -357,7 +354,7 @@ BOOL KMD_Linux46KernelSeekSignature(_Inout_ PPCILEECH_CONTEXT ctx, _Out_ PSIGNAT // read 16M of memory first, if KASLR read 2M chunks at top of analysis buffer (performance reasons). dwKernelBase = 0x01000000 + cKSlide * 0x00200000; // KASLR = 16M + ([RND:0..511] * 2M) ??? if(cKSlide == 0) { - DeviceReadDMA(ctx, dwKernelBase, pb, 0x01000000, PCILEECH_MEM_FLAG_RETRYONFAIL); + DeviceReadDMAEx(ctx, dwKernelBase, pb, 0x01000000, NULL); } else { memmove(pb, pb + 0x00200000, CONFIG_LINUX_SEEK_BUFFER_SIZE - 0x00200000); result = DeviceReadDMA( @@ -388,16 +385,16 @@ QWORD KMD_Linux48KernelBaseSeek(_Inout_ PPCILEECH_CONTEXT ctx) memset(pbCMP90, 0x90, 0x400); memset(pbCMP00, 0x00, 0x100); qwA = max(0x01000000, ctx->cfg->qwAddrMin) & 0xffffffffffe00000; - qwAddrMax = (ctx->cfg->tpDevice == PCILEECH_DEVICE_USB3380) ? - max(0x01000000, (ctx->cfg->qwAddrMax - 0x01000000) & 0xffe00000) : - max(0x01000000, (ctx->cfg->qwAddrMax - 0x01000000) & 0xffffffffffe00000); + qwAddrMax = max(0x01000000, (ctx->cfg->dev.qwAddrMaxNative - 0x01000000) & 0xffffffffffe00000); PageStatInitialize(&ps, qwA, qwAddrMax, "Scanning for Linux kernel base", FALSE, FALSE); // Linux kernel uses 2MB pages. Base of kernel is assumed to have AuthenticAMD and GenuineIntel strings // in first page. First page should also end with at least 0x400 0x90's. 2nd page (hypercall page?) is // assumed to end with 0x100 0x00's. for(; qwA <= qwAddrMax; qwA += 0x00200000) { ps.qwAddr = qwA; - if(!DeviceReadDMA(ctx, qwA, pb, ctx->cfg->fPartialPageReadSupported ? 0x400 : 0x1000, 0)) { // only read partial page to speed up SP605 if connected via UART + // only read partial page to speed up SP605 if connected via UART + // TODO: investigate if this is slower/faster than reading whole pages on FPGA solution. + if(!DeviceReadDMA(ctx, qwA, pb, ctx->cfg->fPartialPageReadSupported ? 0x400 : 0x1000, 0)) { ps.cPageFail += 512; continue; } @@ -576,8 +573,7 @@ BOOL KMDOpen_UEFI_FindEfiBase(_Inout_ PPCILEECH_CONTEXT ctx) PageStatInitialize(&ps, dwAddrCurrent, dwAddrMax, "Searching for EFI BASE", FALSE, FALSE); // loop EFI BASE (IBI SYST) find while(dwAddrCurrent <= dwAddrMax - 0x100000) { - if(DeviceReadDMA(ctx, dwAddrCurrent, pb, 0x100000, 0)) { - PageStatUpdate(&ps, dwAddrCurrent + 0x100000, 0x100, 0); + if(DeviceReadDMAEx(ctx, dwAddrCurrent, pb, 0x100000, &ps)) { for(o = 0; o < 0x100000 - 0x100; o += 8) { if(0x5453595320494249 != *(PQWORD)(pb + o)) { continue; } // IBI SYST qwAddr_BOOTSERV = *(PQWORD)(pb + o + 0x60); @@ -648,8 +644,8 @@ BOOL KMDOpen_UEFI(_Inout_ PPCILEECH_CONTEXT ctx, _In_ BYTE bOffsetHookBootServic // 3: Patch //------------------------------------------------ if(ctx->cfg->fVerbose) { - printf("INFO: IBI SYST: 0x%08x\n", ctx->cfg->qwEFI_IBI_SYST); - printf("INFO: BOOTSERV: 0x%08x\n", qwAddrEFI_BOOTSERV); + printf("INFO: IBI SYST: 0x%08x\n", (DWORD)ctx->cfg->qwEFI_IBI_SYST); + printf("INFO: BOOTSERV: 0x%08x\n", (DWORD)qwAddrEFI_BOOTSERV); } result = DeviceWriteDMA(ctx, qwAddrKMDDATA, pb, 0x2000, PCILEECH_MEM_FLAG_RETRYONFAIL); if(!result) { @@ -848,7 +844,7 @@ BOOL KMD_GetPhysicalMemoryMap(_Inout_ PPCILEECH_CONTEXT ctx) } ctx->phKMD->pPhysicalMap = LocalAlloc(LMEM_ZEROINIT, (ctx->pk->_size + 0x1000) & 0xfffff000); if(!ctx->phKMD->pPhysicalMap) { return FALSE; } - DeviceReadDMA(ctx, ctx->pk->DMAAddrPhysical, (PBYTE)ctx->phKMD->pPhysicalMap, (DWORD)((ctx->pk->_size + 0x1000) & 0xfffff000), 0); + DeviceReadDMAEx(ctx, ctx->pk->DMAAddrPhysical, (PBYTE)ctx->phKMD->pPhysicalMap, (DWORD)((ctx->pk->_size + 0x1000) & 0xfffff000), NULL); ctx->phKMD->cPhysicalMap = ctx->pk->_size / sizeof(PHYSICAL_MEMORY_RANGE); if(ctx->phKMD->cPhysicalMap > 0x2000) { return FALSE; } // adjust max memory according to physical memory @@ -904,7 +900,7 @@ BOOL KMDReadMemory_DMABufferSized(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAd if(!result) { return FALSE; } result = KMD_SubmitCommand(ctx, KMD_CMD_READ); if(!result) { return FALSE; } - return DeviceReadDMA(ctx, ctx->pk->DMAAddrPhysical, pb, cb, 0) && ctx->pk->_result; + return (cb == DeviceReadDMAEx(ctx, ctx->pk->DMAAddrPhysical, pb, cb, NULL)) && ctx->pk->_result; } BOOL KMDWriteMemory_DMABufferSized(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddress, _In_ PBYTE pb, _In_ DWORD cb) diff --git a/pcileech/memdump.c b/pcileech/memdump.c index 376d024..26fb69c 100644 --- a/pcileech/memdump.c +++ b/pcileech/memdump.c @@ -127,7 +127,7 @@ VOID ActionMemoryDump(_Inout_ PPCILEECH_CONTEXT ctx) } } -#define MEMORY_PROBE_PAGES_PER_SWEEP 0x400 +#define MEMORY_PROBE_PAGES_PER_SWEEP 0x1000 VOID ActionMemoryProbe(_Inout_ PPCILEECH_CONTEXT ctx) { @@ -214,7 +214,7 @@ VOID ActionMemoryWrite(_Inout_ PPCILEECH_CONTEXT ctx) printf("Memory Write: Failed. No data to write.\n"); return; } - if(ctx->cfg->cbIn >= 0x01000000) { + if(ctx->cfg->cbIn > 0x01000000) { printf("Memory Write: Failed. Data too large: >16MB.\n"); return; } diff --git a/pcileech/oscompatibility.c b/pcileech/oscompatibility.c index dc42673..7497ea5 100644 --- a/pcileech/oscompatibility.c +++ b/pcileech/oscompatibility.c @@ -3,6 +3,22 @@ // (c) Ulf Frisk, 2017 // Author: Ulf Frisk, pcileech@frizk.net // +#ifdef WIN32 + +#include "oscompatibility.h" + +VOID usleep(_In_ DWORD us) +{ + QWORD tmFreq, tmStart, tmNow, tmThreshold; + QueryPerformanceFrequency((PLARGE_INTEGER)&tmFreq); + tmThreshold = tmFreq * us / (1000 * 1000); // dw_uS uS + QueryPerformanceCounter((PLARGE_INTEGER)&tmStart); + while(QueryPerformanceCounter((PLARGE_INTEGER)&tmNow) && ((tmNow - tmStart) < tmThreshold)) { + ; + } +} + +#endif /* WIN32 */ #if defined(LINUX) || defined(ANDROID) #include "oscompatibility.h" @@ -135,6 +151,11 @@ BOOL WinUsb_Free(WINUSB_INTERFACE_HANDLE InterfaceHandle) return TRUE; } +DWORD InterlockedAdd(DWORD *Addend, DWORD Value) +{ + return __sync_add_and_fetch(Addend, Value); +} + #endif /* LINUX || ANDROID */ #ifdef LINUX diff --git a/pcileech/oscompatibility.h b/pcileech/oscompatibility.h index 2e7affc..8a5ae0d 100644 --- a/pcileech/oscompatibility.h +++ b/pcileech/oscompatibility.h @@ -23,6 +23,8 @@ typedef unsigned __int64 QWORD, *PQWORD; #pragma warning( disable : 4477) +VOID usleep(_In_ DWORD us); + #endif /* WIN32 */ #ifdef LINUX @@ -114,6 +116,7 @@ typedef uint64_t SIZE_T, *PSIZE_T; #define ZeroMemory(pb, cb) (memset(pb, 0, cb)) #define WinUsb_SetPipePolicy(h, p, t, cb, pb) // TODO: implement this for better USB2 performance. #define CloseHandle(h) // TODO: remove this dummy implementation & replace with WARN. +#define SetEvent(h) // TODO: remove this dummy implementation! typedef struct _SYSTEMTIME { WORD wYear; @@ -137,6 +140,7 @@ HANDLE LocalAlloc(DWORD uFlags, SIZE_T uBytes); VOID LocalFree(HANDLE hMem); QWORD GetTickCount64(); VOID GetLocalTime(LPSYSTEMTIME lpSystemTime); +DWORD InterlockedAdd(DWORD *Addend, DWORD Value); BOOL WinUsb_Free(WINUSB_INTERFACE_HANDLE InterfaceHandle); HANDLE CreateThread( diff --git a/pcileech/pcileech.c b/pcileech/pcileech.c index 52ce0d0..2bb26f3 100644 --- a/pcileech/pcileech.c +++ b/pcileech/pcileech.c @@ -6,7 +6,6 @@ #include "pcileech.h" #include "device.h" #include "device3380.h" -#include "device605.h" #include "executor.h" #include "extra.h" #include "help.h" @@ -16,7 +15,7 @@ #include "kmd.h" #include "vfs.h" -BOOL PCILeechInitializeConfig(_In_ DWORD argc, _In_ char* argv[], _Inout_ PPCILEECH_CONTEXT ctx) +BOOL PCILeechConfigIntialize(_In_ DWORD argc, _In_ char* argv[], _Inout_ PPCILEECH_CONTEXT ctx) { struct ACTION { ACTION_TYPE tp; @@ -44,7 +43,6 @@ BOOL PCILeechInitializeConfig(_In_ DWORD argc, _In_ char* argv[], _Inout_ PPCILE {.tp = TLP,.sz = "tlp" }, {.tp = PROBE,.sz = "probe" }, }; - QWORD qw; DWORD j, i = 1; if(argc < 2) { return FALSE; } // allocate memory for config struct @@ -55,7 +53,6 @@ BOOL PCILeechInitializeConfig(_In_ DWORD argc, _In_ char* argv[], _Inout_ PPCILE ctx->cfg->qwAddrMax = ~0; ctx->cfg->fOutFile = TRUE; ctx->cfg->qwMaxSizeDmaIo = ~0; - ctx->cfg->tpDevice = PCILEECH_DEVICE_USB3380; // fetch command line actions/options loop: while(i < argc) { @@ -114,14 +111,17 @@ BOOL PCILeechInitializeConfig(_In_ DWORD argc, _In_ char* argv[], _Inout_ PPCILE ctx->cfg->qwEFI_IBI_SYST = Util_GetNumeric(argv[i + 1]); } else if(0 == strcmp(argv[i], "-iosize")) { ctx->cfg->qwMaxSizeDmaIo = Util_GetNumeric(argv[i + 1]); - } else if(0 == strcmp(argv[i], "-wait")) { - ctx->cfg->qwWaitBeforeExit = Util_GetNumeric(argv[i + 1]); + ctx->cfg->qwMaxSizeDmaIo = ~0xfff & max(0x1000, ctx->cfg->qwMaxSizeDmaIo); + } else if(0 == strcmp(argv[i], "-tlpwait")) { + ctx->cfg->dwListenTlpTimeMs = (DWORD)(1000 * Util_GetNumeric(argv[i + 1])); } else if(0 == strcmp(argv[i], "-device")) { - ctx->cfg->tpDevice = PCILEECH_DEVICE_NA; + ctx->cfg->dev.tp = PCILEECH_DEVICE_NA; if(0 == _stricmp(argv[i + 1], "usb3380")) { - ctx->cfg->tpDevice = PCILEECH_DEVICE_USB3380; - } else if(0 == _stricmp(argv[i + 1], "sp605")) { - ctx->cfg->tpDevice = PCILEECH_DEVICE_SP605; + ctx->cfg->dev.tp = PCILEECH_DEVICE_USB3380; + } else if(0 == _stricmp(argv[i + 1], "sp605_uart")) { + ctx->cfg->dev.tp = PCILEECH_DEVICE_SP605_UART; + } else if(0 == _stricmp(argv[i + 1], "sp605_ft601")) { + ctx->cfg->dev.tp = PCILEECH_DEVICE_SP605_FT601; } } else if(0 == strcmp(argv[i], "-out")) { if((0 == _stricmp(argv[i + 1], "none")) || (0 == _stricmp(argv[i + 1], "null"))) { @@ -130,7 +130,10 @@ BOOL PCILeechInitializeConfig(_In_ DWORD argc, _In_ char* argv[], _Inout_ PPCILE strcpy_s(ctx->cfg->szFileOut, MAX_PATH, argv[i + 1]); } } else if(0 == strcmp(argv[i], "-in")) { - if(!Util_ParseHexFileBuiltin(argv[i + 1], ctx->cfg->pbIn, CONFIG_MAX_INSIZE, (PDWORD)&ctx->cfg->cbIn)) { return FALSE; } + ctx->cfg->cbIn = max(0x40000, 0x1000 + Util_GetFileSize(argv[i + 1])); + ctx->cfg->pbIn = LocalAlloc(LMEM_ZEROINIT, ctx->cfg->cbIn); + if(!ctx->cfg->pbIn) { return FALSE; } + if(!Util_ParseHexFileBuiltin(argv[i + 1], ctx->cfg->pbIn, (DWORD)ctx->cfg->cbIn, (PDWORD)&ctx->cfg->cbIn)) { return FALSE; } } else if(0 == strcmp(argv[i], "-s")) { strcpy_s(ctx->cfg->szInS, MAX_PATH, argv[i + 1]); } else if(0 == strcmp(argv[i], "-sig")) { @@ -147,24 +150,25 @@ BOOL PCILeechInitializeConfig(_In_ DWORD argc, _In_ char* argv[], _Inout_ PPCILE } i += 2; } + if(!ctx->cfg->pbIn) { + ctx->cfg->pbIn = LocalAlloc(LMEM_ZEROINIT, 0x40000); + } // try correct erroneous options, if needed - if((ctx->cfg->tpAction == NA) || (ctx->cfg->tpDevice == PCILEECH_DEVICE_NA)) { + if(ctx->cfg->tpAction == NA) { return FALSE; } + return TRUE; +} + +VOID PCILeechConfigFixup(_Inout_ PPCILEECH_CONTEXT ctx) +{ + QWORD qw; // device specific configuration - if(ctx->cfg->tpDevice == PCILEECH_DEVICE_USB3380) { - ctx->cfg->qwAddrMaxDeviceNative = 0xffffffff; - ctx->cfg->qwMaxSizeDmaIo = min(0x01000000, ctx->cfg->qwMaxSizeDmaIo); - } - if(ctx->cfg->tpDevice == PCILEECH_DEVICE_SP605) { - ctx->cfg->qwAddrMaxDeviceNative = 0x0000ffffffffffff; - ctx->cfg->qwMaxSizeDmaIo = min(0x00004000, ctx->cfg->qwMaxSizeDmaIo); - ctx->cfg->fPartialPageReadSupported = TRUE; - } + ctx->cfg->qwMaxSizeDmaIo = min(ctx->cfg->qwMaxSizeDmaIo, ctx->cfg->dev.qwMaxSizeDmaIo); // no kmd -> max address == max address that device support if(!ctx->cfg->szKMDName[0] && !ctx->cfg->qwKMD) { - if(ctx->cfg->qwAddrMax == 0 || ctx->cfg->qwAddrMax > ctx->cfg->qwAddrMaxDeviceNative) { - ctx->cfg->qwAddrMax = ctx->cfg->qwAddrMaxDeviceNative; + if(ctx->cfg->qwAddrMax == 0 || ctx->cfg->qwAddrMax > ctx->cfg->dev.qwAddrMaxNative) { + ctx->cfg->qwAddrMax = ctx->cfg->dev.qwAddrMaxNative; } } // fixup addresses @@ -175,7 +179,6 @@ BOOL PCILeechInitializeConfig(_In_ DWORD argc, _In_ char* argv[], _Inout_ PPCILE } ctx->cfg->qwCR3 &= ~0xfff; ctx->cfg->qwKMD &= ~0xfff; - return TRUE; } VOID PCILeechFreeContext(_Inout_ PPCILEECH_CONTEXT ctx) @@ -183,7 +186,10 @@ VOID PCILeechFreeContext(_Inout_ PPCILEECH_CONTEXT ctx) if(!ctx) { return; } KMDClose(ctx); DeviceClose(ctx); - if(ctx->cfg) { LocalFree(ctx->cfg); } + if(ctx->cfg) { + if(ctx->cfg->pbIn) { LocalFree(ctx->cfg->pbIn); } + LocalFree(ctx->cfg); + } if(ctx) { LocalFree(ctx); } } @@ -198,11 +204,11 @@ int main(_In_ int argc, _In_ char* argv[]) printf("PCILEECH: Out of memory.\n"); return 1; } - result = PCILeechInitializeConfig((DWORD)argc, argv, ctx); + result = PCILeechConfigIntialize((DWORD)argc, argv, ctx); if(!result) { Help_ShowGeneral(); PCILeechFreeContext(ctx); - return FALSE; + return 1; } if(ctx->cfg->tpAction == EXEC) { result = Util_LoadKmdExecShellcode(ctx->cfg->szShellcodeName, &pKmdExec); @@ -234,6 +240,7 @@ int main(_In_ int argc, _In_ char* argv[]) PCILeechFreeContext(ctx); return 1; } + PCILeechConfigFixup(ctx); // post device config adjustments if(ctx->cfg->szKMDName[0] || ctx->cfg->qwKMD) { result = KMDOpen(ctx); if(!result) { @@ -267,7 +274,7 @@ int main(_In_ int argc, _In_ char* argv[]) } else if(ctx->cfg->tpAction == PT_PHYS2VIRT) { Action_PT_Phys2Virt(ctx); } else if(ctx->cfg->tpAction == TLP) { - Action_Device605_TlpTx(ctx); + Action_TlpTx(ctx); } else if(ctx->cfg->tpAction == PROBE) { ActionMemoryProbe(ctx); } else if(ctx->cfg->tpAction == MOUNT) { @@ -284,11 +291,11 @@ int main(_In_ int argc, _In_ char* argv[]) } else { printf("Failed. Not yet implemented.\n"); } - if((ctx->cfg->tpAction != KMDLOAD) && !ctx->cfg->fAddrKMDSetByArgument) { + if(ctx->phKMD && (ctx->cfg->tpAction != KMDLOAD) && !ctx->cfg->fAddrKMDSetByArgument) { KMDUnload(ctx); printf("KMD: Hopefully unloaded.\n"); } - Sleep(1000 * (DWORD)ctx->cfg->qwWaitBeforeExit); + DeviceListenTlp(ctx, ctx->cfg->dwListenTlpTimeMs); PCILeechFreeContext(ctx); ExitProcess(0); return 0; diff --git a/pcileech/pcileech.h b/pcileech/pcileech.h index 219adae..70772e3 100644 --- a/pcileech/pcileech.h +++ b/pcileech/pcileech.h @@ -17,6 +17,8 @@ typedef struct tdSignaturePTE { } SIGNATUREPTE, *PSIGNATUREPTE; #pragma pack(pop) /* RE-ENABLE STRUCT PADDINGS */ +typedef struct tdPCILEECH_CONTEXT PCILEECH_CONTEXT, *PPCILEECH_CONTEXT; + typedef enum tdActionType { NA, INFO, @@ -45,40 +47,54 @@ typedef enum tdActionType { typedef enum tdPCILEECH_DEVICE_TYPE { PCILEECH_DEVICE_NA, PCILEECH_DEVICE_USB3380, - PCILEECH_DEVICE_SP605 + PCILEECH_DEVICE_SP605_UART, + PCILEECH_DEVICE_SP605_FT601 } PCILEECH_DEVICE_TYPE; -#define CONFIG_MAX_INSIZE 0x400000 // 4MB +typedef struct tdDeviceConfig { + QWORD qwMaxSizeDmaIo; + QWORD qwAddrMaxNative; + PCILEECH_DEVICE_TYPE tp; + BOOL fPartialPageReadSupported; + BOOL(*pfnReadDMA)(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _Out_ PBYTE pb, _In_ DWORD cb); + BOOL(*pfnWriteDMA)(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ PBYTE pb, _In_ DWORD cb); + VOID(*pfnProbeDMA)(_Inout_ PPCILEECH_CONTEXT ctx, _In_ QWORD qwAddr, _In_ DWORD cPages, _Out_ __bcount(cPages) PBYTE pbResultMap); + BOOL(*pfnWriteTlp)(_Inout_ PPCILEECH_CONTEXT ctx, _In_ PBYTE pb, _In_ DWORD cb); + BOOL(*pfnListenTlp)(_Inout_ PPCILEECH_CONTEXT ctx, _In_ DWORD dwTime); + VOID(*pfnClose)(_Inout_ PPCILEECH_CONTEXT ctx); +} DEVICE_CONFIG; + typedef struct tdConfig { QWORD qwAddrMin; QWORD qwAddrMax; - QWORD qwAddrMaxDeviceNative; QWORD qwCR3; QWORD qwEFI_IBI_SYST; QWORD qwKMD; CHAR szFileOut[MAX_PATH]; - BYTE pbIn[CONFIG_MAX_INSIZE]; + PBYTE pbIn; QWORD cbIn; CHAR szInS[MAX_PATH]; QWORD qwDataIn[10]; ACTION_TYPE tpAction; - PCILEECH_DEVICE_TYPE tpDevice; CHAR szSignatureName[MAX_PATH]; CHAR szKMDName[MAX_PATH]; CHAR szShellcodeName[MAX_PATH]; QWORD qwMaxSizeDmaIo; - QWORD qwWaitBeforeExit; + DWORD dwListenTlpTimeMs; + // flags below BOOL fPageTableScan; BOOL fPatchAll; BOOL fForceRW; BOOL fShowHelp; BOOL fOutFile; - BOOL fForceUsb2; + BOOL fForceUsb2; // USB3380 BOOL fVerbose; BOOL fVerboseExtra; BOOL fDebug; BOOL fPartialPageReadSupported; BOOL fAddrKMDSetByArgument; + // device information below + DEVICE_CONFIG dev; } CONFIG, *PCONFIG; #define SIGNATURE_CHUNK_TP_OFFSET_FIXED 0 @@ -193,11 +209,11 @@ typedef struct tdKMDHANDLE { BYTE pbPageData[4096]; } KMDHANDLE, *PKMDHANDLE; -typedef struct tdPCILEECH_CONTEXT { +struct tdPCILEECH_CONTEXT { PCONFIG cfg; HANDLE hDevice; PKMDHANDLE phKMD; PKMDDATA pk; -} PCILEECH_CONTEXT, *PPCILEECH_CONTEXT; +}; #endif /* __PCILEECH_H__ */ diff --git a/pcileech/pcileech.vcxproj b/pcileech/pcileech.vcxproj index 16b4132..475b87b 100644 --- a/pcileech/pcileech.vcxproj +++ b/pcileech/pcileech.vcxproj @@ -85,7 +85,8 @@ - + + @@ -104,7 +105,8 @@ - + + diff --git a/pcileech/pcileech.vcxproj.filters b/pcileech/pcileech.vcxproj.filters index 5029427..78612b6 100644 --- a/pcileech/pcileech.vcxproj.filters +++ b/pcileech/pcileech.vcxproj.filters @@ -57,9 +57,6 @@ Header Files\dokan - - Header Files - Header Files @@ -69,6 +66,12 @@ Header Files + + Header Files + + + Header Files + @@ -104,9 +107,6 @@ Source Files - - Source Files - Source Files @@ -116,6 +116,12 @@ Source Files + + Source Files + + + Source Files + diff --git a/pcileech/statistics.c b/pcileech/statistics.c index ecf8fb6..39e2039 100644 --- a/pcileech/statistics.c +++ b/pcileech/statistics.c @@ -28,7 +28,7 @@ VOID _PageStatPrintMemMap(_Inout_ PPAGE_STATISTICS ps) if(!ps->i.MemMap[i] || (i == PAGE_STATISTICS_MEM_MAP_MAX_ENTRY - 1)) { break; } - qwAddrEnd = qwAddrBase + ps->i.MemMap[i] * 0x1000; + qwAddrEnd = qwAddrBase + 0x1000 * (QWORD)ps->i.MemMap[i]; if((i % 2) == 0) { fIsLinePrinted = TRUE; printf( diff --git a/pcileech/statistics.h b/pcileech/statistics.h index 7edcb8f..65af539 100644 --- a/pcileech/statistics.h +++ b/pcileech/statistics.h @@ -7,7 +7,7 @@ #define __STATISTICS_H__ #include "pcileech.h" -#define PAGE_STATISTICS_MEM_MAP_MAX_ENTRY 512 +#define PAGE_STATISTICS_MEM_MAP_MAX_ENTRY 1024 typedef struct tdPageStatistics { QWORD qwAddr; diff --git a/pcileech/tlp.c b/pcileech/tlp.c index 6f2de73..7e7cbf9 100644 --- a/pcileech/tlp.c +++ b/pcileech/tlp.c @@ -17,7 +17,7 @@ VOID TLP_Print(_In_ PBYTE pbTlp, _In_ DWORD cbTlp, _In_ BOOL isTx) PTLP_HDR_MRdWr64 hdrM64; if(cbTlp < 12 || cbTlp > 0x1000 || cbTlp & 0x3) { return; } for(i = 0; i < cbTlp; i += 4) { - buf[i] = _byteswap_ulong(*(PDWORD)(pbTlp + i)); + buf[i >> 2] = _byteswap_ulong(*(PDWORD)(pbTlp + i)); } printf("%s_TLP: TypeFmt: %02x Length: %03x(%04x)", (isTx ? "TX" : "RX"), hdr->TypeFmt, hdr->Length, (hdr->Length << 2)); if(hdr->TypeFmt == TLP_CplD) { @@ -28,8 +28,67 @@ VOID TLP_Print(_In_ PBYTE pbTlp, _In_ DWORD cbTlp, _In_ BOOL isTx) printf("\n%s: ReqID: %04x BE1: %01x BEL: %01x Tag: %02x Addr: %08x", (hdr->TypeFmt == TLP_MRd32) ? "MRd32" : "MWr32", hdrM32->RequesterID, hdrM32->FirstBE, hdrM32->LastBE, hdrM32->Tag, hdrM32->Address); } else if(hdr->TypeFmt == TLP_MRd64 || hdr->TypeFmt == TLP_MWr64) { hdrM64 = (PTLP_HDR_MRdWr64)pb; - printf("\n%s: ReqID: %04x BE1: %01x BEL: %01x Tag: %02x Addr: %016llx", (hdr->TypeFmt == TLP_MRd32) ? "MRr64" : "MWr64", hdrM64->RequesterID, hdrM64->FirstBE, hdrM64->LastBE, hdrM64->Tag, ((QWORD)hdrM64->AddressHigh << 32) + hdrM64->AddressLow); + printf("\n%s: ReqID: %04x BE1: %01x BEL: %01x Tag: %02x Addr: %016llx", (hdr->TypeFmt == TLP_MRd64) ? "MRd64" : "MWr64", hdrM64->RequesterID, hdrM64->FirstBE, hdrM64->LastBE, hdrM64->Tag, ((QWORD)hdrM64->AddressHigh << 32) + hdrM64->AddressLow); } printf("\n"); Util_PrintHexAscii(pbTlp, cbTlp); } + +BOOL TLP_CallbackMRd(_Inout_ PTLP_CALLBACK_BUF_MRd pBufferMRd, _In_ PBYTE pb, _In_ DWORD cb, _In_opt_ HANDLE hEventCompleted) +{ + PTLP_HDR_CplD hdrC = (PTLP_HDR_CplD)pb; + PTLP_HDR hdr = (PTLP_HDR)pb; + PDWORD buf = (PDWORD)pb; + DWORD o, c; + buf[0] = _byteswap_ulong(buf[0]); + if(cb < ((DWORD)hdr->Length << 2) - 12) { return FALSE; } + if((hdr->TypeFmt == TLP_CplD) && pBufferMRd) { + buf[1] = _byteswap_ulong(buf[1]); + buf[2] = _byteswap_ulong(buf[2]); + // NB! read algorithm below only support reading full 4kB pages _or_ + // partial page if starting at page boundry and read is less than 4kB. + o = ((DWORD)hdrC->Tag << 12) + min(0x1000, pBufferMRd->cbMax) - (hdrC->ByteCount ? hdrC->ByteCount : 0x1000); + c = (DWORD)hdr->Length << 2; + if(cb != c + 12) { return FALSE; } + if(o + c <= pBufferMRd->cbMax) { + memcpy(pBufferMRd->pb + o, pb + 12, c); + if(pBufferMRd->cbMax <= (DWORD)InterlockedAdd(&pBufferMRd->cb, c)) { + if(hEventCompleted) { + SetEvent(hEventCompleted); + } + return TRUE; + } + } + } else { + QWORD qwDebug[16]; + memset(qwDebug, 0xcc, 16 * 8); + memcpy(qwDebug, pb, min(cb, 16 * 8)); + DWORD DEBUGX = 0x01; + } + return FALSE; +} + +BOOL TLP_CallbackMRdProbe(_Inout_ PTLP_CALLBACK_BUF_MRd pBufferMRd, _In_ PBYTE pb, _In_ DWORD cb, _In_opt_ HANDLE hEventCompleted) +{ + PTLP_HDR_CplD hdrC = (PTLP_HDR_CplD)pb; + PDWORD buf = (PDWORD)pb; + DWORD i; + if(cb < 16) { return FALSE; } // min size CplD = 16 bytes. + buf[0] = _byteswap_ulong(buf[0]); + buf[1] = _byteswap_ulong(buf[1]); + buf[2] = _byteswap_ulong(buf[2]); + if((hdrC->h.TypeFmt == TLP_CplD) && pBufferMRd) { + // 5 low address bits coded into the dword read, 8 high address bits coded into tag. + i = ((DWORD)hdrC->Tag << 5) + ((hdrC->LowerAddress >> 2) & 0x1f); + if(i < pBufferMRd->cbMax) { + pBufferMRd->pb[i] = 1; + if(pBufferMRd->cbMax <= (DWORD)InterlockedAdd(&pBufferMRd->cb, 1)) { + if(hEventCompleted) { + SetEvent(hEventCompleted); + } + return TRUE; + } + } + } + return FALSE; +} diff --git a/pcileech/tlp.h b/pcileech/tlp.h index 011f001..35add20 100644 --- a/pcileech/tlp.h +++ b/pcileech/tlp.h @@ -1,4 +1,4 @@ -// device605.h : definitions related PCIe TLPs (transaction layper packets). +// tlp.h : definitions related PCIe TLPs (transaction layper packets). // // (c) Ulf Frisk, 2017 // Author: Ulf Frisk, pcileech@frizk.net @@ -75,4 +75,32 @@ typedef struct tdTLP_HDR_CplD { */ VOID TLP_Print(_In_ PBYTE pbTlp, _In_ DWORD cbTlp, _In_ BOOL isTx); +typedef struct tdTLP_CALLBACK_BUF_MRd { + DWORD cbMax; + DWORD cb; + PBYTE pb; +} TLP_CALLBACK_BUF_MRd, *PTLP_CALLBACK_BUF_MRd; + +/* +* Generic callback function that may be used by TLP capable devices to aid the +* collection of memory read completions. Receives single TLP packet. +* -- pBufferMrd +* -- pb +* -- cb +* -- hEventCompleted = optional Event that will be signaled once all MRd's are completed. +* -- return = TRUE if all required TLPs required to fill up buffer have been processed, otherwise FALSE. +*/ +BOOL TLP_CallbackMRd(_Inout_ PTLP_CALLBACK_BUF_MRd pBufferMrd, _In_ PBYTE pb, _In_ DWORD cb, _In_opt_ HANDLE hEventCompleted); + +/* +* Generic callback function that may be used by TLP capable devices to aid the +* collection of completions from the probe function. Receives single TLP packet. +* -- pBufferMrd +* -- pb +* -- cb +* -- hEventCompleted = optional Event that will be signaled once all MRd's are completed. +* -- return = TRUE if all required TLPs required TLPs have been processed, otherwise FALSE. +*/ +BOOL TLP_CallbackMRdProbe(_Inout_ PTLP_CALLBACK_BUF_MRd pBufferMRd, _In_ PBYTE pb, _In_ DWORD cb, _In_opt_ HANDLE hEventCompleted); + #endif /* __TLP_H__ */ diff --git a/pcileech/util.c b/pcileech/util.c index 9598c48..63162ff 100644 --- a/pcileech/util.c +++ b/pcileech/util.c @@ -317,6 +317,17 @@ BOOL Util_HexAsciiToBinary(_In_ LPSTR sz, _Out_ PBYTE pb, _In_ DWORD cb, _Out_ P return TRUE; } +DWORD Util_GetFileSize(_In_ LPSTR sz) +{ + FILE *pFile; + DWORD size; + if(fopen_s(&pFile, sz, "rb") || !pFile) { return 0; } + fseek(pFile, 0, SEEK_END); // seek to end of file + size = ftell(pFile); // get current file pointer + fclose(pFile); + return size; +} + BOOL Util_ParseHexFileBuiltin(_In_ LPSTR sz, _Out_ PBYTE pb, _In_ DWORD cb, _Out_ PDWORD pcb) { SIZE_T i; @@ -401,7 +412,7 @@ BOOL Util_LoadSignatures(_In_ LPSTR szSignatureName, _In_ LPSTR szFileExtension, fclose(pFile); if(!cbFile || cbFile == 0x10000) { return FALSE; } // parse file - szLine = strtok_s(pbFile, "\r\n", &szContext); + szLine = strtok_s((char*)pbFile, "\r\n", &szContext); while(szLine && cSignatureIdx < *cSignatures) { if(Util_ParseSignatureLine(szLine, cSignatureChunks, pSignatures[cSignatureIdx].chunk)) { cSignatureIdx++; @@ -599,6 +610,9 @@ BOOL Util_Read16M(_Inout_ PPCILEECH_CONTEXT ctx, _Out_ PBYTE pbBuffer16M, _In_ Q { BOOL isSuccess[4] = { FALSE, FALSE, FALSE, FALSE }; QWORD i, o, qwOffset; + if(!ctx->phKMD) { // Native DMA + return 0 != DeviceReadDMAEx(ctx, qwBaseAddress, pbBuffer16M, 0x01000000, pPageStat); + } // try read 16M if((qwBaseAddress + 0x01000000 <= ctx->cfg->qwAddrMax) && DeviceReadMEM(ctx, qwBaseAddress, pbBuffer16M, 0x01000000, 0)) { PageStatUpdate(pPageStat, qwBaseAddress + 0x010000000, 4096, 0); @@ -610,16 +624,11 @@ BOOL Util_Read16M(_Inout_ PPCILEECH_CONTEXT ctx, _Out_ PBYTE pbBuffer16M, _In_ Q o = 0x00400000 * i; isSuccess[i] = (qwBaseAddress + o + 0x00400000 <= ctx->cfg->qwAddrMax) && DeviceReadMEM(ctx, qwBaseAddress + o, pbBuffer16M + o, 0x00400000, 0); } - // DMA mode + all memory inside scope + and all 4M reads fail + no force flag + iosize >= 1MB => fail - if(!ctx->cfg->fForceRW && !ctx->phKMD && (ctx->cfg->qwMaxSizeDmaIo >= 0x01000000) && qwBaseAddress + 0x01000000 <= ctx->cfg->qwAddrMax && !isSuccess[0] && !isSuccess[1] && !isSuccess[2] && !isSuccess[3]) { - PageStatUpdate(pPageStat, qwBaseAddress + 0x010000000, 0, 4096); - return FALSE; - } - // try read failed 4M chunks in 1M chunks + // try read failed chunks. for(i = 0; i < 4; i++) { if(isSuccess[i]) { PageStatUpdate(pPageStat, qwBaseAddress + (i + 1) * 0x00400000, 1024, 0); - } else { + } else { qwOffset = 0x00400000 * i; for(o = 0; o < 0x00400000; o += 0x00100000) { Util_Read1M(ctx, pbBuffer16M + qwOffset + o, qwBaseAddress + qwOffset + o, pPageStat); diff --git a/pcileech/util.h b/pcileech/util.h index a091f8a..4187ac2 100644 --- a/pcileech/util.h +++ b/pcileech/util.h @@ -108,6 +108,13 @@ VOID Util_GenRandom(_Out_ PBYTE pb, _In_ DWORD cb); */ BOOL Util_LoadKmdExecShellcode(_In_ LPSTR szKmdExecName, _Out_ PKMDEXEC* ppKmdExec); +/* +* Retrieve the file size of the file. If the file isn't found 0 is returned. +* -- sz = file to retrieve size of. +* -- return = file size, or 0 on fail. +*/ +DWORD Util_GetFileSize(_In_ LPSTR sz); + /* * Parse an input line consisting of either builtin, hexascii or file name to * data buffer. diff --git a/pcileech/vfs.c b/pcileech/vfs.c index ce3b586..0aa5a4b 100644 --- a/pcileech/vfs.c +++ b/pcileech/vfs.c @@ -358,7 +358,7 @@ BOOL UnicodeToAscii(_Out_ LPSTR szDst, _In_ SIZE_T cDst, _In_ LPCWSTR wcsSrc) VOID Vfs_UtilSplitPathFile(_Out_ WCHAR wszPath[MAX_PATH], _Out_ LPWSTR *pwcsFile, _In_ LPCWSTR wcsFileName) { DWORD i, iSplitFilePath; - wcscpy_s(wszPath, MAX_PATH, wcsFileName); + wcsncpy_s(wszPath, MAX_PATH, wcsFileName, _TRUNCATE); for(i = 0; i < MAX_PATH; i++) { if(wszPath[i] == '\\') { iSplitFilePath = i; diff --git a/pcileech_files/pcileech b/pcileech_files/pcileech index cec7f31e2147fd0689eea7cef28a4ad1db31dab1..09e76202e24f249e335de07e012e042abc9e5f86 100644 GIT binary patch delta 40462 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 QNh`MEAVXyy9WyZY0|xm+fdBvi delta 36691 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

@N%~-jxv&%TW-x2c>#U{?!7NCB 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 diff --git a/readme.md b/readme.md index e861324..15b65fd 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ PCILeech Summary: ================= PCILeech uses PCIe hardware devices to read and write from the target system memory. This is achieved by using DMA over PCIe. No drivers are needed on the target system. -PCILeech supports multiple hardware. Currently only the USB3380 hardware is publically available. The USB3380 is only able to read 4GB of memory natively, but is able to read all memory if a kernel module (KMD) is first inserted into the target system kernel. +PCILeech supports multiple hardware. USB3380 based hardware is only able to read 4GB of memory natively, but is able to read all memory if a kernel module (KMD) is first inserted into the target system kernel. FPGA based hardware is able to read all memory. PCILeech is capable of inserting a wide range of kernel implants into the targeted kernels - allowing for easy access to live ram and the file system via a "mounted drive". It is also possible to remove the logon password requirement, loading unsigned drivers, executing code and spawn system shells. PCIleech runs on Windows/Linux/Android. Supported target systems are currently the x64 versions of: UEFI, Linux, FreeBSD, macOS and Windows. @@ -10,10 +10,12 @@ PCILeech is capable of inserting a wide range of kernel implants into the target Capabilities: ============= -* Retrieve memory from the target system at >150MB/s. +* Retrieve memory from the target system at >150MB/s. * Write data to the target system memory. -* 4GB memory can be accessed in native DMA mode. +* 4GB memory can be accessed in native DMA mode (USB3380 hardware). +* ALL memory can be accessed in native DMA mode (FPGA hardware). * ALL memory can be accessed if kernel module (KMD) is loaded. +* Raw PCIe TLP access (FPGA hardware). * Mount live RAM as file [Linux, Windows, macOS]. * Mount file system as drive [Linux, Windows, macOS]. * Execute kernel code on the target system. @@ -33,7 +35,9 @@ Please ensure you do have the most recent version of PCILeech by visiting the PC Clone the PCILeech Github repository. The binaries are found in pcileech_files and should work on 64-bit Windows and Linux. Please copy all files from pcileech_files since some files contains additional modules and signatures. #### Windows: -The Google Android USB driver also have to be installed. Download the Google Android USB driver from: http://developer.android.com/sdk/win-usb.html#download Unzip the driver. Open Device Manager. Right click on the computer, choose add legacy hardware. Select install the hardware manually. Click Have Disk. Navigate to the Android Driver, select android_winusb.inf and install. +The Google Android USB driver also have to be installed if USB3380 hardware is used. Download the Google Android USB driver from: http://developer.android.com/sdk/win-usb.html#download Unzip the driver. Open Device Manager. Right click on the computer, choose add legacy hardware. Select install the hardware manually. Click Have Disk. Navigate to the Android Driver, select android_winusb.inf and install. + +FTDI drivers have to be installed if FPGA is used with FT601 USB3 addon card. FTDI drivers will installed automatically on Windows from Windows Update at first connection. PCILeech also requires 64-bit [`FTD3XX.dll`](http://www.ftdichip.com/Drivers/D3XX/FTD3XXLibrary_v1.2.0.6.zip) which must be downloaded from FTDI and placed alongside `pcileech.exe`. To mount live ram and target file system as drive in Windows the Dokany file system library must be installed. Please download and install the latest version of Dokany at: https://github.com/dokan-dev/dokany/releases/latest @@ -43,7 +47,11 @@ PCILeech on Linux must be run as root. PCILeech also requires libusb. Libusb is #### Android: Separate instructions for [Android](Android.md). -Hardware: +Hardware (FPGA): +================= +Please check out the [PCILeech FPGA project](https://github.com/ufrisk/pcileech-fpga/) for information about supported FPGA based hardware. + +Hardware (USB3380): ================= PCILeech use the PLX Technologies USB3380 chip. The actual chip can be purchased for around $15, but it's more convenient to purchase a development board on which the chip is already mounted. Development boards can be purchased from BPlus Technology, or on eBay / Ali Express. Please note that adapters may be required too depending on your requirements. In addition to the USB3380 PCILeech also supports not yet released FPGA based hardware. @@ -67,7 +75,7 @@ Recommended adapters: Please note that other adapters may also work. -Flashing Hardware: +Flashing Hardware (USB3380): ================== In order to turn the USB3380 development board into a PCILeech device it must be flashed. Flashing may be done in Windows 10 (as administrator) or in Linux (as root). The board must be connected to the system via PCIe when performing the initial flash. @@ -118,7 +126,7 @@ Dump all memory from the target system given that a kernel module is loaded at a Force dump memory below 4GB including accessible memory mapped devices using more stable USB2 approach. * ` pcileech.exe dump -force -usb2 ` -Exploit a vulnerable mac to retrieve the FileVault2 password. +Exploit a vulnerable mac to retrieve the FileVault2 password. (USB3380 only). * ` pcileech.exe mac_fvrecover ` Receive PCIe TLPs (Transaction Layer Packets) and print them on screen (correctly configured FPGA dev board required). @@ -130,6 +138,15 @@ Load a "kernel" module by searching for and hooking UEFI BootServices.SignalEven Load a "kernel" module by hooking and BootServices.ExitBootServices(). Base memory location of UEFI specified manually (IBI SYST table). * ` pcileech.exe kmdload -kmd UEFI_EXIT_BOOT_SERVICES -efibase 0x7b399018 ` +Probe/Enumerate the memory of the target system for readable memory pages and maximum memory. (FPGA hardware only). +* ` pcileech.exe probe ` + +Dump all memory between addresses min and max, don't stop on failed pages. Native access to 64-bit memory is only supported on FPGA hardware. +* ` pcileech.exe dump -min 0x0 -max 0x21e5fffff -force ` + +Force the usage of a specific device (instead of default auto detecting it). +* ` pcileech.exe pagedisplay -min 0x1000 -device usb3380 ` + Generating Signatures: ====================== PCILeech comes with built in signatures for Windows, Linux, FreeBSD and macOS. For Windows 8.1 or later it is also possible to use the pcileech_gensig.exe program to generate alternative signatures. @@ -143,6 +160,7 @@ Limitations/Known Issues: * Windows Vista: some shellcode modules such as wx64_pscmd does not work. * Windows 7: signatures are not published. * The Linux/Android versions of PCILeech dumps memory slightly slower than the Windows version. Mount target file system and live RAM are also not availabe in the Linux/Android versions. +* FPGA support currently only exists if PCILeech is run on Windows. Linux and Android support is planned for the future. Building: ========= @@ -179,3 +197,7 @@ v2.2 * UEFI support. * Linux 2.6.33-4.6 target support. * signature: Windows 10 updates to pcileech_gensig.exe + +v2.3 +* [FPGA hardware support (SP605/FT601)](https://github.com/ufrisk/pcileech-fpga). +* Various changes.