|
|
@ -230,10 +230,26 @@ static char *opt_kernel = NULL; |
|
|
|
|
|
|
|
|
|
|
|
enum cl_kernel chosen_kernel; |
|
|
|
enum cl_kernel chosen_kernel; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static bool ping = true; |
|
|
|
|
|
|
|
|
|
|
|
struct sigaction termhandler, inthandler; |
|
|
|
struct sigaction termhandler, inthandler; |
|
|
|
|
|
|
|
|
|
|
|
struct thread_q *getq; |
|
|
|
struct thread_q *getq; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void get_datestamp(char *f, struct timeval *tv) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
struct tm tm; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
localtime_r(&tv->tv_sec, &tm); |
|
|
|
|
|
|
|
sprintf(f, "[%d-%02d-%02d %02d:%02d:%02d]", |
|
|
|
|
|
|
|
tm.tm_year + 1900, |
|
|
|
|
|
|
|
tm.tm_mon + 1, |
|
|
|
|
|
|
|
tm.tm_mday, |
|
|
|
|
|
|
|
tm.tm_hour, |
|
|
|
|
|
|
|
tm.tm_min, |
|
|
|
|
|
|
|
tm.tm_sec); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void applog_and_exit(const char *fmt, ...) |
|
|
|
static void applog_and_exit(const char *fmt, ...) |
|
|
|
{ |
|
|
|
{ |
|
|
|
va_list ap; |
|
|
|
va_list ap; |
|
|
@ -773,17 +789,21 @@ static void curses_print_status(int thr_id) |
|
|
|
whline(statuswin, '-', 80); |
|
|
|
whline(statuswin, '-', 80); |
|
|
|
wmove(statuswin, logstart - 1, 0); |
|
|
|
wmove(statuswin, logstart - 1, 0); |
|
|
|
whline(statuswin, '-', 80); |
|
|
|
whline(statuswin, '-', 80); |
|
|
|
mvwprintw(statuswin, gpucursor - 1, 1, "[P]ool management [S]ettings [D]isplay options [Q]uit"); |
|
|
|
mvwprintw(statuswin, gpucursor - 1, 1, "[P]ool management %s[S]ettings [D]isplay options [Q]uit", |
|
|
|
|
|
|
|
opt_g_threads ? "[G]PU management " : ""); |
|
|
|
|
|
|
|
|
|
|
|
if (thr_id >= 0 && thr_id < gpu_threads) { |
|
|
|
if (thr_id >= 0 && thr_id < gpu_threads) { |
|
|
|
int gpu = dev_from_id(thr_id); |
|
|
|
int gpu = dev_from_id(thr_id); |
|
|
|
struct cgpu_info *cgpu = &gpus[gpu]; |
|
|
|
struct cgpu_info *cgpu = &gpus[gpu]; |
|
|
|
|
|
|
|
|
|
|
|
wmove(statuswin, gpucursor + gpu, 0); |
|
|
|
wmove(statuswin, gpucursor + gpu, 0); |
|
|
|
|
|
|
|
if (!gpu_devices[gpu] || !cgpu->alive) |
|
|
|
|
|
|
|
wattron(logwin, A_DIM); |
|
|
|
wprintw(statuswin, " GPU %d: [%.1f Mh/s] [Q:%d A:%d R:%d HW:%d E:%.0f%% U:%.2f/m]", |
|
|
|
wprintw(statuswin, " GPU %d: [%.1f Mh/s] [Q:%d A:%d R:%d HW:%d E:%.0f%% U:%.2f/m]", |
|
|
|
gpu, cgpu->total_mhashes / total_secs, |
|
|
|
gpu, cgpu->total_mhashes / total_secs, |
|
|
|
cgpu->getworks, cgpu->accepted, cgpu->rejected, cgpu->hw_errors, |
|
|
|
cgpu->getworks, cgpu->accepted, cgpu->rejected, cgpu->hw_errors, |
|
|
|
cgpu->efficiency, cgpu->utility); |
|
|
|
cgpu->efficiency, cgpu->utility); |
|
|
|
|
|
|
|
wattroff(logwin, A_DIM); |
|
|
|
wclrtoeol(statuswin); |
|
|
|
wclrtoeol(statuswin); |
|
|
|
} else if (thr_id >= gpu_threads) { |
|
|
|
} else if (thr_id >= gpu_threads) { |
|
|
|
int cpu = dev_from_id(thr_id); |
|
|
|
int cpu = dev_from_id(thr_id); |
|
|
@ -1389,18 +1409,10 @@ static void switch_pools(struct pool *selected) |
|
|
|
static void set_curblock(char *hexstr) |
|
|
|
static void set_curblock(char *hexstr) |
|
|
|
{ |
|
|
|
{ |
|
|
|
struct timeval tv_now; |
|
|
|
struct timeval tv_now; |
|
|
|
struct tm tm; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
memcpy(current_block, hexstr, 36); |
|
|
|
memcpy(current_block, hexstr, 36); |
|
|
|
gettimeofday(&tv_now, NULL); |
|
|
|
gettimeofday(&tv_now, NULL); |
|
|
|
localtime_r(&tv_now.tv_sec, &tm); |
|
|
|
get_datestamp(blockdate, &tv_now); |
|
|
|
sprintf(blockdate, "[%d-%02d-%02d %02d:%02d:%02d]", |
|
|
|
|
|
|
|
tm.tm_year + 1900, |
|
|
|
|
|
|
|
tm.tm_mon + 1, |
|
|
|
|
|
|
|
tm.tm_mday, |
|
|
|
|
|
|
|
tm.tm_hour, |
|
|
|
|
|
|
|
tm.tm_min, |
|
|
|
|
|
|
|
tm.tm_sec); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static void test_work_current(struct work *work) |
|
|
|
static void test_work_current(struct work *work) |
|
|
@ -1797,6 +1809,102 @@ retry: |
|
|
|
opt_loginput = false; |
|
|
|
opt_loginput = false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_OPENCL |
|
|
|
|
|
|
|
static void reinit_thread(long thr_id); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void manage_gpu(void) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
struct thr_info *thr; |
|
|
|
|
|
|
|
int selected, gpu, i; |
|
|
|
|
|
|
|
char checkin[40]; |
|
|
|
|
|
|
|
char input; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!opt_g_threads) |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
opt_loginput = true; |
|
|
|
|
|
|
|
immedok(logwin, true); |
|
|
|
|
|
|
|
retry: |
|
|
|
|
|
|
|
clear_logwin(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (gpu = 0; gpu < nDevs; gpu++) { |
|
|
|
|
|
|
|
struct cgpu_info *cgpu = &gpus[gpu]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
wlog("GPU %d: [%.1f Mh/s] [Q:%d A:%d R:%d HW:%d E:%.0f%% U:%.2f/m]\n", |
|
|
|
|
|
|
|
gpu, cgpu->total_mhashes / total_secs, |
|
|
|
|
|
|
|
cgpu->getworks, cgpu->accepted, cgpu->rejected, cgpu->hw_errors, |
|
|
|
|
|
|
|
cgpu->efficiency, cgpu->utility); |
|
|
|
|
|
|
|
for (i = 0; i < mining_threads; i++) { |
|
|
|
|
|
|
|
if (dev_from_id(i) != gpu) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
thr = &thr_info[i]; |
|
|
|
|
|
|
|
get_datestamp(checkin, &thr->last); |
|
|
|
|
|
|
|
wlog("Thread %d %s %s reported in %s\n", i, |
|
|
|
|
|
|
|
gpu_devices[gpu] ? "Enabled" : "Disabled", |
|
|
|
|
|
|
|
cgpu->alive ? "Alive" : "Dead", checkin); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
wlogprint("[E]nable [D]isable [R]estart GPU\n"); |
|
|
|
|
|
|
|
wlogprint("Or press any other key to continue\n"); |
|
|
|
|
|
|
|
input = getch(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!strncasecmp(&input, "e", 1)) { |
|
|
|
|
|
|
|
selected = curses_int("Select GPU to enable"); |
|
|
|
|
|
|
|
if (selected < 0 || selected > nDevs) { |
|
|
|
|
|
|
|
wlogprint("Invalid selection\n"); |
|
|
|
|
|
|
|
goto retry; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!gpus[selected].alive) { |
|
|
|
|
|
|
|
wlogprint("Device dead, need to attempt to restart before enabling\n"); |
|
|
|
|
|
|
|
goto retry; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
gpu_devices[selected] = true; |
|
|
|
|
|
|
|
for (i = 0; i < mining_threads; i++) { |
|
|
|
|
|
|
|
if (dev_from_id(i) != selected) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
thr = &thr_info[i]; |
|
|
|
|
|
|
|
tq_thaw(thr->q); |
|
|
|
|
|
|
|
tq_push(thr->q, &ping); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} if (!strncasecmp(&input, "d", 1)) { |
|
|
|
|
|
|
|
selected = curses_int("Select GPU to disable"); |
|
|
|
|
|
|
|
if (selected < 0 || selected > nDevs) { |
|
|
|
|
|
|
|
wlogprint("Invalid selection\n"); |
|
|
|
|
|
|
|
goto retry; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
gpu_devices[selected] = false; |
|
|
|
|
|
|
|
for (i = 0; i < mining_threads; i++) { |
|
|
|
|
|
|
|
if (dev_from_id(i) != selected) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
thr = &thr_info[i]; |
|
|
|
|
|
|
|
tq_freeze(thr->q); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else if (!strncasecmp(&input, "r", 1)) { |
|
|
|
|
|
|
|
selected = curses_int("Select GPU to attempt to restart"); |
|
|
|
|
|
|
|
if (selected < 0 || selected > nDevs) { |
|
|
|
|
|
|
|
wlogprint("Invalid selection\n"); |
|
|
|
|
|
|
|
goto retry; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
thr = &thr_info[selected]; |
|
|
|
|
|
|
|
for (i = 0; i < mining_threads; i++) { |
|
|
|
|
|
|
|
if (dev_from_id(i) != selected) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
wlogprint("Attempting to restart thread %d\n", i); |
|
|
|
|
|
|
|
reinit_thread(i); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
clear_logwin(); |
|
|
|
|
|
|
|
immedok(logwin, false); |
|
|
|
|
|
|
|
opt_loginput = false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
#else |
|
|
|
|
|
|
|
static void manage_gpu(void) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
static void *input_thread(void *userdata) |
|
|
|
static void *input_thread(void *userdata) |
|
|
|
{ |
|
|
|
{ |
|
|
|
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); |
|
|
|
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); |
|
|
@ -1817,6 +1925,8 @@ static void *input_thread(void *userdata) |
|
|
|
display_pools(); |
|
|
|
display_pools(); |
|
|
|
else if (!strncasecmp(&input, "s", 1)) |
|
|
|
else if (!strncasecmp(&input, "s", 1)) |
|
|
|
set_options(); |
|
|
|
set_options(); |
|
|
|
|
|
|
|
else if (!strncasecmp(&input, "g", 1)) |
|
|
|
|
|
|
|
manage_gpu(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return NULL; |
|
|
|
return NULL; |
|
|
@ -2737,6 +2847,11 @@ static void *gpuminer_thread(void *userdata) |
|
|
|
requested = true; |
|
|
|
requested = true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (unlikely(!gpu_devices[dev_from_id(thr_id)])) { |
|
|
|
|
|
|
|
applog(LOG_WARNING, "Thread %d being disabled\n", thr_id); |
|
|
|
|
|
|
|
tq_pop(mythr->q, NULL); /* Ignore ping that's popped */ |
|
|
|
|
|
|
|
applog(LOG_WARNING, "Thread %d being re-enabled\n", thr_id); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
out: |
|
|
|
out: |
|
|
|
tq_freeze(mythr->q); |
|
|
|
tq_freeze(mythr->q); |
|
|
@ -2928,6 +3043,8 @@ static void *reinit_cputhread(void *userdata) |
|
|
|
applog(LOG_WARNING, "Thread %d restarted", thr_id); |
|
|
|
applog(LOG_WARNING, "Thread %d restarted", thr_id); |
|
|
|
thread_reportin(thr); |
|
|
|
thread_reportin(thr); |
|
|
|
tq_thaw(thr->q); |
|
|
|
tq_thaw(thr->q); |
|
|
|
|
|
|
|
tq_push(thr->q, &ping); |
|
|
|
|
|
|
|
|
|
|
|
return NULL; |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -2942,7 +3059,7 @@ static void *reinit_gputhread(void *userdata) |
|
|
|
tq_freeze(thr->q); |
|
|
|
tq_freeze(thr->q); |
|
|
|
/* Disable the GPU device in case the pthread never joins, hung in GPU
|
|
|
|
/* Disable the GPU device in case the pthread never joins, hung in GPU
|
|
|
|
* space */ |
|
|
|
* space */ |
|
|
|
gpu_devices[dev_from_id(thr_id)] = false; |
|
|
|
gpu_devices[gpu] = false; |
|
|
|
if (!pthread_cancel(*thr->pth)) |
|
|
|
if (!pthread_cancel(*thr->pth)) |
|
|
|
pthread_join(*thr->pth, NULL); |
|
|
|
pthread_join(*thr->pth, NULL); |
|
|
|
free(clStates[thr_id]); |
|
|
|
free(clStates[thr_id]); |
|
|
@ -2964,8 +3081,10 @@ static void *reinit_gputhread(void *userdata) |
|
|
|
* for it */ |
|
|
|
* for it */ |
|
|
|
applog(LOG_WARNING, "Thread %d restarted", thr_id); |
|
|
|
applog(LOG_WARNING, "Thread %d restarted", thr_id); |
|
|
|
thread_reportin(thr); |
|
|
|
thread_reportin(thr); |
|
|
|
|
|
|
|
gpu_devices[gpu] = true; |
|
|
|
tq_thaw(thr->q); |
|
|
|
tq_thaw(thr->q); |
|
|
|
gpu_devices[dev_from_id(thr_id)] = true; |
|
|
|
tq_push(thr->q, &ping); |
|
|
|
|
|
|
|
gpus[gpu].alive = true; |
|
|
|
|
|
|
|
|
|
|
|
return NULL; |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
} |
|
|
@ -3029,10 +3148,8 @@ static void *watchdog_thread(void *userdata) |
|
|
|
|
|
|
|
|
|
|
|
if (curses_active) { |
|
|
|
if (curses_active) { |
|
|
|
pthread_mutex_lock(&curses_lock); |
|
|
|
pthread_mutex_lock(&curses_lock); |
|
|
|
for (i = 0; i < mining_threads; i++) { |
|
|
|
for (i = 0; i < mining_threads; i++) |
|
|
|
if (active_device(i)) |
|
|
|
|
|
|
|
curses_print_status(i); |
|
|
|
curses_print_status(i); |
|
|
|
} |
|
|
|
|
|
|
|
redrawwin(logwin); |
|
|
|
redrawwin(logwin); |
|
|
|
redrawwin(statuswin); |
|
|
|
redrawwin(statuswin); |
|
|
|
pthread_mutex_unlock(&curses_lock); |
|
|
|
pthread_mutex_unlock(&curses_lock); |
|
|
@ -3068,11 +3185,12 @@ static void *watchdog_thread(void *userdata) |
|
|
|
for (i = 0; i < gpu_threads; i++) { |
|
|
|
for (i = 0; i < gpu_threads; i++) { |
|
|
|
struct thr_info *thr = &thr_info[i]; |
|
|
|
struct thr_info *thr = &thr_info[i]; |
|
|
|
|
|
|
|
|
|
|
|
/* Thread is waiting on getwork, don't test it */ |
|
|
|
/* Thread is waiting on getwork or disabled */ |
|
|
|
if (thr->getwork) |
|
|
|
if (thr->getwork || !gpu_devices[i]) |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
if (now.tv_sec - thr->last.tv_sec > 60) { |
|
|
|
if (now.tv_sec - thr->last.tv_sec > 60) { |
|
|
|
|
|
|
|
gpus[i].alive = false; |
|
|
|
applog(LOG_ERR, "Attempting to restart thread %d, idle for more than 60 seconds", i); |
|
|
|
applog(LOG_ERR, "Attempting to restart thread %d, idle for more than 60 seconds", i); |
|
|
|
/* Create one mandatory work item */ |
|
|
|
/* Create one mandatory work item */ |
|
|
|
inc_staged(current_pool(), 1, true); |
|
|
|
inc_staged(current_pool(), 1, true); |
|
|
@ -3259,7 +3377,6 @@ int main (int argc, char *argv[]) |
|
|
|
struct sigaction handler; |
|
|
|
struct sigaction handler; |
|
|
|
struct thr_info *thr; |
|
|
|
struct thr_info *thr; |
|
|
|
char name[256]; |
|
|
|
char name[256]; |
|
|
|
struct tm tm; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* This dangerous functions tramples random dynamically allocated
|
|
|
|
/* This dangerous functions tramples random dynamically allocated
|
|
|
|
* variables so do it before anything at all */ |
|
|
|
* variables so do it before anything at all */ |
|
|
@ -3283,14 +3400,7 @@ int main (int argc, char *argv[]) |
|
|
|
|
|
|
|
|
|
|
|
gettimeofday(&total_tv_start, NULL); |
|
|
|
gettimeofday(&total_tv_start, NULL); |
|
|
|
gettimeofday(&total_tv_end, NULL); |
|
|
|
gettimeofday(&total_tv_end, NULL); |
|
|
|
localtime_r(&total_tv_start.tv_sec, &tm); |
|
|
|
get_datestamp(datestamp, &total_tv_start); |
|
|
|
sprintf(datestamp, "[%d-%02d-%02d %02d:%02d:%02d]", |
|
|
|
|
|
|
|
tm.tm_year + 1900, |
|
|
|
|
|
|
|
tm.tm_mon + 1, |
|
|
|
|
|
|
|
tm.tm_mday, |
|
|
|
|
|
|
|
tm.tm_hour, |
|
|
|
|
|
|
|
tm.tm_min, |
|
|
|
|
|
|
|
tm.tm_sec); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < 36; i++) |
|
|
|
for (i = 0; i < 36; i++) |
|
|
|
strcat(current_block, "0"); |
|
|
|
strcat(current_block, "0"); |
|
|
@ -3502,10 +3612,6 @@ int main (int argc, char *argv[]) |
|
|
|
gpus[gpu].is_gpu = 1; |
|
|
|
gpus[gpu].is_gpu = 1; |
|
|
|
gpus[gpu].cpu_gpu = gpu; |
|
|
|
gpus[gpu].cpu_gpu = gpu; |
|
|
|
|
|
|
|
|
|
|
|
/* Skip devices not set to mine */ |
|
|
|
|
|
|
|
if (!gpu_devices[gpu]) |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
thr = &thr_info[i]; |
|
|
|
thr = &thr_info[i]; |
|
|
|
thr->id = i; |
|
|
|
thr->id = i; |
|
|
|
thr->cgpu = &gpus[gpu]; |
|
|
|
thr->cgpu = &gpus[gpu]; |
|
|
@ -3514,6 +3620,11 @@ int main (int argc, char *argv[]) |
|
|
|
if (!thr->q) |
|
|
|
if (!thr->q) |
|
|
|
quit(1, "tq_new failed in starting gpu mining threads"); |
|
|
|
quit(1, "tq_new failed in starting gpu mining threads"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Enable threads for devices set not to mine but disable
|
|
|
|
|
|
|
|
* their queue in case we wish to enable them later*/ |
|
|
|
|
|
|
|
if (!gpu_devices[gpu]) |
|
|
|
|
|
|
|
tq_freeze(thr->q); |
|
|
|
|
|
|
|
|
|
|
|
applog(LOG_INFO, "Init GPU thread %i", i); |
|
|
|
applog(LOG_INFO, "Init GPU thread %i", i); |
|
|
|
clStates[i] = initCl(gpu, name, sizeof(name)); |
|
|
|
clStates[i] = initCl(gpu, name, sizeof(name)); |
|
|
|
if (!clStates[i]) { |
|
|
|
if (!clStates[i]) { |
|
|
@ -3527,10 +3638,14 @@ int main (int argc, char *argv[]) |
|
|
|
|
|
|
|
|
|
|
|
if (unlikely(thr_info_create(thr, NULL, gpuminer_thread, thr))) |
|
|
|
if (unlikely(thr_info_create(thr, NULL, gpuminer_thread, thr))) |
|
|
|
quit(1, "thread %d create failed", i); |
|
|
|
quit(1, "thread %d create failed", i); |
|
|
|
|
|
|
|
gpus[gpu].alive = true; |
|
|
|
|
|
|
|
|
|
|
|
i++; |
|
|
|
i++; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
applog(LOG_INFO, "%d gpu miner threads started", gpu_threads); |
|
|
|
applog(LOG_INFO, "%d gpu miner threads started", gpu_threads); |
|
|
|
|
|
|
|
#else |
|
|
|
|
|
|
|
opt_g_threads = 0; |
|
|
|
#endif |
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
/* start CPU mining threads */ |
|
|
|
/* start CPU mining threads */ |
|
|
|