keyboard now processes keys via rfb. framework in place for multiple modules.
[dcpu16] / hw_lem1802.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <errno.h>
5
6 #ifdef HAVE_LIBPNG
7 #include <setjmp.h>
8 #include <png.h>
9 #endif /* HAVE_LIBPNG */
10
11 #ifdef HAVE_LIBVNCSERVER
12 #include <rfb/rfb.h>
13 #include <rfb/keysym.h>
14 #endif /* HAVE_LIBVNCSERVER */
15
16 #include "dcpu16.h"
17 #include "chargen-4x8.h"
18 #include "hw_lem1802.h"
19
20 #ifdef DEBUG
21 #define TRACE(...) do { printf("[debug] "); printf(__VA_ARGS__); printf("\n"); } while (0)
22 #else /* DEBUG */
23 #define TRACE(...) do {} while (0)
24 #endif /* DEBUG */
25
26 static dcpu16_hw_signal_t lem1802_reset_;
27 static dcpu16_hw_signal_t lem1802_cycle_;
28 static dcpu16_hw_signal_t lem1802_hwi_;
29 static struct dcpu16_hw hw_ = {
30 .name_ = "LEM1802 - Low Energy Monitor",
31 .id_l = 0xf615,
32 .id_h = 0x7349,
33 .ver = 0x1802,
34 .mfg_l = 0x8b36,
35 .mfg_h = 0x1c6c,
36 .hwi = lem1802_hwi_,
37 .cycle = lem1802_cycle_,
38 .reset = lem1802_reset_,
39 .data = (struct lem1802_ *)NULL
40 };
41
42 #define PIX_X 160 /* pixels in display */
43 #define PIX_Y 128 /* pixels in display */
44 #define PIX_BORDER 16 /* border pixels from edge to first tile */
45 #define CELL_X_SZ 4
46 #define CELL_Y_SZ 8
47 #define CELL_X ((PIX_X - (2 * PIX_BORDER)) / CELL_X_SZ) /* tiles in display */
48 #define CELL_Y ((PIX_Y - (2 * PIX_BORDER)) / CELL_Y_SZ) /* tiles in display */
49
50 #define PALETTE_ENTRIES 16
51 static const DCPU16_WORD palette_default_[PALETTE_ENTRIES] = {
52 0x0000, /* black */
53 0x000a, /* blue */
54 0x00a0, /* green */
55 0x00aa, /* cyan */
56 0x0a05, /* red */
57 0x0a0f, /* magenta */
58 0x0aa5, /* yellow */
59 0x0aaf, /* pale blue */
60 0x0555, /* grey */
61 0x055f, /* light blue */
62 0x05f5, /* light green*/
63 0x05ff, /* light cyan */
64 0x0f55, /* light red */
65 0x0f5f, /* light magenta */
66 0x0ff5, /* light yellow */
67 0x0fff /* white */
68 };
69
70 struct pixel_ {
71 unsigned char r;
72 unsigned char g;
73 unsigned char b;
74 unsigned char a;
75 };
76
77 struct lem1802_ {
78 long long cycle_activated; /* for tracking 'turn on delay' */
79
80 DCPU16_WORD video_base;
81 DCPU16_WORD font_base;
82 DCPU16_WORD palette_base;
83 DCPU16_WORD border_color;
84 struct pixel_ *pixbuf;
85
86 unsigned int refresh_rate; /* redraw every n cycles */
87 unsigned int refresh_tally_; /* tick */
88
89 unsigned int blink_rate; /* toggle every n cycles? still figuring this out.. */
90 unsigned int blink_tally_; /* tick */
91 unsigned int blink_state;
92
93 int (*render)(void *, struct pixel_ *, size_t, size_t);
94 void *renderer_data;
95 };
96
97 static inline
98 void pixel_color_(struct pixel_ *pix, DCPU16_WORD color) {
99 unsigned char x;
100
101 x = (color >> 0) & 0x000f;
102 pix->r = x | (x << 4);
103
104 x = (color >> 4) & 0x000f;
105 pix->g = x | (x << 4);
106
107 x = (color >> 8) & 0x000f;
108 pix->b = x | (x << 4);
109
110 x = (color >> 12) & 0x000f;
111 pix->a = x | (x << 4);
112 }
113
114 static
115 void pixbuf_border_paint_(struct pixel_ *pixbuf, struct pixel_ *border) {
116 size_t x, y, i;
117
118 TRACE("%s>> painted border", __func__);
119
120 /* top */
121 for (y = 0; y < PIX_BORDER; y++) {
122 for (x = 0; x < PIX_X; x++) {
123 i = (y * PIX_X) + x;
124 pixbuf[i] = *border;
125 i = ((PIX_Y - y) * PIX_X) + x;
126 pixbuf[i] = *border;
127 }
128 }
129
130 /* sides */
131 for (y = PIX_BORDER; y < (PIX_Y - PIX_BORDER + 1); y++)
132 for (x = 0; x < PIX_BORDER; x++) {
133 i = (y * PIX_X) + x;
134 pixbuf[i] = *border;
135 pixbuf[i + (PIX_X - PIX_BORDER)] = *border;
136 }
137 }
138
139 static
140 void font_tile_paint_(struct pixel_ *p, struct pixel_ *fg, struct pixel_ *bg, DCPU16_WORD *tile) {
141 size_t pix_x, pix_y;
142 unsigned char *font_bitmap = (unsigned char *)tile;
143
144 #if 0
145 TRACE("%s>> fg:(%u,%u,%u) bg:(%u,%u,%u) font_bitmap:%02x %02x %02x %02x", __func__,
146 fg->r, fg->g, fg->b,
147 bg->r, bg->g, bg->b,
148 font_bitmap[0], font_bitmap[1], font_bitmap[2], font_bitmap[3]);
149 #endif
150
151 for (pix_x = 0; pix_x < CELL_X_SZ; pix_x++) {
152 for (pix_y = 0; pix_y < CELL_Y_SZ; pix_y++) {
153 if ((font_bitmap[pix_x] >> pix_y) & 0x01)
154 p[((CELL_Y_SZ - pix_y - 1) * PIX_X) + pix_x] = *fg;
155 else
156 p[((CELL_Y_SZ - pix_y - 1) * PIX_X) + pix_x] = *bg;
157 }
158 }
159 }
160
161 static
162 void pixbuf_addr_paint_(struct pixel_ *pixbuf, DCPU16_WORD *mem, DCPU16_WORD base, DCPU16_WORD addr, DCPU16_WORD *palette, DCPU16_WORD *tiles, unsigned int blink_state) {
163 struct pixel_ *tilestart = pixbuf; /* start of display */
164 unsigned int cell_x = addr % (PIX_X / CELL_X_SZ),
165 cell_y = addr / (PIX_X / CELL_X_SZ);
166 struct pixel_ fg, bg;
167 DCPU16_WORD *font_bitmap;
168 int blink;
169
170 cell_x = (addr - base) % CELL_X;
171 cell_y = (addr - base) / CELL_X;
172
173 #if 0
174 TRACE("%s>> addr:0x%04x col:%u row:%u v:%hu",
175 __func__,
176 addr,
177 cell_x, cell_y, mem[addr]);
178 #endif
179
180 blink = mem[addr] & 0x0080;
181
182 /* tiles take two words each */
183 font_bitmap = tiles + (2 * (mem[addr] & 0x7f));
184
185 pixel_color_(&bg, palette[(mem[addr] >> 8) & 0x0f]);
186 if (blink && blink_state)
187 pixel_color_(&fg, palette[(mem[addr] >> 8) & 0x0f]);
188 else
189 pixel_color_(&fg, palette[(mem[addr] >> 12) & 0x0f]);
190
191 tilestart += (PIX_X * PIX_BORDER); /* skip top border */
192 tilestart += (CELL_Y_SZ * PIX_X) * cell_y; /* skip down to row */
193
194 tilestart += PIX_BORDER; /* skip side border */
195 tilestart += (CELL_X_SZ) * cell_x; /* skip to column */
196
197 font_tile_paint_(tilestart, &fg, &bg, font_bitmap);
198 }
199
200 static
201 void lem1802_pixbuf_refresh_full_(struct lem1802_ *display, DCPU16_WORD *mem) {
202 struct pixel_ border;
203 size_t tile;
204
205 #if 0
206 TRACE("%s>> video_base:0x%04x", __func__, display->video_base);
207 #endif
208
209 if (display->video_base == 0) {
210 /* disconnected, blank display. static might be fun, too */
211 memset(display->pixbuf, 0, PIX_X * PIX_Y * sizeof *display->pixbuf);
212 return;
213 }
214
215 pixel_color_(&border, display->border_color);
216 pixbuf_border_paint_(display->pixbuf, &border);
217
218 for (tile = 0; tile < CELL_X * CELL_Y; tile++) {
219 pixbuf_addr_paint_(display->pixbuf,
220 mem,
221 display->video_base,
222 display->video_base + tile,
223 display->palette_base ? mem + display->palette_base : palette_default_,
224 display->font_base ? mem + display->font_base : (unsigned short *)chargen_4x8_glyphs,
225 display->blink_state);
226 }
227 }
228
229 static
230 int pixbuf_render_pnm_(void *data, struct pixel_ *pixbuf, size_t x, size_t y) {
231 FILE *f = (FILE *)data;
232 size_t i;
233 fprintf(f, "P6\n"
234 "%lu %lu\n"
235 "255\n",
236 x, y);
237 for (i = 0; i < x * y; i++) {
238 fwrite(&pixbuf[i].r, 1, 1, f);
239 fwrite(&pixbuf[i].g, 1, 1, f);
240 fwrite(&pixbuf[i].b, 1, 1, f);
241 }
242 fclose(f);
243
244 return 0;
245 }
246
247 #ifdef HAVE_LIBPNG
248 static
249 int pixbuf_render_png_(void *data, struct pixel_ *pixbuf, size_t x, size_t y) {
250 FILE *f = (FILE *)data;
251 int retval = 0;
252 png_structp png;
253 png_infop info;
254 size_t i;
255
256 png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
257 if (png == NULL) {
258 goto f_done;
259 }
260
261 info = png_create_info_struct(png);
262 if (info == NULL) {
263 png_destroy_write_struct(&png, (png_infopp)NULL);
264 goto f_done;
265 }
266
267 if (setjmp(png_jmpbuf(png))) {
268 png_destroy_write_struct(&png, &info);
269 goto f_done;
270 }
271
272 png_init_io(png, f);
273 png_set_IHDR(png, info, x, y, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
274 png_set_invert_alpha(png);
275 png_write_info(png, info);
276 for (i = 0; i < y; i++) {
277 png_write_row(png, (unsigned char *)(pixbuf + (i * x)));
278 }
279 png_write_end(png, info);
280
281 png_destroy_write_struct(&png, &info);
282
283 f_done:
284 fclose(f);
285
286 return retval;
287 }
288 #endif /* HAVE_LIBPNG */
289
290 #ifdef HAVE_LIBVNCSERVER
291 /* create and return a new screen, easiest to do here because we know the screen dimensions */
292 rfbScreenInfoPtr lem1802_rfb_new(int argc, char *argv[]) {
293 rfbScreenInfoPtr s;
294 int paddedWidth = PIX_X + ( (PIX_X & 3) ? (4 - (PIX_X & 3)) : 0 );
295 int height = PIX_Y;
296 int bitsPerSample = 8;
297 int samplesPerPixel = 3;
298 int bytesPerPixel = 4;
299
300 s = rfbGetScreen(&argc, argv, paddedWidth, height, bitsPerSample, samplesPerPixel, bytesPerPixel);
301 if (s == NULL)
302 return NULL;
303
304 s->alwaysShared = TRUE;
305
306 #if 0
307 s->httpDir = "../classes";
308 #endif
309
310 TRACE("%s>> s:%p", __func__, s);
311 TRACE("%s>> s->kbdAddEvent:%p s->frameBuffer:%p", __func__, s->kbdAddEvent, s->frameBuffer);
312
313 return s;
314 }
315
316 /* set up a new screen to see our pixels */
317 void lem1802_vnc_associate(struct dcpu16_hw *hw, rfbScreenInfoPtr s) {
318 struct lem1802_ *display = (struct lem1802_ *)hw->data;
319
320 s->desktopName = "NYA ELEKTRISKA LEM1802";
321 s->frameBuffer = (char *)display->pixbuf;
322
323 TRACE("%s>> s:%p\n", __func__, s);
324 }
325
326 /* notify rfb server that pixels may have changed */
327 static
328 int pixbuf_render_vnc_(void *data, struct pixel_ *pixbuf, size_t x, size_t y) {
329 rfbScreenInfoPtr s = (rfbScreenInfoPtr)data;
330 int retval = 0;
331
332 (void)pixbuf;
333
334 /* derp */
335 rfbMarkRectAsModified(s, 0, 0, x, y);
336
337 TRACE("%s>>", __func__);
338
339 return retval;
340 }
341 #endif /* HAVE_LIBVNCSERVER */
342
343
344 static
345 void lem1802_reset_(struct dcpu16 *vm, void *data) {
346 struct lem1802_ *display = (struct lem1802_ *)data;
347
348 (void)vm;
349
350 display->cycle_activated = 0;
351 display->video_base = 0;
352 display->font_base = 0;
353 display->palette_base = 0;
354 display->border_color = 0;
355
356 memset(display->pixbuf, 0, PIX_X * PIX_Y * sizeof *display->pixbuf);
357
358 display->refresh_tally_ = 0;
359 display->blink_tally_ = 0;
360 display->blink_state = 0;
361
362 #if DEBUG
363 vm->trace_cb_("%s>>", __func__);
364 #endif /* DEBUG */
365 }
366
367 static
368 void lem1802_cycle_(struct dcpu16 *vm, void *data) {
369 struct lem1802_ *display = (struct lem1802_ *)data;
370
371 (void)vm;
372 /*
373 maybe just step through video memory (if set)
374 one word per clock..? could just cheat and
375 use accounting callbacks..
376
377 for now just count cycles and issue a full refresh/render
378 every so often
379 */
380
381 display->blink_tally_++;
382 if (display->blink_tally_ >= display->blink_rate) {
383 display->blink_tally_ = 0;
384 display->blink_state ^= 1;
385 TRACE("%s>> blink:%u (%u cycles)", __func__, display->blink_state, display->blink_rate);
386 }
387
388 display->refresh_tally_++;
389 if (display->refresh_tally_ >= display->refresh_rate) {
390 display->refresh_tally_ = 0;
391 if (display->render)
392 TRACE("%s>> refresh", __func__);
393 lem1802_pixbuf_refresh_full_(display, vm->ram);
394 display->render(display->renderer_data, display->pixbuf, PIX_X, PIX_Y);
395 }
396 }
397
398 static
399 void lem1802_hwi_(struct dcpu16 *vm, void *data) {
400 struct lem1802_ *display = (struct lem1802_ *)data;
401 DCPU16_WORD reg_a = vm->reg[DCPU16_REG_A];
402 DCPU16_WORD reg_b = vm->reg[DCPU16_REG_B];
403 size_t i;
404
405 TRACE("%s>> A:0x%04x B:0x%04x", __func__, reg_a, reg_b);
406
407 switch (reg_a) {
408 case 0: /* MEM_MAP_SCREEN */
409 if (display->cycle_activated == 0 && reg_b) {
410 display->cycle_activated = vm->cycle;
411 }
412 display->video_base = reg_b;
413 if (reg_b == 0)
414 display->cycle_activated = 0;
415 break;
416
417 case 1: /* MEM_MAP_FONT */
418 display->font_base = reg_b;
419 break;
420
421 case 2: /* MEM_MAP_PALETTE */
422 display->palette_base = reg_b;
423 break;
424
425 case 3: /* SET_BORDER_COLOR */
426 display->border_color = reg_b & 0x000f;
427 break;
428
429 case 4: /* MEM_DUMP_FONT */
430 for (i = 0; i < 128 ; i++) {
431 vm->ram[reg_b] = chargen_4x8_glyphs[reg_b][0] << 8;
432 vm->ram[reg_b] |= chargen_4x8_glyphs[reg_b][1];
433 reg_b += 1;
434 vm->ram[reg_b] = chargen_4x8_glyphs[reg_b][2] << 8;
435 vm->ram[reg_b] |= chargen_4x8_glyphs[reg_b][3];
436 reg_b += 1;
437 }
438 vm->cycle += 256;
439 break;
440
441 case 5: /* MEM_DUMP_PALETTE */
442 for (i = 0; i < 16; i++) {
443 vm->ram[reg_b] = palette_default_[i];
444 reg_b += 1;
445 }
446 vm->cycle += 16;
447 break;
448 }
449 }
450
451 static struct renderer_ {
452 char *name;
453 char *args;
454 int (*renderer)(void *, struct pixel_ *, size_t, size_t);
455 } lem1802_renderers_[] = {
456 { "pnm", "filename", pixbuf_render_pnm_ },
457 #ifdef HAVE_LIBPNG
458 { "png", "filename", pixbuf_render_png_ },
459 #endif /* HAVE_LIBPNG */
460 #ifdef HAVE_LIBVNCSERVER
461 { "vnc", "", pixbuf_render_vnc_ },
462 #endif /* HAVE_LIBVNCSERVER */
463 { "none", "", NULL },
464 { NULL, NULL, NULL }
465 };
466
467 int lem1802_renderer_set(struct dcpu16_hw *hw, const char *renderer, void *data) {
468 struct renderer_ *r;
469
470 for (r = lem1802_renderers_; r->renderer; r++) {
471 if (strcmp(renderer, r->name) == 0) {
472 ((struct lem1802_ *)(hw->data))->render = r->renderer;
473 ((struct lem1802_ *)(hw->data))->renderer_data = data;
474 TRACE("%s>> renderer set to %s", __func__, renderer);
475 return 0;
476 }
477 }
478
479 return -1;
480 }
481
482 char *lem1802_renderers_iter(void **iterp, char **name, char **args) {
483 struct renderer_ **r = (struct renderer_ **)iterp;
484
485 if (*r == NULL)
486 *r = lem1802_renderers_;
487 else
488 (*r)++;
489
490 if ((*r)->name == NULL) {
491 *r = NULL;
492 return NULL;
493 }
494
495 *name = (*r)->name;
496 *args = (*r)->args;
497
498 return (*r)->name;
499 }
500
501 /* instantitate a new display */
502 struct dcpu16_hw *lem1802_new(struct dcpu16 *vm) {
503 struct dcpu16_hw *hw;
504
505 hw = calloc(1, sizeof *hw);
506 if (hw == NULL) {
507 vm->warn_cb_("%s():%s", "calloc", strerror(errno));
508 return NULL;
509 }
510
511 memcpy(hw, &hw_, sizeof *hw);
512
513 hw->data = calloc(1, sizeof(struct dcpu16_hw));
514 if (hw->data == NULL) {
515 vm->warn_cb_("%s():%s", "calloc", strerror(errno));
516 free(hw);
517 return NULL;
518 }
519
520 ((struct lem1802_ *)(hw->data))->pixbuf = calloc(1, PIX_X * PIX_Y * sizeof *((struct lem1802_ *)(hw->data))->pixbuf);
521 if (((struct lem1802_ *)(hw->data))->pixbuf == NULL) {
522 vm->warn_cb_("%s():%s", "calloc", strerror(errno));
523 free(hw->data);
524 free(hw);
525 return NULL;
526 }
527
528 ((struct lem1802_ *)(hw->data))->refresh_rate = 1666;
529 ((struct lem1802_ *)(hw->data))->blink_rate = 75000;
530
531 hw->vm = vm;
532
533 return hw;
534 }
535
536 void lem1802_del(struct dcpu16_hw **hw) {
537 if (hw) {
538 if (*hw) {
539 if ((*hw)->data) {
540 if (((struct lem1802_ *)(*hw)->data)->pixbuf) {
541 free(((struct lem1802_ *)(*hw)->data)->pixbuf);
542 ((struct lem1802_ *)(*hw)->data)->pixbuf = NULL;
543 }
544 free((*hw)->data);
545 (*hw)->data = NULL;
546 }
547 free(*hw);
548 }
549 *hw = NULL;
550 }
551 }