Alexey Ryabov
5593d344cd
This commit adds a new serial port driver that allows other drivers to add or remove ports dynamically and allows applications to use a single serial ports API. It also modifies the usbftdi driver to support the new serial ports API. The driver may conflict with kernel if it is compiled with debug_com_base. Topic on forum https://board.kolibrios.org/viewtopic.php?p=78764 Reviewed-on: #94 Reviewed-by: Gleb Zaharov <sweetbread@coders-squad.com> Reviewed-by: Mikhail Frolov <mixa.frolov2003@gmail.com> Co-authored-by: Alexey Ryabov <alex@b00bl1k.ru> Co-committed-by: Alexey Ryabov <alex@b00bl1k.ru>
676 lines
17 KiB
NASM
Executable File
676 lines
17 KiB
NASM
Executable File
format PE DLL native 0.05
|
|
entry START
|
|
|
|
L_DBG = 1
|
|
L_ERR = 2
|
|
|
|
__DEBUG__ = 0
|
|
__DEBUG_LEVEL__ = L_DBG
|
|
|
|
SERIAL_RING_BUF_SIZE = 32768
|
|
|
|
API_VERSION = 1
|
|
|
|
section '.flat' readable writable executable
|
|
|
|
include '../struct.inc'
|
|
include '../proc32.inc'
|
|
include '../fdo.inc'
|
|
include '../macros.inc'
|
|
include '../peimport.inc'
|
|
|
|
include 'common.inc'
|
|
include 'ring_buf.inc'
|
|
include 'uart16550.inc'
|
|
|
|
struct SERIAL_OBJ
|
|
magic dd ?
|
|
destroy dd ?
|
|
fd dd ?
|
|
bk dd ?
|
|
pid dd ?
|
|
port dd ? ; pointer to SERIAL_PORT
|
|
ends
|
|
|
|
struct SERIAL_PORT
|
|
fd dd ?
|
|
bk dd ?
|
|
id dd ? ; unique port number
|
|
mtx MUTEX
|
|
con dd ? ; pointer to SERIAL_OBJ
|
|
drv dd ? ; pointer to struct SP_DRIVER
|
|
drv_data dd ? ; pointer to driver-defined data
|
|
rx_buf RING_BUF
|
|
tx_buf RING_BUF
|
|
conf SP_CONF
|
|
ends
|
|
|
|
proc START c, reason:dword
|
|
cmp [reason], DRV_ENTRY
|
|
jne .fail
|
|
|
|
mov ecx, port_list_mutex
|
|
invoke MutexInit
|
|
|
|
stdcall uart_probe, 0x3f8, 4
|
|
stdcall uart_probe, 0x2f8, 3
|
|
stdcall uart_probe, 0x3e8, 4
|
|
stdcall uart_probe, 0x2e8, 3
|
|
invoke RegService, drv_name, service_proc
|
|
ret
|
|
|
|
.fail:
|
|
xor eax, eax
|
|
ret
|
|
endp
|
|
|
|
srv_calls:
|
|
dd service_proc.get_version
|
|
dd service_proc.drv_add_port
|
|
dd service_proc.drv_remove_port
|
|
dd service_proc.drv_handle_event
|
|
dd service_proc.open
|
|
dd service_proc.close
|
|
dd service_proc.setup
|
|
dd service_proc.read
|
|
dd service_proc.write
|
|
; TODO enumeration
|
|
srv_calls_end:
|
|
|
|
proc service_proc stdcall uses ebx esi edi, ioctl:dword
|
|
mov edx, [ioctl]
|
|
mov eax, [edx + IOCTL.io_code]
|
|
cmp eax, (srv_calls_end - srv_calls) / 4
|
|
jae .err
|
|
jmp dword [srv_calls + eax * 4]
|
|
|
|
.get_version:
|
|
cmp [edx + IOCTL.out_size], 4
|
|
jb .err
|
|
mov edx, [edx + IOCTL.output]
|
|
mov dword [edx], API_VERSION
|
|
xor eax, eax
|
|
ret
|
|
|
|
.drv_add_port:
|
|
; in:
|
|
; +0: driver
|
|
; +4: driver data
|
|
cmp [edx + IOCTL.inp_size], 8
|
|
jb .err
|
|
mov ebx, [edx + IOCTL.input]
|
|
mov ecx, [ebx]
|
|
mov edx, [ebx + 4]
|
|
call add_port
|
|
ret
|
|
|
|
.drv_remove_port:
|
|
; in:
|
|
; +0: port handle
|
|
cmp [edx + IOCTL.inp_size], 4
|
|
jb .err
|
|
mov ebx, [edx + IOCTL.input]
|
|
mov ecx, [ebx]
|
|
call remove_port
|
|
ret
|
|
|
|
.drv_handle_event:
|
|
; in:
|
|
; +0: port handle
|
|
; +4: event
|
|
; +8: count
|
|
; +12: buf
|
|
cmp [edx + IOCTL.inp_size], 16
|
|
jb .err
|
|
mov ebx, [edx + IOCTL.input]
|
|
mov eax, [ebx]
|
|
mov edx, [ebx + 4]
|
|
mov ecx, [ebx + 8]
|
|
mov esi, [ebx + 12]
|
|
call handle_event
|
|
ret
|
|
|
|
.open:
|
|
; in:
|
|
; +0 port number
|
|
; +4 addr to SERIAL_CONF
|
|
; out:
|
|
; +0 port handle if success
|
|
cmp [edx + IOCTL.inp_size], 8
|
|
jb .err
|
|
cmp [edx + IOCTL.out_size], 4
|
|
jb .err
|
|
mov ebx, [edx + IOCTL.input]
|
|
mov ecx, [edx + IOCTL.output]
|
|
stdcall sp_open, [ebx], [ebx + 4], ecx
|
|
ret
|
|
|
|
.close:
|
|
; in:
|
|
; +0 port handle
|
|
cmp [edx + IOCTL.inp_size], 4
|
|
jb .err
|
|
mov ecx, [edx + IOCTL.input]
|
|
mov ecx, [ecx]
|
|
call sp_close
|
|
ret
|
|
|
|
.setup:
|
|
; in:
|
|
; +0 port handle
|
|
; +4 addr to SERIAL_CONF
|
|
; out:
|
|
; +0 result
|
|
cmp [edx + IOCTL.inp_size], 8
|
|
jb .err
|
|
cmp [edx + IOCTL.out_size], 4
|
|
jb .err
|
|
mov ebx, [edx + IOCTL.input]
|
|
push edx
|
|
mov eax, [ebx]
|
|
mov esi, [ebx + 4]
|
|
call sp_setup
|
|
pop edx
|
|
mov ebx, [edx + IOCTL.output]
|
|
mov [ebx], eax
|
|
ret
|
|
|
|
.read:
|
|
; in:
|
|
; +0 port handle
|
|
; +4 addr of dest buf
|
|
; +8 count to read
|
|
; out:
|
|
; +0 bytes read
|
|
cmp [edx + IOCTL.inp_size], 12
|
|
jb .err
|
|
cmp [edx + IOCTL.out_size], 4
|
|
jb .err
|
|
mov ebx, [edx + IOCTL.input]
|
|
push edx
|
|
mov eax, [ebx]
|
|
mov edi, [ebx + 4]
|
|
mov ecx, [ebx + 8]
|
|
call sp_read
|
|
pop edx
|
|
mov ebx, [edx + IOCTL.output]
|
|
mov [ebx], ecx
|
|
ret
|
|
|
|
.write:
|
|
; in:
|
|
; +0 port handle
|
|
; +4 addr to source buf
|
|
; +8 count to write
|
|
; out:
|
|
; +0 bytes written
|
|
cmp [edx + IOCTL.inp_size], 12
|
|
jb .err
|
|
cmp [edx + IOCTL.out_size], 4
|
|
jb .err
|
|
mov ebx, [edx + IOCTL.input]
|
|
push edx
|
|
mov eax, [ebx]
|
|
mov esi, [ebx + 4]
|
|
mov ecx, [ebx + 8]
|
|
call sp_write
|
|
pop edx
|
|
mov ebx, [edx + IOCTL.output]
|
|
mov [ebx], ecx
|
|
ret
|
|
|
|
.err:
|
|
or eax, -1
|
|
ret
|
|
endp
|
|
|
|
; struct SERIAL_PORT __fastcall *add_port(const struct SP_DRIVER *drv, const void *drv_data);
|
|
align 4
|
|
proc add_port uses edi
|
|
DEBUGF L_DBG, "serial.sys: add port drv=%x drv_data=%x\n", ecx, edx
|
|
|
|
mov eax, [ecx + SP_DRIVER.size]
|
|
cmp eax, sizeof.SP_DRIVER
|
|
jne .fail
|
|
|
|
; alloc memory for serial port descriptor
|
|
push ecx
|
|
push edx
|
|
movi eax, sizeof.SERIAL_PORT
|
|
invoke Kmalloc
|
|
pop edx
|
|
pop ecx
|
|
test eax, eax
|
|
jz .fail
|
|
|
|
; initialize fields of descriptor
|
|
mov edi, eax
|
|
mov [edi + SERIAL_PORT.drv], ecx
|
|
mov [edi + SERIAL_PORT.drv_data], edx
|
|
lea ecx, [edi + SERIAL_PORT.mtx]
|
|
invoke MutexInit
|
|
and [edi + SERIAL_PORT.con], 0
|
|
|
|
mov ecx, port_list_mutex
|
|
invoke MutexLock
|
|
|
|
; TODO obtain unused id's
|
|
mov eax, [port_count]
|
|
mov [edi + SERIAL_PORT.id], eax
|
|
inc [port_count]
|
|
|
|
; add port to linked list
|
|
mov eax, port_list
|
|
mov ecx, [eax + SERIAL_PORT.bk]
|
|
mov [edi + SERIAL_PORT.bk], ecx
|
|
mov [edi + SERIAL_PORT.fd], eax
|
|
mov [ecx + SERIAL_PORT.fd], edi
|
|
mov [eax + SERIAL_PORT.bk], edi
|
|
|
|
DEBUGF L_DBG, "serial.sys: add port %x with id %x\n", edi, [edi + SERIAL_PORT.id]
|
|
|
|
mov ecx, port_list_mutex
|
|
invoke MutexUnlock
|
|
|
|
mov eax, edi
|
|
ret
|
|
|
|
.fail:
|
|
xor eax, eax
|
|
ret
|
|
endp
|
|
|
|
align 4
|
|
; u32 __fastcall *remove_port(struct SERIAL_PORT *port);
|
|
proc remove_port uses esi
|
|
mov esi, ecx
|
|
mov ecx, port_list_mutex
|
|
invoke MutexLock
|
|
|
|
lea ecx, [esi + SERIAL_PORT.mtx]
|
|
invoke MutexLock
|
|
|
|
mov eax, [esi + SERIAL_PORT.con]
|
|
test eax, eax
|
|
jz @f
|
|
push esi
|
|
call sp_destroy
|
|
pop esi
|
|
@@:
|
|
|
|
mov eax, [esi + SERIAL_PORT.fd]
|
|
mov edx, [esi + SERIAL_PORT.bk]
|
|
mov [edx + SERIAL_PORT.fd], eax
|
|
mov [eax + SERIAL_PORT.bk], edx
|
|
DEBUGF L_DBG, "serial.sys: remove port %x with id %x\n", esi, [esi + SERIAL_PORT.id]
|
|
mov eax, esi
|
|
invoke Kfree
|
|
|
|
mov ecx, port_list_mutex
|
|
invoke MutexUnlock
|
|
|
|
xor eax, eax
|
|
ret
|
|
endp
|
|
|
|
align 4
|
|
; @param eax port
|
|
; @param edx event
|
|
; @param ecx count
|
|
; @param esi buffer
|
|
; @return eax count
|
|
proc handle_event uses edi
|
|
mov edi, eax
|
|
cmp edx, SERIAL_EVT_RXNE
|
|
jz .rx
|
|
cmp edx, SERIAL_EVT_TXE
|
|
jz .tx
|
|
xor eax, eax
|
|
jmp .exit
|
|
.rx:
|
|
lea eax, [edi + SERIAL_PORT.rx_buf]
|
|
stdcall ring_buf_write, eax, esi, ecx
|
|
jmp .exit
|
|
.tx:
|
|
lea eax, [edi + SERIAL_PORT.tx_buf]
|
|
stdcall ring_buf_read, eax, esi, ecx
|
|
; fallthrough
|
|
.exit:
|
|
ret
|
|
endp
|
|
|
|
align 4
|
|
proc sp_validate_conf
|
|
mov eax, [ecx + SP_CONF.size]
|
|
cmp eax, sizeof.SP_CONF
|
|
jnz .fail
|
|
mov eax, [ecx + SP_CONF.baudrate]
|
|
test eax, eax
|
|
jz .fail
|
|
mov al, [ecx + SP_CONF.word_size]
|
|
cmp al, 8
|
|
jne .fail ; TODO implement different word size
|
|
mov al, [ecx + SP_CONF.stop_bits]
|
|
cmp al, 1
|
|
jne .fail ; TODO implement different stop bits count
|
|
mov al, [ecx + SP_CONF.parity]
|
|
cmp al, SERIAL_CONF_PARITY_NONE
|
|
jne .fail ; TODO implement parity
|
|
mov al, [ecx + SP_CONF.flow_ctrl]
|
|
cmp al, SERIAL_CONF_FLOW_CTRL_NONE
|
|
jne .fail ; TODO implement flow control
|
|
.ok:
|
|
xor eax, eax
|
|
ret
|
|
.fail:
|
|
or eax, -1
|
|
ret
|
|
endp
|
|
|
|
align 4
|
|
proc sp_open stdcall uses ebx esi edi, port_id:dword, conf:dword, handle:dword
|
|
DEBUGF L_DBG, "serial.sys: sp_open %x %x %x\n", [port_id], [conf], [handle]
|
|
|
|
mov ecx, [conf]
|
|
call sp_validate_conf
|
|
test eax, eax
|
|
jz @f
|
|
mov eax, SERIAL_API_ERR_CONF
|
|
ret
|
|
@@:
|
|
mov edi, [conf]
|
|
|
|
; get access to the serial ports list
|
|
mov ecx, port_list_mutex
|
|
invoke MutexLock
|
|
|
|
; find port by id
|
|
mov eax, [port_id]
|
|
mov esi, port_list
|
|
.find_port:
|
|
mov esi, [esi + SERIAL_PORT.fd]
|
|
cmp esi, port_list
|
|
jz .not_found
|
|
mov ecx, [esi + SERIAL_PORT.id]
|
|
cmp ecx, eax
|
|
jz .found
|
|
jmp .find_port
|
|
|
|
.not_found:
|
|
DEBUGF L_DBG, "serial.sys: port not found\n"
|
|
mov eax, SERIAL_API_ERR_PORT_INVALID
|
|
jmp .unlock_list
|
|
|
|
.found:
|
|
DEBUGF L_DBG, "serial.sys: found port %x\n", esi
|
|
|
|
; get access to serial port
|
|
lea ecx, [esi + SERIAL_PORT.mtx]
|
|
invoke MutexLock
|
|
|
|
; availability check
|
|
cmp [esi + SERIAL_PORT.con], 0
|
|
jz .open
|
|
mov eax, SERIAL_API_ERR_PORT_BUSY
|
|
jmp .unlock_port
|
|
|
|
.open:
|
|
; create rx and tx ring buffers
|
|
lea ecx, [esi + SERIAL_PORT.rx_buf]
|
|
mov edx, SERIAL_RING_BUF_SIZE
|
|
call ring_buf_create
|
|
test eax, eax
|
|
jnz @f
|
|
jmp .unlock_port
|
|
@@:
|
|
lea ecx, [esi + SERIAL_PORT.tx_buf]
|
|
mov edx, SERIAL_RING_BUF_SIZE
|
|
call ring_buf_create
|
|
test eax, eax
|
|
jnz @f
|
|
jmp .free_rx_buf
|
|
@@:
|
|
invoke GetPid
|
|
mov ebx, eax
|
|
mov eax, sizeof.SERIAL_OBJ
|
|
invoke CreateObject
|
|
test eax, eax
|
|
jnz @f
|
|
or eax, -1
|
|
jmp .free_tx_buf
|
|
@@:
|
|
DEBUGF L_DBG, "serial.sys: created object %x\n", eax
|
|
|
|
; save port handle
|
|
mov ecx, [handle]
|
|
mov [ecx], eax
|
|
mov [eax + SERIAL_OBJ.magic], 'UART'
|
|
mov [eax + SERIAL_OBJ.destroy], sp_destroy
|
|
mov [eax + SERIAL_OBJ.port], esi
|
|
|
|
; fill fields
|
|
mov [esi + SERIAL_PORT.con], eax
|
|
; copy conf
|
|
mov eax, [edi + SP_CONF.size]
|
|
mov [esi + SERIAL_PORT.conf + SP_CONF.size], eax
|
|
mov eax, [edi + SP_CONF.baudrate]
|
|
mov [esi + SERIAL_PORT.conf + SP_CONF.baudrate], eax
|
|
mov eax, dword [edi + SP_CONF.word_size]
|
|
mov dword [esi + SERIAL_PORT.conf + SP_CONF.word_size], eax
|
|
|
|
; tell driver about port open
|
|
mov ebx, [esi + SERIAL_PORT.drv]
|
|
mov ecx, [esi + SERIAL_PORT.drv_data]
|
|
stdcall dword [ebx + SP_DRIVER.startup], ecx, edi
|
|
test eax, eax
|
|
jz .unlock_port
|
|
; on error fallthrough
|
|
push eax
|
|
mov eax, [esi + SERIAL_PORT.con]
|
|
invoke DestroyObject
|
|
and [esi + SERIAL_PORT.con], 0
|
|
pop eax
|
|
|
|
.free_tx_buf:
|
|
push eax
|
|
lea ecx, [esi + SERIAL_PORT.tx_buf]
|
|
call ring_buf_destroy
|
|
pop eax
|
|
|
|
.free_rx_buf:
|
|
push eax
|
|
lea ecx, [esi + SERIAL_PORT.rx_buf]
|
|
call ring_buf_destroy
|
|
pop eax
|
|
|
|
.unlock_port:
|
|
push eax
|
|
lea ecx, [esi + SERIAL_PORT.mtx]
|
|
invoke MutexUnlock
|
|
pop eax
|
|
|
|
.unlock_list:
|
|
push eax
|
|
mov ecx, port_list_mutex
|
|
invoke MutexUnlock
|
|
pop eax
|
|
|
|
ret
|
|
endp
|
|
|
|
align 4
|
|
; @param ecx serial port handle
|
|
proc sp_close uses ebx esi
|
|
mov eax, ecx
|
|
cmp [eax + SERIAL_OBJ.magic], 'UART'
|
|
je .ok
|
|
or eax, -1
|
|
ret
|
|
.ok:
|
|
mov ebx, [eax + SERIAL_OBJ.port]
|
|
push eax
|
|
lea ecx, [ebx + SERIAL_PORT.mtx]
|
|
invoke MutexLock
|
|
pop eax
|
|
|
|
push ebx
|
|
call sp_destroy
|
|
pop ebx
|
|
|
|
lea ecx, [ebx + SERIAL_PORT.mtx]
|
|
invoke MutexUnlock
|
|
|
|
xor eax, eax
|
|
ret
|
|
endp
|
|
|
|
align 4
|
|
; @param eax port handle
|
|
; @param esi pointer to SP_CONF
|
|
; @return eax = 0 on success
|
|
proc sp_setup
|
|
test esi, esi
|
|
jz .fail
|
|
cmp [eax + SERIAL_OBJ.magic], 'UART'
|
|
jne .fail
|
|
mov ebx, eax
|
|
mov ecx, esi
|
|
call sp_validate_conf
|
|
test eax, eax
|
|
jz @f
|
|
DEBUGF L_DBG, "serial.sys: invalid conf %x\n", ecx
|
|
mov eax, SERIAL_API_ERR_CONF
|
|
ret
|
|
@@:
|
|
; lock mutex
|
|
mov edi, [ebx + SERIAL_OBJ.port]
|
|
lea ecx, [edi + SERIAL_PORT.mtx]
|
|
invoke MutexLock
|
|
; reconfigure port
|
|
mov eax, [edi + SERIAL_PORT.drv]
|
|
mov ecx, [edi + SERIAL_PORT.drv_data]
|
|
stdcall dword [eax + SP_DRIVER.reconf], ecx, esi
|
|
xor eax, eax
|
|
push eax
|
|
test eax, eax
|
|
jnz @f
|
|
; copy conf if success
|
|
mov eax, [esi + SP_CONF.size]
|
|
mov [edi + SERIAL_PORT.conf + SP_CONF.size], eax
|
|
mov eax, [esi + SP_CONF.baudrate]
|
|
mov [edi + SERIAL_PORT.conf + SP_CONF.baudrate], eax
|
|
mov eax, dword [esi + SP_CONF.word_size]
|
|
mov dword [edi + SERIAL_PORT.conf + SP_CONF.word_size], eax
|
|
@@:
|
|
; unlock mutex
|
|
lea ecx, [edi + SERIAL_PORT.mtx]
|
|
invoke MutexUnlock
|
|
pop eax
|
|
ret
|
|
.fail:
|
|
or eax, -1
|
|
ret
|
|
endp
|
|
|
|
align 4
|
|
; @param eax serial obj
|
|
proc sp_destroy
|
|
mov esi, [eax + SERIAL_OBJ.port]
|
|
DEBUGF L_DBG, "serial.sys: destroy port %x\n", esi
|
|
|
|
invoke DestroyObject
|
|
and [esi + SERIAL_PORT.con], 0
|
|
|
|
; tell driver about port close
|
|
mov ebx, [esi + SERIAL_PORT.drv]
|
|
mov edx, [esi + SERIAL_PORT.drv_data]
|
|
stdcall dword [ebx + SP_DRIVER.shutdown], edx
|
|
|
|
lea ecx, [esi + SERIAL_PORT.tx_buf]
|
|
call ring_buf_destroy
|
|
lea ecx, [esi + SERIAL_PORT.rx_buf]
|
|
call ring_buf_destroy
|
|
ret
|
|
endp
|
|
|
|
|
|
align 4
|
|
; @param eax port handle
|
|
; @param ecx bytes count
|
|
; @param edi address of destination buffer
|
|
; @return eax = 0 on success and ecx = count bytes read
|
|
proc sp_read
|
|
test edi, edi
|
|
jz .fail
|
|
test ecx, ecx
|
|
jz .fail
|
|
cmp [eax + SERIAL_OBJ.magic], 'UART'
|
|
jne .fail
|
|
mov esi, [eax + SERIAL_OBJ.port]
|
|
push ecx ; last arg for ring_buf_read
|
|
lea ecx, [esi + SERIAL_PORT.mtx]
|
|
invoke MutexLock
|
|
lea eax, [esi + SERIAL_PORT.rx_buf]
|
|
stdcall ring_buf_read, eax, edi
|
|
push eax
|
|
lea ecx, [esi + SERIAL_PORT.mtx]
|
|
invoke MutexUnlock
|
|
pop ecx
|
|
xor eax, eax
|
|
ret
|
|
.fail:
|
|
or eax, -1
|
|
ret
|
|
endp
|
|
|
|
align 4
|
|
; @param eax port handle
|
|
; @param ecx bytes count
|
|
; @param esi address of source buffer
|
|
; @return eax = 0 on success and ecx = count bytes written
|
|
proc sp_write
|
|
test esi, esi
|
|
jz .fail
|
|
test ecx, ecx
|
|
jz .fail
|
|
cmp [eax + SERIAL_OBJ.magic], 'UART'
|
|
jne .fail
|
|
|
|
push ecx ; last arg for ring_buf_write
|
|
mov edi, [eax + SERIAL_OBJ.port]
|
|
lea ecx, [edi + SERIAL_PORT.mtx]
|
|
invoke MutexLock
|
|
|
|
lea ecx, [edi + SERIAL_PORT.tx_buf]
|
|
stdcall ring_buf_write, ecx, esi
|
|
push eax
|
|
test eax, eax
|
|
jz @f
|
|
mov ebx, [edi + SERIAL_PORT.drv]
|
|
mov edx, [edi + SERIAL_PORT.drv_data]
|
|
stdcall dword [ebx + SP_DRIVER.tx], edx
|
|
@@:
|
|
lea ecx, [edi + SERIAL_PORT.mtx]
|
|
invoke MutexUnlock
|
|
pop ecx
|
|
xor eax, eax
|
|
ret
|
|
.fail:
|
|
or eax, -1
|
|
ret
|
|
endp
|
|
|
|
drv_name db 'SERIAL', 0
|
|
include_debug_strings
|
|
|
|
align 4
|
|
port_count dd 0
|
|
port_list_mutex MUTEX
|
|
port_list:
|
|
.fd dd port_list
|
|
.bk dd port_list
|
|
|
|
align 4
|
|
data fixups
|
|
end data
|