Initial commit of OpenSPARC T2 architecture model.
[OpenSPARC-T2-SAM] / sam-t2 / sam / devices / serial / serial.cc
// ========== Copyright Header Begin ==========================================
//
// OpenSPARC T2 Processor File: serial.cc
// Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
// DO NOT ALTER OR REMOVE COPYRIGHT NOTICES.
//
// The above named program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License version 2 as published by the Free Software Foundation.
//
// The above named 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 work; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
//
// ========== Copyright Header End ============================================
/*
* Copyright (C) 1991, 2001 Sun Microsystems, Inc.
* All rights reserved.
*/
#pragma ident "@(#)1.11 04/08/04 serial.cc"
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include "types.h"
#include "blaze_globals.h"
#include "system.h"
#include "dr.h"
#include "term.h"
#include "serial.h"
#include "serial_impl.h"
typedef void* TM_OPAQUE_DATA;
#include "tracemod.h"
#include "serial_mod.h"
#define BYTE_OFFSET 7
extern Serial *systemConsole;;
/*
* This function is to allocate a serial object
*/
serialT *Serial::allocation_obj()
{
msp = (serialT *) calloc(1, sizeof(serialT));
if (msp == NULL) {
debug_err("%s: malloc serial object failed\n", HERE);
return NULL;
}
reset_scc();
return (msp);
}
/*
* This routine is called to receive requests to read and write the serial's
* registers. Return a status of 1 if error
*/
int
Serial::reg_access(char *buf, uint64_t paddr, bool wr)
{
uint8_t *reg_value = (uint8_t *) (buf + BYTE_OFFSET); /* value of register read */
uint32_t addr = (uint32_t) paddr & SERIAL_PORT_ADDR_MASK;
if (!wr) {
switch (addr) {
case PORT_A_CONTROL:
*reg_value = rd_control(PORT_A);
break;
case PORT_B_CONTROL:
*reg_value = rd_control(PORT_B);
break;
case PORT_A_DATA:
*reg_value = rd_port(PORT_A);
break;
case PORT_B_DATA:
*reg_value = rd_port(PORT_B);
break;
default:
printf("%s: read invalid address 0x%lx\n", HERE, addr);
debug_err("%s: read invalid address 0x%lx\n", HERE, addr);
return 1;
}
} else {
switch (addr) {
case PORT_A_CONTROL:
wr_control(PORT_A,
*(uint8_t *) (buf + BYTE_OFFSET));
break;
case PORT_B_CONTROL:
wr_control(PORT_B,
*(uint8_t *) (buf + BYTE_OFFSET));
break;
case PORT_A_DATA:
wr_port(PORT_A,
*(uint8_t *) (buf + BYTE_OFFSET));
break;
case PORT_B_DATA:
wr_port(PORT_B,
*(uint8_t *) (buf + BYTE_OFFSET));
break;
default:
printf("%s: write invalid address 0x%lx\n", HERE,
paddr);
debug_err("%s: write invalid address 0x%lx\n", HERE,
paddr);
return 1;
}
}
return 0;
}
/*
* This routine is called to receive characters as they arrive. It queues
* them up in the serial's internal queue.
*/
void
Serial::char_input(uint8_t c, int portnum)
{
channel_regs *rcv_regs;
rcv_regs = &(msp->scc.port[portnum]);
enq(rcv_regs, c);
/* turn on the "ready to receive" flag */
rcv_regs->rr[0] |= RX_CHAR_AVAILABLE;
if (INTERRUPTS_ENABLED(msp, portnum) && !RCV_INT_PENDING(msp, portnum)
&& RCV_INT_ENABLED(msp, portnum) && rcv_regs->qcnt) {
/*
* Update status reg - Interrupt Pending Send interrupt
*/
channel_regs *channelA = &(msp->scc.port[PORT_A]);
channel_regs *channelB = &(msp->scc.port[PORT_B]);
/*
* if we're generating interrupts in Vector Include Status mode,
* the interrupt vector on channelB includes status in the lower
* nibble which is characteristic of the event causing the
* interrupt.
*/
if (rcv_regs->wr[9] & VIS) {
/*
* if no interrupts are pending, clear out
* vector include status bits before or'ing in new state
* (channelA.rr3) is the interrupt pending register
* (channelB.rr2) is the interrupt vector (VIS) register
*/
if (!channelA->rr[3])
/* clearing idle "special rcv condition" bits */
channelB->rr[2] &= ~VIS_LO_MASK;
/* add "Rx ready" to status vector */
SET_VIS_RX_CHAR_AVAIL(msp, portnum);
}
SET_RCV_INT_PENDING(msp, portnum);
//dev_interrupt_in(TRUE,device, 0);
pciMaster_set_int(0,true);
}
}
/*
* Add character 'c' to end of port receive queue.
*/
void
Serial::enq(channel_regs * rcv_regs, char c)
{
squeue *q, *cursor;
if ((q = (squeue *) calloc(1, sizeof(squeue))) == NULL) {
assert(0);
/* fatal("serial: enq: Unable to malloc.\n"); */
}
q->c = c;
q->next = NULL;
mutex_lock(&rcv_regs->serial_lock);
cursor = &rcv_regs->q;
while (cursor->next) {
cursor = cursor->next;
}
cursor->next = q;
rcv_regs->qcnt++;
mutex_unlock(&rcv_regs->serial_lock);
}
/*
* Remove character from front of queue and return it. If no char, return
* the NULL character.
*/
char
Serial::deq(channel_regs * rcv_regs)
{
squeue *cursor;
char c = '\0'; /* still return 0 if nothing here */
mutex_lock(&rcv_regs->serial_lock);
if (rcv_regs->qcnt) {
cursor = &rcv_regs->q;
if (cursor->next) {
cursor = cursor->next;
c = cursor->c;
rcv_regs->q.next = cursor->next;
rcv_regs->qcnt--;
free((char *) cursor);
} else {
assert(0);
/* fatal("serial: deq: qcnt inconsistent"); */
}
}
mutex_unlock(&rcv_regs->serial_lock);
return (c);
}
char
Serial::rd_port(int portnum)
{
char c;
channel_regs *rcv_regs;
rcv_regs = &(msp->scc.port[portnum]);
c = deq(rcv_regs);
/* turn off "ready to receive" flag if we're out of characters */
if (rcv_regs->qcnt == 0) {
rcv_regs->rr[0] &= ~RX_CHAR_AVAILABLE;
}
if (RCV_INT_PENDING(msp, portnum) && !(rcv_regs->qcnt)) {
CLEAR_RCV_INT_PENDING(msp, portnum);
//dev_interrupt_in(FALSE, device, 0);
pciMaster_set_int(0,false);
}
return c;
}
void
Serial::wr_port(int portnum, uint8_t c)
{
channel_regs *xmit_regs = &(msp->scc.port[portnum]);
xmit_regs->rr[0] &= ~TX_READY;
xmit_regs->rr[1] &= ~ALL_SENT;
xmit_regs->wr[8] = c;
/*
* Note! the transmit & subsequent status posting operation
* may need to be delayed an indeterminate number of cycles
* for kernel/zs driver purposes.
* ...leaving it as a synchronous function call for now.
*/
transmit(portnum);
}
uint8_t
Serial::rd_control(int portnum)
{
AB_Regs_t *sccp = &msp->scc;
short read_reg;
/*--------------------------------------------------------
* Register Addressing
*
* WR0(D5-D3) WR0 RR
* (Point High) D2 D1 D0
* NOT 001 0 0 0 0
* " 0 0 1 1
* " 0 1 0 2
* " 0 1 1 3
* " 1 0 0 0
* " 1 0 1 1
* " 1 1 0 2
* " 1 1 1 3
* 001 0 0 0 data (not implemented in this func)
* 001 0 0 1 -
* 001 0 1 0 10
* 001 0 1 1 15
* 001 1 0 0 12
* 001 1 0 1 13
* 001 1 1 0 10
* 001 1 1 1 15
*------------------------------------------------------
*/
/* Read WR0 (D2-D0) */
read_reg = (sccp->port[portnum].wr[0] & REG_NUMBER);
if ((sccp->port[portnum].wr[0] & WR0_BITS3_5) != POINT_HIGH) {
read_reg &= REGS_0_3;
} else {
if (read_reg & REG_2)
read_reg = 10;
else if (read_reg & REG_3)
read_reg = 15;
else if (read_reg == 1) {
debug_err("%s: rd_control: POINT_HIGH and WR0 D2-D0 is 1.\nRequest ignored.\n", HERE);
return (0);
} else {
read_reg += 8;
}
}
/*
* If just read a non-zero register, clear D2-D0 so it points to WR0
* or RR0 again
*/
if (read_reg != 0)
sccp->port[portnum].wr[0] &= WR0_BITS0_4;
return (sccp->port[portnum].rr[read_reg]);
}
void
Serial::wr_control(int portnum, unsigned char buf)
{
AB_Regs_t *sccp = &msp->scc;
short write_reg;
/*--------------------------------------------------------
* Register Addressing
*
* WR0(D5-D3) WR0 WR
* (Point High) D2 D1 D0
* NOT 001 0 0 0 0 (comand register)
* " 0 0 1 1
* " 0 1 0 2
* " 0 1 1 3
* " 1 0 0 4
* " 1 0 1 5
* " 1 1 0 6
* " 1 1 1 7
* 001 0 0 0 data (DATA request - not in this func)
* 001 0 0 1 9
* 001 0 1 0 10
* 001 0 1 1 11
* 001 1 0 0 12
* 001 1 0 1 13
* 001 1 1 0 14
* 001 1 1 1 15
*------------------------------------------------------
*/
/* Read WR0 D2-D0 */
write_reg = (sccp->port[portnum].wr[0] & REG_NUMBER);
if ((sccp->port[portnum].wr[0] & WR0_BITS3_5) == POINT_HIGH) {
if (write_reg == 0) {
/* should never get here */
debug_err("%s: wr_control: POINT_HIGH and WR0 D2-D0 is 0. Request Ignored.\n", HERE);
return;
}
write_reg += 8;
}
sccp->port[portnum].wr[write_reg] = buf;
/*
* WR0 - If a channel command is being requested,
* perform necessary house cleaning
*/
if (sccp->port[portnum].wr[0] & WR0_BITS4_5)
channel_cmd(portnum);
/*
* WR2 and WR9 are shared by the two channels.
*/
if (write_reg == 2 || write_reg == 9) {
int otherport;
if (portnum == PORT_A) {
otherport = PORT_B;
} else if (portnum == PORT_B) {
otherport = PORT_A;
}
sccp->port[otherport].wr[write_reg] = buf;
}
/* WR5 transmit parameter & control */
if (write_reg == 5) {
/* if DTR than set CD */
if (buf & DTR)
sccp->port[portnum].rr[0] |= DCD;
/* if RTS than set CTS */
if (buf & RTS)
sccp->port[portnum].rr[0] |= CTS;
/* for status update purposes ... */
if (buf & (DTR | RTS))
status(portnum);
}
/* RR2,RR12,RR13,RR15 reflect the values in the write registers */
if ((write_reg == 2) || (write_reg == 12) || (write_reg == 13) ||
(write_reg == 15))
sccp->port[portnum].rr[write_reg] = buf;
/*
* If just written a non-zero register, clear D2-D0 so it points to
* WR0 or RR0 again
*/
if (write_reg != 0)
sccp->port[portnum].wr[0] &= WR0_BITS0_4;
}
void
Serial::reset_scc()
{
short port, i;
for (port = 0; port < NUM_PORTS; port++) {
/* Clear out all registers for port. */
msp->scc.port[port].qcnt = 0;
msp->scc.port[port].q.next = NULL;
for (i = 0; i < SERIAL_REGS; i++) {
msp->scc.port[port].wr[i] = 0;
msp->scc.port[port].rr[i] = 0;
}
/* set "ready to transmit" */
msp->scc.port[port].rr[0] = TX_READY;
/* set "all sent" */
msp->scc.port[port].rr[1] = ALL_SENT;
/*
* set to the "special receive condition" whose need
* was determined by the kernel zs bringup efforts.
*/
msp->scc.port[port].rr[2] = SRC;
mutex_init(&(msp->scc.port[port].serial_lock), USYNC_THREAD,
(void *) NULL);
}
/*
* Set interrrupts enabled for port A if there is an interrupt
* interface, otherwise leave them disabled.
*/
/*
* The state of WR1 after reset is all interrrupts disabled.
* The state of WR1 after reset is bitwise: <00x00x00>
* see pg 7-4; in the AMD Z85C30/SCC tech manual
*
* if (msp->intr_intf) {
* SET_INTERRUPTS_ENABLE(msp, PORT_A);
* SET_RCV_INT_ENABLE(msp, PORT_A);
* }
*/
}
void
Serial::transmit(int portnum)
{
channel_regs *xmit_regs = &(msp->scc.port[portnum]);
/* transmit the data character */
term_write(&xmit_regs->wr[8],portHandle[portnum]);
//(msp == console_msp) ? portnum:portnum+2);
xmit_regs->rr[0] |= TX_READY;
xmit_regs->rr[1] |= ALL_SENT;
if (TX_INT_ENABLED(msp, portnum)) {
channel_regs *channelA = &(msp->scc.port[PORT_A]);
channel_regs *channelB = &(msp->scc.port[PORT_B]);
/*
* if generating interrupts in Vector Include Status mode,
* the interrupt vector on channelB includes status in the
* lower nibble which is characteristic of the event
* causing the interrupt.
*/
if (xmit_regs->wr[9] & VIS) {
/*
* if no interrupts are pending, clear out
* vector include status bits before or'ing in new state
* (channelA.rr3) is the interrupt pending register
* (channelB.rr2) is the interrupt vector (VIS) register
*/
if (!channelA->rr[3])
/* clearing idle "special rcv condition" bits */
channelB->rr[2] &= ~VIS_LO_MASK;
/* add "Tx ready" to status vector */
SET_VIS_TX_BUF_EMPTY(msp, portnum);
}
/* assert serial interrupt */
SET_TX_INT_PENDING(msp, portnum);
//dev_interrupt_in(TRUE, device, 0);
pciMaster_set_int(0,true);
}
}
/*
* Channel Command Register (WR0)
* - executes various channel/port commands
*/
void
Serial::channel_cmd(int portnum)
{
channel_regs *channelp = &(msp->scc.port[portnum]);
channel_regs *channelA = &(msp->scc.port[PORT_A]);
channel_regs *channelB = &(msp->scc.port[PORT_B]);
int otherport;
if (portnum == PORT_A) {
otherport = PORT_B;
} else if (portnum == PORT_B) {
otherport = PORT_A;
}
/*
* individual reset functions used by
* kernel zs drvier:
* reset external/status interrupts (0x10)
* reset TxINT pending (0x28)
* error reset (0x30)
* reset highest Interrupt Under Service (0x38)
*/
switch (channelp->wr[0] & CMDCODE_MASK) {
case ENABLE_RX_INT:
/*
* enable int on next Rx, used for 1st receive interrupt
* ignore for now !
*/
break;
case RESET_XS_INT:
/*
* reset external/status interrupts (0x10)
* (channelB RR2) is the interrupt vector (VIS) register
* (channelA RR3) is the interrupt pending register
*/
/* reset ext/status interrrupt */
CLEAR_XS_INT_PENDING(msp, portnum);
/*
* if no XS interrupts pending for other channel,
* clear Ext/Status Vector Include Status bit
*/
if (!XS_INT_PENDING(msp, otherport))
CLEAR_VIS_XS_CHANGE(msp);
break;
case RESET_TX_INT:
/*
* Reset TxINT Pending (0x28)
* (channelB RR2) is the interrupt vector (VIS) register
* (channelA RR3) is the interrupt pending register
*/
/* reset transmit interrrupt */
CLEAR_TX_INT_PENDING(msp, portnum);
break;
case RESET_ERROR:
/*
* error reset (0x30)
*/
channelB->rr[2] = 0;
channelA->rr[3] = 0;
break;
case RESET_IUS:
/*
* Reset Highest Interrupt Under Service command (0x38),
* clear respective bits in interrupt vector (RR2),
* and the interrupt pending register (RR3).
* Note, only channelB contains VIS bits, and
* only channelA contains the IP bits.
*/
/* clear the highest pending interrupt */
if (RCV_INT_PENDING(msp, portnum)) {
CLEAR_RCV_INT_PENDING(msp, portnum);
/*
* if no Rx interrupts pending for other channel,
* clear Rx Vector Include Status bit
*/
if (!RCV_INT_PENDING(msp, otherport))
CLEAR_VIS_RX_CHAR_AVAIL(msp);
} else if (TX_INT_PENDING(msp, portnum)) {
CLEAR_TX_INT_PENDING(msp, portnum);
/*
* no VIS bits to clear for Tx interrupts
*/
} else if (XS_INT_PENDING(msp, portnum)) {
CLEAR_XS_INT_PENDING(msp, portnum);
/*
* if no XS interrupts pending for other channel,
* clear Ext/Status Vector Include Status bit
*/
if (!XS_INT_PENDING(msp, otherport))
CLEAR_VIS_XS_CHANGE(msp);
}
break;
default:
debug_err("%s: blaze warning: unsupported SCC/wr[0] command 0x%x\n", HERE,
channelp->wr[0]);
}
/*
* if no more interrupts pending for channelA,
* clear the Vector Include Status channel bit
*/
if (!(channelA->rr[3] & A_IP_MASK))
channelB->rr[2] &= ~CHAN_A;
/*
* if no interrupts are pending in RR3, de-assert the serial interrupt.
* if WR9 (master interrupt contorl) is in VIS mode,
* force the VIS bits to be: V3,V2,V1 = 011 "special receive condition"
* (...we currently *ONLY* support status low bits)
*
* (channelB RR2) is the interrupt vector (VIS) register
* (channelA RR3) is the interrupt pending register
*/
if (channelA->rr[3] == 0) {
if (channelp->wr[9] & VIS)
channelB->rr[2] = SRC; /* special receive condition */
/* Deasserting the interrupt */
//dev_interrupt_in(FALSE,device, 0);
pciMaster_set_int(0,false);
}
/*
* After a reset request has been performed,
* clear the WR0 (command register)
* (including special resets, point high, and register indexes, ...)
*/
channelp->wr[0] = 0;
}
/*
* Get the serial status
*/
void
Serial::status(int portnum)
{
channel_regs *channelp = &(msp->scc.port[portnum]);
/* if External/Status Master Interrupt Enable'd */
if (channelp->wr[1] & XS_INT_ENABLED) {
channel_regs *channelA = &(msp->scc.port[PORT_A]);
channel_regs *channelB = &(msp->scc.port[PORT_B]);
/*
* if we're generating interrupts in Vector Include Status
* mode, the interrupt vector on channelB includes status
* in the lower nibble which is characteristic of the
* event causing the interrupt.
*/
if (channelp->wr[9] & VIS) {
/*
* if no interrupts are pending, clear out vector
* include status bits before or'ing in new state
* (channelA.rr3) is the interrupt pending register
* (channelB.rr2) is the interrupt vector (VIS) register
*/
if (!channelA->rr[3])
/* clearing idle "special rcv condition" bits */
channelB->rr[2] &= ~VIS_LO_MASK;
/* add "external/status change" to status vector */
SET_VIS_XS_CHANGE(msp, portnum);
}
/* assert "external/status change" in rr3 */
SET_XS_INT_PENDING(msp, portnum);
/* assert serial interrupt */
//dev_interrupt_in(TRUE, device, 0);
pciMaster_set_int(0,true);
}
}
//
// Insert the following string into the incomming stream for PORTA, the
// console.
//
void
Serial::chars_send(char *str, int portHandle)
{
int len = (int)strlen(str);
int i;
int portnum = mapHandle2Port(portHandle);
for (i = 0; i < len; i++) {
/* Jam them one at a time into the input buffer */
char_input(str[i], portnum);
}
}
bool
Serial::dump(FILE* fp)
{
int portnum;
struct channel_regs *rcv_regs;
debug_err("%s: dump\n", HERE);
if (fwrite((char*)msp, sizeof(serial_structT), 1, fp) != 1) {
debug_err("%s: serial_dump: fwrite failed\n", HERE);
return FALSE;
}
/* Dump queue of chars waiting on each port. */
for (portnum = 0 ; portnum < NUM_PORTS ; portnum++) {
rcv_regs = &(msp->scc.port[portnum]);
while (rcv_regs->qcnt) {
putc(deq(rcv_regs), fp);
}
}
return genericPciDev::dump(DR_get_dir(),getName());
}
bool
Serial::restore(FILE* fp)
{
int portnum;
struct channel_regs *rcv_regs;
int count;
if (fread((char*)msp, sizeof(serial_structT), 1, fp) != 1) {
debug_err("%s: serial_restore: fread failed\n", HERE);
return FALSE;
}
/* Restore queue of chars waiting on each port. */
for (portnum = 0 ; portnum < NUM_PORTS ; portnum++) {
rcv_regs = &(msp->scc.port[portnum]);
/*
* # chars to load is in qcnt of state. Load them in but
* first set queue to empty state since the characters
* really aren't there yet.
*/
count = rcv_regs->qcnt; /* save count */
rcv_regs->qcnt = 0;
rcv_regs->q.next = NULL;
while (count--) {
enq(rcv_regs, getc(fp));
}
}
if (dump_version() >= 5)
return genericPciDev::restore(DR_get_dir(),getName());
else {
// slot_irl[0] = 54;
return TRUE;
}
}