#ifndef _HW_COM_C_
#define _HW_COM_C_
#ifndef STATIC_INLINE_HW_COM
#define STATIC_INLINE_HW_COM STATIC_INLINE
#endif
#include "device_table.h"
#ifdef HAVE_STRING_H
#include <string.h>
#else
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
enum {
max_hw_com_registers = 8,
};
typedef struct _com_port {
int ready;
int delay;
int interrupting;
FILE *file;
} com_port;
typedef struct _com_modem {
int carrier;
int carrier_changed;
int interrupting;
} com_modem;
typedef struct _hw_com_device {
com_port input;
com_port output;
com_modem modem;
char dlab[2];
char reg[max_hw_com_registers];
int interrupting;
} hw_com_device;
static void
hw_com_device_init_data(device *me)
{
hw_com_device *com = (hw_com_device*)device_data(me);
if (com->output.file != NULL)
fclose(com->output.file);
if (com->input.file != NULL)
fclose(com->input.file);
memset(com, 0, sizeof(hw_com_device));
com->output.delay = (device_find_property(me, "output-delay") != NULL
? device_find_integer_property(me, "output-delay")
: 0);
com->input.delay = (device_find_property(me, "input-delay") != NULL
? device_find_integer_property(me, "input-delay")
: 0);
if (device_find_property(me, "input-file") != NULL) {
const char *input_file = device_find_string_property(me, "input-file");
com->input.file = fopen(input_file, "r");
if (com->input.file == NULL)
device_error(me, "Problem opening input file %s\n", input_file);
if (device_find_property(me, "input-buffering") != NULL) {
const char *buffering = device_find_string_property(me, "input-buffering");
if (strcmp(buffering, "unbuffered") == 0)
setbuf(com->input.file, NULL);
}
}
if (device_find_property(me, "output-file") != NULL) {
const char *output_file = device_find_string_property(me, "output-file");
com->output.file = fopen(output_file, "w");
if (com->input.file == NULL)
device_error(me, "Problem opening output file %s\n", output_file);
if (device_find_property(me, "output-buffering") != NULL) {
const char *buffering = device_find_string_property(me, "output-buffering");
if (strcmp(buffering, "unbuffered") == 0)
setbuf(com->output.file, NULL);
}
}
com->input.ready = 1;
com->modem.carrier = 1;
com->output.ready = 1;
}
static void
update_com_interrupts(device *me,
hw_com_device *com)
{
int interrupting;
com->modem.interrupting = (com->modem.carrier_changed && (com->reg[1] & 0x80));
com->input.interrupting = (com->input.ready && (com->reg[1] & 0x1));
com->output.interrupting = (com->output.ready && (com->reg[1] & 0x2));
interrupting = (com->input.interrupting
|| com->output.interrupting
|| com->modem.interrupting);
if (interrupting) {
if (!com->interrupting) {
device_interrupt_event(me, 0 , 1 , NULL, 0);
}
}
else {
if (com->interrupting)
device_interrupt_event(me, 0 , 0 , NULL, 0);
}
com->interrupting = interrupting;
}
static void
make_read_ready(void *data)
{
device *me = (device*)data;
hw_com_device *com = (hw_com_device*)device_data(me);
com->input.ready = 1;
update_com_interrupts(me, com);
}
static void
read_com(device *me,
hw_com_device *com,
unsigned_word a,
char val[1])
{
unsigned_word addr = a % 8;
if (com->reg[3] & 0x8 && addr < 2) {
*val = com->dlab[addr];
return;
}
switch (addr) {
case 0:
if (!com->modem.carrier)
*val = '\0';
if (com->input.ready) {
if (com->input.file == NULL) {
if (sim_io_read_stdin(val, 1) < 0)
com->modem.carrier_changed = 1;
}
else {
if (fread(val, 1, 1, com->input.file) == 0)
com->modem.carrier_changed = 1;
}
if (com->modem.carrier_changed) {
com->modem.carrier = 0;
com->input.ready = 0;
*val = '\0';
}
else if (com->input.delay > 0) {
com->input.ready = 0;
device_event_queue_schedule(me, com->input.delay, make_read_ready, me);
}
}
else {
*val = '\0';
}
break;
case 2:
if (com->interrupting) {
if (com->input.interrupting)
*val = 0x4;
else if (com->output.interrupting)
*val = 0x2;
else if (com->modem.interrupting == 0)
*val = 0;
else
device_error(me, "bad elif for interrupts\n");
}
else
*val = 0x1;
break;
case 5:
*val = ((com->input.ready ? 0x1 : 0)
| (com->output.ready ? 0x60 : 0)
);
break;
case 6:
*val = ((com->modem.carrier_changed ? 0x08 : 0)
| (com->modem.carrier ? 0x80 : 0)
);
com->modem.carrier_changed = 0;
break;
default:
*val = com->reg[addr];
break;
}
update_com_interrupts(me, com);
}
static unsigned
hw_com_io_read_buffer_callback(device *me,
void *dest,
int space,
unsigned_word addr,
unsigned nr_bytes,
cpu *processor,
unsigned_word cia)
{
hw_com_device *com = device_data(me);
int i;
for (i = 0; i < nr_bytes; i++) {
read_com(me, com, addr + i, &((char*)dest)[i]);
}
return nr_bytes;
}
static void
make_write_ready(void *data)
{
device *me = (device*)data;
hw_com_device *com = (hw_com_device*)device_data(me);
com->output.ready = 1;
update_com_interrupts(me, com);
}
static void
write_com(device *me,
hw_com_device *com,
unsigned_word a,
char val)
{
unsigned_word addr = a % 8;
if (com->reg[3] & 0x8 && addr < 2) {
com->dlab[addr] = val;
return;
}
switch (addr) {
case 0:
if (com->output.file == NULL) {
sim_io_write_stdout(&val, 1);
}
else {
fwrite(&val, 1, 1, com->output.file);
}
if (com->output.ready && com->output.delay > 0) {
com->output.ready = 0;
device_event_queue_schedule(me, com->output.delay, make_write_ready, me);
}
break;
default:
com->reg[addr] = val;
break;
}
update_com_interrupts(me, com);
}
static unsigned
hw_com_io_write_buffer_callback(device *me,
const void *source,
int space,
unsigned_word addr,
unsigned nr_bytes,
cpu *processor,
unsigned_word cia)
{
hw_com_device *com = device_data(me);
int i;
for (i = 0; i < nr_bytes; i++) {
write_com(me, com, addr + i, ((char*)source)[i]);
}
return nr_bytes;
}
static void
hw_com_instance_delete(device_instance *instance)
{
return;
}
static int
hw_com_instance_read(device_instance *instance,
void *buf,
unsigned_word len)
{
device *me = device_instance_device(instance);
hw_com_device *com = device_data(me);
if (com->input.file == NULL)
return sim_io_read_stdin(buf, len);
else {
return fread(buf, 1, len, com->input.file);
}
}
static int
hw_com_instance_write(device_instance *instance,
const void *buf,
unsigned_word len)
{
device *me = device_instance_device(instance);
hw_com_device *com = device_data(me);
if (com->output.file == NULL)
return sim_io_write_stdout(buf, len);
else {
return fwrite(buf, 1, len, com->output.file);
}
}
static const device_instance_callbacks hw_com_instance_callbacks = {
hw_com_instance_delete,
hw_com_instance_read,
hw_com_instance_write,
};
static device_instance *
hw_com_create_instance(device *me,
const char *path,
const char *args)
{
return device_create_instance_from(me, NULL,
device_data(me),
path, args,
&hw_com_instance_callbacks);
}
static device_callbacks const hw_com_callbacks = {
{ generic_device_init_address,
hw_com_device_init_data },
{ NULL, },
{ hw_com_io_read_buffer_callback,
hw_com_io_write_buffer_callback, },
{ NULL, },
{ NULL, },
{ NULL, },
hw_com_create_instance,
};
static void *
hw_com_create(const char *name,
const device_unit *unit_address,
const char *args)
{
hw_com_device *hw_com = ZALLOC(hw_com_device);
return hw_com;
}
const device_descriptor hw_com_device_descriptor[] = {
{ "com", hw_com_create, &hw_com_callbacks },
{ NULL },
};
#endif