NMI bit at port 0x70 is stuck on reads

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
~
Member
Member
Posts: 1227
Joined: Tue Mar 06, 2007 11:17 am
Libera.chat IRC: ArcheFire

NMI bit at port 0x70 is stuck on reads

Post by ~ »

According to the Protected Mode Tutorial #1 by Alexei Frounze, bit 7 (NMI bit) is stuck when reading CMOS index port 0x70, so I cannot clear it in a Thinkpad 390X nor in DOSBox, so there must be many more machines like these.

Maybe it should be initialized to a known state and then enable or disable NMIs without trying to read the current contents of byte at port 0x70 to preserve a probably invalid state from dummy data.

Is it an obsolete bit usable only in very old systems, 8088, 286, 386...?

This is the code I'm using:
http://devel.archefire.org/downfile.php ... ame=01.zip

This is what I get from my execution log:

Code: Select all

main.119: Point in a variable to absolute offset 0x46C, where the BIOS tick timer resides
          How to properly keep it updated from our OS at all times???

main.065: Clear the screen, print what this program does
          and set the function to be executed by the run time at exit (atexit).

main.076: Use the SMSW, store machine status word instruction,
          and if bit 0 is 1, exit the program
          as the CPU is currently in protected mode.

          Bit 0 of CR0 is currently 0

main.101: Execute CLI.

main.112: Disable NMIs -- call this log with parameters computed outside the function since x86 code calls it
backwards on stack and then it will fail to be set properly to display our numbers.
          Port 0x70 currently contains 0xff -- 11111111b


bmain.121: Bit 7 of port 0x70 (CMOS Memory Index) is 1
          so NMIs are disabled.

main.137: We simply enable protected mode by setting
bit 0 of CR0 to 1 (mov eax,cr0 -- inc eax -- mov cr0,eax), but after that we still need to set it up properly.

delay_RTC.053 -- We will iterate for 5 seconds.
delay_RTC.059 -- STEP 1. Get current seconds count, 68, BCD 0x44, in x.  The BIOS timer at 0x46C is now 1462296.
delay_RTC.064 -- STEP 2. Wait until the new seconds in the CMOS memory index 0 become different than the cached x second.
delay_RTC.072 -- STEP 3 (debug only). We iterated 108234 times to wait for 1 second change. The BIOS timer at 0x46C is no 1462300 (4 ticks total difference)
delay_RTC.059 -- STEP 1. Get current seconds count, 69, BCD 0x45, in x.  The BIOS timer at 0x46C is now 1462300.
delay_RTC.064 -- STEP 2. Wait until the new seconds in the CMOS memory index 0 become different than the cached x second.
delay_RTC.072 -- STEP 3 (debug only). We iterated 720653 times to wait for 1 second change. The BIOS timer at 0x46C is now 1462318 (18 ticks total difference)
delay_RTC.059 -- STEP 1. Get current seconds count, 70, BCD 0x46, in x.  The BIOS timer at 0x46C is now 1462318.
delay_RTC.064 -- STEP 2. Wait until the new seconds in the CMOS memory index 0 become different than the cached x second.
delay_RTC.072 -- STEP 3 (debug only). We iterated 910483 times to wait for 1 second change. The BIOS timer at 0x46C is now 1462336 (18 ticks total difference)
delay_RTC.059 -- STEP 1. Get current seconds count, 71, BCD 0x47, in x.  The BIOS timer at 0x46C is now 1462336.
delay_RTC.064 -- STEP 2. Wait until the new seconds in the CMOS memory index 0 become different than the cached x second.
delay_RTC.072 -- STEP 3 (debug only). We iterated 947023 times to wait for 1 second change. The BIOS timer at 0x46C is now 1462355 (19 ticks total difference)
delay_RTC.059 -- STEP 1. Get current seconds count, 72, BCD 0x48, in x.  The BIOS timer at 0x46C is now 1462355.
delay_RTC.064 -- STEP 2. Wait until the new seconds in the CMOS memory index 0 become different than the cached x second.
delay_RTC.072 -- STEP 3 (debug only). We iterated 882140 times to wait for 1 second change. The BIOS timer at 0x46C is now 1462373 (18 ticks total difference)


delay_RTC.080 -- Here we could improve the accuracy of our timer some more
                 if we made an average of all the counts, except the first one,
                 then we subtracted the first count from the average, got
                 the absolute value, or subtracted the smaller from the bigger value
                 and then we simply iterated one more time in a second loop only for the
                 amount of the subtraction to complete an obviously partial amount of time
                 to complete a whole second, as long as that count is smaller than the bigger count so far.

                 The only drawback is that it would be very CPU intensive, not suited for every multitasking application
                 and even less for a multitasking kernel and libraries.


main.147: We simply disable protected mode by clearing
bit 0 of CR0 to 0 (mov eax,cr0 -- dec eax, mov cr0,eax). We didn't set up anything further so we don't need to
reconfigure anything to return to Real Mode, but we can't call any Turbo C library functions or Real-Mode specific
code during that time or we will crash in a badly-enabled protected mode.

main.233: Enable NMIs -- call this log with parameters computed outside the function since x86 code calls it
backwards on stack and then it will fail to be set properly to display our numbers.
          Port 0x70 currently contains 0xff -- 11111111b


main.121: Bit 7 of port 0x70 (CMOS Memory Index) is 1
          so NMIs are disabled.

main.259: Now port 0x70 contains 0xff -- 11111111b


main.235: Execute STI.

main.291: WARNING: Port 0x70 is stuck on reads to 11111111b, so we can't enable/disable NMIs
          It must be a machine with an invalid port 0x70 on reads and write-only NMI enabled/disabled state at write-only byte port 0x70.

my_exit.047 -- Just print a message right before the program terminates.


NOTE: I see that this topic has cooled down properly, as it should. The question, although can develop a much more complete answer, has been fully answered. I have also given myself time to evaluate the suggestion from LtG for simplifying my book editing automation function, but now that everything has been cooled down and the conversation has been processed mentally in depth, now I see that LtG's suggestion isn't convenient because it simply does not allow for complex documentation, just for extremely basic logging, which is useless for what I want to achieve. My function is thought to generate many kinds of data in the future (image files for graphical memory snapshots, sound chunks, video or GIF loops, HTML, and anything that I could need), so my function is OK as it is. After all, it's intended to write a book, so it cannot be much more simple that it already is here. I need to leave it like that as a minimum.
Last edited by ~ on Thu Aug 03, 2017 11:13 pm, edited 4 times in total.
YouTube:
http://youtube.com/@AltComp126

My x86 emulator/kernel project and software tools/documentation:
http://master.dl.sourceforge.net/projec ... ip?viasf=1
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: NMI bit at port 0x70 is stuck on reads

Post by Brendan »

Hi,
~ wrote:According to the Protected Mode Tutorial #1 by Alexei Frounze, bit 7 (NMI bit) is stuck when reading CMOS index port 0x70, so I cannot clear it in a Thinkpad 390X nor in DOSBox, so there must be many more machines like these.

Maybe it should be initialized to a known state and then enable or disable NMIs without reading the current contents of byte at port 0x70.
The entire point of NMI is that it's not maskable; which is important for things like critical hardware errors (that should never be ignored).

This means that there should always by a valid NMI exception handler (that at a minimum, ceases normal execution and reports/displays an error to the user). Unfortunately there is one special case where this isn't easy - during CPU mode switches (e.g. where you can't atomically change the IDT from "real mode IVT" to "protected mode IDT" at the same time you enable protected mode).

There are 3 solutions to this:
  • Disable NMI, switch CPU modes, load a suitable IDT (with a suitable NMI handler), enable NMI. This leaves a tiny period of time where NMI is ignored (which is bad), but because the CPU mode is rarely changed (e.g. only during boot and then never again) it's possibly an acceptable risk.
  • Set the IDT limit to zero, switch CPU modes, load a suitable IDT. In this case if an NMI occurs during that tiny period of time you end up with "guaranteed behaviour" (a triple fault) that is likely to be much better than "bizarre who knows what behaviour" that would be caused by an ignored hardware error.
  • Use "excessive cleverness". Specifically, it's possible to have the same IDT that works correctly in 2 (or more) CPU modes because the size an IDT entry changes. In other words; you can have valid IVT entry for a real mode NMI handler at offset 2*4 (0x008 to 0x00B), and a valid IDT entry for a 32-bit protected mode NMI handler at offset 2*8 (0x010 to 0x017), and a valid IDT entry for a long mode NMI handler at offset 2*16 (0x020 to 0x02F). In this case you'd load the special IDT (to suit multiple CPU modes), then switch CPU modes, then load the real IDT.
For my code, I always use the second option ("set IDT limit to zero").


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Octocontrabass
Member
Member
Posts: 5575
Joined: Mon Mar 25, 2013 7:01 pm

Re: NMI bit at port 0x70 is stuck on reads

Post by Octocontrabass »

~ wrote:trying to read the current contents of byte at port 0x70
Port 0x70 is write-only. You can't read anything useful from it.
User avatar
~
Member
Member
Posts: 1227
Joined: Tue Mar 06, 2007 11:17 am
Libera.chat IRC: ArcheFire

Re: NMI bit at port 0x70 is stuck on reads

Post by ~ »

Read the code from Protected Mode Tutorial #1 from Alexei Frounze.

It tries to read port 0x70 to toggle the NMI bit (bit 7), but the document generated while running the program apparently shows that it can't be done.

So if that's the case, the program would have an error in the assumptions made to implement it, assuming that the state of the NMI bit could be read:

Code: Select all

/*
  PMode tutorials in C and Asm
  Copyright (C) 2000 Alexei A. Frounze
  The programs and sources come under the GPL 
  (GNU General Public License), for more information
  read the file gnu-gpl.txt (originally named COPYING).
*/

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <dos.h>

#include "pm.h"

#define byte unsigned char
#define word unsigned int


unsigned long far *BIOS_timer = MK_FP (0x40, 0x6C);



FILE *messageHandle;
char strbuff[512];
char strbuff2[512];
unsigned int uintbuff=0;
unsigned long ulongbuff=0;

byte port70initialValue=0;
int port70isStuck=1;



void WriteBookFromProgram(FILE *documentHandle, char *message, long messageLength, long messageType);



byte read_CMOS_reg (byte reg) {
  outportb (0x70, reg);
  return inportb (0x71);
}

void write_CMOS_reg (byte reg, byte value) {
  outportb (0x70, reg);
  outportb (0x71, value);
}

void delay_RTC (int secs) {
  byte x;

  while (secs--) {
    x = read_CMOS_reg(0);               /* read seconds from RTC */
    while (read_CMOS_reg(0) == x);      /* wait for the next value */
  };
}


void delay_RTC_RM (int secs) {
  byte x;
  unsigned long tickend,tickstart;



  #define msg_delay_RTC_0000 "delay_RTC.053 -- We will iterate for %d seconds.\r\n"
  sprintf(strbuff,msg_delay_RTC_0000,secs);
  WriteBookFromProgram(messageHandle, strbuff, strlen(strbuff)+1, 0);

  while (secs--) {
    x = read_CMOS_reg(0);               /* read seconds from RTC */
  tickstart=*BIOS_timer;
  #define msg_delay_RTC_0001 "delay_RTC.059 -- STEP 1. Get current seconds count, %d, BCD 0x%s, in x.  The BIOS timer at 0x46C is now %ld.\r\n"
  itoa(x,strbuff2,16);
  sprintf(strbuff,msg_delay_RTC_0001,(int)x, strbuff2,*BIOS_timer);
  WriteBookFromProgram(messageHandle, strbuff, strlen(strbuff)+1, 0);


  #define msg_delay_RTC_0002 "delay_RTC.064 -- STEP 2. Wait until the new seconds in the CMOS memory index 0 become different than the cached x second.\r\n"
  WriteBookFromProgram(messageHandle, msg_delay_RTC_0002, strlen(msg_delay_RTC_0002)+1, 0);

    ulongbuff=0;
    while (read_CMOS_reg(0) == x)ulongbuff++;      /* wait for the next value */

  tickend=*BIOS_timer;
  #define msg_delay_RTC_0003 "delay_RTC.072 -- STEP 3 (debug only). We iterated %ld times to wait for 1 second change. The BIOS timer at 0x46C is now %ld (%ld ticks total difference)\r\n"
  sprintf(strbuff,msg_delay_RTC_0003,ulongbuff,*BIOS_timer,tickend-tickstart);
  WriteBookFromProgram(messageHandle, strbuff, strlen(strbuff)+1, 0);


  };

  WriteBookFromProgram(messageHandle, "\r\n\r\n", 5, 0);

  #define msg_delay_RTC_0004 "delay_RTC.080 -- Here we could improve the accuracy of our timer some more\r\n"
  #define msg_delay_RTC_0005 "                 if we made an average of all the counts, except the first one,\r\n"
  #define msg_delay_RTC_0006 "                 then we subtracted the first count from the average, got\r\n"
  #define msg_delay_RTC_0007 "                 the absolute value, or subtracted the smaller from the bigger value\r\n"
  #define msg_delay_RTC_0008 "                 and then we simply iterated one more time in a second loop only for the\r\n"
  #define msg_delay_RTC_0009 "                 amount of the subtraction to complete an obviously partial amount of time\r\n"
  #define msg_delay_RTC_0010 "                 to complete a whole second, as long as that count is smaller than the bigger count so far.\r\n\r\n"
  #define msg_delay_RTC_0011 "                 The only drawback is that it would be very CPU intensive, not suited for every multitasking application\r\n"
  #define msg_delay_RTC_0012 "                 and even less for a multitasking kernel and libraries.\r\n"
  WriteBookFromProgram(messageHandle, msg_delay_RTC_0004, strlen(msg_delay_RTC_0004)+1, 0);
  WriteBookFromProgram(messageHandle, msg_delay_RTC_0005, strlen(msg_delay_RTC_0005)+1, 0);
  WriteBookFromProgram(messageHandle, msg_delay_RTC_0006, strlen(msg_delay_RTC_0006)+1, 0);
  WriteBookFromProgram(messageHandle, msg_delay_RTC_0007, strlen(msg_delay_RTC_0007)+1, 0);
  WriteBookFromProgram(messageHandle, msg_delay_RTC_0008, strlen(msg_delay_RTC_0008)+1, 0);
  WriteBookFromProgram(messageHandle, msg_delay_RTC_0009, strlen(msg_delay_RTC_0009)+1, 0);
  WriteBookFromProgram(messageHandle, msg_delay_RTC_0010, strlen(msg_delay_RTC_0010)+1, 0);
  WriteBookFromProgram(messageHandle, msg_delay_RTC_0011, strlen(msg_delay_RTC_0011)+1, 0);
  WriteBookFromProgram(messageHandle, msg_delay_RTC_0012, strlen(msg_delay_RTC_0012)+1, 0);


  WriteBookFromProgram(messageHandle, "\r\n\r\n", 5, 0);

}

void my_exit() {

  #define msg_my_exit_0000 "my_exit.047 -- Just print a message right before the program terminates.\r\n"
  WriteBookFromProgram(messageHandle, msg_my_exit_0000, sizeof(msg_my_exit_0000), 0);

  printf ("\nWe're back...\n");
  printf ("\nPMode Tutorial by Alexei A. Frounze (c) 2000\n");
  printf ("E-mail  : [email protected]\n");
  printf ("Homepage: http://alexfru.chat.ru\n");
  printf ("Mirror  : http://members.xoom.com/alexfru\n");
  printf ("PMode...: http://welcome.to/pmode\n");
}

int main() {
  port70initialValue=inportb(0x70);

  messageHandle=fopen("SrcDoc00.txt","wb");
/*  messageHandle=fopen("SourceDoc0000.txt","wb"); */  /* Use DOSLFN to use this long file name. */
  /* Apparently Turbo C truncates long file names, so using DOSLFN will be of no use here
     with the default C library from Turbo C.  */

  if(!messageHandle)
  {
   printf("ERROR: Couldn't create SourceDoc file, automatically generated book from program.");
   exit(-1);
  }


  #define msg_main_FP0 "main.119: Point in a variable to absolute offset 0x46C, where the BIOS tick timer resides\r\n"
  #define msg_main_FP1 "          How to properly keep it updated from our OS at all times???\r\n\r\n"
  WriteBookFromProgram(messageHandle, msg_main_FP0, sizeof(msg_main_FP0), 0);
  WriteBookFromProgram(messageHandle, msg_main_FP1, sizeof(msg_main_FP1), 0);


  #define msg_main_0000 "main.065: Clear the screen, print what this program does\r\n"
  #define msg_main_0001 "          and set the function to be executed by the run time at exit (atexit).\r\n\r\n"
  WriteBookFromProgram(messageHandle, msg_main_0000, sizeof(msg_main_0000), 0);
  WriteBookFromProgram(messageHandle, msg_main_0001, sizeof(msg_main_0001), 0);
   clrscr();
   printf ("Welcome to the 1st PMode tutorial!\n\n");
   atexit (my_exit);




  #define msg_main_0002 "main.076: Use the SMSW, store machine status word instruction,\r\n"
  #define msg_main_0003 "          and if bit 0 is 1, exit the program\r\n"
  #define msg_main_0004 "          as the CPU is currently in protected mode.\r\n\r\n"
  WriteBookFromProgram(messageHandle, msg_main_0002, sizeof(msg_main_0002), 0);
  WriteBookFromProgram(messageHandle, msg_main_0003, sizeof(msg_main_0003), 0);
  WriteBookFromProgram(messageHandle, msg_main_0004, sizeof(msg_main_0004), 0);

  #define msg_main_0005 "          Bit 0 of CR0 is currently "
  WriteBookFromProgram(messageHandle, msg_main_0005, sizeof(msg_main_0005), 0);
  WriteBookFromProgram(messageHandle, itoa(read_msw() & 1,strbuff,10), 2, 0);
  WriteBookFromProgram(messageHandle, "\r\n\r\n", 5, 0);


  if (read_msw() & 1) {
    printf ("The CPU is already in PMode.\nAborting...");
    return 0;
  };




  printf ("We're going to PMode using CR0 for 5 seconds...\n");



  #define msg_main_0006 "main.101: Execute CLI.\r\n\r\n"
  WriteBookFromProgram(messageHandle, msg_main_0006, sizeof(msg_main_0006), 0);

  /* disable interrupts so that IRQs don't cause exceptions */
  disable();



  if(inportb(0x70)!=port70initialValue)port70isStuck=0;
  uintbuff=(unsigned int)inportb(0x70);
  #define msg_main_0007 "main.112: Disable NMIs -- call this log with parameters computed outside the function since x86 code calls it\r\nbackwards on stack and then it will fail to be set properly to display our numbers.\r\n          Port 0x70 currently contains 0x"
  WriteBookFromProgram(messageHandle, msg_main_0007, sizeof(msg_main_0007), 0);
  itoa(uintbuff,strbuff,16);
  WriteBookFromProgram(messageHandle, strbuff, strlen(strbuff)+1,0);
  WriteBookFromProgram(messageHandle, " -- ", 5, 0);
  itoa(uintbuff,strbuff,2);
  WriteBookFromProgram(messageHandle, strbuff, strlen(strbuff)+1,0);
  WriteBookFromProgram(messageHandle, "b", 2, 0);
  WriteBookFromProgram(messageHandle, "\r\n\r\n\r\n", 7, 0);
  WriteBookFromProgram(messageHandle, "b", 2, 0);
  if(inportb(0x70)!=port70initialValue)port70isStuck=0;

  if(uintbuff&0x80)
  {
   #define msg_main_0008 "main.121: Bit 7 of port 0x70 (CMOS Memory Index) is 1\r\n          so NMIs are disabled.\r\n\r\n"
   WriteBookFromProgram(messageHandle, msg_main_0008, sizeof(msg_main_0008), 0);
  }
   else
   {
    #define msg_main_0009 "main.126: Bit 7 of port 0x70 is 0\r\n          so NMIs are enabled.\r\n\r\n"
    WriteBookFromProgram(messageHandle, msg_main_0009, sizeof(msg_main_0009), 0);
   }


  /* disable NMIs as well */
  outportb (0x70, inportb(0x70) | 0x80);


  #define msg_main_0010 "main.137: We simply enable protected mode by setting\r\nbit 0 of CR0 to 1 (mov eax,cr0 -- inc eax -- mov cr0,eax), but after that we still need to set it up properly.\r\n\r\n"
  WriteBookFromProgram(messageHandle, msg_main_0010, sizeof(msg_main_0010), 0);

  /* WOW!!! This switches us to PMode just setting up CR0.PM bit to 1 */
  write_cr0 (read_cr0() | 1L);


  /* a delay for 5 seconds */
/*  delay_RTC (0);*/

  /* get out of PMode clearing CR0.PM bit to 0 */
  write_cr0 (read_cr0() & 0xFFFFFFFEL);


enable();
  delay_RTC_RM (5);
disable();



  #define msg_main_0011 "main.147: We simply disable protected mode by clearing\r\nbit 0 of CR0 to 0 (mov eax,cr0 -- dec eax, mov cr0,eax). We didn't set up anything further so we don't need to\r\nreconfigure anything to return to Real Mode, but we can't call any Turbo C library functions or Real-Mode specific\r\ncode during that time or we will crash in a badly-enabled protected mode.\r\n\r\n"
  WriteBookFromProgram(messageHandle, msg_main_0011, sizeof(msg_main_0011), 0);


/*  *BIOS_timer += 91L;*/  /* 5*18.2 ticks total */



  if(inportb(0x70)!=port70initialValue)port70isStuck=0;
  uintbuff=(unsigned int)inportb(0x70);
  #define msg_main_0233 "main.233: Enable NMIs -- call this log with parameters computed outside the function since x86 code calls it\r\nbackwards on stack and then it will fail to be set properly to display our numbers.\r\n          Port 0x70 currently contains 0x"
  WriteBookFromProgram(messageHandle, msg_main_0233, sizeof(msg_main_0233), 0);
  itoa(uintbuff,strbuff,16);
  WriteBookFromProgram(messageHandle, strbuff, strlen(strbuff)+1,0);
  WriteBookFromProgram(messageHandle, " -- ", 5, 0);
  itoa(uintbuff,strbuff,2);
  WriteBookFromProgram(messageHandle, strbuff, strlen(strbuff)+1,0);
  WriteBookFromProgram(messageHandle, "b", 2, 0);
  WriteBookFromProgram(messageHandle, "\r\n\r\n\r\n", 7, 0);
  if(inportb(0x70)!=port70initialValue)port70isStuck=0;

  if(uintbuff&0x80)
  {
   #define msg_main_0008 "main.121: Bit 7 of port 0x70 (CMOS Memory Index) is 1\r\n          so NMIs are disabled.\r\n\r\n"
   WriteBookFromProgram(messageHandle, msg_main_0008, sizeof(msg_main_0008), 0);
  }
   else
   {
    #define msg_main_0009 "main.126: Bit 7 of port 0x70 is 0\r\n          so NMIs are enabled.\r\n\r\n"
    WriteBookFromProgram(messageHandle, msg_main_0009, sizeof(msg_main_0009), 0);
   }


  /* enable NMIs */
  outportb (0x70, inportb(0x70) & 0x7F);



  if(inportb(0x70)!=port70initialValue)port70isStuck=0;
  uintbuff=(unsigned int)inportb(0x70);
  #define msg_main_0259 "main.259: Now port 0x70 contains 0x"
  WriteBookFromProgram(messageHandle, msg_main_0259, sizeof(msg_main_0259), 0);
  itoa(uintbuff,strbuff,16);
  WriteBookFromProgram(messageHandle, strbuff, strlen(strbuff)+1,0);
  WriteBookFromProgram(messageHandle, " -- ", 5, 0);
  itoa(uintbuff,strbuff,2);
  WriteBookFromProgram(messageHandle, strbuff, strlen(strbuff)+1,0);
  WriteBookFromProgram(messageHandle, "b", 2, 0);
  WriteBookFromProgram(messageHandle, "\r\n\r\n\r\n", 7, 0);
  if(inportb(0x70)!=port70initialValue)port70isStuck=0;





  #define msg_main_0012 "main.235: Execute STI.\r\n\r\n"
  WriteBookFromProgram(messageHandle, msg_main_0012, sizeof(msg_main_0012), 0);

  if(port70isStuck)
  {
   #define msg_main_0291 "main.291: WARNING: Port 0x70 is stuck on reads to %sb, so we can't enable/disable NMIs\r\n          It must be a machine with an invalid port 0x70 on reads and write-only NMI enabled/disabled state at write-only byte port 0x70.\r\n\r\n"
   sprintf(strbuff,msg_main_0291,itoa((unsigned int)inportb(0x70),strbuff2,2));
   WriteBookFromProgram(messageHandle, strbuff, sizeof(strbuff), 0);
  }



  /* enabling interrupts */
  enable();

  return 0;
}

Last edited by ~ on Wed Aug 02, 2017 10:23 pm, edited 1 time in total.
YouTube:
http://youtube.com/@AltComp126

My x86 emulator/kernel project and software tools/documentation:
http://master.dl.sourceforge.net/projec ... ip?viasf=1
LtG
Member
Member
Posts: 384
Joined: Thu Aug 13, 2015 4:57 pm

Re: NMI bit at port 0x70 is stuck on reads

Post by LtG »

Tilde, you do know that programming is supposed to make things easier, not harder?

Your WriteBookFromProgram seems awfully difficult to use. You might want to consider implementing it like so:
WriteBookFromProgram(FILE* file, char* fmt, ...);

So call site would look like:
WriteBookFromProgram(file, "Some message: %d\n", someVar);

Instead of:
#define msg_0000 "Some message: %d\r\n"
sprintf(strbuff,msg_0000,secs);
WriteBookFromProgram(messageHandle, strbuff, strlen(strbuff)+1, 0);


That way all the grunt work happens inside the function, instead of _EACH_ call site, and seriously, don't use "define"'s unless there's a good reason.


As for the original topic, why do you want to read the NMI? Just decide what you want it to be and set it and forget it. If you need to deal with CMOS then you always set/clear it depending on what your current "virtual" desired setting is. There's really no reason to read it.

Do you feel the need to test the value in some reg (eg. EAX) after setting it?
User avatar
~
Member
Member
Posts: 1227
Joined: Tue Mar 06, 2007 11:17 am
Libera.chat IRC: ArcheFire

Re: NMI bit at port 0x70 is stuck on reads

Post by ~ »

LtG wrote:As for the original topic, why do you want to read the NMI? Just decide what you want it to be and set it and forget it. If you need to deal with CMOS then you always set/clear it depending on what your current "virtual" desired setting is. There's really no reason to read it.

Do you feel the need to test the value in some reg (eg. EAX) after setting it?
I didn't write that code, it's from Alexei Frounze, so it would be extremely valuable to ask him, when he is around, what he was trying to do at the time or if there are machines with a readable port 0x70 on byte reads, or maybe it was just an unexpected erroneous assumption.

But I suspect that this tiny detail has taken many people here by surprise. I was one of those.
YouTube:
http://youtube.com/@AltComp126

My x86 emulator/kernel project and software tools/documentation:
http://master.dl.sourceforge.net/projec ... ip?viasf=1
alexfru
Member
Member
Posts: 1111
Joined: Tue Mar 04, 2014 5:27 am

Re: NMI bit at port 0x70 is stuck on reads

Post by alexfru »

Octocontrabass wrote:
~ wrote:trying to read the current contents of byte at port 0x70
Port 0x70 is write-only. You can't read anything useful from it.
The Undocumented PC book says the same. I wouldn't recall if it was my invention or my source at the time was wrong, but I didn't notice any malfunction back then.
roolebo
Posts: 2
Joined: Tue Dec 11, 2018 11:53 am
Libera.chat IRC: roolebo

Re: NMI bit at port 0x70 is stuck on reads

Post by roolebo »

alexfru wrote:
Octocontrabass wrote:
~ wrote:trying to read the current contents of byte at port 0x70
Port 0x70 is write-only. You can't read anything useful from it.
The Undocumented PC book says the same. I wouldn't recall if it was my invention or my source at the time was wrong, but I didn't notice any malfunction back then.
Alexey, thanks for tutorials! They also work in QEMU.

However, I don't think I/O port 70h is readable on many systems. So far I have found only one non-Intel chipset, SiS 85C496/497 that has r/w access to I/O port 70h (see Table 2.8 85C497 Address Decoding):
0070h | r/w | NMI enable and RTC address index register | Control
IBM PC AT Technical Reference doesn't mention if the I/O port is readable but the BIOS only performs writes to port 70h, here's how NMI is enabled and disabled in the AT BIOS v1:

Code: Select all

        MOV     AL,0FH                  ; TURN ON NMI
        OUT     CMOS_PORT,AL            ;
        MOV     CX,00FFH                ; DELAY
D7_B:   LOOP    D7_B                    ;
        MOV     AL,8FH                  ; TURN OFF NMI
        OUT     CMOS_PORT,AL            ;
Note that no access to Port 71h follows the writes to Port 70h. The technical reference doesn't explicitly require it:
At power on time, the non-maskable interrupt (NMI) into the 80286 is masked off. The mask bit can be set and reset with system programs as follows:
Mask On Write to I/O address hex 070, with data bit 7 equal to a logic 0
Mask Off Write to I/O address hex 070, with data bit 7 equal to a logic 1
Note: At the end of POST, the system sets the NMI mask on (NMI enabled).
IBM PS/2 Model 50 Technical Reference is much more verbose and strict in terms of operations with I/O port 70h. It explicitly prohibits to read NMI mask bit and requires that a write to port 70h is followed by an access of port 71h:
RT/CMOS Address Register and NMI Mask (Hex 0070)
This register is used in conjunction with the port at hex 0071 to read and write the RT/CMOS RAM bytes.

Bit Function
7 NMI Mask
6 Reserved
5 - 0 RT/CMOS RAM Address

Figure 3-9. RT/CMOS Address Register and NMI Mask (Hex 0070)

Warning: The operation following a write to hex 0070 should access port hex 0071; otherwise intermittent malfunctions and unreliable operation of the RT/CMOS RAM can occur.

Bit 7 When this bit is set to 1, the NMI is masked off (the NMI is disabled). This bit is set to 1 by a power-on reset and is a write-only bit.
Bit 6 Reserved.
Bits 5 - 0 These bits are used to select RT/CMOS RAM addresses.

RT/CMOS RAM I/O Operations

During I/O operations to the RT/CMOS RAM addresses, interrupts should be masked to prevent other interrupt service routines from changing the CMOS address register before data is read or written. After I/O operations, the RT/CMOS and NMI Mask register (hex 0070) should be left pointing to Status Register D (hex 00D).

Warning: The operation following a write to hex 0070 should access hex 0071; otherwise intermittent malfunctions and unreliable operation of the RT/CMOS RAM can occur.

The following steps are required to perform I/O operations to the RT/CMOS RAM addresses:
1. Write the RT/CMOS RAM address to the RT/CMOS and NMI Mask register (hex 0070).
2. Write the data to address hex 0071.

Reading RT/CMOS RAM requires the following steps:
1. Write the RT/CMOS RAM address to the RT/CMOS and NMI Mask register (hex 0070).
2. Read the data from address hex 0071.
Most Intel chipsets since 90's don't allow to read NMI register, unless the chipset is in Alternative Access Mode (it's used to save or restore system state if it is going into low power mode or exiting it, see PIIX4 or newer chipset datasheets for details). Intel® 7 Series / C216 Chipset Family Platform Controller Hub (PCH) also has this note:
Software must preserve the value of bit 7 at I/O addresses 70h and 74h. When writing to this address, software must first read the value, and then write the same value for bit 7 during the sequential address write. Port 70h is not directly readable. The only way to read this register is through Alt Access mode. Although RTC Index bits 6:0 are readable from port 74h, bit 7 will always return 0. If the NMI# enable is not changed during normal operation, software can alternatively read this bit once and then retain the value for all subsequent writes to port 70h.
Intel also provides a guideline with an example program how to work with the the I/O ports. The guideline affirms that Alternative Access Mode is the way to read NMI Enable bit:
The NMI Enable bit nominally located at 0x70[7] is best accessed ONLY when the chipset is put into Alt-Access Mode. This guarantees successful read and write cycles regardless of the settings of the other registers.§
An attempt to read NMI register in regular mode won't hang but instead will return all-1's:
Accessing these registers can be difficult. The NMI Enable bit (NMI_EN = I/O 0x70[7]) is especially troublesome as a straight read of this register will return all 0xFF data, although writes work fine. This paper will explain the details and the necessary steps that software needs to perform to access these registers.
Octocontrabass
Member
Member
Posts: 5575
Joined: Mon Mar 25, 2013 7:01 pm

Re: NMI bit at port 0x70 is stuck on reads

Post by Octocontrabass »

roolebo wrote:IBM PC AT Technical Reference doesn't mention if the I/O port is readable
But it does include full hardware schematics, and you can see from those that no hardware exists to store and return the values written to port 0x70.

(Reads and writes to I/O ports 0x60-0x7F are controlled by a 74S288 ROM, shown on sheet 18. Since the ROM contents aren't provided, it could theoretically tell one of the attached components to respond to reads on port 0x70. However, none of them are capable of replying with the value previously written to port 0x70 while also performing their expected functions. The MC146818 on sheet 18 does not allow its address to be read back. The 8042 on sheet 17 isn't attached to enough address lines to differentiate between port 0x60 and port 0x70. The 74ALS175/74ALS244 pair on sheet 17 have no address lines at all. The 74ALS74 on sheet 3 isn't even capable of driving the data bus.)
roolebo
Posts: 2
Joined: Tue Dec 11, 2018 11:53 am
Libera.chat IRC: roolebo

Re: NMI bit at port 0x70 is stuck on reads

Post by roolebo »

Octocontrabass wrote:
roolebo wrote:IBM PC AT Technical Reference doesn't mention if the I/O port is readable
But it does include full hardware schematics, and you can see from those that no hardware exists to store and return the values written to port 0x70.

(Reads and writes to I/O ports 0x60-0x7F are controlled by a 74S288 ROM, shown on sheet 18. Since the ROM contents aren't provided, it could theoretically tell one of the attached components to respond to reads on port 0x70. However, none of them are capable of replying with the value previously written to port 0x70 while also performing their expected functions. The MC146818 on sheet 18 does not allow its address to be read back. The 8042 on sheet 17 isn't attached to enough address lines to differentiate between port 0x60 and port 0x70. The 74ALS175/74ALS244 pair on sheet 17 have no address lines at all. The 74ALS74 on sheet 3 isn't even capable of driving the data bus.)
Thanks for the explanation! 74S288 ROM contains a mapping of an I/O operation (Read/Write using -XIOR/-XIOW pins), internal data address bus (XA) bits 0 and 4 and -PPICS to eight output pins. One output pin of the ROM, -NMI CS, and bit 7 from internal data bus (XD7) go to D-type flip-flop 74ALS74 on sheet 3. That allows to mask NMI by a write to I/O port 70 but there's no way to read current value from the flip-flop because 74ALS74 can't drive data bus as you said.
Post Reply