| /*  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> | 
 | #ifdef HAVE_UNISTD_H | 
 | #include <unistd.h> | 
 | #endif | 
 | #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_ */ |