mirror of
https://github.com/enjoy-digital/litex.git
synced 2025-01-04 09:52:26 -05:00
liteusb: refactor software (use python instead of libftdicom in C) and provide simple example.
small modifications to fastftdi.c are also done to select our interface (A or B) and mode (synchronous, asynchronous)
This commit is contained in:
parent
603b4cdc8c
commit
da0fe2ecfb
13 changed files with 511 additions and 489 deletions
14
misoclib/com/liteusb/software/ftdi/README
Normal file
14
misoclib/com/liteusb/software/ftdi/README
Normal file
|
@ -0,0 +1,14 @@
|
|||
[> Libftdicom
|
||||
------------------------------
|
||||
|
||||
[> Windows build
|
||||
--------------------------
|
||||
1. Install MinGW32
|
||||
2. Download libusbx windows binaries (tested version: libusbx-1.0.17-win)
|
||||
3. Put libusb-1.0.dll.a in mingw lib directory
|
||||
4. Download Zadig and use WinUSB driver for Interface A and Interface B
|
||||
5. make all in libftdicom/win
|
||||
|
||||
[> Linux build
|
||||
--------------------------
|
||||
1. make all in libftdicom/linux
|
352
misoclib/com/liteusb/software/ftdi/__init__.py
Normal file
352
misoclib/com/liteusb/software/ftdi/__init__.py
Normal file
|
@ -0,0 +1,352 @@
|
|||
import platform
|
||||
import ctypes
|
||||
import os
|
||||
import time
|
||||
import queue
|
||||
import threading
|
||||
|
||||
_lpath = (os.path.dirname(__file__))
|
||||
if _lpath == '':
|
||||
_lpath = '.'
|
||||
if platform.system() == "Windows":
|
||||
libftdicom = ctypes.cdll.LoadLibrary(_lpath + "/libftdicom.dll")
|
||||
else:
|
||||
libftdicom = ctypes.cdll.LoadLibrary(_lpath + "/libftdicom.so")
|
||||
|
||||
|
||||
class FTDI_Device(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('_1', ctypes.c_void_p),
|
||||
('_2', ctypes.c_void_p),
|
||||
]
|
||||
|
||||
pFTDI_Device = ctypes.POINTER(FTDI_Device)
|
||||
|
||||
# FTDIDevice_Open
|
||||
FTDIDevice_Open = libftdicom.FTDIDevice_Open
|
||||
FTDIDevice_Open.argtypes = [
|
||||
pFTDI_Device, # Dev
|
||||
ctypes.c_int # Interface
|
||||
]
|
||||
FTDIDevice_Open.restype = ctypes.c_int
|
||||
|
||||
# FTDIDevice_Close
|
||||
FTDIDevice_Close = libftdicom.FTDIDevice_Close
|
||||
FTDIDevice_Close.argtypes = [pFTDI_Device]
|
||||
|
||||
FTDIDevice_SetMode = libftdicom.FTDIDevice_SetMode
|
||||
FTDIDevice_SetMode.argtypes = [
|
||||
pFTDI_Device, # Dev
|
||||
ctypes.c_int, # Interface
|
||||
ctypes.c_int, # Mode
|
||||
ctypes.c_char, # PinDirection
|
||||
ctypes.c_char, # baudrate
|
||||
]
|
||||
|
||||
|
||||
FTDIDevice_Write = libftdicom.FTDIDevice_Write
|
||||
FTDIDevice_Write.argtypes = [
|
||||
pFTDI_Device, # Dev
|
||||
ctypes.c_int, # Interface
|
||||
ctypes.c_char_p, # Buf
|
||||
ctypes.c_size_t, # N
|
||||
ctypes.c_bool, # async
|
||||
]
|
||||
FTDIDevice_Write.restype = ctypes.c_int
|
||||
|
||||
p_cb_StreamCallback = ctypes.CFUNCTYPE(
|
||||
ctypes.c_int, # retval
|
||||
ctypes.POINTER(ctypes.c_uint8), # buf
|
||||
ctypes.c_int, # length
|
||||
ctypes.c_void_p, # progress
|
||||
ctypes.c_void_p) # userdata
|
||||
|
||||
FTDIDevice_ReadStream = libftdicom.FTDIDevice_ReadStream
|
||||
FTDIDevice_ReadStream.argtypes = [
|
||||
pFTDI_Device, # dev
|
||||
ctypes.c_int, # interface
|
||||
p_cb_StreamCallback, # callback
|
||||
ctypes.c_void_p, # userdata
|
||||
ctypes.c_int, # packetsPerTransfer
|
||||
ctypes.c_int, # numTransfers
|
||||
]
|
||||
FTDIDevice_ReadStream.restype = ctypes.c_int
|
||||
|
||||
FTDI_INTERFACE_A = 1
|
||||
FTDI_INTERFACE_B = 2
|
||||
|
||||
FTDI_BITMODE_SYNC_FIFO = (1 << 6)
|
||||
|
||||
|
||||
class FTDIDevice:
|
||||
def __init__(self, interface, mode):
|
||||
self.__is_open = False
|
||||
self._dev = FTDI_Device()
|
||||
self.interface = interface
|
||||
self.mode = mode
|
||||
|
||||
def __del__(self):
|
||||
if self.__is_open:
|
||||
self.__is_open = False
|
||||
FTDIDevice_Close(self._dev)
|
||||
|
||||
def open(self):
|
||||
err = FTDIDevice_Open(self._dev, self.interface)
|
||||
if err:
|
||||
return err
|
||||
else:
|
||||
self.__is_open = True
|
||||
|
||||
if self.mode == "synchronous":
|
||||
err = FTDIDevice_SetMode(self._dev, interface, FTDI_BITMODE_SYNC_FIFO, 0xFF, 0)
|
||||
|
||||
return err
|
||||
|
||||
def write(self, intf, buf, async=False):
|
||||
if not isinstance(buf, bytes):
|
||||
raise TypeError("buf must be bytes")
|
||||
|
||||
return FTDIDevice_Write(self._dev, intf, buf, len(buf), async)
|
||||
|
||||
def read(self, intf, n):
|
||||
buf = []
|
||||
|
||||
def callback(b, prog):
|
||||
buf.extend(b)
|
||||
return int(len(buf) >= n)
|
||||
|
||||
self.read_async(intf, callback, 4, 4)
|
||||
|
||||
return buf
|
||||
|
||||
def read_async(self, intf, callback, packetsPerTransfer, numTransfers):
|
||||
def callback_wrapper(buf, ll, prog, user):
|
||||
if ll:
|
||||
b = ctypes.string_at(buf, ll)
|
||||
else:
|
||||
b = b''
|
||||
return callback(b, prog)
|
||||
|
||||
cb = p_cb_StreamCallback(callback_wrapper)
|
||||
|
||||
return FTDIDevice_ReadStream(self._dev, intf, cb,
|
||||
None, packetsPerTransfer, numTransfers)
|
||||
|
||||
|
||||
class ProtocolError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TimeoutError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
INCOMPLETE = -1
|
||||
UNMATCHED = 0
|
||||
class BaseService:
|
||||
def match_identifier(self, byt):
|
||||
r = True
|
||||
r = r and (byt[0] == 0x5A)
|
||||
r = r and (byt[1] == 0xA5)
|
||||
r = r and (byt[2] == 0x5A)
|
||||
r = r and (byt[3] == 0xA5)
|
||||
r = r and (byt[4] == self.tag)
|
||||
return r
|
||||
|
||||
def get_needed_size_for_identifier(self):
|
||||
return self.NEEDED_FOR_SIZE
|
||||
|
||||
def present_bytes(self, b):
|
||||
if len(b) < self.get_needed_size_for_identifier():
|
||||
return INCOMPLETE
|
||||
|
||||
if not self.match_identifier(b):
|
||||
return UNMATCHED
|
||||
|
||||
size = self.get_packet_size(b)
|
||||
|
||||
if len(b) < size:
|
||||
return INCOMPLETE
|
||||
|
||||
self.consume(b[:size])
|
||||
|
||||
return size
|
||||
|
||||
|
||||
class UART:
|
||||
class __UARTService(BaseService):
|
||||
NEEDED_FOR_SIZE = 9
|
||||
|
||||
def __init__(self, tag):
|
||||
self.tag = tag
|
||||
self.q = queue.Queue()
|
||||
|
||||
def get_packet_size(self, buf):
|
||||
return 10
|
||||
|
||||
def consume(self, buf):
|
||||
self.q.put(buf[9])
|
||||
|
||||
def __init__(self, tag):
|
||||
self.tag = tag
|
||||
self.service = UART.__UARTService(self.tag)
|
||||
|
||||
def do_read(self, timeout=None):
|
||||
try:
|
||||
resp = self.service.q.get(True, timeout)
|
||||
except queue.Empty:
|
||||
return -1
|
||||
return resp
|
||||
|
||||
def do_write(self, value):
|
||||
msg = [0x5A, 0xA5, 0x5A, 0xA5, self.tag, 0x00, 0x00, 0x00, 1, value&0xFF]
|
||||
self.service.write(bytes(msg))
|
||||
|
||||
|
||||
class DMA:
|
||||
class __DMAService(BaseService):
|
||||
NEEDED_FOR_SIZE = 9
|
||||
|
||||
def __init__(self, tag):
|
||||
self.tag = tag
|
||||
self.q = queue.Queue()
|
||||
|
||||
def get_packet_size(self, buf):
|
||||
payload_size = buf[5] << 24
|
||||
payload_size |= buf[6] << 16
|
||||
payload_size |= buf[7] << 8
|
||||
payload_size |= buf[8] << 0
|
||||
return 9 + payload_size
|
||||
|
||||
def consume(self, buf):
|
||||
self.q.put(buf[9:])
|
||||
|
||||
def __init__(self, tag):
|
||||
self.tag = tag
|
||||
self.service = DMA.__DMAService(self.tag)
|
||||
|
||||
def do_read(self, timeout=None):
|
||||
try:
|
||||
resp = list(self.service.q.get(True, timeout))
|
||||
except queue.Empty:
|
||||
raise TimeoutError("DMA read timed out")
|
||||
return resp
|
||||
|
||||
def do_write(self, data):
|
||||
length = len(data)
|
||||
msg = [0x5A, 0xA5, 0x5A, 0xA5, self.tag,
|
||||
(length & 0xff000000) >> 24,
|
||||
(length & 0x00ff0000) >> 16,
|
||||
(length & 0x0000ff00) >> 8,
|
||||
(length & 0x000000ff) >> 0]
|
||||
msg += data
|
||||
self.service.write(bytes(msg))
|
||||
|
||||
|
||||
class FTDIComDevice:
|
||||
def __init__(self, interface, mode, uart_tag=0, dma_tag=1, verbose=False):
|
||||
self.__is_open = False
|
||||
|
||||
self.interface = interface
|
||||
self.mode = mode
|
||||
|
||||
self.dev = FTDIDevice(interface, mode)
|
||||
self.verbose = verbose
|
||||
|
||||
self.uart = UART(uart_tag)
|
||||
self.dma = DMA(dma_tag)
|
||||
|
||||
self.__services = [self.uart.service, self.dma.service]
|
||||
|
||||
# Inject a write function into the services
|
||||
for service in self.__services:
|
||||
def write(msg):
|
||||
if self.verbose:
|
||||
print("< %s" % " ".join("%02x" % i for i in msg))
|
||||
|
||||
self.dev.write(self.interface, msg, async=False)
|
||||
|
||||
service.write = write
|
||||
|
||||
def __comms(self):
|
||||
self.__buf = b""
|
||||
|
||||
def callback(b, prog):
|
||||
try:
|
||||
if self.verbose and b:
|
||||
print("> %s" % " ".join("%02x" % i for i in b))
|
||||
|
||||
self.__buf += b
|
||||
|
||||
incomplete = False
|
||||
|
||||
while self.__buf and not incomplete:
|
||||
for service in self.__services:
|
||||
code = service.present_bytes(self.__buf)
|
||||
if code == INCOMPLETE:
|
||||
incomplete = True
|
||||
break
|
||||
elif code:
|
||||
self.__buf = self.__buf[code:]
|
||||
break
|
||||
else:
|
||||
self.__buf = self.__buf[1:]
|
||||
|
||||
return int(self.__comm_term)
|
||||
except Exception as e:
|
||||
self.__comm_term = True
|
||||
self.__comm_exc = e
|
||||
return 1
|
||||
|
||||
while not self.__comm_term:
|
||||
self.dev.read_async(self.interface, callback, 8, 16)
|
||||
|
||||
if self.__comm_exc:
|
||||
raise self.__comm_exc
|
||||
|
||||
def __del__(self):
|
||||
if self.__is_open:
|
||||
self.close()
|
||||
|
||||
def open(self):
|
||||
if self.__is_open:
|
||||
raise ValueError("FTDICOMDevice doubly opened")
|
||||
|
||||
stat = self.dev.open()
|
||||
if stat:
|
||||
print("USB: Error opening device\n")
|
||||
return stat
|
||||
|
||||
self.commthread = threading.Thread(target=self.__comms, daemon=True)
|
||||
self.__comm_term = False
|
||||
self.__comm_exc = None
|
||||
|
||||
self.commthread.start()
|
||||
|
||||
self.__comm_term = False
|
||||
self.__is_open = True
|
||||
|
||||
def close(self):
|
||||
if not self.__is_open:
|
||||
raise ValueError("FTDICOMDevice doubly closed")
|
||||
|
||||
self.__comm_term = True
|
||||
self.commthread.join()
|
||||
|
||||
self.__is_open = False
|
||||
|
||||
def uartflush(self, timeout=0.25):
|
||||
while (self.uartread(timeout) != -1):
|
||||
pass
|
||||
|
||||
def uartread(self, timeout=None):
|
||||
return self.uart.do_read(timeout)
|
||||
|
||||
def uartwrite(self, value):
|
||||
return self.uart.do_write(value)
|
||||
|
||||
def dmaread(self):
|
||||
return self.dma.do_read()
|
||||
|
||||
def dmawrite(self, data):
|
||||
return self.dma.do_write(data)
|
81
misoclib/com/liteusb/software/ftdi/example.py
Normal file
81
misoclib/com/liteusb/software/ftdi/example.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
import platform
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
|
||||
# XXX FTDI Communication POC
|
||||
|
||||
sys.path.append("../")
|
||||
from ftdi import FTDIComDevice, FTDI_INTERFACE_B
|
||||
|
||||
def uart_console(ftdi_com):
|
||||
def read():
|
||||
while True:
|
||||
print(chr(ftdi_com.uartread()), end="")
|
||||
|
||||
readthread = threading.Thread(target=read, daemon=True)
|
||||
readthread.start()
|
||||
|
||||
def write():
|
||||
while True:
|
||||
for e in input():
|
||||
c = ord(e)
|
||||
ftdi_com.uartwrite(c)
|
||||
ftdi_com.uartwrite(ord("\n"))
|
||||
|
||||
|
||||
writethread = threading.Thread(target=write, daemon=True)
|
||||
writethread.start()
|
||||
|
||||
|
||||
def uart_virtual(ftdi_com):
|
||||
import pty, serial
|
||||
master, slave = pty.openpty()
|
||||
s_name = os.ttyname(slave)
|
||||
ser = serial.Serial(s_name)
|
||||
|
||||
def read():
|
||||
while True:
|
||||
s = ftdi_com.uartread()
|
||||
s = bytes(chr(s).encode('utf-8'))
|
||||
os.write(master, s)
|
||||
|
||||
readthread = threading.Thread(target=read, daemon=True)
|
||||
readthread.start()
|
||||
|
||||
def write():
|
||||
while True:
|
||||
for c in list(os.read(master, 100)):
|
||||
ftdi_com.uartwrite(c)
|
||||
|
||||
writethread = threading.Thread(target=write, daemon=True)
|
||||
writethread.start()
|
||||
|
||||
return s_name
|
||||
|
||||
|
||||
ftdi_map = {
|
||||
"uart": 0,
|
||||
"dma": 1
|
||||
}
|
||||
ftdi_com = FTDIComDevice(FTDI_INTERFACE_B,
|
||||
mode="asynchronous",
|
||||
uart_tag=ftdi_map["uart"],
|
||||
dma_tag=ftdi_map["dma"],
|
||||
verbose=False)
|
||||
ftdi_com.open()
|
||||
# test DMA
|
||||
for i in range(256):
|
||||
ftdi_com.dmawrite([i])
|
||||
print("%02x" %(ftdi_com.dmaread()[0]), end="")
|
||||
sys.stdout.flush()
|
||||
print("")
|
||||
# test UART
|
||||
if platform.system() == "Windows":
|
||||
uart_console(ftdi_com) # redirect uart to console since pty does not exist on Windows platforms
|
||||
else:
|
||||
s_name = uart_virtual(ftdi_com)
|
||||
print(s_name)
|
||||
while True:
|
||||
time.sleep(1)
|
|
@ -4,6 +4,7 @@
|
|||
* synchronous FIFO mode. Requires libusb-1.0
|
||||
*
|
||||
* Copyright (C) 2009 Micah Elizabeth Scott
|
||||
* Copyright (C) 2015 Florent Kermarrec
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -51,16 +52,14 @@ typedef struct {
|
|||
} FTDIStreamState;
|
||||
|
||||
static int
|
||||
DeviceInit(FTDIDevice *dev)
|
||||
DeviceInit(FTDIDevice *dev, FTDIInterface interface)
|
||||
{
|
||||
int err, interface;
|
||||
int err;
|
||||
|
||||
for (interface = 0; interface < 2; interface++) {
|
||||
if (libusb_kernel_driver_active(dev->handle, interface) == 1) {
|
||||
if ((err = libusb_detach_kernel_driver(dev->handle, interface))) {
|
||||
perror("Error detaching kernel driver");
|
||||
return err;
|
||||
}
|
||||
if (libusb_kernel_driver_active(dev->handle, (interface-1)) == 1) {
|
||||
if ((err = libusb_detach_kernel_driver(dev->handle, (interface-1)))) {
|
||||
perror("Error detaching kernel driver");
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,11 +68,9 @@ DeviceInit(FTDIDevice *dev)
|
|||
return err;
|
||||
}
|
||||
|
||||
for (interface = 0; interface < 2; interface++) {
|
||||
if ((err = libusb_claim_interface(dev->handle, interface))) {
|
||||
perror("Error claiming interface");
|
||||
return err;
|
||||
}
|
||||
if ((err = libusb_claim_interface(dev->handle, (interface-1)))) {
|
||||
perror("Error claiming interface");
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -81,7 +78,7 @@ DeviceInit(FTDIDevice *dev)
|
|||
|
||||
|
||||
int
|
||||
FTDIDevice_Open(FTDIDevice *dev)
|
||||
FTDIDevice_Open(FTDIDevice *dev, FTDIInterface interface)
|
||||
{
|
||||
int err;
|
||||
|
||||
|
@ -93,9 +90,6 @@ FTDIDevice_Open(FTDIDevice *dev)
|
|||
|
||||
libusb_set_debug(dev->libusb, 0);
|
||||
|
||||
dev->handle = libusb_open_device_with_vid_pid(dev->libusb,
|
||||
LITEUSB_VENDOR,
|
||||
LITEUSB_PRODUCT);
|
||||
|
||||
if (!dev->handle) {
|
||||
dev->handle = libusb_open_device_with_vid_pid(dev->libusb,
|
||||
|
@ -107,7 +101,7 @@ FTDIDevice_Open(FTDIDevice *dev)
|
|||
return LIBUSB_ERROR_NO_DEVICE;
|
||||
}
|
||||
|
||||
return DeviceInit(dev);
|
||||
return DeviceInit(dev, interface);
|
||||
}
|
||||
|
||||
|
||||
|
@ -120,7 +114,7 @@ FTDIDevice_Close(FTDIDevice *dev)
|
|||
|
||||
|
||||
int
|
||||
FTDIDevice_Reset(FTDIDevice *dev)
|
||||
FTDIDevice_Reset(FTDIDevice *dev, FTDIInterface interface)
|
||||
{
|
||||
int err;
|
||||
|
||||
|
@ -128,7 +122,7 @@ FTDIDevice_Reset(FTDIDevice *dev)
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
return DeviceInit(dev);
|
||||
return DeviceInit(dev, interface);
|
||||
}
|
||||
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
* for synchronous FIFO mode.
|
||||
*
|
||||
* Copyright (C) 2009 Micah Elizabeth Scott
|
||||
* Copyright (C) 2015 Florent Kermarrec
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -79,9 +80,6 @@ typedef struct {
|
|||
#define FTDI_VENDOR 0x0403
|
||||
#define FTDI_PRODUCT_FT2232H 0x6010
|
||||
|
||||
#define LITEUSB_VENDOR 0x1d50
|
||||
#define LITEUSB_PRODUCT 0x607c
|
||||
|
||||
#define FTDI_COMMAND_TIMEOUT 1000
|
||||
|
||||
#define FTDI_SET_BAUD_REQUEST 0x03
|
||||
|
@ -102,9 +100,9 @@ typedef int (FTDIStreamCallback)(uint8_t *buffer, int length,
|
|||
* Public Functions
|
||||
*/
|
||||
|
||||
int FTDIDevice_Open(FTDIDevice *dev);
|
||||
int FTDIDevice_Open(FTDIDevice *dev, FTDIInterface interface);
|
||||
void FTDIDevice_Close(FTDIDevice *dev);
|
||||
int FTDIDevice_Reset(FTDIDevice *dev);
|
||||
int FTDIDevice_Reset(FTDIDevice *dev, FTDIInterface interface);
|
||||
|
||||
int FTDIDevice_SetMode(FTDIDevice *dev, FTDIInterface interface,
|
||||
FTDIBitmode mode, uint8_t pinDirections,
|
|
@ -1,15 +0,0 @@
|
|||
CC=gcc
|
||||
CFLAGS =-Wall -O0
|
||||
CFLAGS_DLL =-Wall -O0 -g -shared -Wl,--out-implib,libftdicom.a
|
||||
INC=-I. -I../libusb
|
||||
LIBS_PATHS= -L. -L../libusb
|
||||
LIBS_DLL= -lusb-1.0 -lpthreadGC2
|
||||
|
||||
all: libftdicom.dll
|
||||
|
||||
libftdicom.dll: crc32.c fastftdi.c ftdicom.c
|
||||
$(CC) $(INC) -o $@ $(CFLAGS_DLL) $^ $(LIBS_PATHS) $(LIBS_DLL)
|
||||
|
||||
clean:
|
||||
rm libftdicom.a
|
||||
rm libftdicom.dll
|
|
@ -1,7 +0,0 @@
|
|||
#ifndef __CRC_H
|
||||
#define __CRC_H
|
||||
|
||||
unsigned short crc16(const unsigned char *buffer, int len);
|
||||
unsigned int crc32(const unsigned char *buffer, unsigned int len);
|
||||
|
||||
#endif
|
|
@ -1,81 +0,0 @@
|
|||
/* crc32.c -- compute the CRC-32 of a data stream
|
||||
* Copyright (C) 1995-1998 Mark Adler
|
||||
* For conditions of distribution and use, see copyright notice in zlib.h
|
||||
*/
|
||||
|
||||
#include <crc.h>
|
||||
|
||||
static const unsigned int crc_table[256] = {
|
||||
0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
|
||||
0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
|
||||
0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
|
||||
0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
|
||||
0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
|
||||
0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
|
||||
0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
|
||||
0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
|
||||
0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
|
||||
0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
|
||||
0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
|
||||
0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
|
||||
0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
|
||||
0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL,
|
||||
0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL,
|
||||
0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
|
||||
0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
|
||||
0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
|
||||
0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
|
||||
0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
|
||||
0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
|
||||
0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
|
||||
0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
|
||||
0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
|
||||
0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
|
||||
0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
|
||||
0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L,
|
||||
0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L,
|
||||
0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L,
|
||||
0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
|
||||
0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
|
||||
0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
|
||||
0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
|
||||
0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
|
||||
0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
|
||||
0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
|
||||
0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
|
||||
0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
|
||||
0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
|
||||
0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
|
||||
0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL,
|
||||
0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L,
|
||||
0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
|
||||
0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
|
||||
0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
|
||||
0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
|
||||
0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
|
||||
0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
|
||||
0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
|
||||
0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
|
||||
0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
|
||||
0x2d02ef8dL
|
||||
};
|
||||
|
||||
#define DO1(buf) crc = crc_table[((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8);
|
||||
#define DO2(buf) DO1(buf); DO1(buf);
|
||||
#define DO4(buf) DO2(buf); DO2(buf);
|
||||
#define DO8(buf) DO4(buf); DO4(buf);
|
||||
|
||||
unsigned int crc32(const unsigned char *buffer, unsigned int len)
|
||||
{
|
||||
unsigned int crc;
|
||||
crc = 0;
|
||||
crc = crc ^ 0xffffffffL;
|
||||
while(len >= 8) {
|
||||
DO8(buffer);
|
||||
len -= 8;
|
||||
}
|
||||
if(len) do {
|
||||
DO1(buffer);
|
||||
} while(--len);
|
||||
return crc ^ 0xffffffffL;
|
||||
}
|
|
@ -1,257 +0,0 @@
|
|||
/*
|
||||
* ftdicom.c - Low Level USB communication interface
|
||||
*
|
||||
* Provides UART and DMA low level communication
|
||||
* functions for FT2232H in slave fifo mode.
|
||||
*
|
||||
* Copyright (C) 2014 florent@enjoy-digital.fr
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "ftdicom.h"
|
||||
#include "crc.h"
|
||||
|
||||
/*
|
||||
* Open / close functions
|
||||
*/
|
||||
int ftdicom_open(FTDICom *com)
|
||||
{
|
||||
int err = 0;
|
||||
err = FTDIDevice_Open(com->dev);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
com->raw_tx_buf = malloc(RAW_BUFFER_SIZE);
|
||||
com->raw_rx_buf = malloc(RAW_BUFFER_SIZE);
|
||||
com->uart_rx_buf = malloc(UART_RINGBUFFER_SIZE_RX);
|
||||
|
||||
err = FTDIDevice_SetMode(com->dev, FTDI_INTERFACE_A, FTDI_BITMODE_SYNC_FIFO, 0xFF, 0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
com->raw_rx_buf_length = 0;
|
||||
|
||||
pthread_t thread;
|
||||
com->thread = &thread;
|
||||
pthread_create(com->thread, NULL, ftdicom_read_thread, com);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ftdicom_close(FTDICom *com)
|
||||
{
|
||||
free(com->raw_tx_buf);
|
||||
free(com->raw_rx_buf);
|
||||
free(com->uart_rx_buf);
|
||||
FTDIDevice_Close(com->dev);
|
||||
free(com->thread);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write (Tx) functions
|
||||
*/
|
||||
int ftdicom_write(FTDICom *com, uint8_t tag, uint8_t *data, size_t length, uint8_t with_crc)
|
||||
{
|
||||
unsigned int computed_crc;
|
||||
|
||||
com->raw_tx_buf[0] = 0x5A;
|
||||
com->raw_tx_buf[1] = 0xA5;
|
||||
com->raw_tx_buf[2] = 0x5A;
|
||||
com->raw_tx_buf[3] = 0xA5;
|
||||
com->raw_tx_buf[4] = tag;
|
||||
if (with_crc)
|
||||
length += 4;
|
||||
com->raw_tx_buf[5] = (length >> 24) & 0xff;
|
||||
com->raw_tx_buf[6] = (length >> 16) & 0xff;
|
||||
com->raw_tx_buf[7] = (length >> 8) & 0xff;
|
||||
com->raw_tx_buf[8] = (length >> 0) & 0xff;
|
||||
|
||||
memcpy(com->raw_tx_buf+9, data, length);
|
||||
if (with_crc) {
|
||||
computed_crc = crc32(data, length-4);
|
||||
com->raw_tx_buf[9+length-1] = (computed_crc >> 24) & 0xff;
|
||||
com->raw_tx_buf[9+length-2] = (computed_crc >> 16) & 0xff;
|
||||
com->raw_tx_buf[9+length-3] = (computed_crc >> 8) & 0xff;
|
||||
com->raw_tx_buf[9+length-4] = (computed_crc >> 0) & 0xff;
|
||||
}
|
||||
return FTDIDevice_Write(com->dev, FTDI_INTERFACE_A, com->raw_tx_buf, 9+length, false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read (Rx) common functions
|
||||
*/
|
||||
|
||||
int ftdicom_present_bytes(uint8_t tag, uint8_t *buffer, int length)
|
||||
{
|
||||
if (length < NEEDED_FOR_SIZE)
|
||||
return INCOMPLETE;
|
||||
|
||||
if (buffer[0] != 0x5A ||
|
||||
buffer[1] != 0xA5 ||
|
||||
buffer[2] != 0x5A ||
|
||||
buffer[3] != 0xA5 ||
|
||||
buffer[4] != tag)
|
||||
return UNMATCHED;
|
||||
|
||||
int size = NEEDED_FOR_SIZE;
|
||||
size += buffer[5] << 24;
|
||||
size += buffer[6] << 16;
|
||||
size += buffer[7] << 8;
|
||||
size += buffer[8];
|
||||
|
||||
if (length < size)
|
||||
return INCOMPLETE;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
int ftdicom_uart_present_bytes(uint8_t *buffer, int length)
|
||||
{
|
||||
return ftdicom_present_bytes(UART_TAG, buffer, length);
|
||||
}
|
||||
|
||||
int ftdicom_dma_present_bytes(uint8_t *buffer, int length)
|
||||
{
|
||||
return ftdicom_present_bytes(DMA_TAG, buffer, length);
|
||||
}
|
||||
|
||||
int ftdicom_read_callback(uint8_t *buffer, int length, FTDIProgressInfo *progress, void *userdata)
|
||||
{
|
||||
FTDICom *com = (FTDICom *) userdata;
|
||||
|
||||
// Concatenate buffer & raw_rx_buf
|
||||
memcpy(com->raw_rx_buf + com->raw_rx_buf_length, buffer, length);
|
||||
com->raw_rx_buf_length += length;
|
||||
|
||||
int code = 0;
|
||||
int incomplete = 0;
|
||||
int i = 0;
|
||||
|
||||
// Search frames in raw_rx_buf
|
||||
while (i != com->raw_rx_buf_length && !incomplete)
|
||||
{
|
||||
code = 0;
|
||||
|
||||
// UART
|
||||
code = ftdicom_uart_present_bytes(com->raw_rx_buf + i, com->raw_rx_buf_length-i);
|
||||
if (code == INCOMPLETE)
|
||||
{
|
||||
incomplete = 1;
|
||||
break;
|
||||
} else if (code)
|
||||
{
|
||||
ftdicom_uart_read_callback(com, com->raw_rx_buf + i + NEEDED_FOR_SIZE, code-NEEDED_FOR_SIZE);
|
||||
i += code-1;
|
||||
}
|
||||
|
||||
// DMA
|
||||
code = ftdicom_dma_present_bytes(com->raw_rx_buf + i, com->raw_rx_buf_length-i);
|
||||
if (code == INCOMPLETE)
|
||||
{
|
||||
incomplete = 1;
|
||||
break;
|
||||
} else if (code)
|
||||
{
|
||||
ftdicom_dma_read_callback(com, com->raw_rx_buf + i + NEEDED_FOR_SIZE, code-NEEDED_FOR_SIZE);
|
||||
i += code;
|
||||
}
|
||||
|
||||
// Nothing found, increment index
|
||||
if (code == UNMATCHED)
|
||||
i=i+1;
|
||||
|
||||
}
|
||||
|
||||
// Prepare raw_rx_buf for next callback
|
||||
if (incomplete == 1)
|
||||
{
|
||||
com->raw_rx_buf_length = com->raw_rx_buf_length - i;
|
||||
memcpy(com->raw_rx_buf, com->raw_rx_buf + i, com->raw_rx_buf_length);
|
||||
} else {
|
||||
com->raw_rx_buf_length = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *ftdicom_read_thread(void *userdata)
|
||||
{
|
||||
FTDICom *com = (FTDICom *) userdata;
|
||||
FTDIDevice_ReadStream(com->dev, FTDI_INTERFACE_A, ftdicom_read_callback, com, 8, 16);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* UART functions
|
||||
*/
|
||||
|
||||
int ftdicom_uart_write_buffer(FTDICom *com, uint8_t *data, size_t length)
|
||||
{
|
||||
return ftdicom_write(com, UART_TAG, data, length, 0);
|
||||
}
|
||||
|
||||
int ftdicom_uart_write(FTDICom *com, uint8_t c)
|
||||
{
|
||||
return ftdicom_write(com, UART_TAG, &c, 1, 0);
|
||||
}
|
||||
|
||||
void ftdicom_uart_read_callback(FTDICom *com, uint8_t *buffer, int length)
|
||||
{
|
||||
while (length > 0) {
|
||||
com->uart_rx_buf[com->uart_rx_produce] = buffer[0];
|
||||
com->uart_rx_produce = (com->uart_rx_produce + 1) & UART_RINGBUFFER_MASK_RX;
|
||||
length -=1;
|
||||
buffer +=1;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ftdicom_uart_read(FTDICom *com)
|
||||
{
|
||||
uint8_t c;
|
||||
|
||||
while(com->uart_rx_consume == com->uart_rx_produce);
|
||||
c = com->uart_rx_buf[com->uart_rx_consume];
|
||||
com->uart_rx_consume = (com->uart_rx_consume + 1) & UART_RINGBUFFER_MASK_RX;
|
||||
return c;
|
||||
}
|
||||
|
||||
int ftdicom_uart_read_nonblock(FTDICom *com)
|
||||
{
|
||||
return (com->uart_rx_consume != com->uart_rx_produce);
|
||||
}
|
||||
|
||||
/*
|
||||
* DMA functions
|
||||
*/
|
||||
int ftdicom_dma_write(FTDICom *com, uint8_t *data, size_t length)
|
||||
{
|
||||
return ftdicom_write(com, DMA_TAG, data, length, 1);
|
||||
}
|
||||
|
||||
void ftdicom_dma_read_set_callback(FTDICom *com, dma_read_ext_callback_t callback, void *userdata)
|
||||
{
|
||||
com->dma_read_ext_callback = callback;
|
||||
com->userdata = userdata;
|
||||
}
|
||||
|
||||
int ftdicom_dma_read_callback(FTDICom *com, uint8_t *buffer, int length)
|
||||
{
|
||||
unsigned int received_crc;
|
||||
unsigned int computed_crc;
|
||||
|
||||
received_crc = ((unsigned int)buffer[length-1] << 24)
|
||||
|((unsigned int)buffer[length-2] << 16)
|
||||
|((unsigned int)buffer[length-3] << 8)
|
||||
|((unsigned int)buffer[length-4]);
|
||||
computed_crc = crc32(buffer, length-4);
|
||||
if(received_crc != computed_crc) return -1;
|
||||
|
||||
if (com->dma_read_ext_callback != NULL)
|
||||
return com->dma_read_ext_callback(buffer, length-4, com->userdata);
|
||||
else
|
||||
return -1;
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* ftdicom.c - Low Level USB communication interface
|
||||
*
|
||||
* Provides UART and DMA low level communication
|
||||
* functions for FT2232H in slave fifo mode.
|
||||
*
|
||||
* Copyright (C) 2014 florent@enjoy-digital.fr
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __FTDICOM_H
|
||||
#define __FTDICOM_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "fastftdi.h"
|
||||
|
||||
/*
|
||||
* Protocol Constants
|
||||
*/
|
||||
|
||||
#define UART_TAG 0
|
||||
#define DMA_TAG 1
|
||||
|
||||
#define NEEDED_FOR_SIZE 9
|
||||
#define PAYLOAD_OFFSET 10
|
||||
|
||||
#define INCOMPLETE -1
|
||||
#define UNMATCHED 0
|
||||
|
||||
|
||||
/*
|
||||
* Buffer Constants
|
||||
*
|
||||
* Buffer sizes must be a power of 2 so that modulos can be computed
|
||||
* with logical AND.
|
||||
*/
|
||||
|
||||
// RAW
|
||||
#define RAW_BUFFER_SIZE 20*1024*1024
|
||||
|
||||
// UART
|
||||
#define UART_RINGBUFFER_SIZE_RX 4096
|
||||
#define UART_RINGBUFFER_MASK_RX (UART_RINGBUFFER_SIZE_RX-1)
|
||||
|
||||
// DMA
|
||||
#define DMA_BUFFER_SIZE_TX 20*1024*1024
|
||||
#define DMA_BUFFER_SIZE_RX 20*1024*1024
|
||||
|
||||
|
||||
/*
|
||||
* Struct
|
||||
*/
|
||||
typedef int (*dma_read_ext_callback_t)(uint8_t *buffer, int length, void *userdata);
|
||||
typedef struct {
|
||||
FTDIDevice *dev;
|
||||
|
||||
uint8_t *raw_tx_buf;
|
||||
uint8_t *raw_rx_buf;
|
||||
unsigned int raw_rx_buf_length;
|
||||
|
||||
char *uart_rx_buf;
|
||||
volatile unsigned int uart_rx_produce;
|
||||
volatile unsigned int uart_rx_consume;
|
||||
|
||||
pthread_t *thread;
|
||||
dma_read_ext_callback_t dma_read_ext_callback;
|
||||
|
||||
void *userdata;
|
||||
} FTDICom;
|
||||
|
||||
/*
|
||||
* Public Functions
|
||||
*/
|
||||
|
||||
int ftdicom_open(FTDICom *com);
|
||||
void ftdicom_close(FTDICom *com);
|
||||
|
||||
int ftdicom_uart_write_buffer(FTDICom *com, uint8_t *data, size_t length);
|
||||
int ftdicom_uart_write(FTDICom *com, uint8_t c);
|
||||
uint8_t ftdicom_uart_read(FTDICom *com);
|
||||
int ftdicom_uart_read_nonblock(FTDICom *com);
|
||||
|
||||
int ftdicom_dma_write(FTDICom *com, uint8_t *data, size_t length);
|
||||
void ftdicom_dma_read_set_callback(FTDICom *com, dma_read_ext_callback_t callback, void *userdata);
|
||||
|
||||
/*
|
||||
* Private Functions
|
||||
*/
|
||||
int ftdicom_write(FTDICom *com, uint8_t tag, uint8_t *data, size_t length, uint8_t with_crc);
|
||||
|
||||
int ftdicom_present_bytes(uint8_t tag, uint8_t *buffer, int length);
|
||||
int ftdicom_uart_present_bytes(uint8_t *buffer, int length);
|
||||
int ftdicom_dma_present_bytes(uint8_t *buffer, int length);
|
||||
int ftdicom_read_callback(uint8_t *buffer, int length, FTDIProgressInfo *progress, void *userdata);
|
||||
void *ftdicom_read_thread(void *userdata);
|
||||
|
||||
void ftdicom_uart_read_callback(FTDICom *com, uint8_t *buffer, int length);
|
||||
int ftdicom_dma_read_callback(FTDICom *com, uint8_t *buffer, int length);
|
||||
|
||||
|
||||
#endif /* __FTDICOM_H */
|
35
misoclib/com/liteusb/software/ftdi/linux/Makefile
Normal file
35
misoclib/com/liteusb/software/ftdi/linux/Makefile
Normal file
|
@ -0,0 +1,35 @@
|
|||
UNAME := $(shell uname)
|
||||
|
||||
LIBNAME := libftdicom
|
||||
|
||||
|
||||
# Load libusb via pkg-config
|
||||
PACKAGES := libusb-1.0
|
||||
CFLAGS += $(shell pkg-config --cflags $(PACKAGES))
|
||||
LDFLAGS += $(shell pkg-config --libs $(PACKAGES))
|
||||
|
||||
# Large file support
|
||||
CFLAGS += $(shell getconf LFS_CFLAGS)
|
||||
|
||||
CFLAGS += -fPIC
|
||||
|
||||
SO := $(LIBNAME).so
|
||||
SO_LDFLAGS := $(LDFLAGS) -shared
|
||||
|
||||
# Local headers
|
||||
CFLAGS += -I../include
|
||||
|
||||
SO_OBJS := ../fastftdi.o
|
||||
|
||||
CFLAGS += -O3 -g --std=c99
|
||||
|
||||
all: $(SO)
|
||||
cp libftdicom.so ../libftdicom.so
|
||||
|
||||
$(SO): $(SO_OBJS)
|
||||
cc -o $@ $^ $(SO_LDFLAGS)
|
||||
|
||||
*.o: *.h Makefile
|
||||
|
||||
clean:
|
||||
rm -f $(SO) $(OBJS) $(SO_OBJS)
|
12
misoclib/com/liteusb/software/ftdi/windows/Makefile
Normal file
12
misoclib/com/liteusb/software/ftdi/windows/Makefile
Normal file
|
@ -0,0 +1,12 @@
|
|||
CC=gcc
|
||||
CFLAGS_DLL =-Wall -O0 -g -shared -Wl,--subsystem,windows -DDLL
|
||||
LIBS= -lusb-1.0
|
||||
|
||||
all: libftdicom.dll
|
||||
cp libftdicom.dll ../libftdicom.dll
|
||||
|
||||
libftdicom.dll: ../fastftdi.c
|
||||
$(CC) -o $@ $(CFLAGS_DLL) $^ $(LIBS)
|
||||
|
||||
clean:
|
||||
rm -f libftdicom.dll ../libftdicom.dll
|
Loading…
Reference in a new issue