Browse Source

Incomplete (but workable) asynchronous minerloop

- Probably missing a lot of necessary functionality for various FPGAs/ASICs
- Requires very strict (undocumented) driver behaviour
- Cannot be disabled or interrupted for a work restart
- Not fully asynchronous yet
Luke Dashjr 13 years ago
parent
commit
e07f3324f7
3 changed files with 179 additions and 2 deletions
  1. 135 2
      miner.c
  2. 21 0
      miner.h
  3. 23 0
      util.h

+ 135 - 2
miner.c

@@ -6676,7 +6676,7 @@ bool hashes_done(struct thr_info *thr, int64_t hashes, struct timeval *tvp_hashe
 }
 
 // Miner loop to manage a single processor (with possibly multiple threads per processor)
-void minerloop(struct thr_info *mythr)
+void minerloop_scanhash(struct thr_info *mythr)
 {
 	const int thr_id = mythr->id;
 	struct cgpu_info *cgpu = mythr->cgpu;
@@ -6735,6 +6735,139 @@ disabled:
 	}
 }
 
+void minerloop_async(struct thr_info *mythr)
+{
+	const int thr_id = mythr->id;
+	struct cgpu_info *cgpu = mythr->cgpu;
+	const struct device_api *api = cgpu->api;
+	struct timeval tv_start, tv_end;
+	struct timeval tv_hashes, tv_worktime;
+	struct timeval tv_now;
+	struct timeval tv_timeout, *tvp_timeout;
+	uint32_t max_nonce = api->can_limit_work ? api->can_limit_work(mythr) : 0xffffffff;
+	int64_t hashes;
+	struct work *work;
+	const bool primary = (!mythr->device_thread) || mythr->primary_thread;
+	struct cgpu_info *proc;
+	bool starting_new_work;
+	// NOTE: prev_job_work/new_job_work are distinct from mythr->prev_work/next_work (job v work boundary)
+	struct work *prev_job_work, *new_job_work;
+	int proc_thr_no;
+	
+	while (1) {
+		if (unlikely(cgpu->thr[0]->work_restart)) {
+			for (proc = cgpu; proc; proc = proc->next_proc)
+			{
+				for (proc_thr_no = proc->threads ?: 1; proc_thr_no--; )
+				{
+					mythr = proc->thr[proc_thr_no];
+					mythr->work_restart = true;
+					if ((!proc_thr_no) || mythr->primary_thread)
+						timerclear(&mythr->tv_morework);
+					else
+						/* Apart from device_thread 0, we stagger the
+						 * starting of every next thread to try and get
+						 * all devices busy before worrying about
+						 * getting work for their extra threads */
+						timer_set_delay_from_now(&mythr->tv_morework, 250000 * proc_thr_no);
+				}
+			}
+		}
+		tv_timeout.tv_sec = -1;
+		gettimeofday(&tv_now, NULL);
+		for (proc = cgpu; proc; proc = proc->next_proc)
+		{
+			mythr = proc->thr[0];
+			
+			if (timercmp(&mythr->tv_morework, &tv_now, <))
+			{
+				thread_reportin(mythr);
+				prev_job_work = mythr->work;
+				tv_start = mythr->tv_jobstart;
+				if (prev_job_work)
+					timersub(&tv_now, &mythr->work->tv_work_start, &tv_worktime);
+				if ((!prev_job_work) || abandon_work(mythr->work, &tv_worktime, cgpu->max_hashes))
+				{
+					mythr->work_restart = false;
+					request_work(mythr);
+					// FIXME: Allow get_work to return NULL to retry on notification
+					mythr->next_work = get_work(mythr);
+					if (api->prepare_work && !api->prepare_work(mythr, mythr->next_work)) {
+						applog(LOG_ERR, "work prepare failed, exiting "
+							"mining thread %d", thr_id);
+						break;  // FIXME
+					}
+					starting_new_work = true;
+					new_job_work = mythr->next_work;
+				}
+				else
+				{
+					starting_new_work = false;
+					new_job_work = mythr->work;
+				}
+				api->job_prepare(mythr, new_job_work, max_nonce);
+				if (likely(prev_job_work))
+				{
+					hashes = -101;
+					if (api->job_get_results)
+						hashes = api->job_get_results(mythr, prev_job_work);
+				}
+				gettimeofday(&tv_now, NULL);  // NOTE: Can go away when fully async
+				if (starting_new_work)
+					mythr->next_work->tv_work_start = tv_now;
+				mythr->tv_jobstart = tv_now;
+				api->job_start(mythr);
+				if (starting_new_work)
+				{
+					if (mythr->prev_work)
+						free_work(mythr->prev_work);
+					mythr->prev_work = mythr->work;
+					mythr->work = mythr->next_work;
+					mythr->next_work = NULL;
+				}
+				if (likely(prev_job_work))
+				{
+					if (api->job_process_results)
+						hashes = api->job_process_results(mythr, prev_job_work);
+					thread_reportin(mythr);
+					
+					if (hashes != -101)
+					{
+						timersub(&tv_now, &tv_start, &tv_hashes);
+						if (!hashes_done(mythr, hashes, &tv_hashes, api->can_limit_work ? &max_nonce : NULL))
+							break;  // FIXME: Disable the processor
+					}
+				}
+			}
+			
+			if (timercmp(&mythr->tv_poll, &tv_now, <))
+				api->poll(mythr);
+			
+			reduce_timeout_to(&tv_timeout, &mythr->tv_morework);
+			reduce_timeout_to(&tv_timeout, &mythr->tv_poll);
+		}
+		
+		gettimeofday(&tv_now, NULL);  // NOTE: Can go away when fully async
+		if (tv_timeout.tv_sec == -1)
+			tvp_timeout = NULL;
+		else
+		{
+			tvp_timeout = &tv_timeout;
+			if (timercmp(&tv_timeout, &tv_now, <))
+				timerclear(&tv_timeout);
+			else
+				timersub(&tv_timeout, &tv_now, &tv_timeout);
+		}
+		// FIXME: break select on work restart
+		select(0, NULL, NULL, NULL, tvp_timeout);
+#if 0
+			if (unlikely(mythr->pause || cgpu->deven != DEV_ENABLED))
+disabled:
+				mt_disable(mythr, thr_id, api);
+#endif
+	}
+}
+
 void *miner_thread(void *userdata)
 {
 	struct thr_info *mythr = userdata;
@@ -6762,7 +6895,7 @@ void *miner_thread(void *userdata)
 	if (api->minerloop)
 		api->minerloop(mythr);
 	else
-		minerloop(mythr);
+		minerloop_scanhash(mythr);
 
 out:
 	if (api->thread_shutdown)

+ 21 - 0
miner.h

@@ -295,6 +295,17 @@ struct device_api {
 	void (*hw_error)(struct thr_info *);
 	void (*thread_shutdown)(struct thr_info *);
 	void (*thread_enable)(struct thr_info *);
+
+	// === Implemented by minerloop_async ===
+
+	// Can be used per-thread or per-processor
+	void (*poll)(struct thr_info *);
+
+	// Job-specific functions (only with minerloop_async!)
+	bool (*job_prepare)(struct thr_info*, struct work*, uint64_t);
+	void (*job_start)(struct thr_info*);
+	int64_t (*job_get_results)(struct thr_info*, struct work*);
+	int64_t (*job_process_results)(struct thr_info*, struct work*);  // return value ignored if job_get_results is used
 };
 
 enum dev_enable {
@@ -542,6 +553,14 @@ struct thr_info {
 	time_t	getwork;
 	double	rolling;
 
+	// Used by minerloop_async
+	struct work *prev_work;
+	struct work *work;
+	struct work *next_work;
+	struct timeval tv_morework;
+	struct timeval tv_jobstart;
+	struct timeval tv_poll;
+
 	bool	work_restart;
 	int		work_restart_fd;
 	int		_work_restart_fd_w;
@@ -1084,6 +1103,8 @@ enum test_nonce2_result {
 	TNR_HIGH,
 	TNR_BAD,
 };
+extern void minerloop_scanhash(struct thr_info *);
+extern void minerloop_async(struct thr_info *);
 extern void request_work(struct thr_info *);
 extern struct work *get_work(struct thr_info *);
 extern enum test_nonce2_result _test_nonce2(struct work *, uint32_t nonce, bool checktarget);

+ 23 - 0
util.h

@@ -81,4 +81,27 @@ static inline void align_len(size_t *len)
 		*len += 4 - (*len % 4);
 }
 
+
+#define timer_set_delay(tvp_timer, tvp_now, usecs)  do {  \
+	struct timeval tv_add = {  \
+		.tv_sec = usecs / 1000000,  \
+		.tv_usec = usecs % 1000000,  \
+	};  \
+	timeradd(&tv_add, tvp_now, tvp_timer);  \
+} while(0)
+
+#define timer_set_delay_from_now(tvp_timer, usecs)  do {  \
+	struct timeval tv_now;  \
+	gettimeofday(&tv_now, NULL);  \
+	timer_set_delay(tvp_timer, &tv_now, usecs);  \
+} while(0)
+
+static inline
+void reduce_timeout_to(struct timeval *tvp_timeout, struct timeval *tvp_time)
+{
+	if (tvp_timeout->tv_sec == -1 /* no timeout */ || timercmp(tvp_time, tvp_timeout, <))
+		*tvp_timeout = *tvp_time;
+}
+
+
 #endif /* __UTIL_H__ */