Palacios Public Git Repository

To checkout Palacios execute

  git clone http://v3vee.org/palacios/palacios.web/palacios.git
This will give you the master branch. You probably want the devel branch or one of the release branches. To switch to the devel branch, simply execute
  cd palacios
  git checkout --track -b devel origin/devel
The other branches are similar.


Major rewrite of serial port device
Peter Dinda [Fri, 2 Aug 2013 21:15:26 +0000 (16:15 -0500)]
- 16450 and 16550A behaviors
- internal interrupt register, plus interrupt priority encoding
- numerous other fixes

palacios/src/devices/serial.c

index 687b357..85eecc8 100644 (file)
@@ -12,7 +12,8 @@
  * All rights reserved.
  *
  * Author: Rumou Duan <duanrumou@gmail.com>
- *             Lei Xia <lxia@northwestern.edu>
+ *         Lei Xia <lxia@northwestern.edu>
+ &         Peter Dinda <pdinda@northwestern.edu>
  *
  * This is free software.  You are permitted to use,
  * redistribute, and modify it as specified in the file "V3VEE_LICENSE".
 #define PrintDebug(fmt, args...)
 #endif
 
+/*
+
+  This is an implementation of a 16450 and 16550A-compatible UART (fifo-capable), based on
+  the TI 16550D spec (http://www.ti.com/lit/ds/symlink/pc16550d.pdf) and the Altera
+  16450 spec (ftp://ftp.altera.com/pub/lit_req/document/ds/ds16450.pdf)
+
+*/
+
+// This is not yet tested - enable at your own risk!
+#define BE_16550A  0  
+
+
 
 #define COM1_DATA_PORT           0x3f8
 #define COM1_IRQ_ENABLE_PORT     0x3f9
 
 
 // Interrupt IDs (in priority order, highest is first)
-#define STATUS_IRQ_LSR_OE_SET   0x3
-#define STATUS_IRQ_LSR_PE_SET   0x3
-#define STATUS_IRQ_LSR_FE_SET   0x3
-#define STATUS_IRQ_LSR_BI_SET   0x3
+#define RX_IRQ_STATUS           0x3
 #define RX_IRQ_DR               0x2
 #define RX_IRQ_TRIGGER_LEVEL    0x2
 #define FIFO_IRQ                0x6
 #define TX_IRQ_THRE             0x1
-#define MODEL_IRQ_DELTA_SET     0x0
+#define MODEM_IRQ_DELTA_SET     0x0
 
 //COMs IRQ ID
 #define COM1_IRQ  0x4
 
 //initial value for registers
 
-#define  IER_INIT_VAL 0x3
-//receive data available interrupt and THRE interrupt are enabled
-#define  IIR_INIT_VAL 0x1
-//No Pending Interrupt bit is set.
-#define  FCR_INIT_VAL 0xc0
-//fifo control register is set to 0
-#define  LCR_INIT_VAL 0x3
+// Per TI spec: http://www.ti.com/lit/ds/symlink/pc16550d.pdf
+// all interrupt enables off
+#define  IER_INIT_VAL 0x0 
+// no pending interrupts (active low flag)
+#define  IIR_INIT_VAL 0x1 
+// fifos disabled (comes up in 16450 mode)
+#define  FCR_INIT_VAL 0x0
+// 5 bits, 1 stop bit, no parity, no break, divisor latch off
+#define  LCR_INIT_VAL 0x0
+// not DTR, not RTS, not OUT1, not OUT2, not loopback
 #define  MCR_INIT_VAL 0x0
+// tx registers empty, no errors, no data available
 #define  LSR_INIT_VAL 0x60
+// not cts, not dsr, not ring, etc.
 #define  MSR_INIT_VAL 0x0
+// baud rate 115200 (divisor is 1)
 #define  DLL_INIT_VAL 0x1
 #define  DLM_INIT_VAL 0x0
 
-
-
 //receiver buffer register
 struct rbr_register {
     uint8_t data;
@@ -138,7 +152,7 @@ struct ier_register {
     union {
        uint8_t val;
        struct {
-           uint8_t erbfi   : 1;   // Enable Receiver Buffer full interrupt
+           uint8_t erbfi   : 1;  // Enable Receiver Buffer full interrupt
            uint8_t etbei   : 1;  // Enable Transmit buffer empty interrupt
            uint8_t elsi    : 1;  // Enable Line Status Interrupt
            uint8_t edssi   : 1;  // Enable Delta Status signals interrupt
@@ -186,7 +200,7 @@ struct lcr_register {
            uint8_t stop_bits      : 1;  // Stop Bit select
            uint8_t parity_enable  : 1;  // Enable parity
            uint8_t even_sel       : 1;  // Even Parity Select
-           uint8_t stick_parity   : 1;  // Stick Parity Select
+           uint8_t stick_parity   : 1;  // Stick Parity Select (e.g., force mark or space)
            uint8_t sbr            : 1;  // Set Break
            uint8_t dlab           : 1;  // Divisor latch access bit
        } __attribute__((packed));
@@ -215,7 +229,7 @@ struct lsr_register {
     union {
        uint8_t val;
        struct {
-           uint8_t dr      : 1;  // data ready
+           uint8_t dr      : 1;   // data ready
            uint8_t oe       : 1;  // Overrun error
            uint8_t pe       : 1;  // Parity Error
            uint8_t fe       : 1;  // Framing Error
@@ -258,7 +272,7 @@ struct dll_register {
 struct dlm_register {
     uint8_t data;
 };
-#define SERIAL_BUF_LEN 128
+#define SERIAL_BUF_LEN 16   
 
 struct serial_buffer {
     int head; // most recent data
@@ -286,6 +300,29 @@ struct serial_port {
     struct serial_buffer rx_buffer;
     uint_t irq_number;
 
+    /*
+      Multiple interrupt conditions can be active simultaneously. 
+      The chip does a priority encoding to determine which one shows up in the iid
+
+      INT_RX_STAT      1      line status change   cleared by LSR read
+      INT_RX_DATA      2      rx data available    cleared by RBR read or when FIFO below trigger level
+      INT_RX_TIMEOUT   2      rx fifo lonely       cleared by RBR read
+      INT_TX_EMPTY     3      tx space available   cleared by IIR read (if in iid) or write to THR
+      INT_MD_STAT      4      modem status change  cleared by MSR read
+
+    */
+#define INT_RX_STAT     1     
+#define INT_RX_DATA     2
+#define INT_RX_TIMEOUT  4
+#define INT_TX_EMPTY    8
+#define INT_MD_STAT     16
+#define INT_MASK        0x1f
+// whether we currently have an IRQ raised
+#define IRQ_RAISED_MASK  128
+
+    uint8_t int_state;  // all currently signaled interrupts, not just the one being delivered
+
+    v3_lock_t  lock;
 
     void * backend_data;
     struct v3_dev_char_ops * ops;
@@ -302,15 +339,19 @@ struct serial_state {
 
 static struct serial_port * get_com_from_port(struct serial_state * serial, uint16_t port) {
     if ((port >= COM1_DATA_PORT) && (port <= COM1_SCRATCH_PORT)) {
+        PrintDebug(VM_NONE, VCORE_NONE, "UART: COM1\n");
        return &(serial->coms[0]);
     } else if ((port >= COM2_DATA_PORT) && (port <= COM2_SCRATCH_PORT)) {
+        PrintDebug(VM_NONE, VCORE_NONE, "UART: COM2\n");
        return &(serial->coms[1]);
     } else if ((port >= COM3_DATA_PORT) && (port <= COM3_SCRATCH_PORT)) {
+        PrintDebug(VM_NONE, VCORE_NONE, "UART: COM3\n");
        return &(serial->coms[2]);
     } else if ((port >= COM4_DATA_PORT) && (port <= COM4_SCRATCH_PORT)) {
+        PrintDebug(VM_NONE, VCORE_NONE, "UART: COM4\n");
        return &(serial->coms[3]);
     } else {
-       PrintError(VM_NONE, VCORE_NONE, "Error: Could not find serial port associated with IO port %d\n", port);
+       PrintError(VM_NONE, VCORE_NONE, "UART: Error: Could not find serial port associated with IO port %d\n", port);
        return NULL;
     }
 }
@@ -331,6 +372,7 @@ static inline bool receive_buffer_trigger(int number, int trigger_number) {
     return false;
 }
 
+
 static int getNumber(struct serial_buffer * buf) {
     int number = buf->head - buf->tail;
   
@@ -343,156 +385,391 @@ static int getNumber(struct serial_buffer * buf) {
     }
 }
 
-static int updateIRQ(struct v3_vm_info * vm, struct serial_port * com) {
-    
-    if ( (com->ier.erbfi == 0x1) && 
-        (receive_buffer_trigger( getNumber(&(com->rx_buffer)), com->fcr.rx_trigger)) ) {
 
-       PrintDebug(vm, VCORE_NONE, "UART: receive buffer interrupt(trigger level reached)\n");
+                                                            
 
-       com->iir.iid = RX_IRQ_TRIGGER_LEVEL;
-       v3_raise_irq(vm, com->irq_number);
-    }
-    
-    if ( (com->iir.iid == RX_IRQ_TRIGGER_LEVEL) && 
-        (!(receive_buffer_trigger( getNumber(&(com->rx_buffer)), com->fcr.rx_trigger))) ) {
-
-       com->iir.iid = 0x0;   //reset interrupt identification register
-       com->iir.pending = 0x1;
-    }
-    
-    if ( (com->iir.iid == TX_IRQ_THRE) && 
-        (getNumber(&(com->tx_buffer)) == SERIAL_BUF_LEN)) {
-
-       com->iir.iid = 0x0; //reset interrupt identification register
-       com->iir.pending = 0x1;
-
-    } else if ( (com->ier.etbei == 0x1) && 
-               (getNumber(&(com->tx_buffer)) != SERIAL_BUF_LEN )) {
-       
-       PrintDebug(vm, VCORE_NONE, "UART: transmit buffer interrupt(buffer not full)\n");
+static inline int can_queue(struct serial_buffer * buf) 
+{
+  return !(buf->full);
+}
 
-       com->iir.iid = TX_IRQ_THRE;
-       com->iir.pending = 0;
+static inline int can_dequeue(struct serial_buffer * buf) 
+{
+  return !( (buf->head == buf->tail) && (buf->full != 1) );
 
-       v3_raise_irq(vm, com->irq_number);
-    }
+}
 
-    return 1;
+static inline int peek_queue(struct serial_buffer *buf, uint8_t *data)
+{
+  int next_tail = (buf->tail + 1) % SERIAL_BUF_LEN;
+  *data = buf->buffer[next_tail];
+  
+  return 0;
 }
 
 
+//  0 = successfully queued byte
+// <0 = full
+// caller responsible for locking
+// caller responsible for updateIRQ
 static int queue_data(struct v3_vm_info * vm, struct serial_port * com,
                      struct serial_buffer * buf, uint8_t data) {
     int next_loc = (buf->head + 1) % SERIAL_BUF_LEN;    
 
-    if (buf->full == 1) {
-       PrintDebug(vm, VCORE_NONE, "Buffer is full!\n");
+    PrintDebug(vm, VCORE_NONE,"UART: queue 0x%x ('%c') to %s buffer\n", data, data, 
+              buf == &(com->rx_buffer) ? "RX" : "TX");
 
-       if (buf == &(com->rx_buffer)) {
-           com->lsr.oe = 1; //overrun error bit set
+    if (!can_queue(buf)) {
+       PrintDebug(vm, VCORE_NONE, "UART: Buffer is full!\n");
+
+       // We will not consider this an overrun, instead,
+       // we push the problem back to the caller
+
+       return -1;
+
+    } else {
+    
+       buf->buffer[next_loc] = data;
+       buf->head = next_loc;
+       
+       if (buf->head == buf->tail) {
+           buf->full = 1;
        }
+       
+       if (buf == &(com->rx_buffer)) {
+           com->lsr.dr = 1; //as soon as new data arrives at receive buffer, set data ready bit in lsr.
+           
+           // Set INT_RX_DATA if fifo is now above trigger level or fifos are off
+           
+           if ( (com->fcr.enable && (receive_buffer_trigger( getNumber(&(com->rx_buffer)), com->fcr.rx_trigger)))   
+                || !com->fcr.enable) {
+               com->lsr.dr = 1;
+               com->int_state |= INT_RX_DATA;
 
-       updateIRQ(vm, com);
+               PrintDebug(vm, VCORE_NONE, "UART: Set data ready and raised INT_RX_DATA\n");
+               
+           }
+           
+       }
+       
+       if ((buf == &(com->tx_buffer))) {
+           // at least one item in the TX queue now
+           
+           com->lsr.thre = 0; //reset thre and temt bits.
+           com->lsr.temt = 0;
+           
+           // TX EMPTY deasserted 
+           com->int_state &= ~INT_TX_EMPTY;
+           
+           PrintDebug(vm, VCORE_NONE, "UART: lowered INT_TX_EMPTY\n");
+       }
 
        return 0;
+
     }
     
-    buf->buffer[next_loc] = data;
-    buf->head = next_loc;
-    
-    if (buf->head == buf->tail) {
-       buf->full = 1;
-    }
-    
-    if (buf == &(com->rx_buffer)) {
-       com->lsr.dr = 1; //as soon as new data arrives at receive buffer, set data ready bit in lsr.
-    }
-    
-    if (buf == &(com->tx_buffer)) {
-       com->lsr.thre = 0; //reset thre and temt bits.
-       com->lsr.temt = 0;
-    }
-        
-    return 0;
 }
-
+//  0 = data returned
+// <0 = data not returned
+// caller responsible for locking
+// caller responsible for doing updateIRQ
 static int dequeue_data(struct v3_vm_info * vm, struct serial_port * com,
-                       struct serial_buffer * buf, uint8_t * data) {
+                       struct serial_buffer * buf, uint8_t * data)
+{
 
-    int next_tail = (buf->tail + 1) % SERIAL_BUF_LEN;
 
+    PrintDebug(vm, VCORE_NONE,"UART: dequeue from %s buffer\n",
+              buf == &(com->rx_buffer) ? "RX" : "TX");
 
-    if ( (buf->head == buf->tail) && (buf->full != 1) ) {
-       PrintDebug(vm, VCORE_NONE, "no data to delete!\n");
+    if (!can_dequeue(buf)) {
+       PrintDebug(vm, VCORE_NONE, "UART: queue is empty - no state change, returning '!'\n");
+       *data='!'; // just in case it uses what's there, blindly
+       // for both rx and tx queues, we signaled when we droped to zero
+       // so we don't resignal here, so no need to call updateIRQ
        return -1;
-    }
-    
-    if (buf->full == 1) {
-       buf->full = 0;
-    }
-    
+    } else {
+
+       int next_tail = (buf->tail + 1) % SERIAL_BUF_LEN;
+       
+       
+       if (buf->full == 1) {
+           buf->full = 0;
+       }
+       
         
-    *data = buf->buffer[next_tail];
-    buf->buffer[next_tail] = 0;
-    buf->tail = next_tail;
-    
-    if ( (buf == &(com->rx_buffer)) && (getNumber(&(com->rx_buffer)) == 0) ) {
-       com->lsr.dr = 0;
+       *data = buf->buffer[next_tail];
+       
+       buf->buffer[next_tail] = 0;
+       buf->tail = next_tail;
+       
+       PrintDebug(vm,VCORE_NONE,"UART: dequeue will return 0x%x ('%c')\n", *data, *data);
+
+       if (buf == &(com->rx_buffer)) { 
+           
+           // Reset INT_RX_DATA if RBR read with if fifo is now below trigger level or fifos are off
+           
+           if ( (com->fcr.enable && (!receive_buffer_trigger( getNumber(&(com->rx_buffer)), com->fcr.rx_trigger)))   
+                || !com->fcr.enable) {
+               PrintDebug(vm, VCORE_NONE, "UART: lowering INT_RX_DATA since we're below threshold\n");
+               com->int_state &= ~INT_RX_DATA;
+               
+           }
+
+           // Reset DR if we have dropped to zero bytes
+           if (getNumber(&(com->rx_buffer)) ==0) { 
+               PrintDebug(vm, VCORE_NONE, "UART: setting DR to zero since we have zero bytes\n");
+               com->lsr.dr = 0;
+               // should have lowered INT_RX_DATA earlier
+           }
+           
+           // reset timeout
+           com->int_state &= ~INT_RX_TIMEOUT;
+           
+       }
+       
+       if ((buf == &(com->tx_buffer)) && (getNumber(&(com->tx_buffer)) == 0)) {
+           com->lsr.thre = 1;
+           com->lsr.temt = 1;
+           
+           // Aassert TX empty when we truly drop to zero; 
+           com->int_state |= INT_TX_EMPTY;
+       }
+
+       return 0;
     }
-    
-    if ((buf == &(com->tx_buffer)) && (getNumber(&(com->tx_buffer)) == 0)) {
-       com->lsr.thre = 1;
-       com->lsr.temt = 1;
+}
+
+//
+// Caller is assumed to have acquired the lock
+//
+static int pump_transmit(struct v3_vm_info * vm, struct serial_port * com) 
+// Now we will pump transmit data to the backend, if any
+{
+  uint8_t buf;
+  
+
+  while (can_dequeue(&(com->tx_buffer))) {
+    if (com->ops) { // do we have a back-end to toss it to?
+      int rc;
+      // let's take a peek and see if we can send it
+      peek_queue(&(com->tx_buffer),&buf);
+      rc = com->ops->output(&buf, 1, com->backend_data);
+      if (rc<0) {
+       PrintError(vm, VCORE_NONE, "UART: backend write returned error\n");
+       // we need to give up at this point - 
+       break;
+      } else if (rc==0) { 
+       // no room to send it
+       PrintDebug(vm, VCORE_NONE, "UART: backend write would block\n");
+       // we do nothing but we don't want to iterate again
+       break; // out we go
+      } else {
+       // it was sent, now we need to remove it from the queue for real
+       // as well as update the device state
+       if (dequeue_data(vm,com,&(com->tx_buffer),&buf)) { 
+         PrintError(vm, VCORE_NONE, "UART: uh... dequeue_data failed after successful peek?!\n");
+         break;  // out we go
+       }
+       // we have already sent the byte to the backend, so we
+       // just discard it now and continue to the next one
+      }
+    } else { // there is no backend
+      if (dequeue_data(vm,com,&(com->tx_buffer),&buf)) { 
+       PrintError(vm, VCORE_NONE, "UART: uh... dequeue_data failed after successful can_dequeue?!\n");
+      }
+      // no backend, so just discard the data
     }
-    
-    updateIRQ(vm, com);
-    
-    return 0;
+  }
+  return 0;
+}
+
+static int updateIRQ(struct v3_vm_info * vm, struct serial_port * com) {
+
+
+
+  PrintDebug(vm,VCORE_NONE, "UART: updateIRQ before pending check: iir.pending=%d iir.iid=%d int_state=0x%x\n", 
+            com->iir.pending, com->iir.iid, com->int_state);
+
+  // if we have have raised an irq, we need to lower it if it's been handled
+  // we should also presumably lower it if the guest decided to 
+  // disable the interrupt
+  if (!com->iir.pending && (com->int_state & IRQ_RAISED_MASK)) {  // active low pending, and we have raised
+      switch (com->iir.iid) {
+         case RX_IRQ_STATUS:
+             if (!(com->int_state & INT_RX_STAT) ||                     // now low or
+                 ((com->int_state & INT_RX_STAT) && !com->ier.elsi))  { // no longer enabled
+                 // no longer asserted
+                 v3_lower_irq(vm, com->irq_number);
+                 com->int_state &= ~IRQ_RAISED_MASK;
+                 com->iir.pending=1;
+                 com->iir.iid=0;
+                 PrintDebug(vm, VCORE_NONE, "UART: lowered irq on reset of INT_RX_STAT\n");
+             }
+             break;
+         case RX_IRQ_DR:    
+         // case RX_IRQ_TRIGGER_LEVEL: is the smae
+             if (!(com->int_state & INT_RX_DATA) || 
+                 ((com->int_state & INT_RX_STAT) && !com->ier.erbfi)) {
+                 // no longer asserted
+                 v3_lower_irq(vm, com->irq_number);
+                 com->int_state &= ~IRQ_RAISED_MASK;
+                 com->iir.pending=1;
+                 com->iir.iid=0;
+                 PrintDebug(vm, VCORE_NONE, "UART: lowered irq on reset of INT_RX_DATA\n");
+             }
+             break;
+         case FIFO_IRQ:
+             if (!(com->int_state & INT_RX_TIMEOUT) ||
+                 ((com->int_state & INT_RX_TIMEOUT) && !com->ier.erbfi)) {
+                 // no longer asserted
+                 v3_lower_irq(vm, com->irq_number);
+                 com->int_state &= ~IRQ_RAISED_MASK;
+                 com->iir.pending=1;
+                 com->iir.iid=0;
+                 PrintDebug(vm, VCORE_NONE, "UART: lowered irq on reset of INT_RX_TIMEOUT\n");
+             }
+             break;
+         case TX_IRQ_THRE:
+             if (!(com->int_state & INT_TX_EMPTY)  ||
+                 ((com->int_state & INT_TX_EMPTY) && !com->ier.etbei)) {
+                 // no longer asserted
+                 v3_lower_irq(vm, com->irq_number);
+                 com->int_state &= ~IRQ_RAISED_MASK;
+                 com->iir.pending=1;
+                 com->iir.iid=0;
+                 PrintDebug(vm, VCORE_NONE, "UART: lowered irq on reset of INT_TX_EMPTY\n");
+             }
+             break;
+         case MODEM_IRQ_DELTA_SET:
+             if (!(com->int_state & INT_MD_STAT) ||
+                 ((com->int_state & INT_MD_STAT) && !com->ier.edssi)) {
+                 // no longer asserted
+                 v3_lower_irq(vm, com->irq_number);
+                 com->int_state &= ~IRQ_RAISED_MASK;
+                 com->iir.pending=1;
+                 com->iir.iid=0;
+                 PrintDebug(vm, VCORE_NONE, "UART: lowered irq on reset of INT_MD_STAT\n");
+             }
+             break;
+      }
+  }
+
+  PrintDebug(vm,VCORE_NONE, "UART: updateIRQ before transmit-pump: iir.pending=%d iir.iid=%d int_state=0x%x\n", 
+            com->iir.pending, com->iir.iid, com->int_state);
+  
+  if (pump_transmit(vm,com)) {
+    PrintError(vm,VCORE_NONE, "UART: pump_transmit failed - eh?\n");
+  }
+
+  // At this point, INT_TX_EMPTY might have also been raised
+  if (com->lsr.temt) { 
+      // even if pump_transmit wasn't able to do anything, we need to
+      // raise the interrupt - this is the case, for example, if the
+      // guest does an interrupt enable of the tx empty interrupt before
+      // writing anything
+      com->int_state |= INT_TX_EMPTY;
+  }
+
+  PrintDebug(vm,VCORE_NONE, "UART: updateIRQ before priority encode:  iir.pending=%d iir.iid=%d int_state=0x%x\n", 
+            com->iir.pending, com->iir.iid, com->int_state);
+
+
+  if (!(com->int_state & IRQ_RAISED_MASK)) { 
+      // We can inject a new interrupt since the last one is done
+      // Now we do the priority encode
+      if ((com->int_state & INT_RX_STAT) && com->ier.elsi ) { 
+         // highest priority
+         com->iir.pending=0;
+         com->iir.iid = RX_IRQ_STATUS;
+         v3_raise_irq(vm,com->irq_number);
+         com->int_state |= IRQ_RAISED_MASK;
+         PrintDebug(vm, VCORE_NONE, "UART: raised irq on set of INT_RX_STAT\n");
+      } else if ((com->int_state & INT_RX_DATA) && com->ier.erbfi) { 
+         // 2nd highest priority
+         com->iir.pending=0;
+         com->iir.iid = RX_IRQ_DR;
+         v3_raise_irq(vm,com->irq_number);
+         com->int_state |= IRQ_RAISED_MASK;
+         PrintDebug(vm, VCORE_NONE, "UART: raised irq on set of INT_RX_DATA\n");
+      } else if ((com->int_state & INT_RX_TIMEOUT) && com->ier.erbfi) { 
+         // Also 2nd highest priority
+         com->iir.pending=0;
+         com->iir.iid = FIFO_IRQ;
+         v3_raise_irq(vm,com->irq_number);
+         com->int_state |= IRQ_RAISED_MASK;
+         PrintDebug(vm, VCORE_NONE, "UART: raised irq on set of INT_RX_TIMEOUT\n");
+      }        else if ((com->int_state & INT_TX_EMPTY) && com->ier.etbei) { 
+         // 3rd highest priority
+         com->iir.pending=0;
+         com->iir.iid = TX_IRQ_THRE;
+         v3_raise_irq(vm,com->irq_number);
+         com->int_state |= IRQ_RAISED_MASK;
+         PrintDebug(vm, VCORE_NONE, "UART: raised irq on set of INT_TX_EMPTY\n");
+      } else if ((com->int_state & INT_MD_STAT) && com->ier.edssi) { 
+         // 4th highest priority
+         com->iir.pending=0;
+         com->iir.iid = MODEM_IRQ_DELTA_SET;
+         v3_raise_irq(vm,com->irq_number);
+         com->int_state |= IRQ_RAISED_MASK;
+         PrintDebug(vm, VCORE_NONE, "UART: raised irq on set of INT_MD_STAT\n");
+      } else {
+         // nothing to do
+      }
+  }
+  
+  return 0;
 }
 
+
+
 static int write_data_port(struct guest_info * core, uint16_t port, 
                           void * src, uint_t length, void * priv_data) {
     struct serial_state * state = priv_data;
     uint8_t * val = (uint8_t *)src;
     struct serial_port * com_port = NULL;
+    addr_t irq_status;
 
-    PrintDebug(core->vm_info, core, "Write to Data Port 0x%x (val=%x)\n", port, *val);
+    PrintDebug(core->vm_info, core, "UART: Write to Data Port 0x%x (val=%x, '%c')\n", port, *val, *val);
     
     if (length != 1) {
-       PrintError(core->vm_info, core, "Invalid length(%d) in write to 0x%x\n", length, port);
+       PrintError(core->vm_info, core, "UART: Invalid length(%d) in write to 0x%x\n", length, port);
        return -1;
     }
 
     if ((port != COM1_DATA_PORT) && (port != COM2_DATA_PORT) && 
        (port != COM3_DATA_PORT) && (port != COM4_DATA_PORT)) {
-       PrintError(core->vm_info, core, "Serial Read data port for illegal port Number (%d)\n", port);
+       PrintError(core->vm_info, core, "UART: Serial write data port for illegal port Number (%d)\n", port);
        return -1;
     }
 
     com_port = get_com_from_port(state, port);
 
     if (com_port == NULL) {
-       PrintError(core->vm_info, core, "UART:read from NOBODY\n");
+       PrintError(core->vm_info, core, "UART: write of unknown port %d - lookup failed?!\n",port);
        return -1;
     }
     
-
+    irq_status = v3_lock_irqsave(com_port->lock);
+    
     // dlab is always checked first
     if (com_port->lcr.dlab == 1) {
+       PrintDebug(core->vm_info, core, "UART:  Write to DLM, old DLM is 0x%x\n",com_port->dlm.data);
        com_port->dll.data = *val;
+       PrintDebug(core->vm_info, core, "UART:  Write to DLM, new DLM is 0x%x\n",com_port->dlm.data);
     }  else {
-       
-
-       /* JRL: Some buffering would probably be a good idea here.... */
-       if (com_port->ops) {
-           com_port->ops->output(val, 1, com_port->backend_data);
+       // queue data to send and update interrupts
+       PrintDebug(core->vm_info, core, "UART: queue transmission of 0x%x ('%c')\n",*val,*val);
+       if (queue_data(core->vm_info, com_port, &(com_port->tx_buffer), *val)) { 
+         PrintError(core->vm_info,core, "UART: no room for transmitted data - dropped\n");
+         // note that it must "succeed" since this is a device port
        } else {
-           queue_data(core->vm_info, com_port, &(com_port->tx_buffer), *val);
-           updateIRQ(core->vm_info, com_port);
+         updateIRQ(core->vm_info, com_port);
        }
     }
-    
+
+    v3_unlock_irqrestore(com_port->lock, irq_status);
+
     return length;
 }
 
@@ -503,163 +780,266 @@ static int read_data_port(struct guest_info * core, uint16_t port,
     struct serial_state * state = priv_data;
     uint8_t * val = (uint8_t *)dst;
     struct serial_port * com_port = NULL;
+    addr_t irq_status;
 
-    PrintDebug(core->vm_info, core, "Read from Data Port 0x%x\n", port);
+    PrintDebug(core->vm_info, core, "UART: Read from Data Port 0x%x\n", port);
     
     if (length != 1) {
-       PrintError(core->vm_info, core, "Invalid length(%d) in write to 0x%x\n", length, port);
+       PrintError(core->vm_info, core, "UART: Invalid length(%d) in write to 0x%x\n", length, port);
        return -1;
     }
     
     if ((port != COM1_DATA_PORT) && (port != COM2_DATA_PORT) && 
        (port != COM3_DATA_PORT) && (port != COM4_DATA_PORT)) {
-       PrintError(core->vm_info, core, "Serial Read data port for illegal port Number (%d)\n", port);
+       PrintError(core->vm_info, core, "UART: Serial Read data port for illegal port Number (%d)\n", port);
        return -1;
     }
 
     com_port = get_com_from_port(state, port);
 
     if (com_port == NULL) {
-       PrintError(core->vm_info, core, "UART:read from NOBODY\n");
+       PrintError(core->vm_info, core, "UART: write of unknown port %d - lookup failed?!\n",port);
        return -1;
     }
-    
+
+    irq_status = v3_lock_irqsave(com_port->lock);
+
     if (com_port->lcr.dlab == 1) {
        *val = com_port->dll.data;
+       PrintDebug(core->vm_info, core, "UART: read of DLL returning 0x%x\n",*val);
     } else {
-       dequeue_data(core->vm_info, com_port, &(com_port->rx_buffer), val);
+      if (dequeue_data(core->vm_info, com_port, &(com_port->rx_buffer), val)) { 
+       PrintError(core->vm_info, core, "UART: no received data available - returning garbaage\n");
+       // note that it must "succeed" since this is device port read
+      } else {
+       updateIRQ(core->vm_info, com_port);
+      }
+      PrintDebug(core->vm_info, core, "UART: dequeued received data 0x%x ('%c')\n",*val,*val);
     }    
        
+    v3_unlock_irqrestore(com_port->lock, irq_status);
+
     return length;
 }
 
 
+static void flush_buffer(struct serial_buffer *buf)
+{
+    buf->head = 0;
+    buf->tail = 0;
+    buf->full = 0;
+    memset(buf->buffer, 0, SERIAL_BUF_LEN);
+}
 
-static int handle_fcr_write(struct serial_port * com, uint8_t value) {
+//
+// caller is assumed to have acquired the lock
+// caller is responsible for updateIRQ
+static void handle_fcr_write(struct serial_port * com, uint8_t value) {
 
-    com->fcr.enable = value & 0x1;
-    
-    if (com->fcr.enable == 0x1) {
-       com->fcr.val = value;
+#if BE_16550A
+
+    if ((!com->fcr.enable && (value & 0x1)) || (com->fcr.enable && !(value & 0x1))) { 
+       // switch of modes from 16450<->16550
+       // flush fifos, reset state
+       flush_buffer(&(com->rx_buffer));
+       flush_buffer(&(com->tx_buffer));
+       com->lsr.dr = 0;
+       com->int_state &= ~INT_RX_DATA; // no data in rx buffer - lower
 
-       com->fcr.enable = 1; // Do we need to set this??
+       com->lsr.thre = 1;
+       com->lsr.temt = 1;
+       com->int_state |= INT_TX_EMPTY; // tx buffer empty - raise
+    }
 
+    if (!(value & 0x1)) { // disabling
+       // enable->disable requires flush and state reset, handled above
+       // disable->disable doesn't require flush, I don't think 
+       com->fcr.enable = 0;
+       com->iir.fifo_en = 0;
+       // reset does not change rest of fcr register
+    } else { // enabling
+       com->fcr.val = value;
+       com->fcr.rsvd = 0; // we are not some weird chip
+       
        //if rfres set, clear receive buffer.
        if (com->fcr.rfres == 0x1) {
-           com->rx_buffer.head = 0;
-           com->rx_buffer.tail = 0;
-           com->rx_buffer.full = 0;
-           memset(com->rx_buffer.buffer, 0, SERIAL_BUF_LEN);
-           com->fcr.rfres = 0;
+           flush_buffer(&(com->rx_buffer));
+           com->fcr.rfres = 0; // bit is self-clearing
+           com->lsr.dr = 0;
+           com->int_state &= ~INT_RX_DATA; // no data in rx buffer - lower
        }
-
+       
        //if xfres set, clear transmit buffer.
        if (com->fcr.xfres == 0x1) {
-           com->tx_buffer.head = 0;
-           com->tx_buffer.tail = 0;
-           com->tx_buffer.full = 0;
-           memset(com->tx_buffer.buffer, 0, SERIAL_BUF_LEN);
-           com->fcr.xfres = 0;
+           flush_buffer(&(com->tx_buffer));
+           com->fcr.xfres = 0; // bit is self-clearing
+           com->lsr.thre = 1;
+           com->lsr.temt = 1;
+           com->int_state |= INT_TX_EMPTY; // tx buffer empty -raise
        }
-    } else {
-       //clear both buffers.
-       com->tx_buffer.head = 0;
-       com->tx_buffer.tail = 0;
-       com->tx_buffer.full = 0;
-       com->rx_buffer.head = 0;
-       com->rx_buffer.tail = 0;
-       com->rx_buffer.full = 0;
-       
-       memset(com->rx_buffer.buffer, 0, SERIAL_BUF_LEN);
-       memset(com->tx_buffer.buffer, 0, SERIAL_BUF_LEN);
-    }
-    
-    return 1;
+
+       com->iir.fifo_en = 0x3; // We are a 16550A
+       com->iir.fifo_en = 0x0; // We are a 16450A
+
+       // 00  => 8250/16450 (latter if have scratchpad)
+       // 10  => 16550
+       // 11  => 16550A
+    }   
+
+#else
+    // does nothing since a 16450 has no fcr
+#endif
+
+    // caller must update irq!
 }
 
 
 
 
 
+static int handle_multiple(int (*func)(struct guest_info *core,
+                                      uint16_t port, void *dst,
+                                      uint_t length, void *priv_data),
+                          struct guest_info *core, uint16_t port, 
+                          void *dst, uint_t length, void *priv_data) 
+{
+  uint16_t i;
+  int rc;
+
+  if (length == 1) { 
+    return func(core,port,dst,length,priv_data);
+  } else {
+    for (i=0;i<length;i++) {
+      rc = func(core,port+i,dst+i,1,priv_data);
+      if (rc!=1) { 
+       return i;
+      }
+    }
+    return length;
+  }
+}
+
 static int write_ctrl_port(struct guest_info * core, uint16_t port, void * src, 
                           uint_t length, void * priv_data) {
     struct serial_state * state = priv_data;
     uint8_t val = *(uint8_t *)src;
     struct serial_port * com_port = NULL;
+    addr_t irq_status;
+    int ret;
 
-    PrintDebug(core->vm_info, core, "UART:Write to Control Port (val=%x)\n", val);
+    PrintDebug(core->vm_info, core, "UART: Write to Control Port (val=%x)\n", val);
     
     if (length != 1) {
-       PrintError(core->vm_info, core, "UART:Invalid Write length to control port%d\n", port);
+       PrintError(core->vm_info, core, "UART: Invalid Write length to control port%d\n", port);
        return -1;
     }
 
     com_port = get_com_from_port(state, port);
 
     if (com_port == NULL) {
-       PrintError(core->vm_info, core, "Could not find serial port corresponding to IO port %d\n", port);
+       PrintError(core->vm_info, core, "UART: Could not find serial port corresponding to IO port %d\n", port);
        return -1;
     }
-    
+
+    irq_status = v3_lock_irqsave(com_port->lock);
+  
+    ret = 1;
+
     //always check dlab first
     switch (port) {
        case COM1_IRQ_ENABLE_PORT:
        case COM2_IRQ_ENABLE_PORT:
        case COM3_IRQ_ENABLE_PORT:
        case COM4_IRQ_ENABLE_PORT: {
-           PrintDebug(core->vm_info, core, "UART:Write to IER/LATCH port: dlab is %x\n", com_port->lcr.dlab);
+           PrintDebug(core->vm_info, core, "UART: Write to IER/LATCH port: dlab is %x\n", com_port->lcr.dlab);
 
            if (com_port->lcr.dlab == 1) {
+               PrintDebug(core->vm_info, core, "UART:  Write to DLM, old DLM is 0x%x\n",com_port->dlm.data);
                com_port->dlm.data = val;
+               PrintDebug(core->vm_info, core, "UART:  Write to DLM, new DLM is 0x%x\n",com_port->dlm.data);
            } else {
+               PrintDebug(core->vm_info, core, "UART: Write to IER, old IER is erbfi=%d etbei=%d elsi=%d edssi=%d\n",
+                          com_port->ier.erbfi,com_port->ier.etbei,com_port->ier.elsi,com_port->ier.edssi);
                com_port->ier.val = val;
+               com_port->ier.rsvd = 0; // we are not some weird chip
+               PrintDebug(core->vm_info, core, "UART: Write to IER, new IER is erbfi=%d etbei=%d elsi=%d edssi=%d\n",
+                          com_port->ier.erbfi,com_port->ier.etbei,com_port->ier.elsi,com_port->ier.edssi);
+               // some signaled interrupt might now need to fire
            }
 
+           updateIRQ(core->vm_info,com_port);
+
            break;
        }           
        case COM1_FIFO_CTRL_PORT:
        case COM2_FIFO_CTRL_PORT:
        case COM3_FIFO_CTRL_PORT:
        case COM4_FIFO_CTRL_PORT: {
-           PrintDebug(core->vm_info, core, "UART:Write to FCR\n");
+           PrintDebug(core->vm_info, core, "UART: Write to FCR, old FCR is enable=%d rfres=%d xfres=%d dma_sel=%d rx_trigger=%d\n",
+                      com_port->fcr.enable, com_port->fcr.rfres, com_port->fcr.xfres, com_port->fcr.dma_sel, com_port->fcr.rx_trigger);
 
-           if (handle_fcr_write(com_port, val) == -1) {
-               return -1;
-           }
+           handle_fcr_write(com_port, val); // cannot fail
+           
+           PrintDebug(core->vm_info, core, "UART: Write to FCR, new FCR is enable=%d rfres=%d xfres=%d dma_sel=%d rx_trigger=%d\n",
+                      com_port->fcr.enable, com_port->fcr.rfres, com_port->fcr.xfres, com_port->fcr.dma_sel, com_port->fcr.rx_trigger);
 
+           updateIRQ(core->vm_info,com_port);
+           
            break;
        }
        case COM1_LINE_CTRL_PORT:
        case COM2_LINE_CTRL_PORT:
        case COM3_LINE_CTRL_PORT:
        case COM4_LINE_CTRL_PORT: {
-           PrintDebug(core->vm_info, core, "UART:Write to LCR\n");
+           PrintDebug(core->vm_info, core, "UART: Write to LCR, old LCR is word_len=%d stop_bits=%d parity_enable=%d even_sel=%d stick_parity=%d sbr=%d dlab=%d\n",
+                      com_port->lcr.word_len,com_port->lcr.stop_bits,com_port->lcr.parity_enable,com_port->lcr.even_sel,com_port->lcr.stick_parity,com_port->lcr.sbr,com_port->lcr.dlab);
+
            com_port->lcr.val = val;
+           // no reserved bits
+           
+           PrintDebug(core->vm_info, core, "UART: Write to LCR, new LCR is word_len=%d stop_bits=%d parity_enable=%d even_sel=%d stick_parity=%d sbr=%d dlab=%d\n",
+                      com_port->lcr.word_len,com_port->lcr.stop_bits,com_port->lcr.parity_enable,com_port->lcr.even_sel,com_port->lcr.stick_parity,com_port->lcr.sbr,com_port->lcr.dlab);
+
+           updateIRQ(core->vm_info, com_port);
+
            break;
        }
        case COM1_MODEM_CTRL_PORT:
        case COM2_MODEM_CTRL_PORT:
        case COM3_MODEM_CTRL_PORT:
        case COM4_MODEM_CTRL_PORT: {
-           PrintDebug(core->vm_info, core, "UART:Write to MCR\n");
+           PrintDebug(core->vm_info, core, "UART: Write to MCR, old MCR is dtr=%d rts=%d out1=%d out2=%d loop=%d\n",
+                      com_port->mcr.dtr,com_port->mcr.rts,com_port->mcr.out1,com_port->mcr.out2,com_port->mcr.loop);
+
            com_port->mcr.val = val;
+           com_port->mcr.rsvd = 0; // we are not some weird chip
+
+           PrintDebug(core->vm_info, core, "UART: Write to MCR, new MCR is dtr=%d rts=%d out1=%d out2=%d loop=%d\n",
+                      com_port->mcr.dtr,com_port->mcr.rts,com_port->mcr.out1,com_port->mcr.out2,com_port->mcr.loop);
+           
+           updateIRQ(core->vm_info, com_port);
+
            break;
        }
        case COM1_SCRATCH_PORT:
        case COM2_SCRATCH_PORT:
        case COM3_SCRATCH_PORT:
        case COM4_SCRATCH_PORT: {
-           PrintDebug(core->vm_info, core, "UART:Write to SCRATCH\n");
+           PrintDebug(core->vm_info, core, "UART: Write to SCRATCH, old value is 0x%x\n",com_port->scr.data);
+
            com_port->scr.data = val;
+
+           PrintDebug(core->vm_info, core, "UART: Write to SCRATCH, new value is 0x%x\n",com_port->scr.data);
            break;
        }
        default:
-           PrintError(core->vm_info, core, "UART:Write to NOBODY, ERROR\n");
-           return -1;
+           PrintError(core->vm_info, core, "UART: Write to unknown port %d, ERROR\n",port);
+           ret = -1;
     }
     
+    v3_unlock_irqrestore(com_port->lock,irq_status);
 
-    return length;
+    return ret;
 }
 
 
@@ -670,33 +1050,42 @@ static int read_ctrl_port(struct guest_info * core, uint16_t port, void * dst,
     struct serial_state * state = priv_data;
     uint8_t * val = (uint8_t *)dst;
     struct serial_port * com_port = NULL;
+    addr_t irq_status;
+    int ret;
 
-    PrintDebug(core->vm_info, core, "Read from Control Port\n");
+    PrintDebug(core->vm_info, core, "UART: Read from Control Port\n");
     
     if (length != 1) {
-       PrintError(core->vm_info, core, "Invalid Read length to control port\n");
+       PrintError(core->vm_info, core, "UART: Invalid Read length to control port\n");
        return -1;
     }
 
     com_port = get_com_from_port(state, port);
 
     if (com_port == NULL) {
-       PrintError(core->vm_info, core, "Could not find serial port corresponding to IO port %d\n", port);
+       PrintError(core->vm_info, core, "UART: Could not find serial port corresponding to IO port %d\n", port);
        return -1;
     }
     
+    irq_status = v3_lock_irqsave(com_port->lock);
+
+    ret = 1;
+
     //always check dlab first
     switch (port) {
        case COM1_IRQ_ENABLE_PORT:
        case COM2_IRQ_ENABLE_PORT:
        case COM3_IRQ_ENABLE_PORT:
        case COM4_IRQ_ENABLE_PORT: {
-           PrintDebug(core->vm_info, core, "UART:read from IER\n");
+           PrintDebug(core->vm_info, core, "UART: Read from IER/LATCH port: dlab is %x\n", com_port->lcr.dlab);
 
            if (com_port->lcr.dlab == 1) {
+               PrintDebug(core->vm_info, core, "UART: Read of DLM, value 0x%x\n",com_port->dlm.data);
                *val = com_port->dlm.data;
            } else {
                *val = com_port->ier.val;
+               PrintDebug(core->vm_info, core, "UART: Read of IER is val=0x%x - erbfi=%d etbei=%d elsi=%d edssi=%d\n",
+                          com_port->ier.val, com_port->ier.erbfi,com_port->ier.etbei,com_port->ier.elsi,com_port->ier.edssi);
            }
            break;
        }
@@ -705,15 +1094,28 @@ static int read_ctrl_port(struct guest_info * core, uint16_t port, void * dst,
        case COM2_IIR_PORT:
        case COM3_IIR_PORT:
        case COM4_IIR_PORT:
-           PrintDebug(core->vm_info, core, "UART:read from IIR\n");
+           PrintDebug(core->vm_info, core, "UART: read from IIR is val=0x%x - pending=%d iid=%d fifo_en=%d\n",
+                      com_port->iir.val,com_port->iir.pending,com_port->iir.iid,com_port->iir.fifo_en);
            *val = com_port->iir.val;
+
+           if ((com_port->int_state & IRQ_RAISED_MASK) && 
+               !com_port->iir.pending && com_port->iir.iid==TX_IRQ_THRE) { 
+               // we are firing a TX_IRQ_THRE interrupt, therefore
+               // this read resets it
+
+               com_port->int_state &= ~INT_TX_EMPTY;
+               
+               updateIRQ(core->vm_info,com_port);
+           }
+
            break;
 
        case COM1_LINE_CTRL_PORT:
        case COM2_LINE_CTRL_PORT:
        case COM3_LINE_CTRL_PORT:
        case COM4_LINE_CTRL_PORT:
-           PrintDebug(core->vm_info, core, "UART:read from LCR\n");
+           PrintDebug(core->vm_info, core, "UART: read from LCR is val=0x%x - word_len=%d stop_bits=%d parity_enable=%d even_sel=%d stick_parity=%d sbr=%d dlab=%d\n",
+                      com_port->lcr.val,com_port->lcr.word_len, com_port->lcr.stop_bits,com_port->lcr.parity_enable,com_port->lcr.even_sel,com_port->lcr.stick_parity,com_port->lcr.sbr,com_port->lcr.dlab);
            *val = com_port->lcr.val;
            break;
 
@@ -721,7 +1123,8 @@ static int read_ctrl_port(struct guest_info * core, uint16_t port, void * dst,
        case COM2_MODEM_CTRL_PORT:
        case COM3_MODEM_CTRL_PORT:
        case COM4_MODEM_CTRL_PORT:
-           PrintDebug(core->vm_info, core, "UART:read from MCR\n");
+           PrintDebug(core->vm_info, core, "UART: read from MCR is val=0x%x - dtr=%d rts=%d out1=%d out2=%d loop=%d\n",
+                      com_port->mcr.val,com_port->mcr.dtr,com_port->mcr.rts,com_port->mcr.out1,com_port->mcr.out2,com_port->mcr.loop);
            *val = com_port->mcr.val;
            break;
 
@@ -729,16 +1132,18 @@ static int read_ctrl_port(struct guest_info * core, uint16_t port, void * dst,
        case COM2_SCRATCH_PORT:
        case COM3_SCRATCH_PORT:
        case COM4_SCRATCH_PORT:
-           PrintDebug(core->vm_info, core, "UART:read from SCRATCH\n");
+           PrintDebug(core->vm_info, core, "UART: read from SCRATCH is val=0x%x\n",com_port->scr.data);
            *val = com_port->scr.data;
            break;
 
        default:
-           PrintError(core->vm_info, core, "UART:read from NOBODY\n");
-           return -1;
+           PrintError(core->vm_info, core, "UART: read from unknown port %d\n",port);
+           ret = -1;
     }
 
-    return length;
+    v3_unlock_irqrestore(com_port->lock,irq_status);
+
+    return ret;
 }
 
 
@@ -747,76 +1152,128 @@ static int write_status_port(struct guest_info * core, uint16_t port, void * src
     struct serial_state * state = priv_data;
     uint8_t val = *(uint8_t *)src;
     struct serial_port * com_port = NULL;
+    addr_t irq_status;
+    int ret;
 
-    PrintDebug(core->vm_info, core, "Write to Status Port (val=%x)\n", val);
+    PrintDebug(core->vm_info, core, "UART: Write to Status Port (val=0x%x)\n", val);
 
     if (length != 1) {
-       PrintError(core->vm_info, core, "Invalid Write length to status port %d\n", port);
+       PrintError(core->vm_info, core, "UART: Invalid Write length (%d) to status port %d\n", length, port);
        return -1;
     }
 
     com_port = get_com_from_port(state, port);
 
     if (com_port == NULL) {
-       PrintError(core->vm_info, core, "Could not find serial port corresponding to IO port %d\n", port);
+       PrintError(core->vm_info, core, "UART: Could not find serial port corresponding to IO port %d\n", port);
        return -1;
     }
 
+    irq_status = v3_lock_irqsave(com_port->lock);
+    
+    ret = 1;
+
     switch (port) {
        case COM1_LINE_STATUS_PORT:
        case COM2_LINE_STATUS_PORT:
        case COM3_LINE_STATUS_PORT:
        case COM4_LINE_STATUS_PORT:
-           PrintDebug(core->vm_info, core, "UART:write to LSR\n");
+           PrintDebug(core->vm_info, core, "UART: Write to LSR, old value is 0x%x - dr=%d oe=%d pe=%d fe=%d brk=%d thre=%d temt=%d fifo_err=%d\n",
+                      com_port->lsr.val,com_port->lsr.dr,com_port->lsr.oe,com_port->lsr.pe,com_port->lsr.fe,com_port->lsr.brk,com_port->lsr.thre,com_port->lsr.temt,com_port->lsr.fifo_err);
+
            com_port->lsr.val = val;
+           // no reserved bits
+
+           PrintDebug(core->vm_info, core, "UART: Write to LSR, new value is 0x%x - dr=%d oe=%d pe=%d fe=%d brk=%d thre=%d temt=%d fifo_err=%d\n",
+                      com_port->lsr.val,com_port->lsr.dr,com_port->lsr.oe,com_port->lsr.pe,com_port->lsr.fe,com_port->lsr.brk,com_port->lsr.thre,com_port->lsr.temt,com_port->lsr.fifo_err);
+
+           updateIRQ(core->vm_info, com_port);
+
            break;
 
        case COM1_MODEM_STATUS_PORT:
        case COM2_MODEM_STATUS_PORT:
        case COM3_MODEM_STATUS_PORT:
        case COM4_MODEM_STATUS_PORT:
-           PrintDebug(core->vm_info, core, "UART:write to MSR\n");
+
+           PrintDebug(core->vm_info,core,"UART: Write to MSR, old value is 0x%x - dcts=%d ddsr=%d teri=%d ddcd=%d cts=%d dsr=%d ri=%d dcd=%d\n",
+                      com_port->msr.val,com_port->msr.dcts,com_port->msr.ddsr,com_port->msr.teri,com_port->msr.ddcd,com_port->msr.cts,com_port->msr.dsr,com_port->msr.ri,com_port->msr.dcd);
+
            com_port->msr.val = val;
+           // no reserved bits
+
+           PrintDebug(core->vm_info,core,"UART: Write to MSR, new value is 0x%x - dcts=%d ddsr=%d teri=%d ddcd=%d cts=%d dsr=%d ri=%d dcd=%d\n",
+                      com_port->msr.val,com_port->msr.dcts,com_port->msr.ddsr,com_port->msr.teri,com_port->msr.ddcd,com_port->msr.cts,com_port->msr.dsr,com_port->msr.ri,com_port->msr.dcd);
+           
+           updateIRQ(core->vm_info, com_port);
+
            break;
 
        default:
-           PrintError(core->vm_info, core, "UART:write to NOBODY\n");
-           return -1;
+           PrintError(core->vm_info, core, "UART: write to unsupported port %d\n",port);
+           ret = -1;
     }
 
-    return length;
+    v3_unlock_irqrestore(com_port->lock,irq_status);
+
+    return ret;
 }
 
+
 static int read_status_port(struct guest_info * core, uint16_t port, void * dst, 
                            uint_t length, void * priv_data) {
     struct serial_state * state = priv_data;
     uint8_t * val = (uint8_t *)dst;
     struct serial_port * com_port = NULL;
-
-    PrintDebug(core->vm_info, core, "Read from Status Port 0x%x\n", port);
+    addr_t irq_status;
+    int ret;
+
+    if (length==2 && (port==COM1_MODEM_STATUS_PORT ||
+                     port==COM2_MODEM_STATUS_PORT ||
+                     port==COM3_MODEM_STATUS_PORT ||
+                     port==COM4_MODEM_STATUS_PORT) ) {
+      return handle_multiple(read_status_port,core,port,dst,length, priv_data);
+    } else if (length!=1) { 
+      PrintError(core->vm_info, core, "UART: Invalid Read length (%d) from status port %d\n", length, port);
+      return -1;
+    }
+      
+    PrintDebug(core->vm_info, core, "UART: Read from Status Port 0x%x\n", port);
 
     com_port = get_com_from_port(state, port);
 
     if (com_port == NULL) {
-       PrintError(core->vm_info, core, "Could not find serial port corresponding to IO port %d\n", port);
+       PrintError(core->vm_info, core, "UART: Could not find serial port corresponding to IO port %d\n", port);
        return -1;
     }
-
+    
+    irq_status = v3_lock_irqsave(com_port->lock);
+    ret = 1;
+    
     switch (port) {
        case COM1_LINE_STATUS_PORT:
        case COM2_LINE_STATUS_PORT:
        case COM3_LINE_STATUS_PORT:
        case COM4_LINE_STATUS_PORT:
 
-           if (length != 1) {
-               PrintError(core->vm_info, core, "Invalid Read length to control port\n");
-               return -1;
-           }
-
-           PrintDebug(core->vm_info, core, "UART:read from LSR\n");
+           PrintDebug(core->vm_info, core, "UART: Read from LSR is 0x%x - dr=%d oe=%d pe=%d fe=%d brk=%d thre=%d temt=%d fifo_err=%d\n",
+                      com_port->lsr.val,com_port->lsr.dr,com_port->lsr.oe,com_port->lsr.pe,com_port->lsr.fe,com_port->lsr.brk,com_port->lsr.thre,com_port->lsr.temt,com_port->lsr.fifo_err);
 
            *val = com_port->lsr.val;
-           com_port->lsr.oe = 0;     // Why do we clear this??
+
+
+           // Reading the LSR resets the line error state
+           com_port->lsr.oe = 0;     
+           com_port->lsr.pe = 0;     
+           com_port->lsr.fe = 0;     
+           com_port->lsr.brk = 0;     
+           com_port->lsr.fifo_err = 0;     
+
+           // and clears any interrupt we set due to it
+           com_port->int_state &= ~INT_RX_STAT;
+           
+           updateIRQ(core->vm_info,com_port);
+
 
            break;
 
@@ -824,37 +1281,38 @@ static int read_status_port(struct guest_info * core, uint16_t port, void * dst,
        case COM2_MODEM_STATUS_PORT:
        case COM3_MODEM_STATUS_PORT:
        case COM4_MODEM_STATUS_PORT:
-           PrintDebug(core->vm_info, core, "UART:read from COM4 MSR (length = %d)\n", length);
+           PrintDebug(core->vm_info,core,"UART: read of MSR is 0x%x - dcts=%d ddsr=%d teri=%d ddcd=%d cts=%d dsr=%d ri=%d dcd=%d\n",
+                      com_port->msr.val,com_port->msr.dcts,com_port->msr.ddsr,com_port->msr.teri,com_port->msr.ddcd,com_port->msr.cts,com_port->msr.dsr,com_port->msr.ri,com_port->msr.dcd);
 
-           if (length > 2) {
-               PrintError(core->vm_info, core, "Invalid Read length to MSR port\n");
-               return -1;
-           }
+           *val = com_port->msr.val;
 
-           if (length == 2) {
-               /* Windows XP expects to be able to read this register and the next in one go */
+           // MSR read resets any interrupt due to modem error
+           com_port->int_state &= ~INT_MD_STAT;
 
-               if (read_ctrl_port(core, port + 1, val + 1, 1, priv_data) < 0) {
-                   PrintError(core->vm_info, core, "Error reading control port for word size read of Status register\n");
-                   return -1;
-               }
-           }
+           updateIRQ(core->vm_info,com_port);
 
-           // always read low byte...
-           
-           *val = com_port->msr.val;
            break;
 
        default:
-           PrintError(core->vm_info, core, "UART:read from NOBODY (length = %d)\n", length);
-           return -1;
+           PrintError(core->vm_info, core, "UART: read of unknown port %d (length = %d)\n", port, length);
+           ret = -1; 
     }
 
-    return length;
+    v3_unlock_irqrestore(com_port->lock, irq_status);
+
+    return ret;
 }
 
+
+static int deinit_serial_port(struct serial_port *);
+
 static int serial_free(struct serial_state * state) {
 
+    deinit_serial_port(&(state->coms[0]));
+    deinit_serial_port(&(state->coms[1]));
+    deinit_serial_port(&(state->coms[2]));
+    deinit_serial_port(&(state->coms[3]));
+
     V3_Free(state);
     return 0;
 }
@@ -938,6 +1396,8 @@ static int serial_save(struct v3_chkpt_ctx * ctx, void * private_data) {
     V3_CHKPT_SAVE(ctx, keyname, serial->dll.data,failout);
     snprintf(keyname, 128,"COM%d_DLM",i);
     V3_CHKPT_SAVE(ctx, keyname, serial->dlm.data,failout);
+    snprintf(keyname, 128,"COM%d_int_state",i);
+    V3_CHKPT_SAVE(ctx, keyname, serial->int_state,failout);
     
     if (serial_buffer_save(ctx, i, &(serial->tx_buffer), "TX")) { 
       PrintError(VM_NONE, VCORE_NONE, "Failed to save serial tx buffer %d\n",i);
@@ -993,7 +1453,9 @@ static int serial_load(struct v3_chkpt_ctx * ctx, void * private_data) {
     V3_CHKPT_LOAD(ctx, keyname, serial->dll.data,failout);
     snprintf(keyname, 128,"COM%d_DLM",i);
     V3_CHKPT_LOAD(ctx, keyname, serial->dlm.data,failout);
-    
+    snprintf(keyname, 128,"COM%d_int_state",i);
+    V3_CHKPT_LOAD(ctx, keyname, serial->int_state,failout);
+
     if (serial_buffer_load(ctx, i, &(serial->tx_buffer), "RX")) { 
       PrintError(VM_NONE, VCORE_NONE, "Failed to load serial tx buffer %d\n",i);
       goto failout;
@@ -1030,6 +1492,9 @@ static struct v3_device_ops dev_ops = {
 
 static int init_serial_port(struct serial_port * com) {
 
+    // zero all
+    memset(com, 0, sizeof(*com));
+
     com->ier.val = IER_INIT_VAL;
     com->iir.val = IIR_INIT_VAL;
     com->fcr.val = FCR_INIT_VAL;
@@ -1040,34 +1505,43 @@ static int init_serial_port(struct serial_port * com) {
 
     com->dll.data =  DLL_INIT_VAL;
     com->dlm.data =  DLM_INIT_VAL;
-    
-    com->tx_buffer.head = 0;
-    com->tx_buffer.tail = 0;
-    com->tx_buffer.full = 0;
-    memset(com->tx_buffer.buffer, 0, SERIAL_BUF_LEN);
-
-    com->rx_buffer.head = 0;
-    com->rx_buffer.tail = 0;
-    com->rx_buffer.full = 0;
-    memset(com->rx_buffer.buffer, 0, SERIAL_BUF_LEN);
-    
+
+    flush_buffer(&(com->tx_buffer));
+    flush_buffer(&(com->rx_buffer));
+
+    v3_lock_init(&(com->lock));
+
     com->ops = NULL;
     com->backend_data = NULL;
 
     return 0;
 }
 
-static uint64_t serial_input(struct v3_vm_info * vm, uint8_t * buf, uint64_t len, void * priv_data){
+static int deinit_serial_port(struct serial_port * com) {
+
+    v3_lock_deinit(&(com->lock));
+
+    return 0;
+}
+
+static sint64_t serial_input(struct v3_vm_info * vm, uint8_t * buf, sint64_t len, void * priv_data){
     struct serial_port * com_port = (struct serial_port *)priv_data;
     int i;
+    addr_t irq_status;
 
+    irq_status = v3_lock_irqsave(com_port->lock);
+    
     for(i = 0; i < len; i++){
-       queue_data(vm, com_port, &(com_port->rx_buffer), buf[i]);
+      if (queue_data(vm, com_port, &(com_port->rx_buffer), buf[i])) {
+       break;
+      }
     }
 
     updateIRQ(vm, com_port);
 
-    return len;
+    v3_unlock_irqrestore(com_port->lock,irq_status);
+
+    return i;
 }
 
 
@@ -1084,14 +1558,14 @@ static int connect_fn(struct v3_vm_info * vm,
     int com_idx = 0;
 
     if (com_port == NULL) {
-        PrintError(vm, VCORE_NONE, "Invalid Serial frontend config: missing \"com_port\"\n");
+        PrintError(vm, VCORE_NONE, "UART: Invalid Serial frontend config: missing \"com_port\"\n");
        return -1;
     }
     
     com_idx = atoi(com_port) - 1;
 
     if ((com_idx > 3) || (com_idx < 0)) {
-      PrintError(vm, VCORE_NONE, "Invalid Com port (%s) \n", com_port);
+      PrintError(vm, VCORE_NONE, "UART: Invalid Com port (%s) \n", com_port);
        return -1;
     }
 
@@ -1114,7 +1588,7 @@ static int serial_init(struct v3_vm_info * vm, v3_cfg_tree_t * cfg) {
     state = (struct serial_state *)V3_Malloc(sizeof(struct serial_state));
     
     if (state == NULL) {
-        PrintError(vm,VCORE_NONE, "Could not allocate Serial Device\n");
+        PrintError(vm,VCORE_NONE, "UART: Could not allocate Serial Device\n");
        return -1;
     }
     
@@ -1134,12 +1608,12 @@ static int serial_init(struct v3_vm_info * vm, v3_cfg_tree_t * cfg) {
     struct vm_device * dev = v3_add_device(vm, dev_id, &dev_ops, state);
 
     if (dev == NULL) {
-       PrintError(vm, VCORE_NONE, "Could not attach device %s\n", dev_id);
+       PrintError(vm, VCORE_NONE, "UART: Could not attach device %s\n", dev_id);
        V3_Free(state);
        return -1;
     }
 
-    PrintDebug(vm, VCORE_NONE, "Serial device attached\n");
+    PrintDebug(vm, VCORE_NONE, "UART: Serial device attached\n");
 
     ret |= v3_dev_hook_io(dev, COM1_DATA_PORT, &read_data_port, &write_data_port);
     ret |= v3_dev_hook_io(dev, COM1_IRQ_ENABLE_PORT, &read_ctrl_port, &write_ctrl_port);
@@ -1178,17 +1652,17 @@ static int serial_init(struct v3_vm_info * vm, v3_cfg_tree_t * cfg) {
     ret |= v3_dev_hook_io(dev, COM4_SCRATCH_PORT, &read_ctrl_port, &write_ctrl_port);
 
     if (ret != 0) {
-       PrintError(vm, VCORE_NONE, "Error hooking Serial IO ports\n");
+       PrintError(vm, VCORE_NONE, "UART: Error hooking Serial IO ports\n");
        v3_remove_device(dev);
        return -1;
     }
 
-    PrintDebug(vm, VCORE_NONE, "Serial ports hooked\n");
+    PrintDebug(vm, VCORE_NONE, "UART: Serial ports hooked\n");
 
 
 
     if (v3_dev_add_char_frontend(vm, dev_id, connect_fn, (void *)state) == -1) {
-       PrintError(vm, VCORE_NONE, "Could not register %s as frontend\n", dev_id);
+       PrintError(vm, VCORE_NONE, "UART: Could not register %s as frontend\n", dev_id);
        v3_remove_device(dev);
        return -1;
     }