blob: 684307f6ae5522bb540afc2bf664fe44d113b307 [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/local_apic.h>
#include <hypervisor/uart.h>
#include <hypervisor/vcpu.h>
#include <unittest/unittest.h>
#define EXPECTED_INT 43u
static void stub_io_apic(IoApic* io_apic) {
static LocalApic::Registers local_apic_regs;
static LocalApic local_apic(ZX_HANDLE_INVALID, reinterpret_cast<uintptr_t>(&local_apic_regs));
memset(&local_apic_regs, 0, sizeof(local_apic_regs));
io_apic->RegisterLocalApic(0, &local_apic);
}
static void io_apic_init_with_vector(IoApic* io_apic) {
stub_io_apic(io_apic);
IoApic::RedirectEntry entry = {
.upper = 0,
.lower = EXPECTED_INT,
};
io_apic->SetRedirect(X86_INT_UART, entry);
}
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;
{
IoApic io_apic;
Uart uart(&io_apic, &fail_raise_interrupt);
// Interrupts cannot be raised unless the UART IRQ redirect is in place.
stub_io_apic(&io_apic);
IoValue io_value = {};
io_value.access_size = 1;
io_value.u8 = UART_INTERRUPT_ENABLE_THR_EMPTY;
zx_status_t status = uart.Write(UART_INTERRUPT_ENABLE_PORT, io_value);
ASSERT_EQ(status, ZX_OK, "");
ASSERT_EQ(uart.interrupt_id(), UART_INTERRUPT_ID_NONE, "");
}
{
IoApic io_apic;
Uart uart(&io_apic, &ok_raise_interrupt);
// Interrupts can be raised after the UART IRQ redirect is in place.
io_apic_init_with_vector(&io_apic);
IoValue io_value = {};
io_value.access_size = 1;
io_value.u8 = UART_INTERRUPT_ENABLE_THR_EMPTY;
zx_status_t status = uart.Write(UART_INTERRUPT_ENABLE_PORT, io_value);
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;
{
IoApic io_apic;
Uart uart(&io_apic, &fail_raise_interrupt);
// If interrupt id is thr, it should be cleared to none
uart.set_interrupt_id(UART_INTERRUPT_ID_THR_EMPTY);
IoValue io_value = {};
zx_status_t status = uart.Read(UART_INTERRUPT_ID_PORT, &io_value);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(io_value.access_size, 1);
ASSERT_EQ(io_value.u8, UART_INTERRUPT_ID_THR_EMPTY);
ASSERT_EQ(uart.interrupt_id(), UART_INTERRUPT_ID_NONE);
}
{
IoApic io_apic;
Uart uart(&io_apic, &fail_raise_interrupt);
// If interrupt id is not thr, it should be left alone.
uart.set_interrupt_id(UART_INTERRUPT_ID_RDA);
IoValue io_value = {};
zx_status_t status = uart.Read(UART_INTERRUPT_ID_PORT, &io_value);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(io_value.access_size, 1);
ASSERT_EQ(io_value.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;
{
IoApic io_apic;
Uart uart(&io_apic, &ok_raise_interrupt);
// 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.set_line_status(UART_LINE_STATUS_THR_EMPTY | UART_LINE_STATUS_DATA_READY);
uart.set_rx_buffer('a');
uart.set_interrupt_id(UART_INTERRUPT_ID_RDA);
uart.set_interrupt_enable(UART_INTERRUPT_ENABLE_THR_EMPTY);
IoValue io_value = {};
zx_status_t status = uart.Read(UART_RECEIVE_PORT, &io_value);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(io_value.access_size, 1);
ASSERT_EQ(io_value.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.
IoApic io_apic;
Uart uart(&io_apic, &fail_raise_interrupt);
uart.set_interrupt_id(UART_INTERRUPT_ID_THR_EMPTY);
uart.set_interrupt_enable(UART_INTERRUPT_ENABLE_NONE);
IoValue io_value = {};
zx_status_t status = uart.Read(UART_RECEIVE_PORT, &io_value);
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;
{
// Setting IER when divisor latch is on should be a no-op
IoApic io_apic;
Uart uart(&io_apic, &ok_raise_interrupt);
stub_io_apic(&io_apic);
uart.set_line_control(UART_LINE_CONTROL_DIV_LATCH);
uart.set_interrupt_enable(0);
IoValue io_value = {};
io_value.access_size = 1;
io_value.u8 = UART_INTERRUPT_ENABLE_RDA;
zx_status_t status = uart.Write(UART_INTERRUPT_ENABLE_PORT, io_value);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(uart.interrupt_enable(), 0);
ASSERT_EQ(uart.interrupt_id(), UART_INTERRUPT_ID_NONE); // should be untouched
}
{
IoApic io_apic;
Uart uart(&io_apic, &fail_raise_interrupt);
// 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);
IoValue io_value = {};
io_value.access_size = 1;
io_value.u8 = UART_INTERRUPT_ENABLE_RDA;
zx_status_t status = uart.Write(UART_INTERRUPT_ENABLE_PORT, io_value);
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
}
{
IoApic io_apic;
Uart uart(&io_apic, &fail_raise_interrupt);
// 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.set_line_status(UART_LINE_STATUS_DATA_READY);
IoValue io_value = {};
io_value.access_size = 1;
// THR enable should trigger a THR interrupt
io_value.u8 = UART_INTERRUPT_ENABLE_THR_EMPTY;
zx_status_t status = uart.Write(UART_INTERRUPT_ENABLE_PORT, io_value);
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
}
{
IoApic io_apic;
Uart uart(&io_apic, &ok_raise_interrupt);
// 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);
IoValue io_value = {};
io_value.access_size = 1;
// THR enable should trigger a THR interrupt
io_value.u8 = UART_INTERRUPT_ENABLE_THR_EMPTY;
zx_status_t status = uart.Write(UART_INTERRUPT_ENABLE_PORT, io_value);
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;
{
IoApic io_apic;
Uart uart(&io_apic, &fail_raise_interrupt);
io_apic_init_with_vector(&io_apic);
uart.set_line_status(UART_LINE_STATUS_DATA_READY);
uart.set_interrupt_enable(UART_INTERRUPT_ENABLE_NONE);
// If divisor latch is enabled, this should be a no-op, so interrupt_id
// should remain the same.
uart.set_line_control(UART_LINE_CONTROL_DIV_LATCH);
uart.set_interrupt_id(UART_INTERRUPT_ID_THR_EMPTY);
IoValue io_value;
io_value.u8 = 0x1;
io_value.access_size = 1;
zx_status_t status = uart.Write(UART_RECEIVE_PORT, io_value);
ASSERT_EQ(status, ZX_OK);
ASSERT_EQ(uart.interrupt_id(), UART_INTERRUPT_ID_THR_EMPTY);
}
{
IoApic io_apic;
Uart uart(&io_apic, &fail_raise_interrupt);
io_apic_init_with_vector(&io_apic);
// If this was responding to a THR empty interrupt, IIR should be reset
// on THR write.
uart.set_interrupt_id(UART_INTERRUPT_ID_THR_EMPTY);
uart.set_line_status(UART_LINE_STATUS_DATA_READY);
uart.set_interrupt_enable(UART_INTERRUPT_ENABLE_NONE);
IoValue io_value;
io_value.access_size = 3;
io_value.data[0] = 0x75;
io_value.data[1] = 0x61;
io_value.data[2] = 0x0d;
zx_status_t status = uart.Write(UART_RECEIVE_PORT, io_value);
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);
}
{
IoApic io_apic;
Uart uart(&io_apic, &ok_raise_interrupt);
io_apic_init_with_vector(&io_apic);
uart.set_line_status(UART_LINE_STATUS_DATA_READY);
// If THR empty interrupts are enabled, an interrupt should be raised.
uart.set_interrupt_enable(UART_INTERRUPT_ENABLE_THR_EMPTY);
uart.set_interrupt_id(UART_INTERRUPT_ID_NONE);
IoValue io_value;
io_value.access_size = 3;
io_value.data[0] = 0x72;
io_value.data[1] = 0x74;
io_value.data[2] = 0x0d;
zx_status_t status = uart.Write(UART_RECEIVE_PORT, io_value);
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)