blob: 03cc4454a344b6aa1ce274252fc0ea8e6b8381d3 [file] [log] [blame]
#include "kvm/8250-serial.h"
#include "kvm/read-write.h"
#include "kvm/ioport.h"
#include "kvm/mutex.h"
#include "kvm/util.h"
#include "kvm/term.h"
#include "kvm/kvm.h"
#include <linux/types.h>
#include <linux/serial_reg.h>
#include <pthread.h>
struct serial8250_device {
pthread_mutex_t mutex;
u16 iobase;
u8 irq;
u8 rbr; /* receive buffer */
u8 dll;
u8 dlm;
u8 iir;
u8 ier;
u8 fcr;
u8 lcr;
u8 mcr;
u8 lsr;
u8 msr;
u8 scr;
};
static struct serial8250_device devices[] = {
/* ttyS0 */
[0] = {
.mutex = PTHREAD_MUTEX_INITIALIZER,
.iobase = 0x3f8,
.irq = 4,
.iir = UART_IIR_NO_INT,
.lsr = UART_LSR_TEMT | UART_LSR_THRE,
.msr = UART_MSR_DCD | UART_MSR_DSR | UART_MSR_CTS,
.mcr = UART_MCR_OUT2,
},
/* ttyS1 */
[1] = {
.mutex = PTHREAD_MUTEX_INITIALIZER,
.iobase = 0x2f8,
.irq = 3,
.iir = UART_IIR_NO_INT,
},
/* ttyS2 */
[2] = {
.mutex = PTHREAD_MUTEX_INITIALIZER,
.iobase = 0x3e8,
.irq = 4,
.iir = UART_IIR_NO_INT,
},
};
#define SYSRQ_PENDING_NONE 0
#define SYSRQ_PENDING_BREAK 1
#define SYSRQ_PENDING_CMD 2
static int sysrq_pending;
static void serial8250__sysrq(struct kvm *kvm, struct serial8250_device *dev)
{
switch (sysrq_pending) {
case SYSRQ_PENDING_BREAK:
dev->lsr |= UART_LSR_DR | UART_LSR_BI;
sysrq_pending = SYSRQ_PENDING_CMD;
break;
case SYSRQ_PENDING_CMD:
dev->rbr = 'p';
dev->lsr |= UART_LSR_DR;
sysrq_pending = SYSRQ_PENDING_NONE;
break;
}
}
static void serial8250__receive(struct kvm *kvm, struct serial8250_device *dev)
{
int c;
if (dev->lsr & UART_LSR_DR)
return;
if (sysrq_pending) {
serial8250__sysrq(kvm, dev);
return;
}
if (!term_readable(CONSOLE_8250))
return;
c = term_getc(CONSOLE_8250);
if (c < 0)
return;
dev->rbr = c;
dev->lsr |= UART_LSR_DR;
}
/*
* Interrupts are injected for ttyS0 only.
*/
void serial8250__inject_interrupt(struct kvm *kvm)
{
struct serial8250_device *dev = &devices[0];
mutex_lock(&dev->mutex);
serial8250__receive(kvm, dev);
if (dev->ier & UART_IER_RDI && dev->lsr & UART_LSR_DR)
dev->iir = UART_IIR_RDI;
else if (dev->ier & UART_IER_THRI)
dev->iir = UART_IIR_THRI;
else
dev->iir = UART_IIR_NO_INT;
if (dev->iir != UART_IIR_NO_INT) {
kvm__irq_line(kvm, dev->irq, 0);
kvm__irq_line(kvm, dev->irq, 1);
}
mutex_unlock(&dev->mutex);
}
void serial8250__inject_sysrq(struct kvm *kvm)
{
sysrq_pending = SYSRQ_PENDING_BREAK;
}
static struct serial8250_device *find_device(u16 port)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(devices); i++) {
struct serial8250_device *dev = &devices[i];
if (dev->iobase == (port & ~0x7))
return dev;
}
return NULL;
}
static bool serial8250_out(struct kvm *kvm, u16 port, void *data, int size, u32 count)
{
struct serial8250_device *dev;
u16 offset;
bool ret = true;
dev = find_device(port);
if (!dev)
return false;
mutex_lock(&dev->mutex);
offset = port - dev->iobase;
if (dev->lcr & UART_LCR_DLAB) {
switch (offset) {
case UART_DLL:
dev->dll = ioport__read8(data);
break;
case UART_DLM:
dev->dlm = ioport__read8(data);
break;
case UART_FCR:
dev->fcr = ioport__read8(data);
break;
case UART_LCR:
dev->lcr = ioport__read8(data);
break;
case UART_MCR:
dev->mcr = ioport__read8(data);
break;
case UART_LSR:
/* Factory test */
break;
case UART_MSR:
/* Not used */
break;
case UART_SCR:
dev->scr = ioport__read8(data);
break;
default:
ret = false;
goto out_unlock;
}
} else {
switch (offset) {
case UART_TX: {
char *addr = data;
if (!(dev->mcr & UART_MCR_LOOP))
term_putc(CONSOLE_8250, addr, size * count);
dev->iir = UART_IIR_NO_INT;
break;
}
case UART_FCR:
dev->fcr = ioport__read8(data);
break;
case UART_IER:
dev->ier = ioport__read8(data) & 0x3f;
break;
case UART_LCR:
dev->lcr = ioport__read8(data);
break;
case UART_MCR:
dev->mcr = ioport__read8(data);
break;
case UART_LSR:
/* Factory test */
break;
case UART_MSR:
/* Not used */
break;
case UART_SCR:
dev->scr = ioport__read8(data);
break;
default:
ret = false;
goto out_unlock;
}
}
out_unlock:
mutex_unlock(&dev->mutex);
return ret;
}
static bool serial8250_in(struct kvm *kvm, u16 port, void *data, int size, u32 count)
{
struct serial8250_device *dev;
u16 offset;
bool ret = true;
dev = find_device(port);
if (!dev)
return false;
mutex_lock(&dev->mutex);
offset = port - dev->iobase;
if (dev->lcr & UART_LCR_DLAB) {
switch (offset) {
case UART_DLL:
ioport__write8(data, dev->dll);
goto out_unlock;
case UART_DLM:
ioport__write8(data, dev->dlm);
goto out_unlock;
default:
break;
}
} else {
switch (offset) {
case UART_RX:
ioport__write8(data, dev->rbr);
dev->lsr &= ~UART_LSR_DR;
dev->iir = UART_IIR_NO_INT;
goto out_unlock;
case UART_IER:
ioport__write8(data, dev->ier);
goto out_unlock;
default:
break;
}
}
switch (offset) {
case UART_IIR: {
u8 iir = dev->iir;
if (dev->fcr & UART_FCR_ENABLE_FIFO)
iir |= 0xc0;
ioport__write8(data, iir);
break;
}
case UART_LCR:
ioport__write8(data, dev->lcr);
break;
case UART_MCR:
ioport__write8(data, dev->mcr);
break;
case UART_LSR:
ioport__write8(data, dev->lsr);
dev->lsr &= ~(UART_LSR_OE|UART_LSR_PE|UART_LSR_FE|UART_LSR_BI);
break;
case UART_MSR:
ioport__write8(data, dev->msr);
break;
case UART_SCR:
ioport__write8(data, dev->scr);
break;
default:
ret = false;
goto out_unlock;
}
out_unlock:
mutex_unlock(&dev->mutex);
return ret;
}
static struct ioport_operations serial8250_ops = {
.io_in = serial8250_in,
.io_out = serial8250_out,
};
static void serial8250__device_init(struct kvm *kvm, struct serial8250_device *dev)
{
ioport__register(dev->iobase, &serial8250_ops, 8);
kvm__irq_line(kvm, dev->irq, 0);
}
void serial8250__init(struct kvm *kvm)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(devices); i++) {
struct serial8250_device *dev = &devices[i];
serial8250__device_init(kvm, dev);
}
}