0

I have some code that writes to and reads the I/O ports for the VGA. I am trying to implement the working C code functionality in the inline assembler. I am using Open Watcom 2.0 and compiling for DOS 16bit.

To write to the color palette on the VGA, I have come up with this. This does not work correctly.

EDIT: The code for setPaletteColor is not entirely accurate. I have updated to reflect the actual code.

void setPaletteColor (unsigned char index, rgbColor *p_color)
{
    _asm
    {
        ; tell VGA card we are going to update a palette register
        mov dx,PALETTE_MASK
        mov al,0xff
        out dx,al

        ; tell VGA which register we will be updating
        mov dx,PALETTE_REGISTER_WR
        mov al,index
        out dx,al

        ; update the color in the register at index
        mov dx,PALETTE_DATA
        mov al,*p_color
        out dx,al
        mov al,*p_color // this is actually *(p_color+1) but this actually gets the next structure not the next data member, so I left it out of the code I typed for my question.
        out dx,al
        mov al,*p_color // same here, actually is *(p_color+2)
        out dx,al
    }
}

And for reading, I have this. This, also, doesn't work correctly.

void getPaletteColor (unsigned char index, rgbColor *p_color)
{
    unsigned char *p_red = &p_color->red;
    unsigned char *p_green = &p_color->green;
    unsigned char *p_blue = &p_color->blue;
    _asm
    {
        ; tell VGA card we are going to read a palette register
        mov dx,PALETTE_MASK
        mov al,0xff
        out dx,al

        ; tell VGA which register we will be reading
        mov dx,PALETTE_REGISTER_RD
        mov al,index
        out dx,al

        ; read the data into the color struct at 'p_color'
        mov dx,PALETTE_DATA
        in  al,dx
        mov *p_red,al
        in  al,dx
        mov *p_green,al
        in  al,dx
        mov *p_blue,al
     }   
}

Now here are the pure C versions that DO work.

void setPaletteColor (unsigned char index, rgbColor *p_color)
{
    outp(PALETTE_MASK,0xff);
    outp(PALETTE_REGISTER_WR, index);
    outp(PALETTE_DATA,p_color->red);
    outp(PALETTE_DATA,p_color->green);
    outp(PALETTE_DATA,p_color->blue); 
}

And for read.

void getPaletteColor (unsigned char index, rgbColor *p_color)
{
    outp(PALETTE_MASK,0xff);
    outp(PALETTE_REGISTER_RD, index);
    p_color->red   = inp(PALETTE_DATA);
    p_color->green = inp(PALETTE_DATA);
    p_color->blue  = inp(PALETTE_DATA); 
}

NOTE: I cannot use the '.' operator nor the '->' operator in the inline assembly because the compiler doesn't support it.

Here is the definition of the rgbColor struct.

typedef struct rgbColorTag
{
    unsigned char red;
    unsigned char green;
    unsigned char blue;
} rgbColor;
SeanRamey
  • 665
  • 7
  • 19
  • Does http://bos.asmhackers.net/docs/vga_without_bios/docs/palettesetting.pdf help? Looks like it's doing the same thing you are. – Peter Cordes Oct 31 '16 at 03:55
  • *NOTE: I cannot use the '.' operator nor the '->' operator in the inline assembly because the compiler doesn't support it.* So assign the data to a local variable in C, and read or write that in inline asm. That might make the compiler do something stupid like actually emit extra instructions to copy it to the stack, though. What's wrong with the pure C version? Does the compiler emit slow code for it? Why do you think you can get faster code from doing it yourself with inline asm? Or is `outp` an actual function that gets called, not an intrinsic? – Peter Cordes Oct 31 '16 at 03:58
  • 1
    @PeterCordes Look at my assembly "getPaletteColor()" I do use local pointer variables to get the offsets of the members for the color struct. The pure C version is fine but I want to know how to do all this stuff. That's why I am writing a DOS program in the first place. This is all a learning experience, and so far, I have learned a lot. `outp` is a function supplied with the compiler for writing to the I/O ports. Same with `inp`. – SeanRamey Oct 31 '16 at 04:08
  • But I meant if you look at the compiler's asm output, is there a CALL instruction to an `outp` function, or does it inline to just an OUT instruction? IDK why you'd want to learn asm with DOS in the first place, but that's a separate issue. (learning 32-bit or 64-bit x86 in the first place is easier IMO, and definitely more useful, and you can run your code natively on real hardware in your usual OS instead of in a DOSbox emulated environment or whatever, or having to reboot your machine.) – Peter Cordes Oct 31 '16 at 04:14
  • Anyway, you're still trying to dereference a C variable in an inline-asm operand, instead of loading the variable into a register. ([MSVC inline-asm syntax, which I assume Watcom uses, sucks and forces data to go through memory to get into / out of asm statements, instead of being in registers](http://stackoverflow.com/questions/3323445/what-is-the-difference-between-asm-and-asm/35959859#35959859)). The compiler can't do that for you. What would make sense is having C local variables like `unsigned char red`, and do `mov red, al`, then after the asm block, do `p_color->red = red`. – Peter Cordes Oct 31 '16 at 04:15
  • Usage of structs within inline assembler (using `_asm`) isn't supported. You'd likely even find that using dereference like `*p_color` won't give a compiler error but won't generate the instruction you think it will (if it generated one at all). If you are going to create entire functions of inline assembler I highly suggest you consider using `#pragma aux` instead of `_asm`. – Michael Petch Oct 31 '16 at 15:23
  • @MichaelPetch What is the difference between `#pragma aux` and `_asm`? I thought that `_asm` was just a microsoft compatible way to do `#pragma aux` – SeanRamey Nov 01 '16 at 01:46
  • If you are going to inline an entire function then `#pragma aux` gives you control over the calling convention, the registers saved and destroyed and allows the compiler to potentially do better optimizations. Which begs the question what was wrong with the generated code that used `inp` and `outp` functions all done in _C_. Usually you'd leave inline assembler for those tasks that can't be done with _C_ code directly. – Michael Petch Nov 01 '16 at 01:48

1 Answers1

1

A good question would have described how it doesn't work. Just saying it "doesn't work" made me assume it was a syntax error, because I don't know Watcom-style inline asm. I just assumed it was similar to MSVC-style, and that using a C dereference operator in asm was a syntax error (e.g. in mov al,*p_color).

Apparently that is valid syntax for Open Watcom, but you're loading the same byte 3 times. Maybe try mov al, *(p_color+1) for the second byte? Although that might just do C pointer math, and get the start of the next struct. Check your compiler manual for available syntax options.


You could also just load the pointer into a register and use offsets from it yourself (with addressing modes like mov al, [si+1]). This depends on the 3 struct members being laid out in order with no padding, but that should be a safe assumption, I think. You could always check the compiler's asm output to see how it lays out the struct.

Since your struct is laid out in the right order, you should be able to loop over the 3 bytes in it using OUTS. Or even REP OUTS so you don't need to write a loop.

    cld
    mov   si, p_color        ; get the function arg in a register
    mov   dx, PALETTE_DATA
    mov   cx, 3
    rep outsb                ; OUT 3 times, to port DX, using data from DS:[SI] (and do SI++ post-increment)

Similarly, for reading,

    cld
    mov   di, p_color
    mov   dx, PALETTE_DATA
    mov   cx, 3
    rep insb              ; IN 3 times, to port DX, using data from DS:[DI] (and do DI++)
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Actually, my code compiles without error, or warning, and runs. It just doesn't do what I expected. Thank you for this code though. I will try to adapt it and see if it works! :) – SeanRamey Oct 31 '16 at 04:17
  • @SeanRamey: oh, ok so I guess Watcom inline-asm is different from MSVC. This just strengthens my point that learning asm this way means you spend a bunch of time learning tools and syntax that aren't usable for anything outside of 16-bit code, so they won't help you look at the asm output from modern C++ code while looking at performance-counter profiles, and other stuff that it's useful to know asm for in 2016. – Peter Cordes Oct 31 '16 at 04:20
  • Ok, so I tried it. It runs, but it does nearly the exact same thing as before, with the incredibly weird side effect of writing a couple of random pixels that should never have been written to. Also, actually this does help me quite a bit with asm in modern C code. Taking away all the extra bullshit helps me see more clearly. I also just like the historical part of this stuff too. And I learn an incredible amount about the actual hardware. I learn because I get into these really weird situations and by the time I figure them out, I have definitely learned something. – SeanRamey Oct 31 '16 at 04:34
  • @SeanRamey: Ok, that's fair. I did once hook up LEDs to a parallel port and light them up with an OUT instruction (from a 32-bit Linux executable). So I get that seeing your code actually do something to hardware directly is cool. My biggest problem with 16-bit code is the `int 21h` ms-dos system-call API, which is not special or any better for asm than the Linux system call API. And you're not talking about using that. – Peter Cordes Oct 31 '16 at 04:43
  • @SeanRamey: updated my answer based on your update that "it doesn't work" didn't mean syntax error. – Peter Cordes Oct 31 '16 at 04:51
  • Wouldn't *(p_color+1) give me the next p_color struct, not the next byte of the struct? Same for [si+1] assuming si = p_color? Also, I forgot that the setColorPalette is actually inaccurate. The code doesn't dereference the same byte 3 times. I just had a typo. – SeanRamey Oct 31 '16 at 05:08
  • @SeanRamey: yeah, `*(p_color+1)` might obey C pointer-math semantics and get the next struct, IDK. `[SI+1]` is definitely just +1 byte, though. It's just a normal x86 addressing mode, without any inline-asm substitution going on. For it to be anything else, the compiler/assembler would have to remember how you set SI, and that's not how assembly language works. Registers don't have a "type", they just hold bytes, and it's up to the programmer to scale things correctly to index different size objects. – Peter Cordes Oct 31 '16 at 05:22
  • @PeterCordes doing asm in DOS for learning purposes has two advantages, which may counter a bit the disadvantages you listed: 1) you can run it in dosbox, so messing up will not crash your main OS, just the VM box (although with modern OS this is not as likely, as back then, when I was always a bit worried to run my binary first time after long session of asm writing, whether I will have to reboot a restore everything, or not) 2) you can access VGA "directly" (emulated). I absolutely miss 13h mode in linux :/ ... and COM files... would be nice to be able to do 256B intros in linux too. – Ped7g Oct 31 '16 at 11:22
  • An observation is that OUTS wasn't available on the original 8086/8088 and first appeared on the 80186/80188 – Michael Petch Oct 31 '16 at 15:11
  • I don't understand _means you spend a bunch of time learning tools and syntax that aren't usable for anything outside of 16-bit code_ . Openwatcom is still maintained and it is both an optimizing 32-bit and 16-bit compiler. It happens to be OpenWatcom does one thing GCC doesn't - generate real 16-bit code. It also has the ability to target specific variants of 16-bit processors. The inline assembly in OpenWatcom 32-bit code gen is the same as 16-bit. One powerful feature of OW inline is that you can create a wide variety of custom calling conventions (Something GCC is very limited at doing) – Michael Petch Oct 31 '16 at 15:28
  • @MichaelPetch: ok fine, I just wanted to be annoyed at 16-bit weirdness, and it wasn't really justified in this case. Still, Open Watcom can't make 64-bit binaries can it? I need to get better at not answering questions when I'm grumpy. – Peter Cordes Oct 31 '16 at 17:30
  • You shouldn't answer questions where you don't know what you are talking about. If you have a problem with 16-bit question don't answer them. You are doing people asking valid questions no favors by doing what you do. It is clear from your answer you know nothing about OW and its inline assembly. – Michael Petch Oct 31 '16 at 17:34
  • @Ped7g: 1) Real OSes (like Linux, OS X, or Windows NT and derivatives) don't have that problem. Still a good idea to run your binaries as non-root, just to further reduce the chance of making a random system call that does something. – Peter Cordes Oct 31 '16 at 17:36
  • @MichaelPetch: I had another look at the actual answer I posted. I think it's an honest attempt to answer the question without bias, based on what the question said. I admit some of my comments were unhelpful, but with a description like "doesn't work", syntax error seemed like a reasonable guess to me, and my current version of my answer definitely identifies an obvious problem in the original question as posted. I don't expect it to earn a lot of upvotes, but I thought it might have helped the OP enough that nobody else needed to spend time on it. – Peter Cordes Oct 31 '16 at 17:46