blob: 05803270692a82135f637b4b9cf328e9eee6b44b [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 <machina/io_port.h>
#include <time.h>
#include <fbl/auto_lock.h>
#include <hypervisor/address.h>
#include <hypervisor/bits.h>
// clang-format off
/* PIC constatants. */
constexpr uint16_t kPicDataPort = 1;
constexpr uint8_t kPicInvalid = UINT8_MAX;
/* PM1 relative port mappings. */
constexpr uint16_t kPm1StatusPort = 0;
constexpr uint16_t kPm1EnablePort = 2;
constexpr uint16_t kPm1ControlPort = PM1_CONTROL_PORT - PM1_EVENT_PORT;
constexpr uint16_t kPm1Size = kPm1EnablePort + 1;
/* RTC relative port mappings. */
constexpr uint16_t kRtcIndexPort = 0;
constexpr uint16_t kRtcDataPort = 1;
/* RTC register addresses. */
constexpr uint8_t kRtcRegisterSeconds = 0;
constexpr uint8_t kRtcRegisterSecondsAlarm = 1;
constexpr uint8_t kRtcRegisterMinutes = 2;
constexpr uint8_t kRtcRegisterMinutesAlarm = 3;
constexpr uint8_t kRtcRegisterHours = 4;
constexpr uint8_t kRtcRegisterHoursAlarm = 5;
constexpr uint8_t kRtcRegisterDayOfMonth = 7;
constexpr uint8_t kRtcRegisterMonth = 8;
constexpr uint8_t kRtcRegisterYear = 9;
constexpr uint8_t kRtcRegisterA = 10;
constexpr uint8_t kRtcRegisterB = 11;
constexpr uint8_t kRtcRegisterC = 12;
/* RTC register B flags. */
constexpr uint8_t kRtcRegisterBDaylightSavings = 1 << 0;
constexpr uint8_t kRtcRegisterBHourFormat = 1 << 1;
constexpr uint8_t kRtcRegisterBInterruptMask = 0x70;
/* RTC relative port mappings. */
constexpr uint16_t kI8042DataPort = 0x0;
constexpr uint16_t kI8042CommandPort = 0x4;
/* I8042 status flags. */
constexpr uint8_t kI8042StatusOutputFull = 1 << 0;
/* I8042 test constants. */
constexpr uint8_t kI8042CommandTest = 0xaa;
constexpr uint8_t kI8042DataTestResponse = 0x55;
// clang-format on
zx_status_t PicHandler::Init(Guest* guest, uint16_t base) {
return guest->CreateMapping(TrapType::PIO_SYNC, base, PIC_SIZE, 0, this);
}
zx_status_t PicHandler::Read(uint64_t addr, IoValue* value) const {
if (addr == kPicDataPort) {
value->access_size = 1;
value->u8 = kPicInvalid;
return ZX_OK;
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t PicHandler::Write(uint64_t addr, const IoValue& value) {
return ZX_OK;
}
zx_status_t PitHandler::Init(Guest* guest) {
return guest->CreateMapping(TrapType::PIO_SYNC, PIT_BASE, PIT_SIZE, 0, this);
}
zx_status_t PitHandler::Read(uint64_t addr, IoValue* value) const {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t PitHandler::Write(uint64_t addr, const IoValue& value) {
return ZX_OK;
}
zx_status_t Pm1Handler::Init(Guest* guest) {
// Map 2 distinct register blocks for event and control registers.
zx_status_t status = guest->CreateMapping(TrapType::PIO_SYNC, PM1_EVENT_PORT, kPm1Size, 0,
this);
if (status != ZX_OK)
return status;
return guest->CreateMapping(TrapType::PIO_SYNC, PM1_CONTROL_PORT, kPm1Size, kPm1ControlPort,
this);
}
zx_status_t Pm1Handler::Read(uint64_t addr, IoValue* value) const {
switch (addr) {
case kPm1StatusPort:
value->access_size = 2;
value->u16 = 0;
break;
case kPm1EnablePort: {
value->access_size = 2;
fbl::AutoLock lock(&mutex_);
value->u16 = enable_;
break;
}
case kPm1ControlPort:
value->u32 = 0;
break;
default:
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
zx_status_t Pm1Handler::Write(uint64_t addr, const IoValue& value) {
switch (addr) {
case kPm1StatusPort:
break;
case kPm1EnablePort: {
if (value.access_size != 2)
return ZX_ERR_IO_DATA_INTEGRITY;
fbl::AutoLock lock(&mutex_);
enable_ = value.u16;
break;
}
case kPm1ControlPort: {
uint16_t slp_en = bit_shift(value.u16, 13);
uint16_t slp_type = bits_shift(value.u16, 12, 10);
if (slp_en != 0) {
// Only power-off transitions are supported.
return slp_type == SLP_TYP5 ? ZX_ERR_STOP : ZX_ERR_NOT_SUPPORTED;
}
break;
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
static uint8_t to_bcd(int binary) {
return static_cast<uint8_t>(((binary / 10) << 4) | (binary % 10));
}
zx_status_t RtcHandler::Init(Guest* guest) {
return guest->CreateMapping(TrapType::PIO_SYNC, RTC_BASE, RTC_SIZE, 0, this);
}
zx_status_t RtcHandler::Read(uint64_t addr, IoValue* value) const {
switch (addr) {
case kRtcDataPort: {
value->access_size = 1;
uint8_t rtc_index;
{
fbl::AutoLock lock(&mutex_);
rtc_index = index_;
}
return ReadRtcRegister(rtc_index, &value->u8);
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
zx_status_t RtcHandler::Write(uint64_t addr, const IoValue& value) {
switch (addr) {
case kRtcDataPort: {
uint8_t rtc_index;
{
fbl::AutoLock lock(&mutex_);
rtc_index = index_;
}
return WriteRtcRegister(rtc_index, value.u8);
}
case kRtcIndexPort: {
if (value.access_size != 1)
return ZX_ERR_IO_DATA_INTEGRITY;
fbl::AutoLock lock(&mutex_);
index_ = value.u8;
return ZX_OK;
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
zx_status_t RtcHandler::ReadRtcRegister(uint8_t rtc_index, uint8_t* value) const {
time_t now = time(nullptr);
struct tm tm;
if (localtime_r(&now, &tm) == nullptr)
return ZX_ERR_INTERNAL;
switch (rtc_index) {
case kRtcRegisterSeconds:
*value = to_bcd(tm.tm_sec);
break;
case kRtcRegisterMinutes:
*value = to_bcd(tm.tm_min);
break;
case kRtcRegisterHours:
*value = to_bcd(tm.tm_hour);
break;
case kRtcRegisterDayOfMonth:
*value = to_bcd(tm.tm_mday);
break;
case kRtcRegisterMonth:
// struct tm represents months as 0-11, RTC uses 1-12.
*value = to_bcd(tm.tm_mon + 1);
break;
case kRtcRegisterYear: {
// RTC expects the number of years since 2000.
int year = tm.tm_year - 100;
if (year < 0)
year = 0;
*value = to_bcd(year);
break;
}
case kRtcRegisterA:
// Ensure that UIP is 0. Other values (clock frequency) are obsolete.
*value = 0;
break;
case kRtcRegisterB:
*value = kRtcRegisterBHourFormat;
if (tm.tm_isdst)
*value |= kRtcRegisterBDaylightSavings;
break;
// Alarms are not implemented but allow reads of the registers.
case kRtcRegisterSecondsAlarm:
case kRtcRegisterMinutesAlarm:
case kRtcRegisterHoursAlarm:
case kRtcRegisterC:
*value = 0;
break;
default:
fprintf(stderr, "Unsupported RTC register read %x\n", rtc_index);
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
zx_status_t RtcHandler::WriteRtcRegister(uint8_t rtc_index, uint8_t value) {
switch (rtc_index) {
case kRtcRegisterA:
return ZX_OK;
case kRtcRegisterB:
// No interrupts are implemented.
if (value & kRtcRegisterBInterruptMask)
return ZX_ERR_NOT_SUPPORTED;
return ZX_OK;
default:
fprintf(stderr, "Unsupported RTC register write %x\n", rtc_index);
return ZX_ERR_NOT_SUPPORTED;
}
}
zx_status_t I8042Handler::Init(Guest* guest) {
zx_status_t status = guest->CreateMapping(TrapType::PIO_SYNC, I8042_BASE + kI8042DataPort,
1, kI8042DataPort, this);
if (status != ZX_OK)
return status;
return guest->CreateMapping(TrapType::PIO_SYNC, I8042_BASE + kI8042CommandPort,
1, kI8042CommandPort, this);
}
zx_status_t I8042Handler::Read(uint64_t port, IoValue* value) const {
switch (port) {
case kI8042DataPort: {
value->access_size = 1;
fbl::AutoLock lock(&mutex_);
value->u8 = command_ == kI8042CommandTest ? kI8042DataTestResponse : 0;
break;
}
case kI8042CommandPort:
value->access_size = 1;
value->u8 = kI8042StatusOutputFull;
break;
default:
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
zx_status_t I8042Handler::Write(uint64_t port, const IoValue& value) {
switch (port) {
case kI8042DataPort:
case kI8042CommandPort: {
if (value.access_size != 1)
return ZX_ERR_IO_DATA_INTEGRITY;
fbl::AutoLock lock(&mutex_);
command_ = value.u8;
break;
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
zx_status_t IoPort::Init(Guest* guest) {
zx_status_t status;
status = pic1_.Init(guest, PIC1_BASE);
if (status != ZX_OK)
return status;
status = pic2_.Init(guest, PIC2_BASE);
if (status != ZX_OK)
return status;
status = pit_.Init(guest);
if (status != ZX_OK)
return status;
status = pm1_.Init(guest);
if (status != ZX_OK)
return status;
status = rtc_.Init(guest);
if (status != ZX_OK)
return status;
status = i8042_.Init(guest);
if (status != ZX_OK)
return status;
return ZX_OK;
}