Why I got `invalid operand for instruction`

When I run the code, why I got invalid operand for instruction in fedora?
So how to fix it?

#[inline]
pub fn read_cmos(addr: u8) -> u32 {
    let mut value: u8 = 0x80 | addr;
    unsafe {
        asm!(
            "out ax, {0}",
            "in {0}, dx",
            inout(reg_byte) value,
            in("ax") 0x70u16,
            in("dx") 0x71u16,
            options(nomem, nostack, preserves_flags)
        );
    }
    value as u32
}

It looks like… OUT — Output to Port

the instruction OUT cannot work with arbitrary registers.

Instruction Op/En 64-Bit Mode Compat/Leg Mode Description
E6 ib OUT imm8, AL I Valid Valid Output byte in AL to I/O port address imm8.
E7 ib OUT imm8, AX I Valid Valid Output word in AX to I/O port address imm8.
E7 ib OUT imm8, EAX I Valid Valid Output doubleword in EAX to I/O port address imm8.
EE OUT DX, AL ZO Valid Valid Output byte in AL to I/O port address in DX.
EF OUT DX, AX ZO Valid Valid Output word in AX to I/O port address in DX.
EF OUT DX, EAX ZO Valid Valid Output doubleword in EAX to I/O port address in DX.

As you see, the instruction code itself does not encode the choice of registers. It only supports the specific choices of

out dx, al
out dx, ax
out dx, eax

or an immediate value in place of dx. The choices of al/ax/eax appear not to be mere examples but literally the only registers you can use here.

The error message calls out

error: invalid operand for instruction
 --> src/main.rs:8:14
  |
8 |             "out ax, {0}",
  |              ^
  |
note: instantiated into assembly here
 --> <inline asm>:2:2
  |
2 |     out ax, cl
  |     ^

so it’s trying to use the assembly

out ax, cl

which is thus unsurprisingly invalid (neither ax nor cl are allowed in the respective positions)

3 Likes

Given your addresses are constants anyways, you can write them in the assembly directly. For the registers, you need to choose the specific register to make the instruction valid… the one matching the reg_byte class would thus be al. in command has similar restrictions. The following seems to compile successfully:

asm!(
    "out 0x70, al",
    "in al, 0x71",
    inout("al") value,
    options(nomem, nostack, preserves_flags)
);

I have never used inline assembly with Rust before, so I cannot predict further complications you might encounter or other issues your code might still be having.

1 Like

Thank you very much!

The complications would definitely arise somewhere along the way. The only environment which Rust normally supports but which doesn't make it impossible to use I/O instructions is UEFI.

All other enviroments (Linux, MacOS, Windows), of course, would stop you if you would try to access I/O ports.

And UEFI doesn't need CMOS (although it may be emulated, probably).

This doesn't mean such code is entirely useless, one may use some kind of unofficial port of Rust for DOS or Windows 9x… these are very non-standard and unusual environments, thus chances are high one would face very non-standard and unusual bugs there.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.