* elf64-ppc.c (dec_dynrel_count): Don't error when elf_gc_sweep_symbol
[external/binutils.git] / sim / ppc / hw_com.c
index 1153e1f..648c4df 100644 (file)
+/*  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 immediatly 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 immediatly 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_write_buffer_callback(device *me,
-                                const void *source,
-                                int space,
-                                unsigned_word addr,
-                                unsigned nr_bytes,
-                                cpu *processor,
-                                unsigned_word cia)
+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 *hw_com = (hw_com_device*)device_data(me);
-  unsigned_1 val = *(unsigned_1*)source;
+  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);
+}
 
-  switch ((int)addr & hw_com_offset_mask) {
+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:
-    error("hw_com_write_callback() internal error\n");
+    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;
 }
 
-#endif /* _HW_COM_ */
+
+/* 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_ */