;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                                 ;;
;; Copyright (C) KolibriOS team 2004-2009. All rights reserved.    ;;
;; Distributed under terms of the GNU General Public License       ;;
;;                                                                 ;;
;;  ETHERNET.INC                                                   ;;
;;                                                                 ;;
;;  Ethernet network layer for KolibriOS                           ;;
;;                                                                 ;;
;;    Written by hidnplayr@kolibrios.org                           ;;
;;                                                                 ;;
;;          GNU GENERAL PUBLIC LICENSE                             ;;
;;             Version 2, June 1991                                ;;
;;                                                                 ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

$Revision$

MAX_ETH_DEVICES 	equ MAX_NET_DEVICES
ETH_QUEUE_SIZE		equ 16

struct	ETH_FRAME
	.DstMAC 	dp  ?  ; destination MAC-address [6 bytes]
	.SrcMAC 	dp  ?  ; source MAC-address [6 bytes]
	.Type		dw  ?  ; type of the upper-layer protocol [2 bytes]
	.Data:		       ; data [46-1500 bytes]
ends

struct	ETH_DEVICE
	.unload 	dd ?
	.reset		dd ?
	.transmit	dd ?
	.set_MAC	dd ?
	.get_MAC	dd ?
	.set_mode	dd ?
	.get_mode	dd ?

	.bytes_tx	dq ?
	.bytes_rx	dq ?
	.packets_tx	dd ?
	.packets_rx	dd ?
	.mode		dd ?  ; This dword contains cable status (10mbit/100mbit, full/half duplex, auto negotiation or not,..)
	.name		dd ?
	.mac		dp ?
ends			      ; the rest of the device struct depends on the type of device

struct	eth_queue_entry
	.owner		dd ?
	.data_ptr	dd ?
	.data_size	dd ?
	.size:
ends


align 4
iglobal

	ETH_BROADCAST	dp  0xffffffffffff
endg

align 4
uglobal

	ETH_RUNNING	dd  ?
	ETH_DRV_LIST	rd  MAX_ETH_DEVICES
	ETH_IN_QUEUE	rd  3*ETH_QUEUE_SIZE+3
if QUEUE_BEFORE_SENDING
	ETH_OUT_QUEUE	rd  3*ETH_QUEUE_SIZE+3
end if
endg


;-----------------------------------------------------------------
;
; ETH_init
;
;  This function resets all ethernet variables
;
;  IN:  /
;  OUT: /
;
;-----------------------------------------------------------------
align 4
ETH_init:

	xor	eax, eax
	mov	edi, ETH_RUNNING
	mov	ecx, (1+MAX_ETH_DEVICES)
	rep	stosd

	init_queue ETH_IN_QUEUE

if QUEUE_BEFORE_SENDING
	init_queue ETH_OUT_QUEUE
end if

	ret


;-----------------------------------------------------------------
;
; ETH_Add_Device:
;
;  This function is called by ethernet drivers,
;  to register each running ethernet device to the kernel
;
;  IN:  Pointer to device structure in ebx
;  OUT: Device num in eax, -1 on error
;
;-----------------------------------------------------------------
align 4
ETH_add_device:

	DEBUGF	1,"ETH_Add_Device: %x ", ebx

	mov	eax, [ETH_RUNNING]
	cmp	eax, MAX_ETH_DEVICES
	jge	.error

	test	eax, eax
	jnz	.notfirst
	mov	dword [ETH_IN_QUEUE], eax
if QUEUE_BEFORE_SENDING
	mov	dword [ETH_OUT_QUEUE], eax
end if
      .notfirst:

	mov	eax, ebx
	mov	ecx, MAX_ETH_DEVICES	  ; We need to check whole list because a device may be removed without re-organizing list
	mov	edi, ETH_DRV_LIST

	repne	scasd			  ; See if device is already in the list
	jz	.error

	xor	eax, eax
	mov	ecx, MAX_ETH_DEVICES
	mov	edi, ETH_DRV_LIST

	repne	scasd			  ; Find empty spot in the list
	jnz	.error

	sub	edi, 4
	mov	[edi], ebx		  ; add device to list

	sub	edi, ETH_DRV_LIST	  ; edi = 4*device num       Calculate device number in eax
	mov	eax, edi		  ; edx = 4*device num
	shr	eax, 2

	inc	[ETH_RUNNING]		  ; Indicate that one more ethernet device is up and running

	DEBUGF	1,"- succes: %u\n",eax
	ret

       .error:
	or	eax, -1
	DEBUGF	1,"- fail\n"
	ret




;-----------------------------------------------------------------
;
; ETH_Remove_Device:
;
;  This function is called by ethernet drivers,
;  to unregister ethernet devices from the kernel
;
;  IN:  Pointer to device structure in ebx
;  OUT: eax: -1 on error
;
;-----------------------------------------------------------------
align 4
ETH_remove_device:

	cmp	[ETH_RUNNING], 0
	je	.error

	mov	eax, ebx
	mov	ecx, MAX_ETH_DEVICES
	mov	edi, ETH_DRV_LIST

	repne	scasd
	jnz	.error

	xor	eax, eax
	mov	dword [edi-4], eax

	dec	[ETH_RUNNING]
	jnz	.notlast

	mov	dword [ETH_IN_QUEUE], ETH_QUEUE_SIZE
if QUEUE_BEFORE_SENDING
	mov	dword [ETH_OUT_QUEUE], ETH_QUEUE_SIZE
end if

  .notlast:
	ret

  .error:
	or	eax, -1
	ret



;-----------------------------------------------------------------
;
; ETH_Receiver:
;
;  This function is called by ethernet drivers,
;  It pushes the received ethernet packets onto the eth_in_queue
;
;  IN:  [esp]   = Pointer to buffer
;       [esp-4] = size of buffer
;       ebx     = pointer to eth_device
;  OUT: /
;
;-----------------------------------------------------------------
align 4
ETH_receiver:

	DEBUGF	1,"ETH_Receiver: "
	push	ebx
	mov	esi, esp
	add_to_queue ETH_IN_QUEUE, ETH_QUEUE_SIZE, eth_queue_entry.size, .fail
	DEBUGF	1,"Queued packet successfully\n"
	add	esp, 4*3

	ret

  .fail:
	DEBUGF	1,"ETH_IN_QUEUE is full!\n"
	add	esp, 4
	call	kernel_free
	add	esp, 4

	ret



;-----------------------------------------------------------------
;
; ETH_Handler:
;
;  Handles all queued eth packets (called from kernel's main_loop)
;
;  IN:  /
;  OUT: /
;
;-----------------------------------------------------------------
align 4
ETH_handler:

	get_from_queue ETH_IN_QUEUE, ETH_QUEUE_SIZE, eth_queue_entry.size, .gohome

	push	ETH_handler

	lodsd
	mov	ebx, eax
	lodsd
	mov	ecx, eax
	lodsd
	xchg	eax, ecx
	push	ecx
	push	eax

	DEBUGF	1,"ETH_Handler - size: %u\n", ecx
	cmp	ecx, 60    ; check packet length
	jl	.dump
	sub	ecx, ETH_FRAME.Data

	lea	edx, [eax + ETH_FRAME.Data]
	mov	ax , [eax + ETH_FRAME.Type]

	cmp	ax, ETHER_IPv4
	je	IPv4_handler

	cmp	ax, ETHER_ARP
	je	ARP_handler

	DEBUGF	1,"Unknown ethernet packet type %x\n", ax

  .dump:
	DEBUGF	1,"Dumping packet\n"
	call	kernel_free
	add	esp, 4

  .gohome:
	ret				; return to get more from queue / to caller



;-----------------------------------------------------------------
;
; ETH_sender:
;
;  This function sends an ethernet packet to the correct driver.
;
;  IN:  Pointer to buffer in [esp]
;       size of buffer in [esp+4]
;       pointer to device struct in ebx
;  OUT: /
;
;-----------------------------------------------------------------
align 4
ETH_sender:
if QUEUE_BEFORE_SENDING
	DEBUGF	1,"ETH_Sender: queuing for device: %x, %u bytes\n", [esp], [esp + 4]

	push	ebx
	mov	esi, esp
	add_to_queue ETH_OUT_QUEUE, ETH_QUEUE_SIZE, eth_queue_entry.size, .fail
	DEBUGF	1,"Queued packet successfully\n"
	add	esp, 3*4

	ret

  .fail:
	DEBUGF	1,"ETH_OUT_QUEUE is full!\n"
	add	esp, 4
	call	kernel_free
	add	esp, 4

	ret



;-----------------------------------------------------------------
;
; ETH_send_queued:
;
;  IN:  /
;  OUT: /
;
;-----------------------------------------------------------------
align 4
ETH_send_queued:

	get_from_queue ETH_OUT_QUEUE, ETH_QUEUE_SIZE, eth_queue_entry.size, .gohome

	push	ETH_send_queued 		; this will cause the procedure to check for more packets
						; when a single packet is handled

	mov	ebx, [esi]
	pushd	[esi + 8]
	pushd	[esi + 4]

	DEBUGF	1,"dequeued packet for device %x\n", ebx
end if
	call	[ebx+ETH_DEVICE.transmit]	; we will return to get_from_queue macro after transmitting packet
	call	kernel_free
	add	esp, 4 ; pop (balance stack)

  .gohome:
	ret


;-----------------------------------------------------------------
;
; ETH_struc2dev
;
; IN: pointer to device struct in ebx
;
; OUT: edi is -1 on error, device number otherwise
;
;-----------------------------------------------------------------
align 4
ETH_struc2dev:
	push	eax ecx

	mov	eax, ebx
	mov	ecx, MAX_ETH_DEVICES
	mov	edi, ETH_DRV_LIST

	repne	scasd
	jnz	.error

	sub	edi, ETH_DRV_LIST+4
	shr	edi, 2

	pop	ecx eax
	ret
  .error:
	or	edi, -1
	pop	ecx eax

	ret


;-----------------------------------------------------------------
;
; ETH_create_packet
;
; IN: pointer to source mac in eax
;     pointer to destination mac in ebx
;     packet size in ecx
;     device number in edx
;     protocol in di
;
; OUT: edi is -1 on error, pointer to buffer otherwise
;      eax points to buffer start
;      ebx is pointer to device structure
;      ecx is unchanged (packet size of embedded data)
;      edx is size of complete buffer
;      esi points to procedure wich needs to be called to send packet
;
;-----------------------------------------------------------------
align 4
ETH_create_packet:

	DEBUGF 1,"Creating Ethernet Packet (size=%u): \n", ecx

	cmp	ecx, 1500
	jg	.exit

	push	ecx di eax ebx edx

	add	ecx, ETH_FRAME.Data
	push	ecx
	push	ecx
	call	kernel_alloc
	test	eax, eax
	jz	.pop_exit

	pop	ecx
	pop	edx

	mov	edi, eax
	pop	esi
	movsd
	movsw
	pop	esi
	movsd
	movsw
	pop	ax
	stosw

	lea	eax, [edi - ETH_FRAME.Data]  ; Set eax to buffer start
	mov	edx, ecx		     ; Set ebx to complete buffer size
	pop	ecx
	mov	esi, ETH_sender

	xor	ebx, ebx			;;;; TODO: Fixme
	mov	ebx, [ETH_DRV_LIST + ebx]

	cmp	edx, 46 + ETH_FRAME.Data    ; If data size is less then 46, add padding bytes
	jg	.continue
	mov	edx, 46 + ETH_FRAME.Data
       .continue:

	DEBUGF 1,"done: %x size:%u device:%x\n", eax, edx, ebx
	ret

  .pop_exit:
	DEBUGF 1,"Out of ram space!!\n"
	add	esp, 18
	or	edi,-1
	ret

  .exit:
	DEBUGF 1,"Packet too large!\n"
	or	edi, -1
	ret



;-----------------------------------------------------------------
;
; ETH_API
;
; This function is called by system function 75
;
; IN:  subfunction number in bl
;      device number in bh
;      ecx, edx, .. depends on subfunction
;
; OUT:
;
;-----------------------------------------------------------------
align 4
ETH_API:

	movzx	eax, bh
	shl	eax, 2

	test	bl, bl
	jz	.packets_tx	; 0
	dec	bl
	jz	.packets_rx	; 1
	dec	bl
	jz	.bytes_tx	; 2
	dec	bl
	jz	.bytes_rx	; 3
	dec	bl
	jz	.read_mac	; 4
	dec	bl
	jz	.write_mac	; 5
	dec	bl
	jz	.in_queue	; 6
	dec	bl
	jz	.out_queue	; 7

.error:
	mov	eax, -1
	ret

.packets_tx:
	add	eax, ETH_DRV_LIST
	mov	eax, dword [eax]
	mov	eax, dword [eax + ETH_DEVICE.packets_tx]

	ret

.packets_rx:
	add	eax, ETH_DRV_LIST
	mov	eax, dword [eax]
	mov	eax, dword [eax + ETH_DEVICE.packets_rx]
	ret

.bytes_tx:
	add	eax, ETH_DRV_LIST
	mov	eax, dword [eax]
	mov	ebx, dword [eax + ETH_DEVICE.bytes_tx + 4]
	mov	eax, dword [eax + ETH_DEVICE.bytes_tx]
	mov	[esp+20+4], ebx 			; TODO: fix this ugly code
	ret

.bytes_rx:
	add	eax, ETH_DRV_LIST
	mov	eax, dword [eax]
	mov	ebx, dword [eax + ETH_DEVICE.bytes_rx + 4]
	mov	eax, dword [eax + ETH_DEVICE.bytes_rx]
	mov	[esp+20+4], ebx 			; TODO: fix this ugly code
	ret


.read_mac:
	add	eax, ETH_DRV_LIST
	mov	eax, [eax]
;        push    eax
;        call    dword [eax + ETH_DEVICE.get_MAC]
;        pop     eax
	movzx	ebx, word [eax + ETH_DEVICE.mac]
	mov	eax, dword [eax + ETH_DEVICE.mac + 2]
	mov	[esp+20+4], ebx 			; TODO: fix this ugly code
	ret

.write_mac:
	push	ecx
	push	dx
	add	eax, ETH_DRV_LIST
	mov	eax, [eax]
	mov	eax, dword [eax + ETH_DEVICE.set_MAC]
	call	eax
	ret

.in_queue:
	add	eax, ETH_IN_QUEUE
	mov	eax, [eax + queue.size]
	ret

.out_queue:
if QUEUE_BEFORE_SENDING
	add	eax, ETH_OUT_QUEUE
	mov	eax, [eax + queue.size]
else
	mov	eax, -1
end if
	ret