2017-12-02 19:05:53 +00:00
|
|
|
/*
|
|
|
|
* vm_segment.c
|
|
|
|
*
|
2017-12-09 04:12:31 +00:00
|
|
|
* The functions here allow you to allocate generic blocks of memory (or
|
|
|
|
* "segments") for use anywhere else in the software. They can be used
|
|
|
|
* to represent machine memory, removable media (like floppy disks),
|
|
|
|
* etc.
|
2017-12-02 19:05:53 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
2017-11-22 05:24:51 +00:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "log.h"
|
|
|
|
#include "vm_segment.h"
|
|
|
|
|
2018-01-03 03:11:43 +00:00
|
|
|
/*
|
|
|
|
* This is sort of regrettable, but we need a machine pointer that we
|
|
|
|
* can pass into the read/write map functions (which will assume to have
|
|
|
|
* access to the machine architecture). The alternative is an update to
|
|
|
|
* a lot more of the codebase to add machine pointers -- void pointers
|
|
|
|
* at that -- which is even uglier.
|
|
|
|
*
|
|
|
|
* FIXME: we might consider a dependency injection container at some
|
|
|
|
* point.
|
|
|
|
*/
|
|
|
|
static void *map_mach = NULL;
|
|
|
|
|
2017-12-02 19:05:53 +00:00
|
|
|
/*
|
|
|
|
* Create a new segment, such that it contains a number of bytes indicated
|
|
|
|
* by `size`.
|
|
|
|
*/
|
2017-11-22 05:24:51 +00:00
|
|
|
vm_segment *
|
|
|
|
vm_segment_create(size_t size)
|
|
|
|
{
|
2017-12-02 19:05:53 +00:00
|
|
|
vm_segment *segment;
|
2017-11-22 05:24:51 +00:00
|
|
|
|
|
|
|
// Allocate memory for the current memory segment.
|
2017-12-02 19:05:53 +00:00
|
|
|
segment = malloc(sizeof(vm_segment));
|
2017-11-22 05:24:51 +00:00
|
|
|
|
|
|
|
// Ack! We couldn't get the memory we wanted. Let's bail.
|
2017-12-02 19:05:53 +00:00
|
|
|
if (segment == NULL) {
|
|
|
|
log_critical("Couldn't allocate enough space for vm_segment");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
segment->memory = malloc(sizeof(vm_8bit) * size);
|
|
|
|
if (segment->memory == NULL) {
|
2018-01-01 23:11:03 +00:00
|
|
|
free(segment);
|
2017-11-22 05:24:51 +00:00
|
|
|
log_critical("Couldn't allocate enough space for vm_segment");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2017-12-12 20:45:48 +00:00
|
|
|
// We should zero out memory and make explicit that any new segment
|
2017-12-12 20:33:55 +00:00
|
|
|
// begins life in that state.
|
2017-12-16 03:22:22 +00:00
|
|
|
memset(segment->memory, 0, sizeof(vm_8bit) * size);
|
2017-12-12 20:33:55 +00:00
|
|
|
|
2017-12-12 20:32:32 +00:00
|
|
|
segment->read_table = malloc(sizeof(vm_segment_read_fn) * size);
|
|
|
|
if (segment->read_table == NULL) {
|
|
|
|
log_critical("Couldn't allocate enough space for segment read_table");
|
2018-01-01 23:11:03 +00:00
|
|
|
vm_segment_free(segment);
|
2017-12-12 20:32:32 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
segment->write_table = malloc(sizeof(vm_segment_write_fn) * size);
|
|
|
|
if (segment->write_table == NULL) {
|
|
|
|
log_critical("Couldn't allocate enough space for segment write_table");
|
2018-01-01 23:11:03 +00:00
|
|
|
vm_segment_free(segment);
|
2017-12-12 20:32:32 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2017-12-12 20:35:13 +00:00
|
|
|
// Let's NULL-out the read and write tables. If we don't do so, they
|
|
|
|
// may have some bits of garbage in it, and could cause the
|
|
|
|
// read/write mapper code to attempt to a run a function with
|
|
|
|
// garbage. We could have undefined garbage! We can only properly
|
|
|
|
// work with defined garbage.
|
2017-12-16 03:22:22 +00:00
|
|
|
memset(segment->read_table, (int)NULL, sizeof(vm_segment_read_fn) * size);
|
|
|
|
memset(segment->write_table, (int)NULL, sizeof(vm_segment_write_fn) * size);
|
2017-12-12 20:32:32 +00:00
|
|
|
|
2017-12-02 19:05:53 +00:00
|
|
|
segment->size = size;
|
2017-11-22 05:24:51 +00:00
|
|
|
|
2017-12-02 19:05:53 +00:00
|
|
|
return segment;
|
2017-11-22 05:24:51 +00:00
|
|
|
}
|
|
|
|
|
2017-12-02 19:05:53 +00:00
|
|
|
/*
|
|
|
|
* Free the memory consumed by a given segment.
|
|
|
|
*/
|
2017-11-22 05:24:51 +00:00
|
|
|
void
|
2017-12-02 19:05:53 +00:00
|
|
|
vm_segment_free(vm_segment *segment)
|
|
|
|
{
|
|
|
|
free(segment->memory);
|
|
|
|
free(segment);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the byte in `segment`, at `index`, to the given `value`. Our
|
|
|
|
* bounds-checking here will _crash_ the program if we are
|
|
|
|
* out-of-bounds.
|
|
|
|
*/
|
2017-12-09 21:16:56 +00:00
|
|
|
int
|
2017-12-02 19:05:53 +00:00
|
|
|
vm_segment_set(vm_segment *segment, size_t index, vm_8bit value)
|
2017-11-22 05:24:51 +00:00
|
|
|
{
|
|
|
|
// Some bounds checking.
|
|
|
|
if (!vm_segment_bounds_check(segment, index)) {
|
|
|
|
log_critical(
|
|
|
|
"Attempt to set segment index (%d) greater than bounds (%d)",
|
|
|
|
index,
|
|
|
|
segment->size);
|
|
|
|
|
2017-12-09 21:16:56 +00:00
|
|
|
return ERR_OOB;
|
2017-11-22 05:24:51 +00:00
|
|
|
}
|
|
|
|
|
2017-12-12 21:00:47 +00:00
|
|
|
// Check if we have a write mapper
|
|
|
|
if (segment->write_table[index]) {
|
2018-01-03 03:11:43 +00:00
|
|
|
segment->write_table[index](segment, index, value, map_mach);
|
2017-12-12 21:00:47 +00:00
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
2017-11-22 05:24:51 +00:00
|
|
|
segment->memory[index] = value;
|
2017-12-09 21:16:56 +00:00
|
|
|
return OK;
|
2017-11-22 05:24:51 +00:00
|
|
|
}
|
|
|
|
|
2017-12-02 19:05:53 +00:00
|
|
|
/*
|
|
|
|
* Return the byte in `segment` at the given `index` point. Our
|
|
|
|
* bounds-checking will _crash_ the program if an index is requested out
|
|
|
|
* of bounds.
|
|
|
|
*/
|
|
|
|
vm_8bit
|
2017-11-22 05:24:51 +00:00
|
|
|
vm_segment_get(vm_segment *segment, size_t index)
|
|
|
|
{
|
|
|
|
if (!vm_segment_bounds_check(segment, index)) {
|
|
|
|
log_critical(
|
|
|
|
"Attempt to set segment index (%d) greater than bounds (%d)",
|
|
|
|
index,
|
|
|
|
segment->size);
|
|
|
|
|
|
|
|
// See vm_segment_set() for a justification of this behavior.
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
2017-12-12 21:00:47 +00:00
|
|
|
// We may have a read mapper for this address
|
|
|
|
if (segment->read_table[index]) {
|
2018-01-03 03:11:43 +00:00
|
|
|
return segment->read_table[index](segment, index, map_mach);
|
2017-12-12 21:00:47 +00:00
|
|
|
}
|
|
|
|
|
2017-11-22 05:24:51 +00:00
|
|
|
return segment->memory[index];
|
|
|
|
}
|
|
|
|
|
2018-01-05 22:14:51 +00:00
|
|
|
/*
|
|
|
|
* Return a 16-bit value from a given address. This will read the byte
|
|
|
|
* at addr and the byte at addr+1, then fit those into a two-byte
|
|
|
|
* variable such that addr contains the most significant byte and addr+1
|
|
|
|
* contains the least significant byte.
|
|
|
|
*/
|
|
|
|
vm_16bit
|
|
|
|
vm_segment_get16(vm_segment *segment, size_t addr)
|
|
|
|
{
|
|
|
|
vm_16bit msb, lsb;
|
|
|
|
|
|
|
|
msb = (vm_16bit)vm_segment_get(segment, addr);
|
|
|
|
lsb = (vm_16bit)vm_segment_get(segment, addr+1);
|
|
|
|
|
|
|
|
return (msb << 8) | lsb;
|
|
|
|
}
|
|
|
|
|
2017-12-02 19:05:53 +00:00
|
|
|
/*
|
|
|
|
* Copy a set of bytes from `src` (at `src_index`) to `dest` (at
|
2017-12-12 20:59:00 +00:00
|
|
|
* `dest_index`), such that the range is `length` bytes long. Note that
|
|
|
|
* this function presently bypasses our mapper function code... we may
|
|
|
|
* need to implement such in the future.
|
2017-12-02 19:05:53 +00:00
|
|
|
*/
|
2017-12-09 21:16:56 +00:00
|
|
|
int
|
2017-12-02 19:05:53 +00:00
|
|
|
vm_segment_copy(vm_segment *dest,
|
|
|
|
vm_segment *src,
|
2017-11-22 05:24:51 +00:00
|
|
|
size_t dest_index,
|
2017-12-02 19:05:53 +00:00
|
|
|
size_t src_index,
|
2017-11-22 05:24:51 +00:00
|
|
|
size_t length)
|
|
|
|
{
|
|
|
|
if (src_index + length >= src->size) {
|
|
|
|
log_critical(
|
|
|
|
"Attempt to copy beyond bounds of vm_segment (%d + %d >= %d)",
|
|
|
|
src_index,
|
|
|
|
length,
|
|
|
|
src->size);
|
|
|
|
|
2017-12-09 21:16:56 +00:00
|
|
|
return ERR_OOB;
|
2017-11-22 05:24:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (dest_index + length >= dest->size) {
|
|
|
|
log_critical(
|
|
|
|
"Attempt to copy beyond bounds of vm_segment (%d + %d >= %d)",
|
|
|
|
dest_index,
|
|
|
|
length,
|
|
|
|
dest->size);
|
|
|
|
|
2017-12-09 21:16:56 +00:00
|
|
|
return ERR_OOB;
|
2017-11-22 05:24:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(dest->memory + dest_index,
|
|
|
|
src->memory + src_index,
|
|
|
|
length * sizeof(src->memory[src_index]));
|
2017-12-09 21:16:56 +00:00
|
|
|
|
|
|
|
return OK;
|
2017-11-22 05:24:51 +00:00
|
|
|
}
|
2017-12-12 20:58:06 +00:00
|
|
|
|
2018-01-04 03:10:25 +00:00
|
|
|
/*
|
|
|
|
* Copy the contents of buf into the given dest segment. This is mostly
|
|
|
|
* governed by the same restrictions that copy() has, except that we
|
|
|
|
* can't do all of the bounds-checking we do there. This is just saying,
|
|
|
|
* hey, I have a bunch of bytes and I just need this copied into a
|
|
|
|
* segment, if you don't mind.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
vm_segment_copy_buf(vm_segment *dest, const vm_8bit *src,
|
|
|
|
size_t destoff, size_t srcoff, size_t len)
|
|
|
|
{
|
|
|
|
if (destoff + len > dest->size) {
|
|
|
|
log_critical("Attempt to copy buffer out of bounds (%d + %d >= %d)",
|
|
|
|
destoff, len, dest->size);
|
|
|
|
return ERR_OOB;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Heh heh...there's no way of knowing if srcoff + len is out of
|
|
|
|
// bounds at any point of src, since it's just a dumb buffer. Here's
|
|
|
|
// hopin' it's not! Also, it'll be a fun day when sizeof(vm_8bit) is
|
|
|
|
// not 1, BUT HEY. Let's do it right.
|
|
|
|
memcpy(dest->memory + destoff, src + srcoff,
|
|
|
|
len * sizeof(vm_8bit));
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
2017-12-12 21:10:42 +00:00
|
|
|
/*
|
|
|
|
* Set the read mapper for a given address. We'll use this function
|
|
|
|
* instead of the normal logic on a get for that address.
|
|
|
|
*/
|
2017-12-12 20:58:06 +00:00
|
|
|
int
|
|
|
|
vm_segment_read_map(vm_segment *segment,
|
|
|
|
size_t addr,
|
|
|
|
vm_segment_read_fn fn)
|
|
|
|
{
|
|
|
|
if (addr >= segment->size) {
|
|
|
|
return ERR_OOB;
|
|
|
|
}
|
|
|
|
|
|
|
|
segment->read_table[addr] = fn;
|
|
|
|
return OK;
|
|
|
|
}
|
|
|
|
|
2017-12-12 21:10:42 +00:00
|
|
|
/*
|
|
|
|
* Here we set the map function for a given address to use on writes,
|
|
|
|
* which is to say, when we use the `vm_segment_set()` function.
|
|
|
|
*/
|
2017-12-12 20:58:06 +00:00
|
|
|
int
|
|
|
|
vm_segment_write_map(vm_segment *segment,
|
|
|
|
size_t addr,
|
|
|
|
vm_segment_write_fn fn)
|
|
|
|
{
|
|
|
|
if (addr >= segment->size) {
|
|
|
|
return ERR_OOB;
|
|
|
|
}
|
|
|
|
|
|
|
|
segment->write_table[addr] = fn;
|
|
|
|
return OK;
|
|
|
|
}
|
2018-01-01 02:28:11 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Read the given file stream and write the contents into the given
|
|
|
|
* segment, up to len bytes. If we could not read from the file stream
|
|
|
|
* for some reason, signal that and return an error.
|
|
|
|
*/
|
|
|
|
int
|
2018-01-03 21:16:30 +00:00
|
|
|
vm_segment_fread(vm_segment *segment, FILE *stream, size_t offset, size_t len)
|
2018-01-01 02:28:11 +00:00
|
|
|
{
|
2018-01-03 21:16:30 +00:00
|
|
|
fread(segment->memory + offset, sizeof(vm_8bit), len, stream);
|
2018-01-01 02:28:11 +00:00
|
|
|
|
|
|
|
// fread() may return zero in the case of an error, but it may
|
|
|
|
// return a positive non-zero number short of len; we can't quite
|
|
|
|
// count on just that to tell us something went wrong (especially if
|
|
|
|
// len was not a valid length for the file to begin with).
|
|
|
|
if (ferror(stream)) {
|
|
|
|
log_critical("Could not read file stream: %s\n", strerror(errno));
|
|
|
|
return ERR_BADFILE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return OK;
|
|
|
|
}
|
2018-01-03 03:11:43 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Change the internal notion of the machine used by map functions
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
vm_segment_set_map_machine(void *mach)
|
|
|
|
{
|
|
|
|
map_mach = mach;
|
|
|
|
}
|