| /* 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" | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <stdlib.h> | |
| /* 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_ */ |