Files
usbcdc/usbcdc.asm

447 lines
13 KiB
NASM

format PE DLL native 0.05
entry START
include '../../struct.inc'
L_DBG = 1
L_ERR = 2
__DEBUG__ = 1
__DEBUG_LEVEL__ = L_ERR
USB_IFACE_CDCACM = 0x0202
USB_IFACE_CDCDATA = 0x000A
; bmRequestType: OUT, Class, Interface
BM_REQ_TYPE = 0x21
; USBPSTN1.2 Table 19
SET_COMM_FEATURE = 02h
GET_COMM_FEATURE = 03h
CLEAR_COMM_FEATURE = 04h
SET_AUX_LINE_STATE = 10h
SET_HOOK_STATE = 11h
PULSE_SETUP = 12h
SEND_PULSE = 13h
SET_PULSE_TIME = 14h
RING_AUX_JACK = 15h
SET_LINE_CODING = 20h
GET_LINE_CODING = 21h
SET_CONTROL_LINE_STATE = 22h
SEND_BREAK = 23h
SET_RINGER_PARMS = 30h
GET_RINGER_PARMS = 31h
SET_OPERATION_PARMS = 32h
GET_OPERATION_PARMS = 33h
SET_LINE_PARMS = 34h
GET_LINE_PARMS = 35h
DIAL_DIGITS = 36h
; USBPSTN1.2 Table 17
LINE_CODING_CHAR_FORMAT_1BIT = 0
LINE_CODING_CHAR_FORMAT_1_5BITS = 1
LINE_CODING_CHAR_FORMAT_2BITS = 2
LINE_CONFIG_PARITY_NONE = 0
LINE_CONFIG_PARITY_ODD = 1
LINE_CONFIG_PARITY_EVEN = 2
LINE_CONFIG_PARITY_MARK = 3
LINE_CONFIG_PARITY_SPACE = 4
; USBPSTN1.2 Table 17
struct LINE_CODING
dwDTERate dd ?
bCharFormat db ?
bParityType db ?
bDataBits db ?
ends
struct CDC_DEVICE
conf_pipe dd ?
in_ep db ?
out_ep db ?
in_data_sz dw ?
out_data_sz dw ?
in_pipe dd ?
out_pipe dd ?
port_handle dd ?
rx_timer dd ?
tx_buf dd ?
tx_buf_lock dd ?
rx_buf dd ?
rx_buf_lock dd ?
ends
section '.flat' readable writable executable
include '../../proc32.inc'
include '../../fdo.inc'
include '../../macros.inc'
include '../../peimport.inc'
include '../../serial/common.inc'
include '../../../kernel/trunk/bus/usb/protocol.inc'
include '../../../kernel/trunk/bus/usb/common.inc'
proc START c, .reason:dword, .cmdline:dword
cmp [.reason], DRV_ENTRY
jne .fail
invoke GetService, drv_serial_name
test eax, eax
jnz @f
DEBUGF L_ERR, "usbcdc: couldn't load serial.sys driver"
jmp .fail
@@:
mov [serial_drv_entry], eax
DEBUGF L_DBG, "usbcdc: loaded serial driver %x\n", eax
invoke RegUSBDriver, drv_usbcdc_name, service_proc, usb_functions
ret
.fail:
xor eax, eax
ret
endp
proc service_proc stdcall, .ioctl:dword
or eax, -1
ret
endp
proc usb_add_device stdcall uses ebx edi, .config_pipe:dword, \
.config_descr:dword, .interface:dword
; bInterfaceClass and bInterfaceSubClass must match to CDCDATA
mov ebx, [.interface]
cmp word [ebx + usb_interface_descr.bInterfaceClass], USB_IFACE_CDCDATA
jne .fail
; probably a valid interface, so alloc memory to store further information
mov eax, sizeof.CDC_DEVICE
invoke Kmalloc
test eax, eax
jz .fail
mov edi, eax
; two data endpoints must be present
movzx ecx, [ebx + usb_interface_descr.bNumEndpoints]
cmp ecx, 2
jne .fail_and_free
; jump to the end of the interface descriptor
movzx eax, [ebx + usb_interface_descr.bLength]
add ebx, eax
.lookup:
; validate endpoint descriptor
cmp [ebx + usb_endpoint_descr.bLength], sizeof.usb_endpoint_descr
jb .fail_and_free
cmp [ebx + usb_endpoint_descr.bDescriptorType], USB_ENDPOINT_DESCR
jne .fail_and_free
cmp [ebx + usb_endpoint_descr.bmAttributes], 2 ; bulk data
jne .fail_and_free
mov al, [ebx + usb_endpoint_descr.bEndpointAddress]
test al, 0x80
jnz .inp
; save OUT endpoint num and max packet size
mov [edi + CDC_DEVICE.out_ep], al
mov ax, [ebx + usb_endpoint_descr.wMaxPacketSize]
mov [edi + CDC_DEVICE.out_data_sz], ax
jmp .next_ep
.inp:
; save IN endpoint num and max packet size
mov [edi + CDC_DEVICE.in_ep], al
mov ax, [ebx + usb_endpoint_descr.wMaxPacketSize]
mov [edi + CDC_DEVICE.in_data_sz], ax
.next_ep:
movzx eax, [ebx + usb_endpoint_descr.bLength]
add ebx, eax
loop .lookup
DEBUGF L_DBG, "usbcdc: found endpoints: %x (%x) %x (%x)\n", \
[edi + CDC_DEVICE.in_ep]:2, \
[edi + CDC_DEVICE.in_data_sz]:4, \
[edi + CDC_DEVICE.out_ep]:2, \
[edi + CDC_DEVICE.out_data_sz]:4
; initialize fields
mov eax, [.config_pipe]
mov [edi + CDC_DEVICE.conf_pipe], eax
and [edi + CDC_DEVICE.tx_buf], 0
and [edi + CDC_DEVICE.tx_buf_lock], 0
and [edi + CDC_DEVICE.rx_buf], 0
and [edi + CDC_DEVICE.rx_buf_lock], 0
; open input pipe
movzx ecx, [edi + CDC_DEVICE.in_ep]
movzx edx, [edi + CDC_DEVICE.in_data_sz]
invoke USBOpenPipe, [.config_pipe], ecx, edx, BULK_PIPE, 0
test eax, eax
jz .fail_and_free
mov [edi + CDC_DEVICE.in_pipe], eax
; open output pipe
movzx ecx, [edi + CDC_DEVICE.out_ep]
movzx edx, [edi + CDC_DEVICE.out_data_sz]
invoke USBOpenPipe, [.config_pipe], ecx, edx, BULK_PIPE, 0
test eax, eax
jz .fail_and_free
mov [edi + CDC_DEVICE.out_pipe], eax
; TODO interrupt pipe?
mov eax, [serial_drv_entry]
test eax, eax
jz @f
; register new serial port
stdcall serial_add_port, serial_drv, edi
DEBUGF L_DBG, "usbcdc: add serial port with result %x\n", eax
@@:
mov [edi + CDC_DEVICE.port_handle], eax
mov eax, edi
ret
.fail_and_free:
mov eax, edi
invoke Kfree
.fail:
xor eax, eax
ret
endp
proc usb_device_disconnected stdcall uses ebx, .port:dword
DEBUGF L_DBG, "usbcdc: disconnected\n"
mov ebx, [.port]
stdcall serial_remove_port, [ebx + CDC_DEVICE.port_handle]
mov eax, ebx
invoke Kfree
xor eax, eax
ret
endp
proc serial_startup stdcall uses ebx, .port:dword, .conf:dword
DEBUGF L_DBG, "usbcdc: startup %x %x\n", [.port], [.conf]
stdcall serial_reconf, [.port], [.conf]
test eax, eax
jz @f
DEBUGF L_ERR, "usbcdc: uart reconf error %x\n", eax
jmp .exit
@@:
mov ebx, [.port]
invoke TimerHS, 2, 2, serial_rx, ebx
test eax, eax
jnz @f
DEBUGF L_ERR, "usbcdc: timer creation error\n"
or eax, -1
jmp .exit
@@:
mov [ebx + CDC_DEVICE.rx_timer], eax
xor eax, eax
.exit:
ret
endp
proc serial_shutdown stdcall uses ebx, .port:dword
DEBUGF L_DBG, "usbcdc: shutdown %x\n", [.port]
mov ebx, [.port]
cmp [ebx + CDC_DEVICE.rx_timer], 0
jz @f
invoke CancelTimerHS, [ebx + CDC_DEVICE.rx_timer]
@@:
mov eax, [ebx + CDC_DEVICE.tx_buf]
test eax, eax
jz @f
invoke Kfree
@@:
mov eax, [ebx + CDC_DEVICE.rx_buf]
test eax, eax
jz @f
invoke Kfree
@@:
ret
endp
proc control_complete stdcall uses ebx esi edi, .pipe:dword, .status:dword, \
.buffer:dword, .length:dword, .calldata:dword
mov ecx, [.calldata]
mov eax, [ecx]
mov ebx, [ecx + 4]
mov edx, [.status]
mov [ecx + 8], edx
xor esi, esi
xor edx, edx
invoke RaiseEvent
ret
endp
proc serial_reconf stdcall uses ebx edi esi, .port:dword, .conf:dword
locals
.conf_buf rd 4
.event rd 3
endl
xor esi, esi
xor ecx, ecx
invoke CreateEvent
test eax, eax
jnz .event_ok
or eax, -1
jmp .exit
.event_ok:
mov [.event], eax
mov [.event + 4], edx
mov ebx, [.port]
lea edx, [.conf_buf]
mov dword [edx], BM_REQ_TYPE or (SET_LINE_CODING shl 8)
mov dword [edx + 4], sizeof.LINE_CODING shl 16
mov esi, edx
add esi, 8
mov ecx, [.conf]
mov eax, [ecx + SP_CONF.baudrate]
mov [esi + LINE_CODING.dwDTERate], eax
mov al, [ecx + SP_CONF.stop_bits]
dec al ; TODO 0 = 1 stop bit
mov [esi + LINE_CODING.bCharFormat], al
mov al, [ecx + SP_CONF.parity]
mov [esi + LINE_CODING.bParityType], al
mov al, [ecx + SP_CONF.word_size]
mov [esi + LINE_CODING.bDataBits], al
lea edi, [.event]
invoke USBControlTransferAsync, [ebx + CDC_DEVICE.conf_pipe], edx, esi, \
sizeof.LINE_CODING, control_complete, edi, 0
mov eax, [.event]
mov ebx, [.event + 4]
invoke WaitEvent ; TODO WaitEventTimeout?
mov eax, [.event + 8]
; check the status from control_complete callback
cmp eax, USB_STATUS_OK
je .status_ok
DEBUGF L_ERR, "usbcdc: control transfer error %d\n", eax
mov eax, -2
.status_ok:
push eax
mov eax, [.event]
mov ebx, [.event + 4]
invoke DestroyEvent
pop eax
.exit:
ret
endp
proc bulk_out_complete stdcall uses ebx edi esi, .pipe:dword, .status:dword, \
.buffer:dword, .length:dword, .calldata:dword
mov eax, [.status]
test eax, eax
jz @f
DEBUGF L_ERR, "usbcdc: bulk out error %x\n", eax
@@:
mov ebx, [.calldata]
btr [ebx + CDC_DEVICE.tx_buf_lock], 0
stdcall serial_tx, ebx
ret
endp
proc serial_tx stdcall uses ebx esi, .port:dword
xor eax, eax
mov ebx, [.port]
bts [ebx + CDC_DEVICE.tx_buf_lock], 0
jc .exit
mov esi, [ebx + CDC_DEVICE.tx_buf]
test esi, esi
jnz .read
; allocate buffer for bulk_in transfer if not yet
movzx eax, [ebx + CDC_DEVICE.out_data_sz]
invoke Kmalloc
test eax, eax
jz .error
mov [ebx + CDC_DEVICE.tx_buf], eax
mov esi, eax
.read:
movzx eax, [ebx + CDC_DEVICE.out_data_sz]
stdcall serial_handle_event, [ebx + CDC_DEVICE.port_handle], \
SERIAL_EVT_TXE, eax, esi
test eax, eax
jz .unlock
invoke USBNormalTransferAsync, [ebx + CDC_DEVICE.out_pipe], esi, eax, \
bulk_out_complete, ebx, 0
test eax, eax
jz .error
xor eax, eax
jmp .exit
.error:
or eax, -1
.unlock:
btr [ebx + CDC_DEVICE.tx_buf_lock], 0
.exit:
ret
endp
proc bulk_in_complete stdcall uses ebx edi esi, .pipe:dword, .status:dword, \
.buffer:dword, .length:dword, .calldata:dword
mov eax, [.status]
mov ebx, [.calldata]
test eax, eax
jz @f
DEBUGF L_ERR, "usbcdc: bulk in error %x\n", eax
jmp .exit
@@:
mov ecx, [.length]
test ecx, ecx
jz .exit
mov esi, [.buffer]
stdcall serial_handle_event, [ebx + CDC_DEVICE.port_handle], \
SERIAL_EVT_RXNE, ecx, esi
.exit:
btr [ebx + CDC_DEVICE.rx_buf_lock], 0
ret
endp
proc serial_rx stdcall uses ebx esi, .port:dword
xor eax, eax
mov ebx, [.port]
bts [ebx + CDC_DEVICE.rx_buf_lock], 0
jc .exit
mov esi, [ebx + CDC_DEVICE.rx_buf]
test esi, esi
jnz .read
; allocate buffer for bulk_out transfer if not yet
movzx eax, [ebx + CDC_DEVICE.in_data_sz]
invoke Kmalloc
test eax, eax
jz .error
mov [ebx + CDC_DEVICE.rx_buf], eax
mov esi, eax
.read:
movzx eax, [ebx + CDC_DEVICE.in_data_sz]
invoke USBNormalTransferAsync, [ebx + CDC_DEVICE.in_pipe], esi, \
eax, bulk_in_complete, ebx, 1
test eax, eax
jz .error
xor eax, eax
jmp .exit
.error:
btr [ebx + CDC_DEVICE.rx_buf_lock], 0
or eax, -1
.exit:
ret
endp
align 4
usb_functions:
dd usb_functions_end - usb_functions
dd usb_add_device
dd usb_device_disconnected
usb_functions_end:
serial_drv:
dd serial_drv_end - serial_drv
dd serial_startup
dd serial_shutdown
dd serial_reconf
dd serial_tx
serial_drv_end:
drv_usbcdc_name db 'usbcdc', 0
drv_serial_name db 'SERIAL', 0
serial_drv_entry dd 0
include_debug_strings
align 4
data fixups
end data