;; An 16-bit x86 Assembly Example for INT8/IRQ0

	;; Back story:
	;; I was having trouble understanding how could one possibly
	;; implement preemptive multitasking because it involes (somehow)
	;; *stopping* any program that the CPU was working on and forces
	;; it to work on another one. The answer is to use an interrupt -
	;; normally timer interrupt, some utility programs for DOS uses
	;; keyboard interrupt as well. I searched around for a bit and
	;; found most of the examples are either (1) for 32-bit protected
	;; mode or (2) uses C. I want something that's a bit easier for
	;; students to work with because protected mode is a big hassle
	;; to set up and C compilers that compiles to "pure" 16-bit x86
	;; code are basically non-existent (except for maybe Amsterdam
	;; Compiler Kit, but even that is a bit annoying to set up as
	;; well), so I did this.
	
	;; This program:
	;; 1.  setup Programmable Interval Timer (PIT)
	;; 2.  setup INT8 which handles interrupts generated by PIT.
	;;     PIT have multiple (3) channels and channel 0 is connected
	;;     to PIC (which is a chip that arranges interrupts) as
	;;     IRQ0, which translates to INT8.
	;;     If you want sound you can use channel 2 because it's
	;;     connected to the PC Speaker - not the actual speaker you
	;;     are using; it's a beeper that's on (probably) all (at
	;;     least desktop) IBM compatibles. But this example isn't
	;;     about that.
	;; We can setup PIT to generates interrupts at specified interval
	;; which will force the CPU to *abandon* the current task and work
	;; on something else; this can be used to do all sorts of stuff,
	;; including preemptive multitasking stuff by hooking a scheduler at
	;; INT8. In modern webdev terms it's like having a callback that
	;; got called by another party asynchronously and your single
	;; thread JavaScript runtime has to handle that (because single
	;; thread). Or you can see the INT8 handler as (by itself) another
	;; task as well.
	;; NOTE that if you type this example in DEBUG (if for any
	;; reasons you cannot setup a workflow convenient enough for
	;; developing 16-bit programs in assembly), DEBUG uses hex numbers
	;; by default so a INT 21h in MASM/NASM would be INT 21 in DEBUG.

	ORG 100h
	JMP _MAIN
_MAIN:
	;; Setting up the INT8 handler.
	;; In IBM PC (real mode), there's an Interrupt Vector Table at
	;; 0000h:0000h that contains the location for interrupt INT 0~255
	;; in 2 16-bit numbers of "OFFSET-SEGMENT". We can modify the values
	;; in this table to control where the CPU will jump to when there's
	;; an interrupt. By the design of IBM PC the PIT is connected as IRQ0
	;; through the 8259 PIC, which is eventually mapped to INT 8.
	;; Remember that all of this is only for IBM PC; this is only an
	;; IBM PC thing. But non-IBM PC x86 machines are kinda rare nowadays;
	;; almost all x86 PC nowadays are IBM PC compatible.
	;; Make ES:[BX] points to the memory location storing the actual
	;; location for INT 8, which is 0000h:0020h. (How to calculate: 2
	;; 16-bit numbers which is 4 bytes per table entry, this multiplied
	;; by 8 equals 32, which is 20h in hexadecimal.)
	MOV AX, 0
	MOV ES, AX
	MOV AX, 0020h
	MOV BX, AX

	;; Fill in the "OFFSET" part:
	LEA SI, _INT8
	MOV ES:[BX], SI
	
	;; Now here's one of the confusing part. We have notified the
	;; assembler to compile the code into a .COM file by using ORG 100h
	;; in the beginning, when being executed under DOS it'll be loaded
	;; at SOMEWHERE:0100h and the machine state will be CS=DS=SS (if
	;; you want to learn more about this search "Intel Memory Model")
	;; All .COM file is basically compiled using the Tiny model, so we
	;; know the interrupt handler _INT8 will be "in the same CS" as the
	;; main procedure. "Isn't that pretty vulnerable? I mean you can
	;; just modify the content of the memory and all of this will break"
	;; you might say; yeah, Real Mode does not have memory protection
	;; mechanism, that's just the way it is.
	ADD BX, 2
	MOV AX, CS
	MOV ES:[BX], AX
	
	;; Setting up the PIT.
	;; You setup PIT by sending bytes to specified I/O ports.
	;; 	0x40: channel 0 data port (r/w)
	;; 	0x41: channel 1 data port (r/w)
	;; 	0x42: channel 2 data port (r/w)
	;;      0x43: command port (write only)
	;; PIT setup works as follows: you send the command that
	;; configures the channel, you send the *initial value* of the
	;; register for the channel, then PIT will constantly decrease
	;; the value of the register, and when the value reaches zero
	;; something happens. Depending on the configuration something
	;; else might/can happen too. Data ports are for setting or
	;; reading register counts. The registers are 16-bit. So it's
	;; "SOME_CONSTANT_HERE / value_you_put_in_register" Hz instead
	;; of just "value_you_put_in_register" Hz.
	;; The constant is 1193182 (Hz). Some sources say it's 1193180,
	;; But it really does not matter because the number is big. This
	;; allows us to have a frequency of anywhere between a little
	;; bit more than 18 Hz and 596591 Hz (the counter value of 1
	;; is prohibited for various reasons. The available frequencies are
	;; discrete values of course, e.g. you can't have exactly 8000 Hz).
	;; Because we're writing video memory we want to actually *see*
	;; the thing working, we'll take the slowest option available,
	;; with which we'll have to treat the register as 16-bit binary
	;; counter. (PIT has a BCD mode which I can't think of its uses)
	;; 
	;; 	(2-bit) Channel: Channel 0
	;; 	(2-bit) Data port access: Access low and high byte
	;; 	(3-bit) Operating mode: Mode 3 square wave generator mode
	;; 	(1-bit) Counter mode: 16-bit binary
	;;
	;; The interrupt will only be generated when channel 0 output
	;; changes from low to high (rising edge). In a lot of sources
	;; there's this "Interrupt On Terminal Count" mode and you might
	;; think this is the mode we should use. No. In this mode the
	;; output will remain high until the next time we send a value
	;; to the data port, which certainly isn't desirable in our use
	;; case. Normally you'll use mode 2 or mode 3 that will reload
	;; the previously configured value repeatedly.
	;; All of this will gives us the configuring command for 0x43:
	MOV AL, 00110110b	; or MOV AL, 36h
	OUT 43h, AL
	;; And now the frequency:
	MOV AX, 0FFFFh
	OUT 40h, AL
	;; FIX(2021.9.7): You can't just OUT AH (x86 forbids that).
	MOV AL, AH
	OUT 40h, AL

	;; Wait for any keypress.
	MOV AH, 0
	INT 16H
	
	;; Exit the main process.
	;; If all goes well it won't affect the interrupt handling.
	RET
	

	;; The INT8 handler
_INT8:	PUSHA

	;; We won't force the display mode here.
	;; Setting up ES:DI to point to the 80x25 16-color text mode
	;; video memory, which is at 0B800h:0000h.
	MOV AX, 0B800h
	MOV ES, AX
	MOV DI, 0

	;; Now we set up DS:SI to point to the data we want to retrieve.
	;; This will Make sure DS:SI will point to the correct location
	;; (it may contain different value than the current CS when the
	;; CPU reaches here). Don't worry, all of the values is saved
	;; and restored by PUSHA and POPA.
	PUSH CS
	POP DS
	LEA SI, MSG

	;; Setting up the character to be displayed.
	;; This piece of code will cycle through A to Z and wraps to A
	;; again.
	MOV AL, DS:[SI]
	INC AL
	CMP AL, 'Z'
	JNA _SKIPPING
	MOV AL, 'A'
_SKIPPING:	
	MOV DS:[SI], AL

	;; Write the character into video memory (which is the same as
	;; displaying the character)
	;; This code assumes that we're in 80x25 16-color text mode, and
	;; `MOV BH, 0FH` means to set the written character's color as
	;; "bright white foreground and black background".
_DISP:	MOV BL, DS:[SI]
	MOV BH, 0FH
	MOV ES:[DI], BX

	;; Sending End Of Interrupt signal to 8259 PIC
	;; This is to notify 8259 the interrupt handling is over and it's
	;; allowed to send more interrupt signal now.
	MOV AL, 20H
	OUT 20H, AL

	;; Now we restore the registers.
	POPA
	
	;; Setting Interrupt Enable flag
	;; This flag will be cleared when executing the interrupt handler
	;; so that the interrupt itself won't be interrupted, but we have
	;; to set it manually so that CPU will acknowledge other interrupts
	;; after the handling, or else nothing will display on the screen
	;; ever again (unless some code that directly writes the video
	;; memory got executed) and keyboard won't work because it requires
	;; interrupt to be enabled.
	STI

	;; You need to use IRET inside interrupt handler - it does things
	;; a little bit different than normal RET. Also a good indicator
	;; that this part of the program is probably not called by normal
	;; means.
	IRET

	;; The character that got displayed
	MSG DB 'A'

	;; This piece of code should work with most assemblers (with tiny
	;; tweaks on the syntax and assembler directives). Tested under
	;; DOSBOX.

	;; 2021.8.30

	;; Back