mirror of
https://github.com/GOSTSec/sgminer
synced 2025-01-30 00:14:26 +00:00
mmq usb v0.4 + api usb stats
This commit is contained in:
parent
4584068c67
commit
152e7e36a2
15
API-README
15
API-README
@ -330,6 +330,9 @@ The list of requests - a (*) means it requires privileged access - and replies a
|
||||
queue, scantime, expiry
|
||||
N is an integer in the range 0 to 9999
|
||||
|
||||
substats USBSTATS Stats of all LIBUSB mining devices except ztex
|
||||
e.g. Name=MMQ,ID=0,Stat=SendWork,Count=99,...|
|
||||
|
||||
When you enable, disable or restart a GPU or PGA, you will also get Thread messages
|
||||
in the cgminer status window
|
||||
|
||||
@ -383,7 +386,17 @@ miner.php - an example web page to access the API
|
||||
Feature Changelog for external applications using the API:
|
||||
|
||||
|
||||
API V1.20
|
||||
API V1.21
|
||||
|
||||
Added API commands:
|
||||
'usbstats'
|
||||
|
||||
Modified API commands:
|
||||
each MMQ shows up as 4 devices each with it's own stats
|
||||
|
||||
----------
|
||||
|
||||
API V1.20 (cgminer v2.8.5)
|
||||
|
||||
Modified API commands:
|
||||
'pools' - add 'Has Stratum', 'Stratum Active', 'Stratum URL'
|
||||
|
62
FPGA-README
62
FPGA-README
@ -6,7 +6,65 @@ ModMinerQuad (MMQ)
|
||||
------------------
|
||||
|
||||
The mining bitstream does not survive a power cycle, so cgminer will upload
|
||||
it, if it needs to, before it starts mining
|
||||
it, if it needs to, before it starts mining (approx 7min 40sec)
|
||||
|
||||
The red LED also flashes while it is uploading the bitstream
|
||||
|
||||
-
|
||||
|
||||
When mining on windows, the driver being used will determine if mining will work.
|
||||
|
||||
If the driver doesn't allow mining, you will get a "USB init," error message
|
||||
i.e. one of:
|
||||
open device failed, err %d, you need to install a Windows USB driver for the device
|
||||
or
|
||||
kernel detach failed :(
|
||||
or
|
||||
claim interface %d failed, err %d
|
||||
|
||||
The best solution for this is to use a tool called Zadig to set the driver:
|
||||
http://sourceforge.net/projects/libwdi/files/zadig/
|
||||
|
||||
This allows you set the driver for the device to be WinUSB which is usually
|
||||
required to make it work if your having problems
|
||||
|
||||
You must also make sure you are using the latest libusb-1.0.dll supplied
|
||||
with cgminer (not the libusbx version)
|
||||
|
||||
-
|
||||
|
||||
There is a hidden option in cgminer to dump out a lot of information
|
||||
about USB that will help the developers to assist you if you are having
|
||||
problems:
|
||||
|
||||
--usb-dump 0
|
||||
|
||||
It will only help if you have a working MMQ device attached to the computer
|
||||
|
||||
-
|
||||
|
||||
If the MMQ doesn't respond to cgminer at all, or the red LED isn't flashing
|
||||
then you will need to reset the MMQ
|
||||
|
||||
The red LED should always be flashing when it is mining or ready to mine
|
||||
|
||||
To reset the MMQ, you are best to press the left "RESET" button on the
|
||||
backplane, then unplug and replug the USB cable
|
||||
|
||||
If your MMQ doesn't have a button on the "RESET" pad, you need to join
|
||||
the two left pads of the "RESET" pad with conductive wire to reset it.
|
||||
Cutting a small (metal) paper-clip in half works well for this
|
||||
|
||||
Then unplug the USB cable, wait for 5 seconds, then plug it back in
|
||||
|
||||
After you press reset, the red LED near the USB port should blink continuously
|
||||
|
||||
If it still wont work, power off, wait for 5 seconds, then power on the MMQ
|
||||
This of course means it will upload the bitstream again when you start cgminer
|
||||
|
||||
-
|
||||
|
||||
Device 0 is on the power end of the board
|
||||
|
||||
-
|
||||
|
||||
@ -64,7 +122,7 @@ a (hack) solution to this is to blacklist the MMQ USB device in
|
||||
|
||||
Adding 2 lines like this (just above APC) should help
|
||||
# MMQ
|
||||
ATTRS{idVendor}=="ifc9", ATTRS{idProduct}=="0003", ENV{ID_MM_DEVICE_IGNORE}="1"
|
||||
ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="0003", ENV{ID_MM_DEVICE_IGNORE}="1"
|
||||
|
||||
The change will be lost and need to be re-done, next time you update the
|
||||
modem-manager software
|
||||
|
@ -38,7 +38,7 @@ cgminer_SOURCES := cgminer.c
|
||||
|
||||
cgminer_SOURCES += elist.h miner.h compat.h bench_block.h \
|
||||
util.c util.h uthash.h logging.h \
|
||||
sha2.c sha2.h api.c
|
||||
sha2.c sha2.h api.c usbutils.h
|
||||
|
||||
cgminer_SOURCES += logging.c
|
||||
|
||||
@ -91,7 +91,7 @@ cgminer_SOURCES += driver-icarus.c
|
||||
endif
|
||||
|
||||
if HAS_MODMINER
|
||||
cgminer_SOURCES += driver-modminer.c
|
||||
cgminer_SOURCES += driver-modminer.c usbutils.c
|
||||
bitstreamsdir = $(bindir)/bitstreams
|
||||
dist_bitstreams_DATA = bitstreams/*
|
||||
endif
|
||||
|
75
api.c
75
api.c
@ -131,7 +131,7 @@ static const char SEPARATOR = '|';
|
||||
#define SEPSTR "|"
|
||||
static const char GPUSEP = ',';
|
||||
|
||||
static const char *APIVERSION = "1.20";
|
||||
static const char *APIVERSION = "1.21";
|
||||
static const char *DEAD = "Dead";
|
||||
#if defined(HAVE_OPENCL) || defined(HAVE_AN_FPGA)
|
||||
static const char *SICK = "Sick";
|
||||
@ -227,6 +227,7 @@ static const char *OSINFO =
|
||||
#define _MINECOIN "COIN"
|
||||
#define _DEBUGSET "DEBUG"
|
||||
#define _SETCONFIG "SETCONFIG"
|
||||
#define _USBSTATS "USBSTATS"
|
||||
|
||||
static const char ISJSON = '{';
|
||||
#define JSON0 "{"
|
||||
@ -266,6 +267,7 @@ static const char ISJSON = '{';
|
||||
#define JSON_MINECOIN JSON1 _MINECOIN JSON2
|
||||
#define JSON_DEBUGSET JSON1 _DEBUGSET JSON2
|
||||
#define JSON_SETCONFIG JSON1 _SETCONFIG JSON2
|
||||
#define JSON_USBSTATS JSON1 _USBSTATS JSON2
|
||||
#define JSON_END JSON4 JSON5
|
||||
|
||||
static const char *JSON_COMMAND = "command";
|
||||
@ -369,6 +371,8 @@ static const char *JSON_PARAMETER = "parameter";
|
||||
#define MSG_INVNUM 84
|
||||
#define MSG_CONPAR 85
|
||||
#define MSG_CONVAL 86
|
||||
#define MSG_USBSTA 87
|
||||
#define MSG_NOUSTA 88
|
||||
|
||||
enum code_severity {
|
||||
SEVERITY_ERR,
|
||||
@ -533,6 +537,8 @@ struct CODES {
|
||||
{ SEVERITY_ERR, MSG_INVNUM, PARAM_BOTH, "Invalid number (%d) for '%s' range is 0-9999" },
|
||||
{ SEVERITY_ERR, MSG_CONPAR, PARAM_NONE, "Missing config parameters 'name,N'" },
|
||||
{ SEVERITY_ERR, MSG_CONVAL, PARAM_STR, "Missing config value N for '%s,N'" },
|
||||
{ SEVERITY_SUCC, MSG_USBSTA, PARAM_NONE, "USB Statistics" },
|
||||
{ SEVERITY_INFO, MSG_NOUSTA, PARAM_NONE, "No USB Statistics" },
|
||||
{ SEVERITY_FAIL, 0, 0, NULL }
|
||||
};
|
||||
|
||||
@ -1383,27 +1389,8 @@ static void pgastatus(int pga, bool isjson)
|
||||
frequency = cgpu->device_ztex->freqM1 * (cgpu->device_ztex->freqM + 1);
|
||||
#endif
|
||||
#ifdef USE_MODMINER
|
||||
// TODO: a modminer has up to 4 devices but only 1 set of data for all ...
|
||||
// except 4 sets of data for temp/clock
|
||||
// So this should change in the future to just find the single temp/clock
|
||||
// if the modminer code splits the device into seperate devices later
|
||||
// For now, just display the highest temp and the average clock
|
||||
if (cgpu->api == &modminer_api) {
|
||||
int tc = cgpu->threads;
|
||||
int i;
|
||||
|
||||
temp = 0;
|
||||
if (tc > 4)
|
||||
tc = 4;
|
||||
for (i = 0; i < tc; i++) {
|
||||
struct thr_info *thr = cgpu->thr[i];
|
||||
struct modminer_fpga_state *state = thr->cgpu_data;
|
||||
if (state->temp > temp)
|
||||
temp = state->temp;
|
||||
frequency += state->clock;
|
||||
}
|
||||
frequency /= (tc ? tc : 1);
|
||||
}
|
||||
if (cgpu->api == &modminer_api)
|
||||
frequency = cgpu->clock;
|
||||
#endif
|
||||
|
||||
cgpu->utility = cgpu->accepted / ( total_secs ? total_secs : 1 ) * 60;
|
||||
@ -2990,6 +2977,49 @@ static void setconfig(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __m
|
||||
strcpy(io_buffer, message(MSG_SETCONFIG, value, param, isjson));
|
||||
}
|
||||
|
||||
static void usbstats(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
|
||||
{
|
||||
struct api_data *root = NULL;
|
||||
|
||||
#ifdef USE_MODMINER
|
||||
char buf[TMPBUFSIZ];
|
||||
int count = 0;
|
||||
|
||||
root = api_usb_stats(&count);
|
||||
#endif
|
||||
|
||||
if (!root) {
|
||||
strcpy(io_buffer, message(MSG_NOUSTA, 0, NULL, isjson));
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_MODMINER
|
||||
|
||||
strcpy(io_buffer, message(MSG_USBSTA, 0, NULL, isjson));
|
||||
|
||||
if (isjson) {
|
||||
strcat(io_buffer, COMMA);
|
||||
strcat(io_buffer, JSON_USBSTATS);
|
||||
}
|
||||
|
||||
root = print_data(root, buf, isjson);
|
||||
strcat(io_buffer, buf);
|
||||
|
||||
while (42) {
|
||||
root = api_usb_stats(&count);
|
||||
if (!root)
|
||||
break;
|
||||
|
||||
strcat(io_buffer, COMMA);
|
||||
root = print_data(root, buf, isjson);
|
||||
strcat(io_buffer, buf);
|
||||
}
|
||||
|
||||
if (isjson)
|
||||
strcat(io_buffer, JSON_CLOSE);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void checkcommand(__maybe_unused SOCKETTYPE c, char *param, bool isjson, char group);
|
||||
|
||||
struct CMDS {
|
||||
@ -3045,6 +3075,7 @@ struct CMDS {
|
||||
{ "coin", minecoin, false },
|
||||
{ "debug", debugstate, true },
|
||||
{ "setconfig", setconfig, true },
|
||||
{ "usbstats", usbstats, false },
|
||||
{ NULL, NULL, false }
|
||||
};
|
||||
|
||||
|
21
cgminer.c
21
cgminer.c
@ -146,6 +146,9 @@ bool opt_disable_pool = true;
|
||||
char *opt_icarus_options = NULL;
|
||||
char *opt_icarus_timing = NULL;
|
||||
bool opt_worktime;
|
||||
#ifdef HAVE_LIBUSB
|
||||
int opt_usbdump = -1;
|
||||
#endif
|
||||
|
||||
char *opt_kernel_path;
|
||||
char *cgminer_path;
|
||||
@ -167,6 +170,10 @@ int gpur_thr_id;
|
||||
static int api_thr_id;
|
||||
static int total_threads;
|
||||
|
||||
#ifdef HAVE_LIBUSB
|
||||
pthread_mutex_t cgusb_lock;
|
||||
#endif
|
||||
|
||||
static pthread_mutex_t hash_lock;
|
||||
static pthread_mutex_t qd_lock;
|
||||
static pthread_mutex_t *stgd_lock;
|
||||
@ -1099,6 +1106,11 @@ static struct opt_table opt_config_table[] = {
|
||||
OPT_WITH_ARG("--user|-u",
|
||||
set_user, NULL, NULL,
|
||||
"Username for bitcoin JSON-RPC server"),
|
||||
#ifdef HAVE_LIBUSB
|
||||
OPT_WITH_ARG("--usb-dump",
|
||||
set_int_0_to_10, opt_show_intval, &opt_usbdump,
|
||||
opt_hidden),
|
||||
#endif
|
||||
#ifdef HAVE_OPENCL
|
||||
OPT_WITH_ARG("--vectors|-v",
|
||||
set_vector, NULL, NULL,
|
||||
@ -6583,8 +6595,15 @@ int main(int argc, char *argv[])
|
||||
for (i = 0; i < argc; i++)
|
||||
initial_args[i] = strdup(argv[i]);
|
||||
initial_args[argc] = NULL;
|
||||
|
||||
#ifdef HAVE_LIBUSB
|
||||
libusb_init(NULL);
|
||||
int err = libusb_init(NULL);
|
||||
if (err) {
|
||||
fprintf(stderr, "libusb_init() failed err %d", err);
|
||||
fflush(stderr);
|
||||
quit(1, "libusb_init() failed");
|
||||
}
|
||||
mutex_init(&cgusb_lock);
|
||||
#endif
|
||||
|
||||
mutex_init(&hash_lock);
|
||||
|
@ -321,7 +321,7 @@ fi
|
||||
|
||||
AM_CONDITIONAL([HAS_YASM], [test x$has_yasm = xtrue])
|
||||
|
||||
if test "x$bitforce$modminer" != xnono; then
|
||||
if test "x$bitforce" != xno; then
|
||||
AC_ARG_WITH([libudev], [AC_HELP_STRING([--without-libudev], [Autodetect FPGAs using libudev (default enabled)])],
|
||||
[libudev=$withval],
|
||||
[libudev=auto]
|
||||
@ -343,7 +343,7 @@ AM_CONDITIONAL([HAVE_LIBUDEV], [test x$libudev != xno])
|
||||
|
||||
PKG_PROG_PKG_CONFIG()
|
||||
|
||||
if test "x$ztex" != xno; then
|
||||
if test "x$ztex$modminer" != xnono; then
|
||||
case $target in
|
||||
*-*-freebsd*)
|
||||
LIBUSB_LIBS="-lusb"
|
||||
@ -502,7 +502,7 @@ else
|
||||
echo " Ztex.FPGAs...........: Disabled"
|
||||
fi
|
||||
|
||||
if test "x$bitforce$modminer" != xnono; then
|
||||
if test "x$bitforce" != xno; then
|
||||
echo " libudev.detection....: $libudev"
|
||||
fi
|
||||
|
||||
|
1058
driver-modminer.c
1058
driver-modminer.c
File diff suppressed because it is too large
Load Diff
30
miner.h
30
miner.h
@ -110,6 +110,10 @@ static inline int fsync (int fd)
|
||||
#include "libztex.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_MODMINER
|
||||
#include "usbutils.h"
|
||||
#endif
|
||||
|
||||
#if !defined(WIN32) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
|
||||
#define bswap_16 __builtin_bswap16
|
||||
#define bswap_32 __builtin_bswap32
|
||||
@ -360,9 +364,20 @@ struct cgpu_info {
|
||||
union {
|
||||
#ifdef USE_ZTEX
|
||||
struct libztex_device *device_ztex;
|
||||
#endif
|
||||
#ifdef USE_MODMINER
|
||||
struct cg_usb_device *usbdev;
|
||||
#endif
|
||||
int device_fd;
|
||||
};
|
||||
#ifdef USE_MODMINER
|
||||
int usbstat;
|
||||
char fpgaid;
|
||||
unsigned char clock;
|
||||
pthread_mutex_t *modminer_mutex;
|
||||
bool tried_two_byte_temp;
|
||||
bool one_byte_temp;
|
||||
#endif
|
||||
#ifdef USE_BITFORCE
|
||||
struct timeval work_start_tv;
|
||||
unsigned int wait_ms;
|
||||
@ -373,9 +388,8 @@ struct cgpu_info {
|
||||
bool nonce_range;
|
||||
bool polling;
|
||||
bool flash_led;
|
||||
pthread_mutex_t device_mutex;
|
||||
#endif
|
||||
pthread_mutex_t device_mutex;
|
||||
|
||||
enum dev_enable deven;
|
||||
int accepted;
|
||||
int rejected;
|
||||
@ -654,6 +668,9 @@ extern bool opt_restart;
|
||||
extern char *opt_icarus_options;
|
||||
extern char *opt_icarus_timing;
|
||||
extern bool opt_worktime;
|
||||
#ifdef HAVE_LIBUSB
|
||||
extern int opt_usbdump;
|
||||
#endif
|
||||
#ifdef USE_BITFORCE
|
||||
extern bool opt_bfl_noncerange;
|
||||
#endif
|
||||
@ -684,6 +701,10 @@ extern int opt_queue;
|
||||
extern int opt_scantime;
|
||||
extern int opt_expiry;
|
||||
|
||||
#ifdef HAVE_LIBUSB
|
||||
extern pthread_mutex_t cgusb_lock;
|
||||
#endif
|
||||
|
||||
extern pthread_mutex_t console_lock;
|
||||
extern pthread_mutex_t ch_lock;
|
||||
|
||||
@ -998,9 +1019,10 @@ struct modminer_fpga_state {
|
||||
uint32_t hashes;
|
||||
|
||||
char next_work_cmd[46];
|
||||
char fpgaid;
|
||||
|
||||
unsigned char clock;
|
||||
float temp;
|
||||
bool overheated;
|
||||
bool new_work;
|
||||
|
||||
uint32_t shares;
|
||||
uint32_t shares_last_hw;
|
||||
|
1192
usbutils.c
Normal file
1192
usbutils.c
Normal file
File diff suppressed because it is too large
Load Diff
120
usbutils.h
Normal file
120
usbutils.h
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2012 Andrew Smith
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 3 of the License, or (at your option)
|
||||
* any later version. See COPYING for more details.
|
||||
*/
|
||||
|
||||
#ifndef USBUTILS_H
|
||||
#define USBUTILS_H
|
||||
|
||||
#include <libusb.h>
|
||||
|
||||
// Use the device defined timeout
|
||||
#define DEVTIMEOUT 0
|
||||
|
||||
// For endpoints defined in usb_find_devices.eps,
|
||||
// the first two must be the default IN and OUT
|
||||
#define DEFAULT_EP_IN 0
|
||||
#define DEFAULT_EP_OUT 1
|
||||
|
||||
struct usb_endpoints {
|
||||
uint8_t att;
|
||||
uint16_t size;
|
||||
unsigned char ep;
|
||||
bool found;
|
||||
};
|
||||
|
||||
struct usb_find_devices {
|
||||
int drv;
|
||||
const char *name;
|
||||
uint16_t idVendor;
|
||||
uint16_t idProduct;
|
||||
int config;
|
||||
int interface;
|
||||
unsigned int timeout;
|
||||
int epcount;
|
||||
struct usb_endpoints *eps;
|
||||
};
|
||||
|
||||
struct cg_usb_device {
|
||||
struct usb_find_devices *found;
|
||||
libusb_device_handle *handle;
|
||||
pthread_mutex_t *mutex;
|
||||
struct libusb_device_descriptor *descriptor;
|
||||
uint8_t bus_number;
|
||||
uint8_t device_address;
|
||||
uint16_t usbver;
|
||||
int speed;
|
||||
char *prod_string;
|
||||
char *manuf_string;
|
||||
char *serial_string;
|
||||
unsigned char fwVersion; // ??
|
||||
unsigned char interfaceVersion; // ??
|
||||
};
|
||||
|
||||
enum usb_cmds {
|
||||
C_PING = 0,
|
||||
C_CLEAR,
|
||||
C_REQUESTVERSION,
|
||||
C_GETVERSION,
|
||||
C_REQUESTFPGACOUNT,
|
||||
C_GETFPGACOUNT,
|
||||
C_STARTPROGRAM,
|
||||
C_STARTPROGRAMSTATUS,
|
||||
C_PROGRAM,
|
||||
C_PROGRAMSTATUS,
|
||||
C_PROGRAMSTATUS2,
|
||||
C_FINALPROGRAMSTATUS,
|
||||
C_SETCLOCK,
|
||||
C_REPLYSETCLOCK,
|
||||
C_REQUESTUSERCODE,
|
||||
C_GETUSERCODE,
|
||||
C_REQUESTTEMPERATURE,
|
||||
C_GETTEMPERATURE,
|
||||
C_SENDWORK,
|
||||
C_SENDWORKSTATUS,
|
||||
C_REQUESTWORKSTATUS,
|
||||
C_GETWORKSTATUS,
|
||||
C_MAX
|
||||
};
|
||||
|
||||
struct device_api;
|
||||
struct cgpu_info;
|
||||
|
||||
void usb_uninit(struct cgpu_info *cgpu);
|
||||
bool usb_init(struct cgpu_info *cgpu, struct libusb_device *dev, struct usb_find_devices *found);
|
||||
void usb_detect(struct device_api *api, bool (*device_detect)(struct libusb_device *, struct usb_find_devices *));
|
||||
struct api_data *api_usb_stats(int *count);
|
||||
void update_usb_stats(struct cgpu_info *cgpu);
|
||||
int _usb_read(struct cgpu_info *cgpu, int ep, char *buf, size_t bufsiz, int *processed, unsigned int timeout, int eol, enum usb_cmds);
|
||||
int _usb_write(struct cgpu_info *cgpu, int ep, char *buf, size_t bufsiz, int *processed, unsigned int timeout, enum usb_cmds);
|
||||
void usb_cleanup();
|
||||
|
||||
#define usb_read(cgpu, buf, bufsiz, read, cmd) \
|
||||
_usb_read(cgpu, DEFAULT_EP_IN, buf, bufsiz, read, DEVTIMEOUT, -1, cmd)
|
||||
|
||||
#define usb_read_ep(cgpu, ep, buf, bufsiz, read, cmd) \
|
||||
_usb_read(cgpu, ep, buf, bufsiz, read, DEVTIMEOUT, -1, cmd)
|
||||
|
||||
#define usb_read_timeout(cgpu, buf, bufsiz, read, timeout, cmd) \
|
||||
_usb_read(cgpu, DEFAULT_EP_IN, buf, bufsiz, read, timeout, -1, cmd)
|
||||
|
||||
#define usb_read_ep_timeout(cgpu, ep, buf, bufsiz, read, timeout, cmd) \
|
||||
_usb_read(cgpu, ep, buf, bufsiz, read, timeout, -1, cmd)
|
||||
|
||||
#define usb_write(cgpu, buf, bufsiz, wrote, cmd) \
|
||||
_usb_write(cgpu, DEFAULT_EP_OUT, buf, bufsiz, wrote, DEVTIMEOUT, cmd)
|
||||
|
||||
#define usb_write_ep(cgpu, ep, buf, bufsiz, wrote, cmd) \
|
||||
_usb_write(cgpu, ep, buf, bufsiz, wrote, DEVTIMEOUT, cmd)
|
||||
|
||||
#define usb_write_timeout(cgpu, buf, bufsiz, wrote, timeout, cmd) \
|
||||
_usb_write(cgpu, DEFAULT_EP_OUT, buf, bufsiz, wrote, timeout, cmd)
|
||||
|
||||
#define usb_write_ep_timeout(cgpu, ep, buf, bufsiz, wrote, timeout, cmd) \
|
||||
_usb_write(cgpu, ep, buf, bufsiz, wrote, timeout, cmd)
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user