BIOS Video Service INT 10H (functions 9 and 13H) functionality On the way to protected or long modes, real mode code needs to provide a lot of visual feedback. Video services INT 10H provide this functionality, but lack fluidity. This algorithm provides write character and attribute AH = 9 and write string AH = 13H functionality, reading NULL terminated strings.

ENTER

;    BH = Video page
;    BL = Attribute 
;    CX = Repeat count or NULL to read string to EOS
;    DH = Row    (0 - 24)
;    DL = Column (0 - 79)
; DS:SI = Pointer to ASCIIZ string or character to be repeated CX times


LEAVE

;    CX = Number of characters displayed
; ES:SI = Points to the next position after null. Might be next string


To achieve a result like this:



The string would be defined as:


  Title:    db  'Proto_Sys 1.00.0', 0, 0xc4


and called by:

mov     si, Title
mov     dx, 0x11e       Line 2, Column 32 (values zero indexed)
mov     bx, 13          Light Magenta
xor     cx, cx
call    528H
mov     cl, 18          Repeat 18 times
mov     bl, 15          Bright White
add     dx, 255         Row 3, Column 30
call    528H


NOTE: As this is in a boot loader, there is a lot of code before this that initializes pertinent registers.

Begin with setting ES:DI to point to proper place in video memory B8000 - BFFFF:

528  31C0      xor  ax, ax
52A  88FC      mov  ah, bh
52C  80C4B8    add  ah, VIDEO_SEG
52F  06        push     es
530  8EC0      mov  es, ax
532  88D4      mov  ah, dl
534  C1E807    shr  ax, 7
537  57        push     di
538  89C7      mov  di, ax
53A  B0A0      mov  al, 0xa0
53C  52        push     dx
53D  C1EA08    shr  dx, 8
540  F7EA      imul     dx
542  5A        pop  dx
543  01C7      add  di, ax


Read the first character and then determine if this character is to be repeated CX times or the first of a NULL-terminated string:

547  AC        lodsb
548  09C9      or   cx, cx
54A  7407      jz   553H


Do repeat process and return to caller:

54C  51        push     cx
54D  F3AB      rep  stosw
54F  59        pop  cx

; see comments restored out of order
; -------------------------------
; 550  07        pop    es   [di] 5F
; 551  5F        pop    di   [es] 07
; -------------------------------

552  C3        ret


Keep reading until null and update CL as it returns the length of the string:

553  08C0      or     al, al
555  74F9      jz     550H
557  FEC1      inc    cl
559  AB        stosw
55A  AC        lodsb
55B  EBF6      jmp     553H
Some corrections


The LEAVE comments mention ES:SI where you intended DS:SI
If the comment for mov dx, 0x11e talks about "Line 2", then it should also say "Column 31" (in stead of "32")





add     dx, 255         Row 3, Column 30



This is a very nice approach! Although for reviewing purposes I had to read it a few times over, it is definitely better here than the simpler mov dx, 0x021d would have been.



Optimizations


When registers need to be preserved, it will be better to group the pushes and pops together. You've put them amongst the rest of the instructions and maybe because of this introduced the error of inverse restoration. A trick I've been using for many years now is numbering my push and pop instructions (see below). This facilitates enormously discovering these errors.

push    es            ;(1)
push    di            ;(2)

The calculation of the ES segment register can be shorter and clearer.

mov     ax, VIDEO_SEG ;0xB800
add     ah, bh        ;BH=Display page
mov     es, ax

The calculation for the video address in DI can be optimized a lot. Next code does it in 14 bytes, where your code used 18 bytes. Also it uses only half the instructions.

movzx   ax, dh        ;DH=Row
imul    ax, 80        ;80 characters per row
add     al, dl        ;DL=Column
adc     ah, 0         ;(*)
imul    di, ax, 2     ;Double for character+attribute AND bring in DI


(*) If you know that the counter in CX will always be reasonably small, then maybe write adc ah,ch here and shave off another byte.
In order to decide what the function should do you test the CX register using or cx, cx jz 553H. This can easily be shortened to jcxz 553H. A 2 byte gain.
Incrementing the resulting counter in CX will be shorter using inc cx because that's a 1-byte instruction.
The loop currently uses 2 jump instructions per iteration. This is wasteful in terms of speed and can easily be avoided as you'll see in below summary.


In summary

  push    es            ;(1)
  push    di            ;(2)
  mov     ax, VIDEO_SEG ;0xB800
  add     ah, bh        ;BH=Display page
  mov     es, ax
  movzx   ax, dh        ;DH=Row
  imul    ax, 80        ;80 characters per row
  add     al, dl        ;DL=Column
  adc     ah, 0         ;(*) Possibly use "adc ah, ch" with CH=0
  imul    di, ax, 2     ;Double for character+attribute AND bring in DI
  lodsb
  jcxz    Test          ;Till end of string marker
Char:
  push    cx            ;(3)
  rep     stosw
  pop     cx            ;(3)
  jmp     Exit
String:
  inc     cx
  stosw
  lodsb
Test:
  test    al, al
  jnz     String
Exit:
  pop     di            ;(2)
  pop     es            ;(1)
  ret