Compare commits

..
Author SHA1 Message Date
hidnplayr f6d0f4f2d3 Added NET_TX_BLOCKED flag in network devices to indicate output is blocked.
Implemented the NET_TX_BLOCKED and pending transmit queue for PCNET driver.
Fixed blocking functionality of socket_send_tcp when output buffer is full.
2026-07-31 14:11:26 +02:00
hidnplayr 0f5f56536b Very minimalistic iperf clone for KolibriOS (100% written by Claude Sonnet 5) 2026-07-27 15:07:47 +02:00
8 changed files with 848 additions and 322 deletions

No files matched your search

+143 -13
View File
@@ -1,6 +1,6 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2004-2024. All rights reserved. ;;
;; Copyright (C) KolibriOS team 2004-2026. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;; AMD PCnet driver for KolibriOS ;;
@@ -31,6 +31,11 @@ entry START
TX_RING_SIZE = 32 ; Number of packets in send ring buffer
RX_RING_SIZE = 32 ; Number of packets in receive ring buffer
; pending transmit queue, used when frames arrive while hardware is busy
PENDING_TX_MAX = 8 ; Maximum number of frames in the queue
PENDING_TX_LOWAT = 0 ; Low-water mark for the number of frames in the queue, before resetting the NET_TX_BLOCKED flag
; end configureable area
section '.flat' readable writable executable
@@ -316,6 +321,11 @@ struct device ETH_DEVICE
cur_tx dd ?
last_tx dd ?
; pending transmit queue: linked list of frames that arrived while the hardware was busy
pend_tx_head dd ? ; pointer to oldest queued NET_BUFF, 0 if empty
pend_tx_tail dd ? ; pointer to newest queued NET_BUFF, 0 if empty
pend_tx_count dd ? ; 0..PENDING_TX_MAX
options dd ?
full_duplex db ?
chip_version dw ?
@@ -1000,6 +1010,21 @@ init_ring:
mov [ebx + device.last_tx], 0
mov [ebx + device.cur_rx], 0
mov esi, [ebx + device.pend_tx_head]
.drain_pending:
test esi, esi
jz .pending_drained
mov eax, [esi + NET_BUFF.NextPtr]
push eax
invoke NetFree, esi
pop eax
mov esi, eax
jmp .drain_pending
.pending_drained:
mov dword [ebx + device.pend_tx_head], 0
mov dword [ebx + device.pend_tx_tail], 0
mov dword [ebx + device.pend_tx_count], 0
xor eax, eax
ret
@@ -1045,6 +1070,9 @@ proc transmit stdcall bufferptr
test [edi + descriptor.status], TXCTL_OWN
jnz .overrun
cmp [edi + descriptor.virtual], 0 ; Make sure int handler has performed cleanup already
jne .desc_busy
.desc_free:
; descriptor is free, use it
mov [edi + descriptor.virtual], esi
mov eax, esi
@@ -1058,6 +1086,12 @@ proc transmit stdcall bufferptr
; put to transfer queue
mov [edi + descriptor.status], TXCTL_OWN + TXCTL_STP + TXCTL_ENP
; Update stats
inc [ebx + device.packets_tx]
mov eax, [esi + NET_BUFF.length]
add dword[ebx + device.bytes_tx], eax
adc dword[ebx + device.bytes_tx + 4], 0
; trigger an immediate send
mov edx, [ebx + device.io_addr]
xor ecx, ecx ; CSR0
@@ -1069,16 +1103,16 @@ proc transmit stdcall bufferptr
inc [ebx + device.cur_tx]
and [ebx + device.cur_tx], TX_RING_SIZE - 1
; Update stats
inc [ebx + device.packets_tx]
mov eax, [esi + NET_BUFF.length]
add dword[ebx + device.bytes_tx], eax
adc dword[ebx + device.bytes_tx + 4], 0
spin_unlock_irqrestore
xor eax, eax
ret
.desc_busy:
pusha ; TODO CHECKME
invoke NetFree, [edi + descriptor.virtual]
popa
jmp .desc_free
.error:
DEBUGF 2, "TX packet error\n"
inc [ebx + device.packets_tx_err]
@@ -1089,12 +1123,40 @@ proc transmit stdcall bufferptr
ret
.overrun:
; Hardware ring is full.
DEBUGF 1, "High water\n"
or [ebx + device.state], NET_TX_BLOCKED
; Try the pending tx queue as a last resort
cmp [ebx + device.pend_tx_count], PENDING_TX_MAX
jae .queue_full
mov dword [esi + NET_BUFF.NextPtr], 0
mov dword [esi + NET_BUFF.PrevPtr], 0
mov eax, [ebx + device.pend_tx_tail]
mov [ebx + device.pend_tx_tail], esi
test eax, eax
jz @f
mov [eax + NET_BUFF.NextPtr], esi
jmp .list_linked
@@:
mov [ebx + device.pend_tx_head], esi
.list_linked:
inc [ebx + device.pend_tx_count]
DEBUGF 1, "Pending TX: %u\n", [ebx + device.pend_tx_count]
spin_unlock_irqrestore
xor eax, eax
ret
.queue_full:
DEBUGF 2, "TX overrun\n"
inc [ebx + device.packets_tx_ovr]
invoke NetFree, [bufferptr]
spin_unlock_irqrestore
or eax, -1
dec eax
ret
endp
@@ -1225,20 +1287,20 @@ int_handler:
jz .not_transmit
.tx_loop:
lea edi, [ebx + device.tx_ring]
mov eax, [ebx + device.last_tx]
lea edi, [ebx + device.tx_ring]
shl eax, 4
add edi, eax
test [edi + descriptor.status], TXCTL_OWN
jnz .not_transmit
jnz .tx_done ; still busy
mov eax, [edi + descriptor.virtual]
test eax, eax
jz .not_transmit
jz .tx_done ; never filled, or already reclaimed
mov [edi + descriptor.virtual], 0
DEBUGF 1,"Removing packet %x from memory\n", eax
invoke NetFree, eax
@@ -1247,6 +1309,10 @@ int_handler:
and [ebx + device.last_tx], TX_RING_SIZE - 1
jmp .tx_loop
.tx_done:
cmp [ebx + device.pend_tx_count], 0
jne .refill_loop
.not_transmit:
pop edi esi ebx
xor eax, eax
@@ -1254,6 +1320,70 @@ int_handler:
ret
.refill_loop:
mov ecx, [ebx + device.cur_tx]
shl ecx, 4
lea edi, [ebx + device.tx_ring]
add edi, ecx
test [edi + descriptor.status], TXCTL_OWN
jnz .refill_done ; not actually free after all - stop
cmp [edi + descriptor.virtual], 0
je @f
pusha
invoke NetFree, [edi + descriptor.virtual]
popa
@@:
mov esi, [ebx + device.pend_tx_head]
mov eax, [esi + NET_BUFF.NextPtr]
mov [ebx + device.pend_tx_head], eax
mov [edi + descriptor.virtual], esi
mov eax, esi
add eax, [eax + NET_BUFF.offset]
invoke GetPhysAddr
mov [edi + descriptor.base], eax
mov eax, [esi + NET_BUFF.length]
neg eax
mov [edi + descriptor.length], ax
mov [edi + descriptor.status], TXCTL_OWN + TXCTL_STP + TXCTL_ENP
inc [ebx + device.packets_tx]
mov eax, [esi + NET_BUFF.length]
add dword[ebx + device.bytes_tx], eax
adc dword[ebx + device.bytes_tx + 4], 0
inc [ebx + device.cur_tx]
and [ebx + device.cur_tx], TX_RING_SIZE - 1
dec [ebx + device.pend_tx_count]
jnz .refill_loop
mov dword [ebx + device.pend_tx_tail], 0
.refill_done:
DEBUGF 1, "Pending TX: %u\n", [ebx + device.pend_tx_count]
mov edx, [ebx + device.io_addr]
xor ecx, ecx ; CSR0
call [ebx + device.read_csr]
or eax, CSR_TX
call [ebx + device.write_csr]
cmp [ebx + device.pend_tx_count], PENDING_TX_LOWAT
ja @f
and [ebx + device.state], not NET_TX_BLOCKED
DEBUGF 1, "Low water\n"
@@:
pop edi esi ebx
xor eax, eax
inc eax
ret
@@ -1595,7 +1725,7 @@ proc check_media_mii stdcall dev:dword
mov ecx, eax
and eax, BMSR_LSTATUS
shr eax, 2
cmp eax, [ebx + device.state]
cmp ax, word[ebx + device.state] ; lazy masking of NET_TX_BLOCKED
jne .changed
spin_unlock_irqrestore
@@ -1643,7 +1773,7 @@ proc check_media_mii stdcall dev:dword
mov eax, ETH_LINK_UNKNOWN
.update:
mov [ebx + device.state], eax
mov word[ebx + device.state], ax ; lazy masking of NET_TX_BLOCKED
invoke NetLinkChanged
+158 -154
View File
@@ -1,154 +1,158 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2004-2021. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;; GNU GENERAL PUBLIC LICENSE ;;
;; Version 2, June 1991 ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This macro will prepend driver name to all debug output through DEBUGF macro
; The driver name is taken from my_service label
if defined my_service
macro DEBUGF _level,_format, [args] {
common DEBUGF _level, "%s: " # _format, my_service, args
}
end if
include 'pci.inc'
include 'mii.inc'
; Kernel variables
PAGESIZE = 4096
; Network driver types
NET_TYPE_ETH = 1
NET_TYPE_SLIP = 2
; Link state
ETH_LINK_DOWN = 0 ; Link is down
ETH_LINK_UNKNOWN = 1b ; There could be an active link
ETH_LINK_FULL_DUPLEX = 10b ; full duplex flag
ETH_LINK_SPEED_10M = 100b ; 10 Mbit
ETH_LINK_SPEED_100M = 1000b ; 100 Mbit
ETH_LINK_SPEED_1G = 1100b ; Gigabit
ETH_LINK_SPEED_MASK = 1100b
; Macro to easily set i/o addresses to access device.
; In the beginning of a procedure (or ofter edx may have been destroyed),
; always use set_io with offset 0 to reset the variables.
LAST_IO = 0
macro set_io baseaddr, offset {
if offset = 0
mov edx, baseaddr
else if offset = LAST_IO
else
add edx, offset - LAST_IO
end if
LAST_IO = offset
}
; Macro to allocate a contiguous buffer in memory
; And initialise it to all zeros
; This macro will destroy eax, ecx and edi !
macro allocate_and_clear dest, size, err {
; We need to allocate at least 8 pages, if we want a contiguous area in ram
push edx
if (size < 8*4096) & (size > 4096)
invoke KernelAlloc, 8*4096
else
invoke KernelAlloc, size
end if
pop edx
test eax, eax
jz err
mov dest, eax
mov edi, eax ; look at last part of code!
; Release the unused pages (if any)
if (size < 8*4096) & (size > 4096)
add eax, (size/4096+1)*4096
mov ecx, 8-(size/4096+1)
push edx
invoke ReleasePages
pop edx
end if
; Clear the allocated buffer
mov ecx, size/4 ; divide by 4 because of DWORD
xor eax, eax
rep stosd
if (size - size/4*4)
mov ecx, size - size/4*4
rep stosb
end if
}
struct NET_DEVICE
type dd ? ; Type field
mtu dd ? ; Maximal Transmission Unit
name dd ? ; Ptr to 0 terminated string
unload dd ? ; Ptrs to driver functions
reset dd ? ;
transmit dd ? ;
state dd ? ; link state (0 = no link)
hwacc dd ? ; bitmask stating enabled HW accelerations (offload engines)
bytes_tx dq ? ; Statistics, updated by the driver
bytes_rx dq ? ;
packets_tx dd ? ;
packets_tx_err dd ? ; CRC errors, too long or too short frames
packets_tx_drop dd ? ;
packets_tx_ovr dd ? ; FIFO overrun
packets_rx dd ? ;
packets_rx_err dd ? ; CRC errors, too long or too short frames
packets_rx_drop dd ? ;
packets_rx_ovr dd ? ; FIFO overrun
ends
struct NET_BUFF
NextPtr dd ? ; pointer to next frame in list
PrevPtr dd ? ; pointer to previous frame in list
device dd ? ; ptr to NET_DEVICE structure
type dd ? ; data type (e.g. Ethernet)
length dd ? ; data length
offset dd ? ; offset to actual data (24 bytes for default frame)
data rb 0
ends
struct ETH_DEVICE NET_DEVICE
mac dp ?
dw ? ; qword alignment
ends
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2004-2021. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;; GNU GENERAL PUBLIC LICENSE ;;
;; Version 2, June 1991 ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; This macro will prepend driver name to all debug output through DEBUGF macro
; The driver name is taken from my_service label
if defined my_service
macro DEBUGF _level,_format, [args] {
common DEBUGF _level, "%s: " # _format, my_service, args
}
end if
include 'pci.inc'
include 'mii.inc'
; Kernel variables
PAGESIZE = 4096
; Network driver types
NET_TYPE_ETH = 1
NET_TYPE_SLIP = 2
; Link state
ETH_LINK_DOWN = 0 ; Link is down
ETH_LINK_UNKNOWN = 1b ; Link is up, but speed might be unknown
ETH_LINK_FULL_DUPLEX = 10b ; full duplex flag
ETH_LINK_SPEED_10M = 100b ; 10 Mbit
ETH_LINK_SPEED_100M = 1000b ; 100 Mbit
ETH_LINK_SPEED_1G = 1100b ; Gigabit
ETH_LINK_SPEED_MASK = 1100b
; Hardware state
NET_TX_BLOCKED = 1 shl 31 ; Transmit queue is full, if you call transmit again, driver might drop your frame
; Macro to easily set i/o addresses to access device.
; In the beginning of a procedure (or ofter edx may have been destroyed),
; always use set_io with offset 0 to reset the variables.
LAST_IO = 0
macro set_io baseaddr, offset {
if offset = 0
mov edx, baseaddr
else if offset = LAST_IO
else
add edx, offset - LAST_IO
end if
LAST_IO = offset
}
; Macro to allocate a contiguous buffer in memory
; And initialise it to all zeros
; This macro will destroy eax, ecx and edi !
macro allocate_and_clear dest, size, err {
; We need to allocate at least 8 pages, if we want a contiguous area in ram
push edx
if (size < 8*4096) & (size > 4096)
invoke KernelAlloc, 8*4096
else
invoke KernelAlloc, size
end if
pop edx
test eax, eax
jz err
mov dest, eax
mov edi, eax ; look at last part of code!
; Release the unused pages (if any)
if (size < 8*4096) & (size > 4096)
add eax, (size/4096+1)*4096
mov ecx, 8-(size/4096+1)
push edx
invoke ReleasePages
pop edx
end if
; Clear the allocated buffer
mov ecx, size/4 ; divide by 4 because of DWORD
xor eax, eax
rep stosd
if (size - size/4*4)
mov ecx, size - size/4*4
rep stosb
end if
}
struct NET_DEVICE
type dd ? ; Type field
mtu dd ? ; Maximal Transmission Unit
name dd ? ; Ptr to 0 terminated string
unload dd ? ; Ptrs to driver functions
reset dd ? ;
transmit dd ? ;
state dd ? ; link state (0 = no link)
hwacc dd ? ; bitmask stating enabled HW accelerations (offload engines)
bytes_tx dq ? ; Statistics, updated by the driver
bytes_rx dq ? ;
packets_tx dd ? ;
packets_tx_err dd ? ; CRC errors, too long or too short frames
packets_tx_drop dd ? ;
packets_tx_ovr dd ? ; FIFO overrun
packets_rx dd ? ;
packets_rx_err dd ? ; CRC errors, too long or too short frames
packets_rx_drop dd ? ;
packets_rx_ovr dd ? ; FIFO overrun
ends
struct NET_BUFF
NextPtr dd ? ; pointer to next frame in list
PrevPtr dd ? ; pointer to previous frame in list
device dd ? ; ptr to NET_DEVICE structure
type dd ? ; data type (e.g. Ethernet)
length dd ? ; data length
offset dd ? ; offset to actual data (24 bytes for default frame)
data rb 0
ends
struct ETH_DEVICE NET_DEVICE
mac dp ?
dw ? ; qword alignment
ends
+30 -138
View File
@@ -1,6 +1,6 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2004-2024. All rights reserved. ;;
;; Copyright (C) KolibriOS team 2004-2026. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;; Part of the TCP/IP network stack for KolibriOS ;;
@@ -279,10 +279,8 @@ sys_socket:
dd socket_get_opt ; 9
dd socket_pair ; 10
dd socket_getpeername ; 11
dd socket_getsockname ; 12
;dd socket_sendto ; 13
;dd socket_recvfrom ; 14
;dd socket_sendto ; 11
;dd socket_recvfrom ; 12
.number = ($ - .table) / 4 - 1
.error:
@@ -1036,6 +1034,9 @@ socket_send:
test eax, eax
jz .invalid
test esi, esi
jz .zero_length
stdcall is_region_userspace, edx, esi
jnz .invalid
@@ -1049,6 +1050,10 @@ socket_send:
mov dword[esp + SYSCALL_STACK.eax], -1
ret
.zero_length:
DEBUGF DEBUG_NETWORK_ERROR, "SOCKET_send: data length = 0\n"
mov dword[esp + SYSCALL_STACK.eax], 0
ret
align 4
socket_send_udp:
@@ -1072,11 +1077,15 @@ socket_send_tcp:
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_send: TCP\n"
.loop:
push eax
add eax, STREAM_SOCKET.snd
call socket_ring_write
pop eax
test ecx, ecx
jz .block
mov [esp + SYSCALL_STACK.eax], ecx
mov [eax + SOCKET.errorcode], 0
push eax
@@ -1086,6 +1095,22 @@ socket_send_tcp:
mov [esp + SYSCALL_STACK.ebx], eax
ret
.block:
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_send: TCP is blocked\n"
test [eax + SOCKET.options], SO_NONBLOCK
jnz .wouldblock
call socket_block
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_send: TCP is unblocked\n"
mov ecx, dword[esp + SYSCALL_STACK.esi]
jmp .loop
.wouldblock:
mov dword[esp + SYSCALL_STACK.ebx], EWOULDBLOCK
mov dword[esp + SYSCALL_STACK.eax], -1
pop esi edx
ret
align 4
socket_send_ip:
@@ -1398,139 +1423,6 @@ socket_pair:
;-----------------------------------------------------------------;
; ;
; socket_getpeername: Get name of connected peer socket. ;
; ;
; IN: ecx = socket number ;
; edx = pointer to sockaddr struct ;
; esi = length of sockaddr struct ;
; ;
; OUT: eax = length of sockaddr struct ;
; eax = -1 on error ;
; ebx = errorcode on error ;
; ;
;-----------------------------------------------------------------;
align 4
socket_getpeername:
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_getpeername: socknum=%u sockaddr=%x length=%u\n", ecx, edx, esi
call socket_num_to_ptr
test eax, eax
jz .notsock
stdcall is_region_userspace, edx, esi
jnz .efault
cmp [eax + SOCKET.Domain], AF_INET4
jne .invalid
cmp esi, 8 ; domain + port + ipv4_addr
jb .errlen
mov word[edx], AF_INET4
mov esi, edx
xor edx, edx
cmp [eax + SOCKET.Type], SOCK_RAW
je .raw_protocol
mov dx, [eax + TCP_SOCKET.RemotePort]
.raw_protocol:
cmp [eax + SOCKET.state], SS_ISCONNECTED
jne .notconn
mov [esi + sockaddr.port], dx
mov edx, [eax + IP_SOCKET.RemoteIP]
mov [esi + sockaddr.ip], edx
mov dword[esp + SYSCALL_STACK.eax], 8
ret
.efault:
mov dword[esp + SYSCALL_STACK.eax], -1
mov dword[esp + SYSCALL_STACK.ebx], EFAULT
ret
.notconn:
mov dword[esp + SYSCALL_STACK.ebx], ENOTCONN
mov dword[esp + SYSCALL_STACK.eax], -1
ret
.no_inet4:
.errlen:
.invalid:
mov dword[esp + SYSCALL_STACK.ebx], EINVAL
mov dword[esp + SYSCALL_STACK.eax], -1
ret
.notsock:
mov dword[esp + SYSCALL_STACK.ebx], ENOTSOCK
mov dword[esp + SYSCALL_STACK.eax], -1
ret
;-----------------------------------------------------------------;
; ;
; socket_getsockname: Get socket name. ;
; ;
; IN: ecx = socket number ;
; edx = pointer to sockaddr struct ;
; esi = length of sockaddr struct ;
; ;
; OUT: eax = length of sockaddr struct ;
; eax = -1 on error ;
; ebx = errorcode on error ;
; ;
;-----------------------------------------------------------------;
align 4
socket_getsockname:
DEBUGF DEBUG_NETWORK_VERBOSE, "SOCKET_getsockname: socknum=%u sockaddr=%x length=%u\n", ecx, edx, esi
call socket_num_to_ptr
test eax, eax
jz .notsock
stdcall is_region_userspace, edx, esi
jnz .efault
cmp [eax + SOCKET.Domain], AF_INET4
jne .invalid
cmp esi, 8 ; domain + port + ipv4_addr
jb .errlen
mov word[edx], AF_INET4
mov esi, edx
xor edx, edx
cmp [eax + SOCKET.Type], SOCK_RAW
je .raw_protocol
mov dx, [eax + TCP_SOCKET.RemotePort]
.raw_protocol:
mov [esi + sockaddr.port], dx
mov edx, [eax + IP_SOCKET.RemoteIP]
mov [esi + sockaddr.ip], edx
mov dword[esp + SYSCALL_STACK.eax], 8
ret
.efault:
mov dword[esp + SYSCALL_STACK.eax], -1
mov dword[esp + SYSCALL_STACK.ebx], EFAULT
ret
.errlen:
.invalid:
mov dword[esp + SYSCALL_STACK.ebx], EINVAL
mov dword[esp + SYSCALL_STACK.eax], -1
ret
.notsock:
mov dword[esp + SYSCALL_STACK.ebx], ENOTSOCK
mov dword[esp + SYSCALL_STACK.eax], -1
ret
;-----------------------------------------------------------------;
; ;
; socket_debug: Copy socket variables to application buffer. ;
+4 -4
View File
@@ -1,6 +1,6 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2004-2024. All rights reserved. ;;
;; Copyright (C) KolibriOS team 2004-2026. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;; STACK.INC ;;
@@ -139,9 +139,6 @@ EISCONN = 56
ETIMEDOUT = 60
ECONNREFUSED = 61
EFAULT = 14 ; copy, defined in posix/posix.inc ; Bad address
ENOTSOCK = 38 ; FreeBSD error code, Socket operation on non-socket
; Api protocol numbers
API_ETH = 0
API_IPv4 = 1
@@ -171,6 +168,9 @@ NET_HWACC_TCP_IPv4_OUT = 1 shl 1
NET_BUFF_LOOPBACK = 0
NET_BUFF_ETH = 1
; Hardware state (inside link_state)
NET_TX_BLOCKED = 1 shl 31 ; Transmit queue is full, if you call transmit again, driver might drop your frame
struct NET_DEVICE
device_type dd ? ; Type field
+2 -7
View File
@@ -355,14 +355,9 @@ endl
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_input: Got window scale option\n"
or [ebx + TCP_SOCKET.t_flags], TF_RCVD_SCALE
; remember the peer's scale factor; it is applied to SND_SCALE when the
; connection is established (together with our RCV_SCALE)
lodsb
cmp al, TCP_max_winshift
jbe .wscale_capped
mov al, TCP_max_winshift
.wscale_capped:
mov [ebx + TCP_SOCKET.requested_s_scale], al
mov [ebx + TCP_SOCKET.SND_SCALE], al
;;;;; TODO
@@:
jmp .opt_loop
+28 -3
View File
@@ -1,6 +1,6 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; Copyright (C) KolibriOS team 2004-2024. All rights reserved. ;;
;; Copyright (C) KolibriOS team 2004-2026. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License ;;
;; ;;
;; Part of the TCP/IP network stack for KolibriOS ;;
@@ -554,6 +554,14 @@ endl
mov ecx, esi
mov ebx, [eax + IP_SOCKET.device]
test ebx, ebx
jz @f
; Check if the output path is blocked, dont generate output frames if so.
test [ebx + NET_DEVICE.link_state], NET_TX_BLOCKED
jnz .output_blocked
@@:
mov edx, [eax + IP_SOCKET.LocalIP] ; source ip
mov edi, [eax + IP_SOCKET.RemoteIP] ; dest ip
mov al, [eax + IP_SOCKET.ttl]
@@ -736,6 +744,23 @@ endl
or eax, -1
ret
.output_blocked:
DEBUGF DEBUG_NETWORK_VERBOSE, "TCP_send: output path blocked, aborting\n"
pop ecx
add esp, ecx
add esp, 4
pop eax
mov [eax + TCP_SOCKET.timer_retransmission], TCP_time_re_min
or [eax + TCP_SOCKET.timer_flags], timer_flag_retransmission
lea ecx, [eax + SOCKET.mutex]
call mutex_unlock
or eax, -1
ret
.send_error:
add esp, 4
@@ -746,8 +771,8 @@ endl
DEBUGF DEBUG_NETWORK_ERROR, "TCP_send: sending failed\n"
or eax, -2
or eax, -1
dec eax
ret
endp
+3 -3
View File
@@ -320,12 +320,12 @@ tcp_respond:
stosb
mov eax, SOCKET_BUFFER_SIZE
sub eax, [esi + STREAM_SOCKET.rcv.size]
cmp eax, TCP_max_win
mov cl, [esi + TCP_SOCKET.RCV_SCALE]
shr eax, cl ; scale first, then clamp to the
cmp eax, TCP_max_win ; 16-bit field maximum
jbe .lessthanmax
mov eax, TCP_max_win
.lessthanmax:
mov cl, [esi + TCP_SOCKET.RCV_SCALE]
shr eax, cl
xchg al, ah
stosw ; window
+480
View File
@@ -0,0 +1,480 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;
;; kperf.asm - simple iperf-style TCP throughput tester ;;
;; for KolibriOS ;;
;; ;;
;; Usage: ;;
;; kperf -c <ip> connect to a server and blast data ;;
;; kperf -s listen, accept, and receive data ;;
;; ;;
;; Compatible with real iperf2 in "-C" (compatibility) mode: ;;
;; Linux side, receive test: iperf -s -p 5001 -i 1 ;;
;; Linux side, send test: iperf -c <kolibri-ip> -p 5001 \ ;;
;; -C -i 1 ;;
;; No iperf header is sent or expected in either direction - it's ;;
;; a raw byte-count throughput test, matching -C behaviour. ;;
;; ;;
;; Socket API and command-line parsing patterns verified against ;;
;; tcpserv.asm and telnet.asm (KolibriOS team, hidnplayr). ;;
;; The interval/summary reporting math below is new code and has ;;
;; not been run - sanity check the numbers on first use. ;;
;; ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
format binary as ""
PORT = 5001 ; classic iperf2 default port
SENDBUFSIZE = 32768
RECVBUFSIZE = 32768
DURATION_CS = 1000 ; client test duration: 10.00 sec (centiseconds)
REPORT_CS = 100 ; report interval: 1.00 sec (centiseconds)
use32
; standard header
db 'MENUET01' ; signature
dd 1 ; header version
dd start ; entry point
dd i_end ; initialized size
dd mem+4096 ; required memory
dd mem+4096 ; stack pointer
dd cmdline ; parameters - kernel writes ASCIIZ args here
dd 0 ; path
include '../../macros.inc'
purge mov,add,sub
include '../../proc32.inc'
include '../../dll.inc'
include '../../network.inc'
; ======================================================================
; entry point
; ======================================================================
start:
stdcall dll.Load, @IMPORT
test eax, eax
jnz exit
invoke con_start, 1
invoke con_init, 80, 25, 80, 25, title
mcall 40, EVM_STACK
cmp byte[cmdline], 0
jne .have_args
invoke con_write_asciiz, str_usage
jmp done
.have_args:
mov esi, cmdline
cmp byte[esi], '-'
jne .modechar
inc esi
.modechar:
lodsb
cmp al, 's'
je server_mode
cmp al, 'c'
je client_prep
invoke con_write_asciiz, str_usage
jmp done
; ======================================================================
; client mode - parse IP, connect, blast data
; ======================================================================
client_prep:
.skipsp:
cmp byte[esi], ' '
jne .gotip
inc esi
jmp .skipsp
.gotip:
push esp
invoke getaddrinfo, esi, 0, 0, esp
pop edi
test eax, eax
jnz dns_err
mov eax, [edi+addrinfo.ai_addr]
mov eax, [eax+sockaddr_in.sin_addr]
mov [sockaddr1.ip], eax
invoke freeaddrinfo, edi
invoke con_write_asciiz, str_connecting
invoke inet_ntoa, [sockaddr1.ip]
invoke con_write_asciiz, eax
invoke con_write_asciiz, str_nl
mov ax, PORT
xchg al, ah ; host->network byte order
mov [sockaddr1.port], ax
mcall socket, AF_INET4, SOCK_STREAM, 0
cmp eax, -1
je socket_err
mov [socketnum], eax
mcall connect, [socketnum], sockaddr1, 18
test eax, eax
jnz socket_err
invoke con_write_asciiz, str_connected
; --- timing setup ---
mcall 26, 9
mov [start_time], eax
mov [last_report], eax
mov dword[interval_bytes], 0
mov dword[total_bytes], 0
.sendloop:
invoke con_get_flags
test eax, 0x200
jnz .client_closed
mcall send, [socketnum], sendbuf, SENDBUFSIZE, 0
cmp eax, -1
je .client_closed
;cmp eax, 0 ; 0 means buffer is full
;je .client_closed
add [interval_bytes], eax
add [total_bytes], eax
mcall 26, 9
mov ecx, eax ; ecx = now
mov ebx, ecx
sub ebx, [last_report] ; ebx = elapsed since last report
cmp ebx, REPORT_CS
jl .check_dur
mov edx, [last_report]
sub edx, [start_time] ; edx = interval start (rel. to test start)
stdcall print_interval, [interval_bytes], ebx, edx, ecx
mov [last_report], ecx
mov dword[interval_bytes], 0
.check_dur:
mcall 26, 9
mov ebx, eax
sub ebx, [start_time]
cmp ebx, DURATION_CS
jl .sendloop
mcall 26, 9
mov ebx, eax
sub ebx, [start_time]
stdcall print_summary, [total_bytes], ebx
jmp .client_done
.client_closed:
invoke con_write_asciiz, str_conn_closed
mov ebx, [last_report]
sub ebx, [start_time]
stdcall print_summary, [total_bytes], ebx
.client_done:
mcall close, [socketnum]
jmp done
; ======================================================================
; server mode - listen, accept, receive data
; ======================================================================
server_mode:
mcall socket, AF_INET4, SOCK_STREAM, 0
cmp eax, -1
je socket_err
mov [socketnum], eax
mov ax, PORT
xchg al, ah
mov [sockaddr1.port], ax
mov dword[sockaddr1.ip], 0
mcall bind, [socketnum], sockaddr1, 18
cmp eax, -1
je socket_err
mcall listen, [socketnum], 1
cmp eax, -1
je socket_err
invoke con_write_asciiz, str_listening
mcall accept, [socketnum], sockaddr1, 18
cmp eax, -1
je socket_err
mov [socketnum2], eax
invoke con_write_asciiz, str_accepted
invoke inet_ntoa, [sockaddr1.ip]
invoke con_write_asciiz, eax
invoke con_write_asciiz, str_nl
mcall 26, 9
mov [start_time], eax
mov [last_report], eax
mov dword[interval_bytes], 0
mov dword[total_bytes], 0
.recvloop:
invoke con_get_flags
test eax, 0x200
jnz .server_closed
mcall recv, [socketnum2], recvbuf, RECVBUFSIZE, 0
cmp eax, -1
je .server_closed
cmp eax, 0
je .server_closed
add [interval_bytes], eax
add [total_bytes], eax
mcall 26, 9
mov ecx, eax
mov ebx, ecx
sub ebx, [last_report]
cmp ebx, REPORT_CS
jl .recvloop
mov edx, [last_report]
sub edx, [start_time]
stdcall print_interval, [interval_bytes], ebx, edx, ecx
mov [last_report], ecx
mov dword[interval_bytes], 0
jmp .recvloop
.server_closed:
invoke con_write_asciiz, str_conn_closed
mcall 26, 9
mov ebx, eax
sub ebx, [start_time]
stdcall print_summary, [total_bytes], ebx
mcall close, [socketnum2]
jmp done
; ======================================================================
; error handlers
; ======================================================================
socket_err:
invoke con_write_asciiz, str_sockerr
jmp done
dns_err:
invoke con_write_asciiz, str_dnserr
jmp done
done:
invoke con_getch2
invoke con_exit, 1
exit:
mcall -1
; ======================================================================
; print_interval: bytes, interval_cs, range_start_cs, range_end_cs
; Prints "S.CC-S.CC sec X.XX Mbits/sec"
; ======================================================================
proc print_interval uses eax ebx ecx edx, pbytes, pinterval, prange_start, prange_end
stdcall fmt_cs, [prange_start]
invoke con_write_asciiz, linebuf
invoke con_write_asciiz, str_dash
stdcall fmt_cs, [prange_end]
invoke con_write_asciiz, linebuf
invoke con_write_asciiz, str_sec_tab
stdcall fmt_mbps, [pbytes], [pinterval]
invoke con_write_asciiz, linebuf
invoke con_write_asciiz, str_mbits_nl
ret
endp
; ======================================================================
; print_summary: bytes, total_cs
; Prints "0.00-S.CC sec (total) X.XX Mbits/sec"
; ======================================================================
proc print_summary uses eax ebx ecx edx, pbytes, pinterval
invoke con_write_asciiz, str_zero
invoke con_write_asciiz, str_dash
stdcall fmt_cs, [pinterval]
invoke con_write_asciiz, linebuf
invoke con_write_asciiz, str_sec_total
stdcall fmt_mbps, [pbytes], [pinterval]
invoke con_write_asciiz, linebuf
invoke con_write_asciiz, str_mbits_nl
ret
endp
; ======================================================================
; fmt_cs: format a centisecond dword value as "S.CC" into linebuf
; ======================================================================
proc fmt_cs uses eax ebx edx edi, pcs
mov eax, [pcs]
mov ebx, 100
xor edx, edx
div ebx ; eax = whole seconds, edx = centiseconds
push edx ; save centiseconds
mov edi, linebuf
stdcall itoa, eax, edi
mov edi, eax ; itoa returns pointer to end of written digits
mov byte[edi], '.'
inc edi
pop eax ; eax = centiseconds (0-99)
mov ebx, 10
xor edx, edx
div ebx ; eax = tens digit, edx = ones digit
add al, '0'
mov [edi], al
inc edi
add dl, '0'
mov [edi], dl
inc edi
mov byte[edi], 0
ret
endp
; ======================================================================
; fmt_mbps: format (bytes, interval_cs) as "X.XX" Mbits/sec into linebuf
; Mbps*100 = (bytes * 8) / (interval_cs * 100)
; ======================================================================
proc fmt_mbps uses ebx ecx edx edi, pbytes, pinterval
mov eax, [pbytes]
cmp eax, 0x1FFFFFFF ; guard against *8 overflow at extreme rates
jb .noshift
shr eax, 4 ; scale down bytes and interval together
mov ecx, [pinterval]
shr ecx, 0 ; (kept for clarity; no shift on interval)
jmp .have_scaled
.noshift:
mov ecx, [pinterval]
.have_scaled:
mov ebx, 8
mul ebx ; edx:eax = bytes*8
mov ebx, ecx
imul ebx, 100 ; ebx = interval_cs * 100
cmp ebx, 0
jnz .divok
mov ebx, 1 ; avoid div-by-zero on a zero-length interval
.divok:
div ebx ; eax = Mbps*100
mov edi, linebuf
mov ebx, 100
xor edx, edx
div ebx ; eax = whole Mbps, edx = fractional (0-99)
push edx
stdcall itoa, eax, edi
mov edi, eax
mov byte[edi], '.'
inc edi
pop eax
mov ebx, 10
xor edx, edx
div ebx
add al, '0'
mov [edi], al
inc edi
add dl, '0'
mov [edi], dl
inc edi
mov byte[edi], 0
ret
endp
; ======================================================================
; itoa: unsigned dword -> decimal ASCII, no leading zeros ("0" for zero)
; in: value, dest buffer pointer
; out: eax = pointer to the byte AFTER the last digit written
; (buffer is NOT null-terminated by this routine)
; ======================================================================
proc itoa uses ebx ecx edx esi, value, dest
mov eax, [value]
mov edi, [dest]
mov ecx, 0 ; digit count
mov ebx, 10
test eax, eax
jnz .divloop
mov byte[edi], '0'
inc edi
mov eax, edi
ret
.divloop:
xor edx, edx
div ebx
push edx
inc ecx
test eax, eax
jnz .divloop
.popl:
pop edx
add dl, '0'
mov [edi], dl
inc edi
loop .popl
mov eax, edi
ret
endp
; data
title db 'kperf',0
str_usage db 'Usage: kperf -c <ip> (client)',10,\
' kperf -s (server)',10,10,0
str_connecting db 'Connecting to ',0
str_connected db 'Connected. Starting test...',10,10,0
str_listening db 'Listening on port 5001...',10,0
str_accepted db 'Accepted connection from ',0
str_conn_closed db 10,'Connection closed.',10,0
str_sockerr db 'Socket error.',10,10,0
str_dnserr db 'Could not parse address.',10,10,0
str_nl db 10,0
str_dash db '-',0
str_zero db '0.00',0
str_sec_tab db ' sec ',0
str_sec_total db ' sec (total) ',0
str_mbits_nl db ' Mbits/sec',10,0
sockaddr1:
dw AF_INET4
.port dw 0
.ip dd 0
rb 10
align 4
@IMPORT:
library network, 'network.obj', console, 'console.obj'
import network, \
getaddrinfo, 'getaddrinfo', \
freeaddrinfo, 'freeaddrinfo', \
inet_ntoa, 'inet_ntoa'
import console, \
con_start, 'START', \
con_init, 'con_init', \
con_write_asciiz, 'con_write_asciiz', \
con_exit, 'con_exit', \
con_getch2, 'con_getch2', \
con_get_flags, 'con_get_flags'
i_end:
socketnum dd ?
socketnum2 dd ?
start_time dd ?
last_report dd ?
interval_bytes dd ?
total_bytes dd ?
linebuf rb 32
cmdline rb 256
sendbuf rb SENDBUFSIZE
recvbuf rb RECVBUFSIZE
align 4
rb 4096 ; stack
mem: