;Copyright (c) 2009, 2010 Max Reitz ; ;Permission is hereby granted, free of charge, to any person obtaining a ;copy of this software and associated documentation files (the "Software"), ;to deal in the Software without restriction, including without limitation ;the rights to use, copy, modify, merge, publish, distribute, sublicense, ;and/or sell copies of the Software, and to permit persons to whom the ;Software is furnished to do so, subject to the following conditions: ; ;The above copyright notice and this permission notice shall be included in ;all copies or substantial portions of the Software. ; ;THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ;IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ;FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL ;THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ;LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ;FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ;DEALINGS IN THE SOFTWARE. ;These variables help you to minimize the size of this executable. The default ;level is of course optimized a lot, but it's nice to look at and so on. By ;removing code the output gets "uglier", but still working. ;You may choose one of the following levels by setting Os_level to the ;corresponding value ;No optimization (0): Everything included ;First optimization level: Use it to suppress strings such as the signifant or ;the IP prompt. The newline at the end of the output is also removed. ;Second optimization level: You won't see your own MAC address and the MAC ;address to be searched won't contain colons as seperators (it simply will be a ;huge number, i. e. 52550A000202 instead of 52:55:0A:00:02:02). Exclamation ;marks in case of an error will be suppressed. ;Third optimization level: You may not input a decimal IP but a hexadecimal. ;It consists out of eight hexadecimal digits (each between '0' and '9' or 'a' ;and 'f' (NOT between 'A' and 'F'!)). You mustn't press enter when you've ;finished, a line break will be issued when eight digits have been input ;telling you the IP has been accepted. ;An example for such an IP would be "0a00022a" which is equal to 10.0.2.42 ;Fourth optimization level: The IP is hardcoded, you are unable to input it ;"on-the-fly". Furthermore, the check for PCI BIOS extensions being available ;is disabled because it should be available everywhere today. Os_level = 0 if Os_level >= 4 ;You have to set the IPs here if the optimization level is beyond 3. Watch ;out: These values are in big endian, so the last byte from the IP is the ;most signifant byte here. ;10.0.2.15 - good for qemu my_ip = 0x0F02000A ;10.0.2.2 - also good for qemu dest_ip = 0x0202000A end if use16 org 0x7C00 ;Don't know if this is really necessary but it seems to be cli ;Standard direction flag cld ;Those have to be zero xor ax,ax mov ds,ax mov es,ax mov ss,ax ;Also CS jmp far 0x0000:entry_point if Os_level < 3 ;Reads an IP ;SI: Prompt ;Returns in EAX: IP (big endian) read_ip: if Os_level < 1 ;Print the prompt call print end if ;An IP has 4 blocks mov cx,4 ;Every block is stored in bx xor bx,bx ;EDX holds the IP xor edx,edx read_ip_loop: ;Storing BX while we read the next character (will be destroyed by printc) push bx ;Read a character xor ax,ax int 0x16 ;Print it call printc pop bx ;A dot is the seperator between blocks cmp al,'.' je ip_block_done ;Return means, the IP is finished (originally that value is CR == 13, but ;printc changes it to LF == 10) cmp al,10 jz ip_block_done ;Getting the value from its ASCII character sub al,'0' ;Every other character being no digit is not allowed jb any_error cmp al,9 ja any_error ;We'll add AX later, so we need to zero AH xor ah,ah ;Multiply current block value by 5 (we don't care about the higher word) lea ebx,[ebx*4+ebx] ;Multiply by 2 -> all in all 10 shl bx,1 ;Add current digit add bx,ax ;Block value must fit into one byte test bh,bh jnz any_error ;Read the next character jmp read_ip_loop ip_block_done: ;One block is done ;Shift IP left one byte shl edx,8 ;Set this byte mov dl,bl ;One block done, so decrement the block counter and check if it's zero loop not_the_end_of_ip ;It's zero, so the user must have pressed return cmp al,10 jne any_error ;Turn IP into big endian bswap edx ;And return ret not_the_end_of_ip: ;Zero the block value so we can read the next block xor bx,bx ;IP not yet finished, so the user must have input a dot cmp al,'.' ;If yes, read the next block je read_ip_loop else if Os_level < 4 ;Reads a hexadecimal IP (small letters necessary) ;Returns in EAX: IP (big endian) read_ip: ;Zero EDX, it'll hold the IP xor edx,edx ;4 bytes => 8 nibbles mov cx,8 read_ip_nibble: ;We want to read a character xor ax,ax int 0x16 ;Print it call printc ;Get its value sub al,'0' ;If it was below '0' the character was invalid jb any_error ;If it is between 0 and 9 everything is OK cmp al,9 jna read_ip_got_val ;If not we subtract 'a' so we get values between 0xA and 0xF sub al,'a'-('0'+10) ;If it was below 'a' (but above '9'), the character was invalid jb any_error ;The same thing also applies to characters above 'f' cmp al,0xF ja any_error read_ip_got_val: ;Set this nibble shl edx,4 or dl,al ;And continue, if we read not all nibbles loop read_ip_nibble ;Turn IP into big endian bswap edx ;And go to the next line mov al,13 call printc ret end if ;Any error happened any_error: if Os_level < 2 ;Print an exclamation mark to indicate that mov al,'!' call printc end if hang: ;And hang cli hlt entry_point: ;Activate interrupts (keyboard input) sti ;Stack right below our program mov sp,0x7C00 if Os_level < 1 ;Print the signifant - it's a "famous" anymal, but I think it's only known ;to members of the German "lowlevel" community ;-) ;You may visit the translation of a page explaining the signifant under: ;http://translate.google.com/translate?u=http://lowlevel.brainsware.org/wiki/index.php%3Ftitle%3DSignifant&sl=de&tl=en mov si,signifant call print end if if Os_level < 4 ;Read two IPs (my IP first, then destination's IP) if Os_level < 1 mov si,ask_for_my_ip end if call read_ip mov [my_ip],edx call read_ip mov [dest_ip],edx end if if Os_level < 4 ;Test for PCI BIOS mov ax,0xB101 int 0x1A ;Non-zero, if there's no PCI BIOS test al,al jz any_error ;edx == 'PCI ', if PCI BIOS available cmp edx,'PCI ' jne any_error end if ;Find a PCI device mov ax,0xB102 ;Vendor ID: 0x10EC (Realtek) mov dx,0x10EC ;Device ID: 0x8139 (RTL 8139 network card) mov cx,0x8139 ;Get the first device available xor si,si int 0x1A ;Uh, error occured: No card found jc any_error ;Store the address (bus, device, function) mov [pciaddr],bx ;BAR0 mov di,0x10 ;Scan all BARs for an I/O space bar_loop: ;Restore device address mov bx,[pciaddr] ;Read one dword from PCI configuration space mov ax,0xB10A int 0x1A ;Uhm, f*ck jc any_error ;Is this an I/O space? test cl,1 ;Yes, it is jnz bar_found ;Read the next BAR add di,0x04 ;Have we already read all BARs? cmp di,0x28 ;No, so check the next jb bar_loop ;Yes, we did. Hm. jmp any_error bar_found: ;Hooray, we have our I/O space ;Getting the relevant bits and cx,0xFFF8 mov [addr],cx ;Get the MAC address of our card ;Set DX to the beginning of the I/O space, its first six bytes represent the ;MAC mov dx,cx ;Read six bytes mov cx,6 ;Store the value six (we'll need it later again, saves some bytes) push cx ;Store the MAC in my_mac mov di,my_mac if Os_level < 2 read_mac: ;Get a byte from the current port in al,dx ;Next port, next byte of the MAC inc dx ;Store byte of the MAC in my_mac stosb ;Print the value call print_hexb ;One byte done dec cx ;All bytes done? jcxz mac_done ;No, so go on with printing a colon (seperating two bytes of the MAC) mov al,':' call printc ;Read the next byte jmp read_mac mac_done: ;OK, our MAC has been printed, so go to the next line now mov al,13 call printc else read_mac: ;Get a byte from the current port and store it in [ES:DI] insb ;Next port, next byte of the MAC inc dx ;One byte done, do next, if necessary loop read_mac end if ;Go to the COMMAND register add dx,0x31 ;Reset the card mov al,0x10 out dx,al ;Wait until reset done wait_for_reset_done: in al,dx ;Still resetting? test al,0x10 jnz wait_for_reset_done ;No, so activate reception and transmission now mov al,0x0C out dx,al ;wait a bit call wait_a_bit ;Go to TRANSMISSION CONFIG register add dx,0x09 ;Read it in eax,dx ;Take those bits from it (they should not be changed right now) and eax,0x7FC00000 ;Do 2 kB DMA transfers or ax, 0x0700 out dx,eax ;Go to RECEIVE CONFIG register add dx,0x04 ;Unlimited DMA, broadcast + multicast + singlecast (no promiscuous) mov eax,0x0000070D out dx,eax ;Go to RECEIVE BUFFER register sub dx,0x14 ;Enter the address of our receive buffer here mov eax,receive_buf out dx,eax ;Go to INTERRUPT MASK register add dx,0x0C ;Disable all interrupts xor ax,ax out dx,ax call wait_a_bit ;Destination: everybody (broadcast) mov di,arp_eth_destMAC ;AL is still 0xFF ;Get the value six into CX - do you still remember that? ;-) pop cx push cx ;And store FF:FF:FF:FF:FF:FF (ethernet broadcast) into the "destination MAC" ;field rep stosb ;Source: me ;So get my MAC mov si,my_mac ;(Six bytes again) pop cx push cx ;And store it into the "source MAC" field rep movsb ;Packet type is ARP, of course (0x0806, but in Big Endian) mov ax,0x0608 stosw ;Now the ARP packet ;We use MAC as hardware address (0x0001 in Big Endian) mov ax,0x0100 ;We'll need that value later again push ax stosw ;IPv4 as protocol address (0x0800 in Big Endian) mov ax,0x0008 ;IPv4 stosw ;Every MAC address uses 6 bytes mov al,6 stosb ;And every IPv4 address uses 4 bytes mov al,4 stosb ;Doing an ARP request (code is 1, but in Big Endian, so 0x0100) pop ax stosw ;My MAC address again mov si,my_mac ;6 bytes again pop cx push cx ;And store it into the ARP packet's "source MAC" field rep movsb ;My IP address (a dword) if Os_level < 4 mov eax,[my_ip] else mov eax,my_ip end if ;Store it into the "source IP" field stosd ;Destination's MAC address should be here, but since it's a broadcast nobody ;cares add di,6 ;Destination's IP address if Os_level < 4 mov eax,[dest_ip] else mov eax,dest_ip end if ;Store it into the "destination" IP field stosd ;We will send only one packet - that makes everything a lot ;easier ;Go to TRANSMIT ADDRESS register sub dx,0x1C xor eax,eax ;Enter the address of the our packet mov ax,arp_packet ;address of our buffer out dx,eax ;Go to TRANSMIT STATUS register sub dx,0x10 ;Packet length: It's equal to the minimum packet length (our ARP packet has a ;length of 42 bytes) mov ax,60 ;Start transfer by setting the packet length out dx,eax ;Go to COMMAND register add dx,0x27 wait_for_packet: ;Packet arrived? in al,dx test al,1 ;If no, continue to wait jnz wait_for_packet ;Woohoo, we have the packet, so print out the MAC it came from mov si,rec_arp_eth_srcMAC ;Yep, six bytes again ;-) pop cx get_dest_mac: ;Load a byte lodsb ;And print it call print_hexb ;One byte done - all bytes done? loop dmac_go_on ;Yes, so go the the next line and freeze afterwards if Os_level < 1 mov al,13 call printc end if cli hlt dmac_go_on: ;No, so print a colon if Os_level < 2 mov al,':' call printc end if ;And continue jmp get_dest_mac ;Print a character ;AL: ASCII code printc: ;Print character mov ah,0x0E ;Attribute byte: 0x07; Page: 0x00 mov bx,0x07 ;And print it int 0x10 cmp al,13 jne done_c mov al,10 call printc done_c: ret ;Print one hex character (nibble) ;Lower 4 bits of AL: Value do_one_hex_char: ;Add '0' first add al,'0' cmp al,'9' jbe may_print_hex_now ;If the value was bigger than 9, we have to add 'A'-10 instead of '0', so ;subtract '0' again add al,'A'-('0'+10) may_print_hex_now: ;And print the resulting value call printc ret ;Print a byte as hexadecimal value ;AL: Byte to be printed print_hexb: push ax ;Higher nibble first shr al,4 call do_one_hex_char pop ax ;Now the lower nibble and al,0xF call do_one_hex_char ret if Os_level < 1 ;Prints a string ;SI: Pointer to a zero-terminated string print: ;Load a character lodsb ;If it's zero, we're done test al,al jz print_done ;No, so print it call printc ;And continue jmp print print_done: ;Done, so return ret end if ;Waits a bit wait_a_bit: pusha ;Wait 66 ms ;86h: Wait some microseconds mov ah,0x86 ;We cannot use 'cwd' here, because the MSB of AX is set xor dx,dx xor cx,cx inc cx ;These are 65.536 ms - should be enough int 0x15 popa ret receive_buf = 0x8000 rec_arp_eth_srcMAC = 0x800A arp_packet = 0x9000 arp_eth_destMAC = 0x9000 ;times 6 db arp_eth_srcMAC = 0x9006 ;times 6 db arp_eth_packet_type = 0x900C ;dw arp_pck_hwaddr_type = 0x900E ;dw arp_pck_ptaddr_type = 0x9010 ;dw arp_pck_hwaddr_size = 0x9012 ;db arp_pck_ptaddr_size = 0x9013 ;db arp_pck_operation = 0x9014 ;dw arp_pck_srcMAC = 0x9016 ;times 6 db arp_pck_srcIP = 0x901C ;dd arp_pck_destMAC = 0x9020 ;times 6 db arp_pck_destIP = 0x9026 ;dd my_mac = 0xA000 ;times 6 db addr = 0xA006 ;dw pciaddr = 0xA008 ;dw if Os_level < 4 my_ip = 0xA00C ;dd dest_ip = 0xA010 ;dd end if tipbuf = 0xA800 if Os_level < 1 signifant: db '/\_/\',13 db '\_',0xEC,' /',13 db " '\|'",13,0 ask_for_my_ip db "Me? ",0 ask_for_d_ip db "Destination? ",0 end if times 510-($-$$) db 0 dw 0xAA55