/*
 * Copyright 2013-2014 Andrew Smith - BlackArrow Ltd
 *
 * 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 "compat.h"
#include "miner.h"
#include "klist.h"
#include <ctype.h>
#include <math.h>

#ifndef LINUX
static void minion_detect(__maybe_unused bool hotplug)
{
}
#else

#include <unistd.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <poll.h>

// Define this to 1 to enable interrupt code and enable no_nonce
#define ENABLE_INT_NONO 0

// Define this to 1 if compiling on RockChip and not on RPi
#define MINION_ROCKCHIP 0

// The code is always in - this just decides if it does it
static bool minreread = false;

#if MINION_ROCKCHIP == 1
#define MINION_POWERCYCLE_GPIO 173
#define MINION_CHIP_OFF "1"
#define MINION_CHIP_ON "0"
#define MINION_CHIP_DELAY 100
#endif

// Power cycle if the xff_list is full and the tail is less than
// this long ago
#define MINION_POWER_TIME 60

/*
 * Use pins for board selection
 * If disabled, it will test chips just as 'pin 0'
 * but never do any gpio - the equivalent of the previous 'no pins' code
 */
static bool usepins = false;

#define MINION_PAGE_SIZE 4096

#define BCM2835_BASE 0x20000000
#define BCM2835_GPIO_BASE (BCM2835_BASE + 0x200000)

#define BCM2835_GPIO_SET0 0x001c // GPIO Pin Output Set 0
#define BCM2835_GPIO_CLR0 0x0028 // GPIO Pin Output Clear 0

#define BCM2835_GPIO_FSEL0 0x0000

#define BCM2835_GPIO_FSEL_INPUT  0b000
#define BCM2835_GPIO_FSEL_OUTPUT 0b001
#define BCM2835_GPIO_FSEL_MASK   0b111

#define BCM2835_PIN_HIGH 0x1
#define BCM2835_PIN_LOW 0x0

static const char *minion_memory = "/dev/mem";
static int minion_memory_addr = BCM2835_GPIO_BASE;

#define MINION_SPI_BUS 0
#define MINION_SPI_CHIP 0

#if MINION_ROCKCHIP == 0
#define MINION_SPI_SPEED 8000000
#else
#define MINION_SPI_SPEED 500000
#endif
#define MINION_SPI_BUFSIZ 1024

static struct minion_select_pins {
	int pin;
	int wpi;
	char *name;
	int bcm; // this is what we use
} minionPins[] = {
	{	24,	10,	"CE0",		8,	},
	{	26,	11,	"CE1",		7,	},
	{	16,	4,	"GPIO4",	23,	},
	{	22,	6,	"GPIO6",	25,	},
	{	12,	1,	"GPIO1",	18,	},
	{	18,	5,	"GPIO5",	24,	},
	{	11,	0,	"GPIO0",	17,	},
	{	13,	2,	"GPIO2",	27,	},
	{	15,	3,	"GPIO3",	22,	},
	{	7,	7,	"GPIO7",	4,	}

/* The rest on the RPi
	{	3,	8,	"SDA",		2,	}
	{	5,	9,	"SCL",		3,	}
	{	19,	12,	"MOSI",		10,	}
	{	21,	13,	"MISO",		9,	}
	{	23,	14,	"SCLK",		11,	}
	{	8,	15,	"TxD",		14,	}
	{	10,	16,	"RxD",		15,	}
*/
};

/*
 * uS delays for GPIO pin access
 */
#define MINION_PIN_BEFORE cgsleep_us(33)
#define MINION_PIN_SLEEP cgsleep_us(133)
#define MINION_PIN_AFTER

#define MINION_PIN_COUNT (sizeof(minionPins)/ \
			  sizeof(struct minion_select_pins))

#define CHIP_PIN(_chip) (minioninfo->chip_pin[_chip])

#define MINION_MIN_CHIP 0
#define MINION_MAX_CHIP 11

#define MINION_CHIP_PER_PIN (1 + MINION_MAX_CHIP - MINION_MIN_CHIP)

#define MINION_CHIPS (MINION_PIN_COUNT * MINION_CHIP_PER_PIN)
#define MINION_CORES 99
#define FAKE_CORE MINION_CORES

/*
 * TODO: These will need adjusting for final hardware
 * Look them up and calculate them?
 */
#define MINION_QUE_MAX 64
#define MINION_QUE_HIGH 48
#define MINION_QUE_SEND 16
#define MINION_QUE_LOW 8

#define MINION_FFL " - from %s %s() line %d"
#define MINION_FFL_HERE __FILE__, __func__, __LINE__
#define MINION_FFL_PASS file, func, line
#define MINION_FFL_ARGS __maybe_unused const char *file, \
			__maybe_unused const char *func, \
			__maybe_unused const int line

#define minion_txrx(_task) _minion_txrx(minioncgpu, minioninfo, _task, MINION_FFL_HERE)

#define MINION_SYS_REGS 0x00
#define MINION_CORE_REGS 0x10
#define MINION_RES_BUF 0x20
#define MINION_CMD_QUE 0x30
#define MINION_NONCE_RANGES 0x70

#define DATA_SIZ (sizeof(uint32_t))

// All SYS data sizes are DATA_SIZ
#define MINION_SYS_CHIP_SIG 0x00
#define MINION_SYS_CHIP_STA 0x01
#define MINION_SYS_SPI_LED 0x02
#define MINION_SYS_TEMP_CTL 0x03
#define MINION_SYS_FREQ_CTL 0x04
#define MINION_SYS_NONCE_LED 0x05
#define MINION_SYS_MISC_CTL 0x06
#define MINION_SYS_RSTN_CTL 0x07
#define MINION_SYS_INT_ENA 0x08
#define MINION_SYS_INT_CLR 0x09
#define MINION_SYS_INT_STA 0x0a
#define MINION_SYS_FIFO_STA 0x0b
#define MINION_SYS_QUE_TRIG 0x0c
#define MINION_SYS_BUF_TRIG 0x0d
#define MINION_SYS_IDLE_CNT 0x0e

// How many 32 bit reports make up all the cores - 99 cores = 4 reps
#define MINION_CORE_REPS (int)((((MINION_CORES-1) >> 5) & 0xff) + 1)

// All SYS data sizes are DATA_SIZ
#define MINION_SYS_SIZ DATA_SIZ

// Header Pin 18 = GPIO5 = BCM 24
#define MINION_GPIO_RESULT_INT_PIN 24
// RockChip is pin 172 ...

#define MINION_GPIO_SYS "/sys/class/gpio"
#define MINION_GPIO_ENA "/export"
#define MINION_GPIO_ENA_VAL "%d"
#define MINION_GPIO_DIS "/unexport"
#define MINION_GPIO_PIN "/gpio%d"
#define MINION_GPIO_DIR "/direction"
#define MINION_GPIO_DIR_READ "in"
#define MINION_GPIO_DIR_WRITE "out"
#define MINION_GPIO_EDGE "/edge"
#define MINION_GPIO_EDGE_NONE "none"
#define MINION_GPIO_EDGE_RISING "rising"
#define MINION_GPIO_EDGE_FALLING "falling"
#define MINION_GPIO_EDGE_BOTH "both"
#define MINION_GPIO_ACT "/active_low"
#define MINION_GPIO_ACT_LO "1"
#define MINION_GPIO_ACT_HI "0"
#define MINION_GPIO_VALUE "/value"

#define MINION_RESULT_INT 0x01
#define MINION_RESULT_FULL_INT 0x02
#define MINION_CMD_INT 0x04
#define MINION_CMD_FULL_INT 0x08
#define MINION_TEMP_LOW_INT 0x10
#define MINION_TEMP_HI_INT 0x20
#define MINION_ALL_INT  MINION_RESULT_INT | \
			MINION_RESULT_FULL_INT | \
			MINION_CMD_INT | \
			MINION_CMD_FULL_INT | \
			MINION_TEMP_LOW_INT | \
			MINION_TEMP_HI_INT

#define RSTN_CTL_RESET_CORES 0x01
#define RSTN_CTL_FLUSH_RESULTS 0x02
#define RSTN_CTL_FLUSH_CMD_QUEUE 0x04
#define RSTN_CTL_SPI_SW_RSTN 0x08
#define RSTN_CTL_SHA_MGR_RESET 0x10

// Init
#define SYS_RSTN_CTL_INIT (RSTN_CTL_RESET_CORES | \
				RSTN_CTL_FLUSH_RESULTS | \
				RSTN_CTL_FLUSH_CMD_QUEUE | \
				RSTN_CTL_SPI_SW_RSTN | \
				RSTN_CTL_SHA_MGR_RESET)

// Block change
#define SYS_RSTN_CTL_FLUSH (RSTN_CTL_RESET_CORES | \
				RSTN_CTL_SPI_SW_RSTN | \
				RSTN_CTL_FLUSH_CMD_QUEUE)

#if ENABLE_INT_NONO
// enable 'no nonce' report
#define SYS_MISC_CTL_DEFAULT 0x04
#else
#define SYS_MISC_CTL_DEFAULT 0x00
#endif

// Temperature returned by MINION_SYS_CHIP_STA 0x01 STA_TEMP()
#define MINION_TEMP_40 0
#define MINION_TEMP_60 1
#define MINION_TEMP_80 3
#define MINION_TEMP_100 7
#define MINION_TEMP_OVER 15

static const char *min_temp_40 = "<40";
static const char *min_temp_60 = "40-60";
static const char *min_temp_80 = "60-80";
static const char *min_temp_100 = "80-100";
static const char *min_temp_over = ">100";
static const char *min_temp_invalid = "?";

/*
 * Temperature for MINION_SYS_TEMP_CTL 0x03 temp_thres [0:3]
 * i.e. it starts at 120 and goes up in steps of 5 to 160
 */
#define MINION_TEMP_CTL_MIN 1
#define MINION_TEMP_CTL_MAX 9
#define MINION_TEMP_CTL_BITS 0x0f
#define MINION_TEMP_CTL_DEF 135
#define MINION_TEMP_CTL_STEP 5
#define MINION_TEMP_CTL_MIN_VALUE 120
#define MINION_TEMP_CTL_MAX_VALUE (MINION_TEMP_CTL_MIN_VALUE + \
				(MINION_TEMP_CTL_STEP * \
				(MINION_TEMP_CTL_MAX - MINION_TEMP_CTL_MIN)))
#define MINION_TEMP_DISABLE "disable"
#define MINION_TEMP_CTL_DISABLE -1
#define MINION_TEMP_CTL_DISABLE_VALUE 0x20

// CORE data size is DATA_SIZ
#define MINION_CORE_ENA0_31 0x10
#define MINION_CORE_ENA32_63 0x11
#define MINION_CORE_ENA64_95 0x12
#define MINION_CORE_ENA96_98 0x13
#define MINION_CORE_ACT0_31 0x14
#define MINION_CORE_ACT32_63 0x15
#define MINION_CORE_ACT64_95 0x16
#define MINION_CORE_ACT96_98 0x17

// All CORE data sizes are DATA_SIZ
#define MINION_CORE_SIZ DATA_SIZ

#define MINION_CORE_ALL "all"

// RES data size is minion_result
#define MINION_RES_DATA 0x20
#define MINION_RES_PEEK 0x21

// QUE data size is minion_que
#define MINION_QUE_0 0x30
#define MINION_QUE_R 0x31

// RANGE data sizes are DATA_SIZ
#define MINION_NONCE_START 0x70
#define MINION_NONCE_RANGE 0x71

// This must be >= max txsiz + max rxsiz
#define MINION_BUFSIZ 1024

#define u8tou32(_c, _off) (((uint8_t *)(_c))[(_off)+0] + \
			   ((uint8_t *)(_c))[(_off)+1] * 0x100 + \
			   ((uint8_t *)(_c))[(_off)+2] * 0x10000 + \
			   ((uint8_t *)(_c))[(_off)+3] * 0x1000000 )

#define MINION_ADDR_WRITE 0x7f
#define MINION_ADDR_READ 0x80

#define READ_ADDR(_reg) ((_reg) | MINION_ADDR_READ)
#define WRITE_ADDR(_reg) ((_reg) & MINION_ADDR_WRITE)

#define IS_ADDR_READ(_reg) (((_reg) & MINION_ADDR_READ) == MINION_ADDR_READ)
#define IS_ADDR_WRITE(_reg) (((_reg) & MINION_ADDR_READ) == 0)

#define SET_HEAD_WRITE(_h, _reg) ((_h)->reg) = WRITE_ADDR(_reg)
#define SET_HEAD_READ(_h, _reg) ((_h)->reg) = READ_ADDR(_reg)
#define SET_HEAD_SIZ(_h, _siz) \
		do { \
			((_h)->siz)[0] = (uint8_t)((_siz) & 0xff); \
			((_h)->siz)[1] = (uint8_t)(((_siz) & 0xff00) >> 8); \
		} while (0)

struct minion_header {
	uint8_t chipid;
	uint8_t reg;
	uint8_t siz[2];
	uint8_t data[4]; // placeholder
};

#define HSIZE() (sizeof(struct minion_header) - 4)

#define MINION_NOCHIP_SIG 0x00000000
#define MINION_NOCHIP_SIG2 0xffffffff
#define MINION_CHIP_SIG 0xb1ac8a44

/*
 * Number of times to try and get the SIG with each chip,
 * if the chip returns neither of the above values
 * TODO: maybe need some reset between tries, to handle a shift value?
 */
#define MINION_SIG_TRIES 3

/*
 * TODO: Finding these means the chip is there - but how to fix it?
 * The extra &'s are to ensure there is no sign bit issue since
 * the sign bit carry in a C bit-shift is compiler dependent
 */
#define MINION_CHIP_SIG_SHIFT1 (((MINION_CHIP_SIG & 0x0000ffff) << 16) & 0xffff0000)
#define MINION_CHIP_SIG_SHIFT2 (((MINION_CHIP_SIG & 0x00ffffff) <<  8) & 0xffffff00)
#define MINION_CHIP_SIG_SHIFT3 (((MINION_CHIP_SIG & 0xffffff00) >>  8) & 0x00ffffff)
#define MINION_CHIP_SIG_SHIFT4 (((MINION_CHIP_SIG & 0xffff0000) >> 16) & 0x0000ffff)

#define MINION_SPI_LED_ON 0xa5a5
#define MINION_SPI_LED_OFF 0x0

// Time since first nonce/last reset before turning on the LED
#define MINION_LED_TEST_TIME 600

#define MINION_FREQ_MIN 100
#define MINION_FREQ_DEF 1200
#define MINION_FREQ_MAX 1400
#define MINION_FREQ_FACTOR 100
#define MINION_FREQ_RESET_STEP MINION_FREQ_FACTOR
#define MINION_FREQ_FACTOR_MIN 1
#define MINION_FREQ_FACTOR_MAX 14

static uint32_t minion_freq[] = {
	0x0,
	0x205032,	//  1 =  100Mhz
	0x203042,	//  2 =  200Mhz
	0x20204B,	//  3 =  300Mhz
	0x201042,	//  4 =  400Mhz
	0x201053,	//  5 =  500Mhz
	0x200032,	//  6 =  600Mhz
	0x20003A,	//  7 =  700Mhz
	0x200042,	//  8 =  800Mhz
	0x20004B,	//  9 =  900Mhz
	0x200053,	// 10 = 1000Mhz
	0x21005B,	// 11 = 1100Mhz
	0x210064,	// 12 = 1200Mhz
	0x21006C,	// 13 = 1300Mhz
	0x210074	// 14 = 1400Mhz
};

// When hash rate falls below this in the history hash rate, reset it
#define MINION_RESET_PERCENT 75.0
// When hash rate falls below this after the longer test time
#define MINION_RESET2_PERCENT 85.0

// After the above resets, delay sending work for:
#define MINION_RESET_DELAY_s 0.088

#define STA_TEMP(_sta) ((uint16_t)((_sta)[3] & 0x1f))
#define STA_CORES(_sta) ((uint16_t)((_sta)[2]))
#define STA_FREQ(_sta) ((uint32_t)((_sta)[1]) * 0x100 + (uint32_t)((_sta)[0]))

// Randomly between 1s and 2s per chip
#define MINION_STATS_UPDATE_TIME_mS 1000
#define MINION_STATS_UPDATE_RAND_mS 1000

// Don't report it more than once every ... 5s
#define MINION_IDLE_MESSAGE_ms 5000

struct minion_status {
	uint16_t temp;
	uint16_t cores;
	uint32_t freq;
	uint32_t quework;
	uint32_t chipwork;
	uint32_t realwork; // FIFO_STA
	struct timeval last;
	bool overheat;
	bool islow;
	bool tohigh;
	int lowcount;
	uint32_t overheats;
	struct timeval lastoverheat;
	struct timeval lastrecover;
	double overheattime;
	uint32_t tempsent;
	uint32_t idle;
	uint32_t last_rpt_idle;
	struct timeval idle_rpt;
	struct timeval first_nonce;
	uint64_t from_first_good;
};

#define ENABLE_CORE(_core, _n) ((_core[_n >> 3]) |= (1 << (_n % 8)))
#define CORE_IDLE(_core, _n) ((_core[_n >> 3]) & (1 << (_n % 8)))

#define FIFO_RES(_fifo, _off) ((_fifo)[(_off) + 0])
#define FIFO_CMD(_fifo, _off) ((_fifo)[(_off) + 1])

#define RES_GOLD(_res) ((((_res)->status[3]) & 0x80) == 0)
#define RES_CHIPID(_res) (((_res)->status[3]) & 0x1f)
#define RES_CORE(_res) ((_res)->status[2])
#define RES_TASK(_res) ((int)((_res)->status[1]) * 0x100 + (int)((_res)->status[0]))
#define RES_NONCE(_res) u8tou32((_res)->nonce, 0)

/*
 * This is only valid since we avoid using task_id 0 for work
 * However, it isn't really necessary since we only request
 * the number of results the result buffer says it has
 * However, it is a simple failsafe
 */
#define IS_RESULT(_res) ((_res)->status[1] || (_res)->status[0])

struct minion_result {
	uint8_t status[DATA_SIZ];
	uint8_t nonce[DATA_SIZ];
};

#define MINION_RES_DATA_SIZ sizeof(struct minion_result)

/*
 * (MINION_SPI_BUFSIZ - HSIZE()) / MINION_RES_DATA_SIZ
 * less a little bit to round it out
 */
#define MINION_MAX_RES 120

#define MIDSTATE_BYTES 32
#define MERKLE7_OFFSET 64
#define MERKLE_BYTES 12

#define MINION_MAX_TASK_ID 0xffff

struct minion_que {
	uint8_t task_id[2];
	uint8_t reserved[2];
	uint8_t midstate[MIDSTATE_BYTES];
	uint8_t merkle7[DATA_SIZ];
	uint8_t ntime[DATA_SIZ];
	uint8_t bits[DATA_SIZ];
};

/*
 * Max time to wait before checking the task list
 * Required, since only urgent tasks trigger an immediate check
 * TODO: ? for 2TH/s
 */
#define MINION_TASK_mS 8

/*
 * Max time to wait before checking the result list for nonces
 * This can be long since it's only a failsafe
 * cgsem_post is always sent if there are nonces ready to check
 */
#define MINION_NONCE_mS 888

// Number of results to make a GPIO interrupt
//#define MINION_RESULT_INT_SIZE 1
#define MINION_RESULT_INT_SIZE 2

/*
 * Max time to wait before checking for results
 * The interrupt doesn't occur until MINION_RESULT_INT_SIZE results are found
 * See comment in minion_spi_reply() at poll()
 */
#define MINION_REPLY_mS 88

/*
 * Max time to wait before returning the amount of work done
 * A result interrupt will send a trigger for this also
 * See comment in minion_scanwork()
 * This avoids the cgminer master work loop spinning doing nothing
 */
#define MINION_SCAN_mS 88

// *** Work lists: generated, queued for a chip, sent to chip
typedef struct work_item {
	struct work *work;
	uint32_t task_id;
	struct timeval sent;
	int nonces;
	bool urgent;
	bool stale; // if stale, don't decrement que/chipwork when discarded
	bool rolled;
	int errors; // uncertain since the error could mean task_id is wrong
	struct timeval created; // when work was generated
	uint64_t ioseq;
} WORK_ITEM;

#define ALLOC_WORK_ITEMS 4096
#define LIMIT_WORK_ITEMS 0

// *** Task queue ready to be sent
typedef struct task_item {
	uint64_t tid;
	uint8_t chip;
	bool write;
	uint8_t address;
	uint32_t task_id;
	uint32_t wsiz;
	uint32_t osiz;
	uint32_t rsiz;
	uint8_t wbuf[MINION_BUFSIZ];
	uint8_t obuf[MINION_BUFSIZ];
	uint8_t rbuf[MINION_BUFSIZ];
	int reply;
	bool urgent;
	uint8_t work_state;
	struct work *work;
	K_ITEM *witem;
	uint64_t ioseq;
} TASK_ITEM;

#define ALLOC_TASK_ITEMS 256
#define LIMIT_TASK_ITEMS 0

// *** Results queue ready to be checked
typedef struct res_item {
	int chip;
	int core;
	uint32_t task_id;
	uint32_t nonce;
	struct timeval when;
	/*
	 * Only once per task_id if no nonces were found
	 * Sent with core = 0
	 * However, currently it always sends it at the end of every task
	 * TODO: code assumes it doesn't - change later when we
	 * see what the final hardware does (minor code performance gain)
	 */
	bool no_nonce;
	// If we requested the result twice:
	bool another;
	uint32_t task_id2;
	uint32_t nonce2;
} RES_ITEM;

#define ALLOC_RES_ITEMS 256
#define LIMIT_RES_ITEMS 0

// *** Per chip nonce history
typedef struct hist_item {
	struct timeval when;
} HIST_ITEM;

#define ALLOC_HIST_ITEMS 4096
#define LIMIT_HIST_ITEMS 0

// How much history to keep (5min)
#define MINION_HISTORY_s 300
// History required to decide a reset at MINION_FREQ_DEF Mhz
#define MINION_RESET_s 10
// How many times to reset before changing Freq
// This doesn't include the secondary higher % check
#define MINION_RESET_COUNT 6

// To enable the 2nd check
static bool second_check = true;
// Longer time lapse to expect the higher %
// This intercepts a slow GHs drop earlier
#define MINION_RESET2_s 60

#if (MINION_RESET_s > MINION_HISTORY_s)
#error "MINION_RESET_s can't be greater than MINION_HISTORY_s"
#endif

#define FREQ_DELAY(freq) ((float)(MINION_RESET_s * MINION_FREQ_DEF) / (freq))

#if (MINION_RESET2_s > MINION_HISTORY_s)
#error "MINION_RESET2_s can't be greater than MINION_HISTORY_s"
#endif

// FREQ2_DELAY(MINION_FREQ_MIN) = FREQ2_FACTOR * MINION_RESET2_s
#define FREQ2_FACTOR 1.5

#define FREQ2_DELAY(freq) ((1.0 + (float)((freq - MINION_FREQ_DEF) * (1 - FREQ2_FACTOR)) / \
				(float)(MINION_FREQ_DEF - MINION_FREQ_MIN)) * MINION_RESET2_s)

#if (MINION_RESET2_s <= MINION_RESET_s)
#error "MINION_RESET2_s must be greater than MINION_RESET_s"
#endif

/* If there was no reset for this long, clear the reset history
 * (except the last one) since this means the current clock is ok
 * with rare resets */
#define MINION_CLR_s 300

#if (MINION_CLR_s <= MINION_RESET2_s)
#error "MINION_CLR_s must be greater than MINION_RESET2_s"
#endif

// History must be always generated for the reset check
#define MINION_MAX_RESET_CHECK 2

/* Floating point reset settings required for the code to work properly
 * Basically: RESET2 must be after RESET and CLR must be after RESET2 */
static void define_test()
{
	float test;

	if (MINION_RESET2_PERCENT <= MINION_RESET_PERCENT) {
		quithere(1, "MINION_RESET2_PERCENT=%f must be "
			    "> MINION_RESET_PERCENT=%f",
			    MINION_RESET2_PERCENT, MINION_RESET_PERCENT);
	}

	test = FREQ_DELAY(MINION_FREQ_MIN);
	if (test >= MINION_HISTORY_s) {
		quithere(1, "FREQ_DELAY(MINION_FREQ_MIN)=%f must be "
			    "< MINION_HISTORY_s=%d",
			    test, MINION_HISTORY_s);
	}

	if (MINION_CLR_s <= test) {
		quithere(1, "MINION_CLR_s=%d must be > "
			    "FREQ_DELAY(MINION_FREQ_MIN)=%f",
			    MINION_CLR_s, test);
	}

	if (FREQ2_FACTOR <= 1.0)
		quithere(1, "FREQ2_FACTOR=%f must be > 1.0", FREQ2_FACTOR);


	test = FREQ2_DELAY(MINION_FREQ_MIN);
	if (test >= MINION_HISTORY_s) {
		quithere(1, "FREQ2_DELAY(MINION_FREQ_MIN)=%f must be "
			    "< MINION_HISTORY_s=%d",
			    test, MINION_HISTORY_s);
	}

	if (MINION_CLR_s <= test) {
		quithere(1, "MINION_CLR_s=%d must be > "
			    "FREQ2_DELAY(MINION_FREQ_MIN)=%f",
			    MINION_CLR_s, test);
	}
}

// *** Chip freq/MHs performance history
typedef struct perf_item {
	double elapsed;
	uint64_t nonces;
	uint32_t freq;
	double ghs;
	struct timeval when;
} PERF_ITEM;

#define ALLOC_PERF_ITEMS 128
#define LIMIT_PERF_ITEMS 0

// *** 0xff error history
typedef struct xff_item {
	time_t when;
} XFF_ITEM;

#define ALLOC_XFF_ITEMS 100
#define LIMIT_XFF_ITEMS 100

#define DATA_WORK(_item) ((WORK_ITEM *)(_item->data))
#define DATA_TASK(_item) ((TASK_ITEM *)(_item->data))
#define DATA_RES(_item) ((RES_ITEM *)(_item->data))
#define DATA_HIST(_item) ((HIST_ITEM *)(_item->data))
#define DATA_PERF(_item) ((PERF_ITEM *)(_item->data))
#define DATA_XFF(_item) ((XFF_ITEM *)(_item->data))

// Set this to 1 to enable iostats processing
// N.B. it slows down mining
#define DO_IO_STATS 0

#if DO_IO_STATS
#define IO_STAT_NOW(_tv) cgtime(_tv)
#define IO_STAT_STORE(_sta, _fin, _lsta, _lfin, _tsd, _buf, _siz, _reply, _ioc) \
		do { \
			double _diff, _ldiff, _lwdiff, _1time; \
			int _off; \
			_diff = us_tdiff(_fin, _sta); \
			_ldiff = us_tdiff(_lfin, _lsta); \
			_lwdiff = us_tdiff(_sta, _lsta); \
			_1time = us_tdiff(_tsd, _lfin); \
			_off = (int)(_buf[1]) + (_reply >= 0 ? 0 : 0x100); \
			minioninfo->summary.count++; \
			minioninfo->summary.tsd += _1time; \
			minioninfo->iostats[_off].count++; \
			minioninfo->iostats[_off].tsd += _1time; \
			if (_diff <= 0) { \
				minioninfo->summary.zero_delay++; \
				minioninfo->iostats[_off].zero_delay++; \
			} else { \
				minioninfo->summary.total_delay += _diff; \
				if (minioninfo->summary.max_delay < _diff) \
					minioninfo->summary.max_delay = _diff; \
				if (minioninfo->summary.min_delay == 0 || \
				    minioninfo->summary.min_delay > _diff) \
					minioninfo->summary.min_delay = _diff; \
				minioninfo->iostats[_off].total_delay += _diff; \
				if (minioninfo->iostats[_off].max_delay < _diff) \
					minioninfo->iostats[_off].max_delay = _diff; \
				if (minioninfo->iostats[_off].min_delay == 0 || \
				    minioninfo->iostats[_off].min_delay > _diff) \
					minioninfo->iostats[_off].min_delay = _diff; \
			} \
			if (_ldiff <= 0) { \
				minioninfo->summary.zero_dlock++; \
				minioninfo->iostats[_off].zero_dlock++; \
			} else { \
				minioninfo->summary.total_dlock += _ldiff; \
				if (minioninfo->summary.max_dlock < _ldiff) \
					minioninfo->summary.max_dlock = _ldiff; \
				if (minioninfo->summary.min_dlock == 0 || \
				    minioninfo->summary.min_dlock > _ldiff) \
					minioninfo->summary.min_dlock = _ldiff; \
				minioninfo->iostats[_off].total_dlock += _ldiff; \
				if (minioninfo->iostats[_off].max_dlock < _ldiff) \
					minioninfo->iostats[_off].max_dlock = _ldiff; \
				if (minioninfo->iostats[_off].min_dlock == 0 || \
				    minioninfo->iostats[_off].min_dlock > _ldiff) \
					minioninfo->iostats[_off].min_dlock = _ldiff; \
			} \
			minioninfo->summary.total_dlwait += _lwdiff; \
			minioninfo->iostats[_off].total_dlwait += _lwdiff; \
			if (_siz == 0) { \
				minioninfo->summary.zero_bytes++; \
				minioninfo->iostats[_off].zero_bytes++; \
			} else { \
				minioninfo->summary.total_bytes += _siz; \
				if (minioninfo->summary.max_bytes < _siz) \
					minioninfo->summary.max_bytes = _siz; \
				if (minioninfo->summary.min_bytes == 0 || \
				    minioninfo->summary.min_bytes > _siz) \
					minioninfo->summary.min_bytes = _siz; \
				minioninfo->iostats[_off].total_bytes += _siz; \
				if (minioninfo->iostats[_off].max_bytes < _siz) \
					minioninfo->iostats[_off].max_bytes = _siz; \
				if (minioninfo->iostats[_off].min_bytes == 0 || \
				    minioninfo->iostats[_off].min_bytes > _siz) \
					minioninfo->iostats[_off].min_bytes = _siz; \
			} \
		} while (0);

typedef struct iostat {
	uint64_t count; // total ioctl()

	double total_delay; // total elapsed ioctl()
	double min_delay;
	double max_delay;
	uint64_t zero_delay; // how many had <= 0 delay

	// Above but including locking
	double total_dlock;
	double min_dlock;
	double max_dlock;
	uint64_t zero_dlock;

	// Total time waiting to get lock
	double total_dlwait;

	// these 3 fields are ignored for now since all are '1'
	uint64_t total_ioc; // SPI_IOC_MESSAGE(x)
	uint64_t min_ioc;
	uint64_t max_ioc;

	uint64_t total_bytes; // ioctl() bytes
	uint64_t min_bytes;
	uint64_t max_bytes;
	uint64_t zero_bytes; // how many had siz == 0

	double tsd; // total doing one extra cgtime() each time
} IOSTAT;
#else
#define IO_STAT_NOW(_tv)
#define IO_STAT_STORE(_sta, _fin, _lsta, _lfin, _tsd, _buf, _siz, _reply, _ioc)
#endif

static double time_bands[] = { 0.1, 0.5, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0 };
#define TIME_BANDS ((int)(sizeof(time_bands)/sizeof(double)))

struct minion_info {
	struct thr_info *thr;
	struct thr_info spiw_thr;
	struct thr_info spir_thr;
	struct thr_info res_thr;

	pthread_mutex_t spi_lock;
	pthread_mutex_t sta_lock;

	cgsem_t task_ready;
	cgsem_t nonce_ready;
	cgsem_t scan_work;

	volatile unsigned *gpio;

	int spifd;
	char gpiointvalue[64];
	int gpiointfd;

	// I/O or seconds
	bool spi_reset_io;
	int spi_reset_count;
	time_t last_spi_reset;
	uint64_t spi_resets;

	// TODO: need to track disabled chips - done?
	int chips;
	bool has_chip[MINION_CHIPS];
	int init_temp[MINION_CHIPS];
	uint8_t init_cores[MINION_CHIPS][DATA_SIZ*MINION_CORE_REPS];

	uint8_t chipid[MINION_CHIPS]; // Chip Number
	int chip_pin[MINION_CHIPS];

	uint64_t ioseq;
	uint32_t next_task_id;

	// Stats
	uint64_t chip_nonces[MINION_CHIPS];
	uint64_t chip_nononces[MINION_CHIPS];
	uint64_t chip_good[MINION_CHIPS];
	uint64_t chip_bad[MINION_CHIPS];
	uint64_t chip_err[MINION_CHIPS];
	uint64_t chip_dup[MINION_CHIPS];
	uint64_t core_good[MINION_CHIPS][MINION_CORES+1];
	uint64_t core_bad[MINION_CHIPS][MINION_CORES+1];

	uint32_t chip_core_ena[MINION_CORE_REPS][MINION_CHIPS];
	uint32_t chip_core_act[MINION_CORE_REPS][MINION_CHIPS];

	struct minion_status chip_status[MINION_CHIPS];

	uint64_t interrupts;
	uint64_t result_interrupts;
	uint64_t command_interrupts;
	char last_interrupt[64];

	pthread_mutex_t nonce_lock;
	uint64_t new_nonces;

	uint64_t ok_nonces;
	uint64_t untested_nonces;
	uint64_t tested_nonces;

	uint64_t work_unrolled;
	uint64_t work_rolled;

	uint64_t spi_errors;
	uint64_t fifo_spi_errors[MINION_CHIPS];
	uint64_t res_spi_errors[MINION_CHIPS];
	uint64_t use_res2[MINION_CHIPS];

	uint64_t tasks_failed[MINION_CHIPS];
	uint64_t tasks_recovered[MINION_CHIPS];
	uint64_t nonces_failed[MINION_CHIPS];
	uint64_t nonces_recovered[MINION_CHIPS];
	struct timeval last_reset[MINION_CHIPS];
	double do_reset[MINION_CHIPS];
	bool flag_reset[MINION_CHIPS];

	// Work items
	K_LIST *wfree_list;
	K_STORE *wwork_list;
	K_STORE *wstale_list;
	K_STORE *wque_list[MINION_CHIPS];
	K_STORE *wchip_list[MINION_CHIPS];
	uint64_t wwork_flushed;
	uint64_t wque_flushed;
	uint64_t wchip_staled;

	// Task list
	K_LIST *tfree_list;
	K_STORE *task_list;
	K_STORE *treply_list;

	uint64_t next_tid;

	// Nonce replies
	K_LIST *rfree_list;
	K_STORE *rnonce_list;

	struct timeval last_did;

	// Nonce history
	K_LIST *hfree_list;
	K_STORE *hchip_list[MINION_CHIPS];

	int history_gen;
	struct timeval chip_chk;
	struct timeval chip_rpt;
	double history_ghs[MINION_CHIPS];
	// Point in history for MINION_RESET_s
	int reset_time[MINION_CHIPS];
	K_ITEM *reset_mark[MINION_CHIPS];
	int reset_count[MINION_CHIPS];
	// Point in history for MINION_RESET2_s
	int reset2_time[MINION_CHIPS];
	K_ITEM *reset2_mark[MINION_CHIPS];
	int reset2_count[MINION_CHIPS];

	// Performance history
	K_LIST *pfree_list;
	K_STORE *p_list[MINION_CHIPS];

	// 0xff history
	K_LIST *xfree_list;
	K_STORE *xff_list;
	time_t last_power_cycle;
	uint64_t power_cycles;
	time_t last_xff;
	uint64_t xffs;
	uint64_t last_displayed_xff;

	// Gets reset to zero each time it is used in reporting
	int res_err_count[MINION_CHIPS];

#if DO_IO_STATS
	// Total
	IOSTAT summary;

	// Two for each command plus wasted extras i.e. direct/fast lookup
	// No error uses 0x0 to 0xff, error uses 0x100 to 0x1ff
	IOSTAT iostats[0x200];
#endif

	// Stats on how long work is waiting to move from wwork_list to wque_list
	uint64_t que_work;
	double que_time;
	double que_min;
	double que_max;
	uint64_t que_bands[TIME_BANDS+1];

	// From wwork_list to txrx
	uint64_t wt_work;
	double wt_time;
	double wt_min;
	double wt_max;
	uint64_t wt_bands[TIME_BANDS+1];

	bool lednow[MINION_CHIPS];
	bool setled[MINION_CHIPS];

	// When changing the frequency don't modify 'anything'
	bool changing[MINION_CHIPS];
	int init_freq[MINION_CHIPS];
	int want_freq[MINION_CHIPS];
	uint32_t freqsent[MINION_CHIPS];
	struct timeval lastfreq[MINION_CHIPS];
	int freqms[MINION_CHIPS];

	bool initialised;
};

#if MINION_ROCKCHIP == 1
static bool minion_toggle_gpio(struct cgpu_info *minioncgpu, int gpionum)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	char pindir[64], ena[64], pin[8], dir[64];
	char gpiointvalue[64];
	struct stat st;
	int file, err, chip;
	ssize_t ret;

	snprintf(pindir, sizeof(pindir), MINION_GPIO_SYS MINION_GPIO_PIN, gpionum);
	memset(&st, 0, sizeof(st));

	if (stat(pindir, &st) == 0) { // already exists
		if (!S_ISDIR(st.st_mode)) {
			applog(LOG_ERR, "%s: failed1 to enable GPIO pin %d"
					" - not a directory",
					minioncgpu->drv->dname, gpionum);
			return false;
		}
	} else {
		snprintf(ena, sizeof(ena), MINION_GPIO_SYS MINION_GPIO_ENA);
		file = open(ena, O_WRONLY | O_SYNC);
		if (file == -1) {
			applog(LOG_ERR, "%s: failed2 to export GPIO pin %d (%d)"
					" - you need to be root?",
					minioncgpu->drv->dname,
					gpionum, errno);
			return false;
		}
		snprintf(pin, sizeof(pin), MINION_GPIO_ENA_VAL, gpionum);
		ret = write(file, pin, (size_t)strlen(pin));
		if (ret != (ssize_t)strlen(pin)) {
			if (ret < 0)
				err = errno;
			else
				err = (int)ret;
			close(file);
			applog(LOG_ERR, "%s: failed3 to export GPIO pin %d (%d:%d)",
					minioncgpu->drv->dname,
					gpionum, err, (int)strlen(pin));
			return false;
		}
		close(file);

		// Check again if it exists
		memset(&st, 0, sizeof(st));
		if (stat(pindir, &st) != 0) {
			applog(LOG_ERR, "%s: failed4 to export GPIO pin %d (%d)",
					minioncgpu->drv->dname,
					gpionum, errno);
			return false;
		}
	}

	// Set the pin attributes
	// Direction
	snprintf(dir, sizeof(dir), MINION_GPIO_SYS MINION_GPIO_PIN MINION_GPIO_DIR, gpionum);
	file = open(dir, O_WRONLY | O_SYNC);
	if (file == -1) {
		applog(LOG_ERR, "%s: failed5 to configure GPIO pin %d (%d)"
				" - you need to be root?",
				minioncgpu->drv->dname,
				gpionum, errno);
		return false;
	}
	ret = write(file, MINION_GPIO_DIR_WRITE, sizeof(MINION_GPIO_DIR_WRITE)-1);
	if (ret != sizeof(MINION_GPIO_DIR_WRITE)-1) {
		if (ret < 0)
			err = errno;
		else
			err = (int)ret;
		close(file);
		applog(LOG_ERR, "%s: failed6 to configure GPIO pin %d (%d:%d)",
				minioncgpu->drv->dname, gpionum,
				err, (int)sizeof(MINION_GPIO_DIR_WRITE)-1);
		return false;
	}
	close(file);

	// Open it
	snprintf(gpiointvalue, sizeof(gpiointvalue),
		 MINION_GPIO_SYS MINION_GPIO_PIN MINION_GPIO_VALUE,
		 gpionum);
	int fd = open(gpiointvalue, O_WRONLY);
	if (fd == -1) {
		applog(LOG_ERR, "%s: failed7 to access GPIO pin %d (%d)",
				minioncgpu->drv->dname,
				gpionum, errno);
		return false;
	}

	ret = write(fd, MINION_CHIP_OFF, sizeof(MINION_CHIP_OFF)-1);
	if (ret != sizeof(MINION_CHIP_OFF)-1) {
		close(fd);
		applog(LOG_ERR, "%s: failed8 to toggle off GPIO pin %d (%d:%d)",
				minioncgpu->drv->dname,
				gpionum, (int)ret, errno);
		return false;
	}

	cgsleep_ms(MINION_CHIP_DELAY);

	ret = write(fd, MINION_CHIP_ON, sizeof(MINION_CHIP_ON)-1);
	if (ret != sizeof(MINION_CHIP_OFF)-1) {
		close(fd);
		applog(LOG_ERR, "%s: failed9 to toggle on GPIO pin %d (%d:%d)",
				minioncgpu->drv->dname,
				gpionum, (int)ret, errno);
		return false;
	}

	close(fd);
	minioninfo->last_power_cycle = time(NULL);
	minioninfo->power_cycles++;
	// Reset all chip led counters
	for (chip = 0; chip < (int)MINION_CHIPS; chip++) {
		if (minioninfo->has_chip[chip])
			minioninfo->chip_status[chip].first_nonce.tv_sec = 0L;
	}
	return true;
}
#endif

static void ready_work(struct cgpu_info *minioncgpu, struct work *work, bool rolled)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	K_ITEM *item = NULL;

	K_WLOCK(minioninfo->wfree_list);

	item = k_unlink_head(minioninfo->wfree_list);

	DATA_WORK(item)->work = work;
	DATA_WORK(item)->task_id = 0;
	memset(&(DATA_WORK(item)->sent), 0, sizeof(DATA_WORK(item)->sent));
	DATA_WORK(item)->nonces = 0;
	DATA_WORK(item)->urgent = false;
	DATA_WORK(item)->rolled = rolled;
	DATA_WORK(item)->errors = 0;
	cgtime(&(DATA_WORK(item)->created));

	k_add_head(minioninfo->wwork_list, item);

	K_WUNLOCK(minioninfo->wfree_list);
}

static bool oldest_nonce(struct cgpu_info *minioncgpu, int *chip, int *core, uint32_t *task_id,
			 uint32_t *nonce, bool *no_nonce, struct timeval *when,
			 bool *another, uint32_t *task_id2, uint32_t *nonce2)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	K_ITEM *item = NULL;
	bool found = false;

	K_WLOCK(minioninfo->rnonce_list);

	item = k_unlink_tail(minioninfo->rnonce_list);
	if (item) {
		found = true;
		*chip = DATA_RES(item)->chip;
		*core = DATA_RES(item)->core;
		*task_id = DATA_RES(item)->task_id;
		*nonce = DATA_RES(item)->nonce;
		*no_nonce = DATA_RES(item)->no_nonce;
		memcpy(when, &(DATA_RES(item)->when), sizeof(*when));
		*another = DATA_RES(item)->another;
		*task_id2 = DATA_RES(item)->task_id2;
		*nonce2 = DATA_RES(item)->nonce2;

		k_free_head(minioninfo->rfree_list, item);
	}

	K_WUNLOCK(minioninfo->rnonce_list);

	return found;
}

static const char *addr2txt(uint8_t addr)
{
	switch (addr) {
		case READ_ADDR(MINION_SYS_CHIP_SIG):
			return "RChipSig";
		case READ_ADDR(MINION_SYS_CHIP_STA):
			return "RChipSta";
		case WRITE_ADDR(MINION_SYS_SPI_LED):
			return "WLed";
		case WRITE_ADDR(MINION_SYS_MISC_CTL):
			return "WMiscCtrl";
		case WRITE_ADDR(MINION_SYS_RSTN_CTL):
			return "WResetCtrl";
		case READ_ADDR(MINION_SYS_FIFO_STA):
			return "RFifoSta";
		case READ_ADDR(MINION_CORE_ENA0_31):
			return "RCoreEna0-31";
		case WRITE_ADDR(MINION_CORE_ENA0_31):
			return "WCoreEna0-31";
		case READ_ADDR(MINION_CORE_ENA32_63):
			return "RCoreEna32-63";
		case WRITE_ADDR(MINION_CORE_ENA32_63):
			return "WCoreEna32-63";
		case READ_ADDR(MINION_CORE_ENA64_95):
			return "RCoreEna64-95";
		case WRITE_ADDR(MINION_CORE_ENA64_95):
			return "WCoreEna64-95";
		case READ_ADDR(MINION_CORE_ENA96_98):
			return "RCoreEna96-98";
		case WRITE_ADDR(MINION_CORE_ENA96_98):
			return "WCoreEna96-98";
		case READ_ADDR(MINION_CORE_ACT0_31):
			return "RCoreAct0-31";
		case READ_ADDR(MINION_CORE_ACT32_63):
			return "RCoreAct32-63";
		case READ_ADDR(MINION_CORE_ACT64_95):
			return "RCoreAct64-95";
		case READ_ADDR(MINION_CORE_ACT96_98):
			return "RCoreAct96-98";
		case READ_ADDR(MINION_RES_DATA):
			return "RResData";
		case READ_ADDR(MINION_RES_PEEK):
			return "RResPeek";
		case WRITE_ADDR(MINION_QUE_0):
			return "WQueWork";
		case READ_ADDR(MINION_NONCE_START):
			return "RNonceStart";
		case WRITE_ADDR(MINION_NONCE_START):
			return "WNonceStart";
		case READ_ADDR(MINION_NONCE_RANGE):
			return "RNonceRange";
		case WRITE_ADDR(MINION_NONCE_RANGE):
			return "WNonceRange";
		case READ_ADDR(MINION_SYS_INT_STA):
			return "RIntSta";
		case WRITE_ADDR(MINION_SYS_INT_ENA):
			return "WIntEna";
		case WRITE_ADDR(MINION_SYS_INT_CLR):
			return "WIntClear";
		case WRITE_ADDR(MINION_SYS_BUF_TRIG):
			return "WResTrigger";
		case WRITE_ADDR(MINION_SYS_QUE_TRIG):
			return "WCmdTrigger";
		case READ_ADDR(MINION_SYS_TEMP_CTL):
			return "RTempCtrl";
		case WRITE_ADDR(MINION_SYS_TEMP_CTL):
			return "WTempCtrl";
		case READ_ADDR(MINION_SYS_FREQ_CTL):
			return "RFreqCtrl";
		case WRITE_ADDR(MINION_SYS_FREQ_CTL):
			return "WFreqCtrl";
		case READ_ADDR(MINION_SYS_IDLE_CNT):
			return "RIdleCnt";
	}

	// gcc warning if this is in default:
	if (IS_ADDR_READ(addr))
		return "RUnhandled";
	else
		return "WUnhandled";
}

// For display_ioctl()
#define IOCTRL_LOG LOG_WARNING

// For all other debug so it can easily be switched always on
#define MINION_LOG LOG_DEBUG

// For task corruption logging
#define MINTASK_LOG LOG_DEBUG

// Set to 1 for debug
#define MINION_SHOW_IO 0

#define DATA_ALL 2048
#define DATA_OFF 512

#if MINION_SHOW_IO
static void display_ioctl(int reply, uint32_t osiz, uint8_t *obuf, uint32_t rsiz, uint8_t *rbuf)
{
	struct minion_result *res;
	const char *name, *dir, *ex;
	char buf[4096];
	int i, rescount;

	name = addr2txt(obuf[1]);

	if (IS_ADDR_READ(obuf[1]))
		dir = "from";
	else
		dir = "to";

	buf[0] = '\0';
	ex = "";

	switch (obuf[1]) {
		case READ_ADDR(MINION_SYS_CHIP_SIG):
		case READ_ADDR(MINION_SYS_CHIP_STA):
			break;
		case WRITE_ADDR(MINION_SYS_SPI_LED):
		case WRITE_ADDR(MINION_SYS_MISC_CTL):
		case WRITE_ADDR(MINION_SYS_RSTN_CTL):
			if (osiz > HSIZE()) {
				ex = " wrote ";
				__bin2hex(buf, obuf + HSIZE(), osiz - HSIZE());
			} else
				ex = " wrote nothing";
			break;
		default:
			if (IS_ADDR_WRITE(obuf[1])) {
				if (osiz > HSIZE()) {
					ex = " wrote ";
					__bin2hex(buf, obuf + HSIZE(), osiz - HSIZE());
				} else
					ex = " wrote nothing";
			}
			break;
	}

	if (reply < 0) {
		applog(IOCTRL_LOG, "%s %s chipid %d osiz %d%s%s",
				   name, dir, (int)obuf[0], (int)osiz, ex, buf);
		applog(IOCTRL_LOG, "  reply was error %d", reply);
	} else {
		if (IS_ADDR_WRITE(obuf[1])) {
			applog(IOCTRL_LOG, "%s %s chipid %d osiz %d%s%s",
					   name, dir, (int)obuf[0], (int)osiz, ex, buf);
			applog(IOCTRL_LOG, "  write ret was %d", reply);
		} else {
			switch (obuf[1]) {
				case READ_ADDR(MINION_RES_DATA):
					rescount = (int)((float)rsiz / (float)MINION_RES_DATA_SIZ);
					applog(IOCTRL_LOG, "%s %s chipid %d osiz %d%s%s",
							   name, dir, (int)obuf[0], (int)osiz, ex, buf);
					for (i = 0; i < rescount; i++) {
						res = (struct minion_result *)(rbuf + osiz - rsiz + (i * MINION_RES_DATA_SIZ));
						if (!IS_RESULT(res)) {
							applog(IOCTRL_LOG, "  %s reply %d of %d - none", name, i+1, rescount);
						} else {
							__bin2hex(buf, res->nonce, DATA_SIZ);
							applog(IOCTRL_LOG, "  %s reply %d of %d %d(%d) was task 0x%04x"
										   " chipid %d core %d gold %s nonce 0x%s",
									   name, i+1, rescount, reply, rsiz,
									   RES_TASK(res),
									   (int)RES_CHIPID(res),
									   (int)RES_CORE(res),
									   (int)RES_GOLD(res) ? "Y" : "N",
									   buf);
						}
					}
					break;
				case READ_ADDR(MINION_SYS_CHIP_SIG):
				case READ_ADDR(MINION_SYS_CHIP_STA):
				default:
					applog(IOCTRL_LOG, "%s %s chipid %d osiz %d%s%s",
							   name, dir, (int)obuf[0], (int)osiz, ex, buf);
					__bin2hex(buf, rbuf + osiz - rsiz, rsiz);
					applog(IOCTRL_LOG, "  %s reply %d(%d) was %s", name, reply, rsiz, buf);
					break;
			}
		}
	}
}
#endif

#define MINION_UNEXPECTED_TASK -999
#define MINION_OVERSIZE_TASK -998

static void set_pin(struct minion_info *minioninfo, int pin, bool on)
{
	volatile uint32_t *paddr;
	uint32_t value;
	int bcm;

	bcm = minionPins[pin].bcm;

	paddr = minioninfo->gpio + ((on ? BCM2835_GPIO_SET0 : BCM2835_GPIO_CLR0) / 4) + (bcm / 10);

	value = 1 << (bcm % 32);

	*paddr = value;
	*paddr = value;
}

static void init_pins(struct minion_info *minioninfo)
{
	int pin;

	// Initialise all pins high as required
	MINION_PIN_BEFORE;
	for (pin = 0; pin < (int)MINION_PIN_COUNT; pin++) {
		set_pin(minioninfo, pin, true);
		MINION_PIN_SLEEP;
	}
}

#define EXTRA_LOG_IO 0

static bool minion_init_spi(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int bus, int chip, bool reset);

static int __do_ioctl(struct cgpu_info *minioncgpu, struct minion_info *minioninfo,
		      int pin, uint8_t *obuf, uint32_t osiz, uint8_t *rbuf,
		      uint32_t rsiz, uint64_t *ioseq, MINION_FFL_ARGS)
{
	struct spi_ioc_transfer tran;
	bool fail = false, powercycle = false, show = false;
	double lastshow, total;
	K_ITEM *xitem;
	time_t now;
	int ret;
#if MINION_SHOW_IO
	char dataw[DATA_ALL], datar[DATA_ALL];
#endif

#if DO_IO_STATS
	struct timeval sta, fin, lsta, lfin, tsd;
#endif

	if ((int)osiz > MINION_BUFSIZ)
		quitfrom(1, file, func, line, "%s() invalid osiz %u > %d (chip=%d reg=0x%02x)",
				__func__, osiz, MINION_BUFSIZ, (int)(obuf[0]), obuf[1]);

	if (rsiz >= osiz)
		quitfrom(1, file, func, line, "%s() invalid rsiz %u >= osiz %u (chip=%u reg=0x%02x)",
				__func__, rsiz, osiz, (int)(obuf[0]), obuf[1]);

	memset(&obuf[0] + osiz - rsiz, 0xff, rsiz);

#if MINION_SHOW_IO
	// if the a5/5a outside the data change, it means data overrun or corruption
	memset(dataw, 0xa5, sizeof(dataw));
	memset(datar, 0x5a, sizeof(datar));
	memcpy(&dataw[DATA_OFF], &obuf[0], osiz);

	char *buf = bin2hex((unsigned char *)&(dataw[DATA_OFF]), osiz);
	applog(IOCTRL_LOG, "*** %s() pin %d cid %d sending %02x %02x %s %02x %02x",
			   __func__, pin, (int)(dataw[DATA_OFF]),
			   dataw[0], dataw[DATA_OFF-1], buf,
			   dataw[DATA_OFF+osiz], dataw[DATA_ALL-1]);
	free(buf);
#endif

	memset((char *)rbuf, 0x00, osiz);

//	cgsleep_ms(5); // TODO: a delay ... based on the last command? But subtract elapsed
			// i.e. do any commands need a delay after the I/O has completed before the next I/O?

	memset(&tran, 0, sizeof(tran));
	if (osiz < MINION_SPI_BUFSIZ)
		tran.len = osiz;
	else
		return MINION_OVERSIZE_TASK;

	tran.delay_usecs = opt_minion_spiusec;
	tran.speed_hz = MINION_SPI_SPEED;

#if MINION_SHOW_IO
	tran.tx_buf = (uintptr_t)&(dataw[DATA_OFF]);
	tran.rx_buf = (uintptr_t)&(datar[DATA_OFF]);
#else
	tran.tx_buf = (uintptr_t)obuf;
	tran.rx_buf = (uintptr_t)rbuf;
#endif

	IO_STAT_NOW(&lsta);
	mutex_lock(&(minioninfo->spi_lock));
	if (usepins) {
		// Pin low for I/O
		MINION_PIN_BEFORE;
		set_pin(minioninfo, pin, false);
		MINION_PIN_SLEEP;
	}
	IO_STAT_NOW(&sta);
	ret = ioctl(minioninfo->spifd, SPI_IOC_MESSAGE(1), (void *)&tran);
	*ioseq = minioninfo->ioseq++;
	IO_STAT_NOW(&fin);
	if (usepins) {
		MINION_PIN_AFTER;
		// Pin back high after I/O
		set_pin(minioninfo, pin, true);
	}
	now = time(NULL);
	if (ret >= 0 && rbuf[0] == 0xff && rbuf[ret-1] == 0xff &&
	    (obuf[1] == READ_ADDR(MINION_RES_DATA) || obuf[1] == READ_ADDR(MINION_SYS_FIFO_STA))) {
		int i;
		fail = true;
		for (i = 1; i < ret-2; i++) {
			if (rbuf[i] != 0xff) {
				fail = false;
				break;
			}
		}
		if (fail) {
			powercycle = show = false;
			minioninfo->xffs++;
			minioninfo->last_xff = now;

			if (minioninfo->xfree_list->count > 0)
				xitem = k_unlink_head(minioninfo->xfree_list);
			else
				xitem = k_unlink_tail(minioninfo->xff_list);
			DATA_XFF(xitem)->when = now;
			if (!minioninfo->xff_list->head)
				show = true;
			else {
				// if !changing and xff_list is full
				if (!minioninfo->changing[obuf[0]] &&
				    minioninfo->xfree_list->count == 0) {
					total = DATA_XFF(xitem)->when -
						DATA_XFF(minioninfo->xff_list->tail)->when;
					if (total <= MINION_POWER_TIME) {
						powercycle = true;
						// Discard the history
						k_list_transfer_to_head(minioninfo->xff_list,
									minioninfo->xfree_list);
						k_add_head(minioninfo->xfree_list, xitem);
						xitem = NULL;
					}
				}

				if (!powercycle) {
					lastshow = DATA_XFF(xitem)->when -
						   DATA_XFF(minioninfo->xff_list->head)->when;
					show = (lastshow >= 5);
				}
			}
			if (xitem)
				k_add_head(minioninfo->xff_list, xitem);

#if MINION_ROCKCHIP == 1
			if (powercycle)
				minion_toggle_gpio(minioncgpu, MINION_POWERCYCLE_GPIO);
#endif
			minion_init_spi(minioncgpu, minioninfo, 0, 0, true);
		}
	} else if (minioninfo->spi_reset_count) {
		if (minioninfo->spi_reset_io) {
			if (*ioseq > 0 && (*ioseq % minioninfo->spi_reset_count) == 0)
				minion_init_spi(minioncgpu, minioninfo, 0, 0, true);
		} else {
			if (minioninfo->last_spi_reset == 0)
				minioninfo->last_spi_reset = now;
			else {
				if ((now - minioninfo->last_spi_reset) >= minioninfo->spi_reset_count)
					minion_init_spi(minioncgpu, minioninfo, 0, 0, true);
					minioninfo->last_spi_reset = now;
			}
		}
	}
	if (opt_minion_spidelay)
		cgsleep_ms(opt_minion_spidelay);
	mutex_unlock(&(minioninfo->spi_lock));
	IO_STAT_NOW(&lfin);
	IO_STAT_NOW(&tsd);

	IO_STAT_STORE(&sta, &fin, &lsta, &lfin, &tsd, obuf, osiz, ret, 1);

	if (fail) {
		if (powercycle) {
			applog(LOG_ERR, "%s%d: power cycle ioctl %"PRIu64" (%"PRIu64")",
					minioncgpu->drv->name, minioncgpu->device_id, *ioseq,
					minioninfo->xffs - minioninfo->last_displayed_xff);
			minioninfo->last_displayed_xff = minioninfo->xffs;
		} else if (show) {
			char *what = "unk";
			switch (obuf[1]) {
				case READ_ADDR(MINION_RES_DATA):
					what = "nonce";
					break;
				case READ_ADDR(MINION_SYS_FIFO_STA):
					what = "fifo";
					break;
			}
			applog(LOG_ERR, "%s%d: reset ioctl %"PRIu64" %s all 0xff (%"PRIu64")",
					minioncgpu->drv->name, minioncgpu->device_id,
					*ioseq, what, minioninfo->xffs - minioninfo->last_displayed_xff);
			minioninfo->last_displayed_xff = minioninfo->xffs;
		}
	}

#if MINION_SHOW_IO
	if (ret > 0) {
		buf = bin2hex((unsigned char *)&(datar[DATA_OFF]), ret);
		applog(IOCTRL_LOG, "*** %s() reply %d = pin %d cid %d %02x %02x %s %02x %02x",
				   __func__, ret, pin, (int)(dataw[DATA_OFF]),
				   datar[0], datar[DATA_OFF-1], buf,
				   datar[DATA_OFF+osiz], datar[DATA_ALL-1]);
		free(buf);
	} else
		applog(LOG_ERR, "*** %s() reply = %d", __func__, ret);

	memcpy(&rbuf[0], &datar[DATA_OFF], osiz);

	display_ioctl(ret, osiz, (uint8_t *)(&dataw[DATA_OFF]), rsiz, (uint8_t *)(&datar[DATA_OFF]));
#endif
#if EXTRA_LOG_IO
	if (obuf[1] == READ_ADDR(MINION_RES_PEEK) ||
	    obuf[1] == READ_ADDR(MINION_RES_DATA) ||
	    obuf[1] == READ_ADDR(MINION_SYS_FIFO_STA)) {
		char *uf1, *uf2, c;
		uf1 = bin2hex(obuf, DATA_SIZ);
		uf2 = bin2hex(rbuf, (size_t)ret);
		switch (obuf[1]) {
			case READ_ADDR(MINION_RES_PEEK):
				c = 'P';
				break;
			case READ_ADDR(MINION_RES_DATA):
				c = 'D';
				break;
			case READ_ADDR(MINION_SYS_FIFO_STA):
				c = 'F';
				break;
		}
		applog(LOG_WARNING, "*** ioseq %"PRIu64" cmd %c %s rep %.8s %s",
				    *ioseq, c, uf1, uf2, uf2+8);
		free(uf2);
		free(uf1);
	}
	if (obuf[1] == WRITE_ADDR(MINION_QUE_0)) {
		char *uf;
		uf = bin2hex(obuf, osiz);
		applog(LOG_WARNING, "*** ioseq %"PRIu64" work %s",
				    *ioseq, uf);
		free(uf);
	}
#endif
	return ret;
}

#if 1
#define do_ioctl(_pin, _obuf, _osiz, _rbuf, _rsiz, _ioseq) \
		__do_ioctl(minioncgpu, minioninfo, _pin, _obuf, _osiz, _rbuf, \
			   _rsiz, _ioseq, MINION_FFL_HERE)
#else
#define do_ioctl(_pin, _obuf, _osiz, _rbuf, _rsiz, _ioseq) \
		_do_ioctl(minioninfo, _pin, _obuf, _osiz, _rbuf, \
			  _rsiz, _ioseq, MINION_FFL_HERE)
// This sends an expected to work, SPI command before each SPI command
static int _do_ioctl(struct minion_info *minioninfo, int pin, uint8_t *obuf, uint32_t osiz, uint8_t *rbuf, uint32_t rsiz, uint64_t *ioseq, MINION_FFL_ARGS)
{
	struct minion_header *head;
	uint8_t buf1[MINION_BUFSIZ];
	uint8_t buf2[MINION_BUFSIZ];
	uint32_t siz;

	head = (struct minion_header *)buf1;
	head->chipid = 1; // Needs to be set to a valid chip
	head->reg = READ_ADDR(MINION_SYS_FIFO_STA);
	SET_HEAD_SIZ(head, DATA_SIZ);
	siz = HSIZE() + DATA_SIZ;
	__do_ioctl(minioncgpu, minioninfo, pin, buf1, siz, buf2, MINION_CORE_SIZ, ioseq, MINION_FFL_PASS);

	return __do_ioctl(minioncgpu, minioninfo, pin, obuf, osiz, rbuf, rsiz, ioseq, MINION_FFL_PASS);
}
#endif

static bool _minion_txrx(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, TASK_ITEM *task, MINION_FFL_ARGS)
{
	struct minion_header *head;

	head = (struct minion_header *)(task->obuf);
	head->chipid = minioninfo->chipid[task->chip];
	if (task->write)
		SET_HEAD_WRITE(head, task->address);
	else
		SET_HEAD_READ(head, task->address);

	SET_HEAD_SIZ(head, task->wsiz + task->rsiz);

	if (task->wsiz)
		memcpy(&(head->data[0]), task->wbuf, task->wsiz);
	task->osiz = HSIZE() + task->wsiz + task->rsiz;

	task->reply = do_ioctl(CHIP_PIN(task->chip), task->obuf, task->osiz, task->rbuf, task->rsiz,
			       &(task->ioseq));
	if (task->reply < 0) {
		applog(LOG_ERR, "%s%d: chip=%d ioctl failed reply=%d err=%d" MINION_FFL,
				minioncgpu->drv->name, minioncgpu->device_id,
				task->chip, task->reply, errno, MINION_FFL_PASS);
	} else if (task->reply < (int)(task->osiz)) {
		applog(LOG_ERR, "%s%d: chip=%d ioctl failed to write %d only wrote %d (err=%d)" MINION_FFL,
				minioncgpu->drv->name, minioncgpu->device_id,
				task->chip, (int)(task->osiz), task->reply, errno, MINION_FFL_PASS);
	}

	return (task->reply >= (int)(task->osiz));
}

// Only for DATA_SIZ commands
static int build_cmd(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int chip, uint8_t reg, uint8_t *rbuf, uint32_t rsiz, uint8_t *data)
{
	struct minion_header *head;
	uint8_t wbuf[MINION_BUFSIZ];
	uint32_t wsiz;
	uint64_t ioseq;
	int reply;

	head = (struct minion_header *)wbuf;
	head->chipid = minioninfo->chipid[chip];
	head->reg = reg;
	SET_HEAD_SIZ(head, DATA_SIZ);

	head->data[0] = data[0];
	head->data[1] = data[1];
	head->data[2] = data[2];
	head->data[3] = data[3];

	wsiz = HSIZE() + DATA_SIZ;
	reply = do_ioctl(CHIP_PIN(chip), wbuf, wsiz, rbuf, rsiz, &ioseq);

	if (reply != (int)wsiz) {
		applog(LOG_ERR, "%s: chip %d %s returned %d (should be %d)",
				minioncgpu->drv->dname, chip,
				addr2txt(head->reg),
				reply, (int)wsiz);
	}

	return reply;
}

static void set_freq(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int chip, int freq)
{
	uint8_t rbuf[MINION_BUFSIZ];
	uint8_t data[4];
	uint32_t value;
	__maybe_unused int reply;

	freq /= MINION_FREQ_FACTOR;
	if (freq < MINION_FREQ_FACTOR_MIN)
		freq = MINION_FREQ_FACTOR_MIN;
	if (freq > MINION_FREQ_FACTOR_MAX)
		freq = MINION_FREQ_FACTOR_MAX;
	value = minion_freq[freq];
	data[0] = (uint8_t)(value & 0xff);
	data[1] = (uint8_t)(((value & 0xff00) >> 8) & 0xff);
	data[2] = (uint8_t)(((value & 0xff0000) >> 16) & 0xff);
	data[3] = (uint8_t)(((value & 0xff000000) >> 24) & 0xff);

	minioninfo->freqsent[chip] = value;

	reply = build_cmd(minioncgpu, minioninfo,
			  chip, WRITE_ADDR(MINION_SYS_FREQ_CTL),
			  rbuf, 0, data);

	cgtime(&(minioninfo->lastfreq[chip]));
	applog(LOG_DEBUG, "%s%i: chip %d freq %d sec %d usec %d",
			  minioncgpu->drv->name, minioncgpu->device_id,
			  chip, freq,
			  (int)(minioninfo->lastfreq[chip].tv_sec) % 10,
			  (int)(minioninfo->lastfreq[chip].tv_usec));

	// Reset all this info on chip reset or freq change
	minioninfo->reset_time[chip] = (int)FREQ_DELAY(minioninfo->init_freq[chip]);
	if (second_check)
		minioninfo->reset2_time[chip] = (int)FREQ2_DELAY(minioninfo->init_freq[chip]);

	minioninfo->chip_status[chip].first_nonce.tv_sec = 0L;

	// Discard chip history (if there is any)
	if (minioninfo->hfree_list) {
		K_WLOCK(minioninfo->hfree_list);
		k_list_transfer_to_head(minioninfo->hchip_list[chip], minioninfo->hfree_list);
		minioninfo->reset_mark[chip] = NULL;
		minioninfo->reset_count[chip] = 0;
		K_WUNLOCK(minioninfo->hfree_list);
	}
}

static void init_chip(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int chip)
{
	uint8_t rbuf[MINION_BUFSIZ];
	uint8_t data[4];
	__maybe_unused int reply;
	int choice;

	// Complete chip reset
	data[0] = 0x00;
	data[1] = 0x00;
	data[2] = 0xa5;
	data[3] = 0xf5;

	reply = build_cmd(minioncgpu, minioninfo,
			  chip, WRITE_ADDR(MINION_SYS_RSTN_CTL),
			  rbuf, 0, data);

	// Default reset
	data[0] = SYS_RSTN_CTL_INIT;
	data[1] = 0x00;
	data[2] = 0x00;
	data[3] = 0x00;

	reply = build_cmd(minioncgpu, minioninfo,
			  chip, WRITE_ADDR(MINION_SYS_RSTN_CTL),
			  rbuf, 0, data);

	// Default initialisation
	data[0] = SYS_MISC_CTL_DEFAULT;
	data[1] = 0x00;
	data[2] = 0x00;
	data[3] = 0x00;

	reply = build_cmd(minioncgpu, minioninfo,
			  chip, WRITE_ADDR(MINION_SYS_MISC_CTL),
			  rbuf, 0, data);

	// Set chip frequency
	choice = minioninfo->init_freq[chip];
	if (choice < MINION_FREQ_MIN)
		choice = MINION_FREQ_MIN;
	if (choice > MINION_FREQ_MAX)
		choice = MINION_FREQ_MAX;
	minioninfo->init_freq[chip] = choice;
	set_freq(minioncgpu, minioninfo, chip, choice);

	// Set temp threshold
	choice = minioninfo->init_temp[chip];
	if (choice == MINION_TEMP_CTL_DISABLE)
		choice = MINION_TEMP_CTL_DISABLE_VALUE;
	else {
		if (choice < MINION_TEMP_CTL_MIN_VALUE || choice > MINION_TEMP_CTL_MAX_VALUE)
			choice = MINION_TEMP_CTL_DEF;
		choice -= MINION_TEMP_CTL_MIN_VALUE;
		choice /= MINION_TEMP_CTL_STEP;
		choice += MINION_TEMP_CTL_MIN;
		if (choice < MINION_TEMP_CTL_MIN)
			choice = MINION_TEMP_CTL_MIN;
		if (choice > MINION_TEMP_CTL_MAX)
			choice = MINION_TEMP_CTL_MAX;
	}
	data[0] = (uint8_t)choice;
	data[1] = 0;
	data[2] = 0;
	data[3] = 0;

	minioninfo->chip_status[chip].tempsent = choice;

	reply = build_cmd(minioncgpu, minioninfo,
			  chip, WRITE_ADDR(MINION_SYS_TEMP_CTL),
			  rbuf, 0, data);
}

static void enable_chip_cores(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int chip)
{
	uint8_t rbuf[MINION_BUFSIZ];
	uint8_t data[4];
	__maybe_unused int reply;
	int rep, i;

	for (i = 0; i < 4; i++)
		data[i] = minioninfo->init_cores[chip][i];

	reply = build_cmd(minioncgpu, minioninfo,
			  chip, WRITE_ADDR(MINION_CORE_ENA0_31),
			  rbuf, 0, data);

	for (i = 0; i < 4; i++)
		data[i] = minioninfo->init_cores[chip][i+4];

	reply = build_cmd(minioncgpu, minioninfo,
			  chip, WRITE_ADDR(MINION_CORE_ENA32_63),
			  rbuf, 0, data);

	for (i = 0; i < 4; i++)
		data[i] = minioninfo->init_cores[chip][i+8];

	reply = build_cmd(minioncgpu, minioninfo,
			  chip, WRITE_ADDR(MINION_CORE_ENA64_95),
			  rbuf, 0, data);

	for (i = 0; i < 4; i++)
		data[i] = minioninfo->init_cores[chip][i+12];

	reply = build_cmd(minioncgpu, minioninfo,
			  chip, WRITE_ADDR(MINION_CORE_ENA96_98),
			  rbuf, 0, data);

/* Below is for testing - disabled/use default
	// 1/3 range for each of the 3 cores
//	data[0] = 0x55;
//	data[1] = 0x55;
//	data[2] = 0x55;
//	data[3] = 0x55;

	// quicker replies
//	data[0] = 0x05;
//	data[1] = 0x05;
//	data[2] = 0x05;
//	data[3] = 0x05;

	// 0x00000100 at 20MH/s per core = 336TH/s if 1 nonce per work item
	// 0x00001000 = 21.0TH/s - so well above 2TH/s
	// 0x00002000 = 10.5TH/s - above 2TH/s
	// speed test
	data[0] = 0x00;
	data[1] = 0x01;
	data[2] = 0x00;
	data[3] = 0x00;
//	data[3] = 0x20; // slow it down for other testing

	// 2 cores
//	data[0] = 0xff;
//	data[1] = 0xff;
//	data[2] = 0xff;
//	data[3] = 0x7f;

	reply = build_cmd(minioncgpu, minioninfo,
			  chip, WRITE_ADDR(MINION_NONCE_RANGE),
			  rbuf, 0, data);

	// find lots more nonces in a short time on my test data
	// i.e. emulate a MUCH higher hash rate on SPI and work
	// generation/testing
	// Current test data (same repeated 10 times) has nonce 0x05e0ed6d
	data[0] = 0x00;
	data[1] = 0xed;
	data[2] = 0xe0;
	data[3] = 0x05;

	reply = build_cmd(minioncgpu, minioninfo,
			  chip, WRITE_ADDR(MINION_NONCE_START),
			  rbuf, 0, data);
*/

	// store the core ena state
	for (rep = 0; rep < MINION_CORE_REPS; rep++) {
		data[0] = 0x0;
		data[1] = 0x0;
		data[2] = 0x0;
		data[3] = 0x0;

		reply = build_cmd(minioncgpu, minioninfo,
				  chip, READ_ADDR(MINION_CORE_ENA0_31 + rep),
				  rbuf, MINION_CORE_SIZ, data);

		minioninfo->chip_core_ena[rep][chip] = *((uint32_t *)&(rbuf[HSIZE()]));
	}

	// store the core active state
	for (rep = 0; rep < MINION_CORE_REPS; rep++) {
		data[0] = 0x0;
		data[1] = 0x0;
		data[2] = 0x0;
		data[3] = 0x0;

		reply = build_cmd(minioncgpu, minioninfo,
				  chip, READ_ADDR(MINION_CORE_ACT0_31 + rep),
				  rbuf, MINION_CORE_SIZ, data);

		minioninfo->chip_core_act[rep][chip] = *((uint32_t *)&(rbuf[HSIZE()]));
	}
}

#if ENABLE_INT_NONO
static void enable_interrupt(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int chip)
{
	uint8_t rbuf[MINION_BUFSIZ];
	uint8_t data[4];
	__maybe_unused int reply;

	data[0] = MINION_RESULT_INT_SIZE;
	data[1] = 0x00;
	data[2] = 0x00;
	data[3] = 0x00;

	reply = build_cmd(minioncgpu, minioninfo,
			  chip, WRITE_ADDR(MINION_SYS_BUF_TRIG),
			  rbuf, 0, data);

//	data[0] = MINION_QUE_MAX; // spaces available ... i.e. empty
//	data[0] = MINION_QUE_LOW; // spaces in use
	data[0] = MINION_QUE_MAX - MINION_QUE_LOW; // spaces available
	data[1] = 0x00;
	data[2] = 0x00;
	data[3] = 0x00;

	reply = build_cmd(minioncgpu, minioninfo,
			  chip, WRITE_ADDR(MINION_SYS_QUE_TRIG),
			  rbuf, 0, data);

//	data[0] = MINION_RESULT_INT;
	data[0] = MINION_RESULT_INT | MINION_CMD_INT;
	data[1] = 0x00;
	data[2] = 0x00;
	data[3] = 0x00;

	reply = build_cmd(minioncgpu, minioninfo,
			  chip, WRITE_ADDR(MINION_SYS_INT_ENA),
			  rbuf, 0, data);
}
#endif

static void minion_detect_one(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int pin, int chipid)
{
	struct minion_header *head;
	uint8_t wbuf[MINION_BUFSIZ];
	uint8_t rbuf[MINION_BUFSIZ];
	uint32_t wsiz, rsiz;
	int reply, tries, newchip;
	uint64_t ioseq;
	bool ok;

	head = (struct minion_header *)wbuf;
	head->chipid = chipid;
	rsiz = MINION_SYS_SIZ;
	SET_HEAD_READ(head, MINION_SYS_CHIP_SIG);
	SET_HEAD_SIZ(head, rsiz);
	wsiz = HSIZE() + rsiz;

	tries = 0;
	ok = false;
	do {
		reply = do_ioctl(pin, wbuf, wsiz, rbuf, rsiz, &ioseq);

		if (reply == (int)(wsiz)) {
			uint32_t sig = u8tou32(rbuf, wsiz - rsiz);

			if (sig == MINION_CHIP_SIG) {
				newchip = (minioninfo->chips)++;
				minioninfo->has_chip[newchip] = true;
				minioninfo->chipid[newchip] = chipid;
				minioninfo->chip_pin[newchip] = pin;
				ok = true;
			} else {
				if (sig == MINION_CHIP_SIG_SHIFT1 ||
				    sig == MINION_CHIP_SIG_SHIFT2 ||
				    sig == MINION_CHIP_SIG_SHIFT3 ||
				    sig == MINION_CHIP_SIG_SHIFT4) {
					applog(LOG_WARNING, "%s: pin %d chipid %d detect offset got"
							    " 0x%08x wanted 0x%08x",
							    minioncgpu->drv->dname, pin, chipid,
							    sig, MINION_CHIP_SIG);
				} else {
					if (sig == MINION_NOCHIP_SIG ||
					    sig == MINION_NOCHIP_SIG2) // Assume no chip
						ok = true;
					else {
						applog(LOG_ERR, "%s: pin %d chipid %d detect failed"
								" got 0x%08x wanted 0x%08x",
								minioncgpu->drv->dname, pin,
								chipid, sig, MINION_CHIP_SIG);
					}
				}
			}
		} else {
			applog(LOG_ERR, "%s: pin %d chipid %d reply %d ignored should be %d",
					minioncgpu->drv->dname, pin, chipid, reply, (int)(wsiz));
		}
	} while (!ok && ++tries <= MINION_SIG_TRIES);

	if (!ok) {
		applog(LOG_ERR, "%s: pin %d chipid %d - detect failure status",
				minioncgpu->drv->dname, pin, chipid);
	}
}

// Simple detect - just check each chip for the signature
static void minion_detect_chips(struct cgpu_info *minioncgpu, struct minion_info *minioninfo)
{
	int pin, chipid, chip;
	int pinend, start_freq, want_freq, freqms;

#if MINION_ROCKCHIP == 1
	minion_toggle_gpio(minioncgpu, MINION_POWERCYCLE_GPIO);
	cgsleep_ms(100);
#endif

	if (usepins) {
		init_pins(minioninfo);
		pinend = (int)MINION_PIN_COUNT;
	} else
		pinend = 1;

	for (pin = 0; pin < pinend; pin++) {
		for (chipid = MINION_MIN_CHIP; chipid <= MINION_MAX_CHIP; chipid++) {
			minion_detect_one(minioncgpu, minioninfo, pin, chipid);
		}
	}

	if (minioninfo->chips) {
		for (chip = 0; chip < (int)MINION_CHIPS; chip++) {
			if (minioninfo->has_chip[chip]) {
				want_freq = minioninfo->init_freq[chip];
				start_freq = want_freq * opt_minion_freqpercent / 100;
				start_freq -= (start_freq % MINION_FREQ_FACTOR);
				if (start_freq < MINION_FREQ_MIN)
					start_freq = MINION_FREQ_MIN;
				minioninfo->want_freq[chip] = want_freq;
				minioninfo->init_freq[chip] = start_freq;
				if (start_freq != want_freq) {
					freqms = opt_minion_freqchange;
					freqms /= ((want_freq - start_freq) / MINION_FREQ_FACTOR);
					if (freqms < 0)
						freqms = -freqms;
					minioninfo->freqms[chip] = freqms;
					minioninfo->changing[chip] = true;
				}
				init_chip(minioncgpu, minioninfo, chip);
				enable_chip_cores(minioncgpu, minioninfo, chip);
			}
		}

#if ENABLE_INT_NONO
		// After everything is ready
		for (chip = 0; chip < MINION_CHIPS; chip++)
			if (minioninfo->has_chip[chip])
				enable_interrupt(minioncgpu, minioninfo, chip);
#endif
	}
}

static const char *minion_modules[] = {
#if MINION_ROCKCHIP == 0
	"i2c-dev",
	"i2c-bcm2708",
	"spidev",
	"spi-bcm2708",
#endif
	NULL
};

static struct {
	int request;
	int value;
} minion_ioc[] = {
	{ SPI_IOC_RD_MODE, 0 },
	{ SPI_IOC_WR_MODE, 0 },
	{ SPI_IOC_RD_BITS_PER_WORD, 8 },
	{ SPI_IOC_WR_BITS_PER_WORD, 8 },
	{ SPI_IOC_RD_MAX_SPEED_HZ, MINION_SPI_SPEED },
	{ SPI_IOC_WR_MAX_SPEED_HZ, MINION_SPI_SPEED },
	{ -1, -1 }
};

static bool minion_init_spi(struct cgpu_info *minioncgpu, struct minion_info *minioninfo, int bus, int chip, bool reset)
{
	int i, err, data;
	char buf[64];

	if (reset) {
		// TODO: maybe slow it down?
		close(minioninfo->spifd);
		if (opt_minion_spisleep)
			cgsleep_ms(opt_minion_spisleep);
		minioninfo->spifd = open(minioncgpu->device_path, O_RDWR);
		if (minioninfo->spifd < 0)
			goto bad_out;
		minioninfo->spi_resets++;
//		minioninfo->chip_status[chip].first_nonce.tv_sec = 0L;
	} else {
		for (i = 0; minion_modules[i]; i++) {
			snprintf(buf, sizeof(buf), "modprobe %s", minion_modules[i]);
			err = system(buf);
			if (err) {
				applog(LOG_ERR, "%s: failed to modprobe %s (%d) - you need to be root?",
						minioncgpu->drv->dname,
						minion_modules[i], err);
				goto bad_out;
			}
		}

		snprintf(buf, sizeof(buf), "/dev/spidev%d.%d", bus, chip);
		minioninfo->spifd = open(buf, O_RDWR);
		if (minioninfo->spifd < 0) {
			applog(LOG_ERR, "%s: failed to open spidev (%d)",
					minioncgpu->drv->dname,
					errno);
			goto bad_out;
		}

		minioncgpu->device_path = strdup(buf);
	}

	for (i = 0; minion_ioc[i].value != -1; i++) {
		data = minion_ioc[i].value;
		err = ioctl(minioninfo->spifd, minion_ioc[i].request, (void *)&data);
		if (err < 0) {
			applog(LOG_ERR, "%s: failed ioctl configuration (%d) (%d)",
					minioncgpu->drv->dname,
					i, errno);
			goto close_out;
		}
	}

	return true;

close_out:
	close(minioninfo->spifd);
	minioninfo->spifd = 0;
	free(minioncgpu->device_path);
	minioncgpu->device_path = NULL;

bad_out:
	return false;
}

static bool minion_setup_chip_select(struct cgpu_info *minioncgpu, struct minion_info *minioninfo)
{
	volatile uint32_t *paddr;
	uint32_t mask, value, mem;
	int count, memfd, pin, bcm;

	memfd = open(minion_memory, O_RDWR | O_SYNC);
	if (memfd < 0) {
		applog(LOG_ERR, "%s: failed open %s (%d)",
				minioncgpu->drv->dname,
				minion_memory, errno);
		return false;
	}

	minioninfo->gpio = (volatile unsigned *)mmap(NULL, MINION_PAGE_SIZE,
						  PROT_READ | PROT_WRITE,
						  MAP_SHARED, memfd,
						  minion_memory_addr);
	if (minioninfo->gpio == MAP_FAILED) {
		close(memfd);
		applog(LOG_ERR, "%s: failed mmap gpio (%d)",
				minioncgpu->drv->dname,
				errno);
		return false;
	}

	close(memfd);

	for (pin = 0; pin < (int)MINION_PIN_COUNT; pin++) {
		bcm = minionPins[pin].bcm;

		paddr = minioninfo->gpio + (BCM2835_GPIO_FSEL0 / 4) + (bcm / 10);

		// Set each pin to be an output pin
		mask = BCM2835_GPIO_FSEL_MASK << ((bcm % 10) * 3);
		value = BCM2835_GPIO_FSEL_OUTPUT << ((bcm % 10) * 3);

		// Read settings
		mem = *paddr;
		*paddr;

		mem = (mem & ~mask) | (value & mask);

		// Write appended setting
		*paddr = mem;
		*paddr = mem;

		count++;
	}

	if (count == 0)
		return false;
	else
		return true;
}

#if ENABLE_INT_NONO
static bool minion_init_gpio_interrupt(struct cgpu_info *minioncgpu, struct minion_info *minioninfo)
{
	char pindir[64], ena[64], pin[8], dir[64], edge[64], act[64];
	struct stat st;
	int file, err;
	ssize_t ret;

	snprintf(pindir, sizeof(pindir), MINION_GPIO_SYS MINION_GPIO_PIN,
					 MINION_GPIO_RESULT_INT_PIN);
	memset(&st, 0, sizeof(st));

	if (stat(pindir, &st) == 0) { // already exists
		if (!S_ISDIR(st.st_mode)) {
			applog(LOG_ERR, "%s: failed1 to enable GPIO pin %d interrupt"
					" - not a directory",
					minioncgpu->drv->dname,
					MINION_GPIO_RESULT_INT_PIN);
			return false;
		}
	} else {
		snprintf(ena, sizeof(ena), MINION_GPIO_SYS MINION_GPIO_ENA);
		file = open(ena, O_WRONLY | O_SYNC);
		if (file == -1) {
			applog(LOG_ERR, "%s: failed2 to enable GPIO pin %d interrupt (%d)"
					" - you need to be root?",
					minioncgpu->drv->dname,
					MINION_GPIO_RESULT_INT_PIN,
					errno);
			return false;
		}
		snprintf(pin, sizeof(pin), MINION_GPIO_ENA_VAL, MINION_GPIO_RESULT_INT_PIN);
		ret = write(file, pin, (size_t)strlen(pin));
		if (ret != (ssize_t)strlen(pin)) {
			if (ret < 0)
				err = errno;
			else
				err = (int)ret;
			close(file);
			applog(LOG_ERR, "%s: failed3 to enable GPIO pin %d interrupt (%d:%d)",
					minioncgpu->drv->dname,
					MINION_GPIO_RESULT_INT_PIN,
					err, (int)strlen(pin));
			return false;
		}
		close(file);

		// Check again if it exists
		memset(&st, 0, sizeof(st));
		if (stat(pindir, &st) != 0) {
			applog(LOG_ERR, "%s: failed4 to enable GPIO pin %d interrupt (%d)",
					minioncgpu->drv->dname,
					MINION_GPIO_RESULT_INT_PIN,
					errno);
			return false;
		}
	}

	// Set the pin attributes
	// Direction
	snprintf(dir, sizeof(dir), MINION_GPIO_SYS MINION_GPIO_PIN MINION_GPIO_DIR,
				   MINION_GPIO_RESULT_INT_PIN);
	file = open(dir, O_WRONLY | O_SYNC);
	if (file == -1) {
		applog(LOG_ERR, "%s: failed5 to enable GPIO pin %d interrupt (%d)"
				" - you need to be root?",
				minioncgpu->drv->dname,
				MINION_GPIO_RESULT_INT_PIN,
				errno);
		return false;
	}
	ret = write(file, MINION_GPIO_DIR_READ, (size_t)strlen(MINION_GPIO_DIR_READ));
	if (ret != (ssize_t)strlen(MINION_GPIO_DIR_READ)) {
		if (ret < 0)
			err = errno;
		else
			err = (int)ret;
		close(file);
		applog(LOG_ERR, "%s: failed6 to enable GPIO pin %d interrupt (%d:%d)",
				minioncgpu->drv->dname,
				MINION_GPIO_RESULT_INT_PIN,
				err, (int)strlen(MINION_GPIO_DIR_READ));
		return false;
	}
	close(file);

	// Edge
	snprintf(edge, sizeof(edge), MINION_GPIO_SYS MINION_GPIO_PIN MINION_GPIO_EDGE,
				   MINION_GPIO_RESULT_INT_PIN);
	file = open(edge, O_WRONLY | O_SYNC);
	if (file == -1) {
		applog(LOG_ERR, "%s: failed7 to enable GPIO pin %d interrupt (%d)",
				minioncgpu->drv->dname,
				MINION_GPIO_RESULT_INT_PIN,
				errno);
		return false;
	}
	ret = write(file, MINION_GPIO_EDGE_RISING, (size_t)strlen(MINION_GPIO_EDGE_RISING));
	if (ret != (ssize_t)strlen(MINION_GPIO_EDGE_RISING)) {
		if (ret < 0)
			err = errno;
		else
			err = (int)ret;
		close(file);
		applog(LOG_ERR, "%s: failed8 to enable GPIO pin %d interrupt (%d:%d)",
				minioncgpu->drv->dname,
				MINION_GPIO_RESULT_INT_PIN,
				err, (int)strlen(MINION_GPIO_EDGE_RISING));
		return false;
	}
	close(file);

	// Active
	snprintf(act, sizeof(act), MINION_GPIO_SYS MINION_GPIO_PIN MINION_GPIO_ACT,
				   MINION_GPIO_RESULT_INT_PIN);
	file = open(act, O_WRONLY | O_SYNC);
	if (file == -1) {
		applog(LOG_ERR, "%s: failed9 to enable GPIO pin %d interrupt (%d)",
				minioncgpu->drv->dname,
				MINION_GPIO_RESULT_INT_PIN,
				errno);
		return false;
	}
	ret = write(file, MINION_GPIO_ACT_HI, (size_t)strlen(MINION_GPIO_ACT_HI));
	if (ret != (ssize_t)strlen(MINION_GPIO_ACT_HI)) {
		if (ret < 0)
			err = errno;
		else
			err = (int)ret;
		close(file);
		applog(LOG_ERR, "%s: failed10 to enable GPIO pin %d interrupt (%d:%d)",
				minioncgpu->drv->dname,
				MINION_GPIO_RESULT_INT_PIN,
				err, (int)strlen(MINION_GPIO_ACT_HI));
		return false;
	}
	close(file);

	// Setup fd access to Value
	snprintf(minioninfo->gpiointvalue, sizeof(minioninfo->gpiointvalue),
				   MINION_GPIO_SYS MINION_GPIO_PIN MINION_GPIO_VALUE,
				   MINION_GPIO_RESULT_INT_PIN);
	minioninfo->gpiointfd = open(minioninfo->gpiointvalue, O_RDONLY);
	if (minioninfo->gpiointfd == -1) {
		applog(LOG_ERR, "%s: failed11 to enable GPIO pin %d interrupt (%d)",
				minioncgpu->drv->dname,
				MINION_GPIO_RESULT_INT_PIN,
				errno);
		return false;
	}

	return true;
}
#endif

// Default meaning all cores
static void default_all_cores(uint8_t *cores)
{
	int i;

	// clear all bits
	for (i = 0; i < (int)(DATA_SIZ * MINION_CORE_REPS); i++)
		cores[i] = 0x00;

	// enable (only) all cores
	for (i = 0; i < MINION_CORES; i++)
		ENABLE_CORE(cores, i);
}

static void minion_process_options(struct minion_info *minioninfo)
{
	int last_freq, last_temp;
	char *freq, *temp, *core, *comma, *buf, *plus, *minus;
	uint8_t last_cores[DATA_SIZ*MINION_CORE_REPS];
	int i, core1, core2;
	bool cleared;

	if (opt_minion_spireset && *opt_minion_spireset) {
		bool is_io = true;
		int val;

		switch (tolower(*opt_minion_spireset)) {
			case 'i':
				is_io = true;
				break;
			case 's':
				is_io = false;
				break;
			default:
				applog(LOG_WARNING, "ERR: Invalid SPI reset '%s'",
						    opt_minion_spireset);
				goto skip;
		}
		val = atoi(opt_minion_spireset+1);
		if (val < 0 || val > 9999) {
			applog(LOG_WARNING, "ERR: Invalid SPI reset '%s'",
					    opt_minion_spireset);
		} else {
			minioninfo->spi_reset_io = is_io;
			minioninfo->spi_reset_count = val;
			minioninfo->last_spi_reset = time(NULL);
		}
	}
skip:
	last_freq = MINION_FREQ_DEF;
	if (opt_minion_freq && *opt_minion_freq) {
		buf = freq = strdup(opt_minion_freq);
		comma = strchr(freq, ',');
		if (comma)
			*(comma++) = '\0';

		for (i = 0; i < (int)MINION_CHIPS; i++) {
			if (freq && isdigit(*freq)) {
				last_freq = (int)(round((double)atoi(freq) / (double)MINION_FREQ_FACTOR)) * MINION_FREQ_FACTOR;
				if (last_freq < MINION_FREQ_MIN)
					last_freq = MINION_FREQ_MIN;
				if (last_freq > MINION_FREQ_MAX)
					last_freq = MINION_FREQ_MAX;

				freq = comma;
				if (comma) {
					comma = strchr(freq, ',');
					if (comma)
						*(comma++) = '\0';
				}
			}
			minioninfo->init_freq[i] = last_freq;
		}
		free(buf);
	}

	last_temp = MINION_TEMP_CTL_DEF;
	if (opt_minion_temp && *opt_minion_temp) {
		buf = temp = strdup(opt_minion_temp);
		comma = strchr(temp, ',');
		if (comma)
			*(comma++) = '\0';

		for (i = 0; i < (int)MINION_CHIPS; i++) {
			if (temp) {
				if (isdigit(*temp)) {
					last_temp = atoi(temp);
					last_temp -= (last_temp % MINION_TEMP_CTL_STEP);
					if (last_temp < MINION_TEMP_CTL_MIN_VALUE)
						last_temp = MINION_TEMP_CTL_MIN_VALUE;
					if (last_temp > MINION_TEMP_CTL_MAX_VALUE)
						last_temp = MINION_TEMP_CTL_MAX_VALUE;
				} else {
					if (strcasecmp(temp, MINION_TEMP_DISABLE) == 0)
						last_temp = MINION_TEMP_CTL_DISABLE;
				}

				temp = comma;
				if (comma) {
					comma = strchr(temp, ',');
					if (comma)
						*(comma++) = '\0';
				}
			}
			minioninfo->init_temp[i] = last_temp;
		}
		free(buf);
	}

	default_all_cores(&(last_cores[0]));
	// default to all cores until we find valid data
	cleared = false;
	if (opt_minion_cores && *opt_minion_cores) {
		buf = core = strdup(opt_minion_cores);
		comma = strchr(core, ',');
		if (comma)
			*(comma++) = '\0';

		for (i = 0; i < (int)MINION_CHIPS; i++) {
			// default to previous until we find valid data
			cleared = false;
			if (core) {
				plus = strchr(core, '+');
				if (plus)
					*(plus++) = '\0';
				while (core) {
					minus = strchr(core, '-');
					if (minus)
						*(minus++) = '\0';
					if (isdigit(*core)) {
						core1 = atoi(core);
						if (core1 >= 0 && core1 < MINION_CORES) {
							if (!minus) {
								if (!cleared) {
									memset(last_cores, 0, sizeof(last_cores));
									cleared = true;
								}
								ENABLE_CORE(last_cores, core1);
							} else {
								core2 = atoi(minus);
								if (core2 >= core1) {
									if (core2 >= MINION_CORES)
										core2 = MINION_CORES - 1;
									while (core1 <= core2) {
										if (!cleared) {
											memset(last_cores, 0,
												sizeof(last_cores));
											cleared = true;
										}
										ENABLE_CORE(last_cores, core1);
										core1++;
									}
								}
							}
						}
					} else {
						if (strcasecmp(core, MINION_CORE_ALL) == 0)
							default_all_cores(&(last_cores[0]));
					}
					core = plus;
					if (plus) {
						plus = strchr(core, '+');
						if (plus)
							*(plus++) = '\0';
					}
				}
				core = comma;
				if (comma) {
					comma = strchr(core, ',');
					if (comma)
						*(comma++) = '\0';
				}
			}
			memcpy(&(minioninfo->init_cores[i][0]), &(last_cores[0]), sizeof(last_cores));
		}
		free(buf);
	}
}

static void minion_detect(bool hotplug)
{
	struct cgpu_info *minioncgpu = NULL;
	struct minion_info *minioninfo = NULL;
	char buf[512];
	size_t off;
	int i;

	if (hotplug)
		return;

	define_test();

	minioncgpu = calloc(1, sizeof(*minioncgpu));
	if (unlikely(!minioncgpu))
		quithere(1, "Failed to calloc minioncgpu");

	minioncgpu->drv = &minion_drv;
	minioncgpu->deven = DEV_ENABLED;
	minioncgpu->threads = 1;

	minioninfo = calloc(1, sizeof(*minioninfo)); // everything '0'
	if (unlikely(!minioninfo))
		quithere(1, "Failed to calloc minioninfo");
	minioncgpu->device_data = (void *)minioninfo;

	if (!minion_init_spi(minioncgpu, minioninfo, MINION_SPI_BUS, MINION_SPI_CHIP, false))
		goto unalloc;

#if ENABLE_INT_NONO
	if (!minion_init_gpio_interrupt(minioncgpu, minioninfo))
		goto unalloc;
#endif


	if (usepins) {
		if (!minion_setup_chip_select(minioncgpu, minioninfo))
			goto unalloc;
	}

	mutex_init(&(minioninfo->spi_lock));
	mutex_init(&(minioninfo->sta_lock));

	for (i = 0; i < (int)MINION_CHIPS; i++) {
		minioninfo->init_freq[i] = MINION_FREQ_DEF;
		minioninfo->init_temp[i] = MINION_TEMP_CTL_DEF;
		default_all_cores(&(minioninfo->init_cores[i][0]));
	}

	minion_process_options(minioninfo);

	applog(LOG_WARNING, "%s: checking for chips ...", minioncgpu->drv->dname);

	minion_detect_chips(minioncgpu, minioninfo);

	buf[0] = '\0';
	for (i = 0; i < (int)MINION_CHIPS; i++) {
		if (minioninfo->has_chip[i]) {
			off = strlen(buf);
			snprintf(buf + off, sizeof(buf) - off, " %d:%d/%d",
				 i, minioninfo->chip_pin[i], (int)(minioninfo->chipid[i]));
		}
	}

	applog(LOG_WARNING, "%s: found %d chip%s:%s",
				minioncgpu->drv->dname, minioninfo->chips,
				(minioninfo->chips == 1) ? "" : "s", buf);

	if (minioninfo->chips == 0)
		goto cleanup;

	if (!add_cgpu(minioncgpu))
		goto cleanup;

	mutex_init(&(minioninfo->nonce_lock));

	minioninfo->wfree_list = k_new_list("Work", sizeof(WORK_ITEM),
					    ALLOC_WORK_ITEMS, LIMIT_WORK_ITEMS, true);
	minioninfo->wwork_list = k_new_store(minioninfo->wfree_list);
	minioninfo->wstale_list = k_new_store(minioninfo->wfree_list);
	// Initialise them all in case we later decide to enable chips
	for (i = 0; i < (int)MINION_CHIPS; i++) {
		minioninfo->wque_list[i] = k_new_store(minioninfo->wfree_list);
		minioninfo->wchip_list[i] = k_new_store(minioninfo->wfree_list);
	}

	minioninfo->tfree_list = k_new_list("Task", sizeof(TASK_ITEM),
					    ALLOC_TASK_ITEMS, LIMIT_TASK_ITEMS, true);
	minioninfo->task_list = k_new_store(minioninfo->tfree_list);
	minioninfo->treply_list = k_new_store(minioninfo->tfree_list);

	minioninfo->rfree_list = k_new_list("Reply", sizeof(RES_ITEM),
					    ALLOC_RES_ITEMS, LIMIT_RES_ITEMS, true);
	minioninfo->rnonce_list = k_new_store(minioninfo->rfree_list);

	minioninfo->history_gen = MINION_MAX_RESET_CHECK;
	minioninfo->hfree_list = k_new_list("History", sizeof(HIST_ITEM),
					    ALLOC_HIST_ITEMS, LIMIT_HIST_ITEMS, true);
	for (i = 0; i < (int)MINION_CHIPS; i++)
		minioninfo->hchip_list[i] = k_new_store(minioninfo->hfree_list);

	minioninfo->pfree_list = k_new_list("Performance", sizeof(PERF_ITEM),
					    ALLOC_PERF_ITEMS, LIMIT_PERF_ITEMS, true);
	for (i = 0; i < (int)MINION_CHIPS; i++)
		minioninfo->p_list[i] = k_new_store(minioninfo->pfree_list);

	minioninfo->xfree_list = k_new_list("0xff", sizeof(XFF_ITEM),
					    ALLOC_XFF_ITEMS, LIMIT_XFF_ITEMS, true);
	minioninfo->xff_list = k_new_store(minioninfo->xfree_list);

	cgsem_init(&(minioninfo->task_ready));
	cgsem_init(&(minioninfo->nonce_ready));
	cgsem_init(&(minioninfo->scan_work));

	minioninfo->initialised = true;

	dupalloc(minioncgpu, 10);

	return;

cleanup:
	close(minioninfo->gpiointfd);
	close(minioninfo->spifd);
	mutex_destroy(&(minioninfo->sta_lock));
	mutex_destroy(&(minioninfo->spi_lock));
unalloc:
	free(minioninfo);
	free(minioncgpu);
}

static char *minion_api_set(struct cgpu_info *minioncgpu, char *option, char *setting, char *replybuf, size_t siz)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	int chip, val;
	char *colon;

	if (strcasecmp(option, "help") == 0) {
		snprintf(replybuf, siz, "reset: chip 0-%d freq: 0-%d:%d-%d "
				  "ledcount: 0-100 ledlimit: 0-200 "
				  "spidelay: 0-9999 spireset i|s0-9999 "
				  "spisleep: 0-9999",
				  minioninfo->chips - 1,
				  minioninfo->chips - 1,
				  MINION_FREQ_MIN, MINION_FREQ_MAX);
		return replybuf;
	}

	if (strcasecmp(option, "reset") == 0) {
		if (!setting || !*setting) {
			snprintf(replybuf, siz, "missing chip to reset");
			return replybuf;
		}

		chip = atoi(setting);
		if (chip < 0 || chip >= minioninfo->chips) {
			snprintf(replybuf, siz, "invalid reset: chip '%s' valid range 0-%d",
					  setting,
					  minioninfo->chips);
			return replybuf;
		}

		if (!minioninfo->has_chip[chip]) {
			snprintf(replybuf, siz, "unable to reset chip %d - chip disabled",
					  chip);
			return replybuf;
		}
		minioninfo->flag_reset[chip] = true;
		return NULL;
	}

	// This sets up a freq step up/down to the given freq without a reset
	if (strcasecmp(option, "freq") == 0) {
		if (!setting || !*setting) {
			snprintf(replybuf, siz, "missing chip:freq");
			return replybuf;
		}

		colon = strchr(setting, ':');
		if (!colon) {
			snprintf(replybuf, siz, "missing ':' for chip:freq");
			return replybuf;
		}

		*(colon++) = '\0';
		if (!*colon) {
			snprintf(replybuf, siz, "missing freq in chip:freq");
			return replybuf;
		}

		chip = atoi(setting);
		if (chip < 0 || chip >= minioninfo->chips) {
			snprintf(replybuf, siz, "invalid freq: chip '%s' valid range 0-%d",
					  setting,
					  minioninfo->chips);
			return replybuf;
		}

		if (!minioninfo->has_chip[chip]) {
			snprintf(replybuf, siz, "unable to modify chip %d - chip not enabled",
					  chip);
			return replybuf;
		}

		val = atoi(colon);
		if (val < MINION_FREQ_MIN || val > MINION_FREQ_MAX) {
			snprintf(replybuf, siz, "invalid freq: '%s' valid range %d-%d",
					  setting,
					  MINION_FREQ_MIN, MINION_FREQ_MAX);
			return replybuf;
		}

		int want_freq = val - (val % MINION_FREQ_FACTOR);
		int start_freq = minioninfo->init_freq[chip];
		int freqms;

		if (want_freq != start_freq) {
			minioninfo->changing[chip] = false;
			freqms = opt_minion_freqchange;
			freqms /= ((want_freq - start_freq) / MINION_FREQ_FACTOR);
			if (freqms < 0)
				freqms = -freqms;
			minioninfo->freqms[chip] = freqms;
			minioninfo->want_freq[chip] = want_freq;
			cgtime(&(minioninfo->lastfreq[chip]));
			minioninfo->changing[chip] = true;
		}

		return NULL;
	}

	if (strcasecmp(option, "ledcount") == 0) {
		if (!setting || !*setting) {
			snprintf(replybuf, siz, "missing ledcount value");
			return replybuf;
		}

		val = atoi(setting);
		if (val < 0 || val > 100) {
			snprintf(replybuf, siz, "invalid ledcount: '%s' valid range 0-100",
					  setting);
			return replybuf;
		}

		opt_minion_ledcount = val;
		return NULL;
	}

	if (strcasecmp(option, "ledlimit") == 0) {
		if (!setting || !*setting) {
			snprintf(replybuf, siz, "missing ledlimit value");
			return replybuf;
		}

		val = atoi(setting);
		if (val < 0 || val > 200) {
			snprintf(replybuf, siz, "invalid ledlimit: GHs '%s' valid range 0-200",
					  setting);
			return replybuf;
		}

		opt_minion_ledlimit = val;
		return NULL;
	}

	if (strcasecmp(option, "spidelay") == 0) {
		if (!setting || !*setting) {
			snprintf(replybuf, siz, "missing spidelay value");
			return replybuf;
		}

		val = atoi(setting);
		if (val < 0 || val > 9999) {
			snprintf(replybuf, siz, "invalid spidelay: ms '%s' valid range 0-9999",
					  setting);
			return replybuf;
		}

		opt_minion_spidelay = val;
		return NULL;
	}

	if (strcasecmp(option, "spireset") == 0) {
		bool is_io = true;

		if (!setting || !*setting) {
			snprintf(replybuf, siz, "missing spireset value");
			return replybuf;
		}

		switch (tolower(*setting)) {
			case 'i':
				is_io = true;
				break;
			case 's':
				is_io = false;
				break;
			default:
				snprintf(replybuf, siz, "invalid spireset: '%s' must start with i or s",
						  setting);
				return replybuf;
		}
		val = atoi(setting+1);
		if (val < 0 || val > 9999) {
			snprintf(replybuf, siz, "invalid spireset: %c '%s' valid range 0-9999",
					  *setting, setting+1);
			return replybuf;
		}

		minioninfo->spi_reset_io = is_io;
		minioninfo->spi_reset_count = val;
		minioninfo->last_spi_reset = time(NULL);

		return NULL;
	}

	if (strcasecmp(option, "spisleep") == 0) {
		if (!setting || !*setting) {
			snprintf(replybuf, siz, "missing spisleep value");
			return replybuf;
		}

		val = atoi(setting);
		if (val < 0 || val > 9999) {
			snprintf(replybuf, siz, "invalid spisleep: ms '%s' valid range 0-9999",
					  setting);
			return replybuf;
		}

		opt_minion_spisleep = val;
		return NULL;
	}

	if (strcasecmp(option, "spiusec") == 0) {
		if (!setting || !*setting) {
			snprintf(replybuf, siz, "missing spiusec value");
			return replybuf;
		}

		val = atoi(setting);
		if (val < 0 || val > 9999) {
			snprintf(replybuf, siz, "invalid spiusec: '%s' valid range 0-9999",
					  setting);
			return replybuf;
		}

		opt_minion_spiusec = val;
		return NULL;
	}

	snprintf(replybuf, siz, "Unknown option: %s", option);
	return replybuf;
}

static void minion_identify(__maybe_unused struct cgpu_info *minioncgpu)
{
	// flash a led
}

/*
 * SPI/ioctl write thread
 * Non urgent work is to keep the queue full
 * Urgent work is when an LP occurs (or the queue is empty/low)
 */
static void *minion_spi_write(void *userdata)
{
	struct cgpu_info *minioncgpu = (struct cgpu_info *)userdata;
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	K_ITEM *item, *tail, *task, *work;
	TASK_ITEM *titem;

	applog(MINION_LOG, "%s%i: SPI writing...",
				minioncgpu->drv->name, minioncgpu->device_id);

	// Wait until we're ready
	while (minioncgpu->shutdown == false) {
		if (minioninfo->initialised) {
			break;
		}
		cgsleep_ms(1); // asap to start mining
	}

	// TODO: combine all urgent into a single I/O?
	// Then combine all state 1 for the same chip into a single I/O ?
	// (then again for state 2?)
	while (minioncgpu->shutdown == false) {
		item = NULL;
		K_WLOCK(minioninfo->task_list);
		tail = minioninfo->task_list->tail;
		if (tail) {
			// Find first urgent item
			item = tail;
			while (item && !(DATA_TASK(item)->urgent))
				item = item->prev;

			// No urgent items, just do the tail
			if (!item)
				item = tail;

			k_unlink_item(minioninfo->task_list, item);
		}
		K_WUNLOCK(minioninfo->task_list);

		if (item) {
			bool do_txrx = true;
			bool store_reply = true;
			struct timeval now;
			double howlong;
			int i;

			titem = DATA_TASK(item);

			switch (titem->address) {
				// TODO: case MINION_SYS_TEMP_CTL:
				// TODO: case MINION_SYS_FREQ_CTL:
				case READ_ADDR(MINION_SYS_CHIP_STA):
				case WRITE_ADDR(MINION_SYS_SPI_LED):
				case WRITE_ADDR(MINION_SYS_RSTN_CTL):
				case WRITE_ADDR(MINION_SYS_INT_CLR):
				case READ_ADDR(MINION_SYS_IDLE_CNT):
				case READ_ADDR(MINION_CORE_ENA0_31):
				case READ_ADDR(MINION_CORE_ENA32_63):
				case READ_ADDR(MINION_CORE_ENA64_95):
				case READ_ADDR(MINION_CORE_ENA96_98):
				case READ_ADDR(MINION_CORE_ACT0_31):
				case READ_ADDR(MINION_CORE_ACT32_63):
				case READ_ADDR(MINION_CORE_ACT64_95):
				case READ_ADDR(MINION_CORE_ACT96_98):
					store_reply = false;
					break;
				case WRITE_ADDR(MINION_QUE_0):
//applog(LOG_ERR, "%s%i: ZZZ send task_id 0x%04x - chip %d", minioncgpu->drv->name, minioncgpu->device_id, titem->task_id, titem->chip);
					store_reply = false;
					break;
				default:
					do_txrx = false;
					titem->reply = MINION_UNEXPECTED_TASK;
					applog(LOG_ERR, "%s%i: Unexpected task address 0x%02x (%s)",
							minioncgpu->drv->name, minioncgpu->device_id,
							(unsigned int)(titem->address),
							addr2txt(titem->address));

					break;
			}

			if (do_txrx) {
				if (titem->witem) {
					cgtime(&now);
					howlong = tdiff(&now, &(DATA_WORK(titem->witem)->created));
					minioninfo->wt_work++;
					minioninfo->wt_time += howlong;
					if (minioninfo->wt_min == 0 || minioninfo->wt_min > howlong)
						minioninfo->wt_min = howlong;
					else if (minioninfo->wt_max < howlong)
						minioninfo->wt_max = howlong;
					for (i = 0; i < TIME_BANDS; i++) {
						if (howlong < time_bands[i]) {
							minioninfo->wt_bands[i]++;
							break;
						}
					}
					if (i >= TIME_BANDS)
						minioninfo->wt_bands[TIME_BANDS]++;
				}

				minion_txrx(titem);

				int chip = titem->chip;
				switch (titem->address) {
					case READ_ADDR(MINION_SYS_CHIP_STA):
						if (titem->reply >= (int)(titem->osiz)) {
							uint8_t *rep = &(titem->rbuf[titem->osiz - titem->rsiz]);
							mutex_lock(&(minioninfo->sta_lock));
							minioninfo->chip_status[chip].temp = STA_TEMP(rep);
							minioninfo->chip_status[chip].cores = STA_CORES(rep);
							minioninfo->chip_status[chip].freq = STA_FREQ(rep);
							mutex_unlock(&(minioninfo->sta_lock));

							if (minioninfo->chip_status[chip].overheat) {
								switch (STA_TEMP(rep)) {
									case MINION_TEMP_40:
									case MINION_TEMP_60:
									case MINION_TEMP_80:
										cgtime(&(minioninfo->chip_status[chip].lastrecover));
										minioninfo->chip_status[chip].overheat = false;
										applog(LOG_WARNING, "%s%d: chip %d cooled, restarting",
												    minioncgpu->drv->name,
												    minioncgpu->device_id,
												    chip);
										cgtime(&(minioninfo->chip_status[chip].lastrecover));
										minioninfo->chip_status[chip].overheattime +=
											tdiff(&(minioninfo->chip_status[chip].lastrecover),
												&(minioninfo->chip_status[chip].lastoverheat));
										break;
									default:
										break;
								}
							} else {
								if (opt_minion_overheat && STA_TEMP(rep) == MINION_TEMP_OVER) {
									cgtime(&(minioninfo->chip_status[chip].lastoverheat));
									minioninfo->chip_status[chip].overheat = true;
									applog(LOG_WARNING, "%s%d: chip %d overheated! idling",
											    minioncgpu->drv->name,
											    minioncgpu->device_id,
											    chip);
									K_WLOCK(minioninfo->tfree_list);
									task = k_unlink_head(minioninfo->tfree_list);
									DATA_TASK(task)->tid = ++(minioninfo->next_tid);
									DATA_TASK(task)->chip = chip;
									DATA_TASK(task)->write = true;
									DATA_TASK(task)->address = MINION_SYS_RSTN_CTL;
									DATA_TASK(task)->task_id = 0; // ignored
									DATA_TASK(task)->wsiz = MINION_SYS_SIZ;
									DATA_TASK(task)->rsiz = 0;
									DATA_TASK(task)->wbuf[0] = SYS_RSTN_CTL_FLUSH;
									DATA_TASK(task)->wbuf[1] = 0;
									DATA_TASK(task)->wbuf[2] = 0;
									DATA_TASK(task)->wbuf[3] = 0;
									DATA_TASK(task)->urgent = true;
									k_add_head(minioninfo->task_list, task);
									K_WUNLOCK(minioninfo->tfree_list);
									minioninfo->chip_status[chip].overheats++;
								}
							}
						}
						break;
					case READ_ADDR(MINION_SYS_IDLE_CNT):
						{
							uint32_t *cnt = (uint32_t *)&(titem->rbuf[titem->osiz - titem->rsiz]);
							minioninfo->chip_status[chip].idle = *cnt;
						}
						break;
					case WRITE_ADDR(MINION_SYS_RSTN_CTL):
						// Do this here after it has actually been flushed
						if ((titem->wbuf[0] & SYS_RSTN_CTL_FLUSH) == SYS_RSTN_CTL_FLUSH) {
							int cnt = 0;
							K_WLOCK(minioninfo->wwork_list);
							work = minioninfo->wchip_list[chip]->head;
							while (work) {
								cnt++;
								DATA_WORK(work)->stale = true;
								work = work->next;
							}
							minioninfo->chip_status[chip].chipwork = 0;
							minioninfo->chip_status[chip].realwork = 0;
							minioninfo->wchip_staled += cnt;
#if MINION_SHOW_IO
							applog(IOCTRL_LOG, "RSTN chip %d (cnt=%d) cw0=%u rw0=%u qw=%u",
									   chip, cnt,
									   minioninfo->chip_status[chip].chipwork,
									   minioninfo->chip_status[chip].realwork,
									   minioninfo->chip_status[chip].quework);
#endif
							K_WUNLOCK(minioninfo->wwork_list);
						}
						break;
					case WRITE_ADDR(MINION_QUE_0):
						K_WLOCK(minioninfo->wchip_list[chip]);
						k_unlink_item(minioninfo->wque_list[chip], titem->witem);
						k_add_head(minioninfo->wchip_list[chip], titem->witem);
						DATA_WORK(titem->witem)->ioseq = titem->ioseq;
						minioninfo->chip_status[chip].quework--;
						minioninfo->chip_status[chip].chipwork++;
#if MINION_SHOW_IO
						applog(IOCTRL_LOG, "QUE_0 chip %d cw+1=%u rw=%u qw-1=%u",
								   chip,
								   minioninfo->chip_status[chip].chipwork,
								   minioninfo->chip_status[chip].realwork,
								   minioninfo->chip_status[chip].quework);
#endif
						K_WUNLOCK(minioninfo->wchip_list[chip]);
						applog(LOG_DEBUG, "%s%d: task 0x%04x sent to chip %d",
								  minioncgpu->drv->name, minioncgpu->device_id,
								  titem->task_id, chip);
						break;
					case READ_ADDR(MINION_CORE_ENA0_31):
					case READ_ADDR(MINION_CORE_ENA32_63):
					case READ_ADDR(MINION_CORE_ENA64_95):
					case READ_ADDR(MINION_CORE_ENA96_98):
						{
							uint32_t *rep = (uint32_t *)&(titem->rbuf[titem->osiz - titem->rsiz]);
							int off = titem->address - READ_ADDR(MINION_CORE_ENA0_31);
							minioninfo->chip_core_ena[off][chip] = *rep;
						}
						break;
					case READ_ADDR(MINION_CORE_ACT0_31):
					case READ_ADDR(MINION_CORE_ACT32_63):
					case READ_ADDR(MINION_CORE_ACT64_95):
					case READ_ADDR(MINION_CORE_ACT96_98):
						{
							uint32_t *rep = (uint32_t *)&(titem->rbuf[titem->osiz - titem->rsiz]);
							int off = titem->address - READ_ADDR(MINION_CORE_ACT0_31);
							minioninfo->chip_core_act[off][chip] = *rep;
						}
						break;
					case WRITE_ADDR(MINION_SYS_INT_CLR):
					case WRITE_ADDR(MINION_SYS_SPI_LED):
						break;
					default:
						break;
				}
			}

			K_WLOCK(minioninfo->treply_list);
			if (store_reply)
				k_add_head(minioninfo->treply_list, item);
			else
				k_free_head(minioninfo->tfree_list, item);
			K_WUNLOCK(minioninfo->treply_list);

			/*
			 * Always check for the next task immediately if we just did one
			 * i.e. empty the task queue
			 */
			continue;
		}
		cgsem_mswait(&(minioninfo->task_ready), MINION_TASK_mS);
	}
	return NULL;
}

/*
 * SPI/ioctl reply thread
 * ioctl done every interrupt or MINION_REPLY_mS checking for results
 */
static void *minion_spi_reply(void *userdata)
{
	struct cgpu_info *minioncgpu = (struct cgpu_info *)userdata;
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	struct minion_result *result1, *result2, *use1, *use2;
	K_ITEM *item;
	TASK_ITEM fifo_task, res1_task, res2_task;
	int chip, resoff;
	bool somelow;
	struct timeval now;

#if ENABLE_INT_NONO
	uint64_t ioseq;
	TASK_ITEM clr_task;
	struct pollfd pfd;
	struct minion_header *head;
	uint8_t rbuf[MINION_BUFSIZ];
	uint8_t wbuf[MINION_BUFSIZ];
	uint32_t wsiz, rsiz;
	int ret, reply;
	bool gotreplies = false;
#endif

	applog(MINION_LOG, "%s%i: SPI replying...",
				minioncgpu->drv->name, minioncgpu->device_id);

	// Wait until we're ready
	while (minioncgpu->shutdown == false) {
		if (minioninfo->initialised) {
			break;
		}
		cgsleep_ms(2);
	}

	fifo_task.chip = 0;
	fifo_task.write = false;
	fifo_task.address = MINION_SYS_FIFO_STA;
	fifo_task.wsiz = 0;
	fifo_task.rsiz = MINION_SYS_SIZ;

	res1_task.chip = 0;
	res1_task.write = false;
	if (minreread)
		res1_task.address = MINION_RES_PEEK;
	else
		res1_task.address = MINION_RES_DATA;
	res1_task.wsiz = 0;
	res1_task.rsiz = MINION_RES_DATA_SIZ;

	res2_task.chip = 0;
	res2_task.write = false;
	res2_task.address = MINION_RES_DATA;
	res2_task.wsiz = 0;
	res2_task.rsiz = MINION_RES_DATA_SIZ;

#if ENABLE_INT_NONO
	// Clear RESULT_INT after reading all results
	clr_task.chip = 0;
	clr_task.write = true;
	clr_task.address = MINION_SYS_INT_CLR;
	clr_task.wsiz = MINION_SYS_SIZ;
	clr_task.rsiz = 0;
	clr_task.wbuf[0] = MINION_RESULT_INT;
	clr_task.wbuf[1] = 0;
	clr_task.wbuf[2] = 0;
	clr_task.wbuf[3] = 0;

	memset(&pfd, 0, sizeof(pfd));
	pfd.fd = minioninfo->gpiointfd;
	pfd.events = POLLPRI;

	head = (struct minion_header *)wbuf;
	SET_HEAD_SIZ(head, MINION_SYS_SIZ);
	wsiz = HSIZE() + MINION_SYS_SIZ;
	rsiz = MINION_SYS_SIZ; // for READ, use 0 for WRITE
#endif

	somelow = false;
	while (minioncgpu->shutdown == false) {
		for (chip = 0; chip < (int)MINION_CHIPS; chip++) {
			if (minioninfo->has_chip[chip]) {
				int tries = 0;
				uint8_t res, cmd;

				if (minioninfo->changing[chip] &&
				    ms_tdiff(&now, &minioninfo->lastfreq[chip]) >
				    minioninfo->freqms[chip]) {
					int want_freq = minioninfo->want_freq[chip];
					int init_freq = minioninfo->init_freq[chip];

					if (want_freq > init_freq) {
						minioninfo->init_freq[chip] += MINION_FREQ_FACTOR;
						init_freq += MINION_FREQ_FACTOR;

						set_freq(minioncgpu, minioninfo, chip, init_freq);
					} else if (want_freq < init_freq) {
						minioninfo->init_freq[chip] -= MINION_FREQ_FACTOR;
						init_freq -= MINION_FREQ_FACTOR;

						set_freq(minioncgpu, minioninfo, chip, init_freq);
					}

					if (init_freq == want_freq)
						minioninfo->changing[chip] = false;
				}

				while (++tries < 4) {
					res = cmd = 0;
					fifo_task.chip = chip;
					fifo_task.reply = 0;
					minion_txrx(&fifo_task);
					if (fifo_task.reply <= 0) {
						minioninfo->spi_errors++;
						minioninfo->fifo_spi_errors[chip]++;
						minioninfo->res_err_count[chip]++;
						break;
					} else {
						if (fifo_task.reply < (int)(fifo_task.osiz)) {
							char *buf = bin2hex((unsigned char *)(&(fifo_task.rbuf[fifo_task.osiz - fifo_task.rsiz])),
										(int)(fifo_task.rsiz));
							applog(LOG_DEBUG, "%s%i: Chip %d Bad fifo reply (%s) size %d, should be %d",
									  minioncgpu->drv->name, minioncgpu->device_id,
									  chip, buf,
									  fifo_task.reply, (int)(fifo_task.osiz));
							free(buf);
							minioninfo->spi_errors++;
							minioninfo->fifo_spi_errors[chip]++;
							minioninfo->res_err_count[chip]++;
						} else {
							if (fifo_task.reply > (int)(fifo_task.osiz)) {
								applog(LOG_DEBUG, "%s%i: Chip %d Unexpected fifo reply size %d, "
										  "expected only %d",
										  minioncgpu->drv->name, minioncgpu->device_id,
										  chip, fifo_task.reply, (int)(fifo_task.osiz));
							}
							res = FIFO_RES(fifo_task.rbuf, fifo_task.osiz - fifo_task.rsiz);
							cmd = FIFO_CMD(fifo_task.rbuf, fifo_task.osiz - fifo_task.rsiz);
							// valid reply?
							if (res <= MINION_QUE_MAX && cmd <= MINION_QUE_MAX)
								break;

							applog(LOG_DEBUG, "%s%i: Chip %d Bad fifo reply res %d (max is %d) "
									  "cmd %d (max is %d)",
									  minioncgpu->drv->name, minioncgpu->device_id,
									  chip, (int)res, MINION_QUE_MAX,
									  (int)cmd, MINION_QUE_MAX);
							minioninfo->spi_errors++;
							minioninfo->fifo_spi_errors[chip]++;
							minioninfo->res_err_count[chip]++;
						}
					}
				}

				// Give up on this chip this round
				if (tries >= 4)
					continue;

				K_WLOCK(minioninfo->wwork_list);
				// have to just assume it's always correct since we can't verify it
				minioninfo->chip_status[chip].realwork = (uint32_t)cmd;
#if MINION_SHOW_IO
				applog(IOCTRL_LOG, "SetReal chip %d cw=%u rw==%u qw=%u",
						   chip,
						   minioninfo->chip_status[chip].chipwork,
						   minioninfo->chip_status[chip].realwork,
						   minioninfo->chip_status[chip].quework);
#endif
				K_WUNLOCK(minioninfo->wwork_list);

				if (cmd < MINION_QUE_LOW) {
					somelow = true;
					// Flag it in case the count is wrong
					K_WLOCK(minioninfo->wwork_list);
					minioninfo->chip_status[chip].islow = true;
					minioninfo->chip_status[chip].lowcount = (int)cmd;
					K_WUNLOCK(minioninfo->wwork_list);
				}

				/*
				 * Chip has results?
				 * You can't request results unless it says it has some.
				 * We don't ever directly flush the output queue while processing
				 * (except at startup) so the answer is always valid
				 * i.e. there could be more, but never less ... unless the reply was corrupt
				 */
				if (res > MINION_MAX_RES) {
					applog(LOG_ERR, "%s%i: Large work reply chip %d res %d",
							minioncgpu->drv->name, minioncgpu->device_id, chip, res);
					minioninfo->spi_errors++;
					minioninfo->fifo_spi_errors[chip]++;
					minioninfo->res_err_count[chip]++;
					res = 1; // Just read one result
				}
//else
//applog(LOG_ERR, "%s%i: work reply res %d", minioncgpu->drv->name, minioncgpu->device_id, res);
				uint8_t left = res;
				int peeks = 0;
				while (left > 0) {
					res = left;
					if (res > MINION_MAX_RES)
						res = MINION_MAX_RES;
					left -= res;
repeek:
					res1_task.chip = chip;
					res1_task.reply = 0;
					res1_task.rsiz = res * MINION_RES_DATA_SIZ;
					minion_txrx(&res1_task);
					if (res1_task.reply <= 0)
						break;
					else {
						cgtime(&now);
						if (res1_task.reply < (int)MINION_RES_DATA_SIZ) {
							char *buf = bin2hex((unsigned char *)(&(res1_task.rbuf[res1_task.osiz - res1_task.rsiz])), (int)(res1_task.rsiz));
							applog(LOG_ERR, "%s%i: Chip %d Bad work reply (%s) size %d, should be at least %d",
									minioncgpu->drv->name, minioncgpu->device_id,
									chip, buf,
									res1_task.reply, (int)MINION_RES_DATA_SIZ);
							free(buf);
							minioninfo->spi_errors++;
							minioninfo->res_spi_errors[chip]++;
							minioninfo->res_err_count[chip]++;
						} else {
							if (res1_task.reply != (int)(res1_task.osiz)) {
								applog(LOG_ERR, "%s%i: Chip %d Unexpected work reply size %d, expected %d",
										minioncgpu->drv->name, minioncgpu->device_id,
										chip, res1_task.reply, (int)(res1_task.osiz));
								minioninfo->spi_errors++;
								minioninfo->res_spi_errors[chip]++;
								minioninfo->res_err_count[chip]++;
								// Can retry a PEEK without losing data
								if (minreread) {
									if (++peeks < 4)
										goto repeek;
									break;
								}
							}

							if (minreread) {
								res2_task.chip = chip;
								res2_task.reply = 0;
								res2_task.rsiz = res * MINION_RES_DATA_SIZ;
								minion_txrx(&res2_task);
								if (res2_task.reply <= 0) {
									minioninfo->spi_errors++;
									minioninfo->res_spi_errors[chip]++;
									minioninfo->res_err_count[chip]++;
								}
							}

							for (resoff = res1_task.osiz - res1_task.rsiz; resoff < (int)res1_task.osiz; resoff += MINION_RES_DATA_SIZ) {
								result1 = (struct minion_result *)&(res1_task.rbuf[resoff]);
								if (minreread && resoff < (int)res2_task.osiz)
									result2 = (struct minion_result *)&(res2_task.rbuf[resoff]);
								else
									result2 = NULL;

								if (IS_RESULT(result1) || (minreread && result2 && IS_RESULT(result2))) {
									K_WLOCK(minioninfo->rfree_list);
									item = k_unlink_head(minioninfo->rfree_list);
									K_WUNLOCK(minioninfo->rfree_list);

									if (IS_RESULT(result1)) {
										use1 = result1;
										if (minreread && result2 && IS_RESULT(result2))
											use2 = result2;
										else
											use2 = NULL;
									} else {
										use1 = result2;
										use2 = NULL;
										minioninfo->use_res2[chip]++;
									}

									//DATA_RES(item)->chip = RES_CHIPID(use1);
									// We can avoid any SPI transmission error of the chip number
									DATA_RES(item)->chip = (uint8_t)chip;
									if (minioninfo->chipid[chip] != RES_CHIPID(use1)) {
										minioninfo->spi_errors++;
										minioninfo->res_spi_errors[chip]++;
										minioninfo->res_err_count[chip]++;
									}
									if (use2 && minioninfo->chipid[chip] != RES_CHIPID(use2)) {
										minioninfo->spi_errors++;
										minioninfo->res_spi_errors[chip]++;
										minioninfo->res_err_count[chip]++;
									}
									DATA_RES(item)->core = RES_CORE(use1);
									DATA_RES(item)->task_id = RES_TASK(use1);
									DATA_RES(item)->nonce = RES_NONCE(use1);
									DATA_RES(item)->no_nonce = !RES_GOLD(use1);
									memcpy(&(DATA_RES(item)->when), &now, sizeof(now));
									applog(LOG_DEBUG, "%s%i: reply task_id 0x%04x"
											  " - chip %d - gold %d",
											  minioncgpu->drv->name,
											  minioncgpu->device_id,
											  RES_TASK(use1),
											  (int)RES_CHIPID(use1),
											  (int)RES_GOLD(use1));

									if (!use2)
										DATA_RES(item)->another = false;
									else {
										DATA_RES(item)->another = true;
										DATA_RES(item)->task_id2 = RES_TASK(use2);
										DATA_RES(item)->nonce2 = RES_NONCE(use2);
									}
//if (RES_GOLD(use1))
//applog(MINTASK_LOG, "%s%i: found a result chip %d core %d task 0x%04x nonce 0x%08x gold=%d", minioncgpu->drv->name, minioncgpu->device_id, DATA_RES(item)->chip, DATA_RES(item)->core, DATA_RES(item)->task_id, DATA_RES(item)->nonce, (int)RES_GOLD(use1));

									K_WLOCK(minioninfo->rnonce_list);
									k_add_head(minioninfo->rnonce_list, item);
									K_WUNLOCK(minioninfo->rnonce_list);

									if (!(minioninfo->chip_status[chip].first_nonce.tv_sec)) {
										cgtime(&(minioninfo->chip_status[chip].first_nonce));
										minioninfo->chip_status[chip].from_first_good = 0;
									}

									cgsem_post(&(minioninfo->nonce_ready));
								} else {
									minioninfo->res_err_count[chip]++;
									applog(MINTASK_LOG, "%s%i: Invalid res0 task_id 0x%04x - chip %d",
											    minioncgpu->drv->name, minioncgpu->device_id,
											    RES_TASK(result1), chip);
									if (minreread && result2) {
										applog(MINTASK_LOG, "%s%i: Invalid res1 task_id 0x%04x - chip %d",
												    minioncgpu->drv->name, minioncgpu->device_id,
												    RES_TASK(result2), chip);
									}
								}
							}
						}
					}
				}
			}
		}

		if (somelow)
			cgsem_post(&(minioninfo->scan_work));

#if ENABLE_INT_NONO
		if (gotreplies)
			minion_txrx(&clr_task);
#endif

#if !ENABLE_INT_NONO
		cgsleep_ms(MINION_REPLY_mS);
#else
		// TODO: this is going to require a bit of tuning with 2TH/s mining:
		// The interrupt size MINION_RESULT_INT_SIZE should be high enough to expect
		// most chips to have some results but low enough to cause negligible latency
		// If all chips don't have some results when an interrupt occurs, then it is a waste
		// since we have to check all chips for results anyway since we don't know which one
		// caused the interrupt
		// MINION_REPLY_mS needs to be low enough in the case of bad luck where no chip
		// finds MINION_RESULT_INT_SIZE results in a short amount of time, so we go check
		// them all anyway - to avoid high latency when there are only a few results due to low luck
		ret = poll(&pfd, 1, MINION_REPLY_mS);
		if (ret > 0) {
			bool gotres;
			int c;

			minioninfo->interrupts++;

			read(minioninfo->gpiointfd, &c, 1);

//			applog(LOG_ERR, "%s%i: Interrupt2",
//					minioncgpu->drv->name,
//					minioncgpu->device_id);

			gotres = false;
			for (chip = 0; chip < (int)MINION_CHIPS; chip++) {
				if (minioninfo->has_chip[chip]) {
					SET_HEAD_READ(head, MINION_SYS_INT_STA);
					head->chipid = minioninfo->chipid[chip];
					reply = do_ioctl(CHIP_PIN(chip), wbuf, wsiz, rbuf, rsiz, &ioseq);
					if (reply != (int)wsiz) {
						applog(LOG_ERR, "%s: chip %d int status returned %d"
								" (should be %d)",
								minioncgpu->drv->dname,
								chip, reply, (int)wsiz);
					}

					snprintf(minioninfo->last_interrupt,
						 sizeof(minioninfo->last_interrupt),
						 "%d %d 0x%02x%02x%02x%02x%02x%02x%02x%02x %d %d 0x%02x %d %d",
						 (int)(minioninfo->interrupts), chip,
						 rbuf[0], rbuf[1], rbuf[2], rbuf[3],
						 rbuf[4], rbuf[5], rbuf[6], rbuf[7],
						 (int)wsiz, (int)rsiz, rbuf[wsiz - rsiz],
						 rbuf[wsiz - rsiz] & MINION_RESULT_INT,
						 rbuf[wsiz - rsiz] & MINION_CMD_INT);

					if ((rbuf[wsiz - rsiz] & MINION_RESULT_INT) != 0) {
						gotres = true;
						(minioninfo->result_interrupts)++;
//						applog(LOG_ERR, "%s%i: chip %d got RES interrupt",
//								minioncgpu->drv->name,
//								minioncgpu->device_id,
//								chip);
					}

					if ((rbuf[wsiz - rsiz] & MINION_CMD_INT) != 0) {
						// Work queue is empty
						(minioninfo->command_interrupts)++;
//						applog(LOG_ERR, "%s%i: chip %d got CMD interrupt",
//								minioncgpu->drv->name,
//								minioncgpu->device_id,
//								chip);
					}

//					char *tmp;
//					tmp = bin2hex(rbuf, wsiz);
//					applog(LOG_ERR, "%s%i: chip %d interrupt: %s",
//							minioncgpu->drv->name,
//							minioncgpu->device_id,
//							chip, tmp);
//					free(tmp);

					// Don't clear either interrupt until after send/recv
				}
			}

			// Doing this last means we can't miss an interrupt
			if (gotres)
				cgsem_post(&(minioninfo->scan_work));
		}
#endif
	}

	return NULL;
}

/*
 * Find the matching work item for this chip
 * Discard any older work items for this chip
 */

enum nonce_state {
	NONCE_GOOD_NONCE,
	NONCE_NO_NONCE,
	NONCE_DUP_NONCE,
	NONCE_BAD_NONCE,
	NONCE_BAD_WORK,
	NONCE_NO_WORK,
	NONCE_SPI_ERR
};

static void cleanup_older(struct cgpu_info *minioncgpu, int chip, K_ITEM *item, bool no_nonce)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	K_ITEM *tail;
//	bool errs;

	/* remove older ioseq work items
	   no_nonce means this 'item' has finished also */
	tail = minioninfo->wchip_list[chip]->tail;
	while (tail && (DATA_WORK(tail)->ioseq < DATA_WORK(item)->ioseq)) {
		k_unlink_item(minioninfo->wchip_list[chip], tail);
		if (!(DATA_WORK(tail)->stale)) {
			minioninfo->chip_status[chip].chipwork--;
#if MINION_SHOW_IO
			applog(IOCTRL_LOG, "COld chip %d cw-1=%u rw=%u qw=%u",
					   chip,
					   minioninfo->chip_status[chip].chipwork,
					   minioninfo->chip_status[chip].realwork,
					   minioninfo->chip_status[chip].quework);
#endif
/*
			// If it had no valid work (only errors) then it won't have been cleaned up
			errs = (DATA_WORK(tail)->errors > 0);
			applog(errs ? LOG_DEBUG : LOG_ERR,
			applog(LOG_ERR,
				"%s%i: discarded old task 0x%04x chip %d no reply errs=%d",
				minioncgpu->drv->name, minioncgpu->device_id,
				DATA_WORK(tail)->task_id, chip, DATA_WORK(tail)->errors);
*/
		}
		applog(MINION_LOG, "%s%i: marking complete - old task 0x%04x chip %d",
				   minioncgpu->drv->name, minioncgpu->device_id,
				   DATA_WORK(tail)->task_id, chip);
		if (DATA_WORK(tail)->rolled)
			free_work(DATA_WORK(tail)->work);
		else
			work_completed(minioncgpu, DATA_WORK(tail)->work);
		k_free_head(minioninfo->wfree_list, tail);
		tail = minioninfo->wchip_list[chip]->tail;
	}
	if (no_nonce) {
		if (!(DATA_WORK(item)->stale)) {
			minioninfo->chip_status[chip].chipwork--;
#if MINION_SHOW_IO
		applog(IOCTRL_LOG, "CONoN chip %d cw-1=%u rw=%u qw=%u",
				   chip,
				   minioninfo->chip_status[chip].chipwork,
				   minioninfo->chip_status[chip].realwork,
				   minioninfo->chip_status[chip].quework);
#endif
		}
		applog(MINION_LOG, "%s%i: marking complete - no_nonce task 0x%04x chip %d",
				   minioncgpu->drv->name, minioncgpu->device_id,
				   DATA_WORK(item)->task_id, chip);
		if (DATA_WORK(item)->rolled)
			free_work(DATA_WORK(item)->work);
		else
			work_completed(minioncgpu, DATA_WORK(item)->work);
	}
}

// Need to put it back in the list where it was - according to ioseq
static void restorework(struct minion_info *minioninfo, int chip, K_ITEM *item)
{
	K_ITEM *look;

	look = minioninfo->wchip_list[chip]->tail;
	while (look && DATA_WORK(look)->ioseq < DATA_WORK(item)->ioseq)
		look = look->prev;
	if (!look)
		k_add_head(minioninfo->wchip_list[chip], item);
	else
		k_insert_after(minioninfo->wchip_list[chip], item, look);
}

static enum nonce_state oknonce(struct thr_info *thr, struct cgpu_info *minioncgpu, int chip, int core,
				uint32_t task_id, uint32_t nonce, bool no_nonce, struct timeval *when,
				bool another, uint32_t task_id2, uint32_t nonce2)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	struct timeval now;
	K_ITEM *item, *tail;
	uint32_t min_task_id, max_task_id;
//	uint64_t chip_good;
	bool redo;

	// if the chip has been disabled - but we don't do that - so not possible (yet)
	if (!(minioninfo->has_chip[chip])) {
		minioninfo->spi_errors++;
		applog(MINTASK_LOG, "%s%i: nonce error chip %d not present",
				    minioncgpu->drv->name, minioncgpu->device_id, chip);
		return NONCE_NO_WORK;
	}

	if (core < 0 || core >= MINION_CORES) {
		minioninfo->spi_errors++;
		minioninfo->res_spi_errors[chip]++;
		minioninfo->res_err_count[chip]++;
		applog(MINTASK_LOG, "%s%i: SPI nonce error invalid core %d (chip %d)",
				    minioncgpu->drv->name, minioncgpu->device_id, core, chip);

		// use the fake core number so we don't discard the result
		core = FAKE_CORE;
	}

	if (no_nonce)
		minioninfo->chip_nononces[chip]++;
	else
		minioninfo->chip_nonces[chip]++;

	redo = false;
retry:
	K_WLOCK(minioninfo->wchip_list[chip]);
	item = minioninfo->wchip_list[chip]->tail;

	if (!item) {
		K_WUNLOCK(minioninfo->wchip_list[chip]);
		minioninfo->spi_errors++;
		minioninfo->res_spi_errors[chip]++;
		minioninfo->res_err_count[chip]++;
		applog(MINTASK_LOG, "%s%i: chip %d has no tasks (core %d task 0x%04x)",
				    minioncgpu->drv->name, minioncgpu->device_id,
				chip, core, (int)task_id);
		if (!no_nonce) {
			minioninfo->untested_nonces++;
			minioninfo->chip_err[chip]++;
		}
		return NONCE_NO_WORK;
	}

	min_task_id = DATA_WORK(item)->task_id;
	while (item) {
		if (DATA_WORK(item)->task_id == task_id)
			break;

		item = item->prev;
	}
	max_task_id = DATA_WORK(minioninfo->wchip_list[chip]->head)->task_id;

	if (!item) {
		K_WUNLOCK(minioninfo->wchip_list[chip]);
		if (another && task_id != task_id2) {
			minioninfo->tasks_failed[chip]++;
			task_id = task_id2;
			redo = true;
			goto retry;
		}

		minioninfo->spi_errors++;
		minioninfo->res_spi_errors[chip]++;
		minioninfo->res_err_count[chip]++;
		applog(MINTASK_LOG, "%s%i: chip %d core %d unknown task 0x%04x "
				    "(min=0x%04x max=0x%04x no_nonce=%d)",
				    minioncgpu->drv->name, minioncgpu->device_id,
				    chip, core, (int)task_id, (int)min_task_id,
				    (int)max_task_id, no_nonce);
		if (!no_nonce) {
			minioninfo->untested_nonces++;
			minioninfo->chip_err[chip]++;
		}
		return NONCE_BAD_WORK;
	}
	if (redo)
		minioninfo->tasks_recovered[chip]++;

	k_unlink_item(minioninfo->wchip_list[chip], item);
	if (no_nonce) {
		cleanup_older(minioncgpu, chip, item, no_nonce);
		k_free_head(minioninfo->wfree_list, item);
		K_WUNLOCK(minioninfo->wchip_list[chip]);
		return NONCE_NO_NONCE;
	}
	K_WUNLOCK(minioninfo->wchip_list[chip]);

	minioninfo->tested_nonces++;

	redo = false;
retest:
	if (test_nonce(DATA_WORK(item)->work, nonce)) {
/*
		if (isdupnonce(minioncgpu, DATA_WORK(item)->work, nonce)) {
			minioninfo->chip_dup[chip]++;
			applog(LOG_WARNING, " ... nonce %02x%02x%02x%02x chip %d core %d task 0x%04x",
					    (nonce & 0xff), ((nonce >> 8) & 0xff),
					    ((nonce >> 16) & 0xff), ((nonce >> 24) & 0xff),
					    chip, core, task_id);
			K_WLOCK(minioninfo->wchip_list[chip]);
			restorework(minioninfo, chip, item);
			K_WUNLOCK(minioninfo->wchip_list[chip]);
			return NONCE_DUP_NONCE;
		}
*/
//applog(MINTASK_LOG, "%s%i: Valid Nonce chip %d core %d task 0x%04x nonce 0x%08x", minioncgpu->drv->name, minioncgpu->device_id, chip, core, task_id, nonce);
//
		submit_tested_work(thr, DATA_WORK(item)->work);

		if (redo)
			minioninfo->nonces_recovered[chip]++;

		/* chip_good = */ ++(minioninfo->chip_good[chip]);
		minioninfo->chip_status[chip].from_first_good++;
		minioninfo->core_good[chip][core]++;
		DATA_WORK(item)->nonces++;

		mutex_lock(&(minioninfo->nonce_lock));
		minioninfo->new_nonces++;
		mutex_unlock(&(minioninfo->nonce_lock));
		minioninfo->ok_nonces++;

		K_WLOCK(minioninfo->wchip_list[chip]);
		cleanup_older(minioncgpu, chip, item, no_nonce);
		restorework(minioninfo, chip, item);
		K_WUNLOCK(minioninfo->wchip_list[chip]);

		// add to history and remove old history and keep track of the 2 reset marks
		int chip_tmp;
		cgtime(&now);
		K_WLOCK(minioninfo->hfree_list);
		item = k_unlink_head(minioninfo->hfree_list);
		memcpy(&(DATA_HIST(item)->when), when, sizeof(*when));
		k_add_head(minioninfo->hchip_list[chip], item);
		if (minioninfo->reset_mark[chip])
			minioninfo->reset_count[chip]++;
		if (second_check && minioninfo->reset2_mark[chip])
			minioninfo->reset2_count[chip]++;

		// N.B. this also corrects each reset_mark/reset_count within each hchip_list
		for (chip_tmp = 0; chip_tmp < (int)MINION_CHIPS; chip_tmp++) {
			tail = minioninfo->hchip_list[chip_tmp]->tail;
			while (tail && tdiff(&(DATA_HIST(tail)->when), &now) > MINION_HISTORY_s) {
				if (minioninfo->reset_mark[chip] == tail) {
					minioninfo->reset_mark[chip] = tail->prev;
					minioninfo->reset_count[chip]--;
				}
				if (second_check && minioninfo->reset2_mark[chip] == tail) {
					minioninfo->reset2_mark[chip] = tail->prev;
					minioninfo->reset2_count[chip]--;
				}
				tail = k_unlink_tail(minioninfo->hchip_list[chip_tmp]);
				k_add_head(minioninfo->hfree_list, item);
				tail = minioninfo->hchip_list[chip_tmp]->tail;
			}
			if (!(minioninfo->reset_mark[chip])) {
				minioninfo->reset_mark[chip] = minioninfo->hchip_list[chip]->tail;
				minioninfo->reset_count[chip] = minioninfo->hchip_list[chip]->count;
			}
			if (second_check && !(minioninfo->reset2_mark[chip])) {
				minioninfo->reset2_mark[chip] = minioninfo->hchip_list[chip]->tail;
				minioninfo->reset2_count[chip] = minioninfo->hchip_list[chip]->count;
			}
			tail = minioninfo->reset_mark[chip];
			while (tail && tdiff(&(DATA_HIST(tail)->when), &now) > minioninfo->reset_time[chip]) {
				tail = minioninfo->reset_mark[chip] = tail->prev;
				minioninfo->reset_count[chip]--;
			}
			if (second_check) {
				tail = minioninfo->reset2_mark[chip];
				while (tail && tdiff(&(DATA_HIST(tail)->when), &now) > minioninfo->reset2_time[chip]) {
					tail = minioninfo->reset2_mark[chip] = tail->prev;
					minioninfo->reset2_count[chip]--;
				}
			}
		}
		K_WUNLOCK(minioninfo->hfree_list);

/*
		// Reset the chip after 8 nonces found
		if (chip_good == 8) {
			memcpy(&(minioninfo->last_reset[chip]), &now, sizeof(now));
			init_chip(minioncgpu, minioninfo, chip);
		}
*/

		return NONCE_GOOD_NONCE;
	}

	if (another && nonce != nonce2) {
		minioninfo->nonces_failed[chip]++;
		nonce = nonce2;
		redo = true;
		goto retest;
	}

	DATA_WORK(item)->errors++;
	K_WLOCK(minioninfo->wchip_list[chip]);
	restorework(minioninfo, chip, item);
	K_WUNLOCK(minioninfo->wchip_list[chip]);

	minioninfo->chip_bad[chip]++;
	minioninfo->core_bad[chip][core]++;
	inc_hw_errors(thr);
//applog(MINTASK_LOG, "%s%i: HW ERROR chip %d core %d task 0x%04x nonce 0x%08x", minioncgpu->drv->name, minioncgpu->device_id, chip, core, task_id, nonce);

	return NONCE_BAD_NONCE;
}

/* Check each chip how long since the last nonce
 * Should normally be a fraction of a second
 * so (MINION_RESET_s * 1.5) will certainly be long enough,
 * but also will avoid lots of resets if there is trouble getting work
 * Should be longer than MINION_RESET_s to avoid interfering with normal resets */
static void check_last_nonce(struct cgpu_info *minioncgpu)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	struct timeval now;
	K_ITEM *head;
	double howlong;
	int chip;

	cgtime(&now);
	K_RLOCK(minioninfo->hfree_list);
	for (chip = 0; chip < (int)MINION_CHIPS; chip++) {
		if (minioninfo->has_chip[chip] &&  !(minioninfo->changing[chip])) {
			head = minioninfo->hchip_list[chip]->head;
			if (head) {
				howlong = tdiff(&now, &(DATA_HIST(head)->when));
				if (howlong > ((double)MINION_RESET_s * 1.5)) {
					// Setup a reset
					minioninfo->flag_reset[chip] = true;
					minioninfo->do_reset[chip] = 0.0;
				}
			}
		}
	}
	K_RUNLOCK(minioninfo->hfree_list);
}

// Results checking thread
static void *minion_results(void *userdata)
{
	struct cgpu_info *minioncgpu = (struct cgpu_info *)userdata;
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	struct thr_info *thr;
	int chip = 0, core = 0;
	uint32_t task_id = 0;
	uint32_t nonce = 0;
	bool no_nonce = false;
	struct timeval when;
	bool another;
	uint32_t task_id2 = 0;
	uint32_t nonce2 = 0;
	int last_check;

	applog(MINION_LOG, "%s%i: Results...",
				minioncgpu->drv->name, minioncgpu->device_id);

	// Wait until we're ready
	while (minioncgpu->shutdown == false) {
		if (minioninfo->initialised) {
			break;
		}
		cgsleep_ms(3);
	}

	thr = minioninfo->thr;

	last_check = 0;
	while (minioncgpu->shutdown == false) {
		if (!oldest_nonce(minioncgpu, &chip, &core, &task_id, &nonce,
				  &no_nonce, &when, &another, &task_id2, &nonce2)) {
			check_last_nonce(minioncgpu);
			last_check = 0;
			cgsem_mswait(&(minioninfo->nonce_ready), MINION_NONCE_mS);
			continue;
		}

		oknonce(thr, minioncgpu, chip, core, task_id, nonce, no_nonce, &when,
			another, task_id2, nonce2);

		// Interrupt nonce checking if low CPU and oldest_nonce() is always true
		if (++last_check > 100) {
			check_last_nonce(minioncgpu);
			last_check = 0;
		}
	}

	return NULL;
}

static void minion_flush_work(struct cgpu_info *minioncgpu)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	K_ITEM *prev_unused, *task, *prev_task, *witem;
	int i;

	if (minioninfo->initialised == false)
		return;

	applog(MINION_LOG, "%s%i: flushing work",
				minioncgpu->drv->name, minioncgpu->device_id);

	// TODO: N.B. scanwork also gets work locks - which master thread calls flush?
	K_WLOCK(minioninfo->wwork_list);

	// Simply remove the whole unused wwork_list
	k_list_transfer_to_head(minioninfo->wwork_list, minioninfo->wstale_list);
	minioninfo->wwork_flushed += minioninfo->wstale_list->count;

	// TODO: flush/work tasks should have a block sequence number so this task removal code
	// might be better implemented in minion_spi_write where each work task would
	// update the block sequence number and any work tasks with an old block sequence
	// number would be discarded rather than sent - minion_spi_write will also need to
	// prioritise flush urgent tasks above work urgent tasks - have 3 urgent states?
	// They should however be 2 seperate variables in minioninfo to reduce locking
	// - flush will increment one and put it in the flush task, (and work will use that)
	// minion_spi_write will check/update the other and thus not need a lock

	// No deadlock since this is the only code to get 2 locks
	K_WLOCK(minioninfo->tfree_list);
	task = minioninfo->task_list->tail;
	while (task) {
		prev_task = task->prev;
		if (DATA_TASK(task)->address == WRITE_ADDR(MINION_QUE_0)) {
			minioninfo->chip_status[DATA_TASK(task)->chip].quework--;
#if MINION_SHOW_IO
			applog(IOCTRL_LOG, "QueFlush chip %d cw=%u rw=%u qw-1=%u",
					   (int)DATA_TASK(task)->chip,
					   minioninfo->chip_status[DATA_TASK(task)->chip].chipwork,
					   minioninfo->chip_status[DATA_TASK(task)->chip].realwork,
					   minioninfo->chip_status[DATA_TASK(task)->chip].quework);
#endif
			witem = DATA_TASK(task)->witem;
			k_unlink_item(minioninfo->wque_list[DATA_TASK(task)->chip], witem);
			minioninfo->wque_flushed++;
			if (DATA_WORK(witem)->rolled)
				free_work(DATA_WORK(witem)->work);
			else
				work_completed(minioncgpu, DATA_WORK(witem)->work);
			k_free_head(minioninfo->wfree_list, witem);
			k_unlink_item(minioninfo->task_list, task);
			k_free_head(minioninfo->tfree_list, task);
		}
		task = prev_task;
	}
	for (i = 0; i < (int)MINION_CHIPS; i++) {
		if (minioninfo->has_chip[i]) {
			// TODO: consider sending it now rather than adding to the task list?
			task = k_unlink_head(minioninfo->tfree_list);
			DATA_TASK(task)->tid = ++(minioninfo->next_tid);
			DATA_TASK(task)->chip = i;
			DATA_TASK(task)->write = true;
			DATA_TASK(task)->address = MINION_SYS_RSTN_CTL;
			DATA_TASK(task)->task_id = 0; // ignored
			DATA_TASK(task)->wsiz = MINION_SYS_SIZ;
			DATA_TASK(task)->rsiz = 0;
			DATA_TASK(task)->wbuf[0] = SYS_RSTN_CTL_FLUSH;
			DATA_TASK(task)->wbuf[1] = 0;
			DATA_TASK(task)->wbuf[2] = 0;
			DATA_TASK(task)->wbuf[3] = 0;
			DATA_TASK(task)->urgent = true;
			k_add_head(minioninfo->task_list, task);
		}
	}
	K_WUNLOCK(minioninfo->tfree_list);

	K_WUNLOCK(minioninfo->wwork_list);

	// TODO: send a signal to force getting and sending new work - needs cgsem_wait in the sending thread

	// TODO: should we use this thread to do the following work?
	if (minioninfo->wstale_list->count) {
		// mark complete all stale unused work (oldest first)
		prev_unused = minioninfo->wstale_list->tail;
		while (prev_unused) {
			if (DATA_WORK(prev_unused)->rolled)
				free_work(DATA_WORK(prev_unused)->work);
			else
				work_completed(minioncgpu, DATA_WORK(prev_unused)->work);
			prev_unused = prev_unused->prev;
		}

		// put them back in the wfree_list
		K_WLOCK(minioninfo->wfree_list);
		k_list_transfer_to_head(minioninfo->wstale_list, minioninfo->wfree_list);
		K_WUNLOCK(minioninfo->wfree_list);
	}
}

static void sys_chip_sta(struct cgpu_info *minioncgpu, int chip)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	struct timeval now;
	K_ITEM *item;
	int limit, rep;

	cgtime(&now);
	// No lock required since 'last' is only accessed here
	if (minioninfo->chip_status[chip].last.tv_sec == 0) {
		memcpy(&(minioninfo->chip_status[chip].last), &now, sizeof(now));
	} else {
		limit = MINION_STATS_UPDATE_TIME_mS +
			(int)(random() % MINION_STATS_UPDATE_RAND_mS);
		if (ms_tdiff(&now, &(minioninfo->chip_status[chip].last)) > limit) {
			memcpy(&(minioninfo->chip_status[chip].last), &now, sizeof(now));

			K_WLOCK(minioninfo->tfree_list);
			item = k_unlink_head(minioninfo->tfree_list);
			DATA_TASK(item)->tid = ++(minioninfo->next_tid);
			K_WUNLOCK(minioninfo->tfree_list);

			DATA_TASK(item)->chip = chip;
			DATA_TASK(item)->write = false;
			DATA_TASK(item)->address = READ_ADDR(MINION_SYS_CHIP_STA);
			DATA_TASK(item)->task_id = 0;
			DATA_TASK(item)->wsiz = 0;
			DATA_TASK(item)->rsiz = MINION_SYS_SIZ;
			DATA_TASK(item)->urgent = false;

			K_WLOCK(minioninfo->task_list);
			k_add_head(minioninfo->task_list, item);
			item = k_unlink_head(minioninfo->tfree_list);
			DATA_TASK(item)->tid = ++(minioninfo->next_tid);
			K_WUNLOCK(minioninfo->task_list);

			DATA_TASK(item)->chip = chip;
			DATA_TASK(item)->write = false;
			DATA_TASK(item)->address = READ_ADDR(MINION_SYS_IDLE_CNT);
			DATA_TASK(item)->task_id = 0;
			DATA_TASK(item)->wsiz = 0;
			DATA_TASK(item)->rsiz = MINION_SYS_SIZ;
			DATA_TASK(item)->urgent = false;

			K_WLOCK(minioninfo->task_list);
			k_add_head(minioninfo->task_list, item);
			K_WUNLOCK(minioninfo->task_list);

			// Get the core ena and act state
			for (rep = 0; rep < MINION_CORE_REPS; rep++) {
				// Ena
				K_WLOCK(minioninfo->tfree_list);
				item = k_unlink_head(minioninfo->tfree_list);
				DATA_TASK(item)->tid = ++(minioninfo->next_tid);
				K_WUNLOCK(minioninfo->tfree_list);

				DATA_TASK(item)->chip = chip;
				DATA_TASK(item)->write = false;
				DATA_TASK(item)->address = READ_ADDR(MINION_CORE_ENA0_31 + rep);
				DATA_TASK(item)->task_id = 0;
				DATA_TASK(item)->wsiz = 0;
				DATA_TASK(item)->rsiz = MINION_SYS_SIZ;
				DATA_TASK(item)->urgent = false;

				K_WLOCK(minioninfo->task_list);
				k_add_head(minioninfo->task_list, item);
				// Act
				item = k_unlink_head(minioninfo->tfree_list);
				DATA_TASK(item)->tid = ++(minioninfo->next_tid);
				K_WUNLOCK(minioninfo->task_list);

				DATA_TASK(item)->chip = chip;
				DATA_TASK(item)->write = false;
				DATA_TASK(item)->address = READ_ADDR(MINION_CORE_ACT0_31 + rep);
				DATA_TASK(item)->task_id = 0;
				DATA_TASK(item)->wsiz = 0;
				DATA_TASK(item)->rsiz = MINION_SYS_SIZ;
				DATA_TASK(item)->urgent = false;

				K_WLOCK(minioninfo->task_list);
				k_add_head(minioninfo->task_list, item);
				K_WUNLOCK(minioninfo->task_list);
			}

			if (minioninfo->lednow[chip] != minioninfo->setled[chip]) {
				uint32_t led;

				minioninfo->lednow[chip] = minioninfo->setled[chip];
				if (minioninfo->lednow[chip])
					led = MINION_SPI_LED_ON;
				else
					led = MINION_SPI_LED_OFF;

				K_WLOCK(minioninfo->tfree_list);
				item = k_unlink_head(minioninfo->tfree_list);
				DATA_TASK(item)->tid = ++(minioninfo->next_tid);
				K_WUNLOCK(minioninfo->tfree_list);

				DATA_TASK(item)->chip = chip;
				DATA_TASK(item)->write = true;
				DATA_TASK(item)->address = MINION_SYS_SPI_LED;
				DATA_TASK(item)->task_id = 0;
				DATA_TASK(item)->wsiz = MINION_SYS_SIZ;
				DATA_TASK(item)->rsiz = 0;
				DATA_TASK(item)->wbuf[0] = led & 0xff;
				DATA_TASK(item)->wbuf[1] = (led >> 8) & 0xff;
				DATA_TASK(item)->wbuf[2] = (led >> 16) & 0xff;
				DATA_TASK(item)->wbuf[3] = (led >> 24) & 0xff;
				DATA_TASK(item)->urgent = false;

				K_WLOCK(minioninfo->task_list);
				k_add_head(minioninfo->task_list, item);
				K_WUNLOCK(minioninfo->task_list);
			}
		}
	}
}

static void new_work_task(struct cgpu_info *minioncgpu, K_ITEM *witem, int chip, bool urgent, uint8_t state)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	struct minion_que *que;
	K_ITEM *item;

	K_WLOCK(minioninfo->tfree_list);
	item = k_unlink_head(minioninfo->tfree_list);
	DATA_TASK(item)->tid = ++(minioninfo->next_tid);
	K_WUNLOCK(minioninfo->tfree_list);

	DATA_TASK(item)->chip = chip;
	DATA_TASK(item)->write = true;
	DATA_TASK(item)->address = MINION_QUE_0;

	// if threaded access to new_work_task() is added, this will need locking
	// Don't use task_id 0 so that we can ignore all '0' work replies
	// ... and report them as errors
	if (minioninfo->next_task_id == 0)
		minioninfo->next_task_id = 1;
	DATA_TASK(item)->task_id = minioninfo->next_task_id;
	DATA_WORK(witem)->task_id = minioninfo->next_task_id;
	minioninfo->next_task_id = (minioninfo->next_task_id + 1) & MINION_MAX_TASK_ID;

	DATA_TASK(item)->urgent = urgent;
	DATA_TASK(item)->work_state = state;
	DATA_TASK(item)->work = DATA_WORK(witem)->work;
	DATA_TASK(item)->witem = witem;

	que = (struct minion_que *)&(DATA_TASK(item)->wbuf[0]);
	que->task_id[0] = DATA_TASK(item)->task_id & 0xff;
	que->task_id[1] = (DATA_TASK(item)->task_id & 0xff00) >> 8;

	memcpy(&(que->midstate[0]), &(DATA_WORK(witem)->work->midstate[0]), MIDSTATE_BYTES);
	memcpy(&(que->merkle7[0]), &(DATA_WORK(witem)->work->data[MERKLE7_OFFSET]), MERKLE_BYTES);

	DATA_TASK(item)->wsiz = (int)sizeof(*que);
	DATA_TASK(item)->rsiz = 0;

	K_WLOCK(minioninfo->wque_list[chip]);
	k_add_head(minioninfo->wque_list[chip], witem);
	minioninfo->chip_status[chip].quework++;
#if MINION_SHOW_IO
	applog(IOCTRL_LOG, "Que chip %d cw=%u rw=%u qw+1=%u",
			   chip,
			   minioninfo->chip_status[chip].chipwork,
			   minioninfo->chip_status[chip].realwork,
			   minioninfo->chip_status[chip].quework);
#endif
	K_WUNLOCK(minioninfo->wque_list[chip]);

	K_WLOCK(minioninfo->task_list);
	k_add_head(minioninfo->task_list, item);
	K_WUNLOCK(minioninfo->task_list);

	if (urgent)
		cgsem_post(&(minioninfo->task_ready));

	// N.B. this will only update often enough if a chip is > ~2GH/s
	if (!urgent)
		sys_chip_sta(minioncgpu, chip);
}

// TODO: stale work ...
static K_ITEM *next_work(struct minion_info *minioninfo)
{
	K_ITEM *item;
	struct timeval now;
	double howlong;
	int i;

	K_WLOCK(minioninfo->wwork_list);
	item = k_unlink_tail(minioninfo->wwork_list);
	K_WUNLOCK(minioninfo->wwork_list);
	if (item) {
		cgtime(&now);
		howlong = tdiff(&now, &(DATA_WORK(item)->created));
		minioninfo->que_work++;
		minioninfo->que_time += howlong;
		if (minioninfo->que_min == 0 || minioninfo->que_min > howlong)
			minioninfo->que_min = howlong;
		else if (minioninfo->que_max < howlong)
			minioninfo->que_max = howlong;
		for (i = 0; i < TIME_BANDS; i++) {
			if (howlong < time_bands[i]) {
				minioninfo->que_bands[i]++;
				break;
			}
		}
		if (i >= TIME_BANDS)
			minioninfo->que_bands[TIME_BANDS]++;
	}

	return item;
}

static void minion_do_work(struct cgpu_info *minioncgpu)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	int count, chip, j, lowcount;
	TASK_ITEM fifo_task;
	uint8_t state, cmd;
	K_ITEM *item;
#if ENABLE_INT_NONO
	K_ITEM *task;
#endif
	bool islow, sentwork;

	fifo_task.chip = 0;
	fifo_task.write = false;
	fifo_task.address = MINION_SYS_FIFO_STA;
	fifo_task.wsiz = 0;
	fifo_task.rsiz = MINION_SYS_SIZ;

	// TODO: (remove this) Fake starved of work to test CMD Interrupt
//	if (total_secs > 120) {
//		cgsleep_ms(888);
//		return;
//	}

	/*
	 * Fill the queues as follows:
	 *	1) put at least 1 in each queue or if islow then add 1
	 *	2) push each queue up to LOW or if count is high but islow, then add LOW-1
	 *	3) push each LOW queue up to HIGH
	 */

	sentwork = false;
	for (state = 0; state < 3; state++) {
#define CHP 0
//applog(LOG_ERR, "%s%i: chip %d presta %d: quew %d chw %d", minioncgpu->drv->name, minioncgpu->device_id, CHP, state, minioninfo->chip_status[CHP].quework, minioninfo->chip_status[CHP].chipwork);
		for (chip = 0; chip < (int)MINION_CHIPS; chip++)
			minioninfo->chip_status[chip].tohigh = false;

		for (chip = 0; chip < (int)MINION_CHIPS; chip++) {
			if (minioninfo->has_chip[chip] && !minioninfo->chip_status[chip].overheat) {
				struct timeval now;
				double howlong;
				cgtime(&now);
				howlong = tdiff(&now, &(minioninfo->last_reset[chip]));
				if (howlong < MINION_RESET_DELAY_s)
					continue;

				int tries = 0;
				while (tries++ < 4) {
					cmd = 0;
					fifo_task.chip = chip;
					fifo_task.reply = 0;
					minion_txrx(&fifo_task);
					if (fifo_task.reply <= 0) {
						if (fifo_task.reply < (int)(fifo_task.osiz)) {
							char *buf = bin2hex((unsigned char *)(&(fifo_task.rbuf[fifo_task.osiz - fifo_task.rsiz])),
										(int)(fifo_task.rsiz));
							applog(LOG_ERR, "%s%i: Chip %d Bad fifo reply (%s) size %d, should be %d",
									minioncgpu->drv->name, minioncgpu->device_id,
									chip, buf,
									fifo_task.reply, (int)(fifo_task.osiz));
							free(buf);
							minioninfo->spi_errors++;
							minioninfo->fifo_spi_errors[chip]++;
							minioninfo->res_err_count[chip]++;
						} else {
							if (fifo_task.reply > (int)(fifo_task.osiz)) {
								applog(LOG_ERR, "%s%i: Chip %d Unexpected fifo reply size %d, expected only %d",
										minioncgpu->drv->name, minioncgpu->device_id,
										chip, fifo_task.reply, (int)(fifo_task.osiz));
							}
							cmd = FIFO_CMD(fifo_task.rbuf, fifo_task.osiz - fifo_task.rsiz);
							// valid reply?
							if (cmd < MINION_QUE_MAX) {
								K_WLOCK(minioninfo->wchip_list[chip]);
								minioninfo->chip_status[chip].realwork = cmd;
								K_WUNLOCK(minioninfo->wchip_list[chip]);
								if (cmd <= MINION_QUE_LOW || cmd >= MINION_QUE_HIGH) {
									applog(LOG_DEBUG, "%s%i: Chip %d fifo cmd %d",
											  minioncgpu->drv->name,
											  minioncgpu->device_id,
											  chip, (int)cmd);
								}
								break;
							}

							applog(LOG_ERR, "%s%i: Chip %d Bad fifo reply cmd %d (max is %d)",
									minioncgpu->drv->name, minioncgpu->device_id,
									chip, (int)cmd, MINION_QUE_MAX);
							minioninfo->spi_errors++;
							minioninfo->fifo_spi_errors[chip]++;
							minioninfo->res_err_count[chip]++;
						}
					}
				}

				K_WLOCK(minioninfo->wchip_list[chip]);
				count = minioninfo->chip_status[chip].quework +
					minioninfo->chip_status[chip].realwork;
				islow = minioninfo->chip_status[chip].islow;
				minioninfo->chip_status[chip].islow = false;
				lowcount = minioninfo->chip_status[chip].lowcount;
				K_WUNLOCK(minioninfo->wchip_list[chip]);

				switch (state) {
					case 0:
						if (count == 0 || islow) {
							item = next_work(minioninfo);
							if (item) {
								new_work_task(minioncgpu, item, chip, true, state);
								sentwork = true;
								applog(MINION_LOG, "%s%i: 0 task 0x%04x in chip %d list",
										   minioncgpu->drv->name,
										   minioncgpu->device_id,
										   DATA_WORK(item)->task_id, chip);
							} else {
								applog(LOG_ERR, "%s%i: chip %d urgent empty work list",
										minioncgpu->drv->name,
										minioncgpu->device_id,
										chip);
							}
						}
						break;
					case 1:
						if (count < MINION_QUE_LOW || islow) {
							// do case 2: after we've done other chips
							minioninfo->chip_status[chip].tohigh = true;
							j = count;
							if (count >= MINION_QUE_LOW) {
								// islow means run a full case 1
								j = 1;
								applog(LOG_ERR, "%s%i: chip %d low que (%d) with high count %d",
										minioncgpu->drv->name,
										minioncgpu->device_id,
										chip, lowcount, count);
							}
							for (; j < MINION_QUE_LOW; j++) {
								item = next_work(minioninfo);
								if (item) {
									new_work_task(minioncgpu, item, chip, false, state);
									sentwork = true;
									applog(MINION_LOG, "%s%i: 1 task 0x%04x in chip %d list",
											   minioncgpu->drv->name,
											   minioncgpu->device_id,
											   DATA_WORK(item)->task_id, chip);
								} else {
									applog(LOG_ERR, "%s%i: chip %d non-urgent lo "
											   "empty work list (count=%d)",
											   minioncgpu->drv->name,
											   minioncgpu->device_id,
											   chip, j);
								}
							}
						}
						break;
					case 2:
						if (count <= MINION_QUE_LOW || minioninfo->chip_status[chip].tohigh) {
							for (j = count; j < MINION_QUE_HIGH; j++) {
								item = next_work(minioninfo);
								if (item) {
									new_work_task(minioncgpu, item, chip, false, state);
									sentwork = true;
									applog(MINION_LOG, "%s%i: 2 task 0x%04x in chip %d list",
											   minioncgpu->drv->name,
											   minioncgpu->device_id,
											   DATA_WORK(item)->task_id, chip);
								} else {
									applog(LOG_DEBUG, "%s%i: chip %d non-urgent hi "
											   "empty work list (count=%d)",
											   minioncgpu->drv->name,
											   minioncgpu->device_id,
											   chip, j);
								}
							}
						}
						break;
				}
			} else
				if (minioninfo->has_chip[chip] && minioninfo->chip_status[chip].overheat && state == 2)
					sys_chip_sta(minioncgpu, chip);
		}
	}

	sentwork = sentwork;
#if ENABLE_INT_NONO
	if (sentwork) {
		// Clear CMD interrupt since we've now sent more
		K_WLOCK(minioninfo->tfree_list);
		task = k_unlink_head(minioninfo->tfree_list);
		DATA_TASK(task)->tid = ++(minioninfo->next_tid);
		DATA_TASK(task)->chip = 0; // ignored
		DATA_TASK(task)->write = true;
		DATA_TASK(task)->address = MINION_SYS_INT_CLR;
		DATA_TASK(task)->task_id = 0; // ignored
		DATA_TASK(task)->wsiz = MINION_SYS_SIZ;
		DATA_TASK(task)->rsiz = 0;
		DATA_TASK(task)->wbuf[0] = MINION_CMD_INT;
		DATA_TASK(task)->wbuf[1] = 0;
		DATA_TASK(task)->wbuf[2] = 0;
		DATA_TASK(task)->wbuf[3] = 0;
		DATA_TASK(task)->urgent = false;
		k_add_head(minioninfo->task_list, task);
		K_WUNLOCK(minioninfo->tfree_list);
	}
#endif

//applog(LOG_ERR, "%s%i: chip %d fin: quew %d chw %d", minioncgpu->drv->name, minioncgpu->device_id, CHP, minioninfo->chip_status[CHP].quework, minioninfo->chip_status[CHP].chipwork);
}

static bool minion_thread_prepare(struct thr_info *thr)
{
	struct cgpu_info *minioncgpu = thr->cgpu;
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);

	minioninfo->thr = thr;
	/*
	 * SPI/ioctl write thread
	 */
	if (thr_info_create(&(minioninfo->spiw_thr), NULL, minion_spi_write, (void *)minioncgpu)) {
		applog(LOG_ERR, "%s%i: SPI write thread create failed",
				minioncgpu->drv->name, minioncgpu->device_id);
		return false;
	}
	pthread_detach(minioninfo->spiw_thr.pth);

	/*
	 * SPI/ioctl results thread
	 */
	if (thr_info_create(&(minioninfo->spir_thr), NULL, minion_spi_reply, (void *)minioncgpu)) {
		applog(LOG_ERR, "%s%i: SPI reply thread create failed",
				minioncgpu->drv->name, minioncgpu->device_id);
		return false;
	}
	pthread_detach(minioninfo->spir_thr.pth);

	/*
	 * Seperate results checking thread so ioctl timing can ignore the results checking
	 */
	if (thr_info_create(&(minioninfo->res_thr), NULL, minion_results, (void *)minioncgpu)) {
		applog(LOG_ERR, "%s%i: Results thread create failed",
				minioncgpu->drv->name, minioncgpu->device_id);
		return false;
	}
	pthread_detach(minioninfo->res_thr.pth);

	return true;
}

static void minion_shutdown(struct thr_info *thr)
{
	struct cgpu_info *minioncgpu = thr->cgpu;
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	int i;

	applog(MINION_LOG, "%s%i: shutting down",
				minioncgpu->drv->name, minioncgpu->device_id);

	for (i = 0; i < (int)MINION_CHIPS; i++)
		if (minioninfo->has_chip[i])
// TODO:		minion_shutdown(minioncgpu, minioninfo, i);
			i = i;

	minioncgpu->shutdown = true;
}

static bool minion_queue_full(struct cgpu_info *minioncgpu)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	struct work *work, *usework;
	int count, totneed, need, roll, roll_limit, chip;
	bool ret, rolled;

	if (minioninfo->initialised == false) {
		cgsleep_us(42);
		return true;
	}

	K_RLOCK(minioninfo->wwork_list);
	count = minioninfo->wwork_list->count;
	totneed = 0;
	for (chip = 0; chip < (int)MINION_CHIPS; chip++) {
		if (minioninfo->has_chip[chip] &&
		    !minioninfo->chip_status[chip].overheat) {
			totneed += MINION_QUE_HIGH;
			totneed -= minioninfo->chip_status[chip].quework;
			totneed -= minioninfo->chip_status[chip].realwork;
			// One for the pot :)
			totneed++;
		}
	}
	K_RUNLOCK(minioninfo->wwork_list);

	if (count >= totneed)
		ret = true;
	else {
		need = totneed - count;
		/* Ensure we do enough rolling to reduce CPU
		   but dont roll too much to have them end up stale */
		if (need < 16)
			need = 16;
		work = get_queued(minioncgpu);
		if (work) {
			roll_limit = work->drv_rolllimit;
			roll = 0;
			do {
				if (roll == 0) {
					usework = work;
					minioninfo->work_unrolled++;
					rolled = false;
				} else {
					usework = copy_work_noffset(work, roll);
					minioninfo->work_rolled++;
					rolled = true;
				}
				ready_work(minioncgpu, usework, rolled);
			} while (--need > 0 && ++roll <= roll_limit);
		} else {
			// Avoid a hard loop when we can't get work fast enough
			cgsleep_us(42);
		}

		if (need > 0)
			ret = false;
		else
			ret = true;
	}

	return ret;
}

static void idle_report(struct cgpu_info *minioncgpu)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	struct timeval now;
	uint32_t idle;
	int msdiff;
	int chip;

	for (chip = 0; chip < (int)MINION_CHIPS; chip++) {
		if (minioninfo->has_chip[chip]) {
			idle = minioninfo->chip_status[chip].idle;
			if (idle != minioninfo->chip_status[chip].last_rpt_idle) {
				cgtime(&now);
				msdiff = ms_tdiff(&now, &(minioninfo->chip_status[chip].idle_rpt));
				if (msdiff >= MINION_IDLE_MESSAGE_ms) {
					memcpy(&(minioninfo->chip_status[chip].idle_rpt), &now, sizeof(now));
					applog(LOG_WARNING,
						"%s%d: chip %d internal idle changed %08x",
						minioncgpu->drv->name, minioncgpu->device_id,
						chip, idle);
					minioninfo->chip_status[chip].last_rpt_idle = idle;
				}
			}
		}
	}
}

static void chip_report(struct cgpu_info *minioncgpu)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	struct timeval now;
	char buf[512];
	char res_err_msg[2];
	size_t len;
	double elapsed, ghs, ghs2, expect, howlong;
	char ghs2_display[64];
	K_ITEM *pitem;
	int msdiff, chip;
	int res_err_count;

	cgtime(&now);
	if (!(minioninfo->chip_chk.tv_sec)) {
		memcpy(&(minioninfo->chip_chk), &now, sizeof(now));
		memcpy(&(minioninfo->chip_rpt), &now, sizeof(now));
		return;
	}

	// Always run the calculations to check chip GHs for the LED
	buf[0] = '\0';
	res_err_msg[0] = '\0';
	res_err_msg[1] = '\0';
	K_RLOCK(minioninfo->hfree_list);
	for (chip = 0; chip < (int)MINION_CHIPS; chip++) {
		if (minioninfo->has_chip[chip]) {
			len = strlen(buf);
			if (minioninfo->hchip_list[chip]->count < 2)
				ghs = 0.0;
			else {
				ghs = 0xffffffffull * (minioninfo->hchip_list[chip]->count - 1);
				ghs /= 1000000000.0;
				ghs /= tdiff(&now, &(DATA_HIST(minioninfo->hchip_list[chip]->tail)->when));
			}
			if (minioninfo->chip_status[chip].first_nonce.tv_sec == 0L ||
			    tdiff(&now, &minioninfo->chip_status[chip].first_nonce) < MINION_LED_TEST_TIME) {
				ghs2_display[0] = '\0';
				minioninfo->setled[chip] = false;
			} else {
				ghs2 = 0xffffffffull * (minioninfo->chip_status[chip].from_first_good - 1);
				ghs2 /= 1000000000.0;
				ghs2 /= tdiff(&now, &minioninfo->chip_status[chip].first_nonce);
				minioninfo->setled[chip] = (ghs2 >= opt_minion_ledlimit);
				snprintf(ghs2_display, sizeof(ghs2_display), "[%.2f]", ghs2);
			}

			res_err_count = minioninfo->res_err_count[chip];
			minioninfo->res_err_count[chip] = 0;
			if (res_err_count > 100)
				res_err_msg[0] = '!';
			else if (res_err_count > 50)
				res_err_msg[0] = '*';
			else if (res_err_count > 0)
				res_err_msg[0] = '\'';
			else
				res_err_msg[0] = '\0';
			snprintf(buf + len, sizeof(buf) - len,
				 " %d=%s%.2f%s", chip, res_err_msg, ghs, ghs2_display);
			minioninfo->history_ghs[chip] = ghs;
		}
	}
	K_RUNLOCK(minioninfo->hfree_list);

	// But only display it if required
	if (opt_minion_chipreport > 0) {
		msdiff = ms_tdiff(&now, &(minioninfo->chip_rpt));
		if (msdiff >= (opt_minion_chipreport * 1000)) {
			memcpy(&(minioninfo->chip_chk), &now, sizeof(now));
			applogsiz(LOG_WARNING, 512,
				  "%s%d: Chip GHs%s",
				  minioncgpu->drv->name, minioncgpu->device_id, buf);
			memcpy(&(minioninfo->chip_rpt), &now, sizeof(now));
		}
	}

	msdiff = ms_tdiff(&now, &(minioninfo->chip_chk));
	if (total_secs >= MINION_RESET_s && msdiff >= (minioninfo->history_gen * 1000)) {
		K_RLOCK(minioninfo->hfree_list);
		for (chip = 0; chip < (int)MINION_CHIPS; chip++) {
			if (minioninfo->has_chip[chip]) {
				// Don't reset the chip while 'changing'
				if (minioninfo->changing[chip])
					continue;

				if (!minioninfo->reset_mark[chip] ||
				    minioninfo->reset_count[chip] < 2) {
					elapsed = 0.0;
					ghs = 0.0;
				} else {
					// 'now' includes that it may have stopped getting nonces
					elapsed = tdiff(&now, &(DATA_HIST(minioninfo->reset_mark[chip])->when));
					ghs = 0xffffffffull * (minioninfo->reset_count[chip] - 1);
					ghs /= 1000000000.0;
					ghs /= elapsed;
				}
				expect = (double)(minioninfo->init_freq[chip]) *
					 MINION_RESET_PERCENT / 1000.0;
				howlong = tdiff(&now, &(minioninfo->last_reset[chip]));
				if (ghs <= expect && howlong >= minioninfo->reset_time[chip]) {
					minioninfo->do_reset[chip] = expect;

					// For now - no lock required since no other code accesses it
					pitem = k_unlink_head(minioninfo->pfree_list);
					DATA_PERF(pitem)->elapsed = elapsed;
					DATA_PERF(pitem)->nonces = minioninfo->reset_count[chip] - 1;
					DATA_PERF(pitem)->freq = minioninfo->init_freq[chip];
					DATA_PERF(pitem)->ghs = ghs;
					memcpy(&(DATA_PERF(pitem)->when), &now, sizeof(now));
					k_add_head(minioninfo->p_list[chip], pitem);
				} else if (second_check) {
					expect = (double)(minioninfo->init_freq[chip]) *
						 MINION_RESET2_PERCENT / 1000.0;
					if (ghs < expect && howlong >= minioninfo->reset2_time[chip]) {
						/* Only do a reset, don't record it, since the ghs
						   is still above MINION_RESET_PERCENT */
						minioninfo->do_reset[chip] = expect;
					}
				}
				minioninfo->history_ghs[chip] = ghs;
				// Expire old perf items to stop clockdown
				if (minioninfo->do_reset[chip] <= 1.0 && howlong > MINION_CLR_s) {
					// Always remember the last reset
					while (minioninfo->p_list[chip]->count > 1) {
						pitem = k_unlink_tail(minioninfo->p_list[chip]);
						k_add_head(minioninfo->pfree_list, pitem);
					}
				}
			}
		}
		K_RUNLOCK(minioninfo->hfree_list);

		memcpy(&(minioninfo->chip_chk), &now, sizeof(now));
	}

	for (chip = 0; chip < (int)MINION_CHIPS; chip++) {
		if (minioninfo->has_chip[chip]) {
			// Don't reset the chip while 'changing'
			if (minioninfo->changing[chip])
				continue;

			if (minioninfo->do_reset[chip] > 1.0 ||
			    minioninfo->flag_reset[chip]) {
				bool std_reset = true;
				int curr_freq = minioninfo->init_freq[chip];
				int new_freq = 0.0;
				int count;

				// Adjust frequency down?
				if (!opt_minion_noautofreq &&
				    minioninfo->p_list[chip]->count >= MINION_RESET_COUNT) {
					pitem = minioninfo->p_list[chip]->head;
					count = 1;
					while (pitem && pitem->next && count++ < MINION_RESET_COUNT) {
						if (DATA_PERF(pitem)->freq != DATA_PERF(pitem->next)->freq)
							break;
						if (count >= MINION_RESET_COUNT) {
							new_freq = minioninfo->init_freq[chip] -
									MINION_FREQ_RESET_STEP;
							if (new_freq < MINION_FREQ_MIN)
								new_freq = MINION_FREQ_MIN;
							if (minioninfo->init_freq[chip] != new_freq) {
								minioninfo->init_freq[chip] = new_freq;
								std_reset = false;
							}
							break;
 						} else
							pitem = pitem->next;
					}
				}

				if (std_reset) {
					if (minioninfo->do_reset[chip] > 1.0) {
						applog(LOG_WARNING, "%s%d: Chip %d %dMHz threshold "
								    "%.2fGHs - resetting",
								    minioncgpu->drv->name,
								    minioncgpu->device_id,
								    chip, curr_freq,
								    minioninfo->do_reset[chip]);
					} else {
						applog(LOG_WARNING, "%s%d: Chip %d %dMhz flagged - "
								    "resetting",
								    minioncgpu->drv->name,
								    minioncgpu->device_id,
								    chip, curr_freq);
					}
				} else {
					if (minioninfo->do_reset[chip] > 1.0) {
						applog(LOG_WARNING, "%s%d: Chip %d %dMHz threshold "
								    "%.2fGHs - resetting to %dMhz",
								    minioncgpu->drv->name,
								    minioncgpu->device_id,
								    chip, curr_freq,
								    minioninfo->do_reset[chip],
								    new_freq);
					} else {
						applog(LOG_WARNING, "%s%d: Chip %d %dMhz flagged - "
								    "resetting to %dMHz",
								    minioncgpu->drv->name,
								    minioncgpu->device_id,
								    chip, curr_freq, new_freq);
					}
				}
				minioninfo->do_reset[chip] = 0.0;
				memcpy(&(minioninfo->last_reset[chip]), &now, sizeof(now));
				init_chip(minioncgpu, minioninfo, chip);
				minioninfo->flag_reset[chip] = false;
			}
		}
	}
}

static int64_t minion_scanwork(__maybe_unused struct thr_info *thr)
{
	struct cgpu_info *minioncgpu = thr->cgpu;
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	int64_t hashcount = 0;

	if (minioninfo->initialised == false)
		return hashcount;

	minion_do_work(minioncgpu);

	mutex_lock(&(minioninfo->nonce_lock));
	if (minioninfo->new_nonces) {
		hashcount += 0xffffffffull * minioninfo->new_nonces;
		minioninfo->new_nonces = 0;
	}
	mutex_unlock(&(minioninfo->nonce_lock));

	if (opt_minion_idlecount)
		idle_report(minioncgpu);

	// Must always generate data to check/allow for chip reset
	chip_report(minioncgpu);

	/*
	 * To avoid wasting CPU, wait until we get an interrupt
	 * before returning back to the main cgminer work loop
	 * i.e. we then know we'll need more work
	 */
	cgsem_mswait(&(minioninfo->scan_work), MINION_SCAN_mS);

	return hashcount;
}

static const char *temp_str(uint16_t temp)
{
	switch (temp) {
		case MINION_TEMP_40:
			return min_temp_40;
		case MINION_TEMP_60:
			return min_temp_60;
		case MINION_TEMP_80:
			return min_temp_80;
		case MINION_TEMP_100:
			return min_temp_100;
		case MINION_TEMP_OVER:
			return min_temp_over;
	}
	return min_temp_invalid;
}

static void minion_get_statline_before(char *buf, size_t bufsiz, struct cgpu_info *minioncgpu)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	uint16_t max_temp, cores;
	int chip, core;

	max_temp = 0;
	cores = 0;
	mutex_lock(&(minioninfo->sta_lock));
	for (chip = 0; chip < (int)MINION_CHIPS; chip++) {
		if (minioninfo->has_chip[chip]) {
			if (max_temp < minioninfo->chip_status[chip].temp)
				max_temp = minioninfo->chip_status[chip].temp;
			for (core = 0; core < MINION_CORES; core++) {
				if (minioninfo->chip_core_ena[core >> 5][chip] & (0x1 << (core % 32)))
					cores++;
			}
		}
	}
	mutex_unlock(&(minioninfo->sta_lock));

	tailsprintf(buf, bufsiz, "max%sC Ch:%d Co:%d",
				 temp_str(max_temp), minioninfo->chips, (int)cores);
}

#define CHIPS_PER_STAT 5

static struct api_data *minion_api_stats(struct cgpu_info *minioncgpu)
{
	struct minion_info *minioninfo = (struct minion_info *)(minioncgpu->device_data);
	struct api_data *root = NULL;
	char cores[MINION_CORES+1];
	char data[2048];
	char buf[32];
	int i, to, j;
	size_t datalen, nlen;
	int chip, max_chip, que_work, chip_work, temp;

	if (minioninfo->initialised == false)
		return NULL;

	root = api_add_uint64(root, "OK Nonces", &(minioninfo->ok_nonces), true);
	root = api_add_uint64(root, "New Nonces", &(minioninfo->new_nonces), true);
	root = api_add_uint64(root, "Tested Nonces", &(minioninfo->tested_nonces), true);
	root = api_add_uint64(root, "Untested Nonces", &(minioninfo->untested_nonces), true);

	root = api_add_int(root, "Chips", &(minioninfo->chips), true);
	i = MINION_PIN_COUNT;
	root = api_add_int(root, "GPIO Pins", &i, true);

	max_chip = 0;
	for (chip = 0; chip < (int)MINION_CHIPS; chip++)
		if (minioninfo->has_chip[chip]) {
			max_chip = chip;

			snprintf(buf, sizeof(buf), "Chip %d Pin", chip);
			root = api_add_int(root, buf, &(minioninfo->chip_pin[chip]), true);
			snprintf(buf, sizeof(buf), "Chip %d ChipID", chip);
			i = (int)(minioninfo->chipid[chip]);
			root = api_add_int(root, buf, &i, true);
			snprintf(buf, sizeof(buf), "Chip %d Temperature", chip);
			root = api_add_const(root, buf, temp_str(minioninfo->chip_status[chip].temp), false);
			snprintf(buf, sizeof(buf), "Chip %d Cores", chip);
			root = api_add_uint16(root, buf, &(minioninfo->chip_status[chip].cores), true);
			snprintf(buf, sizeof(buf), "Chip %d Frequency", chip);
			root = api_add_uint32(root, buf, &(minioninfo->chip_status[chip].freq), true);
			snprintf(buf, sizeof(buf), "Chip %d InitFreq", chip);
			root = api_add_int(root, buf, &(minioninfo->init_freq[chip]), true);
			snprintf(buf, sizeof(buf), "Chip %d FreqSent", chip);
			root = api_add_hex32(root, buf, &(minioninfo->freqsent[chip]), true);
			snprintf(buf, sizeof(buf), "Chip %d InitTemp", chip);
			temp = minioninfo->init_temp[chip];
			if (temp == MINION_TEMP_CTL_DISABLE)
				root = api_add_string(root, buf, MINION_TEMP_DISABLE, true);
			else {
				snprintf(data, sizeof(data), "%d", temp);
				root = api_add_string(root, buf, data, true);
			}
			snprintf(buf, sizeof(buf), "Chip %d TempSent", chip);
			root = api_add_hex32(root, buf, &(minioninfo->chip_status[chip].tempsent), true);
			__bin2hex(data, (unsigned char *)(&(minioninfo->init_cores[chip][0])),
				  sizeof(minioninfo->init_cores[chip]));
			snprintf(buf, sizeof(buf), "Chip %d InitCores", chip);
			root = api_add_string(root, buf, data, true);
			snprintf(buf, sizeof(buf), "Chip %d IdleCount", chip);
			root = api_add_hex32(root, buf, &(minioninfo->chip_status[chip].idle), true);
			snprintf(buf, sizeof(buf), "Chip %d QueWork", chip);
			root = api_add_uint32(root, buf, &(minioninfo->chip_status[chip].quework), true);
			snprintf(buf, sizeof(buf), "Chip %d ChipWork", chip);
			root = api_add_uint32(root, buf, &(minioninfo->chip_status[chip].chipwork), true);
			snprintf(buf, sizeof(buf), "Chip %d RealWork", chip);
			root = api_add_uint32(root, buf, &(minioninfo->chip_status[chip].realwork), true);
			snprintf(buf, sizeof(buf), "Chip %d QueListCount", chip);
			root = api_add_int(root, buf, &(minioninfo->wque_list[chip]->count), true);
			snprintf(buf, sizeof(buf), "Chip %d WorkListCount", chip);
			root = api_add_int(root, buf, &(minioninfo->wchip_list[chip]->count), true);
			snprintf(buf, sizeof(buf), "Chip %d Overheat", chip);
			root = api_add_bool(root, buf, &(minioninfo->chip_status[chip].overheat), true);
			snprintf(buf, sizeof(buf), "Chip %d Overheats", chip);
			root = api_add_uint32(root, buf, &(minioninfo->chip_status[chip].overheats), true);
			snprintf(buf, sizeof(buf), "Chip %d LastOverheat", chip);
			root = api_add_timeval(root, buf, &(minioninfo->chip_status[chip].lastoverheat), true);
			snprintf(buf, sizeof(buf), "Chip %d LastRecover", chip);
			root = api_add_timeval(root, buf, &(minioninfo->chip_status[chip].lastrecover), true);
			snprintf(buf, sizeof(buf), "Chip %d OverheatIdle", chip);
			root = api_add_double(root, buf, &(minioninfo->chip_status[chip].overheattime), true);
			for (i = 0; i < MINION_CORES; i++) {
				if (minioninfo->chip_core_ena[i >> 5][chip] & (0x1 << (i % 32)))
					cores[i] = 'o';
				else
					cores[i] = 'x';
			}
			cores[MINION_CORES] = '\0';
			snprintf(buf, sizeof(buf), "Chip %d CoresEna", chip);
			root = api_add_string(root, buf, cores, true);
			for (i = 0; i < MINION_CORES; i++) {
				if (minioninfo->chip_core_act[i >> 5][chip] & (0x1 << (i % 32)))
					cores[i] = '-';
				else
					cores[i] = 'o';
			}
			cores[MINION_CORES] = '\0';
			snprintf(buf, sizeof(buf), "Chip %d CoresAct", chip);
			root = api_add_string(root, buf, cores, true);

			if (opt_minion_extra) {
				data[0] = '\0';
				datalen = 0;
				for (i = 0; i < MINION_CORES; i++) {
					if (datalen < sizeof(data)) {
						nlen = snprintf(data+datalen, sizeof(data)-datalen,
								"%s%"PRIu64"-%s%"PRIu64,
								i == 0 ? "" : "/",
								minioninfo->core_good[chip][i],
								minioninfo->core_bad[chip][i] ? "'" : "",
								minioninfo->core_bad[chip][i]);
						if (nlen < 1)
							break;
						datalen += nlen;
					}
				}
				snprintf(buf, sizeof(buf), "Chip %d Cores Good-Bad", chip);
				root = api_add_string(root, buf, data, true);
			}

			snprintf(buf, sizeof(buf), "Chip %d History GHs", chip);
			root = api_add_mhs(root, buf, &(minioninfo->history_ghs[chip]), true);
		}

	double his = MINION_HISTORY_s;
	root = api_add_double(root, "History length", &his, true);
	his = MINION_RESET_s;
	root = api_add_double(root, "Default reset length", &his, true);
	his = MINION_RESET2_s;
	root = api_add_double(root, "Default reset2 length", &his, true);
	root = api_add_bool(root, "Reset2 enabled", &second_check, true);

	for (i = 0; i <= max_chip; i += CHIPS_PER_STAT) {
		to = i + CHIPS_PER_STAT - 1;
		if (to > max_chip)
			to = max_chip;

		data[0] = '\0';
		for (j = i; j <= to; j++) {
			snprintf(buf, sizeof(buf),
					"%s%d",
					j == i ? "" : " ",
					minioninfo->has_chip[j] ? 1 : 0);
			strcat(data, buf);
		}
		snprintf(buf, sizeof(buf), "Detected %02d - %02d", i, to);
		root = api_add_string(root, buf, data, true);

		data[0] = '\0';
		for (j = i; j <= to; j++) {
			snprintf(buf, sizeof(buf),
					"%s%8"PRIu64,
					j == i ? "" : " ",
					minioninfo->chip_nonces[j]);
			strcat(data, buf);
		}
		snprintf(buf, sizeof(buf), "Nonces %02d - %02d", i, to);
		root = api_add_string(root, buf, data, true);

		data[0] = '\0';
		for (j = i; j <= to; j++) {
			snprintf(buf, sizeof(buf),
					"%s%8"PRIu64,
					j == i ? "" : " ",
					minioninfo->chip_nononces[j]);
			strcat(data, buf);
		}
		snprintf(buf, sizeof(buf), "NoNonces %02d - %02d", i, to);
		root = api_add_string(root, buf, data, true);

		data[0] = '\0';
		for (j = i; j <= to; j++) {
			snprintf(buf, sizeof(buf),
					"%s%8"PRIu64,
					j == i ? "" : " ",
					minioninfo->chip_good[j]);
			strcat(data, buf);
		}
		snprintf(buf, sizeof(buf), "Good %02d - %02d", i, to);
		root = api_add_string(root, buf, data, true);

		data[0] = '\0';
		for (j = i; j <= to; j++) {
			snprintf(buf, sizeof(buf),
					"%s%8"PRIu64,
					j == i ? "" : " ",
					minioninfo->chip_bad[j]);
			strcat(data, buf);
		}
		snprintf(buf, sizeof(buf), "Bad %02d - %02d", i, to);
		root = api_add_string(root, buf, data, true);

		data[0] = '\0';
		for (j = i; j <= to; j++) {
			snprintf(buf, sizeof(buf),
					"%s%8"PRIu64,
					j == i ? "" : " ",
					minioninfo->chip_err[j]);
			strcat(data, buf);
		}
		snprintf(buf, sizeof(buf), "Err %02d - %02d", i, to);
		root = api_add_string(root, buf, data, true);

		data[0] = '\0';
		for (j = i; j <= to; j++) {
			snprintf(buf, sizeof(buf),
					"%s%8"PRIu64,
					j == i ? "" : " ",
					minioninfo->fifo_spi_errors[j]);
			strcat(data, buf);
		}
		snprintf(buf, sizeof(buf), "FifoSpiErr %02d - %02d", i, to);
		root = api_add_string(root, buf, data, true);

		data[0] = '\0';
		for (j = i; j <= to; j++) {
			snprintf(buf, sizeof(buf),
					"%s%8"PRIu64,
					j == i ? "" : " ",
					minioninfo->res_spi_errors[j]);
			strcat(data, buf);
		}
		snprintf(buf, sizeof(buf), "ResSpiErr %02d - %02d", i, to);
		root = api_add_string(root, buf, data, true);

		data[0] = '\0';
		for (j = i; j <= to; j++) {
			snprintf(buf, sizeof(buf),
					"%s%"PRIu64"/%"PRIu64"/%"PRIu64"/%"PRIu64"/%"PRIu64,
					j == i ? "" : " ",
					minioninfo->use_res2[j],
					minioninfo->tasks_failed[j],
					minioninfo->tasks_recovered[j],
					minioninfo->nonces_failed[j],
					minioninfo->nonces_recovered[j]);
			strcat(data, buf);
		}
		snprintf(buf, sizeof(buf), "Redo %02d - %02d", i, to);
		root = api_add_string(root, buf, data, true);
	}

	que_work = chip_work = 0;
	for (chip = 0; chip <= max_chip; chip++) {
		if (minioninfo->has_chip[chip]) {
			que_work += minioninfo->wque_list[chip]->count;
			chip_work += minioninfo->wchip_list[chip]->count;
		}
	}

	root = api_add_int(root, "WFree Total", &(minioninfo->wfree_list->total), true);
	root = api_add_int(root, "WFree Count", &(minioninfo->wfree_list->count), true);
	root = api_add_int(root, "WWork Count", &(minioninfo->wwork_list->count), true);
	root = api_add_uint64(root, "WWork Flushed", &(minioninfo->wwork_flushed), true);
	root = api_add_int(root, "WQue Count", &que_work, true);
	root = api_add_uint64(root, "WQue Flushed", &(minioninfo->wque_flushed), true);
	root = api_add_int(root, "WChip Count", &chip_work, true);
	root = api_add_uint64(root, "WChip Stale", &(minioninfo->wchip_staled), true);

	root = api_add_int(root, "TFree Total", &(minioninfo->tfree_list->total), true);
	root = api_add_int(root, "TFree Count", &(minioninfo->tfree_list->count), true);
	root = api_add_int(root, "Task Count", &(minioninfo->task_list->count), true);
	root = api_add_int(root, "Reply Count", &(minioninfo->treply_list->count), true);

	root = api_add_int(root, "RFree Total", &(minioninfo->rfree_list->total), true);
	root = api_add_int(root, "RFree Count", &(minioninfo->rfree_list->count), true);
	root = api_add_int(root, "RNonce Count", &(minioninfo->rnonce_list->count), true);

	root = api_add_int(root, "XFree Count", &(minioninfo->xfree_list->count), true);
	root = api_add_int(root, "XFF Count", &(minioninfo->xff_list->count), true);
	root = api_add_uint64(root, "XFFs", &(minioninfo->xffs), true);
	root = api_add_uint64(root, "SPI Resets", &(minioninfo->spi_resets), true);
	root = api_add_uint64(root, "Power Cycles", &(minioninfo->power_cycles), true);

	root = api_add_int(root, "Chip Report", &opt_minion_chipreport, true);
	root = api_add_int(root, "LED Count", &opt_minion_ledcount, true);
	root = api_add_int(root, "LED Limit", &opt_minion_ledlimit, true);
	bool b = !opt_minion_noautofreq;
	root = api_add_bool(root, "Auto Freq", &b, true);
	root = api_add_int(root, "SPI Delay", &opt_minion_spidelay, true);
	root = api_add_bool(root, "SPI Reset I/O", &(minioninfo->spi_reset_io), true);
	root = api_add_int(root, "SPI Reset", &(minioninfo->spi_reset_count), true);
	root = api_add_int(root, "SPI Reset Sleep", &opt_minion_spisleep, true);

#if DO_IO_STATS
#define sta_api(_name, _iostat) \
	do { \
		if ((_iostat).count) { \
			float _davg = (float)((_iostat).total_delay) / (float)((_iostat).count); \
			float _dlavg = (float)((_iostat).total_dlock) / (float)((_iostat).count); \
			float _dlwavg = (float)((_iostat).total_dlwait) / (float)((_iostat).count); \
			float _bavg = (float)((_iostat).total_bytes) / (float)((_iostat).count); \
			float _tavg = (float)((_iostat).tsd) / (float)((_iostat).count); \
			snprintf(data, sizeof(data), "%s Count=%"PRIu64 \
				" Delay=%.0fus DAvg=%.3f" \
				" DMin=%.0f DMax=%.0f DZ=%"PRIu64 \
				" DLock=%.0fus DLAvg=%.3f" \
				" DLMin=%.0f DLMax=%.0f DZ=%"PRIu64 \
				" DLWait=%.0fus DLWAvg=%.3f" \
				" Bytes=%"PRIu64" BAvg=%.3f" \
				" BMin=%"PRIu64" BMax=%"PRIu64" BZ=%"PRIu64 \
				" TSD=%.0fus TAvg=%.03f", \
				_name, (_iostat).count, \
				(_iostat).total_delay, _davg, (_iostat).min_delay, \
				(_iostat).max_delay, (_iostat).zero_delay, \
				(_iostat).total_dlock, _dlavg, (_iostat).min_dlock, \
				(_iostat).max_dlock, (_iostat).zero_dlock, \
				(_iostat).total_dlwait, _dlwavg, \
				(_iostat).total_bytes, _bavg, (_iostat).min_bytes, \
				(_iostat).max_bytes, (_iostat).zero_bytes, \
				(_iostat).tsd, _tavg); \
			root = api_add_string(root, buf, data, true); \
		} \
	} while(0);

	for (i = 0; i < 0x200; i++) {
		snprintf(buf, sizeof(buf), "Stat-0x%02x", i);
		sta_api(addr2txt((uint8_t)(i & 0xff)), minioninfo->iostats[i]);
	}

	// Test to avoid showing applog
	if (minioninfo->summary.count) {
		snprintf(buf, sizeof(buf), "Stat-S");
		sta_api("Summary", minioninfo->summary);
		applog(LOG_WARNING, "%s %d: (%.0f) %s - %s",
				    minioncgpu->drv->name, minioncgpu->device_id,
				    total_secs, buf, data);
	}
#endif

	root = api_add_uint64(root, "Total SPI Errors", &(minioninfo->spi_errors), true);
	root = api_add_uint64(root, "Work Unrolled", &(minioninfo->work_unrolled), true);
	root = api_add_uint64(root, "Work Rolled", &(minioninfo->work_rolled), true);
	root = api_add_uint64(root, "Ints", &(minioninfo->interrupts), true);
	root = api_add_uint64(root, "Res Ints", &(minioninfo->result_interrupts), true);
	root = api_add_uint64(root, "Cmd Ints", &(minioninfo->command_interrupts), true);
	root = api_add_string(root, "Last Int", minioninfo->last_interrupt, true);
	root = api_add_hex32(root, "Next TaskID", &(minioninfo->next_task_id), true);

	double avg;
	root = api_add_uint64(root, "ToQue", &(minioninfo->que_work), true);
	if (minioninfo->que_work)
		avg = minioninfo->que_time / (double)(minioninfo->que_work);
	else
		avg = 0;
	root = api_add_double(root, "Que Avg", &avg, true);
	root = api_add_double(root, "Que Min", &(minioninfo->que_min), true);
	root = api_add_double(root, "Que Max", &(minioninfo->que_max), true);
	data[0] = '\0';
	for (i = 0; i <= TIME_BANDS; i++) {
		snprintf(buf, sizeof(buf),
				"%s%"PRIu64,
				i == 0 ? "" : "/",
				minioninfo->que_bands[i]);
		strcat(data, buf);
	}
	root = api_add_string(root, "Que Bands", data, true);

	root = api_add_uint64(root, "ToTxRx", &(minioninfo->wt_work), true);
	if (minioninfo->wt_work)
		avg = minioninfo->wt_time / (double)(minioninfo->wt_work);
	else
		avg = 0;
	root = api_add_double(root, "TxRx Avg", &avg, true);
	root = api_add_double(root, "TxRx Min", &(minioninfo->wt_min), true);
	root = api_add_double(root, "TxRx Max", &(minioninfo->wt_max), true);
	data[0] = '\0';
	for (i = 0; i <= TIME_BANDS; i++) {
		snprintf(buf, sizeof(buf),
				"%s%"PRIu64,
				i == 0 ? "" : "/",
				minioninfo->wt_bands[i]);
		strcat(data, buf);
	}
	root = api_add_string(root, "TxRx Bands", data, true);

	uint64_t checked, dups;
	dupcounters(minioncgpu, &checked, &dups);
	root = api_add_uint64(root, "Dups", &dups, true);

	return root;
}
#endif

struct device_drv minion_drv = {
	.drv_id = DRIVER_minion,
	.dname = "Minion BlackArrow",
	.name = "MBA",
	.drv_detect = minion_detect,
#ifdef LINUX
	.get_api_stats = minion_api_stats,
	.get_statline_before = minion_get_statline_before,
	.set_device = minion_api_set,
	.identify_device = minion_identify,
	.thread_prepare = minion_thread_prepare,
	.hash_work = hash_queued_work,
	.scanwork = minion_scanwork,
	.queue_full = minion_queue_full,
	.flush_work = minion_flush_work,
	.thread_shutdown = minion_shutdown
#endif
};
