Goto sanos source index

//
// serial.c
//
// RS-232 serial driver
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
//    notice, this list of conditions and the following disclaimer.  
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.  
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
// SUCH DAMAGE.
// 

#include <os/krnl.h>

#define QUEUE_SIZE      4096
#define ISR_LIMIT       256

//
// UART registers
//

#define UART_RX         0       // Receive buffer
#define UART_TX         0       // Transmit buffer
#define UART_DLL        0       // Divisor Latch Low

#define UART_DLH        1       // Divisor Latch High
#define UART_IER        1       // Interrupt Enable Register

#define UART_IIR        2       // Interrupt ID Register
#define UART_FCR        2       // FIFO Control Register

#define UART_LCR        3       // Line Control Register
#define UART_MCR        4       // Modem Control Register
#define UART_LSR        5       // Line Status Register
#define UART_MSR        6       // Modem Status Register
#define UART_SCR        7       // Scratch Register

//
// Interrupt Enable Register
//

#define IER_ERXRDY      0x01
#define IER_ETXRDY      0x02
#define IER_ERLS        0x04
#define IER_EMSC        0x08

//
// Interrupt Identification Register
//

#define IIR_IMASK       0x0F
#define IIR_NOPEND      0x01
#define IIR_MLSC        0x00
#define IIR_TXRDY       0x02
#define IIR_RXRDY       0x04
#define IIR_RLS         0x06
#define IIR_RXTOUT      0x0C
#define IIR_FIFO_MASK   0xC0    // Set if FIFOs are enabled

//
// FIFO Control Register
//

#define FCR_ENABLE      0x01
#define FCR_RCV_RST     0x02
#define FCR_XMT_RST     0x04
#define FCR_DMA_MODE    0x08
#define FCR_TRIGGER_1   0x00
#define FCR_TRIGGER_4   0x40
#define FCR_TRIGGER_8   0x80
#define FCR_TRIGGER_14  0xC0

//
// Line Control Register
//

#define LCR_DLAB        0x80
#define LCR_SBREAK      0x40
#define LCR_PZERO       0x30
#define LCR_PONE        0x20
#define LCR_PEVEN       0x10
#define LCR_PODD        0x00
#define LCR_PENAB       0x08
#define LCR_STOPB       0x04
#define LCR_8BITS       0x03
#define LCR_7BITS       0x02
#define LCR_6BITS       0x01
#define LCR_5BITS       0x00

//
// Modem Control Register
//

#define MCR_LOOPBACK    0x10
#define MCR_IENABLE     0x08
#define MCR_DRS         0x04
#define MCR_RTS         0x02
#define MCR_DTR         0x01

//
// Line Status Register
//

#define LSR_RCV_FIFO    0x80
#define LSR_TSRE        0x40
#define LSR_TXRDY       0x20
#define LSR_BI          0x10
#define LSR_FE          0x08
#define LSR_PE          0x04
#define LSR_OE          0x02
#define LSR_RXRDY       0x01
#define LSR_RCV_MASK    0x1F

//
// Modem Status Register
//

#define MSR_DCD         0x80
#define MSR_RI          0x40
#define MSR_DSR         0x20
#define MSR_CTS         0x10
#define MSR_DDCD        0x08
#define MSR_TERI        0x04
#define MSR_DDSR        0x02
#define MSR_DCTS        0x01

//
// UART types
//

#define UART_UNKNOWN    0
#define UART_8250       1
#define UART_16450      2
#define UART_16550      3
#define UART_16550A     4

static char *uart_name[] = {"(unknown)", "8250", "16450", "16550", "16550A"};
static int serial_default_irq[4] = {4, 3, 11, 10};
static int serial_default_iobase[4] = {0x3F8, 0x2F8, 0x3E8, 0x2E8};

int next_serial_portno = 1;

struct fifo {
  unsigned char queue[QUEUE_SIZE];
  int count;
  int head;
  int tail;
};

struct serial_port {
  int iobase;                            // I/O base address
  int irq;                               // Interrupt number
  int type;                              // UART type
  struct serial_config cfg;              // Configuration

  struct fifo rxq;                       // Receive queue
  struct sem rx_sem;                     // Receive queue semaphore
  struct mutex rx_lock;                  // Receiver lock
  int rx_queue_rel;                      // Receiver queue release count

  struct fifo txq;                       // Transmit queue
  struct sem tx_sem;                     // Transmit queue semaphore
  int tx_queue_rel;                      // Transmit queue release count
  struct mutex tx_lock;                  // Transmit lock
  int tx_busy;                           // Transmitter busy

  struct interrupt intr;                 // Serial interrupt object
  struct dpc dpc;                        // Serial DPC
  struct event event;                    // Line or modem status event
  int mlsc;                              // Model line status changed
  int rls;                               // Receiver line status changed
  int linestatus;                        // Line status
  
  unsigned char mcr;                     // Modem control register
  unsigned char msr;                     // Modem status register
};

void serial_dpc(void *arg);
static void drain_tx_queue(struct serial_port *sp);

static void fifo_clear(struct fifo *f) {
  f->head = 0;
  f->tail = 0;
  f->count = 0;
}

__inline static int fifo_put(struct fifo *f, unsigned char c) {
  if (f->count >= QUEUE_SIZE) return -ENOSPC;
  f->queue[f->head++] = c;
  f->count++;
  if (f->head >= QUEUE_SIZE) f->head = 0;

  return 0;
}

__inline static unsigned char fifo_get(struct fifo *f) {
  unsigned char c;

  if (f->count == 0) return 0;
  c = f->queue[f->tail++];
  f->count--;
  if (f->tail == QUEUE_SIZE) f->tail = 0;

  return c;
}

__inline static int fifo_empty(struct fifo *f) {
  return f->count == 0;
}

__inline static int fifo_full(struct fifo *f) {
  return f->count == QUEUE_SIZE;
}

static void check_uart_type(struct serial_port *sp) {
  unsigned char b;
  unsigned char t1;
  unsigned char t2;
  unsigned char t3;

  // Look to see if we can find any sort of FIFO response
  outp(sp->iobase + UART_FCR, FCR_ENABLE);
  b = (inp((unsigned short) (sp->iobase + UART_IIR)) & IIR_FIFO_MASK) >> 6;

  switch(b) {
    case 0:
      // No FIFO response is a 16450 or 8250.  The 8250
      // doesn't have a scratchpad register though.  We
      // make this test attempt to restore the original
      // scratchpad state
      sp->type = UART_16450;
      t3 = inp((unsigned short) (sp->iobase + UART_SCR));
      outp(sp->iobase + UART_SCR, 0xA5);
      t1 = inp((unsigned short) (sp->iobase + UART_SCR));
      outp(sp->iobase + UART_SCR, 0x5A);
      t2 = inp((unsigned short) (sp->iobase + UART_SCR));
      outp(sp->iobase + UART_SCR, t3);
      if (t1 != 0xA5 || t2 != 0x5A) sp->type = UART_8250;
      break;

    case 1:
      sp->type = UART_UNKNOWN;
      break;

    case 2:
      // This is the sort of broken response we get from an
      // early 16550 part with a broken FIFO
      sp->type = UART_16550;
      break;

    case 3:
      // We have a 16550A - working FIFOs
      sp->type = UART_16550A;
      break;
  }
}

static void serial_config(struct serial_port *sp) {
  int divisor;
  unsigned char lcr;

  // Set baudrate
  if (sp->cfg.speed == 0) {
    divisor = (1843200 / (16 * 9600));
  } else {
    divisor = (1843200 / (16 * sp->cfg.speed));
  }

  outp(sp->iobase + UART_LCR, LCR_DLAB);
  outp(sp->iobase + UART_DLH, (divisor >> 8) & 0xFF);
  outp(sp->iobase + UART_DLL, divisor & 0xFF);

  // Set line control register
  lcr = 0;

  switch (sp->cfg.parity) {
    case PARITY_NONE:  lcr |= 0; break;
    case PARITY_EVEN:  lcr |= LCR_PEVEN; break;
    case PARITY_ODD:   lcr |= LCR_PODD; break;
    case PARITY_MARK:  lcr |= LCR_PONE; break;
    case PARITY_SPACE: lcr |= LCR_PZERO; break;
  }

  switch (sp->cfg.stopbits) {
    case 1: lcr |= 0; break;
    case 2: lcr |= LCR_STOPB; break;
  }

  switch (sp->cfg.databits) {
    case 5: lcr |= LCR_5BITS; break;
    case 6: lcr |= LCR_6BITS; break;
    case 7: lcr |= LCR_7BITS; break;
    case 8: lcr |= LCR_8BITS; break;
  }

  outp(sp->iobase + UART_LCR, lcr);
}

static int serial_ioctl(struct dev *dev, int cmd, void *args, size_t size) {
  struct serial_port *sp = (struct serial_port *) dev->privdata;
  struct serial_status  *ss;

  switch (cmd) {
    case IOCTL_GETDEVSIZE:
      return 0;

    case IOCTL_GETBLKSIZE:
      return 1;

    case IOCTL_SERIAL_SETCONFIG:
      if (!args || size != sizeof(struct serial_config)) return -EINVAL;
      memcpy(&sp->cfg, args, sizeof(struct serial_config));
      serial_config(sp);
      return 0;

    case IOCTL_SERIAL_GETCONFIG:
      if (!args || size != sizeof(struct serial_config)) return -EINVAL;
      memcpy(args, &sp->cfg, sizeof(struct serial_config));
      return 0;

    case IOCTL_SERIAL_WAITEVENT:
      if (!args && size == 0) {
        return wait_for_object(&sp->event, INFINITE);
      } else if (args && size == 4) {
        return wait_for_object(&sp->event, *(unsigned int *) args);
      } else {
        return -EINVAL;
      }

    case IOCTL_SERIAL_STAT:
      if (!args || size != sizeof(struct serial_status)) return -EINVAL;
      ss = (struct serial_status *) args;
      ss->linestatus = sp->linestatus;
      sp->linestatus = 0;
      ss->modemstatus = inp((unsigned short) (sp->iobase + UART_MSR)) & 0xFF;
      ss->rx_queue_size = sp->rxq.count;
      ss->tx_queue_size = sp->txq.count;
      return 0;

    case IOCTL_SERIAL_DTR:
      if (!args || size != 4) return -EINVAL;
      
      if (*(int *) args) {
        sp->mcr |= MCR_DTR;
      } else {
        sp->mcr &= ~MCR_DTR;
      }

      outp(sp->iobase + UART_MCR, sp->mcr);
      return 0;

    case IOCTL_SERIAL_RTS:
      if (!args || size != 4) return -EINVAL;

      if (*(int *) args) {
        sp->mcr |= MCR_RTS;
      } else {
        sp->mcr &= ~MCR_RTS;
      }

      outp(sp->iobase + UART_MCR, sp->mcr);
      return 0;

    case IOCTL_SERIAL_FLUSH_TX_BUFFER:
      cli();
      fifo_clear(&sp->txq);
      set_sem(&sp->tx_sem, QUEUE_SIZE);
      sp->tx_queue_rel = 0;
      if (sp->type == UART_16550A) outp(sp->iobase + UART_FCR, FCR_ENABLE | FCR_XMT_RST | FCR_TRIGGER_14);
      sti();
      return 0;

    case IOCTL_SERIAL_FLUSH_RX_BUFFER:
      cli();
      fifo_clear(&sp->rxq);
      set_sem(&sp->rx_sem, 0);
      sp->rx_queue_rel = 0;
      if (sp->type == UART_16550A) outp(sp->iobase + UART_FCR, FCR_ENABLE | FCR_RCV_RST | FCR_TRIGGER_14);
      sti();
      return 0;
  }
  
  return -ENOSYS;
}

static int serial_read(struct dev *dev, void *buffer, size_t count, blkno_t blkno, int flags) {
  struct serial_port *sp = (struct serial_port *) dev->privdata;
  unsigned int n;
  unsigned char *bufp;

  if (wait_for_object(&sp->rx_lock, sp->cfg.rx_timeout) < 0) return -ETIMEOUT;

  bufp = (unsigned char *) buffer;
  for (n = 0; n < count; n++) {
    // Wait until rx queue is not empty
    if (wait_for_object(&sp->rx_sem, n == 0 ? sp->cfg.rx_timeout : 0) < 0) break;

    // Remove next char from receive queue
    cli();
    *bufp++ = fifo_get(&sp->rxq);
    sti();

    //kprintf("serial: read %02X\n", bufp[-1]);
  }

  release_mutex(&sp->rx_lock);
  return n;
}

static int serial_write(struct dev *dev, void *buffer, size_t count, blkno_t blkno, int flags) {
  struct serial_port *sp = (struct serial_port *) dev->privdata;
  unsigned int n;
  unsigned char *bufp;

  if (wait_for_object(&sp->tx_lock, sp->cfg.tx_timeout) < 0) return -ETIMEOUT;

  bufp = (unsigned char *) buffer;
  for (n = 0; n < count; n++) {
    // Wait until tx queue is not full
    if (wait_for_object(&sp->tx_sem, sp->cfg.tx_timeout) < 0) break;

    // Insert next char in transmit queue
    cli();
    fifo_put(&sp->txq, *bufp++);
    sti();

    //kprintf("serial: write %02X\n", bufp[-1]);
    //kprintf("fifo put: h:%d t:%d c:%d\n", sp->txq.head, sp->txq.tail, sp->txq.count);

    // If transmitter idle then queue a DPC to restart transmission
    //if (!sp->tx_busy) queue_dpc(&sp->dpc, serial_dpc, sp);

    // If transmitter idle then restart transmission
    if (!sp->tx_busy) drain_tx_queue(sp);
  }

  release_mutex(&sp->tx_lock);
  return count;
}

static void drain_tx_queue(struct serial_port *sp) {
  unsigned char lsr;
  unsigned char b;
  int count;

  count = 0;
  while (1) {
    cli();

    // Is UART ready to transmit next byte
    lsr = inp((unsigned short) (sp->iobase + UART_LSR));
    sp->linestatus |= (lsr & (LSR_OE | LSR_PE | LSR_FE | LSR_BI));
    //kprintf("drain_tx_queue: lsr=%02X\n", lsr);

    if (!(lsr & LSR_TXRDY)) {
      sti();
      break;
    }

    // Is tx queue empty
    if (fifo_empty(&sp->txq)) {
      sti();
      break;
    }

    // Get next byte from queue
    b = fifo_get(&sp->txq);
    //kprintf("fifo get: h:%d t:%d c:%d\n", sp->txq.head, sp->txq.tail, sp->txq.count);

    //kprintf("serial: xmit %02X (drain)\n", b);
    outp(sp->iobase + UART_TX, b);
    sp->tx_busy = 1;
    count++;
    sti();
  }

  // Release transmitter queue resources
  if (count > 0) release_sem(&sp->tx_sem, count);
}

static void serial_dpc(void *arg) {
  int tqr;
  int rqr;
  int mlsc;
  int rls;
  struct serial_port *sp = (struct serial_port *) arg;

  //kprintf("[sdpc]");

  // Release transmitter and receiver queue resources and
  // signal line or modem status change
  cli();
  tqr = sp->tx_queue_rel;
  sp->tx_queue_rel = 0;
  rqr = sp->rx_queue_rel;
  sp->rx_queue_rel = 0;
  mlsc = sp->mlsc;
  sp->mlsc = 0;
  rls = sp->rls;
  sp->rls = 0;
  sti();

  if (tqr > 0) release_sem(&sp->tx_sem, tqr);
  if (rqr > 0) release_sem(&sp->rx_sem, rqr);
  if (mlsc || rls) set_event(&sp->event);
}

static void serial_transmit(struct serial_port *sp) {
  unsigned char lsr;
  unsigned char b;

  while (1) {
    // Is UART ready to transmit next byte
    lsr = inp((unsigned short) (sp->iobase + UART_LSR));
    sp->linestatus |= (lsr & (LSR_OE | LSR_PE | LSR_FE | LSR_BI));
    //kprintf("serial_transmit: lsr=%02X\n", lsr);

    if (!(lsr & LSR_TXRDY)) break;

    // Is tx queue empty
    if (fifo_empty(&sp->txq)) {
      sp->tx_busy = 0;
      break;
    }

    // Get next byte from queue
    b = fifo_get(&sp->txq);
    //kprintf("fifo get: h:%d t:%d c:%d\n", sp->txq.head, sp->txq.tail, sp->txq.count);
    //kprintf("serial: xmit %02X\n", b);
    outp(sp->iobase + UART_TX, b);
    sp->tx_busy = 1;
    sp->tx_queue_rel++;
  }
}

static void serial_receive(struct serial_port *sp) {
  unsigned char lsr;
  unsigned char b;

  while (1) {
    // Is there any data ready in the UART
    lsr = inp((unsigned short) (sp->iobase + UART_LSR));
    sp->linestatus |= (lsr & (LSR_OE | LSR_PE | LSR_FE | LSR_BI));
    //kprintf("serial_receive: lsr=%02X\n", lsr);
    if (!(lsr & LSR_RXRDY)) break;

    // Get next byte from UART and insert in rx queue
    b = inp((unsigned short) (sp->iobase + UART_RX));
    //kprintf("serial: receive %02X\n", b);
    if (fifo_put(&sp->rxq, b) < 0) {
      kprintf("serial: rx queue overflow\n");
      sp->linestatus |= LINESTAT_OVERFLOW;
    } else {
     sp->rx_queue_rel++;
    }
  }
}

static int serial_handler(struct context *ctxt, void *arg) {
  struct serial_port *sp = (struct serial_port *) arg;
  unsigned char iir;
  unsigned char lsr;
  int boguscnt = ISR_LIMIT;

  while (1) {
    lsr = inp((unsigned short) (sp->iobase + UART_LSR));

    // If receiver ready drain FIFO
    if (lsr & LSR_RXRDY) serial_receive(sp);

    // If transmitter ready send next bytes from tx queue
    if (lsr & LSR_TXRDY) serial_transmit(sp);

    // Get interrupt identification register
    iir = inp((unsigned short) (sp->iobase + UART_IIR));
    //kprintf("[sisr %d %x]", sp->irq, iir);

    if (iir & IIR_NOPEND) break;

    switch (iir & IIR_IMASK) {
      case IIR_MLSC:
        // Modem status changed
        sp->msr = inp((unsigned short) (sp->iobase + UART_MSR));
        sp->mlsc = 1;
        break;

      case IIR_RLS:
        // Line status changed
        sp->linestatus |= (lsr & (LSR_OE | LSR_PE | LSR_FE | LSR_BI));
        sp->rls = 1;
        break;
    }

    if (--boguscnt < 0) {
      kprintf("serial: Too much work at interrupt, iir=0x%02x\n", iir);
      break;
    }
  }

  // Set OUT2 to enable interrupts
  outp(sp->iobase + UART_MCR, sp->mcr);

  queue_irq_dpc(&sp->dpc, serial_dpc, sp);
  eoi(sp->irq);

  return 0;
}

struct driver serial_driver = {
  "serial",
  DEV_TYPE_STREAM,
  serial_ioctl,
  serial_read,
  serial_write
};

static void init_serial_port(char *devname, int iobase, int irq, struct unit *unit) {
  struct serial_port *sp;
  dev_t devno;

  sp = (struct serial_port *) kmalloc(sizeof(struct serial_port));
  memset(sp, 0, sizeof(struct serial_port));

  sp->iobase = iobase;
  sp->irq = irq;

  sp->cfg.speed = 115200;
  sp->cfg.databits = 8;
  sp->cfg.parity = PARITY_NONE;
  sp->cfg.stopbits = 1;
  sp->cfg.rx_timeout = INFINITE;
  sp->cfg.tx_timeout = INFINITE;

  init_dpc(&sp->dpc);
  sp->dpc.flags |= DPC_NORAND;

  init_event(&sp->event, 0, 0);

  init_sem(&sp->tx_sem, QUEUE_SIZE);
  init_mutex(&sp->tx_lock, 0);

  init_sem(&sp->rx_sem, 0);
  init_mutex(&sp->rx_lock, 0);

  // Disable interrupts
  outp(sp->iobase + UART_IER, 0);

  // Determine UART type
  check_uart_type(sp);

  // Set baudrate, parity, databits and stopbits
  serial_config(sp);

  // Enable FIFO
  if (sp->type == UART_16550A) {
    outp(sp->iobase + UART_FCR, FCR_ENABLE | FCR_RCV_RST | FCR_XMT_RST | FCR_TRIGGER_14);
  }

  // Turn on DTR, RTS and OUT2
  sp->mcr = MCR_DTR | MCR_RTS | MCR_IENABLE;  
  outp(sp->iobase + UART_MCR, sp->mcr);

  // Create device
  devno = dev_make(devname, &serial_driver, unit, sp);

  // Enable interrupts
  register_interrupt(&sp->intr, IRQ2INTR(sp->irq), serial_handler, sp);
  enable_irq(sp->irq);
  outp((unsigned short) (sp->iobase + UART_IER), IER_ERXRDY | IER_ETXRDY | IER_ERLS | IER_EMSC);

  kprintf(KERN_INFO "%s: %s iobase 0x%x irq %d\n", device(devno)->name, uart_name[sp->type], sp->iobase, sp->irq);
}

int __declspec(dllexport) serial(struct unit *unit) {
  int iobase;
  int irq;
  char devname[8];

  iobase = get_unit_iobase(unit);
  irq = get_unit_irq(unit);

  if (iobase < 0) iobase = serial_default_iobase[next_serial_portno - 1];
  if (irq < 0) irq = serial_default_irq[next_serial_portno - 1];

  sprintf(devname, "com%d", next_serial_portno++);
  init_serial_port(devname, iobase, irq, unit);

  return 0;
}

void init_serial() {
  int port;
  int iobase;

  // Fallback to BIOS settings if PnP BIOS not present
  if (next_serial_portno == 1) {
    for (port = 0; port < 4; port++) {
      iobase = syspage->biosdata[port * 2] + (syspage->biosdata[port * 2 + 1] << 8);

      if (iobase != 0) {
        switch (port) {
          case 0: init_serial_port("com1", iobase, serial_default_irq[0], NULL); break;
          case 1: init_serial_port("com2", iobase, serial_default_irq[1], NULL); break;
          case 2: init_serial_port("com3", iobase, serial_default_irq[2], NULL); break;
          case 3: init_serial_port("com4", iobase, serial_default_irq[3], NULL); break;
        }
      }
    }
  }
}