mirror of
https://github.com/GOSTSec/sgminer
synced 2025-01-22 20:44:19 +00:00
BitForce FPGA support
cgminer will scan for and mine BitForce FPGAs on USB ports by providing the new --scan-serial <device> option, or autodetect them by searching /dev/serial/by-id for *BitFORCE_SHA256*
This commit is contained in:
parent
69966ffe17
commit
5dfc8b694f
@ -60,3 +60,7 @@ AM_CFLAGS = -DHAS_YASM
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if USE_BITFORCE
|
||||||
|
cgminer_SOURCES += bitforce.c
|
||||||
|
endif
|
||||||
|
219
bitforce.c
Normal file
219
bitforce.c
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 Luke Dashjr
|
||||||
|
*
|
||||||
|
* 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 2 of the License, or (at your option)
|
||||||
|
* any later version. See COPYING for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "elist.h"
|
||||||
|
#include "miner.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct device_api bitforce_api;
|
||||||
|
|
||||||
|
static bool bitforce_detect_one(const char *devpath)
|
||||||
|
{
|
||||||
|
char pdevbuf[0x100];
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
if (total_devices == MAX_DEVICES)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
FILE *fileDev = fopen(devpath, "r+b");
|
||||||
|
if (unlikely(!fileDev))
|
||||||
|
{
|
||||||
|
applog(LOG_DEBUG, "BitForce Detect: Failed to open %s", devpath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setbuf(fileDev, NULL);
|
||||||
|
fprintf(fileDev, "ZGX");
|
||||||
|
if (!fgets(pdevbuf, sizeof(pdevbuf), fileDev))
|
||||||
|
{
|
||||||
|
applog(LOG_ERR, "Error reading from BitForce (ZGX)");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
fclose(fileDev);
|
||||||
|
if (unlikely(!strstr(pdevbuf, "SHA256")))
|
||||||
|
{
|
||||||
|
applog(LOG_DEBUG, "BitForce Detect: Didn't recognize BitForce on %s", devpath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a real BitForce!
|
||||||
|
struct cgpu_info *bitforce;
|
||||||
|
bitforce = calloc(1, sizeof(*bitforce));
|
||||||
|
devices[total_devices++] = bitforce;
|
||||||
|
bitforce->api = &bitforce_api;
|
||||||
|
bitforce->device_id = i++;
|
||||||
|
bitforce->device_path = strdup(devpath);
|
||||||
|
bitforce->enabled = true;
|
||||||
|
bitforce->threads = 1;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bitforce_detect_auto()
|
||||||
|
{
|
||||||
|
DIR *D;
|
||||||
|
struct dirent *de;
|
||||||
|
const char udevdir[] = "/dev/serial/by-id";
|
||||||
|
char devpath[sizeof(udevdir) + 1 + NAME_MAX];
|
||||||
|
char *devfile = devpath + sizeof(udevdir);
|
||||||
|
|
||||||
|
D = opendir(udevdir);
|
||||||
|
if (!D)
|
||||||
|
return;
|
||||||
|
memcpy(devpath, udevdir, sizeof(udevdir) - 1);
|
||||||
|
devpath[sizeof(udevdir) - 1] = '/';
|
||||||
|
while ( (de = readdir(D)) ) {
|
||||||
|
if (!strstr(de->d_name, "BitFORCE_SHA256"))
|
||||||
|
continue;
|
||||||
|
strcpy(devfile, de->d_name);
|
||||||
|
bitforce_detect_one(devpath);
|
||||||
|
}
|
||||||
|
closedir(D);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bitforce_detect()
|
||||||
|
{
|
||||||
|
struct string_elist *iter, *tmp;
|
||||||
|
|
||||||
|
list_for_each_entry_safe(iter, tmp, &scan_devices, list) {
|
||||||
|
if (bitforce_detect_one(iter->string))
|
||||||
|
string_elist_del(iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
bitforce_detect_auto();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bitforce_thread_prepare(struct thr_info *thr)
|
||||||
|
{
|
||||||
|
struct cgpu_info *bitforce = thr->cgpu;
|
||||||
|
|
||||||
|
struct timeval now;
|
||||||
|
|
||||||
|
FILE *fileDev = fopen(bitforce->device_path, "r+b");
|
||||||
|
if (unlikely(!fileDev))
|
||||||
|
{
|
||||||
|
applog(LOG_ERR, "Failed to open BitForce on %s", bitforce->device_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
int nDevFD = fileno(fileDev);
|
||||||
|
struct termios pattr;
|
||||||
|
tcgetattr(nDevFD, &pattr);
|
||||||
|
pattr.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
|
||||||
|
pattr.c_oflag &= ~OPOST;
|
||||||
|
pattr.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
|
||||||
|
pattr.c_cflag &= ~(CSIZE | PARENB);
|
||||||
|
pattr.c_cflag |= CS8;
|
||||||
|
tcsetattr(nDevFD, TCSANOW, &pattr);
|
||||||
|
}
|
||||||
|
setbuf(fileDev, NULL);
|
||||||
|
bitforce->device_file = fileDev;
|
||||||
|
|
||||||
|
applog(LOG_INFO, "Opened BitForce on %s", bitforce->device_path);
|
||||||
|
gettimeofday(&now, NULL);
|
||||||
|
get_datestamp(bitforce->init, &now);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t bitforce_scanhash(struct thr_info *thr, struct work *work, uint64_t max_nonce)
|
||||||
|
{
|
||||||
|
struct cgpu_info *bitforce = thr->cgpu;
|
||||||
|
FILE *fileDev = bitforce->device_file;
|
||||||
|
|
||||||
|
char pdevbuf[0x100];
|
||||||
|
unsigned char ob[61] = ">>>>>>>>12345678901234567890123456789012123456789012>>>>>>>>";
|
||||||
|
int i;
|
||||||
|
char *pnoncebuf;
|
||||||
|
uint32_t nonce;
|
||||||
|
|
||||||
|
fprintf(fileDev, "ZDX");
|
||||||
|
if (!fgets(pdevbuf, sizeof(pdevbuf), fileDev)) {
|
||||||
|
applog(LOG_ERR, "Error reading from BitForce (ZDX)");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (unlikely(pdevbuf[0] != 'O' || pdevbuf[1] != 'K'))
|
||||||
|
{
|
||||||
|
applog(LOG_ERR, "BitForce ZDX reports: %s", pdevbuf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(ob + 8, work->midstate, 32);
|
||||||
|
memcpy(ob + 8 + 32, work->data + 64, 12);
|
||||||
|
fwrite(ob, 60, 1, fileDev);
|
||||||
|
applog(LOG_DEBUG, "BitForce block data: %s", bin2hex(ob + 8, 44));
|
||||||
|
|
||||||
|
if (!fgets(pdevbuf, sizeof(pdevbuf), fileDev))
|
||||||
|
{
|
||||||
|
applog(LOG_ERR, "Error reading from BitForce (block data)");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (unlikely(pdevbuf[0] != 'O' || pdevbuf[1] != 'K'))
|
||||||
|
{
|
||||||
|
applog(LOG_ERR, "BitForce block data reports: %s", pdevbuf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep(4500000);
|
||||||
|
i = 4500;
|
||||||
|
while (1) {
|
||||||
|
fprintf(fileDev, "ZFX");
|
||||||
|
if (!fgets(pdevbuf, sizeof(pdevbuf), fileDev))
|
||||||
|
{
|
||||||
|
applog(LOG_ERR, "Error reading from BitForce (ZFX)");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (pdevbuf[0] != 'B')
|
||||||
|
break;
|
||||||
|
usleep(10000);
|
||||||
|
i += 10;
|
||||||
|
}
|
||||||
|
applog(LOG_DEBUG, "BitForce waited %dms until %s\n", i, pdevbuf);
|
||||||
|
work->blk.nonce = 0xffffffff;
|
||||||
|
if (pdevbuf[2] == '-')
|
||||||
|
return 0xffffffff;
|
||||||
|
else
|
||||||
|
if (strncasecmp(pdevbuf, "NONCE-FOUND", 11)) {
|
||||||
|
applog(LOG_ERR, "BitForce result reports: %s", pdevbuf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pnoncebuf = &pdevbuf[12];
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
hex2bin((void*)&nonce, pnoncebuf, 4);
|
||||||
|
#ifndef __BIG_ENDIAN__
|
||||||
|
nonce = swab32(nonce);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
submit_nonce(thr, work, nonce);
|
||||||
|
if (pnoncebuf[8] != ',')
|
||||||
|
break;
|
||||||
|
pnoncebuf += 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0xffffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct device_api bitforce_api = {
|
||||||
|
.name = "BFL",
|
||||||
|
.api_detect = bitforce_detect,
|
||||||
|
// .reinit_device = TODO
|
||||||
|
.thread_prepare = bitforce_thread_prepare,
|
||||||
|
.scanhash = bitforce_scanhash,
|
||||||
|
};
|
16
configure.ac
16
configure.ac
@ -181,6 +181,15 @@ else
|
|||||||
DLOPEN_FLAGS=""
|
DLOPEN_FLAGS=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
bitforce=yes
|
||||||
|
AC_ARG_ENABLE([bitforce],
|
||||||
|
[AC_HELP_STRING([--disable-bitforce],[Don't compile support for BitForce FPGAs])],
|
||||||
|
[bitforce=$enableval]
|
||||||
|
)
|
||||||
|
if test "x$bitforce" != xno; then
|
||||||
|
AC_DEFINE([USE_BITFORCE], [1], [Defined to 1 if BitForce support is wanted.])
|
||||||
|
fi
|
||||||
|
|
||||||
AC_SEARCH_LIBS(addstr, ncurses pdcurses, ,
|
AC_SEARCH_LIBS(addstr, ncurses pdcurses, ,
|
||||||
AC_MSG_ERROR([Could not find curses library - please install libncurses-dev or pdcurses-dev]))
|
AC_MSG_ERROR([Could not find curses library - please install libncurses-dev or pdcurses-dev]))
|
||||||
|
|
||||||
@ -190,6 +199,7 @@ AC_CHECK_LIB(pdcurses, addstr, PDCURSES_LIBS=-lpdcurses)
|
|||||||
AM_CONDITIONAL([WANT_JANSSON], [test x$request_jansson = xtrue])
|
AM_CONDITIONAL([WANT_JANSSON], [test x$request_jansson = xtrue])
|
||||||
AM_CONDITIONAL([HAVE_WINDOWS], [test x$have_win32 = xtrue])
|
AM_CONDITIONAL([HAVE_WINDOWS], [test x$have_win32 = xtrue])
|
||||||
AM_CONDITIONAL([HAVE_x86_64], [test x$have_x86_64 = xtrue])
|
AM_CONDITIONAL([HAVE_x86_64], [test x$have_x86_64 = xtrue])
|
||||||
|
AM_CONDITIONAL([USE_BITFORCE], [test x$bitforce != xno])
|
||||||
|
|
||||||
if test x$request_jansson = xtrue
|
if test x$request_jansson = xtrue
|
||||||
then
|
then
|
||||||
@ -312,18 +322,20 @@ echo
|
|||||||
echo "Configuration Options Summary:"
|
echo "Configuration Options Summary:"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
echo " BitForce.FPGAs.......: $bitforce"
|
||||||
|
|
||||||
if test "x$opencl" != xno; then
|
if test "x$opencl" != xno; then
|
||||||
if test $found_opencl = 1; then
|
if test $found_opencl = 1; then
|
||||||
echo " OpenCL...............: FOUND. GPU mining support enabled"
|
echo " OpenCL...............: FOUND. GPU mining support enabled"
|
||||||
else
|
else
|
||||||
echo " OpenCL...............: NOT FOUND. GPU mining support DISABLED"
|
echo " OpenCL...............: NOT FOUND. GPU mining support DISABLED"
|
||||||
if test "x$cpumining" != xyes; then
|
if test "x$cpumining$bitforce" = xnono; then
|
||||||
AC_MSG_ERROR([No mining configured in])
|
AC_MSG_ERROR([No mining configured in])
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo " OpenCL...............: Detection overrided. GPU mining support DISABLED"
|
echo " OpenCL...............: Detection overrided. GPU mining support DISABLED"
|
||||||
if test "x$cpumining" != xyes; then
|
if test "x$cpumining$bitforce" = xnono; then
|
||||||
AC_MSG_ERROR([No mining configured in])
|
AC_MSG_ERROR([No mining configured in])
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
29
main.c
29
main.c
@ -216,6 +216,7 @@ static bool opt_restart = true;
|
|||||||
static bool opt_nogpu;
|
static bool opt_nogpu;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct list_head scan_devices;
|
||||||
int nDevs;
|
int nDevs;
|
||||||
static int opt_g_threads = 2;
|
static int opt_g_threads = 2;
|
||||||
static signed int devices_enabled = 0;
|
static signed int devices_enabled = 0;
|
||||||
@ -1003,6 +1004,12 @@ static char *set_float_0_to_99(const char *arg, float *f)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *add_serial(char *arg)
|
||||||
|
{
|
||||||
|
string_elist_add(arg, &scan_devices);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static char *set_devices(char *arg)
|
static char *set_devices(char *arg)
|
||||||
{
|
{
|
||||||
int i = strtol(arg, &arg, 0);
|
int i = strtol(arg, &arg, 0);
|
||||||
@ -1670,6 +1677,11 @@ static struct opt_table opt_config_table[] = {
|
|||||||
OPT_WITHOUT_ARG("--round-robin",
|
OPT_WITHOUT_ARG("--round-robin",
|
||||||
set_rr, &pool_strategy,
|
set_rr, &pool_strategy,
|
||||||
"Change multipool strategy from failover to round robin on failure"),
|
"Change multipool strategy from failover to round robin on failure"),
|
||||||
|
#ifdef USE_BITFORCE
|
||||||
|
OPT_WITH_ARG("--scan-serial|-S",
|
||||||
|
add_serial, NULL, NULL,
|
||||||
|
"Serial port to probe for BitForce device"),
|
||||||
|
#endif
|
||||||
OPT_WITH_ARG("--scan-time|-s",
|
OPT_WITH_ARG("--scan-time|-s",
|
||||||
set_int_0_to_9999, opt_show_intval, &opt_scantime,
|
set_int_0_to_9999, opt_show_intval, &opt_scantime,
|
||||||
"Upper bound on time spent scanning current work, in seconds"),
|
"Upper bound on time spent scanning current work, in seconds"),
|
||||||
@ -4340,6 +4352,7 @@ static inline bool abandon_work(int thr_id, struct work *work, struct timeval *w
|
|||||||
{
|
{
|
||||||
if (wdiff->tv_sec > opt_scantime ||
|
if (wdiff->tv_sec > opt_scantime ||
|
||||||
work->blk.nonce >= MAXTHREADS - hashes ||
|
work->blk.nonce >= MAXTHREADS - hashes ||
|
||||||
|
hashes >= 0xfffffffe ||
|
||||||
stale_work(work, false))
|
stale_work(work, false))
|
||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
@ -4432,7 +4445,7 @@ static void *miner_thread(void *userdata)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sdiff.tv_sec < cycle) {
|
if (unlikely(sdiff.tv_sec < cycle)) {
|
||||||
if (likely(!api->can_limit_work || max_nonce == 0xffffffff))
|
if (likely(!api->can_limit_work || max_nonce == 0xffffffff))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -5386,7 +5399,7 @@ static void cpu_detect()
|
|||||||
#endif /* !WIN32 */
|
#endif /* !WIN32 */
|
||||||
|
|
||||||
if (opt_n_threads < 0 || !forced_n_threads) {
|
if (opt_n_threads < 0 || !forced_n_threads) {
|
||||||
if (nDevs && !opt_usecpu)
|
if (total_devices && !opt_usecpu)
|
||||||
opt_n_threads = 0;
|
opt_n_threads = 0;
|
||||||
else
|
else
|
||||||
opt_n_threads = num_processors;
|
opt_n_threads = num_processors;
|
||||||
@ -5803,6 +5816,12 @@ struct device_api opencl_api = {
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef USE_BITFORCE
|
||||||
|
extern struct device_api bitforce_api;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
static int cgminer_id_count = 0;
|
static int cgminer_id_count = 0;
|
||||||
|
|
||||||
void enable_device(struct cgpu_info *cgpu)
|
void enable_device(struct cgpu_info *cgpu)
|
||||||
@ -5877,6 +5896,8 @@ int main (int argc, char *argv[])
|
|||||||
HASH_ADD_STR(blocks, hash, block);
|
HASH_ADD_STR(blocks, hash, block);
|
||||||
strcpy(current_block, block->hash);
|
strcpy(current_block, block->hash);
|
||||||
|
|
||||||
|
INIT_LIST_HEAD(&scan_devices);
|
||||||
|
|
||||||
memset(gpus, 0, sizeof(gpus));
|
memset(gpus, 0, sizeof(gpus));
|
||||||
for (i = 0; i < MAX_GPUDEVICES; i++)
|
for (i = 0; i < MAX_GPUDEVICES; i++)
|
||||||
gpus[i].dynamic = true;
|
gpus[i].dynamic = true;
|
||||||
@ -5946,6 +5967,10 @@ int main (int argc, char *argv[])
|
|||||||
opencl_api.api_detect();
|
opencl_api.api_detect();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_BITFORCE
|
||||||
|
bitforce_api.api_detect();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef WANT_CPUMINE
|
#ifdef WANT_CPUMINE
|
||||||
cpu_api.api_detect();
|
cpu_api.api_detect();
|
||||||
#endif
|
#endif
|
||||||
|
31
miner.h
31
miner.h
@ -236,6 +236,9 @@ struct cgpu_info {
|
|||||||
int cgminer_id;
|
int cgminer_id;
|
||||||
struct device_api *api;
|
struct device_api *api;
|
||||||
int device_id;
|
int device_id;
|
||||||
|
char *device_path;
|
||||||
|
FILE *device_file;
|
||||||
|
|
||||||
bool enabled;
|
bool enabled;
|
||||||
int accepted;
|
int accepted;
|
||||||
int rejected;
|
int rejected;
|
||||||
@ -297,6 +300,32 @@ struct thr_info {
|
|||||||
extern int thr_info_create(struct thr_info *thr, pthread_attr_t *attr, void *(*start) (void *), void *arg);
|
extern int thr_info_create(struct thr_info *thr, pthread_attr_t *attr, void *(*start) (void *), void *arg);
|
||||||
extern void thr_info_cancel(struct thr_info *thr);
|
extern void thr_info_cancel(struct thr_info *thr);
|
||||||
|
|
||||||
|
|
||||||
|
struct string_elist {
|
||||||
|
char *string;
|
||||||
|
bool free_me;
|
||||||
|
|
||||||
|
struct list_head list;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void string_elist_add(const char *s, struct list_head *head)
|
||||||
|
{
|
||||||
|
struct string_elist *n;
|
||||||
|
|
||||||
|
n = calloc(1, sizeof(*n));
|
||||||
|
n->string = strdup(s);
|
||||||
|
n->free_me = true;
|
||||||
|
list_add_tail(&n->list, head);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void string_elist_del(struct string_elist *item)
|
||||||
|
{
|
||||||
|
if (item->free_me)
|
||||||
|
free(item->string);
|
||||||
|
list_del(&item->list);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline uint32_t swab32(uint32_t v)
|
static inline uint32_t swab32(uint32_t v)
|
||||||
{
|
{
|
||||||
return bswap_32(v);
|
return bswap_32(v);
|
||||||
@ -468,6 +497,7 @@ extern void api(void);
|
|||||||
#define MAX_DEVICES 32
|
#define MAX_DEVICES 32
|
||||||
#define MAX_POOLS (32)
|
#define MAX_POOLS (32)
|
||||||
|
|
||||||
|
extern struct list_head scan_devices;
|
||||||
extern int nDevs;
|
extern int nDevs;
|
||||||
extern int opt_n_threads;
|
extern int opt_n_threads;
|
||||||
extern int num_processors;
|
extern int num_processors;
|
||||||
@ -581,6 +611,7 @@ enum cl_kernel {
|
|||||||
KL_PHATK,
|
KL_PHATK,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern void get_datestamp(char *, struct timeval *);
|
||||||
bool submit_nonce(struct thr_info *thr, struct work *work, uint32_t nonce);
|
bool submit_nonce(struct thr_info *thr, struct work *work, uint32_t nonce);
|
||||||
extern void wlogprint(const char *f, ...);
|
extern void wlogprint(const char *f, ...);
|
||||||
extern int curses_int(const char *query);
|
extern int curses_int(const char *query);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user