blob: 13edef3947f1e99f829ff4c844272c7de0f9e60b [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <hypervisor/address.h>
#include <hypervisor/io_apic.h>
#include <hypervisor/uart.h>
#include <hypervisor/vcpu.h>
#include <unittest/unittest.h>
#define EXPECTED_INT 43u
static void stub_io_apic(io_apic_t* io_apic) {
static local_apic_t local_apic = {};
local_apic.vcpu = ZX_HANDLE_INVALID;
io_apic_init(io_apic);
io_apic->local_apic[0] = &local_apic;
}
static void io_apic_init_with_vector(io_apic_t* io_apic) {
stub_io_apic(io_apic);
io_apic->redirect[X86_INT_UART * 2] = EXPECTED_INT;
}
static zx_status_t ok_raise_interrupt(zx_handle_t vcpu, uint32_t vector) {
return vector == EXPECTED_INT ? ZX_OK : ZX_ERR_INTERNAL;
}
static zx_status_t fail_raise_interrupt(zx_handle_t vcpu, uint32_t vector) {
return ZX_ERR_BAD_STATE;
}
static bool irq_redirect(void) {
BEGIN_TEST;
uart_t uart;
io_apic_t io_apic;
{
// Interrupts cannot be raised unless the UART IRQ redirect is in place.
stub_io_apic(&io_apic);
uart_init(&uart, &io_apic);
uart.raise_interrupt = fail_raise_interrupt;
zx_packet_guest_io_t guest_io = {};
guest_io.port = UART_INTERRUPT_ENABLE_PORT;
guest_io.access_size = 1;
guest_io.u8 = UART_INTERRUPT_ENABLE_THR_EMPTY;
zx_status_t status = uart_write(&uart, &guest_io);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(uart.interrupt_id, UART_INTERRUPT_ID_NONE, "");
}
{
// Interrupts can be raised after the UART IRQ redirect is in place.
io_apic_init_with_vector(&io_apic);
uart_init(&uart, &io_apic);
uart.raise_interrupt = ok_raise_interrupt;
zx_packet_guest_io_t guest_io = {};
guest_io.port = UART_INTERRUPT_ENABLE_PORT;
guest_io.access_size = 1;
guest_io.u8 = UART_INTERRUPT_ENABLE_THR_EMPTY;
zx_status_t status = uart_write(&uart, &guest_io);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(uart.interrupt_id, UART_INTERRUPT_ID_THR_EMPTY, "");
}
END_TEST;
}
// Test behaviour of reads to the Interrupt Identification Register.
static bool read_iir(void) {
BEGIN_TEST;
{
uart_t uart = {};
// If interrupt id is thr, it should be cleared to none
uart.interrupt_id = UART_INTERRUPT_ID_THR_EMPTY;
uart.raise_interrupt = fail_raise_interrupt;
zx_vcpu_io_t vcpu_io;
zx_status_t status = uart_read(&uart, UART_INTERRUPT_ID_PORT, &vcpu_io);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(vcpu_io.access_size, 1);
ASSERT_EQ(vcpu_io.u8, UART_INTERRUPT_ID_THR_EMPTY);
ASSERT_EQ(uart.interrupt_id, UART_INTERRUPT_ID_NONE);
}
{
uart_t uart = {};
// If interrupt id is not thr, it should be left alone.
uart.interrupt_id = UART_INTERRUPT_ID_RDA;
uart.raise_interrupt = fail_raise_interrupt;
zx_vcpu_io_t vcpu_io;
zx_status_t status = uart_read(&uart, UART_INTERRUPT_ID_PORT, &vcpu_io);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(vcpu_io.access_size, 1);
ASSERT_EQ(vcpu_io.u8, UART_INTERRUPT_ID_RDA);
ASSERT_EQ(uart.interrupt_id, UART_INTERRUPT_ID_RDA);
}
END_TEST;
}
// Test behaviour of reads from the Receive Buffer Register.
static bool read_rbr(void) {
BEGIN_TEST;
uart_t uart;
io_apic_t io_apic;
{
// Reads from RBR should unset UART_LINE_STATUS_DATA_READY,
// clear interrupt status and trigger further interrupts if available.
io_apic_init_with_vector(&io_apic);
uart_init(&uart, &io_apic);
uart.raise_interrupt = ok_raise_interrupt;
uart.line_status = UART_LINE_STATUS_THR_EMPTY | UART_LINE_STATUS_DATA_READY;
uart.rx_buffer = 'a';
uart.interrupt_id = UART_INTERRUPT_ID_RDA;
uart.interrupt_enable = UART_INTERRUPT_ENABLE_THR_EMPTY;
zx_vcpu_io_t vcpu_io;
zx_status_t status = uart_read(&uart, UART_RECEIVE_PORT, &vcpu_io);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(vcpu_io.access_size, 1);
ASSERT_EQ(vcpu_io.u8, 'a');
ASSERT_EQ(uart.rx_buffer, 0);
ASSERT_EQ(uart.line_status, UART_LINE_STATUS_THR_EMPTY);
ASSERT_EQ(uart.interrupt_id, UART_INTERRUPT_ID_THR_EMPTY);
}
{
// If interrupt_id was not RDA, it should not be cleared.
io_apic_init_with_vector(&io_apic);
uart_init(&uart, &io_apic);
uart.raise_interrupt = fail_raise_interrupt;
uart.interrupt_id = UART_INTERRUPT_ID_THR_EMPTY;
uart.interrupt_enable = UART_INTERRUPT_ENABLE_NONE;
zx_vcpu_io_t vcpu_io;
zx_status_t status = uart_read(&uart, UART_RECEIVE_PORT, &vcpu_io);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(uart.interrupt_id, UART_INTERRUPT_ID_THR_EMPTY);
}
END_TEST;
}
// Test behaviour of writes to the Interrupt Enable Register.
static bool write_ier(void) {
BEGIN_TEST;
uart_t uart;
io_apic_t io_apic;
{
// Setting IER when divisor latch is on should be a no-op
stub_io_apic(&io_apic);
uart_init(&uart, &io_apic);
uart.line_control = UART_LINE_CONTROL_DIV_LATCH;
uart.interrupt_enable = 0;
zx_packet_guest_io_t guest_io = {};
guest_io.port = UART_INTERRUPT_ENABLE_PORT;
guest_io.access_size = 1;
guest_io.u8 = UART_INTERRUPT_ENABLE_RDA;
zx_status_t status = uart_write(&uart, &guest_io);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(uart.interrupt_enable, 0);
ASSERT_EQ(uart.interrupt_id, UART_INTERRUPT_ID_NONE); // should be untouched
}
{
// Setting anything not THR enable shouldn't trigger any interrupts.
stub_io_apic(&io_apic);
// Only UART_INTERRUPT_ENABLE_THR_EMPTY should trigger interrupts on IER write.
// Anything else should not.
io_apic_init_with_vector(&io_apic);
uart_init(&uart, &io_apic);
uart.raise_interrupt = fail_raise_interrupt;
zx_packet_guest_io_t guest_io = {};
guest_io.port = UART_INTERRUPT_ENABLE_PORT;
guest_io.access_size = 1;
guest_io.u8 = UART_INTERRUPT_ENABLE_RDA;
zx_status_t status = uart_write(&uart, &guest_io);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(uart.interrupt_enable, UART_INTERRUPT_ENABLE_RDA);
ASSERT_EQ(uart.interrupt_id, UART_INTERRUPT_ID_NONE); // should be untouched
}
{
// UART_INTERRUPT_ID_THR_EMPTY should not be raised if
// line status is not UART_LINE_STATUS_THR_EMPTY.
io_apic_init_with_vector(&io_apic);
uart_init(&uart, &io_apic);
uart.line_status = UART_LINE_STATUS_DATA_READY;
uart.raise_interrupt = fail_raise_interrupt;
zx_packet_guest_io_t guest_io = {};
guest_io.port = UART_INTERRUPT_ENABLE_PORT;
guest_io.access_size = 1;
// THR enable should trigger a THR interrupt
guest_io.u8 = UART_INTERRUPT_ENABLE_THR_EMPTY;
zx_status_t status = uart_write(&uart, &guest_io);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(uart.interrupt_enable, UART_INTERRUPT_ENABLE_THR_EMPTY);
ASSERT_EQ(uart.interrupt_id, UART_INTERRUPT_ID_NONE); // should be untouched
}
{
// Setting UART_INTERRUPT_ENABLE_THR_EMPTY should trigger UART_INTERRUPT_ID_THR_EMPTY
// if line status is UART_LINE_STATUS_THR_EMPTY.
io_apic_init_with_vector(&io_apic);
uart_init(&uart, &io_apic);
uart.raise_interrupt = ok_raise_interrupt;
zx_packet_guest_io_t guest_io = {};
guest_io.port = UART_INTERRUPT_ENABLE_PORT;
guest_io.access_size = 1;
// THR enable should trigger a THR interrupt
guest_io.u8 = UART_INTERRUPT_ENABLE_THR_EMPTY;
zx_status_t status = uart_write(&uart, &guest_io);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(uart.interrupt_enable, UART_INTERRUPT_ENABLE_THR_EMPTY);
ASSERT_EQ(uart.interrupt_id, UART_INTERRUPT_ID_THR_EMPTY);
}
END_TEST;
}
// Test behaviour of writes to the Transmit Holding Register
static bool write_thr(void) {
BEGIN_TEST;
uart_t uart;
io_apic_t io_apic;
{
io_apic_init_with_vector(&io_apic);
uart_init(&uart, &io_apic);
uart.line_status = UART_LINE_STATUS_DATA_READY;
uart.interrupt_enable = UART_INTERRUPT_ENABLE_NONE;
uart.raise_interrupt = fail_raise_interrupt;
// If divisor latch is enabled, this should be a no-op, so interrupt_id
// should remain the same.
uart.line_control = UART_LINE_CONTROL_DIV_LATCH;
uart.interrupt_id = UART_INTERRUPT_ID_THR_EMPTY;
zx_packet_guest_io_t guest_io = {};
guest_io.port = UART_RECEIVE_PORT;
guest_io.u8 = 0x1;
guest_io.access_size = 1;
zx_status_t status = uart_write(&uart, &guest_io);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(uart.interrupt_id, UART_INTERRUPT_ID_THR_EMPTY);
}
{
io_apic_init_with_vector(&io_apic);
uart_init(&uart, &io_apic);
// If this was responding to a THR empty interrupt, IIR should be reset
// on THR write.
uart.interrupt_id = UART_INTERRUPT_ID_THR_EMPTY;
uart.line_status = UART_LINE_STATUS_DATA_READY;
uart.interrupt_enable = UART_INTERRUPT_ENABLE_NONE;
uart.raise_interrupt = fail_raise_interrupt;
zx_packet_guest_io_t guest_io = {};
guest_io.port = UART_RECEIVE_PORT;
guest_io.access_size = 3;
guest_io.data[0] = 0x75;
guest_io.data[1] = 0x61;
guest_io.data[2] = 0x0d;
zx_status_t status = uart_write(&uart, &guest_io);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(uart.line_status, UART_LINE_STATUS_THR_EMPTY | UART_LINE_STATUS_DATA_READY);
ASSERT_EQ(uart.interrupt_id, UART_INTERRUPT_ID_NONE);
}
{
io_apic_init_with_vector(&io_apic);
uart_init(&uart, &io_apic);
uart.line_status = UART_LINE_STATUS_DATA_READY;
uart.raise_interrupt = ok_raise_interrupt;
// If THR empty interrupts are enabled, an interrupt should be raised.
uart.interrupt_enable = UART_INTERRUPT_ENABLE_THR_EMPTY;
uart.interrupt_id = UART_INTERRUPT_ID_NONE;
zx_packet_guest_io_t guest_io = {};
guest_io.port = UART_RECEIVE_PORT;
guest_io.access_size = 3;
guest_io.data[0] = 0x72;
guest_io.data[1] = 0x74;
guest_io.data[2] = 0x0d;
zx_status_t status = uart_write(&uart, &guest_io);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(uart.line_status, UART_LINE_STATUS_THR_EMPTY | UART_LINE_STATUS_DATA_READY);
ASSERT_EQ(uart.interrupt_id, UART_INTERRUPT_ID_THR_EMPTY);
}
END_TEST;
}
BEGIN_TEST_CASE(uart)
RUN_TEST(irq_redirect)
RUN_TEST(read_rbr)
RUN_TEST(read_iir)
RUN_TEST(write_ier)
RUN_TEST(write_thr)
END_TEST_CASE(uart)