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