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