/* * Copyright 2014 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 3 of the License, or (at your option) * any later version. See COPYING for more details. * * To avoid doubt: Programs using the minergate protocol to interface with * BFGMiner are considered part of the Corresponding Source, and not an * independent work. This means that such programs distrbuted with BFGMiner * must be released under the terms of the GPLv3 license, or sufficiently * compatible terms. */ #include "config.h" #include #include #include #include #include #include #include #include #include "deviceapi.h" #include "logging.h" #include "miner.h" #define MINERGATE_MAX_NONCE_DIFF 0x20 static const char * const minergate_stats_file = "/var/run/mg_rate_temp"; #define MINERGATE_PROTOCOL_VER 6 #define MINERGATE_MAGIC 0xcaf4 static const int minergate_max_responses = 300; #define MINERGATE_PKT_HEADER_SZ 8 #define MINERGATE_PKT_REQ_ITEM_SZ 0x34 #define MINERGATE_PKT_REQ_MAX 100 #define MINERGATE_PKT_RSP_ITEM_SZ 0x14 #define MINERGATE_PKT_RSP_MAX 300 #define MINERGATE_POLL_US 100000 #define MINERGATE_RETRY_US 5000000 #define MINERGATE_PKT_REQ_SZ (MINERGATE_PKT_HEADER_SZ + (MINERGATE_PKT_REQ_ITEM_SZ * MINERGATE_PKT_REQ_MAX)) #define MINERGATE_PKT_RSP_SZ (MINERGATE_PKT_HEADER_SZ + (MINERGATE_PKT_RSP_ITEM_SZ * MINERGATE_PKT_RSP_MAX)) BFG_REGISTER_DRIVER(minergate_drv) enum minergate_reqpkt_flags { MRPF_FIRST = 1, MRPF_FLUSH = 2, }; struct minergate_state { work_device_id_t next_jobid; unsigned ready_to_queue; uint8_t *req_buffer; long *stats; unsigned stats_count; struct work *flushed_work; }; static int minergate_open(const char * const devpath) { size_t devpath_len = strlen(devpath); struct sockaddr_un sa = { .sun_family = AF_UNIX, }; #ifdef UNIX_PATH_MAX if (devpath_len >= UNIX_PATH_MAX) #else if (devpath_len >= sizeof(sa.sun_path)) #endif return -1; const int fd = socket(PF_UNIX, SOCK_STREAM, 0); strcpy(sa.sun_path, devpath); if (connect(fd, &sa, sizeof(sa))) { close(fd); return -1; } return fd; } static ssize_t minergate_read(const int fd, void * const buf_p, size_t bufLen) { uint8_t *buf = buf_p; ssize_t rv, ret = 0; while (bufLen > 0) { rv = read(fd, buf, bufLen); if (rv <= 0) { if (ret > 0) return ret; return rv; } buf += rv; bufLen -= rv; ret += rv; } return ret; } static bool minergate_detect_one(const char * const devpath) { bool rv = false; const int fd = minergate_open(devpath); if (unlikely(fd < 0)) applogr(false, LOG_DEBUG, "%s: %s: Cannot connect", minergate_drv.dname, devpath); int epfd = -1; uint8_t buf[MINERGATE_PKT_REQ_SZ] = {0xbf, 0x90, MINERGATE_PROTOCOL_VER, MRPF_FIRST, 0,0, 0 /* req count */,}; pk_u16le(buf, 4, MINERGATE_MAGIC); if (MINERGATE_PKT_REQ_SZ != write(fd, buf, MINERGATE_PKT_REQ_SZ)) return_via_applog(out, , LOG_DEBUG, "%s: %s: write incomplete or failed", minergate_drv.dname, devpath); epfd = epoll_create(1); if (epfd < 0) return_via_applog(out, , LOG_DEBUG, "%s: %s: %s failed", minergate_drv.dname, devpath, "epoll_create"); struct epoll_event eev; eev.events = EPOLLIN; if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &eev)) return_via_applog(out, , LOG_DEBUG, "%s: %s: %s failed", minergate_drv.dname, devpath, "epoll_ctl"); size_t read_bytes = 0; static const size_t read_expect = MINERGATE_PKT_HEADER_SZ; ssize_t r; while (read_bytes < read_expect) { if (epoll_wait(epfd, &eev, 1, 1000) != 1) return_via_applog(out, , LOG_DEBUG, "%s: %s: Timeout waiting for response", minergate_drv.dname, devpath); r = read(fd, &buf[read_bytes], read_expect - read_bytes); if (r <= 0) return_via_applog(out, , LOG_DEBUG, "%s: %s: %s failed", minergate_drv.dname, devpath, "read"); read_bytes += r; } if (buf[1] != 0x90) return_via_applog(out, , LOG_DEBUG, "%s: %s: %s mismatch", minergate_drv.dname, devpath, "request_id"); if (buf[2] != MINERGATE_PROTOCOL_VER) return_via_applog(out, , LOG_DEBUG, "%s: %s: %s mismatch", minergate_drv.dname, devpath, "Protocol version"); if (upk_u16le(buf, 4) != MINERGATE_MAGIC) return_via_applog(out, , LOG_DEBUG, "%s: %s: %s mismatch", minergate_drv.dname, devpath, "magic"); uint16_t responses = upk_u16le(buf, 6); if (responses > minergate_max_responses) return_via_applog(out, , LOG_DEBUG, "%s: %s: More than maximum responses", minergate_drv.dname, devpath); struct cgpu_info * const cgpu = malloc(sizeof(*cgpu)); *cgpu = (struct cgpu_info){ .drv = &minergate_drv, .device_path = strdup(devpath), .deven = DEV_ENABLED, .threads = 1, }; rv = add_cgpu(cgpu); out: close(fd); if (epfd >= 0) close(epfd); return rv; } static int minergate_detect_auto(void) { return minergate_detect_one("/tmp/connection_pipe") ? 1 : 0; } static void minergate_detect(void) { generic_detect(&minergate_drv, minergate_detect_one, minergate_detect_auto, 0); } static bool minergate_init(struct thr_info * const thr) { struct cgpu_info * const dev = thr->cgpu; const int fd = minergate_open(dev->device_path); dev->device_fd = fd; if (fd < 0) applogr(false, LOG_ERR, "%s: Cannot connect", dev->dev_repr); struct minergate_state * const state = malloc(sizeof(*state) + MINERGATE_PKT_REQ_SZ); if (!state) applogr(false, LOG_ERR, "%s: %s failed", dev->dev_repr, "malloc"); *state = (struct minergate_state){ .req_buffer = (void*)&state[1] }; thr->cgpu_data = state; thr->work = thr->work_list = NULL; mutex_init(&dev->device_mutex); memset(state->req_buffer, 0, MINERGATE_PKT_REQ_SZ); pk_u8(state->req_buffer, 2, MINERGATE_PROTOCOL_VER); state->req_buffer[3] = MRPF_FIRST | MRPF_FLUSH; pk_u16le(state->req_buffer, 4, MINERGATE_MAGIC); timer_set_delay_from_now(&thr->tv_poll, 0); return true; } static bool minergate_queue_full(struct thr_info * const thr) { static const unsigned max_minergate_jobs = 300, max_requests = 100; struct minergate_state * const state = thr->cgpu_data; bool qf; if (HASH_COUNT(thr->work) + state->ready_to_queue >= max_minergate_jobs) qf = true; else if (state->ready_to_queue >= max_requests) qf = true; else if (state->req_buffer[3] & MRPF_FLUSH) // Job flush occurs after new jobs get queued, so we have to wait until it completes qf = true; else qf = false; thr->queue_full = qf; return qf; } static bool minergate_queue_append(struct thr_info * const thr, struct work * const work) { struct cgpu_info * const dev = thr->cgpu; struct minergate_state * const state = thr->cgpu_data; if (minergate_queue_full(thr)) return false; work->device_id = (uint32_t)(state->next_jobid++); work->tv_stamp.tv_sec = 0; uint8_t * const my_buf = &state->req_buffer[MINERGATE_PKT_HEADER_SZ + (MINERGATE_PKT_REQ_ITEM_SZ * state->ready_to_queue++)]; pk_u32be(my_buf, 0, work->device_id); memcpy(&my_buf[ 4], &work->data[0x48], 4); // nbits memcpy(&my_buf[ 8], &work->data[0x44], 4); // ntime memcpy(&my_buf[0x0c], &work->data[0x40], 4); // merkle-tail memcpy(&my_buf[0x10], work->midstate, 0x20); if (work->work_difficulty >= MINERGATE_MAX_NONCE_DIFF) work->nonce_diff = MINERGATE_MAX_NONCE_DIFF; else work->nonce_diff = work->work_difficulty; const uint16_t zerobits = log2(floor(work->nonce_diff * 4294967296)); work->nonce_diff = pow(2, zerobits) / 4294967296; pk_u8(my_buf, 0x30, zerobits); pk_u8(my_buf, 0x31, 0); // ntime limit pk_u8(my_buf, 0x32, 0); // ntime offset pk_u8(my_buf, 0x33, 0); // reserved struct work *oldwork; HASH_FIND(hh, thr->work, &work->device_id, sizeof(work->device_id), oldwork); if (unlikely(oldwork)) { applog(LOG_WARNING, "%s: Reusing allocated device id %"PRIwdi, dev->dev_repr, work->device_id); HASH_DEL(thr->work, oldwork); free_work(oldwork); } HASH_ADD(hh, thr->work, device_id, sizeof(work->device_id), work); LL_PREPEND(thr->work_list, work); timer_set_delay_from_now(&thr->tv_poll, 0); minergate_queue_full(thr); return true; } static void minergate_queue_flush(struct thr_info * const thr) { struct minergate_state * const state = thr->cgpu_data; struct work *work, *worktmp; // Flush internal ready-to-queue list LL_FOREACH_SAFE(thr->work_list, work, worktmp) { HASH_DEL(thr->work, work); LL_DELETE(thr->work_list, work); free_work(work); } state->ready_to_queue = 0; // Trigger minergate flush state->req_buffer[3] |= MRPF_FLUSH; timer_set_delay_from_now(&thr->tv_poll, 0); } static void minergate_poll(struct thr_info * const thr) { struct cgpu_info * const dev = thr->cgpu; struct minergate_state * const state = thr->cgpu_data; const int fd = dev->device_fd; if (opt_dev_protocol || state->ready_to_queue) applog(LOG_DEBUG, "%s: Polling with %u new jobs", dev->dev_repr, state->ready_to_queue); pk_u16le(state->req_buffer, 6, state->ready_to_queue); if (MINERGATE_PKT_REQ_SZ != write(fd, state->req_buffer, MINERGATE_PKT_REQ_SZ)) return_via_applog(err, , LOG_ERR, "%s: write incomplete or failed", dev->dev_repr); uint8_t flags = state->req_buffer[3]; state->req_buffer[3] = 0; state->ready_to_queue = 0; thr->work_list = NULL; uint8_t buf[MINERGATE_PKT_RSP_SZ]; if (minergate_read(fd, buf, MINERGATE_PKT_RSP_SZ) != MINERGATE_PKT_RSP_SZ) return_via_applog(err, , LOG_ERR, "%s: %s failed", dev->dev_repr, "read"); if (upk_u8(buf, 2) != MINERGATE_PROTOCOL_VER || upk_u16le(buf, 4) != MINERGATE_MAGIC) return_via_applog(err, , LOG_ERR, "%s: Protocol mismatch", dev->dev_repr); uint8_t *jobrsp = &buf[MINERGATE_PKT_HEADER_SZ]; struct work *work; uint16_t rsp_count = upk_u16le(buf, 6); if (rsp_count || opt_dev_protocol) applog(LOG_DEBUG, "%s: Received %u job completions", dev->dev_repr, rsp_count); uint32_t nonce; int64_t hashes = 0; for (unsigned i = 0; i < rsp_count; ++i, (jobrsp += MINERGATE_PKT_RSP_ITEM_SZ)) { work_device_id_t jobid = upk_u32be(jobrsp, 0); nonce = upk_u32le(jobrsp, 8); HASH_FIND(hh, thr->work, &jobid, sizeof(jobid), work); if (!work) { applog(LOG_ERR, "%s: Unknown job %"PRIwdi, dev->dev_repr, jobid); if (nonce) { inc_hw_errors3(thr, NULL, &nonce, 1.); nonce = upk_u32le(jobrsp, 0xc); if (nonce) inc_hw_errors3(thr, NULL, &nonce, 1.); } else inc_hw_errors_only(thr); continue; } if (nonce) { submit_nonce(thr, work, nonce); nonce = upk_u32be(jobrsp, 0xc); if (nonce) submit_nonce(thr, work, nonce); } HASH_DEL(thr->work, work); applog(LOG_DEBUG, "%s: %s job %"PRIwdi" completed", dev->dev_repr, work->tv_stamp.tv_sec ? "Flushed" : "Active", work->device_id); if (!work->tv_stamp.tv_sec) hashes += 100000000 * work->nonce_diff; free_work(work); } hashes_done2(thr, hashes, NULL); if (flags & MRPF_FLUSH) { // Mark all remaining jobs as flushed so we don't count them in hashes_done struct work *worktmp; HASH_ITER(hh, thr->work, work, worktmp) { work->tv_stamp.tv_sec = 1; } } minergate_queue_full(thr); timer_set_delay_from_now(&thr->tv_poll, MINERGATE_POLL_US); return; err: // TODO: reconnect timer_set_delay_from_now(&thr->tv_poll, MINERGATE_RETRY_US); } static bool minergate_get_stats(struct cgpu_info * const dev) { static const int skip_stats = 1; struct thr_info * const thr = dev->thr[0]; struct minergate_state * const state = thr->cgpu_data; FILE *F = fopen(minergate_stats_file, "r"); char buf[0x100]; if (F) { char *p = fgets(buf, sizeof(buf), F); fclose(F); if (p) { long nums[0x80]; char *endptr; unsigned i; float max_temp = 0; for (i = 0; 1; ++i) { if (!p[0]) break; nums[i] = strtol(p, &endptr, 0); if (p == endptr) break; if (i >= skip_stats && nums[i] > max_temp) max_temp = nums[i]; while (endptr[0] && !isspace(endptr[0])) ++endptr; while (endptr[0] && isspace(endptr[0])) ++endptr; p = endptr; } i -= skip_stats; long *new_stats = malloc(sizeof(*state->stats) * i); memcpy(new_stats, &nums[skip_stats], sizeof(*nums) * i); mutex_lock(&dev->device_mutex); free(state->stats); state->stats = new_stats; state->stats_count = i; mutex_unlock(&dev->device_mutex); dev->temp = max_temp; } } return true; } static struct api_data *minergate_api_extra_device_status(struct cgpu_info * const dev) { struct thr_info * const thr = dev->thr[0]; struct minergate_state * const state = thr->cgpu_data; struct api_data *root = NULL; mutex_lock(&dev->device_mutex); if (state->stats_count > 1) { char buf[0x10]; for (unsigned i = 0; i < state->stats_count; ++i) { float temp = state->stats[i]; if (!temp) continue; sprintf(buf, "Temperature%u", i); root = api_add_temp(root, buf, &temp, true); } } mutex_unlock(&dev->device_mutex); return root; } struct device_drv minergate_drv = { .dname = "minergate", .name = "MGT", .drv_detect = minergate_detect, .thread_init = minergate_init, .minerloop = minerloop_queue, .queue_append = minergate_queue_append, .queue_flush = minergate_queue_flush, .poll = minergate_poll, .get_stats = minergate_get_stats, .get_api_extra_device_status = minergate_api_extra_device_status, };