Browse Source

Merge branch 'bifury' into bfgminer

Luke Dashjr 12 years ago
parent
commit
9cf587491e
9 changed files with 888 additions and 74 deletions
  1. 4 0
      Makefile.am
  2. 21 0
      README.ASIC
  3. 14 0
      configure.ac
  4. 576 0
      driver-bifury.c
  5. 8 4
      lowl-vcom.c
  6. 117 63
      miner.c
  7. 4 0
      miner.h
  8. 122 5
      util.c
  9. 22 2
      util.h

+ 4 - 0
Makefile.am

@@ -248,6 +248,10 @@ if HAS_ZTEX
 bfgminer_SOURCES += driver-ztex.c libztex.c libztex.h
 endif
 
+if USE_BIFURY
+bfgminer_SOURCES += driver-bifury.c
+endif
+
 if HAS_BITFURY
 bfgminer_SOURCES += driver-bitfury.c driver-bitfury.h libbitfury.c libbitfury.h spidevc.h spidevc.c
 

+ 21 - 0
README.ASIC

@@ -90,6 +90,27 @@ For example:
     sudo bfgminer -S bfsb:auto
 
 
+BI*FURY
+-------
+
+Bi*Fury should just work; you may need to use -S bifury:<path>
+
+On Windows, you will need to install the standard USB CDC driver for it.
+TODO
+
+If you want to upgrade the firmware, unplug your device. You will need to
+temporarily short a circuit. With the USB connector pointing forward, and the
+heatsink down, look to the forward-right; you will see two tiny lights, a set of
+2 terminals, and a set of 3 terminals. The ones you need to short are the set of
+2. With them shorted, plug the device back into your computer. It will then
+pretend to be a mass storage disk drive. If you use Windows, you can play along
+and just overwrite the firmware.bin file. If you use Linux, you must use mcopy:
+    mcopy -i /dev/disk/by-id/usb-NXP_LPC1XXX_IFLASH_ISP-0:0 firmware.bin \
+        ::/firmware.bin
+After this is complete, unplug the device again and un-short the 2 terminals.
+This completes the upgrade and you can now plug it back in and start mining.
+
+
 BIG PICTURE MINING BITFURY USB
 ------------------------------
 

+ 14 - 0
configure.ac

@@ -602,6 +602,20 @@ fi
 AM_CONDITIONAL([HAS_ZTEX], [test x$ztex = xyes])
 
 
+driverlist="$driverlist bifury"
+AC_ARG_ENABLE([bifury],
+	[AC_HELP_STRING([--disable-bifury],[Compile support for Bi*Fury (default enabled)])],
+	[bifury=$enableval],
+	[bifury=yes]
+	)
+if test "x$bifury" = "xyes"; then
+	AC_DEFINE([USE_BIFURY], [1], [Defined to 1 if Bi*Fury support is wanted])
+	need_lowl_vcom=yes
+	has_asic=yes
+fi
+AM_CONDITIONAL([USE_BIFURY], [test x$bifury = xyes])
+
+
 driverlist="$driverlist bitfury_gpio/bitfury"
 bitfury=yes
 AC_ARG_ENABLE([bitfury],

+ 576 - 0
driver-bifury.c

@@ -0,0 +1,576 @@
+/*
+ * Copyright 2013 Luke Dashjr
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.  See COPYING for more details.
+ */
+
+#include "config.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <uthash.h>
+
+#include "deviceapi.h"
+#include "logging.h"
+#include "lowlevel.h"
+#include "lowl-vcom.h"
+#include "miner.h"
+#include "util.h"
+
+#define BIFURY_MAX_QUEUED 0x10
+
+BFG_REGISTER_DRIVER(bifury_drv)
+
+const char bifury_init_cmds[] = "flush\ntarget ffffffff\nmaxroll 0\n";
+
+static
+ssize_t bifury_write(const struct cgpu_info * const dev, const void * const buf, const size_t count)
+{
+	const int fd = dev->device_fd;
+	if (opt_dev_protocol)
+	{
+		const size_t psz = (((const char*)buf)[count-1] == '\n') ? (count - 1) : count;
+		applog(LOG_DEBUG, "%s: DEVPROTO: SEND %.*s", dev->dev_repr, psz, (const char*)buf);
+	}
+	return write(fd, buf, count);
+}
+
+static
+void *bifury_readln(int fd, bytes_t *leftover)
+{
+	uint8_t buf[0x40];
+	ssize_t r;
+	
+parse:
+	if ( (r = bytes_find(leftover, '\n')) >= 0)
+	{
+		uint8_t *ret = malloc(r+1);
+		if (r)
+			memcpy(ret, bytes_buf(leftover), r);
+		ret[r] = '\0';
+		bytes_shift(leftover, r + 1);
+		return ret;
+	}
+	if ( (r = read(fd, buf, sizeof(buf))) > 0)
+	{
+		bytes_append(leftover, buf, r);
+		goto parse;
+	}
+	return NULL;
+}
+
+struct bifury_state {
+	bytes_t buf;
+	uint32_t last_work_id;
+	int needwork;
+	bool has_needwork;
+	uint8_t *osc6_bits;
+	bool send_clock;
+};
+
+static
+bool bifury_lowl_match(const struct lowlevel_device_info * const info)
+{
+	return lowlevel_match_product(info, "bi\xe2\x80\xa2""fury");
+}
+
+static
+bool bifury_detect_one(const char * const devpath)
+{
+	char buf[0x40], *p, *q;
+	bytes_t reply = BYTES_INIT;
+	int major, minor, hwrev, chips;
+	struct cgpu_info *cgpu;
+	struct timeval tv_timeout;
+	const int fd = serial_open(devpath, 0, 10, true);
+	applog(LOG_DEBUG, "%s: %s %s",
+	       bifury_drv.dname,
+	       ((fd == -1) ? "Failed to open" : "Successfully opened"),
+	       devpath);
+	
+	if (unlikely(fd == -1))
+		return false;
+	
+	while (read(fd, buf, sizeof(buf)) == sizeof(buf))
+	{}
+	
+	if (opt_dev_protocol)
+		applog(LOG_DEBUG, "%s fd=%d: DEVPROTO: SEND %s", bifury_drv.dname, fd, "version");
+	if (8 != write(fd, "version\n", 8))
+	{
+		applog(LOG_DEBUG, "%s: Error sending version request", bifury_drv.dname);
+		goto err;
+	}
+	
+	timer_set_delay_from_now(&tv_timeout, 1000000);
+	while (true)
+	{
+		p = bifury_readln(fd, &reply);
+		if (p)
+		{
+			if (opt_dev_protocol)
+				applog(LOG_DEBUG, "%s fd=%d: DEVPROTO: RECV %s",
+				       bifury_drv.dname, fd, p);
+			if (!strncmp("version ", p, 8))
+				break;
+			free(p);
+		}
+		if (timer_passed(&tv_timeout, NULL))
+		{
+			applog(LOG_DEBUG, "%s: Timed out waiting for response to version request",
+			       bifury_drv.dname);
+			goto err;
+		}
+	}
+	
+	bytes_free(&reply);
+	serial_close(fd);
+	
+	major = strtol(&p[8], &p, 10);
+	if (p == &buf[8] || p[0] != '.')
+		goto parseerr;
+	minor = strtol(&p[1], &q, 10);
+	if (p == q || strncmp(" rev ", q, 5))
+		goto parseerr;
+	hwrev = strtol(&q[5], &p, 10);
+	if (p == q || strncmp(" chips ", p, 7))
+		goto parseerr;
+	chips = strtol(&p[7], &q, 10);
+	if (p == q || chips < 1)
+		goto parseerr;
+	
+	applog(LOG_DEBUG, "%s: Found firmware %d.%d on hardware rev %d with %d chips",
+	       bifury_drv.dname, major, minor, hwrev, chips);
+	
+	cgpu = malloc(sizeof(*cgpu));
+	*cgpu = (struct cgpu_info){
+		.drv = &bifury_drv,
+		.device_path = strdup(devpath),
+		.deven = DEV_ENABLED,
+		.procs = chips,
+		.threads = 1,
+		.cutofftemp = 75,
+	};
+	// NOTE: Xcode's clang has a bug where it cannot find fields inside anonymous unions (more details in fpgautils)
+	cgpu->device_fd = -1;
+	
+	return add_cgpu(cgpu);
+
+parseerr:
+	applog(LOG_DEBUG, "%s: Error parsing version response", bifury_drv.dname);
+	return false;
+
+err:
+	bytes_free(&reply);
+	serial_close(fd);
+	return false;
+}
+
+static
+bool bifury_lowl_probe(const struct lowlevel_device_info * const info)
+{
+	return vcom_lowl_probe_wrapper(info, bifury_detect_one);
+}
+
+static
+bool bifury_set_queue_full(const struct cgpu_info * const dev, int needwork)
+{
+	struct bifury_state * const state = dev->device_data;
+	struct thr_info * const master_thr = dev->thr[0];
+	const int fd = dev->device_fd;
+	if (needwork != -1)
+		state->needwork = needwork;
+	const bool full = (fd == -1 || !state->needwork);
+	if (full == master_thr->queue_full)
+		return full;
+	for (const struct cgpu_info *proc = dev; proc; proc = proc->next_proc)
+	{
+		struct thr_info * const thr = proc->thr[0];
+		thr->queue_full = full;
+	}
+	return full;
+}
+
+void bifury_send_clock(const struct cgpu_info * const dev)
+{
+	struct bifury_state * const state = dev->device_data;
+	const struct cgpu_info *proc;
+	size_t clockbufsz = 5 + (3 * dev->procs) + 1 + 1;
+	char clockbuf[clockbufsz];
+	strcpy(clockbuf, "clock");
+	proc = dev;
+	for (int i = 0; i < dev->procs; ++i, (proc = proc->next_proc))
+	{
+		const struct thr_info * const thr = proc->thr[0];
+		int clk;
+		if (proc->deven == DEV_ENABLED && !thr->pause)
+			clk = state->osc6_bits[i];
+		else
+			clk = 0;
+		tailsprintf(clockbuf, clockbufsz, " %d", clk);
+	}
+	tailsprintf(clockbuf, clockbufsz, "\n");
+	--clockbufsz;
+	if (clockbufsz != bifury_write(dev, clockbuf, clockbufsz))
+	{
+		state->send_clock = true;
+		applog(LOG_ERR, "%s: Failed to send clock assignments",
+		       dev->dev_repr);
+	}
+	else
+		state->send_clock = false;
+}
+
+static
+bool bifury_thread_init(struct thr_info *master_thr)
+{
+	struct cgpu_info * const dev = master_thr->cgpu, *proc;
+	struct bifury_state * const state = malloc(sizeof(*state));
+	if (!state)
+		return false;
+	*state = (struct bifury_state){
+		.buf = BYTES_INIT,
+		.osc6_bits = malloc(sizeof(*state->osc6_bits) * dev->procs),
+	};
+	for (int i = 0; i < dev->procs; ++i)
+		state->osc6_bits[i] = 54;
+	for (proc = dev; proc; proc = proc->next_proc)
+	{
+		proc->device_data = state;
+		proc->status = LIFE_INIT2;
+	}
+	bifury_set_queue_full(dev, 0);
+	timer_set_now(&master_thr->tv_poll);
+	return true;
+}
+
+static
+void bifury_reinit(struct cgpu_info * const proc)
+{
+	timer_set_now(&proc->thr[0]->tv_poll);
+}
+
+void bifury_trigger_send_clock(struct thr_info * const thr)
+{
+	struct cgpu_info * const proc = thr->cgpu;
+	struct bifury_state * const state = proc->device_data;
+	
+	state->send_clock = true;
+}
+
+static
+void bifury_common_error(struct cgpu_info * const dev, const enum dev_reason reason)
+{
+	for (struct cgpu_info *proc = dev; proc; proc = proc->next_proc)
+	{
+		struct thr_info * const thr = proc->thr[0];
+		dev_error(proc, reason);
+		inc_hw_errors_only(thr);
+	}
+}
+
+static
+bool bifury_queue_append(struct thr_info * const thr, struct work *work)
+{
+	const struct cgpu_info * const dev = thr->cgpu->device;
+	struct bifury_state * const state = dev->device_data;
+	if (bifury_set_queue_full(dev, -1))
+		return false;
+	
+	struct thr_info * const master_thr = dev->thr[0];
+	char buf[5 + 0x98 + 1 + 8 + 1];
+	memcpy(buf, "work ", 5);
+	bin2hex(&buf[5], work->data, 0x4c);
+	work->device_id = ++state->last_work_id;
+	sprintf(&buf[5 + 0x98], " %08x", work->device_id);
+	buf[5 + 0x98 + 1 + 8] = '\n';
+	if (sizeof(buf) != bifury_write(dev, buf, sizeof(buf)))
+	{
+		applog(LOG_ERR, "%s: Failed to send work", dev->dev_repr);
+		return false;
+	}
+	HASH_ADD(hh, master_thr->work_list, device_id, sizeof(work->device_id), work);
+	int prunequeue = HASH_COUNT(master_thr->work_list) - BIFURY_MAX_QUEUED;
+	if (prunequeue > 0)
+	{
+		struct work *tmp;
+		applog(LOG_DEBUG, "%s: Pruning %d old work item%s",
+		       dev->dev_repr, prunequeue, prunequeue == 1 ? "" : "s");
+		HASH_ITER(hh, master_thr->work_list, work, tmp)
+		{
+			HASH_DEL(master_thr->work_list, work);
+			free_work(work);
+			if (--prunequeue < 1)
+				break;
+		}
+	}
+	bifury_set_queue_full(dev, state->needwork - 1);
+	return true;
+}
+
+static
+void bifury_queue_flush(struct thr_info * const thr)
+{
+	const struct cgpu_info *dev = thr->cgpu;
+	if (dev != dev->device)
+		return;
+	const int fd = dev->device_fd;
+	if (fd != -1)
+		bifury_write(dev, "flush\n", 6);
+	bifury_set_queue_full(dev, dev->procs);
+}
+
+static
+const struct cgpu_info *device_proc_by_id(const struct cgpu_info * const dev, int procid)
+{
+	const struct cgpu_info *proc = dev;
+	for (int i = 0; i < procid; ++i)
+	{
+		proc = proc->next_proc;
+		if (unlikely(!proc))
+			return NULL;
+	}
+	return proc;
+}
+
+static
+void bifury_handle_cmd(struct cgpu_info * const dev, const char * const cmd)
+{
+	struct thr_info * const master_thr = dev->thr[0];
+	struct bifury_state * const state = dev->device_data;
+	struct thr_info *thr;
+	struct work *work;
+	char *p;
+	
+	if (!strncmp(cmd, "submit ", 7))
+	{
+		// submit <nonce> <jobid> <timestamp> <chip>
+		uint32_t nonce = strtoll(&cmd[7], &p, 0x10);
+		const uint32_t jobid = strtoll(&p[1], &p, 0x10);
+		const uint32_t ntime = strtoll(&p[1], &p, 0x10);
+		const int chip = atoi(&p[1]);
+		nonce = le32toh(nonce);
+		const struct cgpu_info * const proc = device_proc_by_id(dev, chip);
+		thr = proc->thr[0];
+		
+		HASH_FIND(hh, master_thr->work_list, &jobid, sizeof(jobid), work);
+		if (work)
+		{
+			const uint32_t work_ntime = be32toh(*(uint32_t*)&work->data[68]);
+			submit_noffset_nonce(thr, work, nonce, ntime - work_ntime);
+		}
+		else
+		if (!jobid)
+			applog(LOG_DEBUG, "%s: Dummy submit ignored", dev->dev_repr);
+		else
+			inc_hw_errors2(thr, NULL, &nonce);
+		if (!state->has_needwork)
+			bifury_set_queue_full(dev, state->needwork + 2);
+	}
+	else
+	if (!strncmp(cmd, "temp ", 5))
+	{
+		struct cgpu_info *proc;
+		const int decicelsius = atoi(&cmd[5]);
+		if (decicelsius)
+		{
+			const float celsius = 0.1 * (float)decicelsius;
+			for (proc = dev; proc; proc = proc->next_proc)
+				proc->temp = celsius;
+		}
+	}
+	else
+	if (!strncmp(cmd, "job ", 4))
+	{
+		// job <jobid> <timestamp> <chip>
+		const uint32_t jobid = strtoll(&cmd[4], &p, 0x10);
+		strtoll(&p[1], &p, 0x10);
+		const int chip = atoi(&p[1]);
+		const struct cgpu_info * const proc = device_proc_by_id(dev, chip);
+		thr = proc->thr[0];
+		hashes_done2(thr, 0xbd000000, NULL);
+		
+		HASH_FIND(hh, master_thr->work_list, &jobid, sizeof(jobid), work);
+		if (work)
+		{
+			HASH_DEL(master_thr->work_list, work);
+			free_work(work);
+		}
+	}
+	else
+	if (!strncmp(cmd, "hwerror ", 8))
+	{
+		const int chip = atoi(&cmd[8]);
+		const struct cgpu_info * const proc = device_proc_by_id(dev, chip);
+		thr = proc->thr[0];
+		inc_hw_errors2(thr, NULL, UNKNOWN_NONCE);
+	}
+	else
+	if (!strncmp(cmd, "needwork ", 9))
+	{
+		const int needwork = atoi(&cmd[9]);
+		state->has_needwork = true;
+		bifury_set_queue_full(dev, needwork);
+		applog(LOG_DEBUG, "%s: needwork=%d", dev->dev_repr, state->needwork);
+	}
+}
+
+static
+void bifury_poll(struct thr_info * const master_thr)
+{
+	struct cgpu_info * const dev = master_thr->cgpu;
+	struct bifury_state * const state = dev->device_data;
+	int fd = dev->device_fd;
+	char *cmd;
+	
+	if (unlikely(fd == -1))
+	{
+		fd = serial_open(dev->device_path, 0, 1, true);
+		if (unlikely(fd == -1))
+		{
+			applog(LOG_ERR, "%s: Failed to open %s",
+			       dev->dev_repr, dev->device_path);
+			bifury_common_error(dev, REASON_THREAD_FAIL_INIT);
+			return;
+		}
+		
+		dev->device_fd = fd;
+		if (sizeof(bifury_init_cmds)-1 != bifury_write(dev, bifury_init_cmds, sizeof(bifury_init_cmds)-1))
+		{
+			applog(LOG_ERR, "%s: Failed to send configuration", dev->dev_repr);
+			bifury_common_error(dev, REASON_THREAD_FAIL_INIT);
+			serial_close(fd);
+			dev->device_fd = -1;
+			return;
+		}
+		
+		bifury_set_queue_full(dev, dev->procs * 2);
+		state->send_clock = true;
+	}
+	
+	if (state->send_clock)
+		bifury_send_clock(dev);
+	
+	while ( (cmd = bifury_readln(fd, &state->buf)) )
+	{
+		if (opt_dev_protocol)
+			applog(LOG_DEBUG, "%s: DEVPROTO: RECV %s", dev->dev_repr, cmd);
+		bifury_handle_cmd(dev, cmd);
+		free(cmd);
+	}
+}
+
+static
+struct api_data *bifury_api_device_status(struct cgpu_info * const proc)
+{
+	struct bifury_state * const state = proc->device_data;
+	struct api_data *root = NULL;
+	int osc6_bits = state->osc6_bits[proc->proc_id];
+	
+	root = api_add_int(root, "Clock Bits", &osc6_bits, true);
+	
+	return root;
+}
+
+char *bifury_set_device(struct cgpu_info * const proc, char * const option, char * const setting, char * const replybuf)
+{
+	struct bifury_state * const state = proc->device_data;
+	
+	if (!strcasecmp(option, "help"))
+	{
+		sprintf(replybuf, "osc6_bits: range 33-63 (slow to fast)");
+		return replybuf;
+	}
+	
+	if (!strcasecmp(option, "osc6_bits"))
+	{
+		if (!setting || !*setting)
+		{
+			sprintf(replybuf, "missing setting");
+			return replybuf;
+		}
+		const uint8_t val = atoi(setting);
+		if (val < 33 || val > 63)
+		{
+			sprintf(replybuf, "invalid setting");
+			return replybuf;
+		}
+		
+		state->osc6_bits[proc->proc_id] = val;
+		state->send_clock = true;
+		
+		return NULL;
+	}
+	
+	sprintf(replybuf, "Unknown option: %s", option);
+	return replybuf;
+}
+
+#ifdef HAVE_CURSES
+void bifury_tui_wlogprint_choices(struct cgpu_info * const proc)
+{
+	wlogprint("[O]scillator bits ");
+}
+
+const char *bifury_tui_handle_choice(struct cgpu_info * const proc, const int input)
+{
+	struct bifury_state * const state = proc->device_data;
+	
+	switch (input)
+	{
+		case 'o': case 'O':
+		{
+			const int val = curses_int("Set oscillator bits (range 33-63; slow to fast)");
+			if (val < 33 || val > 63)
+				return "Invalid oscillator bits\n";
+			
+			state->osc6_bits[proc->proc_id] = val;
+			state->send_clock = true;
+			
+			return "Oscillator bits changing\n";
+		}
+	}
+	return NULL;
+}
+
+void bifury_wlogprint_status(struct cgpu_info * const proc)
+{
+	const struct bifury_state * const state = proc->device_data;
+	const int osc6_bits = state->osc6_bits[proc->proc_id];
+	wlogprint("Oscillator bits: %d\n", osc6_bits);
+}
+#endif
+
+struct device_drv bifury_drv = {
+	.dname = "bifury",
+	.name = "BIF",
+	.lowl_match = bifury_lowl_match,
+	.lowl_probe = bifury_lowl_probe,
+	
+	.thread_init = bifury_thread_init,
+	.reinit_device = bifury_reinit,
+	.thread_disable = bifury_trigger_send_clock,
+	.thread_enable = bifury_trigger_send_clock,
+	
+	.minerloop = minerloop_queue,
+	.queue_append = bifury_queue_append,
+	.queue_flush = bifury_queue_flush,
+	.poll = bifury_poll,
+	
+	.get_api_extra_device_status = bifury_api_device_status,
+	.set_device = bifury_set_device,
+	
+#ifdef HAVE_CURSES
+	.proc_wlogprint_status = bifury_wlogprint_status,
+	.proc_tui_wlogprint_choices = bifury_tui_wlogprint_choices,
+	.proc_tui_handle_choice = bifury_tui_handle_choice,
+#endif
+};

+ 8 - 4
lowl-vcom.c

@@ -354,7 +354,7 @@ char *windows_usb_get_port_path(HANDLE hubh, const int portno)
 	if (!(DeviceIoControl(hubh, IOCTL_USB_GET_NODE_CONNECTION_NAME, path, bufsz, path, bufsz, &rsz, NULL) && rsz >= sizeof(*path)))
 		applogfailinfor(NULL, LOG_ERR, "ioctl (2)", "%s", bfg_strerror(GetLastError(), BST_SYSTEM));
 	
-	return ucs2tochar_dup(path->NodeName, path->ActualLength);
+	return ucs2_to_utf8_dup(path->NodeName, path->ActualLength);
 }
 
 static
@@ -387,7 +387,7 @@ char *windows_usb_get_string(HANDLE hubh, const int portno, const uint8_t descid
 	if (descsz < 2 || desc->bDescriptorType != USB_STRING_DESCRIPTOR_TYPE || desc->bLength > descsz - sizeof(USB_DESCRIPTOR_REQUEST) || desc->bLength % 2)
 		applogfailr(NULL, LOG_ERR, "sanity check");
 	
-	return ucs2tochar_dup(desc->bString, desc->bLength);
+	return ucs2_to_utf8_dup(desc->bString, desc->bLength);
 }
 
 static void _vcom_devinfo_scan_windows__hub(struct lowlevel_device_info **, const char *);
@@ -505,7 +505,7 @@ char *windows_usb_get_root_hub_path(HANDLE hcntlrh)
 	if (!(DeviceIoControl(hcntlrh, IOCTL_USB_GET_ROOT_HUB_NAME, NULL, 0, hubpath, bufsz, &rsz, NULL) && rsz >= sizeof(*hubpath)))
 		applogfailinfor(NULL, LOG_ERR, "ioctl (2)", "%s", bfg_strerror(GetLastError(), BST_SYSTEM));
 	
-	return ucs2tochar_dup(hubpath->RootHubName, hubpath->ActualLength);
+	return ucs2_to_utf8_dup(hubpath->RootHubName, hubpath->ActualLength);
 }
 
 static
@@ -927,7 +927,9 @@ int serial_open(const char *devpath, unsigned long baud, uint8_t timeout, bool p
 		return -1;
 	}
 
-	// thanks to af_newbie for pointers about this
+	if (baud)
+	{
+
 	COMMCONFIG comCfg = {0};
 	comCfg.dwSize = sizeof(COMMCONFIG);
 	comCfg.wVersion = 1;
@@ -940,6 +942,8 @@ int serial_open(const char *devpath, unsigned long baud, uint8_t timeout, bool p
 
 	SetCommConfig(hSerial, &comCfg, sizeof(comCfg));
 
+	}
+
 	// Code must specify a valid timeout value (0 means don't timeout)
 	const DWORD ctoms = ((DWORD)timeout * 100);
 	COMMTIMEOUTS cto = {ctoms, 0, ctoms, 0, ctoms};

+ 117 - 63
miner.c

@@ -325,6 +325,10 @@ const char unicode_micro = 'u';
 #endif
 
 #ifdef HAVE_CURSES
+#define U8_BAD_START "\xef\x80\x81"
+#define U8_BAD_END   "\xef\x80\x80"
+#define AS_BAD(x) U8_BAD_START x U8_BAD_END
+
 bool selecting_device;
 unsigned selected_device;
 #endif
@@ -3268,7 +3272,7 @@ void temperature_column(char *buf, size_t bufsz, bool maybe_unicode, const float
 		maybe_unicode = false;
 	if (temp && *temp > 0.)
 		if (maybe_unicode)
-			snprintf(buf, bufsz, "%4.1f\xb0""C", *temp);
+			snprintf(buf, bufsz, "%4.1f"U8_DEGREE"C", *temp);
 		else
 			snprintf(buf, bufsz, "%4.1fC", *temp);
 	else
@@ -3388,7 +3392,7 @@ void get_statline3(char *buf, size_t bufsz, struct cgpu_info *cgpu, bool for_cur
 #ifdef HAVE_CURSES
 	if (for_curses)
 	{
-		const char *cHrStatsOpt[] = {"\2DEAD \1", "\2SICK \1", "OFF  ", "\2REST \1", " \2ERR \1", "\2WAIT \1", cHr};
+		const char *cHrStatsOpt[] = {AS_BAD("DEAD "), AS_BAD("SICK "), "OFF  ", AS_BAD("REST "), AS_BAD(" ERR "), AS_BAD("WAIT "), cHr};
 		const char *cHrStats;
 		int cHrStatsI = (sizeof(cHrStatsOpt) / sizeof(*cHrStatsOpt)) - 1;
 		bool all_dead = true, all_off = true, all_rdrv = true;
@@ -3483,73 +3487,77 @@ static
 void bfg_waddstr(WINDOW *win, const char *s)
 {
 	const char *p = s;
-#ifdef USE_UNICODE
-	wchar_t buf[2] = {0, 0};
-#else
-	char buf[1];
-#endif
+	int32_t w;
+	int wlen;
+	unsigned char stop_ascii = (use_unicode ? '|' : 0x80);
 	
-#define PREP_ADDCH  do {  \
-	if (p != s)  \
-		waddnstr(win, s, p - s);  \
-	s = ++p;  \
-}while(0)
 	while (true)
 	{
-next:
-		switch(p[0])
+		while (likely(p[0] >= 0x20 && p[0] < stop_ascii))
 		{
+			// Printable ASCII
+			++p;
+		}
+		if (p != s)
+			waddnstr(win, s, p - s);
+		w = utf8_decode(p, &wlen);
+		if (unlikely(p[0] == '\xb5'))  // HACK for Mu (SI prefix micro-)
+		{
+			w = unicode_micro;
+			wlen = 1;
+		}
+		s = p += wlen;
+		switch(w)
+		{
+			// NOTE: U+F000-U+F7FF are reserved for font hacks
 			case '\0':
-				goto done;
-			default:
-def:
-				++p;
-				goto next;
-			case '\1':
-				PREP_ADDCH;
+				return;
+			case 0xf000:  // "bad" off
 				wattroff(win, attr_bad);
-				goto next;
-			case '\2':
-				PREP_ADDCH;
+				break;
+			case 0xf001:  // "bad" on
 				wattron(win, attr_bad);
-				goto next;
+				break;
 #ifdef USE_UNICODE
 			case '|':
-				if (!use_unicode)
-					goto def;
-				PREP_ADDCH;
 				wadd_wch(win, WACS_VLINE);
-				goto next;
+				break;
 #endif
-			case '\xc1':
-			case '\xc4':
+			case 0x2500:  // BOX DRAWINGS LIGHT HORIZONTAL
+			case 0x2534:  // BOX DRAWINGS LIGHT UP AND HORIZONTAL
 				if (!use_unicode)
 				{
-					buf[0] = '-';
+					waddch(win, '-');
 					break;
 				}
 #ifdef USE_UNICODE
-				PREP_ADDCH;
-				wadd_wch(win, (p[-1] == '\xc4') ? WACS_HLINE : WACS_BTEE);
-				goto next;
-			case '\xb0':  // Degrees symbol
-				buf[0] = ((unsigned char *)p)[0];
+				wadd_wch(win, (w == 0x2500) ? WACS_HLINE : WACS_BTEE);
 				break;
 #endif
-			case '\xb5':  // Mu (SI prefix micro-)
-				buf[0] = unicode_micro;
-		}
-		PREP_ADDCH;
+			case 0x2022:
+				if (w > WCHAR_MAX || !iswprint(w))
+					w = '*';
+			default:
 #ifdef USE_UNICODE
-		waddwstr(win, buf);
+				if (w > WCHAR_MAX || !(iswprint(w) || w == '\n'))
+				{
+#if REPLACEMENT_CHAR <= WCHAR_MAX
+					if (iswprint(REPLACEMENT_CHAR))
+						w = REPLACEMENT_CHAR;
+					else
+#endif
+						w = '?';
+				}
+				{
+					wchar_t wc = w;
+					waddnwstr(win, &wc, 1);
+				}
 #else
-		waddch(win, buf[0]);
+				// TODO: Maybe try using sprintf with %ls?
+				waddch(win, '?');
 #endif
+		}
 	}
-done:
-	PREP_ADDCH;
-	return;
-#undef PREP_ADDCH
 }
 
 static inline
@@ -4951,7 +4959,7 @@ static void roll_work(struct work *work)
 
 /* Duplicates any dynamically allocated arrays within the work struct to
  * prevent a copied work struct from freeing ram belonging to another struct */
-void __copy_work(struct work *work, const struct work *base_work)
+static void _copy_work(struct work *work, const struct work *base_work, int noffset)
 {
 	int id = work->id;
 
@@ -4972,6 +4980,15 @@ void __copy_work(struct work *work, const struct work *base_work)
 		++*work->tmpl_refcount;
 		mutex_unlock(&pool->pool_lock);
 	}
+	
+	if (noffset)
+	{
+		uint32_t *work_ntime = (uint32_t *)(work->data + 68);
+		uint32_t ntime = be32toh(*work_ntime);
+
+		ntime += noffset;
+		*work_ntime = htobe32(ntime);
+	}
 }
 
 /* Generates a copy of an existing work struct, creating fresh heap allocations
@@ -4980,11 +4997,16 @@ struct work *copy_work(const struct work *base_work)
 {
 	struct work *work = make_work();
 
-	__copy_work(work, base_work);
+	_copy_work(work, base_work, 0);
 
 	return work;
 }
 
+void __copy_work(struct work *work, const struct work *base_work)
+{
+	_copy_work(work, base_work, 0);
+}
+
 static struct work *make_clone(struct work *work)
 {
 	struct work *work_clone = copy_work(work);
@@ -7340,7 +7362,17 @@ void show_help(void)
 		"ST: work in queue              | F: network fails  | NB: new blocks detected\n"
 		"AS: shares being submitted     | BW: bandwidth (up/down)\n"
 		"E: # shares * diff per 2kB bw  | U: shares/minute  | BS: best share ever found\n"
-		"\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\n"
+		U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE
+		U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE
+		U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE
+		U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_BTEE
+		U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE
+		U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE
+		U8_HLINE U8_HLINE U8_HLINE U8_BTEE  U8_HLINE U8_HLINE U8_HLINE U8_HLINE
+		U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE
+		U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE
+		U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE U8_HLINE
+		"\n"
 		"devices/processors hashing (only for totals line), hottest temperature\n"
 	);
 	wlogprint(
@@ -7582,9 +7614,9 @@ static void hashmeter(int thr_id, struct timeval *diff,
 		}
 		
 		if (working_devs == working_procs)
-			snprintf(statusline, sizeof(statusline), "%s%d        ", bad ? "\2" : "", working_devs);
+			snprintf(statusline, sizeof(statusline), "%s%d        ", bad ? U8_BAD_START : "", working_devs);
 		else
-			snprintf(statusline, sizeof(statusline), "%s%d/%d     ", bad ? "\2" : "", working_devs, working_procs);
+			snprintf(statusline, sizeof(statusline), "%s%d/%d     ", bad ? U8_BAD_START : "", working_devs, working_procs);
 		
 		divx = 7;
 		if (opt_show_procs && !opt_compact)
@@ -7592,9 +7624,9 @@ static void hashmeter(int thr_id, struct timeval *diff,
 		
 		if (bad)
 		{
-			++divx;
-			statusline[divx] = '\1';
-			++divx;
+			divx += sizeof(U8_BAD_START)-1;
+			strcpy(&statusline[divx], U8_BAD_END);
+			divx += sizeof(U8_BAD_END)-1;
 		}
 		
 		temperature_column(&statusline[divx], sizeof(statusline)-divx, true, &temp);
@@ -8623,6 +8655,8 @@ void gen_stratum_work2(struct work *work, struct stratum_work *swork, const char
 	work->id = total_work++;
 	work->longpoll = false;
 	work->getwork_mode = GETWORK_MODE_STRATUM;
+	/* Nominally allow a driver to ntime roll 60 seconds */
+	work->drv_rolllimit = 60;
 	calc_diff(work, 0);
 }
 
@@ -8717,10 +8751,9 @@ void _submit_work_async(struct work *work)
 	notifier_wake(submit_waiting_notifier);
 }
 
-static void submit_work_async(struct work *work_in, struct timeval *tv_work_found)
+/* Submit a copy of the tested, statistic recorded work item asynchronously */
+static void submit_work_async2(struct work *work, struct timeval *tv_work_found)
 {
-	struct work *work = copy_work(work_in);
-
 	if (tv_work_found)
 		copy_time(&work->tv_work_found, tv_work_found);
 	
@@ -8732,8 +8765,14 @@ void inc_hw_errors2(struct thr_info *thr, const struct work *work, const uint32_
 	struct cgpu_info * const cgpu = thr->cgpu;
 	
 	if (bad_nonce_p)
-		applog(LOG_DEBUG, "%"PRIpreprv": invalid nonce (%08lx) - HW error",
-		       cgpu->proc_repr, (unsigned long)be32toh(*bad_nonce_p));
+	{
+		if (bad_nonce_p == UNKNOWN_NONCE)
+			applog(LOG_DEBUG, "%"PRIpreprv": invalid nonce - HW error",
+			       cgpu->proc_repr);
+		else
+			applog(LOG_DEBUG, "%"PRIpreprv": invalid nonce (%08lx) - HW error",
+			       cgpu->proc_repr, (unsigned long)be32toh(*bad_nonce_p));
+	}
 	
 	mutex_lock(&stats_lock);
 	hw_errors++;
@@ -8789,8 +8828,20 @@ enum test_nonce2_result _test_nonce2(struct work *work, uint32_t nonce, bool che
 /* Returns true if nonce for work was a valid share */
 bool submit_nonce(struct thr_info *thr, struct work *work, uint32_t nonce)
 {
+	return submit_noffset_nonce(thr, work, nonce, 0);
+}
+
+/* Allows drivers to submit work items where the driver has changed the ntime
+ * value by noffset. Must be only used with a work protocol that does not ntime
+ * roll itself intrinsically to generate work (eg stratum). We do not touch
+ * the original work struct, but the copy of it only. */
+bool submit_noffset_nonce(struct thr_info *thr, struct work *work_in, uint32_t nonce,
+			  int noffset)
+{
+	struct work *work = make_work();
+	_copy_work(work, work_in, noffset);
+	
 	uint32_t *work_nonce = (uint32_t *)(work->data + 64 + 12);
-	uint32_t bak_nonce = *work_nonce;
 	struct timeval tv_work_found;
 	enum test_nonce2_result res;
 	bool ret = true;
@@ -8831,9 +8882,11 @@ bool submit_nonce(struct thr_info *thr, struct work *work, uint32_t nonce)
 			goto out;
 	}
 	
-	submit_work_async(work, &tv_work_found);
+	submit_work_async2(work, &tv_work_found);
+	work = NULL;  // Taken by submit_work_async2
 out:
-	*work_nonce = bak_nonce;
+	if (work)
+		free_work(work);
 	thread_reportin(thr);
 
 	return ret;
@@ -10988,6 +11041,7 @@ int main(int argc, char *argv[])
 		test_cgpu_match();
 		test_intrange();
 		test_decimal_width();
+		utf8_test();
 	}
 
 #ifdef HAVE_CURSES

+ 4 - 0
miner.h

@@ -1342,6 +1342,7 @@ struct work {
 	uint64_t	share_diff;
 
 	int		rolls;
+	int		drv_rolllimit; /* How much the driver can roll ntime */
 
 	dev_blk_ctx	blk;
 
@@ -1403,6 +1404,7 @@ extern void stratum_work_cpy(struct stratum_work *dst, const struct stratum_work
 extern void stratum_work_clean(struct stratum_work *);
 extern void gen_stratum_work2(struct work *, struct stratum_work *, const char *nonce1);
 extern void inc_hw_errors2(struct thr_info *thr, const struct work *work, const uint32_t *bad_nonce_p);
+#define UNKNOWN_NONCE ((uint32_t*)inc_hw_errors2)
 extern void inc_hw_errors(struct thr_info *, const struct work *, const uint32_t bad_nonce);
 #define inc_hw_errors_only(thr)  inc_hw_errors(thr, NULL, 0)
 enum test_nonce2_result {
@@ -1414,6 +1416,8 @@ extern enum test_nonce2_result _test_nonce2(struct work *, uint32_t nonce, bool
 #define test_nonce(work, nonce, checktarget)  (_test_nonce2(work, nonce, checktarget) == TNR_GOOD)
 #define test_nonce2(work, nonce)  (_test_nonce2(work, nonce, true))
 extern bool submit_nonce(struct thr_info *thr, struct work *work, uint32_t nonce);
+extern bool submit_noffset_nonce(struct thr_info *thr, struct work *work, uint32_t nonce,
+			  int noffset);
 extern void __add_queued(struct cgpu_info *cgpu, struct work *work);
 extern struct work *get_queued(struct cgpu_info *cgpu);
 extern void add_queued(struct cgpu_info *cgpu, struct work *work);

+ 122 - 5
util.c

@@ -727,16 +727,33 @@ badchar:
 	return likely(!hexstr[0]);
 }
 
-void ucs2tochar(char * const out, const uint16_t * const in, const size_t sz)
+size_t ucs2_to_utf8(char * const out, const uint16_t * const in, const size_t sz)
 {
+	uint8_t *p = (void*)out;
 	for (int i = 0; i < sz; ++i)
-		out[i] = in[i];
+	{
+		const uint16_t c = in[i];
+		if (c < 0x80)
+			p++[0] = c;
+		else
+		{
+			if (c < 0x800)
+				p++[0] = 0xc0 | (c >> 6);
+			else
+			{
+				p++[0] = 0xe0 | (c >> 12);
+				p++[0] = 0x80 | ((c >> 6) & 0x3f);
+			}
+			p++[0] = 0x80 | (c & 0x3f);
+		}
+	}
+	return p - (uint8_t*)(void*)out;
 }
 
-char *ucs2tochar_dup(uint16_t * const in, const size_t sz)
+char *ucs2_to_utf8_dup(uint16_t * const in, size_t sz)
 {
-	char *out = malloc(sz + 1);
-	ucs2tochar(out, in, sz);
+	char * const out = malloc((sz * 4) + 1);
+	sz = ucs2_to_utf8(out, in, sz);
 	out[sz] = '\0';
 	return out;
 }
@@ -1385,6 +1402,106 @@ double tdiff(struct timeval *end, struct timeval *start)
 	return end->tv_sec - start->tv_sec + (end->tv_usec - start->tv_usec) / 1000000.0;
 }
 
+
+int32_t utf8_decode(const void *b, int *out_len)
+{
+	int32_t w;
+	const unsigned char *s = b;
+	
+	if (!(s[0] & 0x80))
+	{
+		// ASCII
+		*out_len = 1;
+		return s[0];
+	}
+	
+#ifdef STRICT_UTF8
+	if (unlikely(!(s[0] & 0x40)))
+		goto invalid;
+#endif
+	
+	if (!(s[0] & 0x20))
+		*out_len = 2;
+	else
+	if (!(s[0] & 0x10))
+		*out_len = 3;
+	else
+	if (likely(!(s[0] & 8)))
+		*out_len = 4;
+	else
+		goto invalid;
+	
+	w = s[0] & ((2 << (6 - *out_len)) - 1);
+	for (int i = 1; i < *out_len; ++i)
+	{
+#ifdef STRICT_UTF8
+		if (unlikely((s[i] & 0xc0) != 0x80))
+			goto invalid;
+#endif
+		w = (w << 6) | (s[i] & 0x3f);
+	}
+	
+#if defined(STRICT_UTF8)
+	if (unlikely(w > 0x10FFFF))
+		goto invalid;
+	
+	// FIXME: UTF-8 requires smallest possible encoding; check it
+#endif
+	
+	return w;
+
+invalid:
+	*out_len = 1;
+	return REPLACEMENT_CHAR;
+}
+
+static
+void _utf8_test(const char *s, const wchar_t expected, int expectedlen)
+{
+	int len;
+	wchar_t r;
+	
+	r = utf8_decode(s, &len);
+	if (unlikely(r != expected || expectedlen != len))
+		applog(LOG_ERR, "UTF-8 test U+%06lX (len %d) failed: got U+%06lX (len %d)", (unsigned long)expected, expectedlen, (unsigned long)r, len);
+}
+#define _test_intrange(s, ...)  _test_intrange(s, (int[]){ __VA_ARGS__ })
+
+void utf8_test()
+{
+	_utf8_test("", 0, 1);
+	_utf8_test("\1", 1, 1);
+	_utf8_test("\x7f", 0x7f, 1);
+#if WCHAR_MAX >= 0x80
+	_utf8_test("\xc2\x80", 0x80, 2);
+#if WCHAR_MAX >= 0xff
+	_utf8_test("\xc3\xbf", 0xff, 2);
+#if WCHAR_MAX >= 0x7ff
+	_utf8_test("\xdf\xbf", 0x7ff, 2);
+#if WCHAR_MAX >= 0x800
+	_utf8_test("\xe0\xa0\x80", 0x800, 3);
+#if WCHAR_MAX >= 0xffff
+	_utf8_test("\xef\xbf\xbf", 0xffff, 3);
+#if WCHAR_MAX >= 0x10000
+	_utf8_test("\xf0\x90\x80\x80", 0x10000, 4);
+#if WCHAR_MAX >= 0x10ffff
+	_utf8_test("\xf4\x8f\xbf\xbf", 0x10ffff, 4);
+#endif
+#endif
+#endif
+#endif
+#endif
+#endif
+#endif
+#ifdef STRICT_UTF8
+	_utf8_test("\x80", REPLACEMENT_CHAR, 1);
+	_utf8_test("\xbf", REPLACEMENT_CHAR, 1);
+	_utf8_test("\xfe", REPLACEMENT_CHAR, 1);
+	_utf8_test("\xff", REPLACEMENT_CHAR, 1);
+#endif
+}
+
+
 bool extract_sockaddr(char *url, char **sockaddr_url, char **sockaddr_port)
 {
 	char *url_begin, *url_end, *ipv6_begin, *ipv6_end, *port_start = NULL;

+ 22 - 2
util.h

@@ -113,8 +113,8 @@ extern json_t *json_rpc_call_completed(CURL *, int rc, bool probe, int *rolltime
 
 extern char *absolute_uri(char *uri, const char *ref);  // ref must be a root URI
 
-extern void ucs2tochar(char *out, const uint16_t *in, size_t sz);
-extern char *ucs2tochar_dup(uint16_t *in, size_t sz);
+extern size_t ucs2_to_utf8(char *out, const uint16_t *in, size_t sz);
+extern char *ucs2_to_utf8_dup(uint16_t *in, size_t sz);
 
 #define BFGINIT(var, val)  do{  \
 	if (!(var))       \
@@ -219,6 +219,17 @@ size_t bytes_len(const bytes_t *b)
 	return b->sz;
 }
 
+static inline
+ssize_t bytes_find(const bytes_t * const b, const uint8_t needle)
+{
+	const size_t blen = bytes_len(b);
+	const uint8_t * const buf = bytes_buf(b);
+	for (int i = 0; i < blen; ++i)
+		if (buf[i] == needle)
+			return i;
+	return -1;
+}
+
 extern void _bytes_alloc_failure(size_t);
 
 static inline
@@ -429,6 +440,15 @@ struct timeval *select_timeout(struct timeval *tvp_timeout, struct timeval *tvp_
 #define _SNP(...)  _SNP2(snprintf, __VA_ARGS__)
 
 
+#define REPLACEMENT_CHAR (0xFFFD)
+#define U8_DEGREE "\xc2\xb0"
+#define U8_HLINE  "\xe2\x94\x80"
+#define U8_BTEE   "\xe2\x94\xb4"
+extern int32_t utf8_decode(const void *, int *out_len);
+extern void utf8_test();
+
+
+
 #define RUNONCE(rv)  do {  \
 	static bool _runonce = false;  \
 	if (_runonce)  \