/* * Copyright 2013 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. */ #include "config.h" #include #include #include #include #include #include #include #ifdef HAVE_LINUX_I2C_DEV_USER_H #include #else #include #endif #include #include #include "deviceapi.h" #include "logging.h" #include "miner.h" #include "spidevc.h" #define KNC_POLL_INTERVAL_US 10000 #define KNC_SPI_SPEED 3000000 #define KNC_SPI_DELAY 0 #define KNC_SPI_MODE (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH) #define KNC_SPI_BITS 8 static const char * const i2cpath = "/dev/i2c-2"; #define KNC_I2C_TEMPLATE "/dev/i2c-%d" enum knc_request_cmd { KNC_REQ_SUBMIT_WORK = 2, KNC_REQ_FLUSH_QUEUE = 3, }; enum knc_reply_type { KNC_REPLY_NONCE_FOUND = 1, KNC_REPLY_WORK_DONE = 2, }; struct device_drv knc_drv; struct knc_device { int i2c; struct spi_port *spi; struct cgpu_info *cgpu; bool need_flush; struct work *workqueue; int workqueue_size; int workqueue_max; int next_id; struct work *devicework; }; struct knc_core { int asicno; int coreno; }; static bool knc_detect_one(const char *devpath) { static struct cgpu_info *prev_cgpu = NULL; struct cgpu_info *cgpu; int i; const int fd = open(i2cpath, O_RDWR); char *leftover = NULL; const int i2cslave = strtol(devpath, &leftover, 0); uint8_t buf[0x20]; if (leftover && leftover[0]) return false; if (unlikely(fd == -1)) { applog(LOG_DEBUG, "%s: Failed to open %s", __func__, i2cpath); return false; } if (ioctl(fd, I2C_SLAVE, i2cslave)) { close(fd); applog(LOG_DEBUG, "%s: Failed to select i2c slave 0x%x", __func__, i2cslave); return false; } i = i2c_smbus_read_i2c_block_data(fd, 0, 0x20, buf); close(fd); if (-1 == i) { applog(LOG_DEBUG, "%s: 0x%x: Failed to read i2c block data", __func__, i2cslave); return false; } for (i = 0; ; ++i) { if (buf[i] == 3) break; if (i == 0x1f) return false; } cgpu = malloc(sizeof(*cgpu)); *cgpu = (struct cgpu_info){ .drv = &knc_drv, .device_path = strdup(devpath), .deven = DEV_ENABLED, .procs = 192, .threads = prev_cgpu ? 0 : 1, }; const bool rv = add_cgpu_slave(cgpu, prev_cgpu); prev_cgpu = cgpu; return rv; } static int knc_detect_auto(void) { const int first = 0x20, last = 0x26; char devpath[4]; int found = 0, i; for (i = first; i <= last; ++i) { sprintf(devpath, "%d", i); if (knc_detect_one(devpath)) ++found; } return found; } static void knc_detect(void) { generic_detect(&knc_drv, knc_detect_one, knc_detect_auto, GDF_REQUIRE_DNAME | GDF_DEFAULT_NOAUTO); } static bool knc_spi_open(const char *repr, struct spi_port * const spi) { const char * const spipath = "/dev/spidev1.0"; const int fd = open(spipath, O_RDWR); const uint8_t lsbfirst = 0; if (fd == -1) return false; if (ioctl(fd, SPI_IOC_WR_MODE , &spi->mode )) goto fail; if (ioctl(fd, SPI_IOC_WR_LSB_FIRST , &lsbfirst )) goto fail; if (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &spi->bits )) goto fail; if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ , &spi->speed)) goto fail; spi->fd = fd; return true; fail: close(fd); spi->fd = -1; applog(LOG_WARNING, "%s: Failed to open %s", repr, spipath); return false; } static bool knc_spi_txrx(struct spi_port * const spi) { const void * const wrbuf = spi_gettxbuf(spi); void * const rdbuf = spi_getrxbuf(spi); const size_t bufsz = spi_getbufsz(spi); const int fd = spi->fd; struct spi_ioc_transfer xf = { .tx_buf = (uintptr_t) wrbuf, .rx_buf = (uintptr_t) rdbuf, .len = bufsz, .delay_usecs = spi->delay, .speed_hz = spi->speed, .bits_per_word = spi->bits, }; return (ioctl(fd, SPI_IOC_MESSAGE(1), &xf) > 0); } static void knc_clean_flush(struct spi_port * const spi) { const uint8_t flushcmd = KNC_REQ_FLUSH_QUEUE << 4; const size_t spi_req_sz = 0x1000; spi_clear_buf(spi); spi_emit_buf(spi, &flushcmd, 1); spi_emit_nop(spi, spi_req_sz - spi_getbufsz(spi)); applog(LOG_DEBUG, "%s: Issuing flush command to clear out device queues", knc_drv.dname); spi_txrx(spi); } static bool knc_init(struct thr_info * const thr) { const int max_cores = 192; struct thr_info *mythr; struct cgpu_info * const cgpu = thr->cgpu, *proc; struct knc_device *knc; struct knc_core *knccore; struct spi_port *spi; const int i2c = open(i2cpath, O_RDWR); int i2cslave, i, j; uint8_t buf[0x20]; if (unlikely(i2c == -1)) { applog(LOG_DEBUG, "%s: Failed to open %s", __func__, i2cpath); return false; } knc = malloc(sizeof(*knc)); for (proc = cgpu; proc; ) { if (proc->device != proc) { applog(LOG_WARNING, "%"PRIpreprv": Extra processor?", proc->proc_repr); continue; } i2cslave = atoi(proc->device_path); if (ioctl(i2c, I2C_SLAVE, i2cslave)) { applog(LOG_DEBUG, "%s: Failed to select i2c slave 0x%x", __func__, i2cslave); return false; } for (i = 0; i < max_cores; i += 0x20) { i2c_smbus_read_i2c_block_data(i2c, i, 0x20, buf); for (j = 0; j < 0x20; ++j) { mythr = proc->thr[0]; mythr->cgpu_data = knccore = malloc(sizeof(*knccore)); *knccore = (struct knc_core){ .asicno = i2cslave - 0x20, .coreno = i + j, }; if (proc != cgpu) mythr->queue_full = true; proc->device_data = knc; if (buf[j] != 3) proc->deven = DEV_DISABLED; proc = proc->next_proc; if ((!proc) || proc->device == proc) goto nomorecores; } } nomorecores: ; } spi = malloc(sizeof(*spi)); *knc = (struct knc_device){ .i2c = i2c, .spi = spi, .cgpu = cgpu, .workqueue_max = 1, }; *spi = (struct spi_port){ .txrx = knc_spi_txrx, .cgpu = cgpu, .repr = knc_drv.dname, .logprio = LOG_ERR, .speed = KNC_SPI_SPEED, .delay = KNC_SPI_DELAY, .mode = KNC_SPI_MODE, .bits = KNC_SPI_BITS, }; if (!knc_spi_open(cgpu->dev_repr, spi)) return false; knc_clean_flush(spi); timer_set_now(&thr->tv_poll); return true; } static void knc_remove_local_queue(struct knc_device * const knc, struct work * const work) { DL_DELETE(knc->workqueue, work); free_work(work); --knc->workqueue_size; } static void knc_prune_local_queue(struct thr_info *thr) { struct cgpu_info * const cgpu = thr->cgpu; struct knc_device * const knc = cgpu->device_data; struct work *work, *tmp; DL_FOREACH_SAFE(knc->workqueue, work, tmp) { if (stale_work(work, false)) knc_remove_local_queue(knc, work); } thr->queue_full = (knc->workqueue_size >= knc->workqueue_max); } static bool knc_queue_append(struct thr_info * const thr, struct work * const work) { struct cgpu_info * const cgpu = thr->cgpu; struct knc_device * const knc = cgpu->device_data; if (knc->workqueue_size >= knc->workqueue_max) { knc_prune_local_queue(thr); if (thr->queue_full) return false; } DL_APPEND(knc->workqueue, work); ++knc->workqueue_size; thr->queue_full = (knc->workqueue_size >= knc->workqueue_max); if (thr->queue_full) knc_prune_local_queue(thr); return true; } #define HASH_LAST_ADDED(head, out) \ (out = (head) ? (ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail)) : NULL) static void knc_queue_flush(struct thr_info * const thr) { struct cgpu_info * const cgpu = thr->cgpu; struct knc_device * const knc = cgpu->device_data; struct work *work, *tmp; if (knc->cgpu != cgpu) return; DL_FOREACH_SAFE(knc->workqueue, work, tmp) { knc_remove_local_queue(knc, work); } thr->queue_full = false; HASH_LAST_ADDED(knc->devicework, work); if (work && stale_work(work, true)) { knc->need_flush = true; timer_set_now(&thr->tv_poll); } } static inline uint16_t get_u16be(const void * const p) { const uint8_t * const b = p; return (((uint16_t)b[0]) << 8) | b[1]; } static inline uint32_t get_u32be(const void * const p) { const uint8_t * const b = p; return (((uint32_t)b[0]) << 0x18) | (((uint32_t)b[1]) << 0x10) | (((uint32_t)b[2]) << 8) | b[3]; } static void knc_poll(struct thr_info * const thr) { struct thr_info *mythr; struct cgpu_info * const cgpu = thr->cgpu, *proc; struct knc_device * const knc = cgpu->device_data; struct spi_port * const spi = knc->spi; struct knc_core *knccore; struct work *work, *tmp; uint8_t buf[0x30], *rxbuf; int works_sent = 0, asicno, i; uint16_t workaccept; int workid = knc->next_id; uint32_t nonce, coreno; size_t spi_req_sz = 0x1000; unsigned long delay_usecs = KNC_POLL_INTERVAL_US; knc_prune_local_queue(thr); spi_clear_buf(spi); if (knc->need_flush) { applog(LOG_NOTICE, "%s: Abandoning stale searches to restart", knc_drv.dname); buf[0] = KNC_REQ_FLUSH_QUEUE << 4; spi_emit_buf(spi, buf, sizeof(buf)); } DL_FOREACH(knc->workqueue, work) { buf[0] = KNC_REQ_SUBMIT_WORK << 4; buf[1] = 0; buf[2] = (workid >> 8) & 0x7f; buf[3] = workid & 0xff; for (i = 0; i < 0x20; ++i) buf[4 + i] = work->midstate[0x1f - i]; for (i = 0; i < 0xc; ++i) buf[0x24 + i] = work->data[0x4b - i]; spi_emit_buf(spi, buf, sizeof(buf)); ++works_sent; ++workid; } spi_emit_nop(spi, spi_req_sz - spi_getbufsz(spi)); spi_txrx(spi); rxbuf = spi_getrxbuf(spi); if (rxbuf[3] & 1) applog(LOG_DEBUG, "%s: Receive buffer overflow reported", knc_drv.dname); workaccept = get_u16be(&rxbuf[6]); applog(LOG_DEBUG, "%s: %lu/%d jobs accepted to queue (max=%d)", knc_drv.dname, (unsigned long)workaccept, works_sent, knc->workqueue_max); while (true) { rxbuf += 0xc; spi_req_sz -= 0xc; if (spi_req_sz < 0xc) break; const int rtype = rxbuf[0] >> 6; if (rtype && opt_debug) { char x[(0xc * 2) + 1]; bin2hex(x, rxbuf, 0xc); applog(LOG_DEBUG, "%s: RECV: %s", knc_drv.dname, x); } if (rtype != KNC_REPLY_NONCE_FOUND && rtype != KNC_REPLY_WORK_DONE) continue; asicno = (rxbuf[0] & 0x38) >> 3; coreno = get_u32be(&rxbuf[8]); proc = cgpu; while (true) { knccore = proc->thr[0]->cgpu_data; if (knccore->asicno == asicno) break; do { proc = proc->next_proc; } while(proc != proc->device); } for (i = 0; i < coreno; ++i) proc = proc->next_proc; mythr = proc->thr[0]; i = get_u16be(&rxbuf[2]); HASH_FIND_INT(knc->devicework, &i, work); if (!work) { const char * const msgtype = (rtype == KNC_REPLY_NONCE_FOUND) ? "nonce found" : "work done"; applog(LOG_WARNING, "%"PRIpreprv": Got %s message about unknown work 0x%04x", proc->proc_repr, msgtype, i); if (KNC_REPLY_NONCE_FOUND == rtype) { nonce = get_u32be(&rxbuf[4]); nonce = le32toh(nonce); inc_hw_errors2(mythr, NULL, &nonce); } else inc_hw_errors2(mythr, NULL, NULL); continue; } switch (rtype) { case KNC_REPLY_NONCE_FOUND: nonce = get_u32be(&rxbuf[4]); nonce = le32toh(nonce); submit_nonce(mythr, work, nonce); break; case KNC_REPLY_WORK_DONE: HASH_DEL(knc->devicework, work); free_work(work); hashes_done2(mythr, 0x100000000, NULL); break; } } if (knc->need_flush) { knc->need_flush = false; HASH_ITER(hh, knc->devicework, work, tmp) { HASH_DEL(knc->devicework, work); free_work(work); } delay_usecs = 0; } if (workaccept) { if (workaccept >= knc->workqueue_max) { knc->workqueue_max = workaccept; delay_usecs = 0; } DL_FOREACH_SAFE(knc->workqueue, work, tmp) { --knc->workqueue_size; DL_DELETE(knc->workqueue, work); work->device_id = knc->next_id++ & 0x7fff; HASH_ADD_INT(knc->devicework, device_id, work); if (!--workaccept) break; } thr->queue_full = (knc->workqueue_size >= knc->workqueue_max); } timer_set_delay_from_now(&thr->tv_poll, delay_usecs); } static bool _knc_core_setstatus(struct thr_info * const thr, uint8_t val) { struct cgpu_info * const proc = thr->cgpu; struct knc_device * const knc = proc->device_data; struct knc_core * const knccore = thr->cgpu_data; const int i2c = knc->i2c; const int i2cslave = 0x20 + knccore->asicno; if (ioctl(i2c, I2C_SLAVE, i2cslave)) { applog(LOG_DEBUG, "%"PRIpreprv": %s: Failed to select i2c slave 0x%x", proc->proc_repr, __func__, i2cslave); return false; } return (-1 != i2c_smbus_write_byte_data(i2c, knccore->coreno, val)); } static void knc_core_disable(struct thr_info * const thr) { _knc_core_setstatus(thr, 0); } static void knc_core_enable(struct thr_info * const thr) { _knc_core_setstatus(thr, 1); } static bool knc_get_stats(struct cgpu_info * const cgpu) { if (cgpu->device != cgpu) return true; struct knc_core * const knccore = cgpu->thr[0]->cgpu_data; struct cgpu_info *proc; const int i2cdev = knccore->asicno + 3; const int i2cslave = 0x48; int i2c; int32_t rawtemp; float temp; bool rv = false; char i2cpath[sizeof(KNC_I2C_TEMPLATE)]; sprintf(i2cpath, KNC_I2C_TEMPLATE, i2cdev); i2c = open(i2cpath, O_RDWR); if (i2c == -1) { applog(LOG_DEBUG, "%s: %s: Failed to open %s", cgpu->dev_repr, __func__, i2cpath); return false; } if (ioctl(i2c, I2C_SLAVE, i2cslave)) { applog(LOG_DEBUG, "%s: %s: Failed to select i2c slave 0x%x", cgpu->dev_repr, __func__, i2cslave); goto out; } rawtemp = i2c_smbus_read_word_data(i2c, 0); if (rawtemp == -1) goto out; temp = ((float)(rawtemp & 0xff)); if (rawtemp & 0x100) temp += 0.5; for (proc = cgpu; proc && proc->device == cgpu; proc = proc->next_proc) proc->temp = temp; rv = true; out: close(i2c); return rv; } struct device_drv knc_drv = { .dname = "knc", .name = "KNC", .drv_detect = knc_detect, .thread_init = knc_init, .thread_disable = knc_core_disable, .thread_enable = knc_core_enable, .minerloop = minerloop_queue, .queue_append = knc_queue_append, .queue_flush = knc_queue_flush, .poll = knc_poll, .get_stats = knc_get_stats, };