[38699] | 1 | /*
|
---|
[98103] | 2 | * Copyright (C) 2006-2023 Oracle and/or its affiliates.
|
---|
[38699] | 3 | *
|
---|
[96407] | 4 | * This file is part of VirtualBox base platform packages, as
|
---|
| 5 | * available from https://www.virtualbox.org.
|
---|
| 6 | *
|
---|
| 7 | * This program is free software; you can redistribute it and/or
|
---|
| 8 | * modify it under the terms of the GNU General Public License
|
---|
| 9 | * as published by the Free Software Foundation, in version 3 of the
|
---|
| 10 | * License.
|
---|
| 11 | *
|
---|
| 12 | * This program is distributed in the hope that it will be useful, but
|
---|
| 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
| 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
| 15 | * General Public License for more details.
|
---|
| 16 | *
|
---|
| 17 | * You should have received a copy of the GNU General Public License
|
---|
| 18 | * along with this program; if not, see <https://www.gnu.org/licenses>.
|
---|
| 19 | *
|
---|
| 20 | * SPDX-License-Identifier: GPL-3.0-only
|
---|
[69498] | 21 | * --------------------------------------------------------------------
|
---|
[38699] | 22 | *
|
---|
| 23 | * This code is based on:
|
---|
| 24 | *
|
---|
| 25 | * ROM BIOS for use with Bochs/Plex86/QEMU emulation environment
|
---|
| 26 | *
|
---|
| 27 | * Copyright (C) 2002 MandrakeSoft S.A.
|
---|
| 28 | *
|
---|
| 29 | * MandrakeSoft S.A.
|
---|
| 30 | * 43, rue d'Aboukir
|
---|
| 31 | * 75002 Paris - France
|
---|
| 32 | * http://www.linux-mandrake.com/
|
---|
| 33 | * http://www.mandrakesoft.com/
|
---|
| 34 | *
|
---|
| 35 | * This library is free software; you can redistribute it and/or
|
---|
| 36 | * modify it under the terms of the GNU Lesser General Public
|
---|
| 37 | * License as published by the Free Software Foundation; either
|
---|
| 38 | * version 2 of the License, or (at your option) any later version.
|
---|
| 39 | *
|
---|
| 40 | * This library is distributed in the hope that it will be useful,
|
---|
| 41 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
| 42 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
| 43 | * Lesser General Public License for more details.
|
---|
| 44 | *
|
---|
| 45 | * You should have received a copy of the GNU Lesser General Public
|
---|
| 46 | * License along with this library; if not, write to the Free Software
|
---|
| 47 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
---|
| 48 | *
|
---|
| 49 | */
|
---|
| 50 |
|
---|
[69501] | 51 | /*
|
---|
| 52 | * Oracle LGPL Disclaimer: For the avoidance of doubt, except that if any license choice
|
---|
| 53 | * other than GPL or LGPL is available it will apply instead, Oracle elects to use only
|
---|
| 54 | * the Lesser General Public License version 2.1 (LGPLv2) at this time for any software where
|
---|
| 55 | * a choice of LGPL license versions is made available with the language indicating
|
---|
| 56 | * that LGPLv2 or any later version may be used, or where a choice of which version
|
---|
| 57 | * of the LGPL is applied is otherwise unspecified.
|
---|
| 58 | */
|
---|
[38699] | 59 |
|
---|
[69501] | 60 |
|
---|
[38699] | 61 | #include <stdint.h>
|
---|
| 62 | #include "biosint.h"
|
---|
| 63 | #include "inlines.h"
|
---|
| 64 |
|
---|
| 65 | #if DEBUG_INT1A
|
---|
| 66 | # define BX_DEBUG_INT1A(...) BX_DEBUG(__VA_ARGS__)
|
---|
| 67 | #else
|
---|
| 68 | # define BX_DEBUG_INT1A(...)
|
---|
| 69 | #endif
|
---|
| 70 |
|
---|
| 71 | // for access to RAM area which is used by interrupt vectors
|
---|
| 72 | // and BIOS Data Area
|
---|
| 73 |
|
---|
| 74 | typedef struct {
|
---|
| 75 | uint8_t filler1[0x400];
|
---|
| 76 | uint8_t filler2[0x6c];
|
---|
| 77 | uint16_t ticks_low;
|
---|
| 78 | uint16_t ticks_high;
|
---|
| 79 | uint8_t midnight_flag;
|
---|
| 80 | } bios_data_t;
|
---|
| 81 |
|
---|
| 82 | #define BiosData ((bios_data_t __far *) 0)
|
---|
| 83 |
|
---|
| 84 | void init_rtc(void)
|
---|
| 85 | {
|
---|
| 86 | outb_cmos(0x0a, 0x26);
|
---|
| 87 | outb_cmos(0x0b, 0x02);
|
---|
| 88 | inb_cmos(0x0c);
|
---|
| 89 | inb_cmos(0x0d);
|
---|
| 90 | }
|
---|
| 91 |
|
---|
| 92 | bx_bool rtc_updating(void)
|
---|
| 93 | {
|
---|
| 94 | // This function checks to see if the update-in-progress bit
|
---|
| 95 | // is set in CMOS Status Register A. If not, it returns 0.
|
---|
| 96 | // If it is set, it tries to wait until there is a transition
|
---|
| 97 | // to 0, and will return 0 if such a transition occurs. A 1
|
---|
| 98 | // is returned only after timing out. The maximum period
|
---|
| 99 | // that this bit should be set is constrained to 244useconds.
|
---|
| 100 | // The count I use below guarantees coverage or more than
|
---|
| 101 | // this time, with any reasonable IPS setting.
|
---|
[48123] | 102 |
|
---|
[38699] | 103 | uint16_t iter;
|
---|
[48123] | 104 |
|
---|
[38699] | 105 | iter = 25000;
|
---|
| 106 | while (--iter != 0) {
|
---|
| 107 | if ( (inb_cmos(0x0a) & 0x80) == 0 )
|
---|
| 108 | return 0;
|
---|
| 109 | }
|
---|
| 110 | return 1; // update-in-progress never transitioned to 0
|
---|
| 111 | }
|
---|
| 112 |
|
---|
| 113 |
|
---|
| 114 | extern void eoi_both_pics(void); /* in assembly code */
|
---|
| 115 | #pragma aux eoi_both_pics "*";
|
---|
| 116 |
|
---|
| 117 | void call_int_4a(void);
|
---|
| 118 | #pragma aux call_int_4a = "int 4Ah";
|
---|
| 119 |
|
---|
| 120 | void BIOSCALL int70_function(pusha_regs_t regs, uint16_t ds, uint16_t es, iret_addr_t iret_addr)
|
---|
| 121 | {
|
---|
| 122 | // INT 70h: IRQ 8 - CMOS RTC interrupt from periodic or alarm modes
|
---|
| 123 | uint8_t registerB = 0, registerC = 0;
|
---|
[48123] | 124 |
|
---|
[38699] | 125 | // Check which modes are enabled and have occurred.
|
---|
| 126 | registerB = inb_cmos( 0xB );
|
---|
| 127 | registerC = inb_cmos( 0xC );
|
---|
[48123] | 128 |
|
---|
[38699] | 129 | if( ( registerB & 0x60 ) != 0 ) {
|
---|
| 130 | if( ( registerC & 0x20 ) != 0 ) {
|
---|
| 131 | // Handle Alarm Interrupt.
|
---|
| 132 | int_enable();
|
---|
| 133 | call_int_4a();
|
---|
| 134 | int_disable();
|
---|
| 135 | }
|
---|
| 136 | if( ( registerC & 0x40 ) != 0 ) {
|
---|
| 137 | // Handle Periodic Interrupt.
|
---|
[48123] | 138 |
|
---|
[38699] | 139 | if( read_byte( 0x40, 0xA0 ) != 0 ) {
|
---|
[89270] | 140 | // Wait Interval (Int 15, AH=83 or AH=86) active.
|
---|
[38699] | 141 | uint32_t time;
|
---|
[48123] | 142 |
|
---|
[38699] | 143 | time = read_dword( 0x40, 0x9C ); // Time left in microseconds.
|
---|
| 144 | if( time < 0x3D1 ) {
|
---|
| 145 | // Done waiting.
|
---|
| 146 | uint16_t segment, offset;
|
---|
[48123] | 147 |
|
---|
[38699] | 148 | segment = read_word( 0x40, 0x98 );
|
---|
| 149 | offset = read_word( 0x40, 0x9A );
|
---|
[89270] | 150 | write_byte( 0x40, 0xA0, 0 ); // Turn off status byte.
|
---|
[38699] | 151 | outb_cmos( 0xB, registerB & 0x37 ); // Clear the Periodic Interrupt.
|
---|
| 152 | write_byte( segment, offset, read_byte(segment, offset) | 0x80 ); // Write to specified flag byte.
|
---|
| 153 | } else {
|
---|
| 154 | // Continue waiting.
|
---|
| 155 | time -= 0x3D1;
|
---|
| 156 | write_dword( 0x40, 0x9C, time );
|
---|
| 157 | }
|
---|
| 158 | }
|
---|
| 159 | }
|
---|
| 160 | }
|
---|
| 161 | eoi_both_pics();
|
---|
| 162 | }
|
---|
| 163 |
|
---|
[63562] | 164 | /// @todo the coding style WRT register access is totally inconsistent
|
---|
[38699] | 165 | // in the following routines
|
---|
| 166 |
|
---|
| 167 | void BIOSCALL int1a_function(pusha_regs_t regs, uint16_t ds, uint16_t es, iret_addr_t iret_addr)
|
---|
| 168 | {
|
---|
| 169 | uint8_t val8;
|
---|
| 170 |
|
---|
[48123] | 171 | BX_DEBUG_INT1A("int1a: AX=%04x BX=%04x CX=%04x DX=%04x DS=%04x\n",
|
---|
[38699] | 172 | regs.u.r16.ax, regs.u.r16.bx, regs.u.r16.cx, regs.u.r16.dx, ds);
|
---|
[48123] | 173 | int_enable();
|
---|
| 174 |
|
---|
[38699] | 175 | switch (regs.u.r8.ah) {
|
---|
| 176 | case 0: // get current clock count
|
---|
| 177 | int_disable();
|
---|
| 178 | regs.u.r16.cx = BiosData->ticks_high;
|
---|
| 179 | regs.u.r16.dx = BiosData->ticks_low;
|
---|
| 180 | regs.u.r8.al = BiosData->midnight_flag;
|
---|
| 181 | BiosData->midnight_flag = 0; // reset flag
|
---|
| 182 | int_enable();
|
---|
| 183 | // AH already 0
|
---|
| 184 | ClearCF(iret_addr.flags); // OK
|
---|
| 185 | break;
|
---|
[48123] | 186 |
|
---|
[38699] | 187 | case 1: // Set Current Clock Count
|
---|
| 188 | int_disable();
|
---|
| 189 | BiosData->ticks_high = regs.u.r16.cx;
|
---|
| 190 | BiosData->ticks_low = regs.u.r16.dx;
|
---|
| 191 | BiosData->midnight_flag = 0; // reset flag
|
---|
| 192 | int_enable();
|
---|
| 193 | regs.u.r8.ah = 0;
|
---|
| 194 | ClearCF(iret_addr.flags); // OK
|
---|
| 195 | break;
|
---|
[48123] | 196 |
|
---|
[38699] | 197 | case 2: // Read CMOS Time
|
---|
| 198 | if (rtc_updating()) {
|
---|
| 199 | SetCF(iret_addr.flags);
|
---|
| 200 | break;
|
---|
| 201 | }
|
---|
[48123] | 202 |
|
---|
[38699] | 203 | regs.u.r8.dh = inb_cmos(0x00); // Seconds
|
---|
| 204 | regs.u.r8.cl = inb_cmos(0x02); // Minutes
|
---|
| 205 | regs.u.r8.ch = inb_cmos(0x04); // Hours
|
---|
| 206 | regs.u.r8.dl = inb_cmos(0x0b) & 0x01; // Stat Reg B
|
---|
| 207 | regs.u.r8.ah = 0;
|
---|
| 208 | regs.u.r8.al = regs.u.r8.ch;
|
---|
| 209 | ClearCF(iret_addr.flags); // OK
|
---|
| 210 | break;
|
---|
[48123] | 211 |
|
---|
[38699] | 212 | case 3: // Set CMOS Time
|
---|
| 213 | // Using a debugger, I notice the following masking/setting
|
---|
| 214 | // of bits in Status Register B, by setting Reg B to
|
---|
| 215 | // a few values and getting its value after INT 1A was called.
|
---|
| 216 | //
|
---|
| 217 | // try#1 try#2 try#3
|
---|
| 218 | // before 1111 1101 0111 1101 0000 0000
|
---|
| 219 | // after 0110 0010 0110 0010 0000 0010
|
---|
| 220 | //
|
---|
| 221 | // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
|
---|
| 222 | // My assumption: RegB = ((RegB & 01100000b) | 00000010b)
|
---|
| 223 | if (rtc_updating()) {
|
---|
| 224 | init_rtc();
|
---|
| 225 | // fall through as if an update were not in progress
|
---|
| 226 | }
|
---|
| 227 | outb_cmos(0x00, regs.u.r8.dh); // Seconds
|
---|
| 228 | outb_cmos(0x02, regs.u.r8.cl); // Minutes
|
---|
| 229 | outb_cmos(0x04, regs.u.r8.ch); // Hours
|
---|
| 230 | // Set Daylight Savings time enabled bit to requested value
|
---|
| 231 | val8 = (inb_cmos(0x0b) & 0x60) | 0x02 | (regs.u.r8.dl & 0x01);
|
---|
| 232 | // (reg B already selected)
|
---|
| 233 | outb_cmos(0x0b, val8);
|
---|
| 234 | regs.u.r8.ah = 0;
|
---|
| 235 | regs.u.r8.al = val8; // val last written to Reg B
|
---|
| 236 | ClearCF(iret_addr.flags); // OK
|
---|
| 237 | break;
|
---|
[48123] | 238 |
|
---|
[38699] | 239 | case 4: // Read CMOS Date
|
---|
| 240 | regs.u.r8.ah = 0;
|
---|
| 241 | if (rtc_updating()) {
|
---|
| 242 | SetCF(iret_addr.flags);
|
---|
| 243 | break;
|
---|
| 244 | }
|
---|
| 245 | regs.u.r8.cl = inb_cmos(0x09); // Year
|
---|
| 246 | regs.u.r8.dh = inb_cmos(0x08); // Month
|
---|
| 247 | regs.u.r8.dl = inb_cmos(0x07); // Day of Month
|
---|
| 248 | regs.u.r8.ch = inb_cmos(0x32); // Century
|
---|
| 249 | regs.u.r8.al = regs.u.r8.ch;
|
---|
| 250 | ClearCF(iret_addr.flags); // OK
|
---|
| 251 | break;
|
---|
[48123] | 252 |
|
---|
[38699] | 253 | case 5: // Set CMOS Date
|
---|
| 254 | // Using a debugger, I notice the following masking/setting
|
---|
| 255 | // of bits in Status Register B, by setting Reg B to
|
---|
| 256 | // a few values and getting its value after INT 1A was called.
|
---|
| 257 | //
|
---|
| 258 | // try#1 try#2 try#3 try#4
|
---|
| 259 | // before 1111 1101 0111 1101 0000 0010 0000 0000
|
---|
| 260 | // after 0110 1101 0111 1101 0000 0010 0000 0000
|
---|
| 261 | //
|
---|
| 262 | // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
|
---|
| 263 | // My assumption: RegB = (RegB & 01111111b)
|
---|
| 264 | if (rtc_updating()) {
|
---|
| 265 | init_rtc();
|
---|
| 266 | SetCF(iret_addr.flags);
|
---|
| 267 | break;
|
---|
| 268 | }
|
---|
| 269 | outb_cmos(0x09, regs.u.r8.cl); // Year
|
---|
| 270 | outb_cmos(0x08, regs.u.r8.dh); // Month
|
---|
| 271 | outb_cmos(0x07, regs.u.r8.dl); // Day of Month
|
---|
| 272 | outb_cmos(0x32, regs.u.r8.ch); // Century
|
---|
| 273 | val8 = inb_cmos(0x0b) & 0x7f; // clear halt-clock bit
|
---|
| 274 | outb_cmos(0x0b, val8);
|
---|
| 275 | regs.u.r8.ah = 0;
|
---|
| 276 | regs.u.r8.al = val8; // AL = val last written to Reg B
|
---|
| 277 | ClearCF(iret_addr.flags); // OK
|
---|
| 278 | break;
|
---|
[48123] | 279 |
|
---|
[38699] | 280 | case 6: // Set Alarm Time in CMOS
|
---|
| 281 | // Using a debugger, I notice the following masking/setting
|
---|
| 282 | // of bits in Status Register B, by setting Reg B to
|
---|
| 283 | // a few values and getting its value after INT 1A was called.
|
---|
| 284 | //
|
---|
| 285 | // try#1 try#2 try#3
|
---|
| 286 | // before 1101 1111 0101 1111 0000 0000
|
---|
| 287 | // after 0110 1111 0111 1111 0010 0000
|
---|
| 288 | //
|
---|
| 289 | // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
|
---|
| 290 | // My assumption: RegB = ((RegB & 01111111b) | 00100000b)
|
---|
| 291 | val8 = inb_cmos(0x0b); // Get Status Reg B
|
---|
| 292 | regs.u.r16.ax = 0;
|
---|
| 293 | if (val8 & 0x20) {
|
---|
| 294 | // Alarm interrupt enabled already
|
---|
| 295 | SetCF(iret_addr.flags); // Error: alarm in use
|
---|
| 296 | break;
|
---|
| 297 | }
|
---|
| 298 | if (rtc_updating()) {
|
---|
| 299 | init_rtc();
|
---|
| 300 | // fall through as if an update were not in progress
|
---|
| 301 | }
|
---|
| 302 | outb_cmos(0x01, regs.u.r8.dh); // Seconds alarm
|
---|
| 303 | outb_cmos(0x03, regs.u.r8.cl); // Minutes alarm
|
---|
| 304 | outb_cmos(0x05, regs.u.r8.ch); // Hours alarm
|
---|
| 305 | outb(0xa1, inb(0xa1) & 0xfe); // enable IRQ 8
|
---|
| 306 | // enable Status Reg B alarm bit, clear halt clock bit
|
---|
| 307 | outb_cmos(0x0b, (val8 & 0x7f) | 0x20);
|
---|
| 308 | ClearCF(iret_addr.flags); // OK
|
---|
| 309 | break;
|
---|
[48123] | 310 |
|
---|
[38699] | 311 | case 7: // Turn off Alarm
|
---|
| 312 | // Using a debugger, I notice the following masking/setting
|
---|
| 313 | // of bits in Status Register B, by setting Reg B to
|
---|
| 314 | // a few values and getting its value after INT 1A was called.
|
---|
| 315 | //
|
---|
| 316 | // try#1 try#2 try#3 try#4
|
---|
| 317 | // before 1111 1101 0111 1101 0010 0000 0010 0010
|
---|
| 318 | // after 0100 0101 0101 0101 0000 0000 0000 0010
|
---|
| 319 | //
|
---|
| 320 | // Bit4 in try#1 flipped in hardware (forced low) due to bit7=1
|
---|
| 321 | // My assumption: RegB = (RegB & 01010111b)
|
---|
| 322 | val8 = inb_cmos(0x0b); // Get Status Reg B
|
---|
| 323 | // clear clock-halt bit, disable alarm bit
|
---|
| 324 | outb_cmos(0x0b, val8 & 0x57); // disable alarm bit
|
---|
| 325 | regs.u.r8.ah = 0;
|
---|
| 326 | regs.u.r8.al = val8; // val last written to Reg B
|
---|
| 327 | ClearCF(iret_addr.flags); // OK
|
---|
| 328 | break;
|
---|
[48123] | 329 |
|
---|
[38699] | 330 | default:
|
---|
| 331 | BX_DEBUG_INT1A("int1a: AX=%04x unsupported\n", regs.u.r16.ax);
|
---|
| 332 | SetCF(iret_addr.flags); // Unsupported
|
---|
| 333 | }
|
---|
| 334 | }
|
---|