Products > Programming

x86 assembly, uart-16450, simple putch

<< < (5/6) > >>

Nominal Animal:

--- Quote from: westfw on May 15, 2023, 01:05:52 am ---
--- Quote ---[size=0px][dx][/size][/size][size=0px] the contents at address pointed to by the dx register.[/size]
--- End quote ---
But it's not "in al, [dx]" ?  :-)

--- End quote ---
No, because the I/O port space on x86 is special, like DiTBho said above.  The angle bracket notation applies exclusively to memory addresses.

You can only read/write to/from I/O ports from/to AL (8-bit) or AX (16-bit) register (or EAX on 32-bit mode).
You can use an immediate byte to access the first 256 ports, but for ports 256-65535, you have to use the DX register.
See in, out.

This is the same in NASM, MASM, and TASM; i.e., this is the Intel syntax.

In 16-bit mode on 8086, the angle bracket notation is very limited:
* [constant] for exact address
* [register] for address specified in register SI, DI, or BX
* [register+constant] for address specified in register SI, DI, BX, or BP plus a fixed offset
* [register1+register2] for address as a sum of registers, register1 being either BX or BP, and register2 either SI or DI
* [register1+register2+constant] for address as a sum of registers plus a fixed constant, register1 being either BX or BP, and register2 either SI or DIThe string instructions (LODSB, LODSW, STOSB, STOSW, MOVSB, MOVSW, CMPSB, CMPSW,  SCASB, and SCASW) use intrinsic addressing (load/compare/scan from DS:SI, store to ES:DI), auto-incrementing (if D flag clear) or auto-decrementing (if D flag set) the registers (SI and/or DI) afterwards.  For 80186 and later processors in 16-bit mode, add INSB, INSW, OUTSB, and OUTSW for I/O port string instructions; the port number in DX stays unchanged.  Some assemblers allow a correct-looking parameter, but don't even check it, leading to confusion since the parameter may not match what the instruction does.  Essentially, ignore all parameters for these instructions!

The REP instruction prefix repeats the following instruction CX register value number of times.  The prefixes REPZ/REPE, REPNZ/REPNE repeat the following instruction as long as Z flag is set or clear, respectively.  They are only reliable across processor models with the string instructions, as the microcode often uses the REP prefix bit for other uses for non-string instructions.

DiTBho:
I usually use GNU binutils/{ as, ld, * } for { 68k, 68hc11, mips, powerpc }
and the Avocet ProTools line of Compilers, Assemblers, and Simulators for 64-bit { mips64, RISC-V }

So why "nasm" for x86?  :o :o :o

well, I don't like x86 asm programming, I need to prepare a special bootloader and nasm offers this


--- Code: ---; 16 bit app
; -----------------------------------------------------------------------------
[bits 16]
[org 0x7c00] ; boot sector startup
begin_16bit:
            mov bp, stack_base         ; base of the stack
            mov sp, stack_base         ; top  of the stack

            call console_init

            mov bx, msg_16bit_mode
            call print16

...


; 32 bit app
; -----------------------------------------------------------------------------
[bits 32]
begin_32bit:
            mov ebx, msg_32bit_mode
            call print32

            call app_start         ; Give control to the app
            jmp $                  ; halt if app returns control

...

; padding
; -----------------------------------------------------------------------------
; The first 512 bytes of a disk are known as the bootsector
; The boot sector is an area of the disk reserved for booting purposes
; If the bootsector of a disk contains a valid boot sector
; the last word of the sector must contain the signature 0xaa55
; then the disk is treated by the BIOS as bootable.
; -----------------------------------------------------------------------------
times 510 - ($-$$) db 0
dw 0xaa55

--- End code ---

so "nasm" because for this specific need, these nasm tricks make it simpler than with g/as  ;D

Nominal Animal:
You can also supply -masm=intel to gcc to use Intel syntax in assembly output (-S) and in extended inline assembly (asm volatile ("instructions" : outputs : inputs : clobbers);).

Similarly, with GNU as (part of binutils), you can use .intel_syntax noprefix at the beginning of the source file, to use Intel syntax.

I don't remember exactly which versions of as and gcc started supporting these, though.  They aren't new, but they were not initially supported either.

To get the same machine code from as as my previous listing produces using nasm, you do need slight changes:

--- Code: ---    .arch           i386
    .intel_syntax   noprefix
    .code16

    .set    SERIAL_BASE,            (0x3F8)             # Base I/O
    .set    SERIAL_THR,             (SERIAL_BASE+0)     # Transmit Hold Register (w)
    .set    SERIAL_LSR,             (SERIAL_BASE+5)     # Line Status Register (r)
    .set    SERIAL_IS_THR_EMPTY,    (0x20)


    # Character to be sent in al
    # Clobbers ah
    .global serial_putc
serial_putc:
    push    dx
    mov     ah, al
    mov     dx, SERIAL_LSR

serial_putc_wait:
    in      al, dx
    test    al, SERIAL_IS_THR_EMPTY
    jz      serial_putc_wait

    mov     dx, SERIAL_THR
    mov     al, ah
    out     dx, al
    pop     dx
    ret


    # Character to be sent in al
    # Carry flag is clear if nothing sent, set if character sent
    # Clobbers ah
    .global serial_putc_nowait
serial_putc_nowait:
    push    dx
    mov     ah, al
    mov     dx, SERIAL_LSR
    in      al, dx
    test    al, SERIAL_IS_THR_EMPTY
    mov     al, ah
    jnz     serial_putc_nowait_send
    pop     dx
    clc
    ret

serial_putc_nowait_send:
    mov     dx, SERIAL_THR
    out     dx, al
    pop     dx
    stc
    ret

--- End code ---
To switch to 32-bit code generation, use .code32 directive.
You can compile the above using as source.s -o target.o.

The main differences are that dot (.) starts a directive, and # a comment; ; is just a separator like newline.  All symbols are local by default, so you need to declare the global ones.  Constants are defined using .set NAME, value instead of EQU.

SiliconWizard:
NASM is way better than gas if you actually write assembly yourself.

Nominal Animal:

--- Quote from: SiliconWizard on May 15, 2023, 08:49:33 pm ---NASM is way better than gas if you actually write assembly yourself.

--- End quote ---
Agreed.  If I was writing a bootloader or similar freestanding code in assembly for 16/32-bit x86, I'd use NASM too, and not GNU as.
I just wanted to show that this is possible to do also in (recent enough) GNU as; NASM isn't explicitly required here.

After switching to 32-bit mode, things become much more interesting, since there are a few function attributes that GCC and Clang support for the calling convention; the default "everything on stack" is pretty annoying in my opinion.  Basically, if you have a function that takes its parameters in eax, edx, and ecx registers (in that order), you can just declare the prototype with __attribute__((regparm (3)), and the compiler will pass the parameters in said registers in calls to that function iff the prototype is visible.

Navigation

[0] Message Index

[#] Next page

[*] Previous page

There was an error while thanking
Thanking...
Go to full version
Powered by SMFPacks Advanced Attachments Uploader Mod