On intel IAPX 80286, as I recall from experience, the undocumented - officially unassigned - "opcode" position 0xF1 was actually decoded as an instruction-prefix, thus counting for maximum allowed instruction length (ten bytes), but otherwise not modifying the prefixed instruction : a "no operation" prefix, sort of. Now, what I have always wondered and could not assert lacking appropriate lab equipment ("logic analyzer" or a mere oscilloscope) : was F1h perhaps an 'alias' for the lock-prefix (F0h), or was it just ignored by the CPU (apart from counting for instruction length) ?
Note : we know that position 0xF1 of the opcode 'matrix' was later reused for a one-byte "int 1" opcode (not a prefix) starting with the i80386, that also was not documented until much later. This later conflicting assignment of the opcode position is not part of the here question.


While it is officially documented as "a prefix which performs no function", a thread on the vcfed.org forums has discovered it is an ICE-mode prefix that forces the instruction's memory access to be performed using the normal bus pins instead of the extra pins normally used by the bond-out version of the 286.

| improve this answer | |
  • 1
    Thank you for that, @Philip ! Truly amazing find!... $Deity$ knows how much time I spent fiddling with loadall and the 0F.04, varying conditions, with controlled shutdowns and NMIs, back in ~1990 ... I always suspected 0F.04 would be a "store all" to "alternate" memory (which I could not test) ! At the same time I was also well aware of F1 being a (practically) "no op" prefix, yet trying to prefix 0F.04 by F1 never occurred to me ! Wow ! – NimbUs Aug 16 '19 at 18:08
  • 1
    By a slight extension one would want to assert the same question - about prefix F1 - on the 8086/88 (original or CMOS versions), and the 186 as well. On those older CPUs where opcode decoding reportedly was done more in "logic" than thru micro (nano?)-programming, it is conceivable that F1==F0, if you pardon the language... – NimbUs Aug 22 '19 at 15:49

According to an Intel document describing opcodes which don’t result in exception 6,

The 0F1H opcode is a prefix which performs no function. It counts like any other prefix towards the maximum instruction length. No restrictions apply to its execution.

The same document mentions explicitly when opcodes are aliases for others. I interpret the above as meaning that 0xF1 is not an alias for 0xF0. If it was, I would have hoped the locking behaviour would be mentioned.

However this 2010 post in comp.lang.asm.x86 does mention that 0xF1 is an alias for 0xF0, and that the only way to determine this is by testing it, since the Intel documentation is unreliable.

I don’t have a 286 handy to test this behaviour though. To test this on a real 80286, you’d need either a logic analyser (as you mention), or a protected-mode test program — since LOCK is a privileged prefix, if 0xF1 is an alias for it, it should generate a general protection fault if it’s used without the appropriate privilege. (On a 386 or later CPU, the test would be easier, since LOCK with an inappropriate instruction or operand generates an invalid opcode exception, but 0xF1 is different there.)

| improve this answer | |
  • 2
    @Ruslan as I understand the 286 manual, the 286 designers considered that it caused I/O (in the shape of the LOCK# signal), so if IOPL < CPL the operation causes an exception. The I/O permission model changed with the 386. However I have conflicting docs around LOCK on 286s, so really its behaviour should be tested on a real 286... – Stephen Kitt Aug 12 '19 at 21:25
  • 1
    @Ruslan yes, they were licensed clones, and should behave identically to Intel CPUs. The things I’d like to test, beyond the 0xF0/0xF1 equivalence, are: is LOCK allowed with any instruction, or does it produce an invalid opcode exception with anything other than INS, MOVS, OUTS and XCHG? Does the behaviour change in protected mode? And obviously the IOPL behaviour above ;-). – Stephen Kitt Aug 12 '19 at 21:28
  • 2
    I might try to test sometime on this weekend. – Ruslan Aug 12 '19 at 21:31
  • 1
    (While I’m at it, I’d also like to check whether the 286 restarts repeating instructions properly when REP isn’t the last prefix; that’s a known 8086 bug but it’s unclear whether it’s fixed on the 286.) – Stephen Kitt Aug 12 '19 at 21:31
  • 2
    @Stephen Kitt : I /did/ test the previously mentioned behaviour on real 286s (several pieces, including authorized clones). The only exception LOCK could cause (though indirectly) was by contributing to exceeding maximum instruction length (or trespassing CS limit) - and then that would be GPF, not UNDefined instruction. – NimbUs Aug 13 '19 at 15:19

I've done an experiment to confirm what Stephen Kitt says in his answer. I've tried some instructions on my AMD 286 system, using the boot sector-based playground given at the bottom of this answer (compilable with FASM).

Namely, when I have lock uncommented and db 0xf1 commented out (see the code after the enterRing3 label), my 286 system prints #BP #GP (#BP for int3 and #GP for LOCK ADD), which confirms that the LOCK prefix is privileged on 80286. If I run the code with lock commented out and db 0xf1 uncommented, I get the same result as when both prefixes are commented out, i.e. #BP #DE (#BP for int3 and #DE for DIV), which confirms that the F1 prefix is simply ignored instead of being equivalent to LOCK, and also that it doesn't result in #UD (which is why I use division by zero instead of UD2 at the end).

The boot sector of the playground:

 BOOT_ADDR = 0x7c00

 CODE_SELECTOR_RING0     = code_descr_ring0 - gdt
 CODE_SELECTOR_RING3     = (code_descr_ring3 - gdt) or 3
 DATA_SELECTOR_RING0     = data_descr_ring0 - gdt
 DATA_SELECTOR_RING3     = (data_descr_ring3 - gdt) or 3
 VRAM_SELECTOR           = vram_descr - gdt
 TSS_SELECTOR            = tss - gdt

 macro GDT_ENTRY_286 base_,limit_,present_,ring_,system_,codeseg_,conforming_,writable_,accessed_
     dw       limit_
     dw       base_ and 0xffff
     db       base_ shr 16
     db       (present_ shl 7) or (ring_ shl 5) or (system_ shl 4) or (codeseg_ shl 3) or (conforming_ shl 2) or (writable_ shl 1) or accessed_
     dw       0     ; reserved on 286
 EXT_DOWNWARDS_SEG=CONFORMING_SEG ; same bit, but for data segments
 EXT_UPWARDS_SEG=NONCONFORMING_SEG ; same bit, but for data segments
 READABLE_SEG=WRITABLE_SEG ; same bit, but for code segments

    jmp      0x0000:start ; make sure we know distribution of linear address bits between CS and IP
    ; print "OK" on the screen to see that we've actually started
    push     0xb800
    pop      es
    xor      di,di
    mov      ax, 0x0700+'O'
    mov      ax, 0x0700+'K'
    ; clear the rest of the screen to have a clear view of the exceptions we'll generate
    mov      cx, 80*25*2-2
    mov      ax, 0x0700+' '
    rep stosw

    lgdt     [cs:gdtr]
    mov      cx, DATA_SELECTOR_RING0
    smsw     ax
    or       al, 1
    lmsw     ax
    jmp      CODE_SELECTOR_RING0:enterPM
    mov      ds, cx
    mov      ss, cx
    mov      sp, 0xfffe
    lidt     [idtr]

    ; fill ring0 stack in the TSS and load TR - so that we could handle ring3-originating exceptions
    mov      word [2], sp   ; ring0 SP
    mov      word [4], cx   ; ring0 ss
    mov      dx, TSS_SELECTOR
    ltr      dx

    mov      ax, sp
    push     DATA_SELECTOR_RING3
    push     ax
    push     CODE_SELECTOR_RING3
    push     enterRing3
    push     ss
    pop      ds
    int3     ; check that at least interrupts and returns from them work
    db       0xf1 ; the prefix we wanted to check for equivalence to LOCK
;    lock          ; LOCK itself
    add word [0], 0
    xor      ax, ax      ; make sure we don't pass
    div      ax          ; through to execute data
    dq       0
    GDT_ENTRY_286 0x00000,0x002c,PRESENT_SEG,3,SYSTEM_SEG,0,0,0,1 ; 16-bit available TSS (in place of real-mode IVT)

    dw       gdtr-gdt-1
    dd       gdt

 rept 14 COUNTER
    dw       intHandler#COUNTER
    dw       CODE_SELECTOR_RING0
    db       0
    db       11100111b    ; present, ring3, system, 16-bit trap gate
    dw       0            ; reserved on 286
    dw       idtr-idt-1
    dd       idt

 rept 14 COUNTER
    mov      si, intNames+(COUNTER-1)*INT_NAME_SIZE
    mov      bx, COUNTER-1
    jmp      handleInt
    push     ds
    mov      cx, VRAM_SELECTOR
    mov      es, cx
    push     ss
    pop      ds
    mov      di, [offsetInVRAM]
    mov      cx, INT_NAME_SIZE
    mov      al, 7
    movsb     ; character
    stosb     ; attribute
    loop     printLoop
    inc      di
    inc      di
    mov      [offsetInVRAM], di
    cmp      bx, 3 ; test that our interrupts work as expected, return after INT3
    jne      $
    pop      ds
    dw       0

    db       (0x7dfe-finish) dup(0)
    dw       0xaa55
| improve this answer | |
  • 1
    Good debugging job ! ... As for me, can only lament that both my 286 & 386 boards are out of order, permanently ;=) – NimbUs Aug 17 '19 at 10:42
  • Nice, so that confirms what the Intel docs say! – Stephen Kitt Aug 17 '19 at 11:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.