// MLAB Xilinx Virtual Cable Network Server
// ----------------------------------------
//
// (c) miho 2012, 2013 http://www.mlab.cz/PermaLink/XVC_SOFTWARE
//
// This program if free.
//
//
// History:
//
// 1.00 2012_09 Proof of concept (no configuration, not for public release)
// 1.01 2012_09 Added parameter for device selection
// 1.02 2012_12 Error handling and debugged
// 1.03 2012_12 Release version ready to publish
// 1.04 2013_04 Socket Bind Error with explanation (multiple instance of XVC Server)
// 1.05 2013_04 Test FTDI cable during wait for Accept (to stop the server immediately when cable is disconnected)
// 1.06 2013_04 Added support for Linux (thanks to Martin Poviser)
// 1.07 2013_04 Rewritten Host Address function for Linux (function gethostbyname returns 127.0.1.1 on Debian systems)
// Solved compatibility problems on Linux (FT_SetLatncyTimer requires delay, udev problem with ftdi_sio driver)
// 1.08 2013_06 Added linux x86_64 variant
//
//
// Purpose:
//
// XILINX development software (ISE, WebPack) supports several types of JTAG programming
// cables. Among them there is one particularly interesting. It is Xilinx Virtual Cable
// which uses (documented) XVC network protocol to send JTAG commands across TCP/IP network.
// So it is possible to realize own hardware/software and have it directly supported by
// XILINX development software (both IMPACT and ChipScope).
//
// This program listens TCP data send by XILINX ISE IMAPACT (or ChipScope) and sends it
// to the JTAG device (typically FPGA) connected to FTDI USB Chip. You can use ordinary
// USB/RS232 translator based on FT232R chip or you can use our own module from
// http://www.mlab.cz/PermaLink/XVC_FT220X
//
// Target device JTAG port is connected to pins on FTDI USB chip. Program writes to standard
// output Which pins are used. Program writes what to set in ISE to enable XVC plugin.
//
//
// Environment:
//
// This is Win32 Console Application and run in WinXP / Win7 / Win8 both 32 and 64 bit.
//
// Program needs to listen to the network so it is necessary to allow doing so. In Windows
// firewall configuration enable networking for the exe file.
// WinXP: run as Administrator c:\WINDOWS\System32\firewall.cpl and add the exe file
// Win7: the system asks directly to do so
//
//
// Technology:
//
// The program uses Windows WINSOCK2 library for network communication
// and FTDI ftd2xx library for communication with FTDI USB chip.
// It can be staticly linked to minimize dependencies on libraries.
// Program requires FTDI drivers installed.
// Because of the usage of standard libraries you don't need to solve how to sign drivers.
//
// The program was debug with FT232R and FT220X device.
// It should work with any similar FTDI USB chip.
//
// XVC protocol is documented (you have to ask XILINX support to gain access).
// The program is inspired by the work http://debugmo.de/2012/02/xvcd-the-xilinx-virtual-cable-daemon/
// Ask Google about Xilinx Virtual Cable.
//
//
// Compilation for Windows:
//
// MS Visual C++ 2010 Express (free, registration required)
// Create new empty project for Win32 Console Application and name project mlab_xvcd (to build mlab_xvcd.exe)
// Header Files / Add / Existing Items - all .h files
// Source Files / Add / Existing Items - all .cpp files
// Library Files / Add / Existing Items - all .lib .h files from lib_win32 directory
// Select Release version (no debug info)
// Set static linkage Project Properties / Configuration Release / Configuration Properties
// / Code Generation / Runtime Library = Multithreaded (/MT)
//
// Compilation for Linux:
//
// On Ubuntu 12.04LTS just run the .sh file
//
// Problems:
//
// Programming of SPI FLASH configuration memory connected to FPGA does not work. No idea why.
// It does not work for internal FLASH of Spartan XC3SxxAN either.
//
//
// Possible improvements:
//
// External definition of JTAG pins.
// Enable Socket Number (to be able to run multiple XVC Servers), now it is constant XVC_TCP_PORT (should be only a default)
// Library Definitions
// -------------------
#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#include "mlab_xvcd.h" // Program Configuration
#include <stdlib.h> // Standard Library (exit, atoi, ...)
#include <stdio.h> // Standard IO (printf, ...)
#include <signal.h> // CTRL+C handling
#ifdef WIN32
#include <windows.h> // Windows Console Application
#include <winsock2.h> // Windows WinSock2
#include <ws2tcpip.h> // Windows WinSock2
// Link with library
#pragma comment (lib, "Ws2_32.lib")
//#pragma comment (lib, "../lib_win32/ftd2xx.lib") // Add this file to Resources
#else // not WIN32
#include "lib_linux/WinTypes.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#endif
#define XVC_RX_BUFLEN (XVC_JTAG_LEN/8*2+20) // Length of receive buffer in bytes (command+length+TMSbuffer+TDIbuffer)
#define XVC_TX_BUFLEN (XVC_JTAG_LEN/8) // Length of transmit buffer in bytes (TDObuffer)
#ifdef WIN32
typedef int socklen_t;
#else //not WIN32
typedef int SOCKET;
#define SOCKET_ERROR -1
#define INVALID_SOCKET -1
void closesocket(int socket)
{
close(socket);
}
void WSACleanup()
{
}
int WSAGetLastError()
{
return errno;
}
#endif
// JTAG state machine
// ------------------
// JTAG States
enum
{
test_logic_reset, run_test_idle, // Starts from 0
select_dr_scan, capture_dr, shift_dr,
exit1_dr, pause_dr, exit2_dr, update_dr,
select_ir_scan, capture_ir, shift_ir,
exit1_ir, pause_ir, exit2_ir, update_ir,
num_states
};
// JTAG State Machine transfer Function
static int jtagStep(int state, int tms)
{
static const int next_state[num_states][2] =
{
/* JTAG State -->> New State */
/* -------------------------------------------------------------*/
/* | TMS=0 | TMS=1 */
/* -------------------------------------------------------------*/
/* [test_logic_reset] -> */ { run_test_idle, test_logic_reset },
/* [run_test_idle] -> */ { run_test_idle, select_dr_scan },
/* [select_dr_scan] -> */ { capture_dr, select_ir_scan },
/* [capture_dr] -> */ { shift_dr, exit1_dr },
/* [shift_dr] -> */ { shift_dr, exit1_dr },
/* [exit1_dr] -> */ { pause_dr, update_dr },
/* [pause_dr] -> */ { pause_dr, exit2_dr },
/* [exit2_dr] -> */ { shift_dr, update_dr },
/* [update_dr] -> */ { run_test_idle, select_dr_scan },
/* [select_ir_scan] -> */ { capture_ir, test_logic_reset },
/* [capture_ir] -> */ { shift_ir, exit1_ir },
/* [shift_ir] -> */ { shift_ir, exit1_ir },
/* [exit1_ir] -> */ { pause_ir, update_ir },
/* [pause_ir] -> */ { pause_ir, exit2_ir },
/* [exit2_ir] -> */ { shift_ir, update_ir },
/* [update_ir] -> */ { run_test_idle, select_dr_scan }
};
return next_state[state][tms];
}
int handleData(SOCKET ClientSocket)
{
bool seen_tlr = false;
bool jtagError = false;
static int jtag_state;
do
{
int iResult;
// Read Command
char command[16];
unsigned int commandLen = 0;
// Read String terminated by ':'
do
{
iResult = recv(ClientSocket, command+commandLen, 1, 0);
if (iResult==0)
{
printf("\n Connection Closed\n\n");
return -1;
}
else if (iResult==1)
{
commandLen++;
}
else
{
fprintf(stderr, "Error Reading Command\n");
return -2;
}
}
while (command[commandLen-1]!=':' && commandLen<sizeof(command)-1 );
command[commandLen] = char(0);
if (0==strncmp(command, "shift:", sizeof(command)))
{
}
else
{
fprintf(stderr, "Invalid Command '%s'\n", command);
return -2;
}
// Read Length (in bits, 32bit integer)
int len;
iResult = recv(ClientSocket, (char *)&len, 4, 0); // TODO: rewrite for multiplatform use
if (iResult==0)
{
printf("\n Connection Closed\n\n");
return -1;
}
if (iResult != 4)
{
fprintf(stderr, "Reading Length Failed\n");
return -2;
}
char buffer[2048];
// Read Data (data string for TMS and TDI)
unsigned int nr_bytes = (len + 7) / 8;
if (nr_bytes * 2 > sizeof(buffer))
{
fprintf(stderr, "Buffer Size Exceeded\n");
return -2;
}
unsigned int iReceivedBytes=0;
while (iReceivedBytes<nr_bytes * 2)
{
iResult = recv(ClientSocket, buffer+iReceivedBytes, nr_bytes * 2 - iReceivedBytes, 0);
if (iResult==0)
{
printf("\n Connection Closed\n\n");
return -1;
}
if (iResult<=0)
{
fprintf(stderr, "Reading Data Failed %d %d\n", iResult, nr_bytes * 2);
return -2;
}
iReceivedBytes += iResult;
}
char result[1024];
memset(result, 0, nr_bytes);
// Deal with JTAG
// Only allow exiting if the state is rti and the IR
// has the default value (IDCODE) by going through test_logic_reset.
// As soon as going through capture_dr or capture_ir no exit is
// allowed as this will change DR/IR.
seen_tlr = (seen_tlr || jtag_state == test_logic_reset) && (jtag_state != capture_dr) && (jtag_state != capture_ir);
// Due to a weird bug(??) xilinx impacts goes through another "capture_ir"/"capture_dr" cycle after
// reading IR/DR which unfortunately sets IR to the read-out IR value.
// Just ignore these transactions.
if ((jtag_state == exit1_ir && len == 5 && buffer[0] == 0x17) || (jtag_state == exit1_dr && len == 4 && buffer[0] == 0x0b))
{
// printf("Ignoring Bogus jtag State movement at jtag_state %d\n", jtag_state);
}
else
{
for (int i = 0; i < len; ++i)
{
//
// Do the actual cycle.
//
int tms = !!(buffer[i/8] & (1<<(i&7)));
//
// Track the state.
//
jtag_state = jtagStep(jtag_state, tms);
}
if (jtagScan((unsigned char *) buffer, (unsigned char *) buffer + nr_bytes, (unsigned char *) result, len) < 0)
{
//fprintf(stderr, "jtagScan failed\n");
// Can't stop now, have to sent (any) answer not to hung the IMPACT
jtagError = true;
}
}
// Send the Ansver
iResult = send(ClientSocket, result, nr_bytes, 0 );
if (iResult == SOCKET_ERROR)
{
printf("Send Failed with Error: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return -2;
}
// printf("Bytes Sent: %d\n", iSendResult);
// printf("jtag state %d\n", jtag_state);
}
while (!(seen_tlr && jtag_state == run_test_idle));
return jtagError ? -2 : 0;
}
// Stop Handler - switch JTAG port off and stop program
void stopHandler(int)
{
jtagClosePort();
exit(1);
}
// Print help and stop program with error
void Help(char *progName)
{
fprintf(stderr, "Bad Parameters\n");
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [arg]\n", progName);
fprintf(stderr, "\n");
fprintf(stderr, " Where [arg] is one of: \n");
fprintf(stderr, " -d Description Fing FTDI device by Description\n");
fprintf(stderr, " -l Location Fing FTDI device by Loaction\n");
fprintf(stderr, " -s Serial_number Fing FTDI device by it's SN\n");
fprintf(stderr, " -n Number Use N-th FTDI device\n");
fprintf(stderr, " The first FTDI device is used if no argument\n");
exit(2);
}
int main(int argc, char *argv[])
{
// Variables
bool verbose = true;
// Program Info
printf("\n");
printf("Xilinx Virtual Cable Network Server\n");
printf("===================================\n");
printf("(c) miho " YEAR " v " VERSION "\n\n");
// Get program name
char *cp;
char *progName;
cp = argv[0];
progName=cp;
while (cp[0]!='\0')
{
if (cp[0]=='/' || cp[0]=='\\')
progName=cp+1;
cp++;
}
// Process command line params
char *findDeviceByStr = 0; // String parameter
int findDeviceBy = 0; // What does the string means
if (argc>1)
{
if (argc==3)
{
findDeviceByStr = argv[2];
if (strcmp(argv[1], "-d")==0)
{
findDeviceBy = OPEN_BY_DESCRIPTION;
}
else if (strcmp(argv[1], "-l")==0)
{
findDeviceBy = OPEN_BY_LOCATION;
}
else if (strcmp(argv[1], "-s")==0)
{
findDeviceBy = OPEN_BY_SERIAL_NUMBER;
}
else if (strcmp(argv[1], "-n")==0)
{
findDeviceBy = 0;
}
else
{
Help(progName);
}
}
else
{
Help(progName);
}
}
else
{
// Empty String - find device by number and number is empty
findDeviceBy = 0;
findDeviceByStr = (char *)"";
}
// Find, Init and Open FTDI USB Chip
if (jtagOpenPort(findDeviceBy, findDeviceByStr)<0) {
// No Device Found
fprintf(stderr, "ERROR: No Device Found\n");
return -1;
}
// Signal Handler (for CRTL+C)
signal(SIGINT, &stopHandler);
printf("Starting Network Server\n");
int iResult;
SOCKET ListenSocket = INVALID_SOCKET;
SOCKET ClientSocket = INVALID_SOCKET;
#ifdef WIN32
// Initialize Winsock
WSADATA wsaData;
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0)
{
fprintf(stderr, "WSAStartup failed with error: %d\n", iResult);
jtagClosePort();
return -2;
}
#endif
// Display HostName
char sMyName[255];
gethostname(sMyName, sizeof(sMyName));
printf(" Host Name %s\n", sMyName);
// Display Address
#ifdef WIN32
hostent * pHostInfo;
pHostInfo = gethostbyname(sMyName);
printf(" Network Name %s\n", pHostInfo->h_name);
if (pHostInfo->h_length>0 && pHostInfo->h_length<=16)
{
printf(" Host Address ");
for (int i=0; i<pHostInfo->h_length-1; i++)
{
printf("%d.", (unsigned char)pHostInfo->h_addr_list[0][i]);
}
printf("%d\n", (unsigned char)pHostInfo->h_addr_list[0][pHostInfo->h_length-1]);
}
#else
int TempSocket;
struct ifreq ifreqs[20];
struct ifconf ic;
ic.ifc_len = sizeof ifreqs;
ic.ifc_req = ifreqs;
TempSocket = socket(AF_INET, SOCK_DGRAM, 0);
if (TempSocket < 0) {
perror("socket");
return -2;
}
if (ioctl(TempSocket, SIOCGIFCONF, &ic) < 0) {
perror("SIOCGIFCONF");
return -2;
}
for (int i = 0; i < ic.ifc_len/sizeof(struct ifreq); ++i)
{
if (ifreqs[i].ifr_name[0]!='l')// remove lo
printf(" Host Address %s: %s\n",
ifreqs[i].ifr_name,
inet_ntoa(((struct sockaddr_in*)&ifreqs[i].ifr_addr)->sin_addr));
}
#endif
// Create Protocol Structure
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET; // IP6
hints.ai_socktype = SOCK_STREAM; // Reliable two-way connection
hints.ai_protocol = IPPROTO_TCP; // Protocol TCP
hints.ai_flags = AI_PASSIVE;
// Resolve the server address and port (allocate structure "result")
struct addrinfo *result = NULL;
iResult = getaddrinfo(NULL, XVC_TCP_PORT, &hints, &result);
if ( iResult != 0 )
{
fprintf(stderr, "getaddrinfo failed with error: %d\n", iResult);
WSACleanup();
jtagClosePort();
return -2;
}
// Create a SOCKET
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET)
{
fprintf(stderr, "socket failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
jtagClosePort();
return -2;
}
// Bind the SOCKED (assign the address)
iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR)
{
int LastError=WSAGetLastError();
fprintf(stderr, "Bind failed with error: %d\n", LastError);
#ifdef WIN32
if (LastError==WSAEADDRINUSE)
#else
if (LastError==EADDRINUSE)
#endif
fprintf(stderr, "Trying to start second instance of XVC Server?\n");
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
jtagClosePort();
return -2;
}
if (verbose)
{
printf(" Bound Socket %s\n", XVC_TCP_PORT);
}
// Help for user
printf(" Set in IMPACT xilinx_xvc host=%s:%s disableversioncheck=true\n", sMyName, XVC_TCP_PORT);
freeaddrinfo(result);
// Listen SOCKET
iResult = listen(ListenSocket, SOMAXCONN);
if (iResult == SOCKET_ERROR)
{
fprintf(stderr, "listen failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
jtagClosePort();
return -2;
}
printf("\n");
do
{
printf(" Listen\n");
jtagSetLED(true);
// Set ListenSocket to non-blocking mode
// We need during waiting for Accept to detect FTDI disconnect
#ifdef WIN32
u_long iMode = 1;
iResult = ioctlsocket(ListenSocket, FIONBIO, &iMode);
if (iResult != NO_ERROR)
{
fprintf(stderr, "ioctlsocket failed with error: %ld\n", iResult);
WSACleanup();
jtagClosePort();
return -2;
}
#else
iResult = fcntl(ListenSocket, F_GETFL, 0);
if (iResult < 0 || fcntl(ListenSocket, F_SETFL, iResult | O_NONBLOCK) < 0)
{
fprintf(stderr, "fcntl failed with error: %d\n", errno);
jtagClosePort();
return -2;
}
#endif
// Accept a client SOCKET (wait for Accept)
sockaddr ClientSocetAddr;
socklen_t ClientSocetAddrLen = sizeof(sockaddr);
do
{
// Try Accept (non-blocking)
ClientSocket = accept(ListenSocket, &ClientSocetAddr, &ClientSocetAddrLen);
if (ClientSocket == INVALID_SOCKET)
{
// Accept Error
#ifdef WIN32
if (WSAGetLastError() != WSAEWOULDBLOCK)
#else
if (WSAGetLastError() != EAGAIN && WSAGetLastError() != EWOULDBLOCK)
#endif
{
fprintf(stderr, "accept failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
jtagClosePort();
return -2;
}
// Not yet Accepted
{
// Check FTDI
if (!CheckCable())
{
fprintf(stderr, "XVC Cable unexpectedly disconnected\n");
closesocket(ListenSocket);
WSACleanup();
jtagClosePort();
return -2;
}
// Sleep some time (do not eat CPU time for nothong)
#ifdef WIN32
Sleep(100); //ms
#else
usleep(100000); //us
#endif
}
}
}
while (ClientSocket == INVALID_SOCKET);
// Set (Accepted) Socket to blocking mode
#ifdef WIN32
iMode = 0;
iResult = ioctlsocket(ClientSocket, FIONBIO, &iMode);
if (iResult != NO_ERROR)
{
fprintf(stderr, "ioctlsocket failed with error: %ld\n", iResult);
WSACleanup();
jtagClosePort();
return -2;
}
#else
iResult = fcntl(ListenSocket, F_GETFL, 0);
if (iResult < 0 || fcntl(ListenSocket, F_SETFL, iResult & ~O_NONBLOCK) < 0)
{
fprintf(stderr, "fcntl failed with error: %d\n", errno);
jtagClosePort();
return -2;
}
#endif
// Print Accepted + Address
printf(" Accepted ");
jtagSetLED(false);
for (int i=2; i<2+4-1; i++)
{
printf("%d.", (unsigned char)ClientSocetAddr.sa_data[i]);
}
printf("%d:%d\n", (unsigned char)ClientSocetAddr.sa_data[2+4-1], (unsigned char)ClientSocetAddr.sa_data[0]*256+(unsigned char)ClientSocetAddr.sa_data[1]);
// Process Data until the peer shuts down the connection
int Cnt = 0;
printf(" Handle Data ");
do
{
iResult = handleData(ClientSocket);
if (iResult>=0)
{
printf(".");
fflush(stdout);
Cnt++;
if (Cnt>40)
{
Cnt = 0;
printf("\n ");
}
}
}
while (iResult >= 0);
// Connection Closed by peer
if (iResult==-1)
{
// JTAG port
jtagSetIdle();
}
// Error - shutdown the connection
if (iResult==-2)
{
fprintf(stderr, " Disconnect\n");
#ifdef WIN32
iResult = shutdown(ClientSocket, SD_SEND);
#else
iResult = shutdown(ClientSocket, SHUT_WR);
#endif
if (iResult == SOCKET_ERROR)
{
fprintf(stderr, "shutdown failed with error: %d\n", WSAGetLastError());
}
iResult=-2; // Error
}
// cleanup
closesocket(ClientSocket);
}
// If not Error Listen Again
while (iResult!=-2);
// cleanup
closesocket(ListenSocket);
WSACleanup();
jtagClosePort();
return 1;
}