123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559 |
- /* 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_ */
|