Browse Source

New driver: BTCFPGA ModMiner

Luke Dashjr 13 years ago
parent
commit
76f96f4717
8 changed files with 583 additions and 7 deletions
  1. 6 0
      Makefile.am
  2. 23 0
      bitstreams/COPYING_fpgaminer
  3. 0 0
      bitstreams/COPYING_ztex
  4. BIN
      bitstreams/fpgaminer_top_fixed7_197MHz.ncd
  5. 17 2
      cgminer.c
  6. 22 5
      configure.ac
  7. 514 0
      driver-modminer.c
  8. 1 0
      miner.h

+ 6 - 0
Makefile.am

@@ -80,6 +80,12 @@ if HAS_ICARUS
 cgminer_SOURCES += driver-icarus.c
 endif
 
+if HAS_MODMINER
+cgminer_SOURCES += driver-modminer.c
+bitstreamsdir = $(bindir)/bitstreams
+dist_bitstreams_DATA = bitstreams/*
+endif
+
 if HAS_ZTEX
 cgminer_SOURCES += driver-ztex.c libztex.c libztex.h
 bitstreamsdir = $(bindir)/bitstreams

+ 23 - 0
bitstreams/COPYING_fpgaminer

@@ -0,0 +1,23 @@
+All the bitstream files included in this directory that follow the name pattern fpgaminer_*.ncd are:
+
+----
+
+Copyright (c) 2011-2012 fpgaminer@bitcoin-mining.com
+
+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.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+----
+
+You can find the original sources at the Open Source FPGA Bitcoin Miner project GitHub repository:
+https://github.com/progranism/Open-Source-FPGA-Bitcoin-Miner/tree/master/projects/X6000_ztex_comm4/hdl

+ 0 - 0
bitstreams/LICENSE.txt → bitstreams/COPYING_ztex


BIN
bitstreams/fpgaminer_top_fixed7_197MHz.ncd


+ 17 - 2
cgminer.c

@@ -664,8 +664,12 @@ static void load_temp_cutoffs()
 			devices[device]->cutofftemp = val;
 		}
 	}
-	else
-		val = opt_cutofftemp;
+	else {
+		for (i = device; i < total_devices; ++i)
+			if (!devices[i]->cutofftemp)
+				devices[i]->cutofftemp = opt_cutofftemp;
+		return;
+	}
 	if (device <= 1) {
 		for (i = device; i < total_devices; ++i)
 			devices[i]->cutofftemp = val;
@@ -1137,6 +1141,9 @@ static char *opt_verusage_and_exit(const char *extra)
 #ifdef USE_ICARUS
 		"icarus "
 #endif
+#ifdef USE_MODMINER
+		"modminer "
+#endif
 #ifdef USE_ZTEX
 		"ztex "
 #endif
@@ -4730,6 +4737,10 @@ extern struct device_api bitforce_api;
 extern struct device_api icarus_api;
 #endif
 
+#ifdef USE_MODMINER
+extern struct device_api modminer_api;
+#endif
+
 #ifdef USE_ZTEX
 extern struct device_api ztex_api;
 #endif
@@ -4972,6 +4983,10 @@ int main(int argc, char *argv[])
 	bitforce_api.api_detect();
 #endif
 
+#ifdef USE_MODMINER
+	modminer_api.api_detect();
+#endif
+
 #ifdef USE_ZTEX
 	ztex_api.api_detect();
 #endif

+ 22 - 5
configure.ac

@@ -209,6 +209,17 @@ if test "x$icarus" = xyes; then
 fi
 AM_CONDITIONAL([HAS_ICARUS], [test x$icarus = xyes])
 
+modminer="no"
+
+AC_ARG_ENABLE([modminer],
+	[AC_HELP_STRING([--enable-modminer],[Compile support for ModMiner FPGAs(default disabled)])],
+	[modminer=$enableval]
+	)
+if test "x$modminer" = xyes; then
+	AC_DEFINE([USE_MODMINER], [1], [Defined to 1 if ModMiner support is wanted])
+fi
+AM_CONDITIONAL([HAS_MODMINER], [test x$modminer = xyes])
+
 ztex="no"
 
 AC_ARG_ENABLE([ztex],
@@ -245,7 +256,7 @@ else
 fi
 
 
-AM_CONDITIONAL([NEED_FPGAUTILS], [test x$icarus$bitforce$ztex != xnonono])
+AM_CONDITIONAL([NEED_FPGAUTILS], [test x$icarus$bitforce$modminer$ztex != xnononono])
 AM_CONDITIONAL([HAVE_CURSES], [test x$curses = xyes])
 AM_CONDITIONAL([WANT_JANSSON], [test x$request_jansson = xtrue])
 AM_CONDITIONAL([HAVE_WINDOWS], [test x$have_win32 = xtrue])
@@ -294,7 +305,7 @@ fi
 
 AM_CONDITIONAL([HAS_YASM], [test x$has_yasm = xtrue])
 
-if test "x$bitforce" != xno; then
+if test "x$bitforce$modminer" != xnono; then
 	AC_ARG_WITH([libudev], [AC_HELP_STRING([--without-libudev], [Autodetect FPGAs using libudev (default enabled)])],
 		[libudev=$withval],
 		[libudev=auto]
@@ -416,13 +427,13 @@ if test "x$opencl" != xno; then
 		echo "  OpenCL...............: FOUND. GPU mining support enabled"
 	else
 		echo "  OpenCL...............: NOT FOUND. GPU mining support DISABLED"
-		if test "x$cpumining$bitforce$icarus$ztex" = xnononono; then
+		if test "x$cpumining$bitforce$icarus$ztex$modminer" = xnonononono; then
 			AC_MSG_ERROR([No mining configured in])
 		fi
 	fi
 else
 	echo "  OpenCL...............: Detection overrided. GPU mining support DISABLED"
-	if test "x$cpumining$bitforce$icarus$ztex" = xnononono; then
+	if test "x$cpumining$bitforce$icarus$ztex$modminer" = xnonononono; then
 		AC_MSG_ERROR([No mining configured in])
 	fi
 fi
@@ -450,13 +461,19 @@ else
 	echo "  Icarus.FPGAs.........: Disabled"
 fi
 
+if test "x$modminer" = xyes; then
+	echo "  ModMiner.FPGAs.......: Enabled"
+else
+	echo "  ModMiner.FPGAs.......: Disabled"
+fi
+
 if test "x$ztex" = xyes; then
 	echo "  Ztex.FPGAs...........: Enabled"
 else
 	echo "  Ztex.FPGAs...........: Disabled"
 fi
 
-if test "x$bitforce" != xno; then
+if test "x$bitforce$modminer" != xnono; then
 	echo "  libudev.detection....: $libudev"
 fi
 

+ 514 - 0
driver-modminer.c

@@ -0,0 +1,514 @@
+/*
+ * Copyright 2012 Luke Dashjr
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.  See COPYING for more details.
+ */
+
+#include "config.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include "fpgautils.h"
+#include "logging.h"
+#include "miner.h"
+
+#define BITSTREAM_FILENAME "fpgaminer_top_fixed7_197MHz.ncd"
+#define BISTREAM_USER_ID "\2\4$B"
+
+struct device_api modminer_api;
+
+static inline bool
+_bailout(int fd, struct cgpu_info*modminer, int prio, const char *fmt, ...)
+{
+	if (fd != -1)
+		serial_close(fd);
+	if (modminer) {
+		modminer->device_fd = -1;
+		mutex_unlock(&modminer->device_mutex);
+	}
+
+	va_list ap;
+	va_start(ap, fmt);
+	vapplog(prio, fmt, ap);
+	va_end(ap);
+	return false;
+}
+#define bailout(...)  return _bailout(fd, NULL, __VA_ARGS__);
+
+static bool
+modminer_detect_one(const char *devpath)
+{
+	int fd = serial_open(devpath, 0, 10, true);
+	if (unlikely(fd == -1))
+		bailout(LOG_DEBUG, "ModMiner detect: failed to open %s", devpath);
+
+	char buf[0x100];
+	size_t len;
+
+	// Sending 45 noops, just in case the device was left in "start job" reading
+	write(fd, "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff", 45) ?:0;
+	while (serial_read(fd, buf, sizeof(buf)) > 0)
+		;
+
+	if (1 != write(fd, "\x01", 1))  // Get version
+		bailout(LOG_DEBUG, "ModMiner detect: write failed on %s (get version)", devpath);
+	len = serial_read(fd, buf, sizeof(buf)-1);
+	if (len < 1)
+		bailout(LOG_DEBUG, "ModMiner detect: no response to version request from %s", devpath);
+	buf[len] = '\0';
+	char*devname = strdup(buf);
+	applog(LOG_DEBUG, "ModMiner identified as: %s", devname);
+
+	if (1 != write(fd, "\x02", 1))  // Get FPGA count
+		bailout(LOG_DEBUG, "ModMiner detect: write failed on %s (get FPGA count)", devpath);
+	len = read(fd, buf, 1);
+	if (len < 1)
+		bailout(LOG_ERR, "ModMiner detect: timeout waiting for FPGA count from %s", devpath);
+	if (!buf[0])
+		bailout(LOG_ERR, "ModMiner detect: zero FPGAs reported on %s", devpath);
+	applog(LOG_DEBUG, "ModMiner %s has %u FPGAs", devname, buf[0]);
+
+	serial_close(fd);
+
+	struct cgpu_info *modminer;
+	modminer = calloc(1, sizeof(*modminer));
+	modminer->api = &modminer_api;
+	mutex_init(&modminer->device_mutex);
+	modminer->device_path = strdup(devpath);
+	modminer->device_fd = -1;
+	modminer->deven = DEV_ENABLED;
+	modminer->threads = buf[0];
+	modminer->name = devname;
+	modminer->cutofftemp = 85;
+
+	return add_cgpu(modminer);
+}
+
+#undef bailout
+
+static char
+modminer_detect_auto()
+{
+	return
+	serial_autodetect_udev     (modminer_detect_one, "BTCFPGA*ModMiner") ?:
+	serial_autodetect_devserial(modminer_detect_one, "BTCFPGA_ModMiner") ?:
+	0;
+}
+
+static void
+modminer_detect()
+{
+	serial_detect_auto("modminer", modminer_detect_one, modminer_detect_auto);
+}
+
+static void
+get_modminer_statline_before(char *buf, struct cgpu_info *modminer)
+{
+	float gt = modminer->temp;
+	if (gt > 0)
+		tailsprintf(buf, "%5.1fC ", gt);
+	else
+		tailsprintf(buf, "       ", gt);
+	tailsprintf(buf, "        | ");
+}
+
+#define bailout(...)  return _bailout(-1, modminer, __VA_ARGS__);
+#define bailout2(...)  return _bailout(fd, modminer, __VA_ARGS__);
+
+#define check_magic(L)  do {  \
+	if (1 != fread(buf, 1, 1, f))  \
+		bailout(LOG_ERR, "Error reading ModMiner firmware ('%c')", L);  \
+	if (buf[0] != L)  \
+		bailout(LOG_ERR, "ModMiner firmware has wrong magic ('%c')", L);  \
+} while(0)
+
+#define read_str(eng)  do {  \
+	if (1 != fread(buf, 2, 1, f))  \
+		bailout(LOG_ERR, "Error reading ModMiner firmware (" eng " len)");  \
+	len = (ubuf[0] << 8) | ubuf[1];  \
+	if (len >= sizeof(buf))  \
+		bailout(LOG_ERR, "ModMiner firmware " eng " too long");  \
+	if (1 != fread(buf, len, 1, f))  \
+		bailout(LOG_ERR, "Error reading ModMiner firmware (" eng ")");  \
+	buf[len] = '\0';  \
+} while(0)
+
+#define status_read(eng)  do {  \
+FD_SET(fd, &fds);  \
+select(fd+1, &fds, NULL, NULL, NULL);  \
+	if (1 != read(fd, buf, 1))  \
+		bailout2(LOG_ERR, "Error programming ModMiner %s (" eng ")", modminer->device_path);  \
+	if (buf[0] != 1)  \
+		bailout2(LOG_ERR, "Wrong " eng " programming ModMiner %s", modminer->device_path);  \
+} while(0)
+
+static bool
+modminer_fpga_upload_bitstream(struct cgpu_info*modminer)
+{
+fd_set fds;
+	char buf[0x100];
+	unsigned char *ubuf = (unsigned char*)buf;
+	unsigned long len;
+	char *p;
+	const char *fwfile = BITSTREAM_FILENAME;
+	char fpgaid = 4;  // "all FPGAs"
+
+	FILE *f = open_bitstream("modminer", fwfile);
+	if (!f)
+		bailout(LOG_ERR, "Error opening ModMiner firmware file %s", fwfile);
+	if (1 != fread(buf, 2, 1, f))
+		bailout(LOG_ERR, "Error reading ModMiner firmware (magic)");
+	if (buf[0] || buf[1] != 9)
+		bailout(LOG_ERR, "ModMiner firmware has wrong magic (9)");
+	if (-1 == fseek(f, 11, SEEK_CUR))
+		bailout(LOG_ERR, "ModMiner firmware seek failed");
+	check_magic('a');
+	read_str("design name");
+	applog(LOG_DEBUG, "ModMiner firmware file %s info:", fwfile);
+	applog(LOG_DEBUG, "  Design name: %s", buf);
+	p = strrchr(buf, ';') ?: buf;
+	p = strrchr(buf, '=') ?: p;
+	if (p[0] == '=')
+		++p;
+	long fwusercode = strtol(p, &p, 16);
+	if (p[0] != '\0')
+		bailout(LOG_ERR, "Bad usercode in ModMiner firmware file");
+	if (fwusercode == 0xffffffff)
+		bailout(LOG_ERR, "ModMiner firmware doesn't support user code");
+	applog(LOG_DEBUG, "  Version: %u, build %u", (fwusercode >> 8) & 0xff, fwusercode & 0xff);
+	check_magic('b');
+	read_str("part number");
+	applog(LOG_DEBUG, "  Part number: %s", buf);
+	check_magic('c');
+	read_str("build date");
+	applog(LOG_DEBUG, "  Build date: %s", buf);
+	check_magic('d');
+	read_str("build time");
+	applog(LOG_DEBUG, "  Build time: %s", buf);
+	check_magic('e');
+	if (1 != fread(buf, 4, 1, f))
+		bailout(LOG_ERR, "Error reading ModMiner firmware (data len)");
+	len = ((unsigned long)ubuf[0] << 24) | ((unsigned long)ubuf[1] << 16) | (ubuf[2] << 8) | ubuf[3];
+	applog(LOG_DEBUG, "  Bitstream size: %lu", len);
+
+	int fd = modminer->device_fd;
+
+	applog(LOG_WARNING, "Programming %s... DO NOT EXIT CGMINER UNTIL COMPLETE", modminer->device_path, fpgaid);
+	buf[0] = '\x05';  // Program Bitstream
+	buf[1] = fpgaid;
+	buf[2] = (len >>  0) & 0xff;
+	buf[3] = (len >>  8) & 0xff;
+	buf[4] = (len >> 16) & 0xff;
+	buf[5] = (len >> 24) & 0xff;
+	if (6 != write(fd, buf, 6))
+		bailout2(LOG_ERR, "Error programming ModMiner %s (cmd)", modminer->device_path);
+	status_read("cmd reply");
+	size_t buflen;
+	while (len) {
+		buflen = len < 32 ? len : 32;
+		if (fread(buf, buflen, 1, f) != 1)
+			bailout2(LOG_ERR, "File underrun programming ModMiner %s (%d bytes left)", modminer->device_path, len);
+		if (write(fd, buf, buflen) != buflen)
+			bailout2(LOG_ERR, "Error programming ModMiner %s (data)");
+		status_read("status");
+		len -= buflen;
+	}
+	status_read("final status");
+	applog(LOG_WARNING, "Done programming %s", modminer->device_path);
+
+	return true;
+}
+
+static bool
+modminer_device_prepare(struct cgpu_info *modminer)
+{
+	int fd = serial_open(modminer->device_path, 0, /*FIXME=-1*/3000, true);
+	if (unlikely(-1 == fd))
+		bailout(LOG_ERR, "Failed to open ModMiner on %s", modminer->device_path);
+
+	modminer->device_fd = fd;
+	applog(LOG_INFO, "Opened ModMiner on %s", modminer->device_path);
+
+	struct timeval now;
+	gettimeofday(&now, NULL);
+	get_datestamp(modminer->init, &now);
+
+	return true;
+}
+
+#undef bailout
+
+struct modminer_fpga_state {
+	bool work_running;
+	struct work running_work;
+	struct timeval tv_workstart;
+	uint32_t hashes;
+
+	char next_work_cmd[46];
+
+	unsigned char clock;
+	int no_nonce_counter;
+	int good_share_counter;
+	time_t last_cutoff_reduced;
+};
+
+static bool
+modminer_fpga_prepare(struct thr_info *thr)
+{
+	struct cgpu_info *modminer = thr->cgpu;
+
+	// Don't need to lock the mutex here, since prepare runs from the main thread before the miner threads start
+	if (modminer->device_fd == -1 && !modminer_device_prepare(modminer))
+		return false;
+
+	struct modminer_fpga_state *state;
+	state = thr->cgpu_data = calloc(1, sizeof(struct modminer_fpga_state));
+	state->next_work_cmd[0] = '\x08';  // Send Job
+	state->next_work_cmd[1] = thr->device_thread;  // FPGA id
+
+	return true;
+}
+
+static bool
+modminer_reduce_clock(struct thr_info*thr, bool needlock)
+{
+	struct cgpu_info*modminer = thr->cgpu;
+	struct modminer_fpga_state *state = thr->cgpu_data;
+	char fpgaid = thr->device_thread;
+	int fd = modminer->device_fd;
+	unsigned char cmd[6], buf[1];
+
+	if (state->clock <= 100)
+		return false;
+
+	cmd[0] = '\x06';  // set clock speed
+	cmd[1] = fpgaid;
+	cmd[2] = state->clock -= 2;
+	cmd[3] = cmd[4] = cmd[5] = '\0';
+
+	if (needlock)
+		mutex_lock(&modminer->device_mutex);
+	if (6 != write(fd, cmd, 6))
+		bailout2(LOG_ERR, "Error writing to ModMiner (set clock speed)");
+	if (serial_read(fd, &buf, 1) != 1)
+		bailout2(LOG_ERR, "Error reading from ModMiner (set clock speed)");
+	if (needlock)
+		mutex_unlock(&modminer->device_mutex);
+
+	applog(LOG_WARNING, "ModMiner: Setting clock speed of %s %d (FPGA #%u) to %u", modminer->api->name, modminer->device_id, fpgaid, state->clock);
+
+	return true;
+}
+
+static bool
+modminer_fpga_init(struct thr_info *thr)
+{
+	struct cgpu_info *modminer = thr->cgpu;
+	struct modminer_fpga_state *state = thr->cgpu_data;
+	int fd;
+	char fpgaid = thr->device_thread;
+
+	unsigned char cmd[2], buf[4];
+
+	mutex_lock(&modminer->device_mutex);
+	fd = modminer->device_fd;
+	if (fd == -1) {
+		// Died in another thread...
+		mutex_unlock(&modminer->device_mutex);
+		return false;
+	}
+
+	cmd[0] = '\x04';  // Read USER code (bitstream id)
+	cmd[1] = fpgaid;
+	if (write(fd, cmd, 2) != 2)
+		bailout2(LOG_ERR, "Error writing to ModMiner (read USER code)");
+	if (serial_read(fd, buf, 4) != 4)
+		bailout2(LOG_ERR, "Error reading from ModMiner (read USER code)");
+
+	if (memcmp(buf, BISTREAM_USER_ID, 4)) {
+		applog(LOG_ERR, "FPGA #%d not programmed", fpgaid);
+		if (!modminer_fpga_upload_bitstream(modminer))
+			return false;
+	}
+	else
+		applog(LOG_DEBUG, "FPGA #%d is already programmed :)", fpgaid);
+
+	state->clock = 212;  // Will be reduced to 210 by modminer_reduce_clock
+	modminer_reduce_clock(thr, false);
+
+	mutex_unlock(&modminer->device_mutex);
+
+	return true;
+}
+
+static bool
+modminer_prepare_next_work(struct modminer_fpga_state*state, struct work*work)
+{
+	char *midstate = state->next_work_cmd + 2;
+	char *taildata = midstate + 32;
+	if (!(memcmp(midstate, work->midstate, 32) || memcmp(taildata, work->data + 64, 12)))
+		return false;
+	memcpy(midstate, work->midstate, 32);
+	memcpy(taildata, work->data + 64, 12);
+	return true;
+}
+
+static bool
+modminer_start_work(struct thr_info*thr)
+{
+fd_set fds;
+	struct cgpu_info*modminer = thr->cgpu;
+	struct modminer_fpga_state *state = thr->cgpu_data;
+	int fd = modminer->device_fd;
+
+	char buf[1];
+
+	mutex_lock(&modminer->device_mutex);
+	if (46 != write(fd, state->next_work_cmd, 46))
+		bailout2(LOG_ERR, "Error writing to ModMiner (start work)");
+	gettimeofday(&state->tv_workstart, NULL);
+	state->hashes = 0;
+	status_read("start work");
+	mutex_unlock(&modminer->device_mutex);
+
+	return true;
+}
+
+#define work_restart(thr)  work_restart[thr->id].restart
+
+static uint64_t
+modminer_process_results(struct thr_info*thr)
+{
+	struct cgpu_info*modminer = thr->cgpu;
+	struct modminer_fpga_state *state = thr->cgpu_data;
+	char fpgaid = thr->device_thread;
+	int fd = modminer->device_fd;
+	struct work *work = &state->running_work;
+
+	char cmd[2], temperature;
+	uint32_t nonce;
+	long iter;
+	bool bad;
+	cmd[0] = '\x0a';
+	cmd[1] = fpgaid;
+
+	mutex_lock(&modminer->device_mutex);
+	if (2 == write(fd, cmd, 2) && read(fd, &temperature, 1) == 1)
+	{
+		modminer->temp = (float)temperature;
+		if (temperature > modminer->cutofftemp - 2) {
+			if (temperature > modminer->cutofftemp) {
+				applog(LOG_WARNING, "Hit thermal cutoff limit on %s %d, disabling!", modminer->api->name, modminer->device_id);
+				modminer->deven = DEV_RECOVER;
+
+				modminer->device_last_not_well = time(NULL);
+				modminer->device_not_well_reason = REASON_DEV_THERMAL_CUTOFF;
+				++modminer->dev_thermal_cutoff_count;
+			} else {
+				time_t now = time(NULL);
+				if (state->last_cutoff_reduced != now) {
+					state->last_cutoff_reduced = now;
+					modminer_reduce_clock(thr, false);
+				}
+			}
+		}
+	}
+
+	cmd[0] = '\x09';
+	iter = 200;
+	while (1) {
+		if (write(fd, cmd, 2) != 2)
+			bailout2(LOG_ERR, "Error reading from ModMiner (get nonce)");
+		serial_read(fd, &nonce, 4);
+		mutex_unlock(&modminer->device_mutex);
+		if (memcmp(&nonce, "\xff\xff\xff\xff", 4)) {
+			state->no_nonce_counter = 0;
+			bad = !test_nonce(work, nonce);
+			if (!bad)
+				submit_nonce(thr, work, nonce);
+			else {
+				++hw_errors;
+				if (++modminer->hw_errors * 100 > 1000 + state->good_share_counter)
+					// Only reduce clocks if hardware errors are more than ~1% of results
+					modminer_reduce_clock(thr, true);
+			}
+		}
+		else
+		if (++state->no_nonce_counter > 18000) {
+			state->no_nonce_counter = 0;
+			modminer_reduce_clock(thr, true);
+		}
+		if (work_restart(thr))
+			break;
+		usleep(10000);
+		if (work_restart(thr) || !--iter)
+			break;
+		mutex_lock(&modminer->device_mutex);
+	}
+
+	struct timeval tv_workend, elapsed;
+	gettimeofday(&tv_workend, NULL);
+	timeval_subtract(&elapsed, &tv_workend, &state->tv_workstart);
+
+	uint64_t hashes = (uint64_t)state->clock * (((uint64_t)elapsed.tv_sec * 1000000) + elapsed.tv_usec);
+	if (hashes > 0xffffffff)
+		hashes = 0xffffffff;
+	else
+	if (hashes <= state->hashes)
+		hashes = 1;
+	else
+		hashes -= state->hashes;
+	state->hashes += hashes;
+	return hashes;
+}
+
+static uint64_t
+modminer_scanhash(struct thr_info*thr, struct work*work, uint64_t __maybe_unused max_nonce)
+{
+	struct modminer_fpga_state *state = thr->cgpu_data;
+	uint64_t hashes = 1;
+	bool startwork;
+
+	startwork = modminer_prepare_next_work(state, work);
+	if (state->work_running) {
+		hashes = modminer_process_results(thr);
+		if (work_restart(thr)) {
+			state->work_running = false;
+			return 1;
+		}
+	}
+	else
+		state->work_running = true;
+
+	if (startwork) {
+		if (!modminer_start_work(thr))
+			return 0;
+		memcpy(&state->running_work, work, sizeof(state->running_work));
+	}
+
+	// This is intentionally early
+	work->blk.nonce += hashes;
+	return hashes;
+}
+
+static void
+modminer_fpga_shutdown(struct thr_info *thr)
+{
+	free(thr->cgpu_data);
+}
+
+struct device_api modminer_api = {
+	.dname = "modminer",
+	.name = "PGA",
+	.api_detect = modminer_detect,
+	.get_statline_before = get_modminer_statline_before,
+	.thread_prepare = modminer_fpga_prepare,
+	.thread_init = modminer_fpga_init,
+	.scanhash = modminer_scanhash,
+	.thread_shutdown = modminer_fpga_shutdown,
+};

+ 1 - 0
miner.h

@@ -313,6 +313,7 @@ struct cgpu_info {
 #endif
 		int device_fd;
 	};
+	pthread_mutex_t		device_mutex;
 
 	enum dev_enable deven;
 	int accepted;