x86 assembly, uart-16450, simple putch - Page 1
Forum section: electronics / general

author: DiTBho
author_rank: Super Contributor
timestamp: May 04, 2023, 07:24:36 pm
content: Code:; 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 bpsserial_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 dxpush ax; Settingsmov dx, serial_cmd3mov al, 0x80 ; DLAB ONout dx, al ; get it on its way.mov dx, serial_cmd0 ; 115200 bpsmov al, 0x01 ; baud rate - Divisor Latch low byteout dx, al ; get it on its way.mov dx, serial_cmd1mov al, 0x00 ; baud rate - divisor latch high byteout dx, al ; get it on its way.mov dx, serial_cmd3mov al, 0x03 ; 8 bits, No parity, 1 stop bitout dx, al ; get it on its way.mov dx, serial_cmd2mov al, 0xC7 ; FIFO control registerout dx, al ; get it on its way.mov dx, serial_cmd4mov al, 0x0B ; turn on DTR, RTS, and OUT2out dx, al ; get it on its way.; clear the Divisor Latch Access Bit; in order to use the Transmitter Holding Registermov dx, serial_cmd3 ; Line Control Registerin al, dxand al, 0x7f ; 0111_1111b, Set DLAB=0, DLAP is bit7out dx, alpop axpop dxret; reg al: received characterch_get:; to be implementedmov al, 0x00ret; reg al: char to transmitch_put:push dxpush axpush cxch_put_wait:mov dx, serial_status ; Line Status Registermov cx, axin al, dxtest cx, 0x20 ; 0010_0000b, use bit 5 to see if THR is emptyjz ch_put_waitmov ax, cxmov dx, serial_cmd0 ; Transmitter Holding Registerout dx, alpop cxpop axpop dxret(serial.s)Code:cli ; disable interruptsmov bp, 0x9000mov sp, bpcall console_initmain:mov al, 'h'call ch_putmov al, 'a'call ch_putmov al, 'l'call ch_putmov al, 'l'call ch_putmov al, 'o'call ch_putjmp main(main.s)do you see any error with this simple code?The serial doesn't correctly show charsedit:nasm syntax

author: Andy Watson
author_rank: Super Contributor
timestamp: May 04, 2023, 08:39:06 pm
content: 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 setafterputting 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.

author: DiTBho
author_rank: Super Contributor
timestamp: May 04, 2023, 09:52:37 pm
content: Code:ch_put_wait:mov dx, serial_status ; Line Status Registermov cx, axin al, dxtest cx, 0x20 ; 0010_0000b, use bit 5 to see if THR is emptyjz ch_put_waitthis part? it waits and loops until the status register reports "THR is empty"

author: Andy Watson
author_rank: Super Contributor
timestamp: May 04, 2023, 10:17:12 pm
content: 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.

author: DiTBho
author_rank: Super Contributor
timestamp: May 04, 2023, 10:22:55 pm
content: but interrupts are always disabled in that code.

author: mwb1100
author_rank: Frequent Contributor
timestamp: May 04, 2023, 10:24:51 pm
content: There's a couple problems in the loop that waits for THR empty:Code:ch_put_wait:mov dx, serial_status ; Line Status Registermov cx, axin 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 tootest cx, 0x20 ; <--- cx doesn't have the serial status register's value - al does. // 0010_0000b, use bit 5 to see if THR is emptyjz ch_put_waitDisclaimer - 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.

author: DiTBho
author_rank: Super Contributor
timestamp: May 04, 2023, 10:26:50 pm
content: 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-bootCan anyone with a BIOS-x86 && physical 16460 uart test/confirm that this code works?Code:; 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 transmitch_put:pusha ; saves all the registersmov 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 SerialErrorpopa ; restores all the registersret; ----------------------------------------------------------------;; 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 sendThe 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

author: DiTBho
author_rank: Super Contributor
timestamp: May 04, 2023, 10:29:11 pm
content: Quote from: mwb1100 on May 04, 2023, 10:24:51 pm"mov cx, ax" saves ax, ax is { ah:al }

author: mwb1100
author_rank: Frequent Contributor
timestamp: May 05, 2023, 12:22:18 am
content: Quote from: DiTBho on May 04, 2023, 10:29:11 pmYes, but:Code:ch_put_wait:mov dx, serial_status ; Line Status Registermov cx, ax ; /* 3 */in al, dx ; /* 1 */test cx, 0x20jz 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

author: mwb1100
author_rank: Frequent Contributor
timestamp: May 05, 2023, 12:29:34 am
content: I'd suggest a wait loop like:Code:ch_put_wait:mv cx, ax ; save axmov dx, serial_status ; Line Status Registerch_put_wait_loop:in al, dxtest al, 0x20jz ch_put_wait_loopmov ax, cx ; restore ax

author: DiTBho
author_rank: Super Contributor
timestamp: May 05, 2023, 07:29:40 am
content: 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 targetCode:; 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 axand that's the reason behind that wrong line of code: I wrote it initially asCode:in cx, dxtest cx, 0x20but 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

author: DiTBho
author_rank: Super Contributor
timestamp: May 06, 2023, 11:09:15 am
content: So, yesterday I installed qemu-i386 and I am simulating the serial port on tty/stdioCode:qemu running ...CPU in Real ModehAlloconfirmed: 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

author: westfw
author_rank: Super Contributor
timestamp: May 14, 2023, 02:14:25 am
content: QuoteDoes 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:mov $serial_cmd3, dx(this probably means you're using some other assembler...)

author: DiTBho
author_rank: Super Contributor
timestamp: May 14, 2023, 11:08:12 am
content: as=nasmCode: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:ch_put:pusha ; saves all registersmov cx, ax ; save al, char to transmit;------------------------------------------------------------------------ch_put_wait:mov dx, serial_status ; Line Status Registerin al, dxtest al, 0x20 ; 0010_0000b, use bit 5 to see if THR is emptyjz ch_put_wait;------------------------------------------------------------------------mov ax, cx ; restore al, char to transmitmov dx, serial_cmd0 ; Transmitter Holding Registerout dx, al ; tramsmit(char)popa ; restore all registersretthis works with nasm

author: DiTBho
author_rank: Super Contributor
timestamp: May 14, 2023, 11:17:06 am
content: Code:dev-lang/nasm-v2.15.05Homepage: https://www.nasm.us/Description: groovy little assemblerLicense: BSD-2

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 14, 2023, 04:51:37 pm
content: If I may, assuming this is protected mode 32-bit code,Code: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 ahserial_putc:push edxmov ah, almovzx edx, SERIAL_LSR.serial_putc_wait:in al, dxtest al, SERIAL_IS_THR_EMPTYjz .serial_putc_waitmovzx edx, SERIAL_THRmov al, ahout dx, alpop edxret; Character to be sent in al; Carry flag is clear if nothing sent, set if character sent; Clobbers ahserial_putc_nowait:push edxmov ah, almovzx edx, SERIAL_LSRin al, dxtest al, SERIAL_IS_THR_EMPTYmov al, ahjnz .serial_putc_nowait_sendpop edxclcret.serial_putc_nowait_send:movzx edx, SERIAL_THRout dx, alpop edxstcretTheserial_putc_nowaitis 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 toal, and callserial_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 loadedalusing a pointer, you can simply add-with-carry zero to the pointer register after the call (oftenesi, i.e:call serial_putc_nowaitfollowed byadc esi, 0), and save the pointer value. Just remember to check ifalis zero (end of string) before callingserial_putc_nowait.As usual, labels starting with.are local (and re-definable), and won't be added to the symbol table.

author: DiTBho
author_rank: Super Contributor
timestamp: May 14, 2023, 08:31:17 pm
content: Quote from: Nominal Animal on May 14, 2023, 04:51:37 pmreal-mode, because it's what the PC_BIOS initializes.

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 14, 2023, 09:29:40 pm
content: Quote from: DiTBho on May 14, 2023, 08:31:17 pmAh, so make thatCode: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 ahserial_putc:push dxmov ah, almov dx, SERIAL_LSR.serial_putc_wait:in al, dxtest al, SERIAL_IS_THR_EMPTYjz .serial_putc_waitmov dx, SERIAL_THRmov al, ahout dx, alpop dxret; Character to be sent in al; Carry flag is clear if nothing sent, set if character sent; Clobbers ahserial_putc_nowait:push dxmov ah, almov dx, SERIAL_LSRin al, dxtest al, SERIAL_IS_THR_EMPTYmov al, ahjnz .serial_putc_nowait_sendpop dxclcret.serial_putc_nowait_send:mov dx, SERIAL_THRout dx, alpop dxstcretIn NASM syntax, raw numbers are literals. (A$prefix does nothing;$0x3F8is exactly the same thing as0x3F8or1016.)Memory access is always in square brackets, so for examplerefers to the contents of address 0x3F8, andthe contents at address pointed to by the dx register.

author: westfw
author_rank: Super Contributor
timestamp: May 15, 2023, 01:05:52 am
content: QuoteBut it's not "in al, " ? :-)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?)

author: DiTBho
author_rank: Super Contributor
timestamp: May 15, 2023, 11:16:56 am
content: Quote from: westfw on May 15, 2023, 01:05:52 amz80, 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)Quote from: westfw on May 15, 2023, 01:05:52 ameh, many more for 68k and 68hc11

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 15, 2023, 04:44:35 pm
content: Quote from: westfw on May 15, 2023, 01:05:52 amNo, 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.Seein,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:for exact addressfor address specified in registerSI,DI, orBX[register+constant]for address specified in registerSI,DI,BX, orBPplus a fixed offset[register1+register2]for address as a sum of registers,register1being eitherBXorBP, andregister2eitherSIorDI[register1+register2+constant]for address as a sum of registers plus a fixed constant,register1being eitherBXorBP, andregister2eitherSIorDIThe string instructions (LODSB,LODSW,STOSB,STOSW,MOVSB,MOVSW,CMPSB,CMPSW,SCASB, andSCASW) use intrinsic addressing (load/compare/scan fromDS:SI, store toES:DI), auto-incrementing (if D flag clear) or auto-decrementing (if D flag set) the registers (SIand/orDI) afterwards. For 80186 and later processors in 16-bit mode, addINSB,INSW,OUTSB, andOUTSWfor I/O port string instructions; the port number inDXstays 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!TheREPinstruction prefix repeats the following instructionCXregister value number of times. The prefixesREPZ/REPE,REPNZ/REPNErepeat the following instruction as long asZflag 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.

author: DiTBho
author_rank: Super Contributor
timestamp: May 15, 2023, 05:13:23 pm
content: 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?well, I don't likex86 asm programming, I need to prepare a special bootloader and nasm offers thisCode:; 16 bit app; -----------------------------------------------------------------------------[bits 16][org 0x7c00] ; boot sector startupbegin_16bit:mov bp, stack_base ; base of the stackmov sp, stack_base ; top of the stackcall console_initmov bx, msg_16bit_modecall print16...; 32 bit app; -----------------------------------------------------------------------------[bits 32]begin_32bit:mov ebx, msg_32bit_modecall print32call app_start ; Give control to the appjmp $ ; 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 0dw 0xaa55so "nasm" because for this specific need, these nasm tricks make it simpler than with g/as

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 15, 2023, 08:27:36 pm
content: You can also supply-masm=inteltogccto use Intel syntax in assembly output (-S) and in extended inline assembly (asm volatile ("instructions" :outputs:inputs:clobbers);).Similarly, with GNUas(part ofbinutils), you can use.intel_syntax noprefixat the beginning of the source file, to use Intel syntax.I don't remember exactly which versions ofasandgccstarted supporting these, though. They aren'tnew, but they were not initially supported either.To get the same machine code fromasas my previous listing produces usingnasm, 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_putcserial_putc:push dxmov ah, almov dx, SERIAL_LSRserial_putc_wait:in al, dxtest al, SERIAL_IS_THR_EMPTYjz serial_putc_waitmov dx, SERIAL_THRmov al, ahout dx, alpop dxret# Character to be sent in al# Carry flag is clear if nothing sent, set if character sent# Clobbers ah.global serial_putc_nowaitserial_putc_nowait:push dxmov ah, almov dx, SERIAL_LSRin al, dxtest al, SERIAL_IS_THR_EMPTYmov al, ahjnz serial_putc_nowait_sendpop dxclcretserial_putc_nowait_send:mov dx, SERIAL_THRout dx, alpop dxstcretTo switch to 32-bit code generation, use.code32directive.You can compile the above usingassource.s-otarget.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.setNAME,valueinstead ofEQU.

author: SiliconWizard
author_rank: Super Contributor
timestamp: May 15, 2023, 08:49:33 pm
content: NASM is way better than gas if you actually write assembly yourself.

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 16, 2023, 12:08:01 am
content: Quote from: SiliconWizard on May 15, 2023, 08:49:33 pmAgreed. 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 afew function attributesthat 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.

author: westfw
author_rank: Super Contributor
timestamp: May 16, 2023, 05:52:45 am
content: QuoteI 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?

author: DiTBho
author_rank: Super Contributor
timestamp: May 16, 2023, 06:42:54 am
content: LOL, crazy, see this trickCode:; -----------------------------------------------------------------------------; console_out the EOS terminated string which follows the call; eg:; call say; db "hAllo", EOS; -----------------------------------------------------------------------------say:push bpmov bp,sppush dspush sipush axmov si, [bp+2]push cspop dssay_body:lodsb ; Load byte at address DS:SI into AL.cmp al, EOSje say_donecall ch_putjmp say_bodysay_done:mov [bp+2], sipop axpop sipop dspop bpretunbelievable that you can easily mixcodeanddatathis wild way, but it actually works!!!ok, seriously it takes too many opcode, let's implement the classic print_string with a string pointer as argument

author: DiTBho
author_rank: Super Contributor
timestamp: May 16, 2023, 07:07:22 am
content: (you can't do anything like this with a MIPSunless you force strict align=4 for the string (zero padding string_size mod 4)and there are some implementations that physically separate data from codex86 is a very primitive beast)

author: Nominal Animal
author_rank: Super Contributor
timestamp: May 16, 2023, 01:01:53 pm
content: Quote from: DiTBho on May 16, 2023, 06:42:54 amAt 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)!Quote from: DiTBho on May 16, 2023, 06:42:54 amWell, this takes 33 bytes, noting that now the character to be sent is in AH (and not AL) register,Code:[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 ALserial_putc:push dxmov dx, SERIAL_LSR.serial_putc_wait:in al, dxtest al, SERIAL_IS_THR_EMPTYjz .serial_putc_waitmov dx, SERIAL_THRmov al, ahout dx, alpop dxret; String to be sent in DS:SI; SI will point to EOSserial_puts:push ax.serial_puts_next:mov ah, cmp ah, EOSje .serial_puts_donecall serial_putcinc sijmp .serial_puts_next.serial_puts_done:pop axretbut if you don't need the single-character one, this takes only 28 bytes:Code:[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 EOSserial_puts:push axpush dx.serial_puts_next:mov ah, cmp ah, EOSjnz .serial_puts_onepop dxpop axret.serial_puts_one:inc simov dx, SERIAL_LSR.serial_puts_wait:in al, dxtest al, SERIAL_IS_THR_EMPTYjz .serial_puts_waitmov dx, SERIAL_THRmov al, ahout dx, aljmp .serial_puts_nextOften, 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 uselodsbin either, is becauselodsb; mov ah, alandmov ah,  ; inc siare 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).