Browse Source

Merge branch 'hotplug2' into bfgminer

Luke Dashjr 12 years ago
parent
commit
cce9df8fb6
18 changed files with 488 additions and 185 deletions
  1. 11 0
      README.RPC
  2. 31 0
      api.c
  3. 3 9
      deviceapi.c
  4. 3 0
      driver-avalon.c
  5. 3 0
      driver-bitforce.c
  6. 17 5
      driver-cpu.c
  7. 1 4
      driver-icarus.c
  8. 3 0
      driver-modminer.c
  9. 14 3
      driver-opencl.c
  10. 3 0
      driver-x6500.c
  11. 2 0
      driver-ztex.c
  12. 80 21
      fpgautils.c
  13. 7 1
      fpgautils.h
  14. 10 0
      ft232r.c
  15. 11 0
      libztex.c
  16. 278 142
      miner.c
  17. 3 0
      miner.h
  18. 8 0
      util.h

+ 11 - 0
README.RPC

@@ -145,6 +145,10 @@ The list of requests - a (*) means it requires privileged access - and replies a
                               Will not report PGAs if PGA mining is disabled
                               Will not report CPUs if CPU mining is disabled
 
+ devscan|info  DEVS           Probes for a device specified by info, which is
+                              the same format as the --scan-serial command line
+                              option
+
  devdetail     DEVS           Each available device with their fixed details
                               e.g. GPU=0,Driver=opencl,Kernel=diablo,Model=...|
 
@@ -410,6 +414,13 @@ api-example.py - a Python script to access the API
 Feature Changelog for external applications using the API:
 
 
+API V1.25.1
+
+Added API commands:
+ 'devscan'
+
+----------
+
 API V1.25 (BFGMiner v3.0.1)
 
 Modified API commands:

+ 31 - 0
api.c

@@ -320,6 +320,8 @@ static const char *JSON_PARAMETER = "parameter";
 #define MSG_ZERSUM 96
 #define MSG_ZERNOSUM 97
 
+#define MSG_DEVSCAN 0x100
+
 enum code_severity {
 	SEVERITY_ERR,
 	SEVERITY_WARN,
@@ -329,6 +331,7 @@ enum code_severity {
 };
 
 enum code_parameters {
+	PARAM_COUNT,
 	PARAM_GPU,
 	PARAM_PGA,
 	PARAM_CPU,
@@ -494,6 +497,7 @@ struct CODES {
  { SEVERITY_ERR,   MSG_ZERINV,	PARAM_STR,	"Invalid zero parameter '%s'" },
  { SEVERITY_SUCC,  MSG_ZERSUM,	PARAM_STR,	"Zeroed %s stats with summary" },
  { SEVERITY_SUCC,  MSG_ZERNOSUM, PARAM_STR,	"Zeroed %s stats without summary" },
+ { SEVERITY_SUCC,  MSG_DEVSCAN, PARAM_COUNT,	"Added %d new device(s)" },
  { SEVERITY_FAIL, 0, 0, NULL }
 };
 
@@ -1184,6 +1188,7 @@ static void message(struct io_data *io_data, int messageid, int paramid, char *p
 			severity[1] = '\0';
 
 			switch(codes[i].params) {
+				case PARAM_COUNT:
 				case PARAM_GPU:
 				case PARAM_PGA:
 				case PARAM_CPU:
@@ -1583,6 +1588,31 @@ static void gpudev(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *p
 }
 #endif
 
+static void devscan(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
+{
+	int n;
+	bool io_open = false;
+	
+	applog(LOG_DEBUG, "RPC: request to scan %s for devices",
+	       param);
+	
+	if (param && !param[0])
+		param = NULL;
+	
+	n = scan_serial(param);
+	
+	message(io_data, MSG_DEVSCAN, n, NULL, isjson);
+	
+	io_open = io_add(io_data, isjson ? COMSTR JSON_DEVS : _DEVS COMSTR);
+
+	n = total_devices - n;
+	for (int i = n; i < total_devices; ++i)
+		devdetail_an(io_data, get_devices(i), isjson, i > n);
+	
+	if (isjson && io_open)
+		io_close(io_data);
+}
+
 #ifdef HAVE_AN_FPGA
 static void pgadev(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
@@ -3072,6 +3102,7 @@ struct CMDS {
 } cmds[] = {
 	{ "version",		apiversion,	false },
 	{ "config",		minerconfig,	false },
+	{ "devscan",		devscan,	false },
 	{ "devs",		devstatus,	false },
 	{ "devdetail",	devdetail,	false },
 	{ "pools",		poolstatus,	false },

+ 3 - 9
deviceapi.c

@@ -608,10 +608,8 @@ bool add_cgpu(struct cgpu_info *cgpu)
 	strcpy(cgpu->proc_repr, cgpu->dev_repr);
 	sprintf(cgpu->proc_repr_ns, "%s%u", cgpu->drv->name, cgpu->device_id);
 	
-	wr_lock(&devices_lock);
-	
-	devices = realloc(devices, sizeof(struct cgpu_info *) * (total_devices + lpcount + 1));
-	devices[total_devices++] = cgpu;
+	devices_new = realloc(devices_new, sizeof(struct cgpu_info *) * (total_devices_new + lpcount + 1));
+	devices_new[total_devices_new++] = cgpu;
 	
 	if (lpcount > 1)
 	{
@@ -645,7 +643,7 @@ bool add_cgpu(struct cgpu_info *cgpu)
 				slave->proc_repr_ns[ns] += i;
 			}
 			slave->threads = tpp;
-			devices[total_devices++] = slave;
+			devices_new[total_devices_new++] = slave;
 			*nlp_p = slave;
 			nlp_p = &slave->next_proc;
 		}
@@ -653,12 +651,8 @@ bool add_cgpu(struct cgpu_info *cgpu)
 		cgpu->proc_id = 0;
 		cgpu->threads -= (tpp * (lpcount - 1));
 	}
-	
-	wr_unlock(&devices_lock);
 
-	mutex_lock(&stats_lock);
 	cgpu->last_device_valid_work = time(NULL);
-	mutex_unlock(&stats_lock);
 	
 	return true;
 }

+ 3 - 0
driver-avalon.c

@@ -571,6 +571,9 @@ static bool avalon_detect_one(const char *devpath)
 	int baud, miner_count, asic_count, timeout, frequency = 0;
 	struct cgpu_info *avalon;
 
+	if (serial_claim(devpath, avalon_drv))
+		return false;
+	
 	int this_option_offset = ++option_offset;
 	get_options(this_option_offset, &baud, &miner_count, &asic_count,
 		    &timeout, &frequency);

+ 3 - 0
driver-bitforce.c

@@ -190,6 +190,9 @@ static bool bitforce_detect_one(const char *devpath)
 		return false;
 	}
 
+	if (serial_claim_v(devpath, &bitforce_drv))
+		return false;
+	
 	applog(LOG_DEBUG, "Found BitForce device on %s", devpath);
 	initdata = malloc(sizeof(*initdata));
 	*initdata = (struct bitforce_init_data){

+ 17 - 5
driver-cpu.c

@@ -30,8 +30,10 @@
 #include <libgen.h>
 
 #include "compat.h"
+#include "fpgautils.h"
 #include "miner.h"
 #include "bench_block.h"
+#include "util.h"
 #include "driver-cpu.h"
 
 #if defined(unix)
@@ -709,7 +711,7 @@ char *force_nthreads_int(const char *arg, int *i)
 #endif
 
 #ifdef WANT_CPUMINE
-static void cpu_detect()
+static int cpu_autodetect()
 {
 	int i;
 
@@ -745,13 +747,10 @@ static void cpu_detect()
 	#endif /* !WIN32 */
 
 	if (opt_n_threads < 0 || !forced_n_threads) {
-		if (total_devices && !opt_usecpu)
-			opt_n_threads = 0;
-		else
 			opt_n_threads = num_processors;
 	}
 	if (num_processors < 1)
-		return;
+		return 0;
 
 	cpus = calloc(opt_n_threads, sizeof(struct cgpu_info));
 	if (unlikely(!cpus))
@@ -767,6 +766,19 @@ static void cpu_detect()
 		cgpu->kname = algo_names[opt_algo];
 		add_cgpu(cgpu);
 	}
+	return opt_n_threads;
+}
+
+static void cpu_detect()
+{
+	RUNONCE();
+	
+	if ((opt_n_threads < 0 || !forced_n_threads)
+	 && ((total_devices || total_devices_new) && !opt_usecpu))
+		// If there are any other devices, only act if the user has explicitly enabled it
+		noserial_detect_manual(&cpu_drv, cpu_autodetect);
+	else
+		noserial_detect(&cpu_drv, cpu_autodetect);
 }
 
 static pthread_mutex_t cpualgo_lock;

+ 1 - 4
driver-icarus.c

@@ -595,11 +595,8 @@ bool icarus_detect_custom(const char *devpath, struct device_drv *api, struct IC
 			devpath, nonce_hex);
 	free(nonce_hex);
 
-	if (serial_claim(devpath, api)) {
-		const char *claimedby = serial_claim(devpath, api)->dname;
-		applog(LOG_DEBUG, "Icarus device %s already claimed by other driver: %s", devpath, claimedby);
+	if (serial_claim_v(devpath, api))
 		return false;
-	}
 
 	/* We have a real Icarus! */
 	struct cgpu_info *icarus;

+ 3 - 0
driver-modminer.c

@@ -123,6 +123,9 @@ modminer_detect_one(const char *devpath)
 	char*devname = strdup(buf);
 	applog(LOG_DEBUG, "ModMiner identified as: %s", devname);
 
+	if (serial_claim_v(devpath, &modminer_drv))
+		return false;
+	
 	if (1 != write(fd, MODMINER_FPGA_COUNT, 1))
 		bailout(LOG_DEBUG, "ModMiner detect: write failed on %s (get FPGA count)", devpath);
 	len = read(fd, buf, 1);

+ 14 - 3
driver-opencl.c

@@ -39,6 +39,7 @@
 #define OMIT_OPENCL_API
 
 #include "compat.h"
+#include "fpgautils.h"
 #include "miner.h"
 #include "driver-opencl.h"
 #include "findnonce.h"
@@ -1408,7 +1409,7 @@ void *reinit_gpu(__maybe_unused void *userdata)
 #ifdef HAVE_OPENCL
 struct device_drv opencl_api;
 
-static void opencl_detect()
+static int opencl_autodetect()
 {
 #ifndef WIN32
 	if (!getenv("DISPLAY")) {
@@ -1419,7 +1420,7 @@ static void opencl_detect()
 
 	if (!load_opencl_symbols()) {
 		nDevs = 0;
-		return;
+		return 0;
 	}
 
 
@@ -1432,7 +1433,7 @@ static void opencl_detect()
 	}
 
 	if (!nDevs)
-		return;
+		return 0;
 
 	/* If opt_g_threads is not set, use default 1 thread on scrypt and
 	 * 2 for regular mining */
@@ -1478,6 +1479,16 @@ static void opencl_detect()
 
 	if (!opt_noadl)
 		init_adl(nDevs);
+	
+	return nDevs;
+}
+
+static void opencl_detect()
+{
+	RUNONCE();
+	
+	// This wrapper ensures users can specify -S opencl:noauto to disable it
+	noserial_detect(&opencl_api, opencl_autodetect);
 }
 
 static void reinit_opencl_device(struct cgpu_info *gpu)

+ 3 - 0
driver-x6500.c

@@ -124,6 +124,9 @@ uint32_t x6500_get_register(struct jtag_port *jp, uint8_t addr)
 
 static bool x6500_foundusb(libusb_device *dev, const char *product, const char *serial)
 {
+	if (bfg_claim_libusb(&x6500_api, true, dev))
+		return false;
+	
 	struct cgpu_info *x6500;
 	x6500 = calloc(1, sizeof(*x6500));
 	x6500->drv = &x6500_api;

+ 2 - 0
driver-ztex.c

@@ -85,6 +85,8 @@ static int ztex_autodetect(void)
 
 	for (i = 0; i < cnt; i++) {
 		ztex_master = ztex_devices[i]->dev;
+		if (bfg_claim_usb(&ztex_drv, true, ztex_master->usbbus, ztex_master->usbaddress))
+			return false;
 		ztex_master->root = ztex_master;
 		fpgacount = libztex_numberOfFpgas(ztex_master);
 		ztex_master->handles = fpgacount;

+ 80 - 21
fpgautils.c

@@ -100,6 +100,18 @@ bool search_needles(const char *haystack, va_list needles)
 
 #define SEARCH_NEEDLES(haystack)  search_needles(haystack, needles)
 
+static
+int _detectone_wrap(const detectone_func_t detectone, const char * const param, const char *fname)
+{
+	if (bfg_claim_serial(NULL, false, param))
+	{
+		applog(LOG_DEBUG, "%s: %s is already claimed, skipping probe", fname, param);
+		return 0;
+	}
+	return detectone(param);
+}
+#define detectone(param)  _detectone_wrap(detectone, param, __func__)
+
 #ifdef HAVE_LIBUDEV
 static
 int _serial_autodetect_udev(detectone_func_t detectone, va_list needles)
@@ -331,6 +343,8 @@ out:
 #	define _serial_autodetect_ftdi(...)  (0)
 #endif
 
+#undef detectone
+
 int _serial_autodetect(detectone_func_t detectone, ...)
 {
 	int rv;
@@ -347,13 +361,11 @@ int _serial_autodetect(detectone_func_t detectone, ...)
 	return rv;
 }
 
-struct device_drv *serial_claim(const char *devpath, struct device_drv *api);
-
 int _serial_detect(struct device_drv *api, detectone_func_t detectone, autoscan_func_t autoscan, int flags)
 {
 	struct string_elist *iter, *tmp;
 	const char *dev, *colon;
-	bool inhibitauto = false;
+	bool inhibitauto = flags & 4;
 	char found = 0;
 	bool forceauto = flags & 1;
 	bool hasname;
@@ -404,11 +416,27 @@ int _serial_detect(struct device_drv *api, detectone_func_t detectone, autoscan_
 	return found;
 }
 
+enum bfg_device_bus {
+	BDB_SERIAL,
+	BDB_USB,
+};
+
+// TODO: claim USB side of USB-Serial devices
+typedef
+struct my_dev_t {
+	enum bfg_device_bus bus;
+	union {
+		struct {
+			uint8_t usbbus;
+			uint8_t usbaddr;
+		};
 #ifndef WIN32
-typedef dev_t my_dev_t;
+		dev_t dev;
 #else
-typedef int my_dev_t;
+		int com;
 #endif
+	};
+} my_dev_t;
 
 struct _device_claim {
 	struct device_drv *drv;
@@ -416,42 +444,73 @@ struct _device_claim {
 	UT_hash_handle hh;
 };
 
-struct device_drv *serial_claim(const char *devpath, struct device_drv *api)
+static
+struct device_drv *bfg_claim_any(struct device_drv * const api, const char * const verbose, const my_dev_t * const dev)
 {
 	static struct _device_claim *claims = NULL;
 	struct _device_claim *c;
-	my_dev_t dev;
+	
+	HASH_FIND(hh, claims, dev, sizeof(*dev), c);
+	if (c)
+	{
+		if (verbose)
+			applog(LOG_DEBUG, "%s device %s already claimed by other driver: %s",
+			       api->dname, verbose, c->drv->dname);
+		return c->drv;
+	}
+	
+	if (!api)
+		return NULL;
+	
+	c = malloc(sizeof(*c));
+	c->dev = *dev;
+	c->drv = api;
+	HASH_ADD(hh, claims, dev, sizeof(*dev), c);
+	return NULL;
+}
 
+struct device_drv *bfg_claim_serial(struct device_drv * const api, const bool verbose, const char * const devpath)
+{
+	my_dev_t dev;
+	
+	dev.bus = BDB_SERIAL;
 #ifndef WIN32
 	{
 		struct stat my_stat;
 		if (stat(devpath, &my_stat))
 			return NULL;
-		dev = my_stat.st_rdev;
+		dev.dev = my_stat.st_rdev;
 	}
 #else
 	{
 		char *p = strstr(devpath, "COM"), *p2;
 		if (!p)
 			return NULL;
-		dev = strtol(&p[3], &p2, 10);
+		dev.com = strtol(&p[3], &p2, 10);
 		if (p2 == p)
 			return NULL;
 	}
 #endif
+	
+	return bfg_claim_any(api, (verbose ? devpath : NULL), &dev);
+}
 
-	HASH_FIND(hh, claims, &dev, sizeof(dev), c);
-	if (c)
-		return c->drv;
-
-	if (!api)
-		return NULL;
-
-	c = malloc(sizeof(*c));
-	c->dev = dev;
-	c->drv = api;
-	HASH_ADD(hh, claims, dev, sizeof(dev), c);
-	return NULL;
+struct device_drv *bfg_claim_usb(struct device_drv * const api, const bool verbose, const uint8_t usbbus, const uint8_t usbaddr)
+{
+	const my_dev_t dev = {
+		.bus = BDB_USB,
+		.usbbus = usbbus,
+		.usbaddr = usbaddr,
+	};
+	char *desc = NULL;
+	
+	if (verbose)
+	{
+		desc = alloca(3 + 1 + 3 + 1);
+		sprintf(desc, "%03u:%03u", (unsigned)usbbus, (unsigned)usbaddr);
+	}
+	
+	return bfg_claim_any(api, desc, &dev);
 }
 
 // This code is purely for debugging but is very useful for that

+ 7 - 1
fpgautils.h

@@ -25,10 +25,16 @@ extern int _serial_detect(struct device_drv *api, detectone_func_t, autoscan_fun
 	_serial_detect(api, detectone,     NULL, 2)
 #define noserial_detect(api, autoscan)  \
 	_serial_detect(api, NULL     , autoscan, 0)
+#define noserial_detect_manual(api, autoscan)  \
+	_serial_detect(api, NULL     , autoscan, 4)
 extern int _serial_autodetect(detectone_func_t, ...);
 #define serial_autodetect(...)  _serial_autodetect(__VA_ARGS__, NULL)
 
-extern struct device_drv *serial_claim(const char *devpath, struct device_drv *);
+extern struct device_drv *bfg_claim_serial(struct device_drv * const, const bool verbose, const char * const devpath);
+#define serial_claim(devpath, drv)    bfg_claim_serial(drv, false, devpath)
+#define serial_claim_v(devpath, drv)  bfg_claim_serial(drv, true , devpath)
+extern struct device_drv *bfg_claim_usb(struct device_drv * const, const bool verbose, const uint8_t usbbus, const uint8_t usbaddr);
+#define bfg_claim_libusb(api, verbose, dev)  bfg_claim_usb(api, verbose, libusb_get_bus_number(dev), libusb_get_device_address(dev))
 
 extern int serial_open(const char *devpath, unsigned long baud, uint8_t timeout, bool purge);
 extern ssize_t _serial_read(int fd, char *buf, size_t buflen, char *eol);

+ 10 - 0
ft232r.c

@@ -63,6 +63,7 @@ void ft232r_scan()
 	struct ft232r_device_info *info;
 	int err;
 	unsigned char buf[0x100];
+	int skipped = 0;
 
 	ft232r_scan_free();
 
@@ -76,6 +77,12 @@ void ft232r_scan()
 	ft232r_devinfo_list = malloc(sizeof(struct ft232r_device_info *) * (count + 1));
 
 	for (i = 0; i < count; ++i) {
+		if (bfg_claim_libusb(NULL, false, list[i]))
+		{
+			++skipped;
+			continue;
+		}
+		
 		err = libusb_get_device_descriptor(list[i], &desc);
 		if (unlikely(err)) {
 			applog(LOG_ERR, "ft232r_scan: Error getting device descriptor: %s", bfg_strerror(err, BST_LIBUSB));
@@ -118,6 +125,9 @@ void ft232r_scan()
 
 	ft232r_devinfo_list[found] = NULL;
 	libusb_free_device_list(list, 1);
+	
+	if (skipped)
+		applog(LOG_DEBUG, "%s: Skipping probe of %d claimed devices", __func__, skipped);
 }
 
 int ft232r_detect(const char *product_needle, const char *serial, foundusb_func_t cb)

+ 11 - 0
libztex.c

@@ -738,6 +738,7 @@ int libztex_scanDevices(struct libztex_dev_list*** devs_p)
 	int found, max_found = 0, pos = 0, err, rescan, ret = 0;
 	libusb_device **list = NULL;
 	ssize_t cnt, i;
+	int skipped = 0;
 
 	do {
 		cnt = libusb_get_device_list(NULL, &list);
@@ -747,6 +748,12 @@ int libztex_scanDevices(struct libztex_dev_list*** devs_p)
 		}
 
 		for (found = rescan = i = 0; i < cnt; i++) {
+			if (bfg_claim_libusb(NULL, false, list[i]))
+			{
+				++skipped;
+				continue;
+			}
+			
 			err = libztex_checkDevice(list[i]);
 			switch (err) {
 			case CHECK_ERROR:
@@ -828,6 +835,10 @@ done:
 		free(devs);
 	if (list)
 		libusb_free_device_list(list, 1);
+	
+	if (skipped)
+		applog(LOG_DEBUG, "%s: Skipping probe of %d claimed devices", __func__, skipped);
+	
 	return ret;
 }
 

+ 278 - 142
miner.c

@@ -162,6 +162,8 @@ static bool opt_display_devs;
 static bool opt_removedisabled;
 int total_devices;
 struct cgpu_info **devices;
+int total_devices_new;
+struct cgpu_info **devices_new;
 bool have_opencl;
 int opt_n_threads = -1;
 int mining_threads;
@@ -1067,9 +1069,42 @@ static int temp_strtok(char *base, char **n)
 	return atoi(i);
 }
 
+static void load_temp_config_cgpu(struct cgpu_info *cgpu, char **cutoff_np, char **target_np)
+{
+	int target_off, val;
+	
+	// cutoff default may be specified by driver during probe; otherwise, opt_cutofftemp (const)
+	if (!cgpu->cutofftemp)
+		cgpu->cutofftemp = opt_cutofftemp;
+	
+	// target default may be specified by driver, and is moved with offset; otherwise, offset minus 6
+	if (cgpu->targettemp)
+		target_off = cgpu->targettemp - cgpu->cutofftemp;
+	else
+		target_off = -6;
+	
+	val = temp_strtok(temp_cutoff_str, cutoff_np);
+	if (val < 0 || val > 200)
+		quit(1, "Invalid value passed to set temp cutoff");
+	if (val)
+		cgpu->cutofftemp = val;
+	
+	val = temp_strtok(temp_target_str, target_np);
+	if (val < 0 || val > 200)
+		quit(1, "Invalid value passed to set temp target");
+	if (val)
+		cgpu->targettemp = val;
+	else
+		cgpu->targettemp = cgpu->cutofftemp + target_off;
+	
+	applog(LOG_DEBUG, "%"PRIprepr": Set temperature config: target=%d cutoff=%d",
+	       cgpu->proc_repr,
+	       cgpu->targettemp, cgpu->cutofftemp);
+}
+
 static void load_temp_config()
 {
-	int i, val = 0, target_off;
+	int i;
 	char *cutoff_n, *target_n;
 	struct cgpu_info *cgpu;
 
@@ -1078,34 +1113,7 @@ static void load_temp_config()
 
 	for (i = 0; i < total_devices; ++i) {
 		cgpu = get_devices(i);
-		
-		// cutoff default may be specified by driver during probe; otherwise, opt_cutofftemp (const)
-		if (!cgpu->cutofftemp)
-			cgpu->cutofftemp = opt_cutofftemp;
-		
-		// target default may be specified by driver, and is moved with offset; otherwise, offset minus 6
-		if (cgpu->targettemp)
-			target_off = cgpu->targettemp - cgpu->cutofftemp;
-		else
-			target_off = -6;
-		
-		val = temp_strtok(temp_cutoff_str, &cutoff_n);
-		if (val < 0 || val > 200)
-			quit(1, "Invalid value passed to set temp cutoff");
-		if (val)
-			cgpu->cutofftemp = val;
-		
-		val = temp_strtok(temp_target_str, &target_n);
-		if (val < 0 || val > 200)
-			quit(1, "Invalid value passed to set temp target");
-		if (val)
-			cgpu->targettemp = val;
-		else
-			cgpu->targettemp = cgpu->cutofftemp + target_off;
-		
-		applog(LOG_DEBUG, "%"PRIprepr": Set temperature config: target=%d cutoff=%d",
-		       cgpu->proc_repr,
-		       cgpu->targettemp, cgpu->cutofftemp);
+		load_temp_config_cgpu(cgpu, &cutoff_n, &target_n);
 	}
 	if (cutoff_n != temp_cutoff_str && cutoff_n[0])
 		quit(1, "Too many values passed to set temp cutoff");
@@ -8554,6 +8562,239 @@ extern void setup_pthread_cancel_workaround();
 extern struct sigaction pcwm_orig_term_handler;
 #endif
 
+static
+void drv_detect_all()
+{
+#ifdef USE_X6500
+	if (likely(have_libusb))
+		ft232r_scan();
+#endif
+
+#ifdef HAVE_OPENCL
+	if (!opt_nogpu)
+		opencl_api.drv_detect();
+	gpu_threads = 0;
+#endif
+
+#ifdef USE_ICARUS
+	if (!opt_scrypt)
+	{
+		cairnsmore_drv.drv_detect();
+		icarus_drv.drv_detect();
+	}
+#endif
+
+#ifdef USE_BITFORCE
+	if (!opt_scrypt)
+		bitforce_drv.drv_detect();
+#endif
+
+#ifdef USE_MODMINER
+	if (!opt_scrypt)
+		modminer_drv.drv_detect();
+#endif
+
+#ifdef USE_X6500
+	if (likely(have_libusb) && !opt_scrypt)
+		x6500_api.drv_detect();
+#endif
+
+#ifdef USE_ZTEX
+	if (likely(have_libusb) && !opt_scrypt)
+		ztex_drv.drv_detect();
+#endif
+
+	/* Detect avalon last since it will try to claim the device regardless
+	 * as detection is unreliable. */
+#ifdef USE_AVALON
+	if (!opt_scrypt)
+		avalon_drv.drv_detect();
+#endif
+
+#ifdef WANT_CPUMINE
+	cpu_drv.drv_detect();
+#endif
+
+#ifdef USE_X6500
+	if (likely(have_libusb))
+		ft232r_scan_free();
+#endif
+}
+
+static
+void allocate_cgpu(struct cgpu_info *cgpu, unsigned int *kp)
+{
+	struct thr_info *thr;
+	int j;
+	
+	struct device_drv *api = cgpu->drv;
+	if (!cgpu->devtype)
+		cgpu->devtype = "PGA";
+	cgpu->cgminer_stats.getwork_wait_min.tv_sec = MIN_SEC_UNSET;
+	
+	int threadobj = cgpu->threads;
+	if (!threadobj)
+		// Create a fake thread object to handle hashmeter etc
+		threadobj = 1;
+	cgpu->thr = calloc(threadobj + 1, sizeof(*cgpu->thr));
+	cgpu->thr[threadobj] = NULL;
+	cgpu->status = LIFE_INIT;
+
+	cgpu->max_hashes = 0;
+
+	// Setup thread structs before starting any of the threads, in case they try to interact
+	for (j = 0; j < threadobj; ++j, ++*kp) {
+		thr = get_thread(*kp);
+		thr->id = *kp;
+		thr->cgpu = cgpu;
+		thr->device_thread = j;
+		thr->work_restart_notifier[1] = INVSOCK;
+		thr->mutex_request[1] = INVSOCK;
+		thr->_job_transition_in_progress = true;
+		timerclear(&thr->tv_morework);
+		thr->_last_sbr_state = true;
+
+		thr->scanhash_working = true;
+		thr->hashes_done = 0;
+		timerclear(&thr->tv_hashes_done);
+		cgtime(&thr->tv_lastupdate);
+		thr->tv_poll.tv_sec = -1;
+		thr->_max_nonce = api->can_limit_work ? api->can_limit_work(thr) : 0xffffffff;
+
+		cgpu->thr[j] = thr;
+	}
+	
+	if (!cgpu->threads)
+		memcpy(&cgpu->thr[0]->notifier, &cgpu->device->thr[0]->notifier, sizeof(cgpu->thr[0]->notifier));
+	else
+	for (j = 0; j < cgpu->threads; ++j)
+	{
+		thr = cgpu->thr[j];
+		notifier_init(thr->notifier);
+	}
+}
+
+static
+void start_cgpu(struct cgpu_info *cgpu)
+{
+	struct thr_info *thr;
+	int j;
+	
+	for (j = 0; j < cgpu->threads; ++j) {
+		thr = cgpu->thr[j];
+
+		/* Enable threads for devices set not to mine but disable
+		 * their queue in case we wish to enable them later */
+		if (cgpu->drv->thread_prepare && !cgpu->drv->thread_prepare(thr))
+			continue;
+
+		thread_reportout(thr);
+
+		if (unlikely(thr_info_create(thr, NULL, miner_thread, thr)))
+			quit(1, "thread %d create failed", thr->id);
+	}
+	if (cgpu->deven == DEV_ENABLED)
+		proc_enable(cgpu);
+}
+
+int scan_serial(const char *s)
+{
+	static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+	struct string_elist *orig_scan_devices;
+	int devcount, i, mining_threads_new = 0;
+	unsigned int k;
+	struct string_elist *iter, *tmp;
+	struct cgpu_info *cgpu;
+	struct thr_info *thr;
+	void *p;
+	char *dummy = "\0";
+	
+	mutex_lock(&mutex);
+	orig_scan_devices = scan_devices;
+	devcount = total_devices;
+	
+	if (s)
+	{
+		// Make temporary scan_devices list
+		scan_devices = NULL;
+		string_elist_add("noauto", &scan_devices);
+		string_elist_add(s, &scan_devices);
+	}
+	
+	drv_detect_all();
+	
+	wr_lock(&devices_lock);
+	p = realloc(devices, sizeof(struct cgpu_info *) * (total_devices + total_devices_new + 1));
+	if (unlikely(!p))
+	{
+		wr_unlock(&devices_lock);
+		applog(LOG_ERR, "scan_serial: realloc failed trying to grow devices array");
+		goto out;
+	}
+	devices = p;
+	wr_unlock(&devices_lock);
+	
+	for (i = 0; i < total_devices_new; ++i)
+	{
+		cgpu = devices_new[i];
+		mining_threads_new += cgpu->threads ?: 1;
+	}
+	
+	wr_lock(&mining_thr_lock);
+	mining_threads_new += mining_threads;
+	p = realloc(mining_thr, sizeof(struct thr_info *) * mining_threads_new);
+	if (unlikely(!p))
+	{
+		wr_unlock(&mining_thr_lock);
+		applog(LOG_ERR, "scan_serial: realloc failed trying to grow mining_thr");
+		goto out;
+	}
+	mining_thr = p;
+	wr_unlock(&mining_thr_lock);
+	for (i = mining_threads; i < mining_threads_new; ++i) {
+		mining_thr[i] = calloc(1, sizeof(*thr));
+		if (!mining_thr[i])
+		{
+			applog(LOG_ERR, "scan_serial: Failed to calloc mining_thr[%d]", i);
+			for ( ; --i >= mining_threads; )
+				free(mining_thr[i]);
+			goto out;
+		}
+	}
+	
+	k = mining_threads;
+	for (i = 0; i < total_devices_new; ++i)
+	{
+		cgpu = devices_new[i];
+		load_temp_config_cgpu(cgpu, &dummy, &dummy);
+		allocate_cgpu(cgpu, &k);
+		start_cgpu(cgpu);
+		register_device(cgpu);
+		++total_devices;
+	}
+	
+#ifdef HAVE_CURSES
+	switch_logsize();
+#endif
+	
+out:
+	if (s)
+	{
+		DL_FOREACH_SAFE(scan_devices, iter, tmp)
+		{
+			string_elist_del(&scan_devices, iter);
+		}
+		scan_devices = orig_scan_devices;
+	}
+	
+	total_devices_new = 0;
+	
+	devcount = total_devices - devcount;
+	mutex_unlock(&mutex);
+	
+	return devcount;
+}
+
 static void probe_pools(void)
 {
 	int i;
@@ -8600,7 +8841,7 @@ int main(int argc, char *argv[])
 	struct thr_info *thr;
 	struct block *block;
 	unsigned int k;
-	int i, j;
+	int i;
 	char *s;
 
 #ifdef WIN32
@@ -8749,11 +8990,6 @@ int main(int argc, char *argv[])
 		successful_connect = true;
 	}
 
-#ifdef USE_X6500
-	if (likely(have_libusb))
-		ft232r_scan();
-#endif
-
 #ifdef HAVE_CURSES
 	if (opt_realquiet || opt_display_devs)
 		use_curses = false;
@@ -8832,59 +9068,11 @@ int main(int argc, char *argv[])
 	}
 #endif
 
-#ifdef HAVE_OPENCL
-	if (!opt_nogpu)
-		opencl_api.drv_detect();
-	gpu_threads = 0;
-#endif
-
-#ifdef USE_ICARUS
-	if (!opt_scrypt)
-	{
-		cairnsmore_drv.drv_detect();
-		icarus_drv.drv_detect();
-	}
-#endif
-
-#ifdef USE_BITFORCE
-	if (!opt_scrypt)
-		bitforce_drv.drv_detect();
-#endif
-
-#ifdef USE_MODMINER
-	if (!opt_scrypt)
-		modminer_drv.drv_detect();
-#endif
-
-#ifdef USE_X6500
-	if (likely(have_libusb) && !opt_scrypt)
-		x6500_api.drv_detect();
-#endif
-
-#ifdef USE_ZTEX
-	if (likely(have_libusb) && !opt_scrypt)
-		ztex_drv.drv_detect();
-#endif
-
-	/* Detect avalon last since it will try to claim the device regardless
-	 * as detection is unreliable. */
-#ifdef USE_AVALON
-	if (!opt_scrypt)
-		avalon_drv.drv_detect();
-#endif
-
-#ifdef WANT_CPUMINE
-	cpu_drv.drv_detect();
-#endif
-
-#ifdef USE_X6500
-	if (likely(have_libusb))
-		ft232r_scan_free();
-#endif
-
-	for (i = 0; i < total_devices; ++i)
-		if (!devices[i]->devtype)
-			devices[i]->devtype = "PGA";
+	drv_detect_all();
+	total_devices = total_devices_new;
+	devices = devices_new;
+	total_devices_new = 0;
+	devices_new = NULL;
 
 	if (opt_display_devs) {
 		applog(LOG_ERR, "Devices detected:");
@@ -8933,9 +9121,6 @@ int main(int argc, char *argv[])
 
 	load_temp_config();
 
-	for (i = 0; i < total_devices; ++i)
-		devices[i]->cgminer_stats.getwork_wait_min.tv_sec = MIN_SEC_UNSET;
-
 #ifdef HAVE_CURSES
 	switch_logsize();
 #endif
@@ -9091,62 +9276,13 @@ begin_bench:
 	k = 0;
 	for (i = 0; i < total_devices; ++i) {
 		struct cgpu_info *cgpu = devices[i];
-		struct device_drv *api = cgpu->drv;
-		int threadobj = cgpu->threads;
-		if (!threadobj)
-			// Create a fake thread object to handle hashmeter etc
-			threadobj = 1;
-		cgpu->thr = calloc(threadobj + 1, sizeof(*cgpu->thr));
-		cgpu->thr[threadobj] = NULL;
-		cgpu->status = LIFE_INIT;
-
-		cgpu->max_hashes = 0;
-
-		// Setup thread structs before starting any of the threads, in case they try to interact
-		for (j = 0; j < threadobj; ++j, ++k) {
-			thr = get_thread(k);
-			thr->id = k;
-			thr->cgpu = cgpu;
-			thr->device_thread = j;
-			thr->work_restart_notifier[1] = INVSOCK;
-			thr->mutex_request[1] = INVSOCK;
-			thr->_job_transition_in_progress = true;
-			timerclear(&thr->tv_morework);
-			thr->_last_sbr_state = true;
-
-			thr->scanhash_working = true;
-			thr->hashes_done = 0;
-			timerclear(&thr->tv_hashes_done);
-			cgtime(&thr->tv_lastupdate);
-			thr->tv_poll.tv_sec = -1;
-			thr->_max_nonce = api->can_limit_work ? api->can_limit_work(thr) : 0xffffffff;
-
-			cgpu->thr[j] = thr;
-		}
+		allocate_cgpu(cgpu, &k);
 	}
 
 	// Start threads
 	for (i = 0; i < total_devices; ++i) {
 		struct cgpu_info *cgpu = devices[i];
-		if (!cgpu->threads)
-			memcpy(&cgpu->thr[0]->notifier, &cgpu->device->thr[0]->notifier, sizeof(cgpu->thr[0]->notifier));
-		for (j = 0; j < cgpu->threads; ++j) {
-			thr = cgpu->thr[j];
-
-			notifier_init(thr->notifier);
-
-			/* Enable threads for devices set not to mine but disable
-			 * their queue in case we wish to enable them later */
-			if (cgpu->drv->thread_prepare && !cgpu->drv->thread_prepare(thr))
-				continue;
-
-			thread_reportout(thr);
-
-			if (unlikely(thr_info_create(thr, NULL, miner_thread, thr)))
-				quit(1, "thread %d create failed", thr->id);
-		}
-		if (cgpu->deven == DEV_ENABLED)
-			proc_enable(cgpu);
+		start_cgpu(cgpu);
 	}
 
 #ifdef HAVE_OPENCL

+ 3 - 0
miner.h

@@ -970,6 +970,8 @@ extern int mining_threads;
 extern struct cgpu_info *cpus;
 extern int total_devices;
 extern struct cgpu_info **devices;
+extern int total_devices_new;
+extern struct cgpu_info **devices_new;
 extern int total_pools;
 extern struct pool **pools;
 extern const char *algo_names[];
@@ -1296,6 +1298,7 @@ extern void __copy_work(struct work *work, const struct work *base_work);
 extern struct work *copy_work(const struct work *base_work);
 extern struct thr_info *get_thread(int thr_id);
 extern struct cgpu_info *get_devices(int id);
+extern int scan_serial(const char *);
 
 enum api_data_type {
 	API_ESCAPE,

+ 8 - 0
util.h

@@ -189,4 +189,12 @@ struct timeval *select_timeout(struct timeval *tvp_timeout, struct timeval *tvp_
 }
 
 
+#define RUNONCE(rv)  do {  \
+	static bool _runonce = false;  \
+	if (_runonce)  \
+		return rv;  \
+	_runonce = true;  \
+} while(0)
+
+
 #endif /* __UTIL_H__ */