Implemented enough of the CMOS to make the kernel happy

This commit is contained in:
StrikerX3 2018-12-09 14:22:03 -02:00
parent d94bdf2225
commit 25490129f2
2 changed files with 228 additions and 11 deletions

View file

@ -5,13 +5,42 @@
namespace vixen {
static inline bool IsRTCRegister(uint8_t reg) {
switch (reg) {
case RTCSeconds:
case RTCMinutes:
case RTCHours:
case RTCDayOfWeek:
case RTCDayOfMonth:
case RTCMonth:
case RTCYear:
case RTCCentury:
return true;
default:
return false;
}
}
CMOS::CMOS() {
// TODO: Are IRQs needed?
}
CMOS::~CMOS() {
}
void CMOS::Reset() {
// The Xbox kernel checks that the CMOS user memory has an specific pattern
for (int i = 0x10; i < 0x70; i++) {
m_memory[i] = 0x55 << (i & 1);
}
for (int i = 0x80; i < 0x100; i++) {
m_memory[i] = 0x55 << (i & 1);
}
// Initialize virtual RTC offset to match host RTC
m_offset = 0;
}
bool CMOS::MapIO(IOMapper *mapper) {
@ -21,16 +50,146 @@ bool CMOS::MapIO(IOMapper *mapper) {
}
bool CMOS::IORead(uint32_t port, uint32_t *value, uint8_t size) {
log_warning("CMOS::IORead: Unhandled read! port = 0x%x, size = %u\n", port, size);
*value = 0;
if (size != 1) {
log_warning("CMOS::IORead: Unexpected read of size %d; truncating to 1 byte", size);
size = 1;
}
return false;
switch (port - PORT_CMOS_BASE) {
case PORT_CMOS_DATA:
if (IsRTCRegister(m_regAddr)) {
ReadRTC(m_regAddr, reinterpret_cast<uint8_t *>(value));
}
else {
switch (m_regAddr) {
case RegC: *value = m_memory[m_regAddr] & 0b11110000; break;
case RegD: *value = m_memory[m_regAddr] & 0b10000000; break;
default: *value = m_memory[m_regAddr]; break;
}
}
break;
default:
log_warning("CMOS::IORead: Unexpected read! port = 0x%x\n", port);
*value = 0;
break;
}
return true;
}
bool CMOS::IOWrite(uint32_t port, uint32_t value, uint8_t size) {
log_warning("CMOS::IOWrite: Unhandled write! port = 0x%x, size = %u, value = 0x%x\n", port, size, value);
return false;
if (size != 1) {
log_warning("CMOS::IOWrite: Unexpected write of size %d; truncating to 1 byte", size);
size = 1;
}
switch (port - PORT_CMOS_BASE) {
case PORT_CMOS_CONTROL:
m_regAddr = value & 0x7f;
break;
case PORT_CMOS_DATA:
if (IsRTCRegister(m_regAddr)) {
WriteRTC(m_regAddr, (uint8_t)value);
}
else {
switch (m_regAddr) {
case RegA:
case RegB:
case RegC:
case RegD:
log_debug("handle this\n");
break;
}
m_memory[m_regAddr] = value;
}
break;
case PORT_CMOS_EXT_CONTROL:
m_regAddr = value;
break;
}
return true;
}
void CMOS::ReadRTC(uint8_t reg, uint8_t *value) {
// Get current host RTC and apply offset
auto now = std::chrono::system_clock::now() + std::chrono::seconds(m_offset);
time_t tt = std::chrono::system_clock::to_time_t(now);
tm local = *localtime(&tt);
// Return requested field
switch (reg) {
case RTCSeconds: *value = ToBCD(local.tm_sec); break;
case RTCMinutes: *value = ToBCD(local.tm_min); break;
case RTCHours: *value = ToBCDHour(local.tm_hour); break;
case RTCDayOfWeek: *value = local.tm_wday + 1; break;
case RTCDayOfMonth: *value = ToBCD(local.tm_mday); break;
case RTCMonth: *value = ToBCD(local.tm_mon) + 1; break;
case RTCYear: *value = ToBCD(local.tm_year % 100); break;
case RTCCentury: *value = ToBCD((local.tm_year + 1900) / 100); break;
default: log_warning("CMOS::ReadRTC: Unexpected register %d\n", reg); *value = 0; break;
}
}
void CMOS::WriteRTC(uint8_t reg, uint8_t value) {
// Get current host RTC and apply offset
auto now = std::chrono::system_clock::now() + std::chrono::seconds(m_offset);
time_t tt = std::chrono::system_clock::to_time_t(now);
tm local = *localtime(&tt);
tm new_local = local;
// Update requested field
switch (reg) {
case RTCSeconds: new_local.tm_sec = FromBCD(value); break;
case RTCMinutes: new_local.tm_min = FromBCD(value); break;
case RTCHours: new_local.tm_hour = FromBCDHour(value); break;
case RTCDayOfWeek: new_local.tm_wday = FromBCD(value) - 1; break;
case RTCDayOfMonth: new_local.tm_mday = FromBCD(value); break;
case RTCMonth: new_local.tm_mon = FromBCD(value) - 1; break;
case RTCYear: new_local.tm_year = (local.tm_year / 100) * 100 + FromBCD(value); break;
case RTCCentury: new_local.tm_year = local.tm_year % 100 + (FromBCD(value) * 100 - 1900); break;
default: log_warning("CMOS::WriteRTC: Unexpected register %d\n", reg); break;
}
// Recalculate time offset
time_t new_tt = mktime(&new_local);
m_offset = difftime(tt, new_tt);
}
uint8_t CMOS::ToBCD(uint8_t value) {
if (m_memory[RegB] & RegB_DM) {
return value;
}
else {
return ((value / 10) << 4) | (value % 10);
}
}
uint8_t CMOS::FromBCD(uint8_t value) {
if (m_memory[RegB] & RegB_DM) {
return value;
}
return ((value >> 4) * 10) + (value & 0xf);
}
uint8_t CMOS::ToBCDHour(uint8_t hour) {
if (m_memory[RegB] & RegB_24_12) {
return hour;
}
// Convert 00 -> 12
uint8_t newHour = (hour % 12) ? (hour % 12) : 12;
if (hour >= 12) {
newHour |= 0x80;
}
return newHour;
}
uint8_t CMOS::FromBCDHour(uint8_t hour) {
if (m_memory[RegB] & RegB_24_12) {
return hour & 0x7f;
}
uint8_t newHour = FromBCD(hour & 0x7f);
return (hour % 12) + ((hour & 0x80) ? 12 : 0);
}
}

View file

@ -1,17 +1,58 @@
#pragma once
#include <cstdint>
#include <chrono>
#include "vixen/cpu.h"
namespace vixen {
#define PORT_CMOS_CONTROL 0x70
#define PORT_CMOS_DATA 0x71
#define PORT_CMOS_EXT_CONTROL 0x72
#define CMOS_IRQ 8
#define PORT_CMOS_BASE PORT_CMOS_CONTROL
#define PORT_CMOS_COUNT (PORT_CMOS_EXT_CONTROL - PORT_CMOS_CONTROL + 1)
#define PORT_CMOS_BASE 0x70
#define PORT_CMOS_COUNT 3
#define PORT_CMOS_CONTROL 0
#define PORT_CMOS_DATA 1
#define PORT_CMOS_EXT_CONTROL 2
enum CMOSRegister {
RTCSeconds = 0x0,
RTCSecondsAlarm = 0x1,
RTCMinutes = 0x2,
RTCMinutesAlarm = 0x3,
RTCHours = 0x4,
RTCHoursAlarm = 0x5,
RTCDayOfWeek = 0x6,
RTCDayOfMonth = 0x7,
RTCMonth = 0x8,
RTCYear = 0x9,
RTCCentury = 0x7f, // Xbox only
RegA = 0xa,
RegB = 0xb,
RegC = 0xc,
RegD = 0xd,
};
enum CMOSRegisterBit {
RegA_UIP = (1 << 7), // (UIP) Update in progress
RegB_SET = (1 << 7), // (SET)
RegB_PIE = (1 << 6), // (PIE) Periodic interrupt enable
RegB_AIE = (1 << 5), // (AIE) Alarm interrupt enable
RegB_UIE = (1 << 4), // (UIE) Update-ended interrupt enable
RegB_SQWE = (1 << 3), // (SQWE) Square wave enable
RegB_DM = (1 << 2), // (DM) Data mode
RegB_24_12 = (1 << 1), // (24/12) Format output to: 1 = 24-hour format, 2 = 12-hour
RegB_DSE = (1 << 0), // (DSE) Daylight savings enable
RegC_IRQF = (1 << 7), // (IRQF) Interrupt request flag
RegC_PF = (1 << 6), // (PF) Periodic interrupt flag
RegC_AF = (1 << 5), // (AF) Alarm flag
RegC_UF = (1 << 4), // (AF) Update-ended interrupt flag
};
class CMOS : public IODevice {
public:
@ -24,6 +65,23 @@ public:
bool IORead(uint32_t port, uint32_t *value, uint8_t size) override;
bool IOWrite(uint32_t port, uint32_t value, uint8_t size) override;
private:
// Selected register address
uint8_t m_regAddr;
// CMOS memory
uint8_t m_memory[0x100];
// Offset from host RTC (in seconds), used to virtualize the RTC
int64_t m_offset;
void ReadRTC(uint8_t reg, uint8_t *value);
void WriteRTC(uint8_t reg, uint8_t value);
uint8_t ToBCD(uint8_t value);
uint8_t FromBCD(uint8_t value);
uint8_t ToBCDHour(uint8_t hour);
uint8_t FromBCDHour(uint8_t hour);
};
}