Author Topic: x86 assembly, uart-16450, simple putch  (Read 3647 times)

0 Members and 1 Guest are viewing this topic.

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4361
  • Country: us
Re: x86 assembly, uart-16450, simple putch
« Reply #25 on: May 16, 2023, 05:52:45 am »
Quote
NASM is way better than gas if you actually write assembly yourself.
I have no doubt.  gas is mostly for use by the compiler, with perhaps its only advantage for a programmer being that you maintain a lot of pseudo-ops and cli properties across many different architectures.


Sometimes I think it's been made intentionally ugly to discourage assembly language programming.  "@" for a comment character (ARM)?  What were they thinking?

 

Offline DiTBhoTopic starter

  • Super Contributor
  • ***
  • Posts: 4463
  • Country: gb
Re: x86 assembly, uart-16450, simple putch
« Reply #26 on: May 16, 2023, 06:42:54 am »
LOL, crazy, see this trick

Code: [Select]
; -----------------------------------------------------------------------------
; console_out the EOS terminated string which follows the call
;    eg:
;            call say
;            db "hAllo", EOS
; -----------------------------------------------------------------------------
say:
                push    bp
                mov     bp,sp
                push    ds
                push    si
                push    ax
                mov     si, [bp+2]
                push    cs
                pop     ds
say_body:
                lodsb               ; Load byte at address DS:SI into AL.
                cmp al, EOS
                je      say_done
                call    ch_put
                jmp     say_body
say_done:
                mov     [bp+2], si
                pop     ax
                pop     si
                pop     ds
                pop     bp
                ret

unbelievable that you can easily mix code and data this wild way, but it actually works!!!  :o :o :o

ok, seriously it takes too many opcode, let's implement the classic print_string with a string pointer as argument
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline DiTBhoTopic starter

  • Super Contributor
  • ***
  • Posts: 4463
  • Country: gb
Re: x86 assembly, uart-16450, simple putch
« Reply #27 on: May 16, 2023, 07:07:22 am »
(
you can't do anything like this with a MIPS

unless you force strict align=4 for the string (zero padding string_size mod 4)
and there are some implementations that physically separate data from code

x86 is a very primitive beast :o :o :o
)
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 7313
  • Country: fi
    • My home page and email address
Re: x86 assembly, uart-16450, simple putch
« Reply #28 on: May 16, 2023, 01:01:53 pm »
LOL, crazy, see this trick
At the time when 8086/80186/80286-class machines had less than 640kB of RAM, it was common for games to use self-modifying code.
Starflight (1986) used this heavily, including storing the modifications on-disk: to restart the game from scratch, you had to reinstall it (create new copies of the game disks)!

ok, seriously it takes too many opcode, let's implement the classic print_string with a string pointer as argument

Well, this takes 33 bytes, noting that now the character to be sent is in AH (and not AL) register,
Code: [Select]
        [bits 16]

        EOS                     EQU     (0)

        SERIAL_BASE             EQU     (0x3F8)
        SERIAL_THR              EQU     (SERIAL_BASE+0)         ; Transmit Hold Register (w)
        SERIAL_RBR              EQU     (SERIAL_BASE+0)         ; Receive Buffer Register (r)
        SERIAL_LSR              EQU     (SERIAL_BASE+5)         ; Line Status Register (r)

        SERIAL_IS_DATA_READY    EQU     (0x01)
        SERIAL_IS_OVERRUN       EQU     (0x02)
        SERIAL_IS_PARITY_ERROR  EQU     (0x04)
        SERIAL_IS_FRAMING_ERROR EQU     (0x08)
        SERIAL_IS_BREAK         EQU     (0x10)
        SERIAL_IS_THR_EMPTY     EQU     (0x20)
        SERIAL_IS_EMPTY         EQU     (0x40)
        SERIAL_IS_FIFO_ERROR    EQU     (0x80)


        ; Character to be sent in AH
        ; Clobbers AL
serial_putc:
        push    dx
        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


        ; String to be sent in DS:SI
        ; SI will point to EOS
serial_puts:
        push    ax

.serial_puts_next:
        mov     ah, [si]
        cmp     ah, EOS
        je      .serial_puts_done
        call    serial_putc
        inc     si
        jmp     .serial_puts_next

.serial_puts_done:
        pop     ax
        ret

but if you don't need the single-character one, this takes only 28 bytes:
Code: [Select]
        [bits 16]

        EOS                     EQU     (0)

        SERIAL_BASE             EQU     (0x3F8)
        SERIAL_THR              EQU     (SERIAL_BASE+0)         ; Transmit Hold Register (w)
        SERIAL_RBR              EQU     (SERIAL_BASE+0)         ; Receive Buffer Register (r)
        SERIAL_LSR              EQU     (SERIAL_BASE+5)         ; Line Status Register (r)

        SERIAL_IS_DATA_READY    EQU     (0x01)
        SERIAL_IS_OVERRUN       EQU     (0x02)
        SERIAL_IS_PARITY_ERROR  EQU     (0x04)
        SERIAL_IS_FRAMING_ERROR EQU     (0x08)
        SERIAL_IS_BREAK         EQU     (0x10)
        SERIAL_IS_THR_EMPTY     EQU     (0x20)
        SERIAL_IS_EMPTY         EQU     (0x40)
        SERIAL_IS_FIFO_ERROR    EQU     (0x80)

        ; String to be sent in DS:SI
        ; SI will point to EOS
serial_puts:
        push    ax
        push    dx

.serial_puts_next:
        mov     ah, [si]
        cmp     ah, EOS
        jnz     .serial_puts_one
        pop     dx
        pop     ax
        ret

.serial_puts_one:
        inc     si
        mov     dx, SERIAL_LSR

.serial_puts_wait:
        in      al, dx
        test    al, SERIAL_IS_THR_EMPTY
        jz      .serial_puts_wait

        mov     dx, SERIAL_THR
        mov     al, ah
        out     dx, al
        jmp     .serial_puts_next
Often, preserving AX isn't useful, so if you let it clobber that too, omit the push/pop AX, and it'll shrink to just 26 bytes.

The reason I didn't use lodsb in either, is because lodsb; mov ah, al and mov ah, [si] ; inc si are both 3 bytes, but the former depends on the direction flag (D), and the latter does not.

If you look at the compare-to-EOS parts, you'll find that adding other characters for true printf-like functionality (in a separate function!) isn't too difficult.  I just recommend that you make it caller-cleanup (i.e., you only look at the parameters on stack, not pop them off).  For integers, I recommend you use the slow DIV/IDIV-by-radix method (with value in DX*65536+AX), constructing it from right to left in a temporary buffer on stack (16 chars including EOS suffices for 32-bit integers in octal, decimal, and hexadecimal).
 
The following users thanked this post: SiliconWizard, DiTBho


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf