{"id":226,"date":"2015-05-24T20:33:52","date_gmt":"2015-05-24T19:33:52","guid":{"rendered":"http:\/\/www.benningtons.net\/?p=226"},"modified":"2015-06-01T00:38:33","modified_gmt":"2015-05-31T23:38:33","slug":"bare-metal-os","status":"publish","type":"post","link":"https:\/\/www.benningtons.net\/index.php\/bare-metal-os\/","title":{"rendered":"Bare Metal OS"},"content":{"rendered":"<p>OK this may not count as bare metal programming if I&#8217;m relying on the BIOS firmware for keyboard input and video output but it&#8217;s still pretty low level stuff. This is an operating system (<b>fernOS<\/b>) written in assembly language for Intel x86 based computers.<br \/>\nI may also be pushing the definition of &#8216;operating system&#8217; but <b>fernOS<\/b> will boot from power-on and it does present the user with some control of the computer hardware.<br \/>\n<nl><br \/>\nBefore I go any further though I must say thanks to Mike Saunders for his machine code tutorials in LXF (in 2012) and more recently in the brilliant <a href=\"http:\/\/www.linuxvoice.com\/\" title=\"http:\/\/www.linuxvoice.com\/\" target=\"_blank\">Linux Voice<\/a> magazine.<br \/>\nBack in my youth I dabbled with some 8-bit Z80 machine code but as CPUs became more complex I gave up on such low level programming because it felt like it was becoming inaccessible to the general hobbyist. So thanks to Mike for introducing me to <a href=\"https:\/\/en.wikipedia.org\/wiki\/Netwide_Assembler\" title=\"https:\/\/en.wikipedia.org\/wiki\/Netwide_Assembler\" target=\"_blank\">nasm<\/a> and re-introducing me to opcodes and operands.<br \/>\n<nl><br \/>\nThinking about it, I also gave-up on electronics around the same time. Thinking surface mount technology would make the subject too complex for the common man. How wrong I was.<br \/>\n<nl><br \/>\n<b>Bootloader:<\/b><br \/>\n<nl><\/p>\n<p style=\"padding-left: 30px;\">When a PC is powered-up it loads a 512 byte bootloader block from the boot device (that&#8217;s the device as selected in the BIOS boot sequence). That small bootloader then loads the operating system from the boot device into memory before passing control on to it.<br \/>\n<nl><br \/>\ni.e. The operating system pulls itself up by its bootstraps!<br \/>\n<nl><br \/>\nI&#8217;m over-simplifying but that is basically what happens. <b>fernOS<\/b> however is less than 512 bytes (509 actually) so, for now, I have it entirely residing within the bootloader block. Here is the main code.<br \/>\n<nl><\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n;********************************************************************\r\n;*\t\t\t\t\t\t\t\tfernOS\t\t\t\t\t\t\t\t*\r\n;*\tAn assembly language based operating system that will be \t\t*\r\n;*\twritten, and no doubt re-written, as I learn.\t\t\t\t\t*\r\n;*\t\t\t\t\t\twww.benningtons.net\t\t\t\t\t\t\t*\r\n;*\twith grateful thanks to the tutorials by Mike Saunders in LXF\t*\r\n;*\t\t\tand the far better Linux Voice magazines.\t\t\t\t*\r\n;********************************************************************\r\n\r\n; Compiler settings and parameters\r\n\tBITS 16\t\t\t\t\t\t;Set nasm to 16-bit mode - required for bootloaders\r\n\tASCII_NL\tequ 0\t\t\t;null\r\n\tASCII_BS\tequ 8\t\t\t;back space\r\n\tASCII_LF\tequ 10\t\t\t;line feed\r\n\tASCII_CR\tequ 13\t\t\t;carriage return\r\n\tASCII_SP\tequ 32\t\t\t;space\r\n\tASCII_DQ\tequ 34\t\t\t;double quotes\r\n\r\nSection .text\t\t\t\t\t;protected code area\r\n\r\nfernOS:\t\t\t\t\t\t\t;main console service\r\n\r\n;fernOS memory map:\t\t\t\t;TODO: need to better understand these memory settings\r\n;\tstart\tend\t\tsize\r\n;\t0\t\t07BF\t31k\t\t\t\t\t\t\treserved\r\n;\t07C0\t08DF\t4k + 512b\tds:si, es:di\tcode + data space\r\n;\t08E0\t09FF\t4k + 512b\tss:sp\t\t\tstack space\r\n;\t\r\n\tmov\t\tax, 07C0h\t\t\t;x86 memory segmentation: data space\r\n\tmov \tds, ax\t\t\t\t;can't load directly into ds\r\n\t\r\n\tmov\t\tax, 08E0h\t\t\t;x86 memory segmentation: stack space\r\n\tmov\t\tss, ax\r\n\tmov\t\tsp, 1000h\t\t\t;stack works backwards from 4k limit into stack space\r\n\r\n\tmov\t\tax, ds\t\t\t\t;x86 memory segmentation: extra space\r\n\tmov \tes, ax\r\n\r\n.fernOS_help:\t\t\t\t\t;HELP command jumps to here to redisplay welcome message\r\n\tmov\t\tsi, bootmsg\t\t\t;output boot message to screen\r\n\tcall\tBIOS_put_string\r\n\r\n.next_prompt:\r\n\tmov \tsi, prompt\r\n\tcall\tBIOS_put_string\t\t;output next prompt\r\n\tmov \tdi, inputbuf\t\t;reset di ready for next command\r\n\tmov \tword [inpoint], inputbuf\t;store pointer to start of input buffer\r\n\r\n.console:\t\t\t\t\t\t;inner loop to allow keyboard command line input\r\n\tcall\tBIOS_get_keystroke\t;check keyboard for input. Out=al\r\n\r\n\tcmp\t\tal, ASCII_SP\r\n\tjge\t\t.char_input\t\t\t;printable character input\r\n\tcmp\t\tal, ASCII_CR\r\n\tje\t\t.carriage_ret\t\t;return key so goto next line and redisplay prompt\r\n\tcmp\t\tal, ASCII_BS\r\n\tje\t\t.back_space\t\t\t;back space key so delete previous char\r\n\tjmp\t\t.console\t\t\t;no valid input so try again\r\n\r\n.char_input:\r\n\tcall\tBIOS_put_char\t\t;output character (al) to screen\r\n\tcall\tUTIL_upper\t\t\t;convert any lowercase letters to uppercase (fernOS is case insensitive)\r\n\tstosb\t\t\t\t\t\t;store ax into [di] and di++\r\n\tcmp\t\tdi, inpoint\t\t\t;input buffer overflow?\r\n\tjl\t\t.console\t\t\t;look for next input\r\n\tdec\t\tdi\r\n\tmov\t\tal, ASCII_NL\r\n\tstosb\t\t\t\t\t\t;reverse one and mark end of input with a null\r\n\tjmp\t\t.syntax_error\r\n\r\n.back_space:\r\n\tcmp\t\tdi, inputbuf\r\n\tje\t\t.console\t\t\t;no text input so ignore\r\n\tmov \tsi, bs_str\r\n\tcall \tBIOS_put_string\t\t;backspace and erase previous char\r\n\tdec\t\tdi\t\t\t\t\t;clear last char input from buffer\r\n\tjmp\t\t.console\r\n\r\n.carriage_ret:\r\n\tcmp\t\tdi, inputbuf\r\n\tje\t\t.next_prompt\t\t;no text input so ignore and display next prompt\r\n\tmov\t\tal, ASCII_NL\r\n\tstosb\t\t\t\t\t\t;mark end of input string with a null\r\n\r\n.any_more:\r\n\tmov \tsi, cmdlist\t\t\t;start at beginning of the list of commands.\r\n\tmov\t\tbx, 0\t\t\t\t;count commands checked\r\n\r\n.parse_cmds:\t\t\t\t\t;check each command against our input string\r\n\tmov \tdi, [inpoint]\t\t;set pointer to the start of our entered command\r\n.pc1:\r\n\tcmp\t\tbyte [di], ASCII_NL\r\n\tje\t\t.next_prompt\t\t;no more so display next prompt\r\n\tcmp\t\tbyte [di], ASCII_SP\t;skip any spaces between commands\r\n\tjne\t\t.pc2\r\n\tinc\t\tdi\r\n\tjmp\t\t.pc1\r\n.pc2:\r\n\tcall\tUTIL_str_cmp\r\n\tcmp\t\tax, 0\r\n\tje\t\t.cmd_match\t\t\t;matched a valid command\r\n\r\n.next_cmd:\t\t\t\t\t\t;not a match so move si to start of the next command\r\n\tcmp\t\tbyte [si], ASCII_NL\r\n\tje\t\t.find_next_cmd\r\n\tinc\t\tsi\r\n\tjmp\t\t.next_cmd\r\n\r\n.find_next_cmd:\r\n\tinc\t\tbx\r\n\tinc\t\tsi\r\n\tcmp \tsi, synterr\t\t\t;check location of the string after the command list\r\n\tjge \t.syntax_error\t\t;gone beyond list of commands so invalid command entered\r\n\tjmp\t\t.parse_cmds\t\t\t;else try again, si will be pointing to the start of next command\r\n\r\n.cmd_match:\t\t\t\t\t\t;rough output of selected command to check if in right place\r\n\tmov \tword [inpoint], di\t;store pointer to where we are in input buffer\r\n\r\n\tcmp\t\tbx,1\r\n\tje\t\t.fernOS_help\r\n\tcmp\t\tbx,2\r\n\tje\t\t.fernOS_say\r\n\tcmp\t\tbx,3\r\n\tje\t\t.fernOS_wait\r\n\tcmp\t\tbx,0\r\n\tjne\t\t.syntax_error\r\n\tcall\tBIOS_boot\r\n\r\n.syntax_error:\r\n\tmov\t\tsi, synterr\t\t\t;display a syntax error message\r\n\tcall \tBIOS_put_string\r\n\tmov \tsi, [inpoint]\r\n\tcall \tBIOS_put_string\t\t;followed by a repeat of the command that was typed\r\n\tjmp\t\t.next_prompt\r\n\r\n\r\n.fernOS_say:\t\t\t\t\t\t;SAY&quot;HELLO&quot; = output HELLO to the screen\r\n\tmov\t\tsi, nl_str\r\n\tcall \tBIOS_put_string\r\n\tcmp\t\tbyte [di], ASCII_DQ\t\t;error if not using quotes\r\n\tjne\t\t.syntax_error\r\n.fs1:\r\n\tinc\t\tdi\t\t\t\t\t\t;skip 1st set of quotes\r\n\tcmp\t\tbyte [di], ASCII_DQ\r\n\tje\t\t.fs2\t\t\t\t\t;look through until reaching 2nd set of quotes\r\n\tcmp\t\tbyte [di], ASCII_NL\r\n\tje\t\t.syntax_error\t\t\t;reached the end rather than quotes so error\r\n\tmov\t\tax, [di]\r\n\tcall \tBIOS_put_char\t\t\t;output each character between the quotes\r\n\tjmp\t\t.fs1\r\n.fs2:\r\n\tinc\t\tdi\t\t\t\t\t\t;skip 2nd set of quotes\r\n\tmov \tword [inpoint], di\t\t;store pointer to where we are in input buffer\r\n\tjmp\t\t.any_more\r\n\r\n\r\n.fernOS_wait:\t\t\t\t\t\t;WAITn = pause n seconds (roughly)\r\n\tmov\t\tbl, [di]\t\t\t\t;check how long to wait\r\n\tsub\t\tbl, '0'\t\t\t\t\t;convert char input to int\r\n\tcmp\t\tbl, 0\t\t\t\t\t;if no valid wait time then skip to next command\r\n\tjle\t\t.any_more\r\n\tcmp\t\tbl, 9\t\t\t\t\t;if no valid wait time then skip to next command\r\n\tjg\t\t.any_more\r\n\tinc\t\tdi\t\t\t\t\t\t;move past wait time\r\n\tmov \tword [inpoint], di\t\t;store pointer to where we are in input buffer\r\n\tmov\t\tbh, 0\r\n.fw1:\r\n\tcall\tBIOS_get_time\r\n\tmov\t\tax, dx\t\t\t\t\t;check changes to lower tics\r\n\tmov\t\tdx, 0\t\t\t\t\t;ignore upper set of tics\r\n\tmov\t\tcx, 10\r\n\tdiv\t\tcx\t\t\t\t\t\t;remainder goes to dx (the lowest digit of system time)\r\n\tcmp\t\tdx, bx\t\t\t\t\t;so count every 10 tics (roughly)\r\n\tjne\t\t.fw1\r\n\tdec\t\tbx\r\n\tcmp\t\tbx, 0\r\n\tjg\t\t.fw1\r\n\tjmp\t\t.any_more\r\n\r\n\r\n;Welcome splash and prompt\r\n\tbootmsg\tdb ASCII_LF, ASCII_CR, &quot;fernOS v0.0.3 x86 version&quot;, ASCII_LF, ASCII_CR\r\n\t\t\tdb &quot;======&quot;, ASCII_LF, ASCII_CR\r\n\t\t\tdb &quot;boot&quot;, ASCII_LF, ASCII_CR\r\n\t\t\tdb &quot;help&quot;, ASCII_LF, ASCII_CR\r\n\t\t\tdb &quot;say&quot;, ASCII_DQ, &quot;hi&quot;, ASCII_DQ, &quot; = display hi to screen&quot;, ASCII_LF, ASCII_CR\r\n\t\t\tdb &quot;waitn = pause n seconds&quot;, ASCII_LF, ASCII_CR, ASCII_NL\r\n\tprompt\tdb ASCII_LF, ASCII_CR, &quot;fernOS&gt;&quot;, ASCII_NL\r\n;Commands:\t[relies on previous null and being followed by synterr]\r\n\tcmdlist\tdb &quot;BOOT&quot;, ASCII_NL, &quot;HELP&quot;, ASCII_NL, &quot;SAY&quot;, ASCII_NL, &quot;WAIT&quot;, ASCII_NL\r\n;Error messages:\r\n\tsynterr db ASCII_LF, ASCII_CR, &quot;You wot? &quot;, ASCII_NL\r\n;Backspace delete sequence:\r\n\tbs_str\tdb ASCII_BS, ASCII_SP, ASCII_BS, ASCII_NL\r\n;new line sequence:\r\n\tnl_str\tdb ASCII_LF, ASCII_CR, ASCII_NL\r\n\r\n\t%include &quot;fernOS_BIOS_library.asm&quot;\r\n\t%include &quot;fernOS_UTIL_library.asm&quot;\r\n\r\nsection .bss\t\t\t\t\t;changeable data section\r\n\tstringbuf\tresb 4\t\t\t;general purpose\r\n\tinputbuf\tresb 80\t\t\t;command line input\r\n\tinpoint\t\tresb 2\t\t\t;pointer to input buffer\r\n\r\nsection .text\t\t\t\t\t;protected code section\r\n\ttimes\t510-($-$$) db 0\t\t;pad remainder of 512 bootloader with nulls\r\n\tdw\t\t0AA55h\t\t\t\t;valid boot block signature\r\n<\/pre>\n<p><nl><\/p>\n<p style=\"padding-left: 30px;\">Unfortunately my WordPress syntax highlighter doesn&#8217;t include machine code in its repertoire so this will look quite plain. I&#8217;ve also avoided looking at online code examples, thus far, to encourage myself to learn rather than copy. So I&#8217;m probably not laying out my code correctly, my inefficient use of registries is probably shocking and I&#8217;ll definitely be using sub-optimal methods. But hey, it works and I&#8217;m sure it will improve!<br \/>\n<nl><br \/>\nHopefully the in code comments give a clue to what the code does but the main &#8216;sections&#8217; are:<br \/>\n.console\t   Where keyboard input is echoed to the screen until the return\/enter key is pressed.<br \/>\n.parse_cmds    Looks through the input commands and calls the requested functions.<br \/>\nThose commands being: BOOT, HELP, SAY and WAIT (well there is only 512 bytes to play with!)<br \/>\n<nl><\/p>\n<p><b>BIOS utilities:<\/b><br \/>\n<nl><\/p>\n<p style=\"padding-left: 30px;\">\nI&#8217;ve kept all the BIOS interrupts in a separate file fernOS_BIOS_library.asm, which you&#8217;ll see included towards the end of the main <b>fernOS<\/b> code above.<br \/>\n<nl><\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n;********************************************************************\r\n;*\t\t\t\t\tfernOS_BIOS_library\t\t\t\t\t\t\t\t*\r\n;*\tx86 assembly code BIOS calling utilities for fernOS\t\t \t\t*\r\n;*\t\t\t\t\twww.benningtons.net\t\t\t\t\t\t\t\t*\r\n;********************************************************************\r\n;\r\n;INTERRUPTS USED:\r\n;\tInt\t\tAH\t\tAL\t\tDescription\r\n;\t10\t\t0E\t\tASCII\tVideo Teletype Output\r\n;\t16\t\t00\t\tASCII\tKeyboard Get Keystroke\r\n;\t1A\t\t00\t\tflag\tGet system time\r\n;\t19\t\t\t\t\t\tSystem bootstrap loader\r\n;\r\n;LIST OF FUNCTIONS:\r\n;\tBIOS_boot\t\t\t\tBoot the PC\r\n;\tBIOS_get_keystroke\t\tGet keystroke from keyboard\r\n;\tBIOS_get_time\t\t\tGet system time\r\n;\tBIOS_put_char\t\t\tOutput character to screen\r\n;\tBIOS_put_string\t\t\tOutput string to screen\r\n\r\n\r\n;***************************************Boot the PC\r\n;In: none\r\n;Out: none\r\n;Destroyed: none\r\n\r\nBIOS_boot:\r\n\r\n\tint\t\t19h\t\t\t;call bios bootstrap loader\r\n\tret\r\n\r\n;***************************************Get keystroke from keyboard\r\n;In: none\r\n;Out: al=ascii character (default null)\r\n;Destroyed: ah\r\n\r\nBIOS_get_keystroke:\r\n\r\n\tmov\t\tah, 00h\t\t;set upper byte of ax ready for get_keystoke bios call\r\n\tmov\t\tal, 00h\t\t;set lower byte to null default\t\t#TODO do I need to initialise?\r\n\tint\t\t16h\t\t\t;call bios to get keystroke\r\n\tret\r\n\r\n;***************************************Get system time\r\n;In: none\r\n;Out: cx:dx = number of clock ticks since midnight, al=midnight flag\r\n;Destroyed: ah\r\n\r\nBIOS_get_time:\r\n\r\n\tmov\t\tah, 00h\t\t;set upper byte of ax ready for bios call\r\n\tint\t\t1Ah\t\t\t;call bios to get system time\r\n\tret\r\n\r\n;***************************************Output character to screen\r\n;In: al=ascii character to output\r\n;Out: none\r\n;Destroyed: ah\r\n\r\nBIOS_put_char:\r\n\r\n\tmov\t\tah, 0Eh\t\t;set upper byte of ax ready for teletype_output bios call\r\n\tint\t\t10h\t\t\t;call bios to output ascii char in al to screen\r\n\tret\r\n\r\n;***************************************Output string to screen\r\n;In: si pointer to a null terminated string\r\n;Out: none\r\n;Destroyed: ax\r\n\r\nBIOS_put_string:\r\n\tmov\t\tah, 0Eh\t\t;set upper byte of ax ready for bios call\r\n\t\r\n.loop:\r\n\tlodsb\t\t\t\t;step through string, incrementing si and loading char into al\r\n\tcmp\t\tal, 0\t\t;test lower byte of ax\r\n\tje\t\t.end\t\t;reached null yet?\r\n\tint\t\t10h\t\t\t;call bios to output char\r\n\tjmp\t\t.loop\t\t;next char\r\n\t\r\n.end:\r\n\tret\r\n<\/pre>\n<p><nl><\/p>\n<p style=\"padding-left: 30px;\">The BIOS interrupt codes are a bit of a black art. I picked up the output_char (10h) from Mike&#8217;s tutorial and the others were a case of trial and error based on the list in this <a href=\"http:\/\/www.ctyme.com\/intr\/int.htm\" title=\"http:\/\/www.ctyme.com\/intr\/int.htm\" target=\"_blank\">BIOS Interrupt Jump Table<\/a><br \/>\n<nl><\/p>\n<p><b>Other utilities:<\/b><br \/>\n<nl><\/p>\n<p style=\"padding-left: 30px;\">\nOne of the joys (some would say pains) of machine code is that if you want to do something then you have to write it yourself. Higher level languages like C or Python have vast pre-written libraries available to do complex tasks for you. They help make the coding quick and the results impressive but at what cost? Are those library routines as efficient as they could be and aren&#8217;t they keeping you from learning about what&#8217;s really happening?<br \/>\nOn the other-hand many will consider programming at such a low level and having to write every minor function a bit too masochistic?<br \/>\n<nl><br \/>\nAnyhow, here are the other utilities that I&#8217;ve used in <b>fernOS<\/b>.<br \/>\n<nl><\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n;********************************************************************\r\n;*\t\t\t\t\tfernOS_UTIL_library\t\t\t\t\t\t\t\t*\r\n;*\tx86 assembly code general utilities for fernOS\t\t\t \t\t*\r\n;*\t\t\t\t\twww.benningtons.net\t\t\t\t\t\t\t\t*\r\n;********************************************************************\r\n;\r\n;LIST OF FUNCTIONS:\r\n;\tUTIL_num_to_str\t\tConvert a number into an ASCII string\r\n;\tUTIL_str_cmp\t\tCompare two strings\r\n;\tUTIL_upper\t\t\tConvert a char to uppercase\r\n\r\n\r\n;***************************************Convert a number into an ASCII string\r\n;In: ax=number to convert, di=pointer to output string\r\n;Out: [di] memory\r\n;Destroyed: none\r\n\r\n;UTIL_num_to_str:\r\n;\tpusha\t\t\t\t\t;preserve registers\r\n;\tmov\t\tbx, 10\t\t\t;Base of the decimal system\r\n;\tmov\t\tcx, 0\t\t\t;Number of digits generated\r\n\r\n;.divloop:\r\n;  \tmov\t\tdx, 0\t\t\t;will divide dx:ax so initialise and the remainder will be stored in dx\r\n;\tdiv\t\tbx\t\t\t\t;Divide ax by the number-base\r\n;\tpush\tdx\t\t\t\t;Save remainder on the stack\r\n;\tinc\t\tcx\t\t\t\t;And count this remainder\r\n;\tcmp\t\tax, 0\t\t\t;Was the quotient zero?\r\n;\tjne\t\t.divloop\t\t;No, do another division\r\n\r\n;.outloop:\r\n;\tpop\t\tax\t\t\t\t;Else pop recent remainder\r\n;\tadd\t\tal, '0'\t\t\t;And convert to a numeral\r\n;\tstosb\t\t\t\t\t;Store to memory-buffer\r\n;\tloop\t.outloop\t\t;Again for other remainders [if(--cx.ne.0) goto .outloop]\r\n\r\n;\tmov\t\tal, ASCII_NL\t;null terminate string\r\n;\tstosb\r\n\r\n;\tpopa\t\t\t\t\t;recover registers\r\n;\tret\r\n\r\n\r\n;***************************************Compare two strings\r\n;In: si=pointer to string1, di=pointer to string2\r\n;Out: ax=-1 (s1&lt;s2), ax=0 (match), ax=1 (s1&gt;s2)\r\n;Destroyed: (si-1) and di left pointing to 1st mismatch or end of strings\r\n\r\nUTIL_str_cmp:\r\n\t\r\n.loop:\r\n\tlodsb\t\t\t\t\t\t;move [si] into ax and inc si\r\n\tcmp\t\tal, ASCII_NL\t\t;reached end of s1 and still matching so a match (even though there may be more to s2\r\n\tje\t\t.a_match\r\n\tcmp \tal, [di]\t\t\t;else compare char from each string\r\n\tjl\t\t.less_than\r\n\tjg\t\t.greater_than\r\n\tinc\t\tdi\t\t\t\t\t;test next char\r\n\tjmp\t\t.loop\r\n\t\r\n.less_than:\r\n\tmov\t\tax, -1\r\n\tret\r\n\r\n.greater_than:\r\n\tmov\t\tax, 1\r\n\tret\r\n\r\n.a_match:\r\n\tmov\t\tax, 0\t\t\t\t;actually ax is already ascii null\r\n\tret\r\n\r\n\r\n;***************************************Convert a char to uppercase\r\n;In: al=ASCII char to convert\r\n;Out: al=uppercase ASCII char\r\n;Destroyed: none\r\n\r\nUTIL_upper:\r\n\tcmp\t\tal, 'a'\r\n\tjl\t\t.end\t\t\t;below ASCII 'a' so no conversion applied\r\n\tcmp\t\tal, 'z'\r\n\tjg\t\t.end\t\t\t;above ASCII 'z' so no conversion applied\r\n\tsub\t\tal, 32\t\t\t;convert a lowercase letter to uppercase\r\n.end:\r\n\tret\r\n<\/pre>\n<p><nl><\/p>\n<p style=\"padding-left: 30px;\">You&#8217;ll notice the first function UTIL_num_to_str is commented out. It&#8217;s no longer used by <b>fernOS<\/b> and there&#8217;s no room left for it in this 512 byte OS. But it was useful as a debugging tool during development so I&#8217;ve kept the code available.<br \/>\n<nl><\/p>\n<p><b>Prepare your boot media:<\/b><br \/>\n<nl><\/p>\n<p style=\"padding-left: 30px;\">To compile <b>fernOS<\/b> the file containing the main program (fernOS.asm) must be in the same folder as the utilities fernOS_BIOS_library.asm and fernOS_UTIL_library.asm. From within that folder you can compile <b>fernOS<\/b> and generate a binary executable image with:<br \/>\n<nl><\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nnasm -f bin -w+all -o fernOS.bin fernOS.asm\r\n<\/pre>\n<p><nl><\/p>\n<p style=\"padding-left: 30px;\">Assuming there were no error messages, you then need to create a blank floppy disk image (1440k in size) and write fernOS.bin to the start of it. First making sure to remove any existing image files.<br \/>\n<nl><\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\nrm fernOS.img\t\t\t\t\t\t\t\t\t#remove previous image\r\nmkdosfs -C fernOS.img 1440\t\t\t\t\t\t#Create a new disk image\r\ndd if=fernOS.bin of=fernOS.img conv=notrunc\t\t#Transfer fernOS to disk image\r\n<\/pre>\n<p><nl><\/p>\n<p style=\"padding-left: 30px;\">You then need to put that image onto your chosen boot media. Mike&#8217;s tutorial used the QEMU emulator directed to boot from the image file. I started using floppy disks but was surprised at how many of my old stock were now generating errors so I then switched to using a memory stick (please note that the whole memory stick will be erased, even though the boot block is only 512 bytes!).<br \/>\nHere are the <b>dd<\/b> commands for writing to floppy (commented out) and to a memory stick on device sdb (make doubly doubly sure you use the right device name for your setup!). Plug in the memory stick and then type <b>dmesg<\/b>. At the end of the display you&#8217;ll see something like:<br \/>\n<b>[31383.723577] sd 4:0:0:0: [sdb] Attached SCSI removable disk<\/b><br \/>\nWhich confirms mine is on sdb, but your&#8217;s may be on sdc or another device, so carefully check OR RISK WIPING THE WRONG DEVICE!<br \/>\n<nl><\/p>\n<pre class=\"brush: bash; title: ; notranslate\" title=\"\">\r\n#dd if=fernOS.img of=\/dev\/fd0\t\t#Format floppy disk with new image\r\ndd if=fernOS.img of=\/dev\/sdb\t\t#Format USB memory stick with new image\r\n<\/pre>\n<p><nl><\/p>\n<p><b>Using fernOS:<\/b><br \/>\n<nl><\/p>\n<p style=\"padding-left: 30px;\">Make sure that your BIOS boot sequence will try your <b>fernOS<\/b> boot media before your hard disk, have your boot media plugged in and power-up.<br \/>\nIn the race to reach a command prompt we&#8217;re already better than Windows and even Linux! OK, they&#8217;ll beat <b>fernOS<\/b> on everything else (except size) but the time to boot-up is impressive and shows what can be achieved with assembly language.<br \/>\nThe welcome display lists the available commands, they can be repeated by the HELP command. The data needed to do that is a waste of precious code space but it will suffice until I learn how to load such data from a separate file outside of the 512 byte boot block.<br \/>\n<nl><br \/>\nNow for our first command in, er&#8230;, <b>fernScript<\/b>:<br \/>\n<nl><\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nfernOS&gt;say&quot;hello world&quot;\r\nHELLO WORLD\r\n<\/pre>\n<p><nl><\/p>\n<p style=\"padding-left: 30px;\">And our first sequence of commands:<br \/>\n<nl><\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nfernOS&gt;say&quot;this system will reboot in 9 seconds&quot; wait4 say&quot;goodbye&quot; wait5  boot\r\nTHIS SYSTEM WILL REBOOT IN 9 SECONDS\r\n&lt;pause...&gt;\r\nGOODBYE\r\n&lt;pause...&gt;\r\n&lt;fernOS will then pass control to the bootloader on your hard disk&gt;\r\n<\/pre>\n<\/p>\n<p><nl><br \/>\nWell it doesn&#8217;t do much but it quickly boots to a console prompt and allows you to interact with the computer via a set of commands. Some could say its a bare metal, single threaded, monolithic kernel operating system. I&#8217;d say its not bad for 509 bytes and as I learn it will hopefully improve.<br \/>\n<\/p>\n","protected":false},"excerpt":{"rendered":"<p>OK this may not count as bare metal programming if I&#8217;m relying on the BIOS firmware for keyboard input and video output but it&#8217;s still pretty low level stuff. This is an operating system (fernOS) written in assembly language for Intel x86 based computers. I may also be pushing the definition of &#8216;operating system&#8217; but [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[9],"tags":[25,24,26],"_links":{"self":[{"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/posts\/226"}],"collection":[{"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/comments?post=226"}],"version-history":[{"count":9,"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/posts\/226\/revisions"}],"predecessor-version":[{"id":237,"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/posts\/226\/revisions\/237"}],"wp:attachment":[{"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/media?parent=226"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/categories?post=226"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.benningtons.net\/index.php\/wp-json\/wp\/v2\/tags?post=226"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}