Subject: pkg/35305: TME sun3 emulator incorrectly emulates ethernet obie & i825x6 hardware
To: None <pkg-manager@netbsd.org, gnats-admin@netbsd.org,>
From: None <sigmfsk@aol.com>
List: pkgsrc-bugs
Date: 12/21/2006 16:05:00
>Number:         35305
>Category:       pkg
>Synopsis:       TME sun3 emulator incorrectly emulates ethernet obie & i825x6 hardware
>Confidential:   no
>Severity:       critical
>Priority:       high
>Responsible:    pkg-manager
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Thu Dec 21 16:05:00 +0000 2006
>Originator:     Arthur Townsend
>Release:        3.1
>Organization:
>Environment:
3.1 for i386
>Description:
TME ethernet hardware emulation is sufficient for NetBSD to run inside the emulator and use the ethernet.  But TME does not emulate the ethernet hardware sufficiently for SunOS to use ethernet inside TME.  Not only can it not use ethernet, if SunOS attempts to initialize ie0, the device driver fails to initialize and the operating system console overflows with IPL 3 errors.
>How-To-Repeat:
Enable ie0 in the carrera startup script, boot SunOS 4.1.1 inside the emulator, then inside SunOS enter: ifconfig ie0 192.168.2.11 netmask 255.255.255.0.
>Fix:
The following patches allow NetBSD and SunOS to fully use ethernet inside the emulator.

The following patch corrects the size field in the transmit buffer descriptor.  Bit 0x4000 is don't-care (per i825x6 data sheet).  NetBSD OS never set the bit, so it was always 0, so TME worked fine in this situation.  But SunOS set the don't care bit, and TME considered it part of the size field and therefore used a size 16384 bytes larger than the actual size.

--- i825x6reg.h.orig    2006-12-13 08:27:48.000000000 -0500
+++ i825x6reg.h 2006-12-13 08:28:27.000000000 -0500
@@ -156,7 +156,7 @@
 /* the i82586 and 32-bit segmented i82586 Transmit Buffer: */
 #define TME_I82586_TBD_EOF_SIZE                (0)
 #define  TME_I82586_TBD_EOF             (0x8000)
-#define  TME_I82586_TBD_SIZE_MASK       (0x7fff)
+#define  TME_I82586_TBD_SIZE_MASK       (0x3fff)
 #define TME_I82586_TBD_TBD_OFFSET      (2)
 #define TME_I82586_TBD_TB_ADDRESS      (4)


The SunOS ethernet problem was difficult to track down, in part because of no error checking of the actual write to the bpf device (so no SunOS ethernet packets made it out of the host, and no errors were recorded).  The following patch ensures that the packet to write is of the correct size, and it ensures that the number of bytes actually written to the ethernet device is equal to the number of bytes commanded.

--- bsd-bpf.c.orig2     2006-12-13 08:07:09.000000000 -0500
+++ bsd-bpf.c   2006-12-13 11:38:27.000000000 -0500
@@ -292,7 +292,7 @@
   struct tme_ethernet_connection *conn_eth;
   int callouts, later_callouts;
   unsigned int ctrl;
-  int rc;
+  int rc, status;
   tme_ethernet_fid_t frame_id;
   struct tme_ethernet_frame_chunk frame_chunk_buffer;
   tme_uint8_t frame[TME_ETHERNET_FRAME_MAX];
@@ -372,6 +372,9 @@
                &frame_id,
                &frame_chunk_buffer,
                TME_ETHERNET_READ_NEXT)));
+
+      /* ensure don't get back bad length from i825x6 */
+      assert(rc <= sizeof(frame));
       
       /* lock the mutex: */
       tme_mutex_lock(&bpf->tme_bsd_bpf_mutex);
@@ -380,7 +383,11 @@
       if (rc > 0) {
 
        /* do the write: */
-       tme_thread_write(bpf->tme_bsd_bpf_fd, frame, rc);
+       status = tme_thread_write(bpf->tme_bsd_bpf_fd, frame, rc);
+
+        /* assert if write failed.  also assert if we wrote less bytes
+           than we commanded */
+        assert (status == rc);
 
        /* mark that we need to loop to callout to read more frames: */
        bpf->tme_bsd_bpf_callout_flags |= TME_BSD_BPF_CALLOUT_READ;


The following is the meat of the ethernet fix.  Comments in the patch describe its changes:
--- sun-obie.c.orig     2005-02-17 07:37:25.000000000 -0500
+++ sun-obie.c  2006-12-19 05:34:13.000000000 -0500
@@ -78,7 +78,6 @@
 #define TME_SUN_OBIE_CALLOUT_RUNNING   TME_BIT(0)
 #define TME_SUN_OBIE_CALLOUTS_MASK     (-2)
 #define  TME_SUN_OBIE_CALLOUT_SIGNALS  TME_BIT(1)
-#define         TME_SUN_OBIE_CALLOUT_INT       TME_BIT(2)
 
 /* structures: */
 
@@ -106,8 +105,15 @@
   /* the callout flags: */
   int tme_sun_obie_callout_flags;
 
-  /* if our interrupt line is currently asserted: */
-  int tme_sun_obie_int_asserted;
+  /* the obie CSR interrupt enable bit has been set since powerup */
+  int csr_ie_has_been_set;
+
+  /* the i825x6 interrupt is currently active to the obie */
+  int i825x6_interrupt_is_active;
+
+  /* the obie interrupt (forward of the i825x6 interrupt) is currently active
+     to the bus */
+  int obie_interrupt_is_active;
 
   /* it's easiest to just model the board registers as a chunk of memory: */
   tme_uint8_t tme_sun_obie_regs[TME_SUN_OBIE_SIZ_REGS];
@@ -137,17 +143,67 @@
 static const struct tme_bus_signals _tme_sun_obie_bus_signals_generic = TME_BUS_SIGNALS_GENERIC;
 static const struct tme_bus_signals _tme_sun_obie_bus_signals_i825x6 = TME_BUS_SIGNALS_I825X6;
 
+
+/* TME originally never set or cleared TME_SUN_OBIE_CSR_INTR in the obie CSR.
+ * This worked fine for running NetBSD inside the emulator, as the NetBSD ie0
+ * driver never checked that bit.  But SunOS 4.1.1 does.
+ * Following is the logic that makes NetBSD and SunOS work inside the
+ * emulator (and therefore is likely close to the logic of the actual OBIE
+ * hardware: The Sun-2 MultiBus Ethernet Controller, part 501-1004).
+ *
+ *  When i825x6 chip asserts/deasserts an interrupt to OBIE:
+ *     1) set or clear TME_SUN_OBIE_CSR_INTR as appropriate
+ *           and, if the TME_SUN_OBIE_IE (interrupt enable) has ever been set:
+ *     2) pass along signal to main bus.
+ *
+ * Note:
+ *  When correcting the TME code to work with SunOS, I originally only
+ *  forwarded the interrupt when IE was active, but SunOS fails five minutes
+ *  after bootup (complaining about level 3 interrupt) with this arrangement.
+ *
+ *  So the actual use of the IE bit remains a mystery.  It is likely sufficient
+ *  to ignore the IE bit and always pass along the i825x6 interrupt to the main
+ *  bus.   But the current "has been set" IE logic allows NetBSD and SunOS to
+ *  fully run ethernet correctly inside the emulator, and that's good enough
+ *  for me.  ART
+ */
+
+static void
+_possibly_set_csr_intr_bit(struct tme_sun_obie *sun_obie)
+{
+  tme_uint16_t csr;
+
+  csr = TME_SUN_OBIE_CSR_GET(sun_obie);
+  sun_obie->csr_ie_has_been_set |=
+                ((csr & TME_SUN_OBIE_CSR_IE) == TME_SUN_OBIE_CSR_IE);
+
+  /* if a change in interrupt status */
+  if (sun_obie->obie_interrupt_is_active != sun_obie->i825x6_interrupt_is_active) {
+
+    /* transition to assert */
+    if (!sun_obie->obie_interrupt_is_active) {
+      csr = (csr | TME_SUN_OBIE_CSR_INTR);
+    }
+
+    /* transition to deassert */
+    else {
+      csr = (csr & ~TME_SUN_OBIE_CSR_INTR);
+    }
+
+    TME_SUN_OBIE_CSR_PUT(sun_obie, csr);
+    sun_obie->obie_interrupt_is_active = sun_obie->i825x6_interrupt_is_active;
+  }
+}
+
 /* the sun_obie callout function.  it must be called with the mutex locked: */
 static void
 _tme_sun_obie_callout(struct tme_sun_obie *sun_obie, int new_callouts)
 {
   struct tme_bus_connection *conn_i825x6;
-  struct tme_bus_connection *conn_bus;
   tme_uint16_t csr, csr_diff;
   unsigned int signal, level;
   int callouts, later_callouts;
   int rc;
-  int int_asserted;
   
   /* add in any new callouts: */
   sun_obie->tme_sun_obie_callout_flags |= new_callouts;
@@ -176,6 +232,8 @@
 
       /* get the current CSR value: */
       csr = TME_SUN_OBIE_CSR_GET(sun_obie);
+      sun_obie->csr_ie_has_been_set |=
+                    ((csr & TME_SUN_OBIE_CSR_IE) == TME_SUN_OBIE_CSR_IE);
 
       /* get the next signal to call out to the i825x6: */
       csr_diff = ((csr
@@ -230,7 +288,10 @@
                 (conn_i825x6,
                  signal | level))
              : TME_OK);
-
+
+        /* possibly update status of INTR bit in CSR */
+        _possibly_set_csr_intr_bit(sun_obie);
+
        /* lock the mutex: */
        tme_mutex_lock(&sun_obie->tme_sun_obie_mutex);
       
@@ -254,55 +315,6 @@
        }
       }
     }
-
-    /* if we need to call out a possible change to our interrupt
-       signal: */
-    if (callouts & TME_SUN_OBIE_CALLOUT_INT) {
-
-      /* get the current CSR value: */
-      csr = TME_SUN_OBIE_CSR_GET(sun_obie);
-
-      /* see if the interrupt signal should be asserted or negated: */
-      int_asserted = ((csr & (TME_SUN_OBIE_CSR_IE
-                             | TME_SUN_OBIE_CSR_INTR))
-                     == (TME_SUN_OBIE_CSR_IE
-                         | TME_SUN_OBIE_CSR_INTR));
-
-      /* if the interrupt signal doesn't already have the right state: */
-      if (!int_asserted != !sun_obie->tme_sun_obie_int_asserted) {
-
-       /* get our bus connection: */
-       conn_bus = sun_obie->tme_sun_obie_conn_regs;
-
-       /* unlock our mutex: */
-       tme_mutex_unlock(&sun_obie->tme_sun_obie_mutex);
-
-       /* call out the bus interrupt signal edge: */
-       rc = (conn_bus != NULL
-             ? ((*conn_bus->tme_bus_signal)
-                (conn_bus,
-                 TME_BUS_SIGNAL_INT_UNSPEC
-                 | (int_asserted
-                    ? TME_BUS_SIGNAL_LEVEL_ASSERTED
-                    : TME_BUS_SIGNAL_LEVEL_NEGATED)))
-             : TME_OK);
-
-       /* lock our mutex: */
-       tme_mutex_lock(&sun_obie->tme_sun_obie_mutex);
-
-       /* if this callout was successful, note the new state of the
-          interrupt signal: */
-       if (rc == TME_OK) {
-         sun_obie->tme_sun_obie_int_asserted = int_asserted;
-       }
-
-       /* otherwise, remember that at some later time this callout
-          should be attempted again: */
-       else {
-         later_callouts |= TME_SUN_OBIE_CALLOUT_INT;
-       }
-      }
-    }
   }
   
   /* put in any later callouts, and clear that callouts are running: */
@@ -321,6 +333,9 @@
   /* recover our data structure: */
   sun_obie = (struct tme_sun_obie *) _sun_obie;
 
+  /* possibly update status of INTR bit in CSR */
+  _possibly_set_csr_intr_bit(sun_obie);
+
   /* assume we won't need any new callouts: */
   new_callouts = 0;
 
@@ -329,6 +344,8 @@
 
   /* get the previous CSR value: */
   csr_old = TME_SUN_OBIE_CSR_GET(sun_obie);
+  sun_obie->csr_ie_has_been_set |=
+                ((csr_old & TME_SUN_OBIE_CSR_IE) == TME_SUN_OBIE_CSR_IE);
 
   /* run the cycle: */
   tme_bus_cycle_xfer_memory(cycle_init, 
@@ -354,12 +371,6 @@
     new_callouts |= TME_SUN_OBIE_CALLOUT_SIGNALS;
   }
   
-  /* if this is an interrupt mask change, possibly call out an
-     interrupt signal change to the bus: */
-  if (csr_diff & TME_SUN_OBIE_CSR_IE) {
-    new_callouts |= TME_SUN_OBIE_CALLOUT_INT;
-  }
-  
 #ifndef TME_NO_LOG
   if (csr_new != sun_obie->tme_sun_obie_last_log_csr) {
     sun_obie->tme_sun_obie_last_log_csr = csr_new;
@@ -397,6 +408,15 @@
   /* recover our data structures: */
   sun_obie = conn_bus->tme_bus_connection.tme_connection_element->tme_element_private;
 
+  /* save this information so we'll know it later */
+  sun_obie->i825x6_interrupt_is_active =
+    ((signal & TME_BUS_SIGNAL_LEVEL_ASSERTED) == TME_BUS_SIGNAL_LEVEL_ASSERTED);
+
+  /* return now if interrupt enable has never been set in CSR */
+  if (!sun_obie->csr_ie_has_been_set) {
+    return (TME_OK);
+  }
+
   /* pass the i825x6's signal through to the obio bus: */
   conn_bus = sun_obie->tme_sun_obie_conn_regs;
   return (conn_bus != NULL


The following patch:
 1) adds a comment to follow the logic in the datasheet
 2) allows i825x6 diagnostic mode to return success instead of abort (needed for SunOS ie driver)
 3) corrects the problem of not calculating total packet size with multiple transmit buffers (NetBSD apparently never uses multiple transmit buffers, but SunOS does).  This patch, changing variable "rc" to "tot_length" appears, on the face of things, to change nothing, but is actually critical, because the macro TME_I825X6_READ16, which uses the macro TME_I825X6_READ overwrites the "rc" variable.

--- i825x6.c.orig2      2006-12-13 08:29:22.000000000 -0500
+++ i825x6.c    2006-12-13 08:31:27.000000000 -0500
@@ -34,6 +34,7 @@
  */
 
 #include <tme/common.h>
+
 _TME_RCSID("$Id: i825x6.c,v 1.5 2005/05/11 00:12:24 fredette Exp $");
 
 /* includes: */
@@ -801,6 +802,9 @@
         | TME_I825X6_SCB_CUS_IDLE
         | TME_I825X6_SCB_RUS_IDLE);
     
+    /* clears the SCB command word": */
+    /* [this is done at end of this routine] */
+
     /* "The 82596 ... sends an interrupt to the CPU": */
     i825x6->tme_i825x6_callout_flags = TME_I825X6_CALLOUTS_RUNNING | TME_I825X6_CALLOUT_INT;
   }
@@ -1259,8 +1263,10 @@
     break;
 
   case TME_I825X6_CB_CMD_DUMP:
-  case TME_I825X6_CB_CMD_DIAGNOSE:
     abort();
+
+  case TME_I825X6_CB_CMD_DIAGNOSE:
+    break;
   }
   
   /* add to the callouts and return the current status: */
@@ -1761,7 +1767,7 @@
         | (int_asserted
            ? TME_BUS_SIGNAL_LEVEL_ASSERTED
            : TME_BUS_SIGNAL_LEVEL_NEGATED));
-      
+
       /* lock our mutex: */
       tme_mutex_lock(&i825x6->tme_i825x6_mutex);
       
@@ -1899,7 +1905,7 @@
   tme_uint16_t c_b_ok_a;
   tme_uint16_t value16;
   tme_uint32_t value32;
-  int rc, err;
+  int rc, err, tot_length;
 
   /* recover our data structures: */
   i825x6 = conn_eth->tme_ethernet_connection.tme_connection_element->tme_element_private;
@@ -1920,7 +1926,7 @@
   tme_mutex_lock(&i825x6->tme_i825x6_mutex);
 
   /* assume that we will have no packet to transmit: */
-  rc = 0;
+  tot_length = 0;
 
   /* if we have a packet to transmit: */
   if ((i825x6->tme_i825x6_el_s_i_cmd
@@ -1935,10 +1941,10 @@
 #define CHUNKS_DMA_TX(addr, size)                                             \
       err = _tme_i825x6_chunks_dma_tx(i825x6, frame_chunks, (addr), (size));  \
       if (err != TME_OK) break;                                               \
-      rc += size
+      tot_length += size
 #define CHUNKS_MEM_TX(data, size)                                             \
       _tme_i825x6_chunks_mem_tx(frame_chunks, (data), (size));                \
-      rc += size
+      tot_length += size
 
       /* if AL-LOC is set to zero, add the Ethernet/802.3 MAC header: */
       if (i825x6->tme_i825x6_al_loc == 0) {
@@ -1966,6 +1972,7 @@
        TME_I825X6_READ16((tbd_address
                           + TME_I82586_TBD_EOF_SIZE),
                          eof_size);
+
        TME_I82586_READ24((tbd_address
                           + TME_I82586_TBD_TB_ADDRESS),
                          tb_address);
@@ -1974,7 +1981,7 @@
        CHUNKS_DMA_TX(tb_address,
                      (eof_size & TME_I82586_TBD_SIZE_MASK));
 
-       /* the next transmit buffer: */
+       /* get out if no next transmit buffer: */
        if (eof_size & TME_I82586_TBD_EOF) {
          break;
        }
@@ -2023,7 +2030,7 @@
   tme_mutex_unlock(&i825x6->tme_i825x6_mutex);
 
   /* done: */
-  return (rc);
+  return (tot_length);
 }
 
 /* this makes a new Ethernet connection: */