/* cgminer driver for KnCminer Jupiter */ #include #include #include #include #include #include #include #include #include "logging.h" #include "miner.h" #define MAX_SPIS 1 #define MAX_BYTES_IN_SPI_XSFER 4096 /* /dev/spidevB.C, where B = bus, C = chipselect */ #define SPI_DEVICE_TEMPLATE "/dev/spidev%d.%d" #define SPI_MODE (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH) #define SPI_BITS_PER_WORD 32 #define SPI_MAX_SPEED 3000000 #define SPI_DELAY_USECS 0 /* Max number of ASICs permitted on one SPI device */ #define MAX_ASICS 6 /* How many hardware errors in a row before disabling the core */ #define HW_ERR_LIMIT 10 #define DISA_ERR_LIMIT 3 #define MAX_ACTIVE_WORKS (192 * 2 * 6 * 2) #define WORK_MIDSTATE_WORDS 8 #define WORK_DATA_WORDS 3 #define WORK_STALE_US 60000000 /* Keep core disabled for no longer than 15 minutes */ #define CORE_DISA_PERIOD_US (15 * 60 * 1000000) struct spidev_context { int fd; uint32_t speed; uint16_t delay; uint8_t mode; uint8_t bits; }; struct spi_request { #define CMD_NOP 0 #define CMD_GET_VERSION 1 #define CMD_SUBMIT_WORK 2 #define CMD_FLUSH_QUEUE 3 #define WORK_ID_MASK 0x7FFF #if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) uint32_t cmd :4; uint32_t rsvd :1; /* set to zero */ uint32_t queue_id :12; uint32_t work_id :15; #else uint32_t work_id :15; uint32_t queue_id :12; uint32_t rsvd :1; /* set to zero */ uint32_t cmd :4; #endif uint32_t midstate[WORK_MIDSTATE_WORDS]; uint32_t data[WORK_DATA_WORDS]; }; struct spi_response { #define RESPONSE_TYPE_NOP 0 #define RESPONSE_TYPE_NONCE_FOUND 1 #define RESPONSE_TYPE_WORK_DONE 2 #if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) uint32_t type :2; uint32_t asic :3; uint32_t queue_id :12; uint32_t work_id :15; #else uint32_t work_id :15; uint32_t queue_id :12; uint32_t asic :3; uint32_t type :2; #endif uint32_t nonce; uint32_t core; }; #define MAX_REQUESTS_IN_BATCH ( MAX_BYTES_IN_SPI_XSFER / \ sizeof(struct spi_request) \ ) static struct spi_request spi_txbuf[MAX_REQUESTS_IN_BATCH]; #define MAX_RESPONSES_IN_BATCH ( (sizeof(spi_txbuf) - 12) / \ sizeof(struct spi_response) \ ) struct spi_rx_t { #if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) uint32_t rsvd_1 :31; uint32_t response_queue_full :1; #else uint32_t response_queue_full :1; uint32_t rsvd_1 :31; #endif #if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) uint32_t rsvd_2 :16; uint32_t works_accepted :16; #else uint32_t works_accepted :16; uint32_t rsvd_2 :16; #endif uint32_t rsvd_3; struct spi_response responses[MAX_RESPONSES_IN_BATCH]; }; static struct spi_rx_t spi_rxbuf; struct device_drv knc_drv; struct active_work { struct work *work; uint32_t work_id; struct timeval begin; }; struct core_disa_data { struct timeval disa_begin; uint8_t asic; uint8_t core; }; struct knc_state { struct spidev_context *ctx; int devices; uint32_t salt; uint32_t next_work_id; /* read - last read item, next is at (read + 1) mod BUFSIZE * write - next write item, last written at (write - 1) mod BUFSIZE * When buffer is empty, read + 1 == write * Buffer full condition: read == write */ int read_q, write_q; #define KNC_QUEUED_BUFFER_SIZE (MAX_REQUESTS_IN_BATCH + 1) struct active_work queued_fifo[KNC_QUEUED_BUFFER_SIZE]; int read_a, write_a; #define KNC_ACTIVE_BUFFER_SIZE (MAX_ACTIVE_WORKS + 1) struct active_work active_fifo[KNC_ACTIVE_BUFFER_SIZE]; uint8_t hwerrs[MAX_ASICS * 256]; uint8_t disa_cnt[MAX_ASICS * 256]; uint32_t hwerr_work_id[MAX_ASICS * 256]; int read_d, write_d; #define KNC_DISA_CORES_SIZE (MAX_ASICS * 256) struct core_disa_data disa_cores_fifo[KNC_DISA_CORES_SIZE]; }; static inline bool knc_queued_fifo_full(struct knc_state *knc) { return (knc->read_q == knc->write_q); } static inline bool knc_active_fifo_full(struct knc_state *knc) { return (knc->read_a == knc->write_a); } static inline void knc_queued_fifo_inc_idx(int *idx) { if (unlikely(*idx >= ((int)KNC_QUEUED_BUFFER_SIZE - 1))) *idx = 0; else ++(*idx); } static inline void knc_active_fifo_inc_idx(int *idx) { if (unlikely(*idx >= (KNC_ACTIVE_BUFFER_SIZE - 1))) *idx = 0; else ++(*idx); } static inline void knc_disa_cores_fifo_inc_idx(int *idx) { if (unlikely(*idx >= (KNC_DISA_CORES_SIZE - 1))) *idx = 0; else ++(*idx); } /* Find SPI device with index idx, init it */ static struct spidev_context * spi_new(int idx) { struct spidev_context *ctx; char dev_fname[PATH_MAX]; if(NULL == (ctx = malloc(sizeof(struct spidev_context)))) { applog(LOG_ERR, "KnC spi: Out of memory"); goto l_exit_error; } ctx->mode = SPI_MODE; ctx->bits = SPI_BITS_PER_WORD; ctx->speed = SPI_MAX_SPEED; ctx->delay = SPI_DELAY_USECS; ctx->fd = -1; sprintf(dev_fname, SPI_DEVICE_TEMPLATE, idx, /* bus */ 0 /* chipselect */ ); if (0 > (ctx->fd = open(dev_fname, O_RDWR))) { applog(LOG_ERR, "KnC spi: Can not open SPI device %s: %m", dev_fname); goto l_free_exit_error; } /* * spi mode */ if (0 > ioctl(ctx->fd, SPI_IOC_WR_MODE, &ctx->mode)) goto l_ioctl_error; if (0 > ioctl(ctx->fd, SPI_IOC_RD_MODE, &ctx->mode)) goto l_ioctl_error; /* * bits per word */ if (0 > ioctl(ctx->fd, SPI_IOC_WR_BITS_PER_WORD, &ctx->bits)) goto l_ioctl_error; if (0 > ioctl(ctx->fd, SPI_IOC_RD_BITS_PER_WORD, &ctx->bits)) goto l_ioctl_error; /* * max speed hz */ if (0 > ioctl(ctx->fd, SPI_IOC_WR_MAX_SPEED_HZ, &ctx->speed)) goto l_ioctl_error; if (0 > ioctl(ctx->fd, SPI_IOC_RD_MAX_SPEED_HZ, &ctx->speed)) goto l_ioctl_error; applog(LOG_INFO, "KnC spi: device %s uses mode %hhu, bits %hhu, speed %u", dev_fname, ctx->mode, ctx->bits, ctx->speed); return ctx; l_ioctl_error: applog(LOG_ERR, "KnC spi: ioctl error on SPI device %s: %m", dev_fname); // l_close_free_exit_error: close(ctx->fd); l_free_exit_error: free(ctx); l_exit_error: return NULL; } static void spi_free(struct spidev_context *ctx) { if (NULL == ctx) return; close(ctx->fd); free(ctx); } static int spi_transfer(struct spidev_context *ctx, uint8_t *txbuf, uint8_t *rxbuf, int len) { int ret; struct spi_ioc_transfer xfr; memset(rxbuf, 0xff, len); ret = len; xfr.tx_buf = (unsigned long)txbuf; xfr.rx_buf = (unsigned long)rxbuf; xfr.len = len; xfr.speed_hz = ctx->speed; xfr.delay_usecs = ctx->delay; xfr.bits_per_word = ctx->bits; xfr.cs_change = 0; xfr.pad = 0; if (1 > (ret = ioctl(ctx->fd, SPI_IOC_MESSAGE(1), &xfr))) { applog(LOG_ERR, "KnC spi xfer: ioctl error on SPI device: %m"); } return ret; } static void disable_core(uint8_t asic, uint8_t core) { char str[256]; snprintf(str, sizeof(str), "i2cset -y 2 0x2%hhu %hhu 0", asic, core); if (0 != WEXITSTATUS(system(str))) applog(LOG_ERR, "KnC: system call failed"); } static void enable_core(uint8_t asic, uint8_t core) { char str[256]; snprintf(str, sizeof(str), "i2cset -y 2 0x2%hhu %hhu 1", asic, core); if (0 != WEXITSTATUS(system(str))) applog(LOG_ERR, "KnC: system call failed"); } static int64_t timediff(const struct timeval *a, const struct timeval *b) { struct timeval diff; timersub(a, b, &diff); return diff.tv_sec * 1000000 + diff.tv_usec; } static void knc_check_disabled_cores(struct knc_state *knc) { int next_read_d; struct timeval now; int64_t us; struct core_disa_data *core; int cidx; next_read_d = knc->read_d; knc_disa_cores_fifo_inc_idx(&next_read_d); if (next_read_d == knc->write_d) return; /* queue empty */ core = &knc->disa_cores_fifo[next_read_d]; gettimeofday(&now, NULL); us = timediff(&now, &core->disa_begin); if ((us >= 0) && (us < CORE_DISA_PERIOD_US)) return; /* latest disabled core still not expired */ cidx = core->asic * 256 + core->core; enable_core(core->asic, core->core); knc->hwerrs[cidx] = 0; applog(LOG_NOTICE, "KnC: core %u-%u was enabled back from disabled state", core->asic, core->core); knc->read_d = next_read_d; } static void knc_work_from_queue_to_spi(struct knc_state *knc, struct active_work *q_work, struct spi_request *spi_req) { uint32_t *buf_from, *buf_to; int i; spi_req->cmd = CMD_SUBMIT_WORK; spi_req->queue_id = 0; /* at the moment we have one and only queue #0 */ spi_req->work_id = (knc->next_work_id ^ knc->salt) & WORK_ID_MASK; q_work->work_id = spi_req->work_id; ++(knc->next_work_id); buf_to = spi_req->midstate; buf_from = (uint32_t *)q_work->work->midstate; for (i = 0; i < WORK_MIDSTATE_WORDS; ++i) buf_to[i] = le32toh(buf_from[8 - i - 1]); buf_to = spi_req->data; buf_from = (uint32_t *)&(q_work->work->data[16 * 4]); for (i = 0; i < WORK_DATA_WORDS; ++i) buf_to[i] = le32toh(buf_from[3 - i - 1]); } static int64_t knc_process_response(struct thr_info *thr, struct cgpu_info *cgpu, struct spi_rx_t *rxbuf, int __maybe_unused num) { struct knc_state *knc = cgpu->knc_state; struct work *work; int64_t us; int submitted, completed, i, num_sent; int next_read_q, next_read_a; struct timeval now; if (knc->write_q > knc->read_q) num_sent = knc->write_q - knc->read_q - 1; else num_sent = knc->write_q + KNC_QUEUED_BUFFER_SIZE - knc->read_q - 1; /* Actually process SPI response */ if (rxbuf->works_accepted) { applog(LOG_DEBUG, "KnC spi: raw response %08X %08X", ((uint32_t *)rxbuf)[0], ((uint32_t *)rxbuf)[1]); applog(LOG_DEBUG, "KnC spi: response, accepted %u (from %u), full %u", rxbuf->works_accepted, num_sent, rxbuf->response_queue_full); } /* move works_accepted number of items from queued_fifo to active_fifo */ gettimeofday(&now, NULL); submitted = 0; for (i = 0; i < rxbuf->works_accepted; ++i) { next_read_q = knc->read_q; knc_queued_fifo_inc_idx(&next_read_q); if ((next_read_q == knc->write_q) || knc_active_fifo_full(knc)) break; memcpy(&knc->active_fifo[knc->write_a], &knc->queued_fifo[next_read_q], sizeof(struct active_work)); knc->active_fifo[knc->write_a].begin = now; knc->queued_fifo[next_read_q].work = NULL; knc->read_q = next_read_q; knc_active_fifo_inc_idx(&knc->write_a); ++submitted; } if (submitted != rxbuf->works_accepted) applog(LOG_ERR, "KnC: accepted by FPGA %u works, but only %d submitted", rxbuf->works_accepted, submitted); /* check for completed works and calculated nonces */ gettimeofday(&now, NULL); completed = 0; for (i = 0; i < (int)MAX_RESPONSES_IN_BATCH; ++i) { if ( (rxbuf->responses[i].type != RESPONSE_TYPE_NONCE_FOUND) && (rxbuf->responses[i].type != RESPONSE_TYPE_WORK_DONE) ) continue; applog(LOG_DEBUG, "KnC spi: raw response %08X %08X", ((uint32_t *)&rxbuf->responses[i])[0], ((uint32_t *)&rxbuf->responses[i])[1]); applog(LOG_DEBUG, "KnC spi: response, T:%u C:%u-%u Q:%u W:%u", rxbuf->responses[i].type, rxbuf->responses[i].asic, rxbuf->responses[i].core, rxbuf->responses[i].queue_id, rxbuf->responses[i].work_id); /* Find active work with matching ID */ next_read_a = knc->read_a; knc_active_fifo_inc_idx(&next_read_a); while (next_read_a != knc->write_a) { if (knc->active_fifo[next_read_a].work_id == rxbuf->responses[i].work_id) break; /* check for stale works */ us = timediff(&now, &knc->active_fifo[next_read_a].begin); if ((us < 0) || (us >= WORK_STALE_US)) { applog(LOG_DEBUG, "KnC spi: remove stale work %u", knc->active_fifo[next_read_a].work_id); work = knc->active_fifo[next_read_a].work; knc_active_fifo_inc_idx(&knc->read_a); work_completed(cgpu, work); if (next_read_a != knc->read_a) memcpy(&(knc->active_fifo[next_read_a]), &(knc->active_fifo[knc->read_a]), sizeof(struct active_work)); knc->active_fifo[knc->read_a].work = NULL; } knc_active_fifo_inc_idx(&next_read_a); } if (next_read_a == knc->write_a) continue; applog(LOG_DEBUG, "KnC spi: response work %u found", rxbuf->responses[i].work_id); work = knc->active_fifo[next_read_a].work; if (rxbuf->responses[i].type == RESPONSE_TYPE_NONCE_FOUND) { if (NULL != thr) { int cidx = rxbuf->responses[i].asic * 256 + rxbuf->responses[i].core; if (submit_nonce(thr, work, rxbuf->responses[i].nonce)) { if (cidx < (int)sizeof(knc->hwerrs)) { knc->hwerrs[cidx] = 0; knc->disa_cnt[cidx] = 0; knc->hwerr_work_id[cidx] = 0xFFFFFFFF; } } else { if ((cidx < (int)sizeof(knc->hwerrs)) && (knc->hwerr_work_id[cidx] != rxbuf->responses[i].work_id)) { knc->hwerr_work_id[cidx] = rxbuf->responses[i].work_id; if (++(knc->hwerrs[cidx]) >= HW_ERR_LIMIT) { struct core_disa_data *core; core = &knc->disa_cores_fifo[knc->write_d]; core->disa_begin = now; core->asic = rxbuf->responses[i].asic; core->core = rxbuf->responses[i].core; disable_core(core->asic, core->core); if (++(knc->disa_cnt[cidx]) >= DISA_ERR_LIMIT) { applog(LOG_WARNING, "KnC: core %u-%u was disabled permanently", core->asic, core->core); } else { applog(LOG_WARNING, "KnC: core %u-%u was disabled due to %u HW errors in a row", core->asic, core->core, HW_ERR_LIMIT); knc_disa_cores_fifo_inc_idx(&knc->write_d); } } } }; } continue; } /* Work completed */ knc_active_fifo_inc_idx(&knc->read_a); work_completed(cgpu, work); if (next_read_a != knc->read_a) memcpy(&(knc->active_fifo[next_read_a]), &(knc->active_fifo[knc->read_a]), sizeof(struct active_work)); knc->active_fifo[knc->read_a].work = NULL; ++completed; } return ((uint64_t)completed) * 0x100000000UL; } /* Send flush command via SPI */ static int _internal_knc_flush_fpga(struct knc_state *knc) { int len; spi_txbuf[0].cmd = CMD_FLUSH_QUEUE; spi_txbuf[0].queue_id = 0; /* at the moment we have one and only queue #0 */ len = spi_transfer(knc->ctx, (uint8_t *)spi_txbuf, (uint8_t *)&spi_rxbuf, sizeof(struct spi_request)); if (len != sizeof(struct spi_request)) return -1; len /= sizeof(struct spi_response); return len; } static bool knc_detect_one(struct spidev_context *ctx) { /* Scan device for ASICs */ int chip_id; int devices = 0; for (chip_id = 0; chip_id < MAX_ASICS; ++chip_id) { /* TODO: perform the ASIC test/detection */ ++devices; } if (!devices) { applog(LOG_INFO, "SPI detected, but not KnCminer ASICs"); return false; } applog(LOG_INFO, "Found a KnC miner with %d ASICs", devices); struct cgpu_info *cgpu = calloc(1, sizeof(*cgpu)); struct knc_state *knc = calloc(1, sizeof(*knc)); if (!cgpu || !knc) { applog(LOG_ERR, "KnC miner detected, but failed to allocate memory"); return false; } knc->ctx = ctx; knc->devices = devices; knc->read_q = 0; knc->write_q = 1; knc->read_a = 0; knc->write_a = 1; knc->read_d = 0; knc->write_d = 1; knc->salt = rand(); memset(knc->hwerr_work_id, 0xFF, sizeof(knc->hwerr_work_id)); _internal_knc_flush_fpga(knc); cgpu->drv = &knc_drv; cgpu->name = "KnCminer"; cgpu->threads = 1; // .. perhaps our number of devices? cgpu->knc_state = knc; add_cgpu(cgpu); return true; } // http://www.concentric.net/~Ttwang/tech/inthash.htm static unsigned long mix(unsigned long a, unsigned long b, unsigned long c) { a=a-b; a=a-c; a=a^(c >> 13); b=b-c; b=b-a; b=b^(a << 8); c=c-a; c=c-b; c=c^(b >> 13); a=a-b; a=a-c; a=a^(c >> 12); b=b-c; b=b-a; b=b^(a << 16); c=c-a; c=c-b; c=c^(b >> 5); a=a-b; a=a-c; a=a^(c >> 3); b=b-c; b=b-a; b=b^(a << 10); c=c-a; c=c-b; c=c^(b >> 15); return c; } /* Probe devices and register with add_cgpu */ void knc_detect(bool __maybe_unused hotplug) { int idx; srand(mix(clock(), time(NULL), getpid())); /* Loop through all possible SPI interfaces */ for (idx = 0; idx < MAX_SPIS; ++idx) { struct spidev_context *ctx = spi_new(idx + 1); if (ctx != NULL) { if (!knc_detect_one(ctx)) spi_free(ctx); } } } /* return value is number of nonces that have been checked since * previous call */ static int64_t knc_scanwork(struct thr_info *thr) { struct cgpu_info *cgpu = thr->cgpu; struct knc_state *knc = cgpu->knc_state; int len, num; int next_read_q; applog(LOG_DEBUG, "KnC running scanwork"); knc_check_disabled_cores(knc); /* Prepare tx buffer */ memset(spi_txbuf, 0, sizeof(spi_txbuf)); num = 0; next_read_q = knc->read_q; knc_queued_fifo_inc_idx(&next_read_q); while (next_read_q != knc->write_q) { knc_work_from_queue_to_spi(knc, &knc->queued_fifo[next_read_q], &spi_txbuf[num]); knc_queued_fifo_inc_idx(&next_read_q); ++num; } /* knc->read_q is advanced in knc_process_response, not here */ len = spi_transfer(knc->ctx, (uint8_t *)spi_txbuf, (uint8_t *)&spi_rxbuf, sizeof(spi_txbuf)); if (len != sizeof(spi_rxbuf)) return -1; len /= sizeof(struct spi_response); applog(LOG_DEBUG, "KnC spi: %d works in request", num); return knc_process_response(thr, cgpu, &spi_rxbuf, len); } static bool knc_queue_full(struct cgpu_info *cgpu) { struct knc_state *knc = cgpu->knc_state; struct work *work; int queue_full = true; applog(LOG_DEBUG, "KnC running queue full"); while (!knc_queued_fifo_full(knc)) { work = get_queued(cgpu); if (!work) { queue_full = false; break; } knc->queued_fifo[knc->write_q].work = work; knc_queued_fifo_inc_idx(&(knc->write_q)); } return queue_full; } static void knc_flush_work(struct cgpu_info *cgpu) { struct knc_state *knc = cgpu->knc_state; struct work *work; int len; int next_read_q, next_read_a; applog(LOG_ERR, "KnC running flushwork"); /* Drain queued works */ next_read_q = knc->read_q; knc_queued_fifo_inc_idx(&next_read_q); while (next_read_q != knc->write_q) { work = knc->queued_fifo[next_read_q].work; work_completed(cgpu, work); knc->queued_fifo[next_read_q].work = NULL; knc->read_q = next_read_q; knc_queued_fifo_inc_idx(&next_read_q); } /* Drain active works */ next_read_a = knc->read_a; knc_active_fifo_inc_idx(&next_read_a); while (next_read_a != knc->write_a) { work = knc->active_fifo[next_read_a].work; work_completed(cgpu, work); knc->active_fifo[next_read_a].work = NULL; knc->read_a = next_read_a; knc_active_fifo_inc_idx(&next_read_a); } len = _internal_knc_flush_fpga(knc); if (len > 0) knc_process_response(NULL, cgpu, &spi_rxbuf, len); } struct device_drv knc_drv = { .drv_id = DRIVER_knc, .dname = "KnCminer", .name = "KnC", .drv_detect = knc_detect, // Probe for devices, add with add_cgpu .hash_work = hash_queued_work, .scanwork = knc_scanwork, .queue_full = knc_queue_full, .flush_work = knc_flush_work, };