10

The OSDev wiki talks about a need for an intermediary 16-bit protected mode. I tried a direct switch many different ways and it just caused a crash, because it still executed 32-bit instruction after a far jump.

And to be sure, the 16-to-32-bit mode is much more easier, with a direct switch, right?

Note: This code is to be executed on a modern x86_64 cpu. I just want to switch back to real mode on a modern cpu. It got placed on retrocomputing with the 8086 accidentally. I posted on stack exchange.

6
  • As soon as cr0 bit 0 is 0, any reload of a segreg (including cs) probably only sets the segment base according to Real Address Mode segmentation, but not the other segment data such as the D bit. On entering 32-bit PM you reload cs with cr0 already having PM enabled, but on the way back this requires a switch to a 16-bit PM cs first. Commented Nov 3 at 21:02
  • 6
    Once you leave protected mode, segment register loads only update the base address portion of the “descriptor cache” (a particularly jarring misnomer in this situation) so the other “cached” values have to be set up before leaving the mode. This mechanism is also what makes the unreal mode possible. Commented Nov 3 at 21:03
  • The LOADALL opcode allows to set the CPU state directly so the intermediate step to first load CS with 16-bit code selector compatible with real mode isn't necessary. Commented Nov 4 at 5:55
  • There were also the triple fault reboot trick (used by some programs, IIRC) Commented Nov 4 at 14:34
  • Thanks. I'll look more into it. I want to make the entire boot-chainijg process and put 64 bit code after the 1mb boundary. So, I figured rather then doing a fs driver in 32 bit mode, using a shared buffer and real 16 UEFI interupts might be a good pedagogical exercise. I'm taking courses online and Am trying to implement everything shown in course. Commented Nov 4 at 20:33

2 Answers 2

8

No it is not possible due to how code selector bitness is set and changed. Others had mentioned unreal mode which works due to descriptor cache limits being "frozen", i.e. retaining their last set value in real mode. Same thing happens to selector bitness - whatever bitness was last set in protected mode, stays in real mode. See e.g. https://f.osdev.org/viewtopic.php?t=32634:

If you do the exact same procedure as you would for unreal mode (entering protected mode, loading selectors with bigger limits, switching back), but you mark the code selector as 32 bit, you'll be (apparently) able to run 32-bit instructions in "real mode" while retaining the segment*16+offset memory model, and without the need for override prefixes (which in this case, would actually turn the instruction into a 16 bit instruction, as in protected mode 32).

The interrupts work the same as in real mode, using the IVT, with real mode addressing (so you can't have vectors that point to memory beyond ffff:ffff). I, too, was pretty weirded out about this, but I did some testing in bochs (which marks the mode as "Real Mode 32"), in qemu (although I believe KVM crashes), and, most surprisingly, it works on real hardware! (Although it had some crashes at times)

Thus, if you are to enter "proper" 16-bit real mode from protected mode, your descriptor caches have to be set with proper 64K limits and 16-bit bitness, i.e. prior mode has to be 16-bit PM.

And to be sure, the 16-to-32-bit mode is much more easier, with a direct switch, right?

I think technically no, switch to 32PM mode also happens through 16 bit PM, if only for a single instruction. Again, in real mode one cannot change bitness; CPU has to be in PM to reload those parts of descriptor cache. In a typical switch, mov eax, cr0 ; or eax, 1 ; mov cr0, eax sequence switches to 16 bit PM mode, and very next instruction jmp far reloads descriptor cache from GDT and switches CPU to 32 bit mode.

3
  • 1
    Indeed, the code selector must be loaded with a selector from a descriptor table where base/limit/bitness etc settings are set to be compatible with 16-bit real mode, before turning off the paging bit CR0, after which loading the CS does not update from selector table but uses it as new value for base to select the segment. Commented Nov 4 at 5:34
  • 1
    @Justme I think you mean the Protection Enable (PE) bit, not "paging bit". Commented Nov 4 at 9:11
  • 1
    @ecm Yes, I might repost a comment or delete it if it is now useless. Commented Nov 4 at 9:18
8

Yes, technically this might not be the method you intended, but it is possible with the LOADALL opcode.

As long as your 32-bit protected mode is set up in a way that you can point ES:(E)DI to the table for the LOADALL instruction to load the internal CPU state from memory, the CPU can continue running in any mode you want.

As an example, LOADALL can be executed in any mode and could be used to simulate a reset where the new CPU state is identical to the state it starts running code after hardware reset, in 16-bit real mode.

So this can be used to skip the step where the code selector needs to be loaded in protected mode to first set the descriptor base/limit/bitness/etc back to values that are compatible with real mode before turning off paging and loading the real mode code segment, which then keeps using the previously loaded base/limit/bitness values in real mode.

1
  • 19
    Caveat to this: LOADALL only exists on "most 286 processors, some 386 and possibly some 486" according to pushbx.org/ecm/doc/insref.htm#insLOADALL. Modern x86 definitely doesn't have it; the opcodes have since been reused as syscall (0F 05) and sysret (0F 07) on x86-64 (and AMD also in 32-bit mode.) Commented Nov 4 at 8:16

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.