EEVblog Electronics Community Forum

Products => Computers => Programming => Topic started by: DiTBho on May 04, 2023, 07:24:36 pm

Title: x86 assembly, uart-16450, simple putch
Post by: DiTBho on May 04, 2023, 07:24:36 pm
Code: [Select]
; Serial Port 16450 IO
; ====================

;base DLAB access Abbreviation  Register Nam
; +0   0   Write      THR **    Transmitter Holding Buffer
; +0   0   Read       RBR **    Receiver Buffer
; +0   1   Read/Write DLL **    Divisor Latch Low Byte
; +1   1   Read/Write DLM **    Divisor Latch High Byte
; +1   0   Read/Write IER       Interrupt Enable Registe
; +2   x   Read       IIR       Interrupt Identification Register
; +3   x   Read/Write LCR **    Line Control Register
; +4   x   Read/Write MCR **    Modem Control Register
; +5   x   Read       LSR       Line Status Register
; +6   x   Read       MSR       Modem Status Register

; baud rate setting
; latch low byte
; 0x00 0x01 = 115200 bps
; 0x00 0x02 =  56700 bps
; 0x00 0x03 =  38400 bps
; 0x00 0x06 =  19200 bps
; 0x00 0x0C =   9600 bps
; 0x00 0x18 =   4800 bps
; 0x00 0x30 =   2400 bps

serial_base     equ (0x3f8)
serial_cmd0     equ (serial_base + 0)
serial_cmd1     equ (serial_base + 1)
serial_cmd2     equ (serial_base + 2)
serial_cmd3     equ (serial_base + 3)
serial_cmd4     equ (serial_base + 4)
serial_status   equ (serial_base + 5)

console_init:
                push   dx
                push   ax
                                         ; Settings
                mov    dx, serial_cmd3
                mov    al, 0x80          ; DLAB ON
                out    dx, al            ; get it on its way.

                mov    dx, serial_cmd0   ; 115200 bps
                mov    al, 0x01          ; baud rate - Divisor Latch low byte
                out    dx, al            ; get it on its way.

                mov    dx, serial_cmd1
                mov    al, 0x00          ; baud rate - divisor latch high byte
                out    dx, al            ; get it on its way.

                mov    dx, serial_cmd3
                mov    al, 0x03          ; 8 bits, No parity, 1 stop bit
                out    dx, al            ; get it on its way.

                mov    dx, serial_cmd2
                mov    al, 0xC7          ; FIFO control register
                out    dx, al            ; get it on its way.

                mov    dx, serial_cmd4
                mov    al, 0x0B          ; turn on DTR, RTS, and OUT2
                out    dx, al            ; get it on its way.

; clear the Divisor Latch Access Bit
; in order to use the Transmitter Holding Register

                mov    dx, serial_cmd3   ; Line Control Register
                in     al, dx
                and    al, 0x7f          ; 0111_1111b, Set DLAB=0, DLAP is bit7
                out    dx, al

                pop    ax
                pop    dx
                ret

; reg al: received character
ch_get:
                ; to be implemented
                mov    al, 0x00
                ret

; reg al: char to transmit
ch_put:
                push   dx
                push   ax
                push   cx
ch_put_wait:
                mov dx, serial_status    ; Line Status Register
                mov cx, ax
                in al, dx
                test cx, 0x20            ; 0010_0000b, use bit 5 to see if THR is empty
                jz ch_put_wait

                mov ax, cx
                mov dx, serial_cmd0      ; Transmitter Holding Register
                out dx, al

                pop    cx
                pop    ax
                pop    dx
                ret
(serial.s)

Code: [Select]
            cli                        ; disable interrupts

            mov bp, 0x9000
            mov sp, bp

            call console_init

main:
            mov al, 'h'
            call ch_put
            mov al, 'a'
            call ch_put
            mov al, 'l'
            call ch_put
            mov al, 'l'
            call ch_put
            mov al, 'o'
            call ch_put

            jmp main
(main.s)

do you see any error with this simple code?
The serial doesn't correctly show chars  :o :o :o

edit:
nasm syntax
Title: Re: x86 assembly, uart-16450, simple putch
Post by: Andy Watson on May 04, 2023, 08:39:06 pm
I'm not familiar with the 16450, however, one common problem is not waiting for the character flag of the transmit register. Once the character is put into the Tx register the tx-full flag will be set, BUT usually this is a state controlled operation that is operating at the speed of the UART clock. Hence it is possible to put a character into tx reg, return from the subroutine, and then re-enter with the next character before the flag is set. The solution, is to use interupts or to wait for the flag to be set after putting the character in the Tx reg. If using the non-interrupt version, you might want to bracket the tx insertion and subsequent wait loop with disabled interupts - so that you don't miss the flag setting completely.
Title: Re: x86 assembly, uart-16450, simple putch
Post by: DiTBho on May 04, 2023, 09:52:37 pm
Code: [Select]
ch_put_wait:
                mov dx, serial_status    ; Line Status Register
                mov cx, ax
                in al, dx
                test cx, 0x20            ; 0010_0000b, use bit 5 to see if THR is empty
                jz ch_put_wait

this part? it waits and loops until the status register reports "THR is empty"
Title: Re: x86 assembly, uart-16450, simple putch
Post by: Andy Watson on May 04, 2023, 10:17:12 pm
You still need the empty check.

Consider what happens when you have multiple characters to transmit. Your present code is:

Loop until Tx empty - fill Tx 1 - go away and fetch next character - Loop until Tx empty - fill Tx 2 - etc..

The problem is that the setting of the UART flags is asynchronos and can be much slower than the "go away and fetch next charater" such that when the code returns with the second character it can find the Tx is still being flagged as empty.
The non-interrupt solution is:
Loop until Tx empty - (disable interrupts) - fill Tx 1 -  Loop until Tx full - (re-eneable interrupts) - go away to fetch nect character - etc..
You need the fill tx and loop until full to be atomic, hence the disabling/enabling of interrupts - if they are being used.
Title: Re: x86 assembly, uart-16450, simple putch
Post by: DiTBho on May 04, 2023, 10:22:55 pm
but interrupts are always disabled in that code.
Title: Re: x86 assembly, uart-16450, simple putch
Post by: mwb1100 on May 04, 2023, 10:24:51 pm
There's a couple problems in the loop that waits for THR empty:

Code: [Select]
ch_put_wait:
                mov dx, serial_status    ; Line Status Register
                mov cx, ax
                in al, dx                ; <--- al is trashed so you have lost the character you want to transmit, if a jump back to ch_put_wait happens then CX is trashed too
                test cx, 0x20            ; <--- cx doesn't have the serial status register's value - al does.    // 0010_0000b, use bit 5 to see if THR is empty
                jz ch_put_wait

Disclaimer - my x86 assembly is very rusty.  I can't remember the last time I had to do anything with it.

Same goes for my 16x50 family UART knowledge.

Title: Re: x86 assembly, uart-16450, simple putch
Post by: DiTBho on May 04, 2023, 10:26:50 pm
ok, let's also try a different approach, just in case ...

Unfortunately, I don't have a serious x86 machine with a physical 16460 chip handy, my mac-mini/x86 doesn't have it, and my Soekris Net5501's serial line "may be" damaged (its firmware doesn't correctly output chars on the serial line), or it's just that its flash is corrupted, which is why I'd like to reprogram it with u-boot


Can anyone with a BIOS-x86 && physical 16460 uart test/confirm that this code works?
Code: [Select]

; Serial Port Initialization
; ==========================
;
; ah=0x00 ; serial BIOS service (request, init)
; serial port status is returned in ax
;
; lets set the baud rate, parity modes, stop bits, bits tx/rx
;
; Bits    Function
; 5..7    baud rate
;  000      110 bps
;  001      150 bps
;  010      300 bps
;  011      600 bps
;  100     1200 bps
;  101     2400 bps
;  110     4800 bps
;  111     9600 bps
;
; Bits    Function
; 3..4    parity
;   00    No parity
;   01    Odd parity
;   10    No parity
;   11    Even parity
;
; Bits    Function
;    2    Stop bits
;    0    One stop bit
;    1    Two stop bits
;
; Bits    Function
; 0..1    Character Size
;   10    7 bits
;   11    8 bits
;
; Although the standard PC serial port hardware supports 19200 bps
; some BIOSes may not support this speed.
;
; Example: COM1,2400,no parity,8data,1stop
;
;                mov     ah, 0           ;Initialize opcode
;                mov     al, 10100111b   ;Parameter data.
;                mov     dx, 0           ;COM1: port.
;                int     14h
;
; -----------------------------------------------------

; reg al: character to transmit
ch_put:
     pusha ; saves all the registers
     mov     dx, 0           ; Select COM1:
     mov     ah, 0x01        ; serial BIOS service (request, putch)
     int     0x14            ; serial BIOS service (exec)
;    test    ah, 0x80h       ; Check for error
;    jnz     SerialError
     popa ; restores all the registers
     ret



; ----------------------------------------------------------------
;
; Serial Port Status
; ==================
;
; ah=0x03      ; serial BIOS service (request, status)
; This call returns status information about the serial port
; including whether or not an error has occurred
; if a character has been received in the receive buffer
; if the transmit buffer is empty
; other pieces of useful information
; On entry into this routine, the dx register contains the serial port number
; On exit, the ax register contains the following values:
;
;  reg ax:     Bit Meaning
;  =================================================
;  bit.15      Time out error
;  bit.14      Transmitter shift register empty
;  bit.13      Transmitter holding register empty
;  bit.12      Break detection error
;  bit.11      Framing error
;  bit.10      Parity error
;  bit.09      Overrun error
;  bit.08      Data available
;  bit.07      Receive line signal detect
;  bit.06      Ring indicator
;  bit.05      Data set ready (DSR)
;  bit.04      Clear to send (CTS)
;  bit.03      Delta receive line signal detect
;  bit.02      Trailing edge ring detector
;  bit.01      Delta data set ready
;  bit.00      Delta clear to send

The UART has been available since the early 1980s, constantly refined rather than reinvented, it has shown little change over the years in its general mode of operation, but modern UARTs like 16450 and 16550 are traceable to early classics like 8250 included on the original IBM PC motherboard to provide communications with modems and serial printers.

The original IBM PC introduced BIOS functions to initialize (max 9600bps) and transfer/receive char.

I am not an expert in BIOS programming (x86/16bit mode), but the above code should be much simpler than the previous uart-chip direct approach :o :o :o
Title: Re: x86 assembly, uart-16450, simple putch
Post by: DiTBho on May 04, 2023, 10:29:11 pm
There's a couple problems in the loop that waits for THR empty:

Code: [Select]
                mov cx, ax
                in al, dx                ; <--- al is trashed so you have lost the character you want to transmit
                test cx, 0x20            ; <--- cx doesn't have the serial status register's value - al does.    //

"mov cx, ax" saves ax, ax is { ah:al }  :D
Title: Re: x86 assembly, uart-16450, simple putch
Post by: mwb1100 on May 05, 2023, 12:22:18 am
"mov cx, ax" saves ax, ax is { ah:al }  :D

Yes, but:

Code: [Select]
ch_put_wait:
                mov dx, serial_status    ; Line Status Register
                mov cx, ax          ; /* 3 */
                in al, dx           ; /* 1 */
                test cx, 0x20         
                jz ch_put_wait      ; /* 2 */

  - the "in al, dx" at /* 1 */ modifies AL
  - if the "jz ch_put_wait" at /* 2 */ results in a jump (which isn't necessarily determined by the read of the status register, since that's in AL not CX), then
  - the "mov cx, ax" at /* 3 */ saves into CX an AL register that has been changed by the instruction at /* 1 */ from the previous loop iteration
Title: Re: x86 assembly, uart-16450, simple putch
Post by: mwb1100 on May 05, 2023, 12:29:34 am
I'd suggest a wait loop like:

Code: [Select]
ch_put_wait:
                mv cx, ax     ; save ax
                mov dx, serial_status    ; Line Status Register
ch_put_wait_loop:
                in al, dx
                test al, 0x20         
                jz ch_put_wait_loop
               
                mov ax, cx    ; restore ax
Title: Re: x86 assembly, uart-16450, simple putch
Post by: DiTBho on May 05, 2023, 07:29:40 am
This code is still not working, but I am now 100% sure it's a hardware problem with the serial of my net5501, and at least I know I'd better try with on my T23 old IBM laptop.


Anyway, see the silly bug? This is what we call "mind vision": you look at the code, but you don't look at the actual code, but rather what your mind has placed on your eyes.

x86-assembly is terrible, especially if you are used to programming m68k or mips, architectures that have much more orthogonality.

With the x86/16bit mode, these only accept al as a target
Code: [Select]
; in al,imm8      Input byte from imm8 I/O port address into al
; in ax,imm8      Input byte from imm8 I/O port address into ax
; in al,dx        Input byte from I/O port in dx into al
; in ax,dx        Input word from I/O port in dx into ax
and that's the reason behind that wrong line of code: I wrote it initially as
Code: [Select]
                in cx, dx
                test cx, 0x20       
but it's illegal x86 code, so I fixed the first line, and forgot the second line.

And behind the bug in the code itself, lurks the most powerful bug of all: the one with how human beings interpret reality, something wrong pre-polarized in the brain that makes the difference between what the brain wants to see rather than what it actually sees.



Thank you guys  :-+ :-+ :-+
Title: Re: x86 assembly, uart-16450, simple putch
Post by: DiTBho on May 06, 2023, 11:09:15 am
So, yesterday I installed qemu-i386 and I am simulating the serial port on tty/stdio
Code: [Select]
qemu running ...
CPU in Real Mode

hAllo
confirmed: the put_ch code does work!

This morning I looked at the physical port of the net5511 and found the ground pin of the serial0 was floating, causing bad contact. By re-soldering the pin with fresh lead it's not perfectly working.

Solved  :-+ :-+ :-+
Title: Re: x86 assembly, uart-16450, simple putch
Post by: westfw on May 14, 2023, 02:14:25 am
Quote
                mov    dx, serial_cmd3
Does whatever assembler you're using interpret that at "move immediate value (address of serial_cmd30 into dx", or is it going to load dx with the contents of that address?  MOV on x86 could be one of about a dozen different opcodes.


The intel assembler would probably want "serial_cmd3" to be strongly typed (strongly typed assembler.  Hmmph.)
I'm not sure what gas does (guessing that you're using gas, based on the .s file extension.)
(and wait - isn't the operand order wrong?  Shouldn't it be more like:
Code: [Select]
  mov $serial_cmd3, dx
(this probably means you're using some other assembler...)
Title: Re: x86 assembly, uart-16450, simple putch
Post by: DiTBho on May 14, 2023, 11:08:12 am
as=nasm

Code: [Select]
serial_base     equ (0x3f8)
serial_cmd0     equ (serial_base + 0)
serial_cmd1     equ (serial_base + 1)
serial_cmd2     equ (serial_base + 2)
serial_cmd3     equ (serial_base + 3)
serial_cmd4     equ (serial_base + 4)
serial_status   equ (serial_base + 5)
Code: [Select]
ch_put:
                pusha                    ; saves all registers
                mov cx, ax               ; save al, char to transmit
                ;------------------------------------------------------------------------
ch_put_wait:
                mov dx, serial_status    ; Line Status Register
                in al, dx
                test al, 0x20            ; 0010_0000b, use bit 5 to see if THR is empty
                jz ch_put_wait
                ;------------------------------------------------------------------------
                mov ax, cx               ; restore al, char to transmit
                mov dx, serial_cmd0      ; Transmitter Holding Register
                out dx, al               ; tramsmit(char)

                popa                     ; restore all registers
                ret

this works with nasm

Title: Re: x86 assembly, uart-16450, simple putch
Post by: DiTBho on May 14, 2023, 11:17:06 am
Code: [Select]
dev-lang/nasm-v2.15.05
      Homepage:      [url]https://www.nasm.us/[/url]
      Description:   groovy little assembler
      License:       BSD-2
Title: Re: x86 assembly, uart-16450, simple putch
Post by: Nominal Animal on May 14, 2023, 04:51:37 pm
If I may, assuming this is protected mode 32-bit code,
Code: [Select]
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 al
        ; Clobbers ah
serial_putc:
        push    edx
        mov     ah, al
        movzx   edx, SERIAL_LSR

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

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


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

.serial_putc_nowait_send:
        movzx   edx, SERIAL_THR
        out     dx, al
        pop     edx
        stc
        ret
The serial_putc_nowait is useful, if you have a control loop and don't want to set up the serial interrupt.
When you have a string to be sent, just load the next character to al, and call serial_putc_nowait.
If carry flag is set, it was sent; otherwise a previous transmission is still in progress and carry flag is clear.
If you loaded al using a pointer, you can simply add-with-carry zero to the pointer register after the call (often esi, i.e: call serial_putc_nowait followed by adc esi, 0), and save the pointer value.  Just remember to check if al is zero (end of string) before calling serial_putc_nowait.

As usual, labels starting with . are local (and re-definable), and won't be added to the symbol table.
Title: Re: x86 assembly, uart-16450, simple putch
Post by: DiTBho on May 14, 2023, 08:31:17 pm
If I may, assuming this is protected mode 32-bit code

real-mode, because it's what the PC_BIOS initializes.

Title: Re: x86 assembly, uart-16450, simple putch
Post by: Nominal Animal on May 14, 2023, 09:29:40 pm
If I may, assuming this is protected mode 32-bit code

real-mode, because it's what the PC_BIOS initializes.
Ah, so make that
Code: [Select]
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 al
        ; Clobbers ah
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
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

In NASM syntax, raw numbers are literals.  (A $ prefix does nothing; $0x3F8 is exactly the same thing as 0x3F8 or 1016.)

Memory access is always in square brackets, so for example [0x3F8] refers to the contents of address 0x3F8, and [dx] the contents at address pointed to by the dx register.
Title: Re: x86 assembly, uart-16450, simple putch
Post by: 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]
But it's not "in al, [dx]" ?  :-)


Thanks for the explanations, although I'm not entirely happy that there are apaprently three common ASM syntaxes for x86 these days!  (?)  (hmm.  online says that NASM does use the Intel syntax.  But it doesn't look much like the MASM code I used to write; wasn't MASM "real" Intel syntax?)
Title: Re: x86 assembly, uart-16450, simple putch
Post by: DiTBho on May 15, 2023, 11:16:56 am
But it's not "in al, [dx]" ?  :-)

z80, 8080 and x86 have two spaces: { port_IO, mem }

{ in, out } instructions serve port_IO

"in y, x" can target to reg, read data from IO_port(x), and put it into reg.y
"out x, y" can source data from a reg, read reg.y, and put it into IO_port(x)

three common ASM syntaxes for x86 these days

eh, many more for 68k and 68hc11  ;D
Title: Re: x86 assembly, uart-16450, simple putch
Post by: Nominal Animal on May 15, 2023, 04:44:35 pm
Quote
[size=0px][dx][/size][/size][size=0px] the contents at address pointed to by the dx register.[/size]
But it's not "in al, [dx]" ?  :-)
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 (https://www.felixcloutier.com/x86/in), out (https://www.felixcloutier.com/x86/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:The string instructions (LODSB, LODSW, STOSB, STOSW, MOVSB, MOVSW, CMPSB, CMPSWSCASB, 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.
Title: Re: x86 assembly, uart-16450, simple putch
Post by: DiTBho on May 15, 2023, 05:13:23 pm
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: [Select]
; 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

so "nasm" because for this specific need, these nasm tricks make it simpler than with g/as  ;D
Title: Re: x86 assembly, uart-16450, simple putch
Post by: Nominal Animal on May 15, 2023, 08:27:36 pm
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: [Select]
    .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
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.
Title: Re: x86 assembly, uart-16450, simple putch
Post by: SiliconWizard on May 15, 2023, 08:49:33 pm
NASM is way better than gas if you actually write assembly yourself.
Title: Re: x86 assembly, uart-16450, simple putch
Post by: Nominal Animal on May 16, 2023, 12:08:01 am
NASM is way better than gas if you actually write assembly yourself.
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 (https://gcc.gnu.org/onlinedocs/gcc/x86-Function-Attributes.html) 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.
Title: Re: x86 assembly, uart-16450, simple putch
Post by: westfw 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?

Title: Re: x86 assembly, uart-16450, simple putch
Post by: DiTBho 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
Title: Re: x86 assembly, uart-16450, simple putch
Post by: DiTBho 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
)
Title: Re: x86 assembly, uart-16450, simple putch
Post by: Nominal Animal 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 (https://en.wikipedia.org/wiki/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).