443 lines
10 KiB
C
443 lines
10 KiB
C
/* This file is part of the program psim.
|
|
|
|
Copyright (C) 1994-1995, 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 2 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, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
|
|
|
|
#ifndef _DEVICES_C_
|
|
#define _DEVICES_C_
|
|
|
|
#ifndef STATIC_INLINE_DEVICES
|
|
#define STATIC_INLINE_DEVICES STATIC_INLINE
|
|
#endif
|
|
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "basics.h"
|
|
#include "device_tree.h"
|
|
#include "devices.h"
|
|
#include "events.h"
|
|
|
|
#include "cpu.h" /* drats */
|
|
|
|
/* Helper functions */
|
|
|
|
STATIC_INLINE_DEVICES void
|
|
parse_device_address(char *name,
|
|
unsigned *base,
|
|
unsigned *flags)
|
|
{
|
|
/* extract the two arguments */
|
|
name = strchr(name, '@');
|
|
if (name == NULL)
|
|
error("missing address for device %s\n", name);
|
|
name++;
|
|
*base = strtol(name, &name, 0);
|
|
*flags = (*name == ','
|
|
? strtol(name+1, &name, 0)
|
|
: 0);
|
|
}
|
|
|
|
|
|
/* Simple console device:
|
|
|
|
Implements a simple text output device that is attached to stdout
|
|
of the process running the simulation. The devices has four
|
|
word registers:
|
|
|
|
0: read
|
|
4: read-status
|
|
8: write
|
|
c: write-status
|
|
|
|
Where a nonzero status register indicates that the device is ready
|
|
(input fifo contains a character or output fifo has space).
|
|
|
|
Illustrates: Mapping read/write to device operations onto actual
|
|
registers.
|
|
|
|
*/
|
|
|
|
typedef struct _console_buffer {
|
|
char buffer;
|
|
int status;
|
|
event_entry_tag event_tag;
|
|
} console_buffer;
|
|
|
|
typedef struct _console_device {
|
|
unsigned_word my_base_address;
|
|
int interrupt_delay;
|
|
console_buffer input;
|
|
console_buffer output;
|
|
} console_device;
|
|
|
|
typedef enum {
|
|
console_read_buffer = 0,
|
|
console_read_status = 4,
|
|
console_write_buffer = 8,
|
|
console_write_status = 12,
|
|
console_offset_mask = 0xc,
|
|
console_size = 16,
|
|
} console_offsets;
|
|
|
|
|
|
STATIC_INLINE_DEVICES unsigned64
|
|
console_read_callback(device_node *device,
|
|
unsigned_word base,
|
|
unsigned nr_bytes,
|
|
cpu *processor,
|
|
unsigned_word cia)
|
|
{
|
|
console_device *con = (console_device*)device->data;
|
|
TRACE(trace_console_device,
|
|
("device=0x%x, base=0x%x, nr_bytes=%d\n",
|
|
device, base, nr_bytes));
|
|
|
|
/* handle the request */
|
|
|
|
switch (base & console_offset_mask) {
|
|
|
|
case console_read_buffer:
|
|
return con->input.buffer;
|
|
|
|
case console_read_status:
|
|
{ /* check for input */
|
|
int flags;
|
|
int status;
|
|
/* get the old status */
|
|
flags = fcntl(0, F_GETFL, 0);
|
|
if (flags == -1) {
|
|
perror("console");
|
|
return 0;
|
|
}
|
|
/* temp, disable blocking IO */
|
|
status = fcntl(0, F_SETFL, flags | O_NDELAY);
|
|
if (status == -1) {
|
|
perror("console");
|
|
return 0;
|
|
}
|
|
/* try for input */
|
|
status = read(0, &con->input.buffer, 1);
|
|
if (status == 1) {
|
|
con->input.status = 1;
|
|
}
|
|
else {
|
|
con->input.status = 0;
|
|
}
|
|
/* return to regular vewing */
|
|
fcntl(0, F_SETFL, flags);
|
|
}
|
|
return con->input.status;
|
|
|
|
case console_write_buffer:
|
|
return con->output.buffer;
|
|
|
|
case console_write_status:
|
|
return con->output.status;
|
|
|
|
default:
|
|
error("console_read_callback() internal error\n");
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
STATIC_INLINE_DEVICES void
|
|
console_write_callback(device_node *device,
|
|
unsigned_word base,
|
|
unsigned nr_bytes,
|
|
unsigned64 val,
|
|
cpu *processor,
|
|
unsigned_word cia)
|
|
{
|
|
console_device *con = (console_device*)device->data;
|
|
|
|
TRACE(trace_console_device,
|
|
("device=0x%x, base=0x%x, nr_bytes=%d, val=0x%x\n",
|
|
device, base, nr_bytes, val));
|
|
|
|
/* check for bus error */
|
|
if (base & 0x3) {
|
|
error("%s - misaligned base address, base=0x%x, nr_bytes=%d\n",
|
|
"console_write_callback", base, nr_bytes);
|
|
}
|
|
|
|
switch (base & console_offset_mask) {
|
|
case console_read_buffer: con->input.buffer = val; break;
|
|
case console_read_status: con->input.status = val; break;
|
|
case console_write_buffer:
|
|
TRACE(trace_console_device,
|
|
("<%c:%d>", val, val));
|
|
printf_filtered("%c", val);
|
|
con->output.buffer = val;
|
|
con->output.status = 1;
|
|
break;
|
|
case console_write_status:
|
|
con->output.status = val;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
static device_callbacks console_callbacks = {
|
|
console_read_callback,
|
|
console_write_callback,
|
|
};
|
|
|
|
STATIC_INLINE_DEVICES device_node *
|
|
console_create(device_node *parent,
|
|
char *name)
|
|
{
|
|
device_node *device;
|
|
unsigned address_base;
|
|
unsigned address_flags;
|
|
|
|
/* create the descriptor */
|
|
console_device *console = ZALLOC(console_device);
|
|
|
|
/* extract the two arguments */
|
|
parse_device_address(name, &address_base, &address_flags);
|
|
|
|
/* fill in the details */
|
|
console->my_base_address = address_base;
|
|
console->interrupt_delay = address_flags;
|
|
console->output.status = 1;
|
|
console->output.buffer = '\0';
|
|
console->input.status = 0;
|
|
console->input.buffer = '\0';
|
|
|
|
/* insert into the device tree along with its address info */
|
|
device = device_node_create(parent, name, sequential_device,
|
|
&console_callbacks, console);
|
|
device_node_add_address(device,
|
|
address_base,
|
|
console_size,
|
|
device_is_read_write_exec,
|
|
NULL);
|
|
|
|
return device;
|
|
}
|
|
|
|
static device_descriptor console_descriptor = {
|
|
"console",
|
|
console_create,
|
|
};
|
|
|
|
|
|
/* ICU device:
|
|
|
|
Single 4 byte register. Read returns processor number. Write
|
|
interrupts specified processor.
|
|
|
|
Illustrates passing of events to parent device. Passing of
|
|
interrupts to parent bus.
|
|
|
|
NB: For the sake of illustrating the passing of interrupts. This
|
|
device doesn't pass interrupt events to its parent. Instead it
|
|
passes them back to its self. */
|
|
|
|
STATIC_INLINE_DEVICES unsigned64
|
|
icu_read_callback(device_node *device,
|
|
unsigned_word base,
|
|
unsigned nr_bytes,
|
|
cpu *processor,
|
|
unsigned_word cia)
|
|
{
|
|
TRACE(trace_icu_device,
|
|
("device=0x%x, base=0x%x, nr_bytes=%d\n",
|
|
device, base, nr_bytes));
|
|
return cpu_nr(processor);
|
|
}
|
|
|
|
STATIC_INLINE_DEVICES void
|
|
icu_write_callback(device_node *device,
|
|
unsigned_word base,
|
|
unsigned nr_bytes,
|
|
unsigned64 val,
|
|
cpu *processor,
|
|
unsigned_word cia)
|
|
{
|
|
psim *system = cpu_system(processor);
|
|
device_node *parent = device; /* NB: normally would be device->parent */
|
|
TRACE(trace_icu_device,
|
|
("device=0x%x, base=0x%x, nr_bytes=%d, val=0x%x\n",
|
|
device, base, nr_bytes, val));
|
|
/* tell the parent device that the interrupt lines have changed.
|
|
For this fake ICU. The interrupt lines just indicate the cpu to
|
|
interrupt next */
|
|
parent->callbacks->interrupt_callback(parent, val, device, processor, cia);
|
|
}
|
|
|
|
STATIC_INLINE_DEVICES void
|
|
icu_do_interrupt(event_queue *queue,
|
|
void *data)
|
|
{
|
|
cpu *target = (cpu*)data;
|
|
/* try to interrupt the processor. If the attempt fails, try again
|
|
on the next tick */
|
|
if (!external_interrupt(target))
|
|
event_queue_schedule(queue, 1, icu_do_interrupt, target);
|
|
}
|
|
|
|
STATIC_INLINE_DEVICES void
|
|
icu_interrupt_callback(device_node *me,
|
|
int interrupt_status,
|
|
device_node *device,
|
|
cpu *processor,
|
|
unsigned_word cia)
|
|
{
|
|
/* the interrupt controler can't interrupt a cpu at any time.
|
|
Rather it must synchronize with the system clock before
|
|
performing an interrupt on the given processor */
|
|
psim *system = cpu_system(processor);
|
|
cpu *target = psim_cpu(system, interrupt_status);
|
|
if (target != NULL) {
|
|
event_queue *events = cpu_event_queue(target);
|
|
event_queue_schedule(events, 1, icu_do_interrupt, target);
|
|
}
|
|
}
|
|
|
|
static device_callbacks icu_callbacks = {
|
|
icu_read_callback,
|
|
icu_write_callback,
|
|
icu_interrupt_callback,
|
|
};
|
|
|
|
STATIC_INLINE_DEVICES device_node *
|
|
icu_create(device_node *parent,
|
|
char *name)
|
|
{
|
|
device_node *device;
|
|
unsigned address_base;
|
|
unsigned address_flags;
|
|
|
|
/* extract the two arguments */
|
|
parse_device_address(name, &address_base, &address_flags);
|
|
|
|
/* insert into the device tree along with its address info */
|
|
device = device_node_create(parent, name, sequential_device,
|
|
&icu_callbacks, 0);
|
|
device_node_add_address(device,
|
|
address_base,
|
|
4,
|
|
device_is_read_write_exec,
|
|
NULL);
|
|
|
|
return device;
|
|
}
|
|
|
|
static device_descriptor icu_descriptor = {
|
|
"icu",
|
|
icu_create,
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/* HALT device:
|
|
|
|
With real hardware, the processor operation is normally terminated
|
|
through a reset. This device illustrates how a reset device could
|
|
be attached to an address */
|
|
|
|
STATIC_INLINE_DEVICES unsigned64
|
|
halt_read_callback(device_node *device,
|
|
unsigned_word base,
|
|
unsigned nr_bytes,
|
|
cpu *processor,
|
|
unsigned_word cia)
|
|
{
|
|
cpu_halt(processor, cia, was_exited, 0);
|
|
return 0;
|
|
}
|
|
|
|
STATIC_INLINE_DEVICES void
|
|
halt_write_callback(device_node *device,
|
|
unsigned_word base,
|
|
unsigned nr_bytes,
|
|
unsigned64 val,
|
|
cpu *processor,
|
|
unsigned_word cia)
|
|
{
|
|
cpu_halt(processor, cia, was_exited, 0);
|
|
}
|
|
|
|
|
|
static device_callbacks halt_callbacks = {
|
|
halt_read_callback,
|
|
halt_write_callback,
|
|
};
|
|
|
|
STATIC_INLINE_DEVICES device_node *
|
|
halt_create(device_node *parent,
|
|
char *name)
|
|
{
|
|
device_node *device;
|
|
unsigned address_base;
|
|
unsigned address_flags;
|
|
|
|
parse_device_address(name, &address_base, &address_flags);
|
|
device = device_node_create(parent, name, other_device,
|
|
&halt_callbacks, NULL);
|
|
device_node_add_address(device,
|
|
address_base,
|
|
4,
|
|
device_is_read_write_exec,
|
|
NULL);
|
|
return device;
|
|
}
|
|
|
|
static device_descriptor halt_descriptor = {
|
|
"halt",
|
|
halt_create,
|
|
};
|
|
|
|
|
|
static device_descriptor *devices[] = {
|
|
&console_descriptor,
|
|
&halt_descriptor,
|
|
&icu_descriptor,
|
|
NULL,
|
|
};
|
|
|
|
|
|
INLINE_DEVICES device_descriptor *
|
|
find_device_descriptor(char *name)
|
|
{
|
|
device_descriptor **device;
|
|
int name_len;
|
|
char *chp;
|
|
chp = strchr(name, '@');
|
|
name_len = (chp == NULL ? strlen(name) : chp - name);
|
|
for (device = devices; *device != NULL; device++) {
|
|
if (strncmp(name, (*device)->name, name_len) == 0
|
|
&& ((*device)->name[name_len] == '\0'
|
|
|| (*device)->name[name_len] == '@'))
|
|
return *device;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
#endif /* _DEVICES_C_ */
|