| /* |
| * QEMU 16450 UART emulation |
| * |
| * Copyright (c) 2003-2004 Fabrice Bellard |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| */ |
| #include "hw.h" |
| #include "qemu-char.h" |
| #include "isa.h" |
| #include "pc.h" |
| #include "qemu-timer.h" |
| |
| //#define DEBUG_SERIAL |
| |
| #define UART_LCR_DLAB 0x80 /* Divisor latch access bit */ |
| |
| #define UART_IER_MSI 0x08 /* Enable Modem status interrupt */ |
| #define UART_IER_RLSI 0x04 /* Enable receiver line status interrupt */ |
| #define UART_IER_THRI 0x02 /* Enable Transmitter holding register int. */ |
| #define UART_IER_RDI 0x01 /* Enable receiver data interrupt */ |
| |
| #define UART_IIR_NO_INT 0x01 /* No interrupts pending */ |
| #define UART_IIR_ID 0x06 /* Mask for the interrupt ID */ |
| |
| #define UART_IIR_MSI 0x00 /* Modem status interrupt */ |
| #define UART_IIR_THRI 0x02 /* Transmitter holding register empty */ |
| #define UART_IIR_RDI 0x04 /* Receiver data interrupt */ |
| #define UART_IIR_RLSI 0x06 /* Receiver line status interrupt */ |
| |
| /* |
| * These are the definitions for the Modem Control Register |
| */ |
| #define UART_MCR_LOOP 0x10 /* Enable loopback test mode */ |
| #define UART_MCR_OUT2 0x08 /* Out2 complement */ |
| #define UART_MCR_OUT1 0x04 /* Out1 complement */ |
| #define UART_MCR_RTS 0x02 /* RTS complement */ |
| #define UART_MCR_DTR 0x01 /* DTR complement */ |
| |
| /* |
| * These are the definitions for the Modem Status Register |
| */ |
| #define UART_MSR_DCD 0x80 /* Data Carrier Detect */ |
| #define UART_MSR_RI 0x40 /* Ring Indicator */ |
| #define UART_MSR_DSR 0x20 /* Data Set Ready */ |
| #define UART_MSR_CTS 0x10 /* Clear to Send */ |
| #define UART_MSR_DDCD 0x08 /* Delta DCD */ |
| #define UART_MSR_TERI 0x04 /* Trailing edge ring indicator */ |
| #define UART_MSR_DDSR 0x02 /* Delta DSR */ |
| #define UART_MSR_DCTS 0x01 /* Delta CTS */ |
| #define UART_MSR_ANY_DELTA 0x0F /* Any of the delta bits! */ |
| |
| #define UART_LSR_TEMT 0x40 /* Transmitter empty */ |
| #define UART_LSR_THRE 0x20 /* Transmit-hold-register empty */ |
| #define UART_LSR_BI 0x10 /* Break interrupt indicator */ |
| #define UART_LSR_FE 0x08 /* Frame error indicator */ |
| #define UART_LSR_PE 0x04 /* Parity error indicator */ |
| #define UART_LSR_OE 0x02 /* Overrun error indicator */ |
| #define UART_LSR_DR 0x01 /* Receiver data ready */ |
| |
| /* |
| * Delay TX IRQ after sending as much characters as the given interval would |
| * contain on real hardware. This avoids overloading the guest if it processes |
| * its output buffer in a loop inside the TX IRQ handler. |
| */ |
| #define THROTTLE_TX_INTERVAL 10 /* ms */ |
| |
| struct SerialState { |
| uint16_t divider; |
| uint8_t rbr; /* receive register */ |
| uint8_t ier; |
| uint8_t iir; /* read only */ |
| uint8_t lcr; |
| uint8_t mcr; |
| uint8_t lsr; /* read only */ |
| uint8_t msr; /* read only */ |
| uint8_t scr; |
| /* NOTE: this hidden state is necessary for tx irq generation as |
| it can be reset while reading iir */ |
| int thr_ipending; |
| qemu_irq irq; |
| CharDriverState *chr; |
| int last_break_enable; |
| target_phys_addr_t base; |
| int it_shift; |
| int baudbase; |
| QEMUTimer *tx_timer; |
| int tx_burst; |
| }; |
| |
| static void serial_receive_byte(SerialState *s, int ch); |
| |
| static void serial_update_irq(SerialState *s) |
| { |
| if ((s->lsr & UART_LSR_DR) && (s->ier & UART_IER_RDI)) { |
| s->iir = UART_IIR_RDI; |
| } else if (s->thr_ipending && (s->ier & UART_IER_THRI)) { |
| s->iir = UART_IIR_THRI; |
| } else { |
| s->iir = UART_IIR_NO_INT; |
| } |
| if (s->iir != UART_IIR_NO_INT) { |
| qemu_irq_raise(s->irq); |
| } else { |
| qemu_irq_lower(s->irq); |
| } |
| } |
| |
| static void serial_tx_done(void *opaque) |
| { |
| SerialState *s = opaque; |
| |
| if (s->tx_burst < 0) { |
| uint16_t divider; |
| |
| if (s->divider) |
| divider = s->divider; |
| else |
| divider = 1; |
| |
| /* We assume 10 bits/char, OK for this purpose. */ |
| s->tx_burst = THROTTLE_TX_INTERVAL * 1000 / |
| (1000000 * 10 / (s->baudbase / divider)); |
| } |
| s->thr_ipending = 1; |
| s->lsr |= UART_LSR_THRE; |
| s->lsr |= UART_LSR_TEMT; |
| serial_update_irq(s); |
| } |
| |
| static void serial_update_parameters(SerialState *s) |
| { |
| int speed, parity, data_bits, stop_bits; |
| QEMUSerialSetParams ssp; |
| |
| if (s->lcr & 0x08) { |
| if (s->lcr & 0x10) |
| parity = 'E'; |
| else |
| parity = 'O'; |
| } else { |
| parity = 'N'; |
| } |
| if (s->lcr & 0x04) |
| stop_bits = 2; |
| else |
| stop_bits = 1; |
| data_bits = (s->lcr & 0x03) + 5; |
| if (s->divider == 0) |
| return; |
| speed = s->baudbase / s->divider; |
| ssp.speed = speed; |
| ssp.parity = parity; |
| ssp.data_bits = data_bits; |
| ssp.stop_bits = stop_bits; |
| qemu_chr_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp); |
| #if 0 |
| printf("speed=%d parity=%c data=%d stop=%d\n", |
| speed, parity, data_bits, stop_bits); |
| #endif |
| } |
| |
| static void serial_ioport_write(void *opaque, uint32_t addr, uint32_t val) |
| { |
| SerialState *s = opaque; |
| unsigned char ch; |
| |
| addr &= 7; |
| #ifdef DEBUG_SERIAL |
| printf("serial: write addr=0x%02x val=0x%02x\n", addr, val); |
| #endif |
| switch(addr) { |
| default: |
| case 0: |
| if (s->lcr & UART_LCR_DLAB) { |
| s->divider = (s->divider & 0xff00) | val; |
| serial_update_parameters(s); |
| } else { |
| s->thr_ipending = 0; |
| s->lsr &= ~UART_LSR_THRE; |
| serial_update_irq(s); |
| ch = val; |
| if (!(s->mcr & UART_MCR_LOOP)) { |
| /* when not in loopback mode, send the char */ |
| qemu_chr_write(s->chr, &ch, 1); |
| } else { |
| /* in loopback mode, say that we just received a char */ |
| serial_receive_byte(s, ch); |
| } |
| if (s->tx_burst > 0) { |
| s->tx_burst--; |
| serial_tx_done(s); |
| } else if (s->tx_burst == 0) { |
| s->tx_burst--; |
| qemu_mod_timer(s->tx_timer, qemu_get_clock(vm_clock) + |
| ticks_per_sec * THROTTLE_TX_INTERVAL / 1000); |
| } |
| } |
| break; |
| case 1: |
| if (s->lcr & UART_LCR_DLAB) { |
| s->divider = (s->divider & 0x00ff) | (val << 8); |
| serial_update_parameters(s); |
| } else { |
| s->ier = val & 0x0f; |
| if (s->lsr & UART_LSR_THRE) { |
| s->thr_ipending = 1; |
| } |
| serial_update_irq(s); |
| } |
| break; |
| case 2: |
| break; |
| case 3: |
| { |
| int break_enable; |
| s->lcr = val; |
| serial_update_parameters(s); |
| break_enable = (val >> 6) & 1; |
| if (break_enable != s->last_break_enable) { |
| s->last_break_enable = break_enable; |
| qemu_chr_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_BREAK, |
| &break_enable); |
| } |
| } |
| break; |
| case 4: |
| s->mcr = val & 0x1f; |
| break; |
| case 5: |
| break; |
| case 6: |
| break; |
| case 7: |
| s->scr = val; |
| break; |
| } |
| } |
| |
| static uint32_t serial_ioport_read(void *opaque, uint32_t addr) |
| { |
| SerialState *s = opaque; |
| uint32_t ret; |
| |
| addr &= 7; |
| switch(addr) { |
| default: |
| case 0: |
| if (s->lcr & UART_LCR_DLAB) { |
| ret = s->divider & 0xff; |
| } else { |
| ret = s->rbr; |
| s->lsr &= ~(UART_LSR_DR | UART_LSR_BI); |
| serial_update_irq(s); |
| if (!(s->mcr & UART_MCR_LOOP)) { |
| /* in loopback mode, don't receive any data */ |
| qemu_chr_accept_input(s->chr); |
| } |
| } |
| break; |
| case 1: |
| if (s->lcr & UART_LCR_DLAB) { |
| ret = (s->divider >> 8) & 0xff; |
| } else { |
| ret = s->ier; |
| } |
| break; |
| case 2: |
| ret = s->iir; |
| /* reset THR pending bit */ |
| if ((ret & 0x7) == UART_IIR_THRI) |
| s->thr_ipending = 0; |
| serial_update_irq(s); |
| break; |
| case 3: |
| ret = s->lcr; |
| break; |
| case 4: |
| ret = s->mcr; |
| break; |
| case 5: |
| ret = s->lsr; |
| break; |
| case 6: |
| if (s->mcr & UART_MCR_LOOP) { |
| /* in loopback, the modem output pins are connected to the |
| inputs */ |
| ret = (s->mcr & 0x0c) << 4; |
| ret |= (s->mcr & 0x02) << 3; |
| ret |= (s->mcr & 0x01) << 5; |
| } else { |
| ret = s->msr; |
| } |
| break; |
| case 7: |
| ret = s->scr; |
| break; |
| } |
| #ifdef DEBUG_SERIAL |
| printf("serial: read addr=0x%02x val=0x%02x\n", addr, ret); |
| #endif |
| return ret; |
| } |
| |
| static int serial_can_receive(SerialState *s) |
| { |
| return !(s->lsr & UART_LSR_DR); |
| } |
| |
| static void serial_receive_byte(SerialState *s, int ch) |
| { |
| s->rbr = ch; |
| s->lsr |= UART_LSR_DR; |
| serial_update_irq(s); |
| } |
| |
| static void serial_receive_break(SerialState *s) |
| { |
| s->rbr = 0; |
| s->lsr |= UART_LSR_BI | UART_LSR_DR; |
| serial_update_irq(s); |
| } |
| |
| static int serial_can_receive1(void *opaque) |
| { |
| SerialState *s = opaque; |
| return serial_can_receive(s); |
| } |
| |
| static void serial_receive1(void *opaque, const uint8_t *buf, int size) |
| { |
| SerialState *s = opaque; |
| serial_receive_byte(s, buf[0]); |
| } |
| |
| static void serial_event(void *opaque, int event) |
| { |
| SerialState *s = opaque; |
| if (event == CHR_EVENT_BREAK) |
| serial_receive_break(s); |
| } |
| |
| static void serial_save(QEMUFile *f, void *opaque) |
| { |
| SerialState *s = opaque; |
| |
| qemu_put_be16s(f,&s->divider); |
| qemu_put_8s(f,&s->rbr); |
| qemu_put_8s(f,&s->ier); |
| qemu_put_8s(f,&s->iir); |
| qemu_put_8s(f,&s->lcr); |
| qemu_put_8s(f,&s->mcr); |
| qemu_put_8s(f,&s->lsr); |
| qemu_put_8s(f,&s->msr); |
| qemu_put_8s(f,&s->scr); |
| } |
| |
| static int serial_load(QEMUFile *f, void *opaque, int version_id) |
| { |
| SerialState *s = opaque; |
| |
| if(version_id > 2) |
| return -EINVAL; |
| |
| if (version_id >= 2) |
| qemu_get_be16s(f, &s->divider); |
| else |
| s->divider = qemu_get_byte(f); |
| qemu_get_8s(f,&s->rbr); |
| qemu_get_8s(f,&s->ier); |
| qemu_get_8s(f,&s->iir); |
| qemu_get_8s(f,&s->lcr); |
| qemu_get_8s(f,&s->mcr); |
| qemu_get_8s(f,&s->lsr); |
| qemu_get_8s(f,&s->msr); |
| qemu_get_8s(f,&s->scr); |
| |
| return 0; |
| } |
| |
| static void serial_reset(void *opaque) |
| { |
| SerialState *s = opaque; |
| |
| s->divider = 0; |
| s->rbr = 0; |
| s->ier = 0; |
| s->iir = UART_IIR_NO_INT; |
| s->lcr = 0; |
| s->mcr = 0; |
| s->lsr = UART_LSR_TEMT | UART_LSR_THRE; |
| s->msr = UART_MSR_DCD | UART_MSR_DSR | UART_MSR_CTS; |
| s->scr = 0; |
| |
| s->thr_ipending = 0; |
| s->last_break_enable = 0; |
| qemu_irq_lower(s->irq); |
| } |
| |
| /* If fd is zero, it means that the serial device uses the console */ |
| SerialState *serial_init(int base, qemu_irq irq, int baudbase, |
| CharDriverState *chr) |
| { |
| SerialState *s; |
| |
| s = qemu_mallocz(sizeof(SerialState)); |
| if (!s) |
| return NULL; |
| s->irq = irq; |
| s->baudbase = baudbase; |
| |
| s->tx_timer = qemu_new_timer(vm_clock, serial_tx_done, s); |
| if (!s->tx_timer) |
| return NULL; |
| |
| qemu_register_reset(serial_reset, s); |
| serial_reset(s); |
| |
| register_savevm("serial", base, 2, serial_save, serial_load, s); |
| |
| register_ioport_write(base, 8, 1, serial_ioport_write, s); |
| register_ioport_read(base, 8, 1, serial_ioport_read, s); |
| s->chr = chr; |
| qemu_chr_add_handlers(chr, serial_can_receive1, serial_receive1, |
| serial_event, s); |
| return s; |
| } |
| |
| /* Memory mapped interface */ |
| uint32_t serial_mm_readb (void *opaque, target_phys_addr_t addr) |
| { |
| SerialState *s = opaque; |
| |
| return serial_ioport_read(s, (addr - s->base) >> s->it_shift) & 0xFF; |
| } |
| |
| void serial_mm_writeb (void *opaque, |
| target_phys_addr_t addr, uint32_t value) |
| { |
| SerialState *s = opaque; |
| |
| serial_ioport_write(s, (addr - s->base) >> s->it_shift, value & 0xFF); |
| } |
| |
| uint32_t serial_mm_readw (void *opaque, target_phys_addr_t addr) |
| { |
| SerialState *s = opaque; |
| uint32_t val; |
| |
| val = serial_ioport_read(s, (addr - s->base) >> s->it_shift) & 0xFFFF; |
| #ifdef TARGET_WORDS_BIGENDIAN |
| val = bswap16(val); |
| #endif |
| return val; |
| } |
| |
| void serial_mm_writew (void *opaque, |
| target_phys_addr_t addr, uint32_t value) |
| { |
| SerialState *s = opaque; |
| #ifdef TARGET_WORDS_BIGENDIAN |
| value = bswap16(value); |
| #endif |
| serial_ioport_write(s, (addr - s->base) >> s->it_shift, value & 0xFFFF); |
| } |
| |
| uint32_t serial_mm_readl (void *opaque, target_phys_addr_t addr) |
| { |
| SerialState *s = opaque; |
| uint32_t val; |
| |
| val = serial_ioport_read(s, (addr - s->base) >> s->it_shift); |
| #ifdef TARGET_WORDS_BIGENDIAN |
| val = bswap32(val); |
| #endif |
| return val; |
| } |
| |
| void serial_mm_writel (void *opaque, |
| target_phys_addr_t addr, uint32_t value) |
| { |
| SerialState *s = opaque; |
| #ifdef TARGET_WORDS_BIGENDIAN |
| value = bswap32(value); |
| #endif |
| serial_ioport_write(s, (addr - s->base) >> s->it_shift, value); |
| } |
| |
| static CPUReadMemoryFunc *serial_mm_read[] = { |
| &serial_mm_readb, |
| &serial_mm_readw, |
| &serial_mm_readl, |
| }; |
| |
| static CPUWriteMemoryFunc *serial_mm_write[] = { |
| &serial_mm_writeb, |
| &serial_mm_writew, |
| &serial_mm_writel, |
| }; |
| |
| SerialState *serial_mm_init (target_phys_addr_t base, int it_shift, |
| qemu_irq irq, int baudbase, |
| CharDriverState *chr, int ioregister) |
| { |
| SerialState *s; |
| int s_io_memory; |
| |
| s = qemu_mallocz(sizeof(SerialState)); |
| if (!s) |
| return NULL; |
| s->irq = irq; |
| s->base = base; |
| s->it_shift = it_shift; |
| s->baudbase= baudbase; |
| |
| s->tx_timer = qemu_new_timer(vm_clock, serial_tx_done, s); |
| if (!s->tx_timer) |
| return NULL; |
| |
| qemu_register_reset(serial_reset, s); |
| serial_reset(s); |
| |
| register_savevm("serial", base, 2, serial_save, serial_load, s); |
| |
| if (ioregister) { |
| s_io_memory = cpu_register_io_memory(0, serial_mm_read, |
| serial_mm_write, s); |
| cpu_register_physical_memory(base, 8 << it_shift, s_io_memory); |
| } |
| s->chr = chr; |
| qemu_chr_add_handlers(chr, serial_can_receive1, serial_receive1, |
| serial_event, s); |
| return s; |
| } |