1 /*
2 * TilEm II
3 *
4 * Copyright (c) 2011-2012 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 <gtk/gtk.h>
27 #include <ticalcs.h>
28 #include <tilem.h>
29
30 #include "gui.h"
31 #include "memmodel.h"
32 #include "charmap.h"
33
34 /* GTK+ requires us to supply several property values for every byte,
35 and we might have hundreds of bytes that need to be refreshed each
36 time the window is repainted. To avoid locking and unlocking the
37 calc for every call to tilem_mem_model_get_value(), we can retrieve
38 information for an entire block of memory and keep it in cache.
39
40 For each address, we cache the current byte value (8 bits), whether
41 or not it is editable (1 bit), and its physical address (up to 22
42 bits for current calculator models), so everything fits in a
43 guint32. For future models, this scheme might need to be
44 modified. */
45
46 #define CACHE_BLOCK_SIZE 256
47 #define CACHE_NUM_BLOCKS 16
48
49 typedef struct {
50 dword address;
51 guint32 *info;
52 } MemModelCacheBlock;
53
54 /* Check if a given physical address is editable (i.e., located in RAM
55 or in a non-protected Flash sector) */
address_editable(TilemCalc * calc,dword a)56 static gboolean address_editable(TilemCalc *calc, dword a)
57 {
58 int start, end, i;
59
60 if (a >= calc->hw.romsize)
61 /* address is in RAM */
62 return TRUE;
63
64 if (!(calc->hw.flags & TILEM_CALC_HAS_FLASH))
65 /* calc does not use Flash */
66 return FALSE;
67
68 /* address is in Flash -> check if sector is protected */
69 start = 0;
70 end = calc->hw.nflashsectors;
71 while (start < end) {
72 i = (start + end) / 2;
73 if (a < calc->hw.flashsectors[i].start)
74 end = i;
75 else if (a >= (calc->hw.flashsectors[i].start
76 + calc->hw.flashsectors[i].size))
77 start = i + 1;
78 else
79 return !(calc->hw.flashsectors[i].protectgroup
80 & ~calc->flash.overridegroup);
81 }
82
83 g_return_val_if_reached(FALSE);
84 }
85
86 /* Copy calc memory contents into cache. */
fill_cache_block(TilemMemModel * mm,MemModelCacheBlock * cb)87 static void fill_cache_block(TilemMemModel *mm, MemModelCacheBlock *cb)
88 {
89 TilemCalc *calc;
90 dword i, addr, phys;
91 byte value, editable;
92
93 g_return_if_fail(mm->emu != NULL);
94
95 tilem_calc_emulator_lock(mm->emu);
96 calc = mm->emu->calc;
97 if (!calc) {
98 tilem_calc_emulator_unlock(mm->emu);
99 return;
100 }
101
102 for (i = 0; i < CACHE_BLOCK_SIZE; i++) {
103 addr = (cb->address + i) % mm->wrap_addr;
104
105 if (mm->use_logical)
106 phys = (*calc->hw.mem_ltop)(calc, addr);
107 else
108 phys = addr;
109
110 editable = address_editable(calc, phys);
111 value = calc->mem[phys];
112
113 cb->info[i] = (value
114 | (editable << 8)
115 | (phys << 9));
116 }
117
118 tilem_calc_emulator_unlock(mm->emu);
119 }
120
121 /* Retrieve info for given address. */
get_mem_info(TilemMemModel * mm,dword addr)122 static guint32 get_mem_info(TilemMemModel *mm, dword addr)
123 {
124 GList *l;
125 MemModelCacheBlock *cb;
126 dword start, index;
127
128 start = addr & ~(CACHE_BLOCK_SIZE - 1);
129 index = addr & (CACHE_BLOCK_SIZE - 1);
130
131 for (l = mm->cache->head; l; l = l->next) {
132 cb = l->data;
133 if (cb->address == start) {
134 if (l->prev) {
135 /* Move this cache block to the start
136 of the list */
137 g_queue_unlink(mm->cache, l);
138 g_queue_push_head_link(mm->cache, l);
139 }
140
141 return cb->info[index];
142 }
143 }
144
145 /* Data not found in cache; drop the least recently used block
146 and retrieve the requested block from the calc */
147 l = g_queue_pop_tail_link(mm->cache);
148 g_queue_push_head_link(mm->cache, l);
149 cb = l->data;
150 cb->address = start;
151 fill_cache_block(mm, cb);
152 return cb->info[index];
153 }
154
155 /* Get address's byte value. */
get_value(TilemMemModel * mm,dword addr)156 static byte get_value(TilemMemModel *mm, dword addr)
157 {
158 return (get_mem_info(mm, addr) & 0xff);
159 }
160
161 /* Get address's editability. */
get_editable(TilemMemModel * mm,dword addr)162 static gboolean get_editable(TilemMemModel *mm, dword addr)
163 {
164 return ((get_mem_info(mm, addr) >> 8) & 1);
165 }
166
167 /* Get address's corresponding physical address. */
get_phys_addr(TilemMemModel * mm,dword addr)168 static dword get_phys_addr(TilemMemModel *mm, dword addr)
169 {
170 return (get_mem_info(mm, addr) >> 9);
171 }
172
173 /* Clear cache. This function should be called any time something
174 happens that might affect memory contents. */
tilem_mem_model_clear_cache(TilemMemModel * mm)175 void tilem_mem_model_clear_cache(TilemMemModel *mm)
176 {
177 GList *l;
178 MemModelCacheBlock *cb;
179
180 g_return_if_fail(TILEM_IS_MEM_MODEL(mm));
181
182 for (l = mm->cache->head; l; l = l->next) {
183 cb = l->data;
184 cb->address = (dword) -1;
185 }
186 }
187
188 /* Get flags for the model */
189 static GtkTreeModelFlags
tilem_mem_model_get_flags(G_GNUC_UNUSED GtkTreeModel * model)190 tilem_mem_model_get_flags(G_GNUC_UNUSED GtkTreeModel *model)
191 {
192 return (GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST);
193 }
194
195 /* Get the number of columns */
196 static int
tilem_mem_model_get_n_columns(GtkTreeModel * model)197 tilem_mem_model_get_n_columns(GtkTreeModel *model)
198 {
199 TilemMemModel *mm;
200 g_return_val_if_fail(TILEM_IS_MEM_MODEL(model), 0);
201 mm = TILEM_MEM_MODEL(model);
202 return (MM_COLUMNS_PER_BYTE * mm->row_size);
203 }
204
205 /* Get type of data for the given column. Currently all columns are
206 strings. */
207 static GType
tilem_mem_model_get_column_type(G_GNUC_UNUSED GtkTreeModel * model,int index)208 tilem_mem_model_get_column_type(G_GNUC_UNUSED GtkTreeModel *model,
209 int index)
210 {
211 index %= MM_COLUMNS_PER_BYTE;
212
213 switch (index) {
214 case MM_COL_ADDRESS_0:
215 case MM_COL_HEX_0:
216 case MM_COL_CHAR_0:
217 return G_TYPE_STRING;
218
219 case MM_COL_BYTE_PTR_0:
220 return G_TYPE_POINTER;
221
222 case MM_COL_EDITABLE_0:
223 return G_TYPE_BOOLEAN;
224
225 default:
226 g_return_val_if_reached(G_TYPE_INVALID);
227 }
228 }
229
230 /* Get an iterator pointing to the nth row */
get_nth_iter(GtkTreeModel * model,GtkTreeIter * iter,int n)231 static gboolean get_nth_iter(GtkTreeModel *model, GtkTreeIter *iter, int n)
232 {
233 TilemMemModel *mm;
234
235 g_return_val_if_fail(TILEM_IS_MEM_MODEL(model), FALSE);
236 mm = TILEM_MEM_MODEL(model);
237
238 if (n >= mm->num_rows)
239 return FALSE;
240
241 iter->stamp = mm->stamp;
242 iter->user_data = GINT_TO_POINTER(n);
243 iter->user_data2 = NULL;
244 iter->user_data3 = NULL;
245 return TRUE;
246 }
247
248 /* Get row number for the given iterator */
get_row_number(GtkTreeModel * model,GtkTreeIter * iter)249 static int get_row_number(GtkTreeModel *model, GtkTreeIter *iter)
250 {
251 TilemMemModel *mm;
252 int n;
253
254 g_return_val_if_fail(TILEM_IS_MEM_MODEL(model), 0);
255 mm = TILEM_MEM_MODEL(model);
256 g_return_val_if_fail(iter != NULL, 0);
257 g_return_val_if_fail(iter->stamp == mm->stamp, 0);
258 n = GPOINTER_TO_INT(iter->user_data);
259 g_return_val_if_fail(n < mm->num_rows, 0);
260 return n;
261 }
262
263 /* Get iterator for a given path */
tilem_mem_model_get_iter(GtkTreeModel * model,GtkTreeIter * iter,GtkTreePath * path)264 static gboolean tilem_mem_model_get_iter(GtkTreeModel *model,
265 GtkTreeIter *iter,
266 GtkTreePath *path)
267 {
268 int *indices;
269
270 if (gtk_tree_path_get_depth(path) != 1)
271 return FALSE;
272
273 indices = gtk_tree_path_get_indices(path);
274 return get_nth_iter(model, iter, indices[0]);
275 }
276
277 /* Get path for an iterator */
tilem_mem_model_get_path(GtkTreeModel * model,GtkTreeIter * iter)278 static GtkTreePath * tilem_mem_model_get_path(GtkTreeModel *model,
279 GtkTreeIter *iter)
280 {
281 int n;
282 n = get_row_number(model, iter);
283 return gtk_tree_path_new_from_indices(n, -1);
284 }
285
286 /* Get next (sibling) iterator */
tilem_mem_model_iter_next(GtkTreeModel * model,GtkTreeIter * iter)287 static gboolean tilem_mem_model_iter_next(GtkTreeModel *model,
288 GtkTreeIter *iter)
289 {
290 int n;
291 n = get_row_number(model, iter);
292 return get_nth_iter(model, iter, n + 1);
293 }
294
295 /* Check if iterator has a child */
296 static gboolean
tilem_mem_model_iter_has_child(G_GNUC_UNUSED GtkTreeModel * model,G_GNUC_UNUSED GtkTreeIter * iter)297 tilem_mem_model_iter_has_child(G_GNUC_UNUSED GtkTreeModel *model,
298 G_GNUC_UNUSED GtkTreeIter *iter)
299 {
300 return FALSE;
301 }
302
303 /* Get number of children (iter = NULL means get number of root
304 nodes) */
tilem_mem_model_iter_n_children(GtkTreeModel * model,GtkTreeIter * iter)305 static gint tilem_mem_model_iter_n_children(GtkTreeModel *model,
306 GtkTreeIter *iter)
307 {
308 TilemMemModel *mm;
309
310 g_return_val_if_fail(TILEM_IS_MEM_MODEL(model), 0);
311 mm = TILEM_MEM_MODEL(model);
312
313 if (iter)
314 return 0;
315 else
316 return (mm->num_rows);
317 }
318
319 /* Get nth child (parent = NULL means get nth root node */
tilem_mem_model_iter_nth_child(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * parent,gint n)320 static gboolean tilem_mem_model_iter_nth_child( GtkTreeModel *model,
321 GtkTreeIter *iter,
322 GtkTreeIter *parent,
323 gint n)
324 {
325 G_GNUC_UNUSED TilemMemModel* mm;
326
327 g_return_val_if_fail(TILEM_IS_MEM_MODEL(model), FALSE);
328 mm = TILEM_MEM_MODEL(model);
329
330 if (parent)
331 return FALSE;
332 else
333 return get_nth_iter(model, iter, n);
334 }
335
336 /* Get first child */
tilem_mem_model_iter_children(GtkTreeModel * model,GtkTreeIter * iter,GtkTreeIter * parent)337 static gboolean tilem_mem_model_iter_children(GtkTreeModel *model,
338 GtkTreeIter *iter,
339 GtkTreeIter *parent)
340 {
341 return tilem_mem_model_iter_nth_child(model, iter, parent, 0);
342 }
343
344 /* Get parent */
tilem_mem_model_iter_parent(G_GNUC_UNUSED GtkTreeModel * model,G_GNUC_UNUSED GtkTreeIter * iter,G_GNUC_UNUSED GtkTreeIter * child)345 static gboolean tilem_mem_model_iter_parent(G_GNUC_UNUSED GtkTreeModel *model,
346 G_GNUC_UNUSED GtkTreeIter *iter,
347 G_GNUC_UNUSED GtkTreeIter *child)
348 {
349 return FALSE;
350 }
351
352 /* Retrieve value for a given column */
tilem_mem_model_get_value(GtkTreeModel * model,GtkTreeIter * iter,gint column,GValue * value)353 static void tilem_mem_model_get_value(GtkTreeModel *model,
354 GtkTreeIter *iter,
355 gint column,
356 GValue *value)
357 {
358 TilemMemModel *mm;
359 dword n, addr, phys;
360 TilemCalc *calc;
361 char buf[100], *s;
362
363 g_return_if_fail(TILEM_IS_MEM_MODEL(model));
364 mm = TILEM_MEM_MODEL(model);
365
366 g_return_if_fail(mm->emu != NULL);
367 g_return_if_fail(mm->emu->calc != NULL);
368
369 n = get_row_number(model, iter);
370
371 calc = mm->emu->calc;
372
373 addr = (mm->start_addr
374 + n * mm->row_size
375 + column / MM_COLUMNS_PER_BYTE) % mm->wrap_addr;
376
377 column %= MM_COLUMNS_PER_BYTE;
378
379 switch (column) {
380 case MM_COL_ADDRESS_0:
381 s = tilem_format_addr(mm->emu->dbg, addr, !mm->use_logical);
382 g_value_init(value, G_TYPE_STRING);
383 g_value_set_string(value, s);
384 g_free(s);
385 break;
386
387 case MM_COL_HEX_0:
388 g_snprintf(buf, sizeof(buf), "%02X", get_value(mm, addr));
389 g_value_init(value, G_TYPE_STRING);
390 g_value_set_string(value, buf);
391 break;
392
393 case MM_COL_CHAR_0:
394 s = ti_to_unicode(calc->hw.model_id, get_value(mm, addr));
395 g_value_init(value, G_TYPE_STRING);
396 g_value_set_string(value, s);
397 g_free(s);
398 break;
399
400 case MM_COL_BYTE_PTR_0:
401 phys = get_phys_addr(mm, addr);
402 g_value_init(value, G_TYPE_POINTER);
403 g_value_set_pointer(value, &calc->mem[phys]);
404 break;
405
406 case MM_COL_EDITABLE_0:
407 g_value_init(value, G_TYPE_BOOLEAN);
408 g_value_set_boolean(value, get_editable(mm, addr));
409 break;
410 }
411 }
412
tilem_mem_model_init(TilemMemModel * mm)413 static void tilem_mem_model_init(TilemMemModel *mm)
414 {
415 int i;
416 MemModelCacheBlock *cb;
417
418 mm->stamp = g_random_int();
419 mm->row_size = 1;
420
421 mm->cache = g_queue_new();
422 for (i = 0; i < CACHE_NUM_BLOCKS; i++) {
423 cb = g_slice_new(MemModelCacheBlock);
424 cb->address = (dword) -1;
425 cb->info = g_new(guint32, CACHE_BLOCK_SIZE);
426 g_queue_push_head(mm->cache, cb);
427 }
428 }
429
tilem_mem_model_finalize(GObject * obj)430 static void tilem_mem_model_finalize(GObject *obj)
431 {
432 TilemMemModel *mm;
433 MemModelCacheBlock *cb;
434
435 g_return_if_fail(TILEM_IS_MEM_MODEL(obj));
436 mm = TILEM_MEM_MODEL(obj);
437
438 while ((cb = g_queue_pop_head(mm->cache))) {
439 g_free(cb->info);
440 g_slice_free(MemModelCacheBlock, cb);
441 }
442 }
443
tilem_mem_model_class_init(TilemMemModelClass * klass)444 static void tilem_mem_model_class_init(TilemMemModelClass *klass)
445 {
446 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
447
448 obj_class->finalize = &tilem_mem_model_finalize;
449 }
450
tilem_mem_tree_model_init(GtkTreeModelIface * iface)451 static void tilem_mem_tree_model_init(GtkTreeModelIface *iface)
452 {
453 iface->get_flags = &tilem_mem_model_get_flags;
454 iface->get_n_columns = &tilem_mem_model_get_n_columns;
455 iface->get_column_type = &tilem_mem_model_get_column_type;
456 iface->get_iter = &tilem_mem_model_get_iter;
457 iface->get_path = &tilem_mem_model_get_path;
458 iface->get_value = &tilem_mem_model_get_value;
459 iface->iter_next = &tilem_mem_model_iter_next;
460 iface->iter_children = &tilem_mem_model_iter_children;
461 iface->iter_has_child = &tilem_mem_model_iter_has_child;
462 iface->iter_n_children = &tilem_mem_model_iter_n_children;
463 iface->iter_nth_child = &tilem_mem_model_iter_nth_child;
464 iface->iter_parent = &tilem_mem_model_iter_parent;
465 }
466
tilem_mem_model_get_type(void)467 GType tilem_mem_model_get_type(void)
468 {
469 static GType type = 0;
470
471 static const GTypeInfo type_info = {
472 sizeof(TilemMemModelClass),
473 NULL,
474 NULL,
475 (GClassInitFunc) tilem_mem_model_class_init,
476 NULL,
477 NULL,
478 sizeof(TilemMemModel),
479 0,
480 (GInstanceInitFunc) tilem_mem_model_init,
481 NULL
482 };
483
484 static const GInterfaceInfo tree_model_info = {
485 (GInterfaceInitFunc) tilem_mem_tree_model_init,
486 NULL,
487 NULL
488 };
489
490 if (!type) {
491 type = g_type_register_static(G_TYPE_OBJECT, "TilemMemModel",
492 &type_info, 0);
493 g_type_add_interface_static(type, GTK_TYPE_TREE_MODEL,
494 &tree_model_info);
495 }
496
497 return type;
498 }
499
tilem_mem_model_new(TilemCalcEmulator * emu,int rowsize,dword start,gboolean logical)500 GtkTreeModel * tilem_mem_model_new(TilemCalcEmulator* emu,
501 int rowsize, dword start,
502 gboolean logical)
503 {
504 TilemMemModel* mm;
505
506 g_return_val_if_fail(emu != NULL, NULL);
507 g_return_val_if_fail(emu->calc != NULL, NULL);
508 g_return_val_if_fail(rowsize > 0, NULL);
509
510 mm = g_object_new(TILEM_TYPE_MEM_MODEL, NULL);
511
512 mm->emu = emu;
513 mm->row_size = rowsize;
514 mm->start_addr = start;
515
516 if (logical) {
517 mm->use_logical = TRUE;
518 mm->wrap_addr = 0x10000;
519 }
520 else {
521 mm->use_logical = FALSE;
522 mm->wrap_addr = (emu->calc->hw.romsize
523 + emu->calc->hw.ramsize);
524 }
525
526 mm->num_rows = (mm->wrap_addr + rowsize - 1) / rowsize;
527
528 return GTK_TREE_MODEL(mm);
529 }
530