// Copyright (C) 2001 MandrakeSoft S.A. // // MandrakeSoft S.A. // 43, rue d'Aboukir // 75002 Paris - France // http://www.linux-mandrake.com/ // http://www.mandrakesoft.com/ // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #include "bochs.h" #define LOG_THIS bx_cmos. bx_cmos_c bx_cmos; #if BX_USE_CMOS_SMF #define this (&bx_cmos) #endif // check that BX_NUM_CMOS_REGS is 64 or 128 #if (BX_NUM_CMOS_REGS == 64) #elif (BX_NUM_CMOS_REGS == 128) #else #error "Invalid BX_NUM_CMOS_REGS value in config.h" #endif bx_cmos_c::bx_cmos_c(void) { put("CMOS"); settype(CMOSLOG); } bx_cmos_c::~bx_cmos_c(void) { BX_DEBUG(("Exit.")); } void bx_cmos_c::init(bx_devices_c *d) { unsigned i; BX_DEBUG(("Init $Id: cmos.cc,v 1.12 2001-06-27 20:11:10 fries Exp $")); // CMOS RAM & RTC BX_CMOS_THIS devices = d; BX_CMOS_THIS devices->register_io_read_handler(this, read_handler, 0x0070, "CMOS RAM"); BX_CMOS_THIS devices->register_io_read_handler(this, read_handler, 0x0071, "CMOS RAM"); BX_CMOS_THIS devices->register_io_write_handler(this, write_handler, 0x0070, "CMOS RAM"); BX_CMOS_THIS devices->register_io_write_handler(this, write_handler, 0x0071, "CMOS RAM"); BX_CMOS_THIS devices->register_irq(8, "CMOS RTC"); BX_CMOS_THIS s.periodic_timer_index = bx_pc_system.register_timer(this, periodic_timer_handler, 1000000, 1,0); // continuous, not-active BX_CMOS_THIS s.one_second_timer_index = bx_pc_system.register_timer(this, one_second_timer_handler, 1000000, 1,0); // continuous, not-active for (i=0; iget () == 1) BX_CMOS_THIS s.timeval = time(NULL); else if (bx_options.cmos.Otime0->get () != 0) BX_CMOS_THIS s.timeval = bx_options.cmos.Otime0->get (); char *tmptime; while( (tmptime = strdup(ctime(&(BX_CMOS_THIS s.timeval)))) == NULL) { BX_PANIC(("Out of memory.")); } tmptime[strlen(tmptime)-1]='\0'; BX_INFO(("Setting initial clock to: %s", tmptime)); update_clock(); // load CMOS from image file if requested. if (bx_options.cmos.OcmosImage->get ()) { // CMOS image file requested int fd, ret; struct stat stat_buf; fd = open(bx_options.cmos.Opath->getptr (), O_RDONLY #ifdef O_BINARY | O_BINARY #endif ); if (fd < 0) { BX_PANIC(("trying to open cmos image file '%s'", bx_options.cmos.Opath->getptr ())); } ret = fstat(fd, &stat_buf); if (ret) { BX_PANIC(("CMOS: could not fstat() image file.")); } if (stat_buf.st_size != BX_NUM_CMOS_REGS) { BX_PANIC(("CMOS: image file not same size as BX_NUM_CMOS_REGS.")); } ret = ::read(fd, (bx_ptr_t) BX_CMOS_THIS s.reg, BX_NUM_CMOS_REGS); if (ret != BX_NUM_CMOS_REGS) { BX_PANIC(("CMOS: error reading cmos file.")); } close(fd); BX_INFO(("successfuly read from image file '%s'.", bx_options.cmos.Opath->getptr ())); } else { // CMOS values generated BX_CMOS_THIS s.reg[0x0a] = 0x26; BX_CMOS_THIS s.reg[0x0b] = 0x02; BX_CMOS_THIS s.reg[0x0c] = 0x00; BX_CMOS_THIS s.reg[0x0d] = 0x80; } } void bx_cmos_c::reset(void) { BX_CMOS_THIS s.cmos_mem_address = 0; // RESET affects the following registers: // CRA: no effects // CRB: bits 4,5,6 forced to 0 // CRC: bits 4,5,6,7 forced to 0 // CRD: no effects BX_CMOS_THIS s.reg[0x0b] &= 0x8f; BX_CMOS_THIS s.reg[0x0c] = 0; // One second timer for updating clock & alarm functions bx_pc_system.activate_timer(BX_CMOS_THIS s.one_second_timer_index, 1000000, 1); // handle periodic interrupt rate select BX_CMOS_THIS CRA_change(); } void bx_cmos_c::CRA_change(void) { unsigned nibble; // Periodic Interrupt timer nibble = BX_CMOS_THIS s.reg[0x0a] & 0x0f; if (nibble == 0) { // No Periodic Interrupt Rate when 0, deactivate timer bx_pc_system.deactivate_timer(BX_CMOS_THIS s.periodic_timer_index); BX_CMOS_THIS s.periodic_interval_usec = (Bit32u) -1; // max value } else { // values 0001b and 0010b are the same as 1000b and 1001b if (nibble <= 2) nibble += 7; BX_CMOS_THIS s.periodic_interval_usec = (unsigned) (1000000.0L / (32768.0L / (1 << (nibble - 1)))); // if Periodic Interrupt Enable bit set, activate timer if ( BX_CMOS_THIS s.reg[0x0b] & 0x40 ) bx_pc_system.activate_timer(BX_CMOS_THIS s.periodic_timer_index, BX_CMOS_THIS s.periodic_interval_usec, 1); else bx_pc_system.deactivate_timer(BX_CMOS_THIS s.periodic_timer_index); } } // static IO port read callback handler // redirects to non-static class handler to avoid virtual functions Bit32u bx_cmos_c::read_handler(void *this_ptr, Bit32u address, unsigned io_len) { #if !BX_USE_CMOS_SMF bx_cmos_c *class_ptr = (bx_cmos_c *) this_ptr; return( class_ptr->read(address, io_len) ); } Bit32u bx_cmos_c::read(Bit32u address, unsigned io_len) { #else UNUSED(this_ptr); #endif Bit8u ret8; if (io_len > 1) BX_PANIC(("io read from address %08x len=%u", (unsigned) address, (unsigned) io_len)); if (bx_dbg.cmos) BX_INFO(("CMOS read of CMOS register 0x%x", (unsigned) BX_CMOS_THIS s.cmos_mem_address)); switch (address) { case 0x0071: if (BX_CMOS_THIS s.cmos_mem_address >= BX_NUM_CMOS_REGS) { BX_PANIC(("unsupported cmos io read, register(0x%02x)!", (unsigned) BX_CMOS_THIS s.cmos_mem_address)); } ret8 = BX_CMOS_THIS s.reg[BX_CMOS_THIS s.cmos_mem_address]; // all bits of Register C are cleared after a read occurs. if (BX_CMOS_THIS s.cmos_mem_address == 0x0c) BX_CMOS_THIS s.reg[0x0c] = 0x00; return(ret8); break; default: BX_PANIC(("unsupported cmos read, address=%0x%x!", (unsigned) address)); return(0); break; } } // static IO port write callback handler // redirects to non-static class handler to avoid virtual functions void bx_cmos_c::write_handler(void *this_ptr, Bit32u address, Bit32u value, unsigned io_len) { #if !BX_USE_CMOS_SMF bx_cmos_c *class_ptr = (bx_cmos_c *) this_ptr; class_ptr->write(address, value, io_len); } void bx_cmos_c::write(Bit32u address, Bit32u value, unsigned io_len) { #else UNUSED(this_ptr); #endif // !BX_USE_CMOS_SMF if (io_len > 1) BX_PANIC(("io write to address %08x len=%u", (unsigned) address, (unsigned) io_len)); if (bx_dbg.cmos) BX_INFO(("CMOS write to address: 0x%x = 0x%x", (unsigned) address, (unsigned) value)); switch (address) { case 0x0070: #if (BX_NUM_CMOS_REGS == 64) BX_CMOS_THIS s.cmos_mem_address = value & 0x3F; #else BX_CMOS_THIS s.cmos_mem_address = value & 0x7F; #endif break; case 0x0071: if (BX_CMOS_THIS s.cmos_mem_address >= BX_NUM_CMOS_REGS) { BX_PANIC(("unsupported cmos io write, register(0x%02x)=%02x!", (unsigned) BX_CMOS_THIS s.cmos_mem_address, (unsigned) value)); return; } switch (BX_CMOS_THIS s.cmos_mem_address) { case 0x00: // seconds case 0x01: // seconds alarm case 0x02: // minutes case 0x03: // minutes alarm case 0x04: // hours case 0x05: // hours alarm case 0x06: // day of the week case 0x07: // day of the month case 0x08: // month case 0x09: // year //BX_INFO(("write reg %02xh: value = %02xh", // (unsigned) BX_CMOS_THIS s.cmos_mem_address, (unsigned) value); BX_CMOS_THIS s.reg[BX_CMOS_THIS s.cmos_mem_address] = value; return; break; case 0x0a: // Control Register A // bit 7: Update in Progress (read-only) // 1 = signifies time registers will be updated within 244us // 0 = time registers will not occur before 244us // note: this bit reads 0 when CRB bit 7 is 1 // bit 6..4: Divider Chain Control // 000 oscillator disabled // 001 oscillator disabled // 010 Normal operation // 011 TEST // 100 TEST // 101 TEST // 110 Divider Chain RESET // 111 Divider Chain RESET // bit 3..0: Periodic Interrupt Rate Select // 0000 None // 0001 3.90625 ms // 0010 7.8125 ms // 0011 122.070 us // 0100 244.141 us // 0101 488.281 us // 0110 976.562 us // 0111 1.953125 ms // 1000 3.90625 ms // 1001 7.8125 ms // 1010 15.625 ms // 1011 31.25 ms // 1100 62.5 ms // 1101 125 ms // 1110 250 ms // 1111 500 ms unsigned dcc; dcc = (value >> 4) & 0x07; if (dcc != 0x02) { BX_PANIC(("CRA: divider chain control 0x%x", dcc)); } BX_CMOS_THIS s.reg[0x0a] = value & 0x7f; BX_CMOS_THIS CRA_change(); return; break; case 0x0b: // Control Register B // bit 0: Daylight Savings Enable // 1 = enable daylight savings // 0 = disable daylight savings // bit 1: 24/12 houre mode // 1 = 24 hour format // 0 = 12 hour format // bit 2: Data Mode // 1 = binary format // 0 = BCD format // bit 3: "square wave enable" // Not supported and always read as 0 // bit 4: Update Ended Interrupt Enable // 1 = enable generation of update ended interrupt // 0 = disable // bit 5: Alarm Interrupt Enable // 1 = enable generation of alarm interrupt // 0 = disable // bit 6: Periodic Interrupt Enable // 1 = enable generation of periodic interrupt // 0 = disable // bit 7: Set mode // 1 = user copy of time is "frozen" allowing time registers // to be accessed without regard for an occurance of an update // 0 = time updates occur normally // can not handle binary or 12-hour mode yet. if (value & 0x04) BX_PANIC(("write status reg B, binary format enabled.")); if ( !(value & 0x02) ) BX_PANIC(("write status reg B, 12 hour mode enabled.")); value &= 0xf7; // bit3 always 0 // Note: setting bit 7 clears bit 4 if (value & 0x80) value &= 0xef; unsigned prev_CRB; prev_CRB = BX_CMOS_THIS s.reg[0x0b]; BX_CMOS_THIS s.reg[0x0b] = value; if ( (prev_CRB & 0x40) != (value & 0x40) ) { // Periodic Interrupt Enabled changed if (prev_CRB & 0x40) { // transition from 1 to 0, deactivate timer bx_pc_system.deactivate_timer( BX_CMOS_THIS s.periodic_timer_index); } else { // transition from 0 to 1 // if rate select is not 0, activate timer if ( (BX_CMOS_THIS s.reg[0x0a] & 0x0f) != 0 ) { bx_pc_system.activate_timer( BX_CMOS_THIS s.periodic_timer_index, BX_CMOS_THIS s.periodic_interval_usec, 1); } } } return; break; case 0x0c: // Control Register C case 0x0d: // Control Register D BX_ERROR(("write to control register 0x%x (read-only)", BX_CMOS_THIS s.cmos_mem_address)); break; case 0x0e: // diagnostic status BX_DEBUG(("write register 0Eh: %02x", (unsigned) value));; break; case 0x0f: // shutdown status switch (value) { case 0x00: /* proceed with normal POST (soft reset) */ BX_DEBUG(("Reg 0F set to 0: shutdown action = normal POST"));; break; case 0x02: /* shutdown after memory test */ BX_DEBUG(("Reg 0Fh: request to change shutdown action" " to shutdown after memory test")); break; case 0x03: BX_DEBUG(("Reg 0Fh(03) : Shutdown after memory test !"));; break; case 0x04: /* jump to disk bootstrap routine */ BX_DEBUG(("Reg 0Fh: request to change shutdown action " "to jump to disk bootstrap routine.")); break; case 0x06: BX_DEBUG(("Reg 0Fh(06) : Shutdown after memory test !"));; break; case 0x09: /* return to BIOS extended memory block move (interrupt 15h, func 87h was in progress) */ BX_DEBUG(("Reg 0Fh: request to change shutdown action " "to return to BIOS extended memory block move.")); break; case 0x0a: /* jump to DWORD pointer at 40:67 */ BX_DEBUG(("Reg 0Fh: request to change shutdown action" " to jump to DWORD at 40:67")); break; default: BX_PANIC(("unsupported cmos io write to reg F, case %x!", (unsigned) value)); break; } break; default: BX_DEBUG(("write reg %02xh: value = %02xh", (unsigned) BX_CMOS_THIS s.cmos_mem_address, (unsigned) value)); break; } BX_CMOS_THIS s.reg[BX_CMOS_THIS s.cmos_mem_address] = value; break; } } void bx_cmos_c::checksum_cmos(void) { unsigned i; Bit16u sum; sum = 0; for (i=0x10; i<=0x2d; i++) { sum += BX_CMOS_THIS s.reg[i]; } BX_CMOS_THIS s.reg[0x2e] = (sum >> 8) & 0xff; /* checksum high */ BX_CMOS_THIS s.reg[0x2f] = (sum & 0xff); /* checksum low */ } void bx_cmos_c::periodic_timer_handler(void *this_ptr) { bx_cmos_c *class_ptr = (bx_cmos_c *) this_ptr; // if periodic interrupts are enabled, trip IRQ 8, and // update status register C if (class_ptr->s.reg[0x0b] & 0x40) { class_ptr->s.reg[0x0c] |= 0xc0; // Interrupt Request, Periodic Int class_ptr->devices->pic->trigger_irq(8); } } void bx_cmos_c::one_second_timer_handler(void *this_ptr) { bx_cmos_c *class_ptr = (bx_cmos_c *) this_ptr; // update internal time/date buffer class_ptr->s.timeval++; // Dont update CMOS user copy of time/date if CRB bit7 is 1 // Nothing else do to if (class_ptr->s.reg[0x0b] & 0x80) return; class_ptr->update_clock(); // if update interrupts are enabled, trip IRQ 8, and // update status register C if (class_ptr->s.reg[0x0b] & 0x10) { class_ptr->s.reg[0x0c] |= 0x90; // Interrupt Request, Update Ended class_ptr->devices->pic->trigger_irq(8); } // compare CMOS user copy of time/date to alarm time/date here if (class_ptr->s.reg[0x0b] & 0x20) { // Alarm interrupts enabled Boolean alarm_match = 1; if ( (class_ptr->s.reg[0x01] & 0xc0) != 0xc0 ) { // seconds alarm not in dont care mode if (class_ptr->s.reg[0x00] != class_ptr->s.reg[0x01]) alarm_match = 0; } if ( (class_ptr->s.reg[0x03] & 0xc0) != 0xc0 ) { // minutes alarm not in dont care mode if (class_ptr->s.reg[0x02] != class_ptr->s.reg[0x03]) alarm_match = 0; } if ( (class_ptr->s.reg[0x05] & 0xc0) != 0xc0 ) { // hours alarm not in dont care mode if (class_ptr->s.reg[0x04] != class_ptr->s.reg[0x05]) alarm_match = 0; } if (alarm_match) { class_ptr->s.reg[0x0c] |= 0xa0; // Interrupt Request, Alarm Int class_ptr->devices->pic->trigger_irq(8); } } } void bx_cmos_c::update_clock() { struct tm *time_calendar; unsigned year, month, day, century; Bit8u val_bcd; time_calendar = localtime(& BX_CMOS_THIS s.timeval); // update seconds val_bcd = ((time_calendar->tm_sec / 10) << 4) | (time_calendar->tm_sec % 10); BX_CMOS_THIS s.reg[0x00] = val_bcd; // update minutes val_bcd = ((time_calendar->tm_min / 10) << 4) | (time_calendar->tm_min % 10); BX_CMOS_THIS s.reg[0x02] = val_bcd; // update hours val_bcd = ((time_calendar->tm_hour / 10) << 4) | (time_calendar->tm_hour % 10); BX_CMOS_THIS s.reg[0x04] = val_bcd; // update day of the week day = time_calendar->tm_wday + 1; // 0..6 to 1..7 BX_CMOS_THIS s.reg[0x06] = ((day / 10) << 4) | (day % 10); // update day of the month day = time_calendar->tm_mday; BX_CMOS_THIS s.reg[0x07] = ((day / 10) << 4) | (day % 10); // update month month = time_calendar->tm_mon + 1; BX_CMOS_THIS s.reg[0x08] = ((month / 10) << 4) | (month % 10); // update year year = time_calendar->tm_year % 100; BX_CMOS_THIS s.reg[0x09] = ((year / 10) << 4) | (year % 10); // update century century = (time_calendar->tm_year / 100) + 19; BX_CMOS_THIS s.reg[0x32] = ((century / 10) << 4) | (century % 10); }