510 lines
13 KiB
C
510 lines
13 KiB
C
#include "common/fmt_binary.h"
|
|
#include "common/op_str.h"
|
|
#include "common/video_character_display.h"
|
|
#include "vm.h"
|
|
#include <SDL2/SDL.h>
|
|
#include <SDL2/SDL_error.h>
|
|
#include <SDL2/SDL_events.h>
|
|
#include <SDL2/SDL_keycode.h>
|
|
#include <SDL2/SDL_pixels.h>
|
|
#include <SDL2/SDL_render.h>
|
|
#include <SDL2/SDL_video.h>
|
|
#include <bits/pthreadtypes.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <stdatomic.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
typedef struct {
|
|
Interrupt* data;
|
|
size_t capacity;
|
|
size_t front;
|
|
size_t back;
|
|
} InterruptQueue;
|
|
|
|
void int_queue_construct(InterruptQueue* queue, size_t capacity)
|
|
{
|
|
*queue = (InterruptQueue) {
|
|
.data = malloc(sizeof(Interrupt) * capacity),
|
|
.capacity = capacity,
|
|
.back = 0,
|
|
.front = 0,
|
|
};
|
|
}
|
|
|
|
void int_queue_destroy(InterruptQueue* queue)
|
|
{
|
|
free(queue->data);
|
|
}
|
|
|
|
void int_queue_push(InterruptQueue* queue, Interrupt req)
|
|
{
|
|
size_t front = queue->front + 1;
|
|
if (front >= queue->capacity) {
|
|
front = 0;
|
|
}
|
|
if (front == queue->back) {
|
|
fprintf(stderr, "error: queue overflow\n");
|
|
exit(1);
|
|
}
|
|
queue->data[queue->front] = req;
|
|
queue->front = front;
|
|
}
|
|
|
|
size_t int_queue_size(const InterruptQueue* queue)
|
|
{
|
|
return queue->front >= queue->back
|
|
? queue->front - queue->back
|
|
: (queue->capacity - queue->back) + queue->front;
|
|
}
|
|
|
|
Interrupt int_queue_pop(InterruptQueue* queue)
|
|
{
|
|
if (queue->back == queue->front) {
|
|
fprintf(stderr, "error: queue underflow\n");
|
|
exit(1);
|
|
}
|
|
Interrupt val = queue->data[queue->back];
|
|
size_t back = queue->back + 1;
|
|
if (back >= queue->capacity) {
|
|
back = 0;
|
|
}
|
|
queue->back = back;
|
|
return val;
|
|
}
|
|
|
|
typedef struct {
|
|
IODevice io_device;
|
|
|
|
SDL_Window* window;
|
|
SDL_Renderer* renderer;
|
|
SDL_Texture* buffer_texture;
|
|
|
|
pthread_t render_thread;
|
|
|
|
atomic_bool should_run;
|
|
pthread_mutex_t mutex;
|
|
pthread_cond_t interrupt_waiter;
|
|
|
|
InterruptQueue int_queue;
|
|
} SdlDevice;
|
|
|
|
int sdldevice_construct(SdlDevice* device);
|
|
void sdldevice_destroy(SdlDevice* device);
|
|
void sdldevice_set_char(
|
|
IODevice* device_device, uint16_t offset, uint8_t value);
|
|
void sdldevice_wait_for_interrupt(IODevice* io_device);
|
|
void* sdldevice_thread_entry(void* data);
|
|
void sdldevice_poll_events(SdlDevice* device);
|
|
Interrupt sdldevice_maybe_next_interrupt(IODevice* io_device);
|
|
|
|
int sdldevice_construct(SdlDevice* device)
|
|
{
|
|
int res;
|
|
res = SDL_Init(SDL_INIT_VIDEO);
|
|
if (res != 0) {
|
|
fprintf(stderr, "error: could not init sdl: %s\n", SDL_GetError());
|
|
return -1;
|
|
}
|
|
SDL_Window* window;
|
|
SDL_Renderer* renderer;
|
|
res = SDL_CreateWindowAndRenderer(
|
|
vcd_width_in_px, vcd_height_in_px, 0, &window, &renderer);
|
|
if (res != 0) {
|
|
fprintf(stderr,
|
|
"error: could not create window/renderer: %s\n",
|
|
SDL_GetError());
|
|
return -1;
|
|
}
|
|
|
|
SDL_Texture* buffer_texture = SDL_CreateTexture(renderer,
|
|
SDL_PIXELFORMAT_RGBA32,
|
|
SDL_TEXTUREACCESS_STREAMING,
|
|
vcd_width_in_px,
|
|
vcd_height_in_px);
|
|
if (buffer_texture == NULL) {
|
|
fprintf(stderr,
|
|
"error: could not create buffer texture: %s\n",
|
|
SDL_GetError());
|
|
return -1;
|
|
}
|
|
|
|
*device = (SdlDevice) {
|
|
.io_device = (IODevice) {
|
|
.self = device,
|
|
.set_char = sdldevice_set_char,
|
|
.wait_for_interrupt = sdldevice_wait_for_interrupt,
|
|
.maybe_next_interrupt = sdldevice_maybe_next_interrupt,
|
|
},
|
|
.window = window,
|
|
.renderer = renderer,
|
|
.buffer_texture = buffer_texture,
|
|
.render_thread = (pthread_t) { 0 },
|
|
.should_run = true,
|
|
.mutex = PTHREAD_MUTEX_INITIALIZER,
|
|
.interrupt_waiter = PTHREAD_COND_INITIALIZER,
|
|
.int_queue = {0},
|
|
};
|
|
|
|
SDL_RenderPresent(device->renderer);
|
|
|
|
pthread_create(
|
|
&device->render_thread, NULL, sdldevice_thread_entry, device);
|
|
|
|
int_queue_construct(&device->int_queue, 128);
|
|
return 0;
|
|
}
|
|
|
|
void sdldevice_destroy(SdlDevice* device)
|
|
{
|
|
device->should_run = false;
|
|
|
|
pthread_join(device->render_thread, NULL);
|
|
pthread_mutex_destroy(&device->mutex);
|
|
pthread_cond_destroy(&device->interrupt_waiter);
|
|
|
|
int_queue_destroy(&device->int_queue);
|
|
|
|
SDL_DestroyTexture(device->buffer_texture);
|
|
SDL_DestroyRenderer(device->renderer);
|
|
SDL_DestroyWindow(device->window);
|
|
SDL_Quit();
|
|
}
|
|
|
|
extern const uint64_t charset[];
|
|
|
|
void sdldevice_set_char(IODevice* io_device, uint16_t offset, uint8_t value)
|
|
{
|
|
if (!((value >= 'A' && value <= 'Z') || value == ' ')) {
|
|
printf("sdldevice: invalid char value = %d\n", value);
|
|
return;
|
|
}
|
|
|
|
SdlDevice* device = io_device->self;
|
|
pthread_mutex_lock(&device->mutex);
|
|
|
|
SDL_Color* buffer;
|
|
int pitch;
|
|
int res = SDL_LockTexture(
|
|
device->buffer_texture, NULL, (void**)&buffer, &pitch);
|
|
if (res != 0) {
|
|
fprintf(stderr, "error: could not lock texture: %s\n", SDL_GetError());
|
|
pthread_mutex_unlock(&device->mutex);
|
|
return;
|
|
}
|
|
|
|
for (int ch_y = 0; ch_y < vcd_ch_height; ++ch_y) {
|
|
for (int ch_x = 0; ch_x < vcd_ch_width; ++ch_x) {
|
|
bool ch = charset[value]
|
|
>> (ch_y * vcd_ch_width + (vcd_ch_width - ch_x - 1))
|
|
& 1;
|
|
|
|
for (int px_y = 0; px_y < vcd_px_height; ++px_y) {
|
|
for (int px_x = 0; px_x < vcd_px_width; ++px_x) {
|
|
|
|
int x = (offset % vcd_width_in_ch * vcd_ch_width + ch_x)
|
|
* vcd_px_width
|
|
+ px_x;
|
|
int y = (offset / vcd_width_in_ch * vcd_ch_height + ch_y)
|
|
* vcd_px_height
|
|
+ px_y;
|
|
|
|
buffer[y * vcd_width_in_px + x] = ch
|
|
? (SDL_Color) { 0xff, 0xff, 0xff, 0xff }
|
|
: (SDL_Color) { 0x00, 0x00, 0x00, 0xff };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SDL_UnlockTexture(device->buffer_texture);
|
|
SDL_RenderCopy(device->renderer, device->buffer_texture, NULL, NULL);
|
|
|
|
SDL_RenderPresent(device->renderer);
|
|
|
|
pthread_mutex_unlock(&device->mutex);
|
|
}
|
|
|
|
void sdldevice_wait_for_interrupt(IODevice* io_device)
|
|
{
|
|
SdlDevice* device = io_device->self;
|
|
|
|
pthread_mutex_lock(&device->mutex);
|
|
|
|
// printf("vm: waiting for interrupt...\n");
|
|
pthread_cond_wait(&device->interrupt_waiter, &device->mutex);
|
|
// printf("vm: got interrupt!\n");
|
|
|
|
pthread_mutex_unlock(&device->mutex);
|
|
}
|
|
|
|
void* sdldevice_thread_entry(void* data)
|
|
{
|
|
SdlDevice* device = (SdlDevice*)data;
|
|
|
|
while (device->should_run) {
|
|
sdldevice_poll_events(device);
|
|
if (!device->should_run)
|
|
break;
|
|
|
|
SDL_Delay(6);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline bool is_exit_event(SDL_Event* event)
|
|
{
|
|
return event->type == SDL_QUIT
|
|
|| (event->type == SDL_KEYDOWN
|
|
&& event->key.keysym.scancode == SDL_SCANCODE_ESCAPE
|
|
// && event->key.keysym.mod & KMOD_CTRL
|
|
);
|
|
}
|
|
|
|
void sdldevice_poll_events(SdlDevice* device)
|
|
{
|
|
pthread_mutex_lock(&device->mutex);
|
|
|
|
bool should_notify = false;
|
|
|
|
SDL_Event event;
|
|
while (SDL_PollEvent(&event)) {
|
|
if (is_exit_event(&event)) {
|
|
device->should_run = false;
|
|
int_queue_push(&device->int_queue,
|
|
(Interrupt) {
|
|
.type = InterruptType_Shutdown,
|
|
});
|
|
should_notify = true;
|
|
break;
|
|
} else if (event.type == SDL_KEYDOWN) {
|
|
int_queue_push(&device->int_queue,
|
|
(Interrupt) {
|
|
.type = InterruptType_KeyEvent,
|
|
.keycode = (uint16_t)event.key.keysym.scancode,
|
|
});
|
|
should_notify = true;
|
|
}
|
|
}
|
|
|
|
pthread_mutex_unlock(&device->mutex);
|
|
|
|
if (should_notify) {
|
|
// printf("sdldevice: interrupt occured!\n");
|
|
pthread_cond_signal(&device->interrupt_waiter);
|
|
}
|
|
}
|
|
|
|
Interrupt sdldevice_maybe_next_interrupt(IODevice* io_device)
|
|
{
|
|
SdlDevice* device = io_device->self;
|
|
|
|
pthread_mutex_lock(&device->mutex);
|
|
|
|
Interrupt val = {
|
|
.type = InterruptType_None,
|
|
};
|
|
|
|
if (int_queue_size(&device->int_queue) > 0) {
|
|
val = int_queue_pop(&device->int_queue);
|
|
}
|
|
|
|
pthread_mutex_unlock(&device->mutex);
|
|
return val;
|
|
}
|
|
|
|
typedef struct {
|
|
Drive drive;
|
|
uint64_t* mem;
|
|
} MemDrive;
|
|
|
|
void memdrive_construct(MemDrive* drive, uint64_t* mem, size_t mem_size);
|
|
void memdrive_drive_read(Drive* in_drive, uint8_t* block, uint16_t i);
|
|
void memdrive_drive_write(Drive* in_drive, const uint8_t* block, uint16_t i);
|
|
|
|
void memdrive_construct(MemDrive* drive, uint64_t* mem, size_t mem_size)
|
|
{
|
|
const uint16_t block_size = 512;
|
|
*drive = (MemDrive) {
|
|
.drive = (Drive) {
|
|
.self = drive,
|
|
.block_size = block_size,
|
|
.block_amount = (uint16_t)(mem_size / block_size),
|
|
.read = memdrive_drive_read,
|
|
.write = memdrive_drive_write,
|
|
},
|
|
.mem = mem,
|
|
};
|
|
}
|
|
|
|
void memdrive_drive_read(Drive* in_drive, uint8_t* block, uint16_t i)
|
|
{
|
|
MemDrive* drive = in_drive->self;
|
|
memcpy(block,
|
|
&drive->mem[i * drive->drive.block_size],
|
|
drive->drive.block_size);
|
|
}
|
|
|
|
void memdrive_drive_write(Drive* in_drive, const uint8_t* block, uint16_t i)
|
|
{
|
|
MemDrive* drive = in_drive->self;
|
|
memcpy(&drive->mem[i * drive->drive.block_size],
|
|
block,
|
|
drive->drive.block_size);
|
|
}
|
|
|
|
typedef struct {
|
|
Drive drive;
|
|
FILE* fp;
|
|
} FileDrive;
|
|
|
|
void filedrive_construct(
|
|
FileDrive* drive, FILE* fp, uint16_t block_size, uint16_t block_amount);
|
|
void filedrive_drive_read(Drive* in_drive, uint8_t* block, uint16_t i);
|
|
void filedrive_drive_write(Drive* in_drive, const uint8_t* block, uint16_t i);
|
|
|
|
void filedrive_construct(
|
|
FileDrive* drive, FILE* fp, uint16_t block_size, uint16_t block_amount)
|
|
{
|
|
*drive = (FileDrive) {
|
|
.drive = (Drive) {
|
|
.self = drive,
|
|
.block_size = block_size,
|
|
.block_amount = block_amount,
|
|
.read = filedrive_drive_read,
|
|
.write = filedrive_drive_write,
|
|
},
|
|
.fp = fp,
|
|
};
|
|
}
|
|
|
|
void filedrive_drive_read(Drive* in_drive, uint8_t* block, uint16_t i)
|
|
{
|
|
FileDrive* drive = in_drive->self;
|
|
fseek(drive->fp, drive->drive.block_size * i, SEEK_SET);
|
|
fread(block, sizeof(uint8_t), drive->drive.block_size, drive->fp);
|
|
}
|
|
|
|
void filedrive_drive_write(Drive* in_drive, const uint8_t* block, uint16_t i)
|
|
{
|
|
FileDrive* drive = in_drive->self;
|
|
fseek(drive->fp, drive->drive.block_size * i, SEEK_SET);
|
|
fwrite(block, sizeof(uint8_t), drive->drive.block_size, drive->fp);
|
|
}
|
|
|
|
__attribute__((unused)) static inline void dump_program(uint16_t* program)
|
|
{
|
|
for (size_t rip = 20; rip < 60; ++rip) {
|
|
uint16_t val = program[rip];
|
|
printf("[%02lx] = %02x %02x %c%c%c%c %c%c%c%c %c%c%c%c %c%c%c%c "
|
|
"%s\n",
|
|
rip * 2,
|
|
val >> 8,
|
|
val & 0xff,
|
|
fmt_binary(val >> 8),
|
|
fmt_binary(val & 0xff),
|
|
op_str(val & 0x3f));
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
const char* disk_file;
|
|
} Args;
|
|
|
|
static inline Args parse_args(int argc, char** argv);
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
Args args = parse_args(argc, argv);
|
|
|
|
int res;
|
|
|
|
int label_ids = 0;
|
|
|
|
SdlDevice io_device;
|
|
res = sdldevice_construct(&io_device);
|
|
if (res != 0) {
|
|
exit(1);
|
|
}
|
|
|
|
FILE* fp = fopen(args.disk_file, "rb");
|
|
if (!fp) {
|
|
fprintf(stderr,
|
|
"error: could not open %s: %s\n",
|
|
args.disk_file,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
FileDrive drive;
|
|
filedrive_construct(&drive, fp, 512, 32);
|
|
|
|
vm_start(&drive.drive, &io_device.io_device);
|
|
|
|
fclose(fp);
|
|
sdldevice_destroy(&io_device);
|
|
}
|
|
|
|
static inline Args parse_args(int argc, char** argv)
|
|
{
|
|
const char* disk_file = "build/image";
|
|
for (int i = 1; i < argc; ++i) {
|
|
if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--disk-file") == 0) {
|
|
if (i + 1 >= argc) {
|
|
fprintf(
|
|
stderr, "error: expected filename after '%s'\n", argv[i]);
|
|
exit(1);
|
|
}
|
|
disk_file = argv[i + 1];
|
|
i += 1;
|
|
} else {
|
|
fprintf(stderr, "error: unrecognized argument '%s'\n", argv[i]);
|
|
exit(1);
|
|
}
|
|
}
|
|
return (Args) {
|
|
disk_file,
|
|
};
|
|
}
|
|
|
|
const char* __asan_default_options(void)
|
|
{
|
|
return "detect_leaks=0";
|
|
}
|
|
|
|
const uint64_t charset[] = {
|
|
[' '] = 0x0000000000000000,
|
|
['A'] = 0x66667E66667E1800,
|
|
['B'] = 0x7C7E667C667E7800,
|
|
['C'] = 0x3C7E6060607E3C00,
|
|
['D'] = 0x7C7E6666667E7C00,
|
|
['E'] = 0x7E7E607E607E7E00,
|
|
['F'] = 0x60607878607E7E00,
|
|
['G'] = 0x7E7E666E607E7E00,
|
|
['H'] = 0x6666667E7E666600,
|
|
['I'] = 0x7E7E1818187E7E00,
|
|
['J'] = 0x3C7E6606067E7E00,
|
|
['K'] = 0x666E7C7C6E666600,
|
|
['L'] = 0x7E7E606060606000,
|
|
['M'] = 0xC3C3C3C3DBFFE700,
|
|
['N'] = 0x6666666E7E766600,
|
|
['O'] = 0x3C66666666663C00,
|
|
['P'] = 0x60607C7E667C7C00,
|
|
['Q'] = 0x3F7E6E66667E3C00,
|
|
['R'] = 0x666C7C7E667E7C00,
|
|
['S'] = 0x3C66063C60663C00,
|
|
['T'] = 0x18181818187E7E00,
|
|
['U'] = 0x3C7E666666666600,
|
|
['V'] = 0x183C7E6666666600,
|
|
['W'] = 0xE7FFDBDBC3C3C300,
|
|
['X'] = 0x6666663C3C666600,
|
|
['Y'] = 0x181818183C666600,
|
|
['Z'] = 0x7E7E30180C7E7E00,
|
|
};
|