dev: Use BitUnions and a RegisterBank in the Uart8250.

Change-Id: I139db4f08f9e6addfed4906ea6c49ee67439d30e
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/36818
Reviewed-by: Giacomo Travaglini <giacomo.travaglini@arm.com>
Maintainer: Giacomo Travaglini <giacomo.travaglini@arm.com>
Tested-by: kokoro <noreply+kokoro@google.com>
This commit is contained in:
Gabe Black
2020-10-30 04:03:01 -07:00
parent 3eb63b688f
commit 150fef8453
2 changed files with 291 additions and 184 deletions

View File

@@ -47,14 +47,14 @@ using namespace std;
void
Uart8250::processIntrEvent(int intrBit)
{
if (intrBit & IER) {
if (intrBit & registers.ier.get()) {
DPRINTF(Uart, "UART InterEvent, interrupting\n");
platform->postConsoleInt();
status |= intrBit;
lastTxInt = curTick();
}
else
} else {
DPRINTF(Uart, "UART InterEvent, not interrupting\n");
}
}
@@ -84,89 +84,141 @@ Uart8250::scheduleIntr(Event *event)
Uart8250::Uart8250(const Params &p)
: Uart(p, 8), IER(0), LCR(0), MCR(0), lastTxInt(0),
: Uart(p, 8), registers(this, name() + ".registers"), lastTxInt(0),
txIntrEvent([this]{ processIntrEvent(TX_INT); }, "TX"),
rxIntrEvent([this]{ processIntrEvent(RX_INT); }, "RX")
{
}
Uart8250::Registers::Registers(Uart8250 *uart, const std::string &new_name) :
RegisterBankLE(new_name, 0), rbrThr(rbr, thr), rbrThrDll(rbrThr, dll),
ierDlh(ier, dlh), iirFcr(iir, fcr)
{
rbr.reader(uart, &Uart8250::readRbr);
thr.writer(uart, &Uart8250::writeThr);
ier.writer(uart, &Uart8250::writeIer);
iir.reader(uart, &Uart8250::readIir);
lcr.writer([this](auto &reg, const auto &value) {
reg.update(value);
rbrThrDll.select(value.dlab);
ierDlh.select(value.dlab);
});
mcr.writer([](auto &reg, const auto &value) {
if (value == (UART_MCR_LOOP | 0x0A))
reg.update(0x9A);
});
lsr.readonly().
reader([device = uart->device](auto &reg) {
Lsr lsr = 0;
if (device->dataAvailable())
lsr.rdr = 1;
lsr.tbe = 1;
lsr.txEmpty = 1;
return lsr;
});
msr.readonly();
addRegisters({rbrThrDll, ierDlh, iirFcr, lcr, mcr, lsr, msr, sr});
}
uint8_t
Uart8250::readRbr(Register8 &reg)
{
uint8_t data = 0;
if (device->dataAvailable())
data = device->readData();
else
DPRINTF(Uart, "empty read of RX register\n");
status &= ~RX_INT;
platform->clearConsoleInt();
if (device->dataAvailable() && registers.ier.get().rdi)
scheduleIntr(&rxIntrEvent);
return data;
}
void
Uart8250::writeThr(Register8 &reg, const uint8_t &data)
{
device->writeData(data);
platform->clearConsoleInt();
status &= ~TX_INT;
if (registers.ier.get().thri)
scheduleIntr(&txIntrEvent);
}
Uart8250::Iir
Uart8250::readIir(Register<Iir> &reg)
{
DPRINTF(Uart, "IIR Read, status = %#x\n", (uint32_t)status);
Iir iir = 0;
if (status & RX_INT) {
// Rx data interrupt has a higher priority.
iir.id = (uint8_t)InterruptIds::Rx;
} else if (status & TX_INT) {
iir.id = (uint8_t)InterruptIds::Tx;
// Tx interrupts are cleared on IIR reads.
status &= ~TX_INT;
} else {
iir.pending = 1;
}
return iir;
}
void
Uart8250::writeIer(Register<Ier> &reg, const Ier &ier)
{
reg.update(ier);
if (ier.thri) {
DPRINTF(Uart, "IER: IER_THRI set, scheduling TX intrrupt\n");
if (curTick() - lastTxInt > 225 * SimClock::Int::ns) {
DPRINTF(Uart, "-- Interrupting Immediately... %d,%d\n",
curTick(), lastTxInt);
txIntrEvent.process();
} else {
DPRINTF(Uart, "-- Delaying interrupt... %d,%d\n",
curTick(), lastTxInt);
scheduleIntr(&txIntrEvent);
}
} else {
DPRINTF(Uart, "IER: IER_THRI cleared, descheduling TX intrrupt\n");
if (txIntrEvent.scheduled())
deschedule(txIntrEvent);
if (status & TX_INT)
platform->clearConsoleInt();
status &= ~TX_INT;
}
if (ier.rdi && device->dataAvailable()) {
DPRINTF(Uart, "IER: IER_RDI set, scheduling RX intrrupt\n");
scheduleIntr(&rxIntrEvent);
} else {
DPRINTF(Uart, "IER: IER_RDI cleared, descheduling RX intrrupt\n");
if (rxIntrEvent.scheduled())
deschedule(rxIntrEvent);
if (status & RX_INT)
platform->clearConsoleInt();
status &= ~RX_INT;
}
}
Tick
Uart8250::read(PacketPtr pkt)
{
assert(pkt->getAddr() >= pioAddr && pkt->getAddr() < pioAddr + pioSize);
assert(pkt->getSize() == 1);
Addr daddr = pkt->getAddr() - pioAddr;
DPRINTF(Uart, " read register %#x\n", daddr);
DPRINTF(Uart, "Read register %#x\n", daddr);
switch (daddr) {
case 0x0:
if (!(LCR & 0x80)) { // read byte
if (device->dataAvailable())
pkt->setRaw(device->readData());
else {
pkt->setRaw((uint8_t)0);
// A limited amount of these are ok.
DPRINTF(Uart, "empty read of RX register\n");
}
status &= ~RX_INT;
platform->clearConsoleInt();
registers.read(daddr, pkt->getPtr<void>(), pkt->getSize());
if (device->dataAvailable() && (IER & UART_IER_RDI))
scheduleIntr(&rxIntrEvent);
} else { // dll divisor latch
;
}
break;
case 0x1:
if (!(LCR & 0x80)) { // Intr Enable Register(IER)
pkt->setRaw(IER);
} else { // DLM divisor latch MSB
;
}
break;
case 0x2: // Intr Identification Register (IIR)
DPRINTF(Uart, "IIR Read, status = %#x\n", (uint32_t)status);
if (status & RX_INT) /* Rx data interrupt has a higher priority */
pkt->setRaw(IIR_RXID);
else if (status & TX_INT) {
pkt->setRaw(IIR_TXID);
//Tx interrupts are cleared on IIR reads
status &= ~TX_INT;
} else
pkt->setRaw(IIR_NOPEND);
break;
case 0x3: // Line Control Register (LCR)
pkt->setRaw(LCR);
break;
case 0x4: // Modem Control Register (MCR)
pkt->setRaw(MCR);
break;
case 0x5: // Line Status Register (LSR)
uint8_t lsr;
lsr = 0;
// check if there are any bytes to be read
if (device->dataAvailable())
lsr = UART_LSR_DR;
lsr |= UART_LSR_TEMT | UART_LSR_THRE;
pkt->setRaw(lsr);
break;
case 0x6: // Modem Status Register (MSR)
pkt->setRaw((uint8_t)0);
break;
case 0x7: // Scratch Register (SCR)
pkt->setRaw((uint8_t)0); // doesn't exist with at 8250.
break;
default:
panic("Tried to access a UART port that doesn't exist\n");
break;
}
/* uint32_t d32 = *data;
DPRINTF(Uart, "Register read to register %#x returned %#x\n", daddr, d32);
*/
pkt->makeAtomicResponse();
return pioDelay;
}
@@ -174,88 +226,13 @@ Uart8250::read(PacketPtr pkt)
Tick
Uart8250::write(PacketPtr pkt)
{
assert(pkt->getAddr() >= pioAddr && pkt->getAddr() < pioAddr + pioSize);
assert(pkt->getSize() == 1);
Addr daddr = pkt->getAddr() - pioAddr;
DPRINTF(Uart, " write register %#x value %#x\n", daddr,
DPRINTF(Uart, "Write register %#x value %#x\n", daddr,
pkt->getRaw<uint8_t>());
switch (daddr) {
case 0x0:
if (!(LCR & 0x80)) { // write byte
device->writeData(pkt->getRaw<uint8_t>());
platform->clearConsoleInt();
status &= ~TX_INT;
if (UART_IER_THRI & IER)
scheduleIntr(&txIntrEvent);
} else { // dll divisor latch
;
}
break;
case 0x1:
if (!(LCR & 0x80)) { // Intr Enable Register(IER)
IER = pkt->getRaw<uint8_t>();
if (UART_IER_THRI & IER)
{
DPRINTF(Uart,
"IER: IER_THRI set, scheduling TX intrrupt\n");
if (curTick() - lastTxInt > 225 * SimClock::Int::ns) {
DPRINTF(Uart, "-- Interrupting Immediately... %d,%d\n",
curTick(), lastTxInt);
txIntrEvent.process();
} else {
DPRINTF(Uart, "-- Delaying interrupt... %d,%d\n",
curTick(), lastTxInt);
scheduleIntr(&txIntrEvent);
}
}
else
{
DPRINTF(Uart, "IER: IER_THRI cleared, "
"descheduling TX intrrupt\n");
if (txIntrEvent.scheduled())
deschedule(txIntrEvent);
if (status & TX_INT)
platform->clearConsoleInt();
status &= ~TX_INT;
}
registers.write(daddr, pkt->getPtr<void>(), pkt->getSize());
if ((UART_IER_RDI & IER) && device->dataAvailable()) {
DPRINTF(Uart,
"IER: IER_RDI set, scheduling RX intrrupt\n");
scheduleIntr(&rxIntrEvent);
} else {
DPRINTF(Uart, "IER: IER_RDI cleared, "
"descheduling RX intrrupt\n");
if (rxIntrEvent.scheduled())
deschedule(rxIntrEvent);
if (status & RX_INT)
platform->clearConsoleInt();
status &= ~RX_INT;
}
} else { // DLM divisor latch MSB
;
}
break;
case 0x2: // FIFO Control Register (FCR)
break;
case 0x3: // Line Control Register (LCR)
LCR = pkt->getRaw<uint8_t>();
break;
case 0x4: // Modem Control Register (MCR)
if (pkt->getRaw<uint8_t>() == (UART_MCR_LOOP | 0x0A))
MCR = 0x9A;
break;
case 0x7: // Scratch Register (SCR)
// We are emulating a 8250 so we don't have a scratch reg
break;
default:
panic("Tried to access a UART port that doesn't exist\n");
break;
}
pkt->makeAtomicResponse();
return pioDelay;
}
@@ -263,9 +240,8 @@ Uart8250::write(PacketPtr pkt)
void
Uart8250::dataAvailable()
{
// if the kernel wants an interrupt when we have data
if (IER & UART_IER_RDI)
{
// If the kernel wants an interrupt when we have data.
if (registers.ier.get().rdi) {
platform->postConsoleInt();
status |= RX_INT;
}
@@ -284,9 +260,9 @@ void
Uart8250::serialize(CheckpointOut &cp) const
{
SERIALIZE_SCALAR(status);
SERIALIZE_SCALAR(IER);
SERIALIZE_SCALAR(LCR);
SERIALIZE_SCALAR(MCR);
paramOut(cp, "IER", registers.ier);
paramOut(cp, "LCR", registers.lcr);
paramOut(cp, "MCR", registers.mcr);
Tick rxintrwhen;
if (rxIntrEvent.scheduled())
rxintrwhen = rxIntrEvent.when();
@@ -305,9 +281,9 @@ void
Uart8250::unserialize(CheckpointIn &cp)
{
UNSERIALIZE_SCALAR(status);
UNSERIALIZE_SCALAR(IER);
UNSERIALIZE_SCALAR(LCR);
UNSERIALIZE_SCALAR(MCR);
paramIn(cp, "IER", registers.ier);
paramIn(cp, "LCR", registers.lcr);
paramIn(cp, "MCR", registers.mcr);
Tick rxintrwhen;
Tick txintrwhen;
UNSERIALIZE_SCALAR(rxintrwhen);

View File

@@ -33,42 +33,173 @@
#ifndef __DEV_UART8250_HH__
#define __DEV_UART8250_HH__
#include "base/bitunion.hh"
#include "base/logging.hh"
#include "dev/io_device.hh"
#include "dev/reg_bank.hh"
#include "dev/serial/uart.hh"
#include "params/Uart8250.hh"
/* UART8250 Interrupt ID Register
* bit 0 Interrupt Pending 0 = true, 1 = false
* bit 2:1 ID of highest priority interrupt
* bit 7:3 zeroes
*/
const uint8_t IIR_NOPEND = 0x1;
// Interrupt IDs
const uint8_t IIR_MODEM = 0x00; /* Modem Status (lowest priority) */
const uint8_t IIR_TXID = 0x02; /* Tx Data */
const uint8_t IIR_RXID = 0x04; /* Rx Data */
const uint8_t IIR_LINE = 0x06; /* Rx Line Status (highest priority)*/
const uint8_t UART_IER_RDI = 0x01;
const uint8_t UART_IER_THRI = 0x02;
const uint8_t UART_IER_RLSI = 0x04;
const uint8_t UART_LSR_TEMT = 0x40;
const uint8_t UART_LSR_THRE = 0x20;
const uint8_t UART_LSR_DR = 0x01;
const uint8_t UART_MCR_LOOP = 0x10;
class Terminal;
class Platform;
class Uart8250 : public Uart
{
protected:
uint8_t IER, LCR, MCR;
BitUnion8(Ier)
Bitfield<0> rdi; // Receive data available interrupt.
Bitfield<1> thri; // Transmit holding register interrupt.
Bitfield<2> rlsi; // Receive line status interrupt.
Bitfield<3> msi; // Modem status interrupt.
EndBitUnion(Ier)
BitUnion8(Iir)
Bitfield<0> pending; // 0 = pending, 1 = not pending.
Bitfield<2, 1> id; // ID of highest priority interrupt.
Bitfield<7, 3> zeroes;
EndBitUnion(Iir)
BitUnion8(Lcr)
Bitfield<1, 0> wordSize;
Bitfield<2> stopBits;
Bitfield<5, 3> parity;
Bitfield<6> breakCont;
Bitfield<7> dlab;
EndBitUnion(Lcr)
BitUnion8(Lsr)
Bitfield<0> rdr; // Received data ready?
Bitfield<1> overrunError;
Bitfield<2> parityError;
Bitfield<3> framingError;
Bitfield<4> breakCond;
Bitfield<5> tbe; // Transmit buffer empty.
Bitfield<6> txEmpty; // Transmitter empty.
Bitfield<7> unused;
EndBitUnion(Lsr)
enum class InterruptIds {
Modem = 0, // Modem Status (lowest priority).
Tx = 1, // Tx Data.
Rx = 2, // Rx Data.
Line = 3, // Rx Line Status (highest priority).
};
class Registers : public RegisterBankLE
{
public:
Registers(Uart8250 *uart, const std::string &new_name);
class PairedRegister : public RegisterBase
{
protected:
RegisterBase &_reg1, &_reg2;
public:
PairedRegister(RegisterBase &reg1, RegisterBase &reg2) :
RegisterBase(reg1.name() + "/" + reg2.name(), reg1.size()),
_reg1(reg1), _reg2(reg2)
{
panic_if(reg1.size() != reg2.size(),
"Mismatched paired register sizes %d, %d",
reg1.size(), reg2.size());
}
void serialize(std::ostream &os) const override {}
bool unserialize(const std::string &s) override { return true; }
};
class BankedRegister : public PairedRegister
{
private:
RegisterBase *selected = nullptr;
public:
BankedRegister(RegisterBase &reg1, RegisterBase &reg2) :
PairedRegister(reg1, reg2), selected(&reg1)
{}
void select(bool second) { selected = second ? &_reg2 : &_reg1; }
const std::string &
name() const override
{
return selected->name();
}
void read(void *buf) override { selected->read(buf); }
void
read(void *buf, off_t offset, size_t bytes) override
{
selected->read(buf, offset, bytes);
}
void write(const void *buf) override { selected->write(buf); }
void
write(const void *buf, off_t offset, size_t bytes) override
{
selected->write(buf, offset, bytes);
}
};
class RWSwitchedRegister : public PairedRegister
{
public:
using PairedRegister::PairedRegister;
void read(void *buf) override { _reg1.read(buf); }
void
read(void *buf, off_t offset, size_t bytes) override
{
_reg1.read(buf, offset, bytes);
}
void write(const void *buf) override { _reg2.write(buf); }
void
write(const void *buf, off_t offset, size_t bytes) override
{
_reg2.write(buf, offset, bytes);
}
};
// Offset 0.
Register8 rbr = {"rbr"};
Register8 thr = {"thr"};
RWSwitchedRegister rbrThr;
Register8 dll = {"dll"};
BankedRegister rbrThrDll;
// Offset 1.
Register<Ier> ier = {"ier", 0};
Register8 dlh = {"dlh"};
BankedRegister ierDlh;
// Offset 2.
Register<Iir> iir = {"iir"};
Register8 fcr = {"fcr"};
RWSwitchedRegister iirFcr;
// Offsets 3 - 6.
Register<Lcr> lcr = {"lcr"};
Register8 mcr = {"mcr"};
Register<Lsr> lsr = {"lsr"};
Register8 msr = {"msr"};
// The scratch register didn't exist on the 8250.
RegisterRaz sr = {"sr", 1};
};
using Register8 = Registers::Register8;
template <class T>
using Register = Registers::Register<T>;
Registers registers;
uint8_t readRbr(Register8 &reg);
void writeThr(Register8 &reg, const uint8_t &data);
void writeIer(Register<Ier> &reg, const Ier &ier);
Iir readIir(Register<Iir> &reg);
Tick lastTxInt;
void processIntrEvent(int intrBit);