559 lines
14 KiB
C
559 lines
14 KiB
C
/* This file is part of the program psim.
|
|
|
|
Copyright (C) 1994-1996, Andrew Cagney <cagney@highland.com.au>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
#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
|
|
|
|
/* DEVICE
|
|
|
|
|
|
com - '550 compatible serial device
|
|
|
|
|
|
DESCRIPTION
|
|
|
|
|
|
Models the basics of the 8 register '550 serial device. The model
|
|
includes an interrupt line, input and output fifos, and status
|
|
information.
|
|
|
|
Independent configuration of the devices input and output streams is
|
|
allowed: use either the console or a file (buffered or unbuffered) as
|
|
the data source/sink; specify the real-time delay between each character
|
|
transfer.
|
|
|
|
When the devices input stream is being taken from a file, the end of
|
|
file is signaled by a loss of carrier (the loss of carrier may be
|
|
incorrectly proceeded by a single null character).
|
|
|
|
|
|
PROPERTIES
|
|
|
|
|
|
reg = <address> <size> ... (optional - note 1)
|
|
|
|
List of <address> <size> pairs. Each pair specifies an address for
|
|
the devices 8 registers. The address should be 8 byte aligned.
|
|
|
|
|
|
alternate-reg = <address> <size> ... (optional - note 1)
|
|
|
|
Alternative addreses for the registers.
|
|
|
|
|
|
assigned-addresses = <address> <size> ... (optional - note 1)
|
|
|
|
On a PCI bus, this property specifies the addresses assigned to the
|
|
device. The values reflect the devices configuration base registers.
|
|
|
|
Note 1: At least one of "assigned-addresses", "reg" or "alternative-reg"
|
|
must be specified. If "assigned-addresses" is specified the other
|
|
address specifications are ignored.
|
|
|
|
|
|
input-file = <file-name> (optional)
|
|
|
|
File to take all serial port input from (instead of the simulation
|
|
console).
|
|
|
|
|
|
output-file = <file-name> (optional)
|
|
|
|
File to send all output to (instead of the simulation console).
|
|
|
|
|
|
input-buffering = "unbuffered" (optional)
|
|
|
|
Specifying "unbuffered" buffering disables buffering on the serial
|
|
devices input stream (all data is immediately read). In the future,
|
|
this option may be used to provide input buffering alternatives.
|
|
|
|
|
|
output-buffering = "unbuffered" (optional)
|
|
|
|
Specifying "unbuffered" buffering disables buffering on the serial
|
|
devices output stream (all data is immediately written). In the future,
|
|
this option may be extended to include other buffering alternatives.
|
|
|
|
|
|
input-delay = <integer-delay> (optional)
|
|
|
|
Specify the number of ticks after the current character has been
|
|
read from the serial port that the next character becomes
|
|
available.
|
|
|
|
|
|
output-delay = <integer-delay> (optional)
|
|
|
|
Specify the number of ticks after a character has been written to
|
|
the empty output fifo that the fifo finishes draining. Any
|
|
characters written to the output fifo before it has drained will
|
|
not be lost and will still be displayed.
|
|
|
|
|
|
EXAMPLES
|
|
|
|
|
|
| /iobus@0xf0000000/com@0x3000/reg 0x3000 8
|
|
|
|
Create a simple console device at address <<0x3000>> within
|
|
<<iobus>>. Since iobus starts at address <<0xf0000000>> the
|
|
absolute address of the serial port will be <<0xf0003000>>.
|
|
|
|
The device will always be ready for I/O (no delay properties specified)
|
|
and both the input and output streams will use the simulation console
|
|
(no file properties).
|
|
|
|
|
|
| $ psim \
|
|
| -o '/cpus/cpu@0' \
|
|
| -o '/iobus@0xf0000000/com@0x4000/reg 0x4000 8' \
|
|
| -o '/iobus@0xf0000000/com@0x4000/input-file /etc/passwd' \
|
|
| -o '/iobus@0xf0000000/com@0x4000/input-delay 1000' \
|
|
| -o '/iobus@0xf0000000/com@0x4000 > 0 int /cpus/cpu@0x0' \
|
|
| psim-test/hw-com/cat.be 0xf0004000
|
|
|
|
The serial port (at address <<0xf0004000>> is configured so that it
|
|
takes its input from the file <</etc/passwd>> while its output is
|
|
allowed to appear on the simulation console.
|
|
|
|
The node <</cpus/cpu@0>> was explicitly specified to ensure that it had
|
|
been created before any interrupts were attached to it.
|
|
|
|
The program <<psim-test/hw-com/cat>> copies any characters on the serial
|
|
port's input (<</etc/passwd>>) to its output (the console).
|
|
Consequently, the aove program will display the contents of the file
|
|
<</etc/passwd>> on the screen.
|
|
|
|
|
|
BUGS
|
|
|
|
|
|
IEEE 1275 requires that a device on a PCI bus have, as its first reg
|
|
entry, the address of its configuration space registers. Currently,
|
|
this device does not even implement configuration registers.
|
|
|
|
This model does not attempt to model the '550's input and output fifos.
|
|
Instead, the input fifo is limited to a single character at a time,
|
|
while the output fifo is effectivly infinite. Consequently, unlike the
|
|
'550, this device will not discard output characters once a stream of 16
|
|
have been written to the data output register.
|
|
|
|
The input and output can only be taken from a file (or the current
|
|
terminal device). In the future, the <<com>> device should allow the
|
|
specification of other data streams (such as an xterm or TK window).
|
|
|
|
The input blocks if no data is available.
|
|
|
|
Interrupts have not been tested.
|
|
|
|
*/
|
|
|
|
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);
|
|
/* clean up */
|
|
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));
|
|
|
|
/* the fifo speed */
|
|
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);
|
|
|
|
/* the data source/sink */
|
|
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->output.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);
|
|
}
|
|
}
|
|
|
|
/* ready from the start */
|
|
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 /*port*/, 1 /*value*/, NULL, 0);
|
|
}
|
|
}
|
|
else /*!interrupting*/ {
|
|
if (com->interrupting)
|
|
device_interrupt_event(me, 0 /*port*/, 0 /*value*/, 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;
|
|
|
|
/* the divisor latch is special */
|
|
if (com->reg[3] & 0x8 && addr < 2) {
|
|
*val = com->dlab[addr];
|
|
return;
|
|
}
|
|
|
|
switch (addr) {
|
|
|
|
case 0:
|
|
/* fifo */
|
|
if (!com->modem.carrier)
|
|
*val = '\0';
|
|
if (com->input.ready) {
|
|
/* read the char in */
|
|
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;
|
|
}
|
|
/* setup for next read */
|
|
if (com->modem.carrier_changed) {
|
|
/* once lost carrier, never ready */
|
|
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 {
|
|
/* discard it? */
|
|
/* overflow input fifo? */
|
|
*val = '\0';
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
/* interrupt ident */
|
|
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:
|
|
/* line status */
|
|
*val = ((com->input.ready ? 0x1 : 0)
|
|
| (com->output.ready ? 0x60 : 0)
|
|
);
|
|
break;
|
|
|
|
case 6:
|
|
/* modem status */
|
|
*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;
|
|
|
|
/* the divisor latch is special */
|
|
if (com->reg[3] & 0x8 && addr < 2) {
|
|
com->dlab[addr] = val;
|
|
return;
|
|
}
|
|
|
|
switch (addr) {
|
|
|
|
case 0:
|
|
/* fifo */
|
|
if (com->output.file == NULL) {
|
|
sim_io_write_stdout(&val, 1);
|
|
}
|
|
else {
|
|
fwrite(&val, 1, 1, com->output.file);
|
|
}
|
|
/* setup for next write */
|
|
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;
|
|
}
|
|
|
|
|
|
/* instances of the hw_com device */
|
|
|
|
static void
|
|
hw_com_instance_delete(device_instance *instance)
|
|
{
|
|
/* nothing to delete, the hw_com is attached to the device */
|
|
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)
|
|
{
|
|
/* point an instance directly at the device */
|
|
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, }, /* address */
|
|
{ hw_com_io_read_buffer_callback,
|
|
hw_com_io_write_buffer_callback, },
|
|
{ NULL, }, /* DMA */
|
|
{ NULL, }, /* interrupt */
|
|
{ NULL, }, /* unit */
|
|
hw_com_create_instance,
|
|
};
|
|
|
|
|
|
static void *
|
|
hw_com_create(const char *name,
|
|
const device_unit *unit_address,
|
|
const char *args)
|
|
{
|
|
/* create the descriptor */
|
|
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 /* _HW_COM_C_ */
|