1 /*
2  * TilEm II
3  *
4  * Copyright (c) 2011 Benjamin Moody
5  *
6  * This program is free software: you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation, either version 3 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23 
24 #include <stdio.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <gtk/gtk.h>
28 #include <ticalcs.h>
29 #include <tilem.h>
30 #include <scancodes.h>
31 
32 #include "gui.h"
33 #include "emucore.h"
34 
35 #define MICROSEC_PER_TICK 10000
36 
37 typedef struct {
38 	TilemCalcEmulator *emu;
39 	TilemTaskMainFunc mainf;
40 	TilemTaskFinishedFunc finishedf;
41 	gpointer userdata;
42 	gboolean cancelled;
43 } Task;
44 
45 /* Add a task to the queue. */
tilem_calc_emulator_begin(TilemCalcEmulator * emu,TilemTaskMainFunc mainf,TilemTaskFinishedFunc finishedf,gpointer data)46 void tilem_calc_emulator_begin(TilemCalcEmulator *emu,
47                                TilemTaskMainFunc mainf,
48                                TilemTaskFinishedFunc finishedf,
49                                gpointer data)
50 {
51 	Task *task;
52 
53 	g_return_if_fail(emu != NULL);
54 	g_return_if_fail(mainf != NULL);
55 
56 	task = g_slice_new0(Task);
57 	task->emu = emu;
58 	task->mainf = mainf;
59 	task->finishedf = finishedf;
60 	task->userdata = data;
61 
62 	tilem_calc_emulator_lock(emu);
63 	g_queue_push_tail(emu->task_queue, task);
64 	tilem_calc_emulator_unlock(emu);
65 }
66 
67 /* Cancel all pending tasks.  If a task is currently running, this
68    will wait for it to finish. */
tilem_calc_emulator_cancel_tasks(TilemCalcEmulator * emu)69 void tilem_calc_emulator_cancel_tasks(TilemCalcEmulator *emu)
70 {
71 	GQueue *oldqueue;
72 	Task *task;
73 
74 	tilem_calc_emulator_lock(emu);
75 	emu->task_abort = TRUE;
76 	emu->link_update->cancel = TRUE;
77 	oldqueue = emu->task_queue;
78 	emu->task_queue = g_queue_new();
79 	tilem_calc_emulator_unlock(emu);
80 
81 	while ((task = g_queue_pop_head(oldqueue))) {
82 		if (task->finishedf)
83 			(*task->finishedf)(emu, task->userdata, TRUE);
84 		g_slice_free(Task, task);
85 	}
86 
87 	g_queue_free(oldqueue);
88 
89 	g_mutex_lock(emu->calc_mutex);
90 	while (emu->task_busy)
91 		g_cond_wait(emu->task_finished_cond, emu->calc_mutex);
92 	emu->task_abort = FALSE;
93 	emu->link_update->cancel = FALSE;
94 	g_cond_broadcast(emu->calc_wakeup_cond);
95 	g_mutex_unlock(emu->calc_mutex);
96 }
97 
98 /* Check if calculator is powered off */
calc_asleep(TilemCalcEmulator * emu)99 static gboolean calc_asleep(TilemCalcEmulator *emu)
100 {
101 	return (emu->calc->z80.halted
102 	        && !emu->calc->z80.interrupts
103 	        && !emu->calc->poweronhalt
104 	        && !emu->key_queue_timer);
105 }
106 
refresh_lcd(gpointer data)107 static gboolean refresh_lcd(gpointer data)
108 {
109 	TilemCalcEmulator* emu = data;
110 
111 	if (emu->ewin)
112 		tilem_emulator_window_refresh_lcd(emu->ewin);
113 
114 	return FALSE;
115 }
116 
117 /* Update screen for display while paused */
update_screen_mono(TilemCalcEmulator * emu)118 static void update_screen_mono(TilemCalcEmulator *emu)
119 {
120 	g_mutex_lock(emu->lcd_mutex);
121 
122 	tilem_lcd_get_frame(emu->calc, emu->lcd_buffer);
123 
124 	if (!emu->lcd_update_pending) {
125 		emu->lcd_update_pending = TRUE;
126 		g_idle_add_full(G_PRIORITY_DEFAULT, &refresh_lcd, emu, NULL);
127 	}
128 
129 	g_mutex_unlock(emu->lcd_mutex);
130 }
131 
132 /* idle callback to update progress bar */
pbar_update(gpointer data)133 static gboolean pbar_update(gpointer data)
134 {
135 	TilemCalcEmulator *emu = data;
136 	progress_bar_update(emu);
137 	return FALSE;
138 }
139 
update_progress(TilemCalcEmulator * emu,gboolean force)140 static void update_progress(TilemCalcEmulator *emu, gboolean force)
141 {
142 	if (force || emu->progress_changed)
143 		g_idle_add(&pbar_update, emu);
144 	emu->progress_changed = FALSE;
145 }
146 
show_debugger(gpointer data)147 static gboolean show_debugger(gpointer data)
148 {
149 	TilemCalcEmulator* emu = data;
150 
151 	if (emu->dbg)
152 		tilem_debugger_show(emu->dbg);
153 
154 	return FALSE;
155 }
156 
157 #define BREAK_MASK (TILEM_STOP_BREAKPOINT \
158                     | TILEM_STOP_INVALID_INST \
159                     | TILEM_STOP_UNDOCUMENTED_INST)
160 
161 /* Run one iteration of the emulator. */
tilem_em_run(TilemCalcEmulator * emu,int linkmode,dword events,dword ff_events,gboolean keep_awake,int timeout,int * elapsed)162 dword tilem_em_run(TilemCalcEmulator *emu, int linkmode,
163                    dword events, dword ff_events, gboolean keep_awake,
164                    int timeout, int *elapsed)
165 {
166 	dword all_events, ev_auto, ev_user;
167 	int rem;
168 	gulong tcur, delaytime;
169 
170 	if (emu->exiting || emu->task_abort) {
171 		if (elapsed) *elapsed = 0;
172 		return 0;
173 	}
174 	else if (emu->paused) {
175 		update_screen_mono(emu);
176 		update_progress(emu, TRUE);
177 		g_cond_wait(emu->calc_wakeup_cond, emu->calc_mutex);
178 		update_progress(emu, TRUE);
179 		g_timer_elapsed(emu->timer, &emu->timevalue);
180 		if (elapsed) *elapsed = 0;
181 		return 0;
182 	}
183 	else if (!keep_awake && calc_asleep(emu)) {
184 		update_progress(emu, FALSE);
185 		update_screen_mono(emu);
186 		g_cond_wait(emu->calc_wakeup_cond, emu->calc_mutex);
187 		g_timer_elapsed(emu->timer, &emu->timevalue);
188 		if (elapsed) *elapsed = timeout;
189 		return 0;
190 	}
191 
192 	update_progress(emu, FALSE);
193 
194 	all_events = events | BREAK_MASK;
195 
196 	emu->calc->linkport.linkemu = linkmode;
197 	emu->calc->z80.stop_mask = ~all_events;
198 
199 	tilem_z80_run_time(emu->calc, timeout, &rem);
200 
201 	ev_user = emu->calc->z80.stop_reason & events;
202 	ev_auto = emu->calc->z80.stop_reason & ~events;
203 
204 	if (elapsed) *elapsed = timeout - rem;
205 
206 	if (ev_auto & BREAK_MASK) {
207 		emu->paused = TRUE;
208 		g_idle_add(&show_debugger, emu);
209 	}
210 
211 	if (emu->limit_speed
212 	    && !(ff_events & ev_user)
213 	    && ff_events != TILEM_EM_ALWAYS_FF) {
214 		emu->timevalue += timeout - rem;
215 		g_timer_elapsed(emu->timer, &tcur);
216 
217 		/* emu->timevalue is the "ideal" time when the
218 		   operation should be completed.  If emu->timevalue
219 		   is greater than tcur, we're running faster than
220 		   real time.  Try to sleep for (emu->timevalue -
221 		   tcur) microseconds.
222 
223 		   If emu->timevalue is less than tcur, we're running
224 		   slower than real time.  If the difference is small,
225 		   just keep going and hope we'll catch up later.
226 
227 		   If the difference is substantial (more than 1/10
228 		   second in either direction), re-synchronize. */
229 
230 		delaytime = emu->timevalue - tcur;
231 
232 		if (delaytime <= (gulong) 100000 + timeout) {
233 			tilem_em_unlock(emu);
234 			g_usleep(delaytime);
235 			tilem_em_lock(emu);
236 		}
237 		else {
238 			tilem_em_check_yield(emu);
239 			if (delaytime < (gulong) -100000)
240 				emu->timevalue = tcur;
241 		}
242 	}
243 	else {
244 		tilem_em_check_yield(emu);
245 	}
246 
247 	return ev_user;
248 }
249 
taskfinished(gpointer data)250 static gboolean taskfinished(gpointer data)
251 {
252 	Task *task = data;
253 
254 	if (task->finishedf)
255 		(*task->finishedf)(task->emu, task->userdata, task->cancelled);
256 
257 	g_slice_free(Task, task);
258 	return FALSE;
259 }
260 
run_task(TilemCalcEmulator * emu,Task * task)261 static void run_task(TilemCalcEmulator *emu, Task *task)
262 {
263 	gboolean status;
264 
265 	emu->task_busy = TRUE;
266 	status = (*task->mainf)(emu, task->userdata);
267 
268 	g_idle_add(&taskfinished, task);
269 
270 	if (!status) {
271 		while ((task = g_queue_pop_head(emu->task_queue))) {
272 			task->cancelled = TRUE;
273 			g_idle_add(&taskfinished, task);
274 		}
275 	}
276 	emu->task_busy = FALSE;
277 }
278 
279 /* Main loop */
tilem_em_main(gpointer data)280 gpointer tilem_em_main(gpointer data)
281 {
282 	TilemCalcEmulator *emu = data;
283 	Task *task;
284 
285 	tilem_em_lock(emu);
286 
287 	g_timer_start(emu->timer);
288 	g_timer_elapsed(emu->timer, &emu->timevalue);
289 
290 	while (!emu->exiting) {
291 		task = g_queue_pop_head(emu->task_queue);
292 		if (task) {
293 			run_task(emu, task);
294 		}
295 		else if (emu->task_abort) {
296 			g_cond_broadcast(emu->task_finished_cond);
297 			g_cond_wait(emu->calc_wakeup_cond, emu->calc_mutex);
298 		}
299 		else {
300 			tilem_em_run(emu, 0, 0, 0, FALSE,
301 			             MICROSEC_PER_TICK, NULL);
302 		}
303 	}
304 
305 	tilem_em_unlock(emu);
306 	return NULL;
307 }
308 
309 /* Run the calculator for a short time. */
tilem_em_delay(TilemCalcEmulator * emu,int timeout,gboolean ff)310 void tilem_em_delay(TilemCalcEmulator *emu, int timeout, gboolean ff)
311 {
312 	int t;
313 	G_GNUC_UNUSED dword events;
314 
315 	while (!emu->task_abort && timeout > 0) {
316 		t = MIN(MICROSEC_PER_TICK, timeout);
317 		events = tilem_em_run(emu, 0, 0,
318 		                      (ff ? TILEM_EM_ALWAYS_FF : 0), TRUE,
319 		                      t, &t);
320 		timeout -= t;
321 	}
322 }
323 
324 #define LINK_EVENTS (TILEM_STOP_LINK_READ_BYTE \
325                      | TILEM_STOP_LINK_WRITE_BYTE \
326                      | TILEM_STOP_LINK_ERROR)
327 
run_until_ready(TilemCalcEmulator * emu,int timeout,gboolean ff)328 static int run_until_ready(TilemCalcEmulator *emu, int timeout, gboolean ff)
329 {
330 	int t;
331 	dword events;
332 
333 	emu->calc->linkport.linkemu = TILEM_LINK_EMULATOR_GRAY;
334 	while (!emu->task_abort && timeout > 0) {
335 		if (tilem_linkport_graylink_ready(emu->calc))
336 			return 0;
337 
338 		t = MIN(MICROSEC_PER_TICK, timeout);
339 		events = tilem_em_run(emu, TILEM_LINK_EMULATOR_GRAY,
340 		                      LINK_EVENTS, (ff ? LINK_EVENTS : 0), TRUE,
341 		                      t, &t);
342 
343 		timeout -= t;
344 		if (events & TILEM_STOP_LINK_ERROR)
345 			break;
346 	}
347 	return -1;
348 }
349 
350 /* Send a byte to the calculator. */
tilem_em_send_byte(TilemCalcEmulator * emu,unsigned value,int timeout,gboolean ff)351 int tilem_em_send_byte(TilemCalcEmulator *emu, unsigned value,
352                        int timeout, gboolean ff)
353 {
354 	if (run_until_ready(emu, timeout, ff))
355 		return -1;
356 	if (tilem_linkport_graylink_send_byte(emu->calc, value))
357 		return -1;
358 	if (run_until_ready(emu, timeout, ff))
359 		return -1;
360 	return 0;
361 }
362 
363 /* Receive a byte from the calculator. */
tilem_em_get_byte(TilemCalcEmulator * emu,int timeout,gboolean ff)364 int tilem_em_get_byte(TilemCalcEmulator *emu, int timeout, gboolean ff)
365 {
366 	int t, v;
367 	dword events;
368 
369 	while (!emu->task_abort && timeout > 0) {
370 		v = tilem_linkport_graylink_get_byte(emu->calc);
371 		if (v >= 0)
372 			return v;
373 
374 		t = MIN(MICROSEC_PER_TICK, timeout);
375 		events = tilem_em_run(emu, TILEM_LINK_EMULATOR_GRAY,
376 		                      LINK_EVENTS, (ff ? LINK_EVENTS : 0), FALSE,
377 		                      t, &t);
378 		timeout -= t;
379 		if (events & TILEM_STOP_LINK_ERROR)
380 			break;
381 	}
382 	return -1;
383 }
384 
385 /* Wake up calculator if currently turned off. */
tilem_em_wake_up(TilemCalcEmulator * emu,gboolean ff)386 void tilem_em_wake_up(TilemCalcEmulator *emu, gboolean ff)
387 {
388 	tilem_em_delay(emu, 1000000, ff);
389 
390 	if (!calc_asleep(emu))
391 		return;
392 
393 	tilem_keypad_press_key(emu->calc, TILEM_KEY_ON);
394 	tilem_em_delay(emu, 500000, ff);
395 	tilem_keypad_release_key(emu->calc, TILEM_KEY_ON);
396 	tilem_em_delay(emu, 500000, ff);
397 }
398 
399 /* Set progress window title.  Set TITLE to NULL to disable progress
400    window. */
tilem_em_set_progress_title(TilemCalcEmulator * emu,const char * title)401 void tilem_em_set_progress_title(TilemCalcEmulator *emu, const char *title)
402 {
403 	g_mutex_lock(emu->pbar_mutex);
404 	g_free(emu->pbar_title);
405 	g_free(emu->pbar_status);
406 	emu->pbar_title = title ? g_strdup(title) : NULL;
407 	emu->pbar_status = NULL;
408 	emu->pbar_progress = 0.0;
409 	if (!emu->pbar_update_pending)
410 		emu->progress_changed = TRUE;
411 	emu->pbar_update_pending = TRUE;
412 	g_mutex_unlock(emu->pbar_mutex);
413 }
414 
415 /* Set current progress information.  FRAC is the estimated fraction
416    of the task completed; STATUS is a text description of the current
417    operation. */
tilem_em_set_progress(TilemCalcEmulator * emu,gdouble frac,const char * status)418 void tilem_em_set_progress(TilemCalcEmulator *emu, gdouble frac,
419                            const char *status)
420 {
421 	g_mutex_lock(emu->pbar_mutex);
422 
423 	if (!emu->pbar_status || !status
424 	    || strcmp(status, emu->pbar_status)) {
425 		g_free(emu->pbar_status);
426 		emu->pbar_status = status ? g_strdup(status) : NULL;
427 	}
428 
429 	emu->pbar_progress = frac;
430 
431 	if (!emu->pbar_update_pending)
432 		emu->progress_changed = TRUE;
433 	emu->pbar_update_pending = TRUE;
434 
435 	g_mutex_unlock(emu->pbar_mutex);
436 }
437