/kernel/branches/Kolibri-acpi/drivers/usbhid.asm |
---|
File deleted |
/kernel/branches/Kolibri-acpi/drivers/usb/usb.asm |
---|
File deleted |
/kernel/branches/Kolibri-acpi/drivers/usb/urb.inc |
---|
File deleted |
/kernel/branches/Kolibri-acpi/drivers/usb |
---|
Property changes: |
Deleted: svn:ignore |
-*.mnt |
-lang.inc |
-*.bat |
-out.txt |
-scin* |
-*.obj |
/kernel/branches/Kolibri-acpi/drivers/agp.asm |
---|
0,0 → 1,310 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2012. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;; simple AGP driver for KolibriOS ;; |
;; ;; |
;; Written by hidnplayr@kolibrios.org ;; |
;; ;; |
;; GNU GENERAL PUBLIC LICENSE ;; |
;; Version 2, June 1991 ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
format MS COFF |
DEBUG equ 1 |
FAST_WRITE equ 0 ; may cause problems with some motherboards |
include 'proc32.inc' |
include 'imports.inc' |
struc IOCTL |
{ .handle dd ? |
.io_code dd ? |
.input dd ? |
.inp_size dd ? |
.output dd ? |
.out_size dd ? |
} |
virtual at 0 |
IOCTL IOCTL |
end virtual |
public START |
public service_proc |
public version |
DRV_ENTRY equ 1 |
DRV_EXIT equ -1 |
SRV_GETVERSION equ 0 |
SRV_DETECT equ 1 |
API_VERSION equ 1 |
section '.flat' code readable align 16 |
proc START stdcall, state:dword |
cmp [state], 1 |
jne .exit |
.entry: |
if DEBUG |
mov esi, msgInit |
call SysMsgBoardStr |
end if |
stdcall RegService, my_service, service_proc |
ret |
.fail: |
.exit: |
xor eax, eax |
ret |
endp |
handle equ IOCTL.handle |
io_code equ IOCTL.io_code |
input equ IOCTL.input |
inp_size equ IOCTL.inp_size |
output equ IOCTL.output |
out_size equ IOCTL.out_size |
align 4 |
proc service_proc stdcall, ioctl:dword |
mov ebx, [ioctl] |
mov eax, [ebx+io_code] |
cmp eax, SRV_GETVERSION |
jne @F |
mov eax, [ebx+output] |
cmp [ebx+out_size], 4 |
jne .fail |
mov [eax], dword API_VERSION |
xor eax, eax |
ret |
@@: |
mov ebx, [ioctl] |
mov eax, [ebx+io_code] |
cmp eax, SRV_DETECT |
jne @F |
call detect |
@@: |
.fail: |
or eax, -1 |
ret |
endp |
restore handle |
restore io_code |
restore input |
restore inp_size |
restore output |
restore out_size |
align 4 |
proc detect |
locals |
last_bus dd ? |
endl |
mov esi, msgSearch |
call SysMsgBoardStr |
xor eax, eax |
mov [bus], eax |
inc eax |
call PciApi ; get last bus |
cmp eax, -1 |
je .error |
mov [last_bus], eax |
.next_bus: |
and [devfn], 0 |
.next_dev: |
stdcall PciRead16, [bus], [devfn], dword 0x0a ; read class/subclass |
cmp ax, 0x0300 ; display controller - vga compatable controller |
je .found |
cmp ax, 0x0302 ; display controller - 3d controller |
je .found |
cmp ax, 0x0380 ; display controller - other display controller |
je .found |
.next: |
inc [devfn] |
cmp [devfn], 256 |
jb .next_dev |
mov eax, [bus] |
inc eax |
mov [bus], eax |
cmp eax, [last_bus] |
jna .next_bus |
.error: |
mov esi, msgFail |
call SysMsgBoardStr |
xor eax, eax |
inc eax |
ret |
.found: |
stdcall PciRead8, [bus], [devfn], dword 0x06 ; read prog IF |
test al, 1 shl 4 ; got capabilities list? |
jnz .got_capabilities_list |
; TODO: Do it the old way: detect device and check with a list of known capabilities |
; stupid pre PCI 2.2 board.... |
jmp .next |
.got_capabilities_list: |
stdcall PciRead8, [bus], [devfn], dword 0x34 ; read capabilities offset |
and eax, 11111100b ; always dword aligned |
mov edi, eax |
.read_capability: |
stdcall PciRead32, [bus], [devfn], edi ; read capability |
cmp al, 0x02 ; AGP |
je .got_agp |
movzx edi, ah ; pointer to next capability |
test edi, edi |
jnz .read_capability |
jmp .next |
.got_agp: |
shr eax, 16 |
mov [revision], al ; high nibble = major revision |
; low nibble = minor revision |
add edi, 4 |
and al, 0xf0 |
cmp al, 0x30 |
je .agp_3 |
.agp_2: |
mov esi, msgAGP2 |
call SysMsgBoardStr |
stdcall PciRead32, [bus], [devfn], edi ; read AGP status |
.agp_2_: |
test al, 100b |
jnz .100b |
test al, 10b |
jnz .010b |
test al, 1b |
jz .error |
.001b: |
mov [cmd], 001b |
mov esi, msg1 |
call SysMsgBoardStr |
jmp .agp_go |
.010b: |
mov [cmd], 010b |
mov esi, msg2 |
call SysMsgBoardStr |
jmp .agp_go |
.100b: |
mov [cmd], 100b |
mov esi, msg4 |
call SysMsgBoardStr |
jmp .agp_go |
.agp_2m: |
mov esi, msgAGP2m |
call SysMsgBoardStr |
jmp .agp_2_ |
.agp_3: |
mov esi, msgAGP3 |
call SysMsgBoardStr |
stdcall PciRead32, [bus], [devfn], edi ; read AGP status |
test al, 1 shl 3 |
jz .agp_2m |
test eax, 10b |
jnz .8x |
mov [cmd], 01b |
mov esi, msg4 |
call SysMsgBoardStr |
jmp .agp_go |
.8x: |
mov [cmd], 10b |
mov esi, msg8 |
call SysMsgBoardStr |
.agp_go: |
if FAST_WRITE |
test ax, 1 shl 4 |
jz @f |
or [cmd], 1 shl 4 |
mov esi, msgfast |
call SysMsgBoardStr |
@@: |
end if |
test ax, 1 shl 9 ; Side band addressing |
jz @f |
or [cmd], 1 shl 9 |
mov esi, msgside |
call SysMsgBoardStr |
@@: |
add edi, 4 |
mov eax, [cmd] |
or eax, 1 shl 8 ; enable AGP |
stdcall PciWrite32, [bus], [devfn], edi, eax ; write AGP cmd |
mov esi, msgOK |
call SysMsgBoardStr |
ret |
endp |
; initialized data |
align 4 |
version dd (5 shl 16) or (API_VERSION and 0xFFFF) |
my_service db 'AGP', 0 ; max 16 chars include zero |
msgInit db 'AGP driver loaded.', 13, 10, 0 |
msgSearch db 'Searching for AGP card...', 13, 10, 0 |
msgFail db 'device not found', 13, 10, 0 |
msgOK db 'AGP device enabled', 13, 10, 0 |
msgAGP2 db 'AGP2 device found', 13, 10, 0 |
msgAGP3 db 'AGP3 device found', 13, 10, 0 |
msgAGP2m db 'Running in AGP2 mode', 13, 10, 0 |
msg8 db '8x speed', 13, 10, 0 |
msg4 db '4x speed', 13, 10, 0 |
msg2 db '2x speed', 13, 10, 0 |
msg1 db '1x speed', 13, 10, 0 |
msgfast db 'Fast Write', 13, 10, 0 |
msgside db 'Side band addressing', 13, 10, 0 |
section '.data' data readable writable align 16 |
; uninitialized data |
revision db ? |
cmd dd ? |
bus dd ? |
devfn dd ? |
/kernel/branches/Kolibri-acpi/drivers/apm.asm |
---|
0,0 → 1,350 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2009-2011. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;; ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
; 11.09.2009 staper@inbox.ru |
; see kernel\docs\apm.txt |
use32 |
org 0x0 |
db 'MENUET01' |
dd 0x1 |
dd START |
dd I_END |
dd (I_END+100) and not 3 |
dd (I_END+100) and not 3 |
dd 0x0,0x0 |
include 'macros.inc' |
START: |
mcall 40,0x7 |
mcall 49,0x0001,0x0001,0x5308 ;CX = FFFFh APM v1.0 |
; mcall 49,0x0001,0x0001,0x530d |
; mcall 49,0x0001,0x0001,0x530f |
; mcall 49,0x0000,,0x5310 ;bl - number of batteries |
redraw: |
mcall 49,0x0000,,0x530c |
dec cl |
jz still |
mcall 49,0x0001,0x0001,0x5308 |
mcall 49,0x01ff,,0x530c |
test cl, cl |
jz @f |
mcall 49,0x0000,0x0001,0x530d |
mcall 49,0x0000,0x0000,0x5307 |
mcall 49,0x0000,0x0001,0x5308 |
@@: |
mcall 12,1 |
mcall 0,100*65536+235,100*65536+90,0x34ffffff,0x000000,title |
mcall 49,0x0000,,0x5300 |
jnc @f |
mcall 4,10*65536+3,0x80000000,text.4 |
bts [flags], 1 |
jmp .end |
@@: |
cmp al, 0 |
jne @f |
mov edx, text.1 |
jmp .0 |
@@: |
cmp al, 1 |
jne @f |
mov edx, text.2 |
jmp .0 |
@@: |
mov edx, text.3 |
.0: |
push edx |
mcall 4,169*65536+3,0x80dddddd,text.0 |
pop edx |
add ebx, 47*65536 |
mcall |
mcall 49,0x0001,,0x530a |
jc .error |
push si dx cx bx ;time of battery life, b. flag, b. status, AC line status |
;AC line status |
cmp bh, 0 |
jne @f |
mov edx, text.01 |
jmp .1 |
@@: |
cmp bh, 1 |
jne @f |
mov edx, text.02 |
jmp .1 |
@@: |
cmp bh, 2 |
jne @f |
mov edx, text.03 |
jmp .1 |
@@: |
mov edx, text.04 |
.1: |
push edx |
mcall 4,10*65536+10,0x80000000,text.00 |
pop edx |
mcall ,100*65536+10,;0x80000000 |
;battery status |
pop bx |
cmp bl, 0 |
jne @f |
mov edx, text.11 |
jmp .2 |
@@: |
cmp bl, 1 |
jne @f |
mov edx, text.12 |
jmp .2 |
@@: |
cmp bl, 2 |
jne @f |
mov edx, text.13 |
jmp .2 |
@@: |
cmp bl, 3 |
jne @f |
mov edx, text.14 |
jmp .2 |
@@: |
mov edx, text.04 |
.2: |
push edx |
mcall 4,10*65536+20,0x80000000,text.10 |
pop edx |
mcall ,100*65536+20, |
;battery life, percentage and minutes/seconds |
mcall ,10*65536+30,,text.20 |
pop cx |
cmp cl, 0xff |
jne @f |
mcall ,100*65536+30,0x80000000,text.04 |
pop eax |
jmp .end |
@@: |
shl ecx, 24 |
shr ecx, 24 |
mcall 47,0x80030000,,100*65536+30,0x347636 |
.3: |
mcall 4,115*65536+30,0x80000000,text.15 |
mov dx, [esp] |
shl edx, 17 |
shr edx, 17 |
mov ecx, edx |
mcall 47,0x80030000,,140*65536+30 |
pop cx |
mov edx, text.21 |
bt cx, 15 |
jc @f |
mov edx, text.22 |
@@: |
mcall 4,160*65536+30,0x80000000 |
pop si |
.error: |
.end: |
;buttons |
mcall 8,148*65536+16,45*65536+15,3,0x00677ab0 |
mcall ,166*65536+16,,4, |
mcall ,184*65536+16,,5, |
mcall ,202*65536+16,,6, |
bt [flags], 1 |
jc @f |
mcall ,65*65536+45,,2, |
@@: |
mcall 4,10*65536+50,0x80564242,text.30 |
mcall 12,2 |
still: |
; mcall 10 |
mcall 23,12000 |
test eax, eax |
jz redraw |
dec al |
jz redraw |
dec al |
jz key |
dec al |
jz button |
jmp still |
key: |
mcall 2 |
jmp still |
button: |
mcall 17 |
cmp ah, 1 |
jne @f |
mcall -1 |
@@: |
cmp ah, 2 |
jne @f |
mcall 5,50 |
mcall 49,0x0001,0x0001,0x5307 |
jmp redraw |
@@: |
cmp ah, 4 |
jg @f |
mov edx, 0x01f7 ;primary chan. |
call reserv_ports |
jc redraw |
sub bh, 3 |
.1: |
call set_drive |
btc [flags], 2 |
jnc .2 |
call device_reset |
jmp .3 |
.2: |
call standby_hdd |
.3: |
call free_ports |
jmp redraw |
@@: |
cmp ah, 6 |
jg redraw |
mov edx, 0x0177 ;secondary chan. |
call reserv_ports |
jc redraw |
sub bh, 5 |
jmp .1 |
set_drive: |
dec dx |
in al, dx |
test bh, bh |
jnz @f |
btr ax, 4 |
.1: |
out dx, al |
inc dx |
ret |
@@: |
bts ax, 4 |
jmp .1 |
standby_hdd: |
; 94h E0h nondata standby immediate |
; 95h E1h nondata idle immediate |
; 96h E2h nondata standby |
; 97h E3h nondata idle |
; 98h E5h nondata check power mode |
; 99h E6h nondata set sleep mode |
xor ecx, ecx |
@@: |
in al, dx |
dec cx |
jz @f |
bt ax, 6 |
jnc @b |
mov al, 0x96 |
out dx, al |
mov al, 0xe2 |
out dx, al |
@@: |
ret |
reserv_ports: |
mov ecx, edx |
dec ecx |
push ax |
mcall 46,0 |
test al, al |
jnz @f |
pop bx |
clc |
ret |
@@: |
pop bx |
stc |
ret |
device_reset: |
xor ecx, ecx |
@@: |
in al, dx |
dec cx |
jz @f |
bt ax, 6 |
jnc @b |
mov al, 0x10 |
out dx, al |
@@: |
ret |
free_ports: |
mov ecx, edx |
dec ecx |
mcall 46,1 |
ret |
; ДАННЫЕ ПРОГРАММЫ |
title db '',0 |
flags dw 0 |
text: |
.0: |
db 'APM v.1.',0 |
.1: |
db '0',0 |
.2: |
db '1',0 |
.3: |
db '2',0 |
.4: |
db 'APM not supported',0 |
.00: |
db 'power status:',0 |
.01: |
db 'off-line',0 |
.02: |
db 'on-line',0 |
.03: |
db 'on backup power',0 |
.04: |
db 'unknown',0 |
.10: |
db 'battery flag:',0 |
.11: |
db 'high',0 |
.12: |
db 'low',0 |
.13: |
db 'critical',0 |
.14: |
db 'charging',0 |
.15: |
db ' % ,',0 |
.20: |
db 'battery life:',0 |
.21: |
db 'min',0 |
.22: |
db 'sec',0 |
.30: |
db 'STAND-BY: SYSTEM HDD: 0 1 2 3',0 |
I_END: |
/kernel/branches/Kolibri-acpi/drivers/fdo.inc |
---|
0,0 → 1,453 |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
;; ;; |
;; Copyright (C) KolibriOS team 2004-2007. All rights reserved. ;; |
;; Distributed under terms of the GNU General Public License ;; |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; |
_esp equ esp |
; |
; Formatted Debug Output (FDO) |
; Copyright (c) 2005-2006, mike.dld |
; Created: 2005-01-29, Changed: 2006-11-10 |
; |
; For questions and bug reports, mail to mike.dld@gmail.com |
; |
; Available format specifiers are: %s, %d, %u, %x (with partial width support) |
; |
; to be defined: |
; __DEBUG__ equ 1 |
; __DEBUG_LEVEL__ equ 5 |
; MOV Immediate. |
; Useful for things like movi eax,10: |
; shorter than regular mov, but slightly slower, |
; do not use it in performance-critical places. |
macro movi dst, imm |
{ |
if imm >= -0x80 & imm <= 0x7F |
push imm |
pop dst |
else |
mov dst, imm |
end if |
} |
macro debug_func name { |
if used name |
name@of@func equ name |
} |
macro debug_beginf { |
align 4 |
name@of@func: |
} |
debug_endf fix end if |
macro DEBUGS _sign,[_str] { |
common |
local tp |
tp equ 0 |
match _arg:_num,_str \{ |
DEBUGS_N _sign,_num,_arg |
tp equ 1 |
\} |
match =0 _arg,tp _str \{ |
DEBUGS_N _sign,,_arg |
\} |
} |
macro DEBUGS_N _sign,_num,[_str] { |
common |
pushf |
pushad |
local ..str,..label,is_str |
is_str = 0 |
forward |
if _str eqtype '' |
is_str = 1 |
end if |
common |
if is_str = 1 |
jmp ..label |
..str db _str,0 |
..label: |
mov edx, ..str |
else |
esp equ esp+4*8+4 |
mov edx, _str |
esp equ _esp |
end if |
if ~_num eq |
if _num eqtype eax |
if _num in <eax,ebx,ecx,edx,edi,ebp,esp> |
mov esi, _num |
else if ~_num eq esi |
movzx esi, _num |
end if |
else if _num eqtype 0 |
mov esi, _num |
else |
local tp |
tp equ 0 |
match [_arg],_num \{ |
mov esi, dword[_arg] |
tp equ 1 |
\} |
match =0 =dword[_arg],tp _num \{ |
mov esi, dword[_arg] |
tp equ 1 |
\} |
match =0 =word[_arg],tp _num \{ |
movzx esi, word[_arg] |
tp equ 1 |
\} |
match =0 =byte[_arg],tp _num \{ |
movzx esi, byte[_arg] |
tp equ 1 |
\} |
match =0,tp \{ |
'Error: specified string width is incorrect' |
\} |
end if |
else |
mov esi, 0x7FFFFFFF |
end if |
call fdo_debug_outstr |
popad |
popf |
} |
macro DEBUGD _sign,_dec { |
local tp |
tp equ 0 |
match _arg:_num,_dec \{ |
DEBUGD_N _sign,_num,_arg |
tp equ 1 |
\} |
match =0 _arg,tp _dec \{ |
DEBUGD_N _sign,,_arg |
\} |
} |
macro DEBUGD_N _sign,_num,_dec { |
pushf |
pushad |
if (~_num eq) |
if (_dec eqtype eax | _dec eqtype 0) |
'Error: precision allowed only for in-memory variables' |
end if |
if (~_num in <1,2,4>) |
if _sign |
'Error: 1, 2 and 4 are only allowed for precision in %d' |
else |
'Error: 1, 2 and 4 are only allowed for precision in %u' |
end if |
end if |
end if |
if _dec eqtype eax |
if _dec in <ebx,ecx,edx,esi,edi,ebp,esp> |
mov eax, _dec |
else if ~_dec eq eax |
if _sign = 1 |
movsx eax, _dec |
else |
movzx eax, _dec |
end if |
end if |
else if _dec eqtype 0 |
mov eax, _dec |
else |
esp equ esp+4*8+4 |
if _num eq |
mov eax, dword _dec |
else if _num = 1 |
if _sign = 1 |
movsx eax, byte _dec |
else |
movzx eax, byte _dec |
end if |
else if _num = 2 |
if _sign = 1 |
movsx eax, word _dec |
else |
movzx eax, word _dec |
end if |
else |
mov eax, dword _dec |
end if |
esp equ _esp |
end if |
mov cl, _sign |
call fdo_debug_outdec |
popad |
popf |
} |
macro DEBUGH _sign,_hex { |
local tp |
tp equ 0 |
match _arg:_num,_hex \{ |
DEBUGH_N _sign,_num,_arg |
tp equ 1 |
\} |
match =0 _arg,tp _hex \{ |
DEBUGH_N _sign,,_arg |
\} |
} |
macro DEBUGH_N _sign,_num,_hex { |
pushf |
pushad |
if (~_num eq) & (~_num in <1,2,3,4,5,6,7,8>) |
'Error: 1..8 are only allowed for precision in %x' |
end if |
if _hex eqtype eax |
if _hex in <eax,ebx,ecx,edx,esi,edi,ebp,esp> |
if ~_hex eq eax |
mov eax, _hex |
end if |
mov edx, 8 |
else if _hex in <ax,bx,cx,dx,si,di,bp,sp> |
if ~_hex eq ax |
movzx eax, _hex |
end if |
if (_num eq) |
mov edx, 4 |
end if |
else if _hex in <al,ah,bl,bh,cl,ch,dl,dh> |
if ~_hex eq al |
movzx eax, _hex |
end if |
if (_num eq) |
mov edx, 2 |
end if |
end if |
else if _hex eqtype 0 |
mov eax, _hex |
else |
esp equ esp+4*8+4 |
mov eax, dword _hex |
esp equ _esp |
end if |
if ~_num eq |
mov edx, _num |
else |
if ~_hex eqtype eax |
mov edx, 8 |
end if |
end if |
call fdo_debug_outhex |
popad |
popf |
} |
;----------------------------------------------------------------------------- |
debug_func fdo_debug_outchar |
debug_beginf |
pushad |
movzx ecx, al |
mov ebx, 1 |
; mov ecx,sys_msg_board |
; call ecx ; sys_msg_board |
stdcall SysMsgBoard |
popad |
ret |
debug_endf |
debug_func fdo_debug_outstr |
debug_beginf |
mov ebx, 1 |
.l1: |
dec esi |
js .l2 |
movzx ecx, byte[edx] |
or cl, cl |
jz .l2 |
; mov ecx,sys_msg_board |
; call ecx ; sys_msg_board |
stdcall SysMsgBoard |
inc edx |
jmp .l1 |
.l2: |
ret |
debug_endf |
debug_func fdo_debug_outdec |
debug_beginf |
or cl, cl |
jz @f |
or eax, eax |
jns @f |
neg eax |
push eax |
mov al, '-' |
call fdo_debug_outchar |
pop eax |
@@: |
movi ecx, 10 |
push -'0' |
.l1: |
xor edx, edx |
div ecx |
push edx |
test eax, eax |
jnz .l1 |
.l2: |
pop eax |
add al, '0' |
jz .l3 |
call fdo_debug_outchar |
jmp .l2 |
.l3: |
ret |
debug_endf |
debug_func fdo_debug_outhex |
__fdo_hexdigits db '0123456789ABCDEF' |
debug_beginf |
mov cl, dl |
neg cl |
add cl, 8 |
shl cl, 2 |
rol eax, cl |
.l1: |
rol eax, 4 |
push eax |
and eax, 0x0000000F |
mov al, [__fdo_hexdigits+eax] |
call fdo_debug_outchar |
pop eax |
dec edx |
jnz .l1 |
ret |
debug_endf |
;----------------------------------------------------------------------------- |
macro DEBUGF _level,_format,[_arg] { |
common |
if __DEBUG__ = 1 & _level >= __DEBUG_LEVEL__ |
local ..f1,f2,a1,a2,c1,c2,c3,..lbl |
_debug_str_ equ __debug_str_ # a1 |
a1 = 0 |
c2 = 0 |
c3 = 0 |
f2 = 0 |
repeat ..lbl-..f1 |
virtual at 0 |
db _format,0,0 |
load c1 word from %-1 |
end virtual |
if c1 = '%s' |
virtual at 0 |
db _format,0,0 |
store word 0 at %-1 |
load c1 from f2-c2 |
end virtual |
if c1 <> 0 |
DEBUGS 0,_debug_str_+f2-c2 |
end if |
c2 = c2 + 1 |
f2 = %+1 |
DEBUGF_HELPER S,a1,0,_arg |
else if c1 = '%x' |
virtual at 0 |
db _format,0,0 |
store word 0 at %-1 |
load c1 from f2-c2 |
end virtual |
if c1 <> 0 |
DEBUGS 0,_debug_str_+f2-c2 |
end if |
c2 = c2 + 1 |
f2 = %+1 |
DEBUGF_HELPER H,a1,0,_arg |
else if c1 = '%d' | c1 = '%u' |
local c4 |
if c1 = '%d' |
c4 = 1 |
else |
c4 = 0 |
end if |
virtual at 0 |
db _format,0,0 |
store word 0 at %-1 |
load c1 from f2-c2 |
end virtual |
if c1 <> 0 |
DEBUGS 0,_debug_str_+f2-c2 |
end if |
c2 = c2 + 1 |
f2 = %+1 |
DEBUGF_HELPER D,a1,c4,_arg |
else if c1 = '\n' |
c3 = c3 + 1 |
end if |
end repeat |
virtual at 0 |
db _format,0,0 |
load c1 from f2-c2 |
end virtual |
if (c1<>0)&(f2<>..lbl-..f1-1) |
DEBUGS 0,_debug_str_+f2-c2 |
end if |
virtual at 0 |
..f1 db _format,0 |
..lbl: |
__debug_strings equ __debug_strings,_debug_str_,<_format>,..lbl-..f1-1-c2-c3 |
end virtual |
end if |
} |
macro __include_debug_strings dummy,[_id,_fmt,_len] { |
common |
local c1,a1,a2 |
forward |
if defined _len & ~_len eq |
_id: |
a1 = 0 |
a2 = 0 |
repeat _len |
virtual at 0 |
db _fmt,0,0 |
load c1 word from %+a2-1 |
end virtual |
if (c1='%s')|(c1='%x')|(c1='%d')|(c1='%u') |
db 0 |
a2 = a2 + 1 |
else if (c1='\n') |
dw $0A0D |
a1 = a1 + 1 |
a2 = a2 + 1 |
else |
db c1 and 0x0FF |
end if |
end repeat |
db 0 |
end if |
} |
macro DEBUGF_HELPER _letter,_num,_sign,[_arg] { |
common |
local num |
num = 0 |
forward |
if num = _num |
DEBUG#_letter _sign,_arg |
end if |
num = num+1 |
common |
_num = _num+1 |
} |
macro include_debug_strings { |
if __DEBUG__ = 1 |
match dbg_str,__debug_strings \{ |
__include_debug_strings dbg_str |
\} |
end if |
} |
/kernel/branches/Kolibri-acpi/drivers/usbhid/keyboard.inc |
---|
0,0 → 1,475 |
; HID keyboard driver, part of USBHID driver. |
; Global constants. |
; They are assembled in a macro to separate code and data; |
; the code is located at the point of "include 'keyboard.inc'", |
; the data are collected when workers_globals is instantiated. |
macro workers_globals |
{ |
; include global constants from previous workers |
workers_globals |
align 4 |
; Callbacks for HID layer. |
keyboard_driver: |
dd keyboard_driver_add_device |
dd keyboard_driver_disconnect |
dd keyboard_driver_begin_packet |
dd keyboard_driver_array_overflow? |
dd keyboard_driver_input_field |
dd keyboard_driver_end_packet |
; Callbacks for keyboard layer. |
kbd_functions: |
dd 12 |
dd CloseKeyboard |
dd SetKeyboardLights |
; Kernel keyboard layer takes input in form of PS/2 scancodes. |
; data for keyboard: correspondence between HID usage keys and PS/2 scancodes. |
EX = 80h ; if set, precede the scancode with special scancode 0xE0 |
label control_keys byte |
; Usages 700E0h ... 700E7h: LCtrl, LShift, LAlt, LWin, RCtrl, RShift, RAlt, RWin |
db 1Dh, 2Ah, 38h, 5Bh+EX, 1Dh+EX, 36h, 38h+EX, 5Ch+EX |
; Usages 70004h ... 70004h + normal_keys_number - 1 |
label normal_keys byte |
db 1Eh, 30h, 2Eh, 20h, 12h, 21h, 22h, 23h, 17h, 24h, 25h, 26h, 32h, 31h, 18h, 19h |
db 10h, 13h, 1Fh, 14h, 16h, 2Fh, 11h, 2Dh, 15h, 2Ch, 02h, 03h, 04h, 05h, 06h, 07h |
db 08h, 09h, 0Ah, 0Bh, 1Ch, 01h, 0Eh, 0Fh, 39h, 0Ch, 0Dh, 1Ah, 1Bh, 2Bh, 0, 27h |
db 28h, 29h, 33h, 34h, 35h, 3Ah, 3Bh, 3Ch, 3Dh, 3Eh, 3Fh, 40h, 41h, 42h, 43h, 44h |
db 57h, 58h,37h+EX,46h,0,52h+EX,47h+EX,49h+EX,53h+EX,4Fh+EX,51h+EX,4Dh+EX,4Bh+EX,50h+EX,48h+EX,45h |
db 35h+EX,37h,4Ah,4Eh,1Ch+EX,4Fh,50h, 51h, 4Bh, 4Ch, 4Dh, 47h, 48h, 49h, 52h, 53h |
db 0,5Dh+EX,5Eh+EX |
normal_keys_number = $ - normal_keys |
} |
; Data that are specific for one keyboard device. |
struct keyboard_device_data |
handle dd ? ; keyboard handle from RegKeyboard |
timer dd ? ; auto-repeat timer handle |
repeatkey db ? ; auto-repeat key code |
rb 3 ; padding |
usbdev dd ? ; pointer to device_data of USB and HID layers |
modifiers dd ? ; state of LCtrl ... RWin |
led_report dd ? ; output report for LEDs state |
numlock_bit dd ? ; position of NumLock bit in LED output report |
capslock_bit dd ? |
scrolllock_bit dd ? ; guess what |
ends |
; This procedure is called when HID layer detects a new keyboard. |
; in: ebx -> usb_device_data, edi -> collection |
; out: eax = device-specific data or NULL on error |
proc keyboard_driver_add_device |
; 1. Allocate memory for keyboard_device_data. If failed, return NULL. |
movi eax, sizeof.keyboard_device_data |
call Kmalloc |
test eax, eax |
jz .nothing |
; 2. Initialize keyboard_device_data: store pointer to USB layer data, |
; zero some fields, initialize bit positions to -1. |
mov [eax+keyboard_device_data.usbdev], ebx |
xor ecx, ecx |
mov [eax+keyboard_device_data.timer], ecx |
mov [eax+keyboard_device_data.repeatkey], cl |
mov [eax+keyboard_device_data.modifiers], ecx |
mov [eax+keyboard_device_data.led_report], ecx |
dec ecx |
mov [eax+keyboard_device_data.numlock_bit], ecx |
mov [eax+keyboard_device_data.capslock_bit], ecx |
mov [eax+keyboard_device_data.scrolllock_bit], ecx |
; 3. Look for LED report and bits corresponding to indicators. |
; For now, assume that all LEDs are set by the same report. |
; 3a. Save registers. |
push ebx esi |
; 3b. Prepare for loop over output reports: get the first output report. |
; If there are no output records, skip step 3; |
; default values of led_report and *_bit were set in step 2. |
mov edx, [edi+collection.output.first_report] |
test edx, edx |
jz .led_report_set |
.scan_led_report: |
; Process one output report. |
; 3c. Prepare for loop over field groups in the current report: |
; get the first field group. |
mov ecx, [edx+report.first_field] |
.scan_led_field: |
; Process one field group. |
; 3d. If there are no more field groups, exit the loop over field groups. |
test ecx, ecx |
jz .next_led_report |
; For now, assume that all LEDs are plain variable fields, not arrays. |
; 3e. Ignore array field groups. |
test byte [ecx+report_field_group.flags], HID_FIELD_VARIABLE |
jz .next_led_field |
; 3f. Loop over all fields in the current group. |
push [ecx+report_field_group.count] |
; esi = pointer to usage of the current field |
lea esi, [ecx+report_field_group.common_sizeof] |
; ebx = bit position of the current field |
mov ebx, [ecx+report_field_group.offset] |
; if report is numbered, add extra byte in the start of report |
cmp [edx+report.id], 0 |
jz .scan_led_usage |
add ebx, 8 |
.scan_led_usage: |
; for USAGE_LED_*LOCK, store the current bit position in the corresponding field |
; and store the current report as the LED report |
cmp dword [esi], USAGE_LED_NUMLOCK |
jz .numlock |
cmp dword [esi], USAGE_LED_CAPSLOCK |
jz .capslock |
cmp dword [esi], USAGE_LED_SCROLLLOCK |
jnz .next_field |
.scrolllock: |
mov [eax+keyboard_device_data.scrolllock_bit], ebx |
jmp @f |
.capslock: |
mov [eax+keyboard_device_data.capslock_bit], ebx |
jmp @f |
.numlock: |
mov [eax+keyboard_device_data.numlock_bit], ebx |
@@: |
mov [eax+keyboard_device_data.led_report], edx |
.next_field: |
add esi, 4 |
add ebx, [ecx+report_field_group.size] |
dec dword [esp] |
jnz .scan_led_usage |
pop ebx |
.next_led_field: |
; 3g. Continue loop over field groups: get next field group. |
mov ecx, [ecx+report_field_group.next] |
jmp .scan_led_field |
.next_led_report: |
; 3h. If the LED report has been set, break from the loop over reports. |
; Otherwise, get the next report and continue if the current report is not |
; the last for this collection. |
cmp [eax+keyboard_device_data.led_report], 0 |
jnz .led_report_set |
cmp edx, [edi+collection.output.last_report] |
mov edx, [edx+report.next] |
jnz .scan_led_report |
.led_report_set: |
; 3i. Restore registers. |
pop esi ebx |
; 4. Register keyboard in the kernel. |
; store pointer to keyboard_device_data in the stack |
push eax |
; call kernel API |
stdcall RegKeyboard, kbd_functions, eax |
; restore pointer to keyboard_device_data from the stack, |
; putting keyboard handle from API to the stack |
xchg eax, [esp] |
; put keyboard handle from API from the stack to keyboard_device_data field |
pop [eax+keyboard_device_data.handle] |
; If failed, free keyboard_device_data and return NULL. |
cmp [eax+keyboard_device_data.handle], 0 |
jz .fail_free |
; 5. Return pointer to keyboard_device_data. |
.nothing: |
ret |
.fail_free: |
call Kfree |
xor eax, eax |
ret |
endp |
; This procedure is called when HID layer detects disconnect of a previously |
; connected keyboard. |
; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device) |
proc keyboard_driver_disconnect |
; 1. If an autorepeat timer is active, stop it. |
cmp [edi+keyboard_device_data.timer], 0 |
jz @f |
stdcall CancelTimerHS, [edi+keyboard_device_data.timer] |
@@: |
; 2. Unregister keyboard in the kernel. |
stdcall DelKeyboard, [edi+keyboard_device_data.handle] |
; We should free data in CloseKeyboard, not here. |
ret |
endp |
; This procedure is called when HID layer starts processing a new input packet |
; from a keyboard. |
; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device) |
proc keyboard_driver_begin_packet |
; Nothing to do. |
ret |
endp |
; This procedure is called when HID layer processes every non-empty array field group. |
; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device) |
; in: ecx = fields count (always nonzero), edx = pointer to fields values |
; in: esi -> report_field_group |
; out: CF set => group is ok, CF cleared => group should be ignored |
proc keyboard_driver_array_overflow? |
; The keyboard signals array overflow by filling the entire array with |
; USAGE_KBD_ROLLOVER codes. |
mov eax, [edx] ; eax = first field in the array |
sub eax, USAGE_KBD_ROLLOVER ; eax = 0 if overflow, nonzero otherwise |
neg eax ; CF cleared if eax was zero, CF set if eax was nonzero |
ret |
endp |
; This procedure is called from HID layer for every field. |
; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device) |
; in: ecx = field usage, edx = value, esi -> report_field_group |
proc keyboard_driver_input_field |
if HID_DUMP_UNCLAIMED |
.unclaimed = default_driver_input_field |
end if |
; 1. Process normal keys: |
; from USAGE_KBD_FIRST_KEY to USAGE_KBD_FIRST_KEY + normal_keys_number - 1, |
; excluding zeroes in [normal_keys]. |
; 1a. Test whether usage is in the range. |
lea eax, [ecx-USAGE_KBD_FIRST_KEY] |
cmp eax, normal_keys_number |
jae .not_normal_key |
; 1b. If the corresponding entry in [normal_keys] is zero, |
; pass this field to the default handler - if HID_DUMP_UNCLAIMED is enabled, |
; default handler is default_driver_input_field, otherwise just ignore the field. |
cmp [normal_keys + eax], 0 |
jz .unclaimed |
; 1c. Get the scancode. |
movzx ecx, [normal_keys + eax] |
; 1d. Further actions are slightly different for key press and key release. |
; Decide what to do. |
test edx, edx |
jz .normal_key_released |
.normal_key_pressed: |
; The key is pressed. |
; 1e. Store the last pressed key for autorepeat. |
mov [edi+keyboard_device_data.repeatkey], cl |
; 1f. Copy bit 7 to CF and send scancode with bit 7 cleared. |
btr ecx, 7 |
call .send_key |
; 1g. Stop the previous autorepeat timer, if any. |
mov eax, [edi+keyboard_device_data.timer] |
test eax, eax |
jz @f |
stdcall CancelTimerHS, eax |
@@: |
; 1h. Start the new autorepeat timer with 250 ms initial delay |
; and 50 ms subsequent delays. |
stdcall TimerHS, 25, 5, autorepeat_timer, edi |
mov [edi+keyboard_device_data.timer], eax |
if ~HID_DUMP_UNCLAIMED |
.unclaimed: |
end if |
ret |
.normal_key_released: |
; The key is released. |
; 1i. Stop the autorepeat timer if it is autorepeating the released key. |
cmp [edi+keyboard_device_data.repeatkey], cl |
jnz .no_stop_timer |
push ecx |
mov [edi+keyboard_device_data.repeatkey], 0 |
mov eax, [edi+keyboard_device_data.timer] |
test eax, eax |
jz @f |
stdcall CancelTimerHS, eax |
mov [edi+keyboard_device_data.timer], 0 |
@@: |
pop ecx |
.no_stop_timer: |
; 1j. Copy bit 7 to CF and send scancode with bit 7 set. |
bts ecx, 7 |
call .send_key |
ret |
.not_normal_key: |
; 2. USAGE_KBD_NOEVENT is simply a filler for free array fields, |
; ignore it. |
cmp ecx, USAGE_KBD_NOEVENT |
jz .nothing |
; 3. Process modifiers: 8 keys starting at USAGE_KBD_LCTRL. |
; 3a. Test whether usage is in range. |
; If not, we don't know what this field means, so pass it to the default handler. |
lea eax, [ecx-USAGE_KBD_LCTRL] |
cmp eax, 8 |
jae .unclaimed |
; 3b. Further actions are slightly different for modifier press |
; and modifier release. Decide what to do. |
test edx, edx |
jz .modifier_not_pressed |
.modifier_pressed: |
; The modifier is pressed. |
; 3c. Set the corresponding status bit. |
; If it was not set, send the corresponding scancode to the kernel |
; with bit 7 cleared. |
bts [edi+keyboard_device_data.modifiers], eax |
jc @f |
movzx ecx, [control_keys+eax] |
btr ecx, 7 |
call .send_key |
@@: |
.nothing: |
ret |
.modifier_not_pressed: |
; The modifier is not pressed. |
; 3d. Clear the correspodning status bit. |
; If it was set, send the corresponding scancode to the kernel |
; with bit 7 set. |
btr [edi+keyboard_device_data.modifiers], eax |
jnc @f |
movzx ecx, [control_keys+eax] |
bts ecx, 7 |
call .send_key |
@@: |
ret |
; Helper procedure. Sends scancode from cl to the kernel. |
; If CF is set, precede it with special code 0xE0. |
.send_key: |
jnc @f |
push ecx |
mov ecx, 0xE0 |
call SetKeyboardData |
pop ecx |
@@: |
call SetKeyboardData |
ret |
endp |
; This procedure is called when HID layer ends processing a new input packet |
; from a keyboard. |
; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device) |
proc keyboard_driver_end_packet |
; Nothing to do. |
ret |
endp |
; Timer callback for SetTimerHS. |
proc autorepeat_timer |
virtual at esp |
dd ? ; return address |
.data dd ? |
end virtual |
; Just resend the last pressed key. |
mov eax, [.data] |
movzx ecx, [eax+keyboard_device_data.repeatkey] |
; Copy bit 7 to CF and send scancode with bit 7 cleared. |
btr ecx, 7 |
call keyboard_driver_input_field.send_key |
ret 4 |
endp |
; This function is called from the keyboard layer |
; when it is safe to free keyboard data. |
proc CloseKeyboard |
virtual at esp |
dd ? ; return address |
.device_data dd ? |
end virtual |
mov eax, [.device_data] |
call Kfree |
ret 4 |
endp |
; This function is called from the keyboard layer |
; to update LED state on the keyboard. |
proc SetKeyboardLights stdcall uses ebx esi edi, device_data, led_state |
locals |
size dd ? |
endl |
; 1. Get the pointer to the LED report. |
; If there is no LED report, exit from the function. |
mov ebx, [device_data] |
mov esi, [ebx+keyboard_device_data.led_report] |
test esi, esi |
jz .nothing |
; 2. Get report size in bytes. |
; report.size is size in bits without possible report ID; |
; if an ID is assigned, the size is one byte greater. |
mov eax, [esi+report.size] |
add eax, 7 |
shr eax, 3 |
cmp [esi+report.id], 0 |
jz @f |
inc eax |
@@: |
mov [size], eax |
; 3. Allocate memory for report + 8 bytes for setup packet. |
; Dword-align size for subsequent rep stosd and bts. |
; If failed, exit from the function. |
add eax, 8 + 3 |
and eax, not 3 |
push eax |
call Kmalloc |
pop ecx |
test eax, eax |
jz .nothing |
; 4. Zero-initialize output report. |
push eax |
mov edi, eax |
shr ecx, 2 |
xor eax, eax |
rep stosd |
pop edi |
add edi, 8 |
; 5. Store report ID, if assigned. If not assigned, that would just write zero |
; over zeroes. |
mov edx, [esi+report.id] |
mov [edi], edx |
; 6. Set report bits corresponding to active indicators. |
mov eax, [led_state] |
test al, 1 ; PS/2 Scroll Lock |
jz @f |
mov ecx, [ebx+keyboard_device_data.scrolllock_bit] |
test ecx, ecx |
js @f |
bts [edi], ecx |
@@: |
test al, 2 ; PS/2 Num Lock |
jz @f |
mov ecx, [ebx+keyboard_device_data.numlock_bit] |
test ecx, ecx |
js @f |
bts [edi], ecx |
@@: |
test al, 4 ; PS/2 Caps Lock |
jz @f |
mov ecx, [ebx+keyboard_device_data.capslock_bit] |
test ecx, ecx |
js @f |
bts [edi], ecx |
@@: |
; 7. Fill setup packet. |
shl edx, 16 ; move Report ID to byte 2 |
or edx, 21h + \ ; Class-specific request to Interface |
(9 shl 8) + \ ; SET_REPORT |
(2 shl 24) ; Report Type = Output |
lea eax, [edi-8] |
mov ebx, [ebx+keyboard_device_data.usbdev] |
mov dword [eax], edx |
mov edx, [size] |
shl edx, 16 ; move Size to last word |
or edx, [ebx+usb_device_data.interface_number] |
mov [eax+4], edx |
; 8. Submit output control request. |
stdcall USBControlTransferAsync, [ebx+usb_device_data.configpipe], \ |
eax, edi, [size], after_set_keyboard_lights, ebx, 0 |
; If failed, free the buffer now. |
; If succeeded, the callback will free the buffer. |
test eax, eax |
jnz .nothing |
lea eax, [edi-8] |
call Kfree |
.nothing: |
ret |
endp |
; This procedure is called from the USB subsystem when the request initiated by |
; SetKeyboardLights is completed, either successfully or unsuccessfully. |
proc after_set_keyboard_lights |
virtual at esp |
dd ? ; return address |
.pipe dd ? |
.status dd ? |
.buffer dd ? |
.length dd ? |
.calldata dd ? |
end virtual |
; Ignore status, just free the buffer allocated by SetKeyboardLights. |
mov eax, [.buffer] |
sub eax, 8 |
call Kfree |
ret 20 |
endp |
/kernel/branches/Kolibri-acpi/drivers/usbhid/mouse.inc |
---|
0,0 → 1,155 |
; HID mouse driver, part of USBHID driver. |
; Global constants. |
; They are assembled in a macro to separate code and data; |
; the code is located at the point of "include 'mouse.inc'", |
; the data are collected when workers_globals is instantiated. |
macro workers_globals |
{ |
; include global constants from previous workers |
workers_globals |
align 4 |
; Callbacks for HID layer. |
mouse_driver: |
dd mouse_driver_add_device |
dd mouse_driver_disconnect |
dd mouse_driver_begin_packet |
dd mouse_driver_array_overflow? |
dd mouse_driver_input_field |
dd mouse_driver_end_packet |
} |
; Data that are specific for one mouse device. |
struct mouse_device_data |
buttons dd ? ; buttons that are currently pressed |
dx dd ? ; current x moving |
dy dd ? ; current y moving |
wheel dd ? ; current wheel moving |
hwheel dd ? |
ends |
; This procedure is called when HID layer detects a new mouse. |
; in: ebx -> device_data from USB layer, edi -> collection |
; out: eax = device-specific data or NULL on error |
proc mouse_driver_add_device |
; Just allocate memory; no initialization needed. |
movi eax, sizeof.mouse_device_data |
call Kmalloc |
ret |
endp |
; This procedure is called when HID layer detects disconnect of a previously |
; connected mouse. |
; in: edi -> mouse_device_data (pointer returned from mouse_driver_add_device) |
proc mouse_driver_disconnect |
; Free the allocated memory. |
mov eax, edi |
call Kfree |
ret |
endp |
; This procedure is called when HID layer starts processing a new input packet |
; from a mouse. |
; in: edi -> mouse_device_data (pointer returned from mouse_driver_add_device) |
proc mouse_driver_begin_packet |
; Zero all variables describing the current state. |
mov [edi+mouse_device_data.buttons], 0 |
mov [edi+mouse_device_data.dx], 0 |
mov [edi+mouse_device_data.dy], 0 |
mov [edi+mouse_device_data.wheel], 0 |
mov [edi+mouse_device_data.hwheel], 0 |
ret |
endp |
; This procedure is called when HID layer processes every non-empty array field group. |
; in: edi -> mouse_device_data (pointer returned from mouse_driver_add_device) |
; in: ecx = fields count (always nonzero), edx = pointer to fields values |
; in: esi -> report_field_group |
; out: CF set => array is ok, CF cleared => array should be ignored |
proc mouse_driver_array_overflow? |
; no array fields, no overflows |
stc |
ret |
endp |
; This procedure is called from HID layer for every field. |
; in: edi -> mouse_device_data (pointer returned from mouse_driver_add_device) |
; in: ecx = field usage, edx = value, esi -> report_field_group |
proc mouse_driver_input_field |
; 1. Determine the handler. We process x/y moving, wheel and up to 32 buttons. |
; Pass other fields to the default handler - default_driver_input_field if |
; HID_DUMP_UNCLAIMED is enabled, just ignore otherwise. |
cmp ecx, USAGE_GD_X |
jz .x |
cmp ecx, USAGE_GD_Y |
jz .y |
cmp ecx, USAGE_GD_WHEEL |
jz .wheel |
cmp ecx, 0xC0238 |
jz .hwheel |
sub ecx, USAGE_BUTTON_PAGE + 1 |
jb .unclaimed |
cmp ecx, 32 |
jae .unclaimed |
; 2. This is a button. |
; If a button is pressed, set the corresponding bit in the state. |
; If a button is not pressed, do nothing. |
test edx, edx |
jz @f |
bts [edi+mouse_device_data.buttons], ecx |
@@: |
if ~HID_DUMP_UNCLAIMED |
.unclaimed: |
end if |
ret |
if HID_DUMP_UNCLAIMED |
.unclaimed: |
add ecx, USAGE_BUTTON_PAGE + 1 |
jmp default_driver_input_field |
end if |
.x: |
; 3. This is x moving. For relative fields, store the value in the state. |
; Pass absolute field to the default handler. |
test byte [esi+report_field_group.flags], HID_FIELD_RELATIVE |
jz .unclaimed |
mov [edi+mouse_device_data.dx], edx |
ret |
.y: |
; 4. This is y moving. For relative fields, store the value in the state, |
; changing the sign: HID uses "mathematics" scheme with Y axis increasing from |
; bottom to top, the kernel expects "programming" PS/2-style with Y axis |
; increasing from top to bottom. |
; Pass absolute fields to the default handler. |
test byte [esi+report_field_group.flags], HID_FIELD_RELATIVE |
jz .unclaimed |
neg edx |
mov [edi+mouse_device_data.dy], edx |
ret |
.wheel: |
; 5. This is wheel event. For relative fields, store the value in the state, |
; changing the sign. Pass absolute fields to the default handler. |
test byte [esi+report_field_group.flags], HID_FIELD_RELATIVE |
jz .unclaimed |
neg edx |
mov [edi+mouse_device_data.wheel], edx |
ret |
.hwheel: |
test byte [esi+report_field_group.flags], HID_FIELD_RELATIVE |
jz .unclaimed |
mov [edi+mouse_device_data.hwheel], edx |
ret |
endp |
; This procedure is called when HID layer ends processing a new input packet |
; from a mouse. |
; in: edi -> mouse_device_data (pointer returned from mouse_driver_add_device) |
proc mouse_driver_end_packet |
; Call the kernel, passing collected state. |
stdcall SetMouseData, \ |
[edi+mouse_device_data.buttons], \ |
[edi+mouse_device_data.dx], \ |
[edi+mouse_device_data.dy], \ |
[edi+mouse_device_data.wheel], \ |
[edi+mouse_device_data.hwheel] |
ret |
endp |
/kernel/branches/Kolibri-acpi/drivers/usbhid/report.inc |
---|
0,0 → 1,1442 |
; Parser of HID structures: parse HID report descriptor, |
; parse/generate input/output/feature reports. |
; ============================================================================= |
; ================================= Constants ================================= |
; ============================================================================= |
; Usage codes from HID specification |
; Generic Desktop usage page |
USAGE_GD_POINTER = 10001h |
USAGE_GD_MOUSE = 10002h |
USAGE_GD_JOYSTICK = 10004h |
USAGE_GD_GAMEPAD = 10005h |
USAGE_GD_KEYBOARD = 10006h |
USAGE_GD_KEYPAD = 10007h |
USAGE_GD_X = 10030h |
USAGE_GD_Y = 10031h |
USAGE_GD_Z = 10032h |
USAGE_GD_RX = 10033h |
USAGE_GD_RY = 10034h |
USAGE_GD_RZ = 10035h |
USAGE_GD_SLIDER = 10036h |
USAGE_GD_DIAL = 10037h |
USAGE_GD_WHEEL = 10038h |
; Keyboard/Keypad usage page |
USAGE_KBD_NOEVENT = 70000h |
USAGE_KBD_ROLLOVER = 70001h |
USAGE_KBD_POSTFAIL = 70002h |
USAGE_KBD_FIRST_KEY = 70004h ; this is 'A', actually |
USAGE_KBD_LCTRL = 700E0h |
USAGE_KBD_LSHIFT = 700E1h |
USAGE_KBD_LALT = 700E2h |
USAGE_KBD_LWIN = 700E3h |
USAGE_KBD_RCTRL = 700E4h |
USAGE_KBD_RSHIFT = 700E5h |
USAGE_KBD_RALT = 700E6h |
USAGE_KBD_RWIN = 700E7h |
; LED usage page |
USAGE_LED_NUMLOCK = 80001h |
USAGE_LED_CAPSLOCK = 80002h |
USAGE_LED_SCROLLLOCK = 80003h |
; Button usage page |
; First button is USAGE_BUTTON_PAGE+1, second - USAGE_BUTTON_PAGE+2 etc. |
USAGE_BUTTON_PAGE = 90000h |
; Flags for input/output/feature fields |
HID_FIELD_CONSTANT = 1 ; if not, then Data field |
HID_FIELD_VARIABLE = 2 ; if not, then Array field |
HID_FIELD_RELATIVE = 4 ; if not, then Absolute field |
HID_FIELD_WRAP = 8 |
HID_FIELD_NONLINEAR = 10h |
HID_FIELD_NOPREFERRED= 20h ; no preferred state |
HID_FIELD_HASNULL = 40h ; has null state |
HID_FIELD_VOLATILE = 80h ; for output/feature fields |
HID_FIELD_BUFBYTES = 100h; buffered bytes |
; Report descriptor can easily describe gigabytes of (meaningless) data. |
; Keep report size reasonable to avoid excessive memory allocations and |
; calculation overflows; 1 Kb is more than enough (typical size is 3-10 bytes). |
MAX_REPORT_BYTES = 1024 |
; ============================================================================= |
; ================================ Structures ================================= |
; ============================================================================= |
; Every meaningful report field group has one or more associated usages. |
; Usages can be individual or joined into continuous ranges. |
; This structure describes one range or one individual usage in a large array; |
; individual usage is equivalent to a range of length 1. |
struct usage_range |
offset dd ? |
; Sum of range sizes over all previous array items. |
; Size of range a equals |
; [a + sizeof.usage_range + usage_range.offset] - [a + usage_range.offset]. |
; The total sum over all array items immediately follows the array, |
; this field must be the first so that the formula above works for the last item. |
first_usage dd ? |
; Usage code for first item in the range. |
ends |
; This structure describes one group of report fields with identical properties. |
struct report_field_group |
next dd ? |
; All field groups in one report are organized in a single-linked list. |
; This is the next group in the report or 0 for the last group. |
size dd ? |
; Size in bits of one field. Cannot be zero or greater than 32. |
count dd ? ; field count, cannot be zero |
offset dd ? ; offset from report start, in bits |
; Following fields are decoded from report descriptor, see HID spec for details. |
flags dd ? |
logical_minimum dd ? |
logical_maximum dd ? |
physical_minimum dd ? |
physical_maximum dd ? |
unit_exponent dd ? |
unit dd ? |
; Following fields are used to speedup extract_field_value. |
mask dd ? |
; Bitmask for all data bits except sign bit: |
; (1 shl .size) - 1 for unsigned fields, (1 shl (.size-1)) - 1 for signed fields |
sign_mask dd ? |
; Zero for unsigned fields. Bitmask with sign bit set for signed fields. |
common_sizeof rd 0 |
; Variable and Array field groups differ significantly. |
; Variable field groups are simple. There are .count fields, each field has |
; predefined Usage, the content of a field is its value. Each field is |
; always present in the report. For Variable field groups, we just keep |
; additional .count dwords with usages for individual fields. |
; Array field groups are complicated. There are .count uniform fields. |
; The content of a field determines Usage; Usages which are currently presented |
; in the report have value = 1, other Usages have value = 0. The number of |
; possible Usages is limited only by field .size; 32-bit field could encode any |
; Usage, so it is unreasonable to keep all Usages in the plain array, as with |
; Variable fields. However, many unrelated Usages in one group are meaningless, |
; so usually possible values are grouped in sequential ranges; number of ranges |
; is limited by report descriptor size (max 0xFFFF bytes should contain all |
; information, including usage ranges and field descriptions). |
; Also, for Array variables we pass changes in state to drivers, not the state |
; itself, because sending information about all possible Usages is inpractical; |
; so we should remember the previous state in addition to the current state. |
; Thus, for Array variables keep the following information, in this order: |
; * some members listed below; note that they do NOT exist for Variable groups; |
; * array of usage ranges in form of usage_range structures, including |
; an additional dword after array described in usage_range structure; |
; * allocated memory for current values of the report; |
; * values of the previous report. |
num_values_prev dd ? ; number of values in the previous report |
num_usage_ranges dd ? ; number of usage_range, always nonzero |
usages rd 0 |
ends |
; This structure describes one report. |
; All reports of one type are organized into a single-linked list. |
struct report |
next dd ? ; pointer to next report of the same type, if any |
size dd ? ; total size in bits |
first_field dd ? ; pointer to first report_field_group for this report |
last_field dd ? |
; pointer to last report_field_group for this report, if any; |
; address of .first_field, if .first_field is 0 |
id dd ? |
; Report ID, if assigned. Zero otherwise. |
top_level_collection dd ? ; top-level collection for this report |
ends |
; This structure describes a set of reports of the same type; |
; there are 3 sets (possibly empty), input, output and feature. |
struct report_set |
data dd ? |
; If .numbered is zero, this is zero for the empty set and |
; a pointer to the (only) report structure otherwise. |
; If .numbered is nonzero, this is a pointer to 256-dword array of pointers |
; to reports organized by report ID. |
first_report dd ? |
; Pointer to the first report or 0 for the empty set. |
numbered db ? |
; If zero, report IDs are not used, there can be at most one report in the set. |
; If nonzero, first byte of the report is report ID. |
rb 3 ; padding |
ends |
; This structure describes a range of reports of one type that belong to |
; some collection. |
struct collection_report_set |
first_report dd ? |
first_field dd ? |
last_report dd ? |
last_field dd ? |
ends |
; This structure defines driver callbacks which are used while |
; device is active; i.e. all callbacks except add_device. |
struct hid_driver_active_callbacks |
disconnect dd ? |
; Called when an existing HID device is disconnected. |
; |
; Four following functions are called when a new input packet arrives |
; in the following order: .begin_packet, then .input_field several times |
; for each input field, interleaved with .array_overflow? for array groups, |
; then .end_packet. |
begin_packet dd ? |
; edi -> driver data |
array_overflow? dd ? |
; edi -> driver data |
; out: CF cleared <=> ignore this array |
input_field dd ? |
; edi -> driver data, ecx = usage, edx = value |
end_packet dd ? |
; edi -> driver data |
ends |
; This structure describes one collection. |
struct collection |
next dd ? ; pointer to the next collection in the same level |
; must be the first field |
parent dd ? ; pointer to nesting collection |
first_child dd ? ; pointer to the first nested collection |
last_child dd ? ; pointer to the last nested collection |
; or to .first_child, if .first_child is zero |
type dd ? ; Application, Physical etc |
usage dd ? ; associated Usage code |
; Next fields are filled only for top-level collections. |
callbacks hid_driver_active_callbacks |
driver_data dd ? ; value to be passed as is to driver callbacks |
input collection_report_set |
output collection_report_set |
feature collection_report_set |
ends |
; This structure keeps all data used by the HID layer for one device. |
struct hid_data |
input report_set |
output report_set |
feature report_set |
first_collection dd ? |
ends |
; This structure defines callbacks required from the driver. |
struct hid_driver_callbacks |
add_device dd ? |
; Called when a new HID device is connected. |
active hid_driver_active_callbacks |
ends |
; Two following structures describe temporary data; |
; the corresponding objects cease to exist when HID parser completes |
; state of Global items |
struct global_items |
next dd ? |
usage_page dd ? |
logical_minimum dd ? |
logical_maximum dd ? |
physical_minimum dd ? |
physical_maximum dd ? |
unit_exponent dd ? |
unit dd ? |
report_size dd ? |
report_id dd ? |
report_count dd ? |
ends |
; one range of Usages |
struct usage_list_item |
next dd ? |
first_usage dd ? |
num_usages dd ? |
ends |
; ============================================================================= |
; =================================== Code ==================================== |
; ============================================================================= |
macro workers_globals |
{ |
workers_globals |
; Jump tables for switch'ing in the code. |
align 4 |
; jump table for two lower bits which encode size of item data |
parse_descr_label.fetch_jumps: |
dd parse_descr_label.fetch_none ; x0, x4, x8, xC |
dd parse_descr_label.fetch_byte ; x1, x5, x9, xD |
dd parse_descr_label.fetch_word ; x2, x6, xA, xE |
dd parse_descr_label.fetch_dword ; x3, x7, xB, xF |
; jump table for two next bits which encode item type |
parse_descr_label.type_jumps: |
dd parse_descr_label.parse_main |
dd parse_descr_label.parse_global |
dd parse_descr_label.parse_local |
dd parse_descr_label.parse_reserved |
; jump table for 4 upper bits in the case of Main item |
parse_descr_label.main_jumps: |
dd parse_descr_label.input ; 80...83 |
dd parse_descr_label.output ; 90...93 |
dd parse_descr_label.collection ; A0...A3 |
dd parse_descr_label.feature ; B0...B3 |
dd parse_descr_label.end_collection ; C0...C3 |
parse_descr_label.num_main_items = ($ - parse_descr_label.main_jumps) / 4 |
; jump table for 4 upper bits in the case of Global item |
parse_descr_label.global_jumps: |
dd parse_descr_label.usage_page ; 04...07 |
dd parse_descr_label.logical_minimum ; 14...17 |
dd parse_descr_label.logical_maximum ; 24...27 |
dd parse_descr_label.physical_minimum ; 34...37 |
dd parse_descr_label.physical_maximum ; 44...47 |
dd parse_descr_label.unit_exponent ; 54...57 |
dd parse_descr_label.unit ; 64...67 |
dd parse_descr_label.report_size ; 74...77 |
dd parse_descr_label.report_id ; 84...87 |
dd parse_descr_label.report_count ; 94...97 |
dd parse_descr_label.push ; A4...A7 |
dd parse_descr_label.pop ; B4...B7 |
parse_descr_label.num_global_items = ($ - parse_descr_label.global_jumps) / 4 |
; jump table for 4 upper bits in the case of Local item |
parse_descr_label.local_jumps: |
dd parse_descr_label.usage ; 08...0B |
dd parse_descr_label.usage_minimum ; 18...1B |
dd parse_descr_label.usage_maximum ; 28...2B |
dd parse_descr_label.item_parsed ; 38...3B = designator item; ignore |
dd parse_descr_label.item_parsed ; 48...4B = designator minimum; ignore |
dd parse_descr_label.item_parsed ; 58...5B = designator maximum; ignore |
dd parse_descr_label.item_parsed ; 68...6B not assigned |
dd parse_descr_label.item_parsed ; 78...7B = string index; ignore |
dd parse_descr_label.item_parsed ; 88...8B = string minimum; ignore |
dd parse_descr_label.item_parsed ; 98...9B = string maximum; ignore |
dd parse_descr_label.delimiter ; A8...AB |
parse_descr_label.num_local_items = ($ - parse_descr_label.local_jumps) / 4 |
} |
; Local variables for parse_descr. |
macro parse_descr_locals |
{ |
cur_item_size dd ? ; encoded size of data for current item |
report_ok db ? ; 0 on error, 1 if everything is ok |
field_type db ? ; 0/1/2 for input/output/feature fields |
rb 2 ; alignment |
field_data dd ? ; data for current item when it describes a field group |
last_reports rd 3 ; pointers to last input/output/feature records |
usage_minimum dd ? ; current value of Usage Minimum |
usage_list dd ? ; list head of usage_list_item |
usage_tail dd ? ; list tail of usage_list_item |
num_usage_ranges dd ? ; number of usage ranges, size of usage_list |
delimiter_depth dd ? ; normally 0; 1 inside of Delimiter(); |
; nested Delimiter()s are not allowed |
usage_variant dd ? ; 0 outside of Delimiter()s and for first Usage inside Delimiter(), |
; incremented with each new Usage inside Delimiter() |
cur_collection dd ? ; current collection |
last_collection dd ? ; last top-level collection |
} |
; Parse report descriptor. The caller should provide local variables |
; [buffer] = pointer to report descriptor, [length] = length of report descriptor, |
; [calldata] = pointer to hid_data (possibly wrapped in a large structure). |
macro parse_descr |
{ |
parse_descr_label: |
; 1. Initialize. |
; 1a. Set some variables to initial values. |
xor edi, edi |
mov dword [report_ok], edi |
mov [usage_list], edi |
mov [cur_collection], edi |
mov eax, [calldata] |
add eax, hid_data.input.first_report |
mov [last_reports+0*4], eax |
add eax, hid_data.output.first_report - hid_data.input.first_report |
mov [last_reports+1*4], eax |
add eax, hid_data.feature.first_report - hid_data.output.first_report |
mov [last_reports+2*4], eax |
add eax, hid_data.first_collection - hid_data.feature.first_report |
mov [last_collection], eax |
; 1b. Allocate state of global items. |
movi eax, sizeof.global_items |
call Kmalloc |
test eax, eax |
jz .memory_error |
; 1c. Zero-initialize it and move pointer to edi. |
push eax |
xchg eax, edi |
movi ecx, sizeof.global_items / 4 |
rep stosd |
pop edi |
; 1d. Load pointer to data into esi and make [length] point to end of data. |
mov esi, [buffer] |
add [length], esi |
; 2. Clear all local items. |
; This is needed in the beginning and after processing any Main item. |
.zero_local_items: |
mov eax, [usage_list] |
@@: |
test eax, eax |
jz @f |
push [eax+usage_list_item.next] |
call Kfree |
pop eax |
jmp @b |
@@: |
lea ecx, [usage_list] |
mov [usage_tail], ecx |
mov [ecx], eax |
mov [delimiter_depth], eax |
mov [usage_variant], eax |
mov [usage_minimum], eax |
mov [num_usage_ranges], eax |
; 3. Parse items until end of data found. |
cmp esi, [length] |
jae .parse_end |
.fetch_next_item: |
; --------------------------------- Parse item -------------------------------- |
; 4. Parse one item. |
; 4a. Get item data. eax = first item byte = code+type+size (4+2+2 bits), |
; ebx = item data interpreted as unsigned, |
; ecx = item data interpreted as signed. |
movzx eax, byte [esi] |
mov ecx, eax |
and ecx, 3 |
mov [cur_item_size], ecx |
jmp dword [.fetch_jumps+ecx*4] |
.invalid_report: |
mov esi, invalid_report_msg |
jmp .end_str |
.fetch_none: |
xor ebx, ebx |
xor ecx, ecx |
inc esi |
jmp .fetched |
.fetch_byte: |
add esi, 2 |
cmp esi, [length] |
ja .invalid_report |
movzx ebx, byte [esi-1] |
movsx ecx, bl |
jmp .fetched |
.fetch_word: |
add esi, 3 |
cmp esi, [length] |
ja .invalid_report |
movzx ebx, word [esi-2] |
movsx ecx, bx |
jmp .fetched |
.fetch_dword: |
add esi, 5 |
cmp esi, [length] |
ja .invalid_report |
mov ebx, dword [esi-4] |
mov ecx, ebx |
.fetched: |
; 4b. Select the branch according to item type. |
; For every type, select the concrete handler and go there. |
mov edx, eax |
shr edx, 2 |
and edx, 3 |
shr eax, 4 |
jmp dword [.type_jumps+edx*4] |
; -------------------------------- Main items --------------------------------- |
.parse_main: |
sub eax, 8 |
cmp eax, .num_main_items |
jae .item_parsed |
jmp dword [.main_jumps+eax*4] |
; There are 5 Main items. |
; Input/Output/Feature items create new field groups in the corresponding report; |
; Collection item opens a new collection (possibly nested), |
; End Collection item closes the most nested collection. |
.output: |
mov [field_type], 1 |
jmp .new_field |
.feature: |
mov [field_type], 2 |
jmp .new_field |
.input: |
mov [field_type], 0 |
.new_field: |
; Create a new field group. |
mov [field_data], ebx |
movzx ebx, [field_type] |
if sizeof.report_set = 12 |
lea ebx, [ebx*3] |
shl ebx, 2 |
else |
err Change the code |
end if |
add ebx, [calldata] |
; 5. Sanity checks: field size and fields count must be nonzero, |
; field size cannot be more than 32 bits, |
; if field count is more than MAX_REPORT_SIZE * 8, the report would be more than |
; MAX_REPORT_SIZE bytes, so it is invalid too. |
; More precise check for size occurs later; this check only guarantees that |
; there will be no overflows during subsequent calculations. |
cmp [edi+global_items.report_size], 0 |
jz .invalid_report |
cmp [edi+global_items.report_size], 32 |
ja .invalid_report |
; There are devices with Report Count(0) + Input(Constant Variable), |
; zero-length padding. Thus, do not consider descriptors with Report Count(0) |
; as invalid; instead, just ignore fields with Report Count(0). |
cmp [edi+global_items.report_count], 0 |
jz .zero_local_items |
cmp [edi+global_items.report_count], MAX_REPORT_BYTES * 8 |
ja .invalid_report |
; 6. Get the pointer to the place for the corresponding report in ebx. |
; 6a. If report ID is not assigned, ebx already points to report_set.data, |
; so go to 7. |
cmp [edi+global_items.report_id], 0 |
jz .report_ptr_found |
; 6b. If table for reports was already allocated, |
; go to 6d skipping the next substep. |
cmp [ebx+report_set.numbered], 0 |
jnz .report_set_allocated |
; 6c. This is the first report with ID; |
; allocate and zero-initialize table for reports. |
; Note: it is incorrect but theoretically possible that some fields were |
; already allocated in report without ID; if so, abort processing with error. |
cmp [ebx+report_set.data], 0 |
jnz .invalid_report |
mov eax, 256*4 |
call Kmalloc |
test eax, eax |
jz .memory_error |
mov [ebx+report_set.data], eax |
inc [ebx+report_set.numbered] |
push edi |
mov edi, eax |
mov ecx, 256 |
xor eax, eax |
rep stosd |
pop edi |
; 6d. Report ID is assigned, report table is allocated, |
; get the pointer to the corresponding item in the report table. |
.report_set_allocated: |
mov ebx, [ebx+report_set.data] |
mov ecx, [edi+global_items.report_id] |
lea ebx, [ebx+ecx*4] |
; 7. If the field group is the first one in the report, |
; allocate and initialize report without fields. |
.report_ptr_found: |
; 7a. Check whether the report has been allocated. |
cmp dword [ebx], 0 |
jnz .report_allocated |
; 7b. Allocate. |
movi eax, sizeof.report |
call Kmalloc |
test eax, eax |
jz .memory_error |
; 7c. Initialize. |
xor edx, edx |
lea ecx, [eax+report.first_field] |
mov [ebx], eax |
mov [eax+report.next], edx |
mov [eax+report.size], edx |
mov [ecx], edx |
mov [eax+report.last_field], ecx |
mov [eax+report.top_level_collection], edx |
mov ecx, [edi+global_items.report_id] |
mov [eax+report.id], ecx |
; 7d. Append to the overall list of reports. |
movzx edx, [field_type] |
lea edx, [last_reports+edx*4] |
mov ecx, [edx] |
mov [edx], eax |
mov [ecx], eax |
.report_allocated: |
mov ebx, [ebx] |
; ebx points to an already existing report; add new field. |
; 8. Calculate total size of the group and |
; check that the new group would not overflow the report. |
mov eax, [edi+global_items.report_size] |
mul [edi+global_items.report_count] |
mov ecx, [ebx+report.size] |
add ecx, eax |
cmp ecx, MAX_REPORT_BYTES * 8 |
ja .invalid_report |
; 9. If there are no usages for this group, this is padding; |
; add it's size to total report size and stop processing. |
cmp [num_usage_ranges], 0 |
jz .padding |
; 10. Allocate memory for the group: this includes field group structure |
; and additional fields depending on field type. |
; See comments in report_field_group structure. |
push eax |
mov edx, [edi+global_items.report_count] |
lea eax, [report_field_group.common_sizeof+edx*4] |
test byte [field_data], HID_FIELD_VARIABLE |
jnz @f |
lea eax, [eax+edx*4] |
mov edx, [num_usage_ranges] |
lea eax, [eax+edx*sizeof.usage_range+4] |
@@: |
call Kmalloc |
pop edx |
test eax, eax |
jz .memory_error |
; 11. Update report data. |
; Field offset is the current report size; |
; get the current report size and update report size. |
; Also store the pointer to new field in the previous last field |
; and update the last field. |
mov ecx, [ebx+report.last_field] |
xadd [ebx+report.size], edx |
mov [ebx+report.last_field], eax |
mov [ecx], eax |
; 12. Initialize field data: offset was calculated in the previous step, |
; copy other characteristics from global_items data, |
; calculate .mask and .sign_mask. |
mov [eax+report_field_group.offset], edx |
xor edx, edx |
mov [eax+report_field_group.next], edx |
mov [eax+report_field_group.sign_mask], edx |
inc edx |
mov ecx, [edi+global_items.report_size] |
mov [eax+report_field_group.size], ecx |
shl edx, cl |
cmp [edi+global_items.logical_minimum], 0 |
jge .unsigned |
shr edx, 1 |
mov [eax+report_field_group.sign_mask], edx |
.unsigned: |
dec edx |
mov [eax+report_field_group.mask], edx |
mov ecx, [edi+global_items.report_count] |
mov [eax+report_field_group.count], ecx |
mov ecx, [field_data] |
mov [eax+report_field_group.flags], ecx |
irps field, logical_minimum logical_maximum physical_minimum physical_maximum unit_exponent unit |
\{ |
mov ecx, [edi+global_items.\#field] |
mov [eax+report_field_group.\#field], ecx |
\} |
; 13. Update the current collection; nesting collections will be updated by |
; end-of-collection handler. |
movzx edx, [field_type] |
if sizeof.collection_report_set = 16 |
shl edx, 4 |
else |
err Change the code |
end if |
mov ecx, [cur_collection] |
test ecx, ecx |
jz .no_collection |
lea ecx, [ecx+collection.input+edx] |
mov [ecx+collection_report_set.last_report], ebx |
mov [ecx+collection_report_set.last_field], eax |
cmp [ecx+collection_report_set.first_field], 0 |
jnz .no_collection |
mov [ecx+collection_report_set.first_report], ebx |
mov [ecx+collection_report_set.first_field], eax |
.no_collection: |
; 14. Transform usage ranges. The target format depends on field type. |
test byte [eax+report_field_group.flags], HID_FIELD_VARIABLE |
jz .transform_usages_for_array |
; For Variable field groups, expand all ranges to array with .count Usages. |
; If total number of Usages in all ranges is too large, ignore excessive. |
; If total number of Usages in all ranges is too small, duplicate the last |
; Usage up to .count Usages (e.g. group of several indicators can have one usage |
; "Generic Indicator" assigned to all fields). |
mov ecx, [eax+report_field_group.count] |
mov ebx, [usage_list] |
.next_usage_range_for_variable: |
mov edx, [ebx+usage_list_item.first_usage] |
push [ebx+usage_list_item.num_usages] |
.next_usage_for_variable: |
mov [eax+report_field_group.common_sizeof], edx |
dec ecx |
jz @f |
add eax, 4 |
inc edx |
dec dword [esp] |
jnz .next_usage_for_variable |
dec edx |
inc dword [esp] |
cmp [ebx+usage_list_item.next], 0 |
jz .next_usage_for_variable |
pop edx |
mov ebx, [ebx+usage_list_item.next] |
jmp .next_usage_range_for_variable |
@@: |
pop ebx |
jmp .zero_local_items |
.transform_usages_for_array: |
; For Array field groups, leave ranges unexpanded, but recode in the form |
; more convenient to value lookup, see comments in report_field_group structure. |
mov ecx, [num_usage_ranges] |
mov [eax+report_field_group.num_usage_ranges], ecx |
and [eax+report_field_group.num_values_prev], 0 |
mov ecx, [usage_list] |
xor ebx, ebx |
@@: |
mov edx, [ecx+usage_list_item.first_usage] |
mov [eax+report_field_group.usages+usage_range.offset], ebx |
add ebx, [ecx+usage_list_item.num_usages] |
jc .invalid_report |
mov [eax+report_field_group.usages+usage_range.first_usage], edx |
add eax, sizeof.usage_range |
mov ecx, [ecx+usage_list_item.next] |
test ecx, ecx |
jnz @b |
mov [eax+report_field_group.usages], ebx |
; New field is initialized. |
jmp .zero_local_items |
.padding: |
mov [ebx+report.size], ecx |
jmp .zero_local_items |
; Create a new collection, nested in the current one. |
.collection: |
; Actions are quite straightforward: |
; allocate, zero-initialize, update parent, if there is one, |
; make it current. |
movi eax, sizeof.collection |
call Kmalloc |
test eax, eax |
jz .memory_error |
push eax edi |
movi ecx, sizeof.collection / 4 |
xchg edi, eax |
xor eax, eax |
rep stosd |
pop edi eax |
mov edx, [cur_collection] |
mov [eax+collection.parent], edx |
lea ecx, [last_collection] |
test edx, edx |
jz .no_parent |
lea ecx, [edx+collection.last_child] |
.no_parent: |
mov edx, [ecx] |
mov [ecx], eax |
mov [edx], eax |
lea ecx, [eax+collection.first_child] |
; In theory, there must be at least one usage. |
; In practice, some nested collections don't have any. Use zero in this case. |
mov edx, [usage_list] |
test edx, edx |
jz @f |
mov edx, [edx+usage_list_item.first_usage] |
@@: |
mov [eax+collection.last_child], ecx |
mov [eax+collection.type], ebx |
mov [eax+collection.usage], edx |
mov [cur_collection], eax |
jmp .zero_local_items |
; Close the current collection. |
.end_collection: |
; There must be an opened collection. |
mov eax, [cur_collection] |
test eax, eax |
jz .invalid_report |
; Make parent collection the current one. |
mov edx, [eax+collection.parent] |
mov [cur_collection], edx |
; Add field range of the closing collection to field range for nesting collection, |
; if there is one. |
test edx, edx |
jz .zero_local_items |
push 3 ; for each type: input, output, feature |
.update_ranges: |
mov ecx, [eax+collection.input.last_report] |
test ecx, ecx |
jz .no_fields |
mov [edx+collection.input.last_report], ecx |
mov ecx, [eax+collection.input.last_field] |
mov [edx+collection.input.last_field], ecx |
cmp [edx+collection.input.first_report], 0 |
jnz .no_fields |
mov ecx, [eax+collection.input.first_report] |
mov [edx+collection.input.first_report], ecx |
mov ecx, [eax+collection.input.first_field] |
mov [edx+collection.input.first_field], ecx |
.no_fields: |
add eax, sizeof.collection_report_set |
add edx, sizeof.collection_report_set |
dec dword [esp] |
jnz .update_ranges |
pop eax |
jmp .zero_local_items |
; ------------------------------- Global items -------------------------------- |
.parse_global: |
cmp eax, .num_global_items |
jae .item_parsed |
jmp dword [.global_jumps+eax*4] |
; For most global items, just store the value in the current global_items structure. |
; Note 1: Usage Page will be used for upper word of Usage[| Minimum|Maximum], so |
; shift it in advance. |
; Note 2: the HID specification allows both signed and unsigned values for |
; logical and physical minimum/maximum, but does not give a method to distinguish. |
; Thus, hope that minimum comes first, parse the minimum as signed value always, |
; if it is less than zero, assume signed values, otherwise assume unsigned values. |
; This covers both common cases Minimum(0)/Maximum(FF) and Minimum(-7F)/Maximum(7F). |
; Note 3: zero value for Report ID is forbidden by the HID specification. |
; It is quite convenient, we use report_id == 0 for reports without ID. |
.usage_page: |
shl ebx, 16 |
mov [edi+global_items.usage_page], ebx |
jmp .item_parsed |
.logical_minimum: |
mov [edi+global_items.logical_minimum], ecx |
jmp .item_parsed |
.logical_maximum: |
cmp [edi+global_items.logical_minimum], 0 |
jge @f |
mov ebx, ecx |
@@: |
mov [edi+global_items.logical_maximum], ebx |
jmp .item_parsed |
.physical_minimum: |
mov [edi+global_items.physical_minimum], ecx |
jmp .item_parsed |
.physical_maximum: |
cmp [edi+global_items.physical_maximum], 0 |
jge @f |
mov ebx, ecx |
@@: |
mov [edi+global_items.physical_maximum], ebx |
jmp .item_parsed |
.unit_exponent: |
mov [edi+global_items.unit_exponent], ecx |
jmp .item_parsed |
.unit: |
mov [edi+global_items.unit], ebx |
jmp .item_parsed |
.report_size: |
mov [edi+global_items.report_size], ebx |
jmp .item_parsed |
.report_id: |
test ebx, ebx |
jz .invalid_report |
cmp ebx, 0x100 |
jae .invalid_report |
mov [edi+global_items.report_id], ebx |
jmp .item_parsed |
.report_count: |
mov [edi+global_items.report_count], ebx |
jmp .item_parsed |
; Two special global items: Push/Pop. |
.push: |
; For Push, allocate new global_items structure, |
; initialize from the current one and make it current. |
movi eax, sizeof.global_items |
call Kmalloc |
test eax, eax |
jz .memory_error |
push esi eax |
movi ecx, sizeof.global_items / 4 |
mov esi, edi |
xchg eax, edi |
rep movsd |
pop edi esi |
mov [edi+global_items.next], eax |
jmp .item_parsed |
.pop: |
; For Pop, restore the last global_items structure and free the current one. |
mov eax, [edi+global_items.next] |
test eax, eax |
jz .invalid_report |
push eax |
xchg eax, edi |
call Kfree |
pop edi |
jmp .item_parsed |
; -------------------------------- Local items -------------------------------- |
.parse_local: |
cmp eax, .num_local_items |
jae .item_parsed |
jmp dword [.local_jumps+eax*4] |
.usage: |
; Usage tag. |
; If length is 0, 1, 2 bytes, append the global item Usage Page. |
cmp [cur_item_size], 2 |
ja @f |
or ebx, [edi+global_items.usage_page] |
@@: |
; If inside Delimiter(), ignore everything except the first tag. |
cmp [delimiter_depth], 0 |
jz .usage.write |
inc [usage_variant] |
cmp [usage_variant], 1 |
jnz .item_parsed |
.usage.write: |
; Add new range with start = item data and length = 1. |
mov [usage_minimum], ebx |
push 1 |
.new_usage: |
movi eax, sizeof.usage_list_item |
call Kmalloc |
pop edx |
test eax, eax |
jz .memory_error |
inc [num_usage_ranges] |
mov ecx, [usage_minimum] |
and [eax+usage_list_item.next], 0 |
mov [eax+usage_list_item.first_usage], ecx |
mov [eax+usage_list_item.num_usages], edx |
mov ecx, [usage_tail] |
mov [usage_tail], eax |
mov [ecx], eax |
jmp .item_parsed |
.usage_minimum: |
; Usage Minimum tag. Just store in the local var. |
; If length is 0, 1, 2 bytes, append the global item Usage Page. |
cmp [cur_item_size], 2 |
ja @f |
or ebx, [edi+global_items.usage_page] |
@@: |
mov [usage_minimum], ebx |
jmp .item_parsed |
.usage_maximum: |
; Usage Maximum tag. |
; If length is 0, 1, 2 bytes, append the global item Usage Page. |
cmp [cur_item_size], 2 |
ja @f |
or ebx, [edi+global_items.usage_page] |
@@: |
; Meaningless inside Delimiter(). |
cmp [delimiter_depth], 0 |
jnz .invalid_report |
; Add new range with start = saved Usage Minimum and |
; length = Usage Maximum - Usage Minimum + 1. |
sub ebx, [usage_minimum] |
inc ebx |
push ebx |
jmp .new_usage |
.delimiter: |
; Delimiter tag. |
test ebx, ebx |
jz .delimiter.close |
; Delimiter(Opened). |
; Store that we are inside Delimiter(), |
; say a warning that only preferred Usage will be used. |
cmp [delimiter_depth], 0 |
jnz .invalid_report |
inc [delimiter_depth] |
push esi |
mov esi, delimiter_note |
call SysMsgBoardStr |
pop esi |
jmp .item_parsed |
.delimiter.close: |
; Delimiter(Closed). |
; Store that we are not inside Delimiter() anymore. |
dec [delimiter_depth] |
js .invalid_report |
and [usage_variant], 0 |
jmp .item_parsed |
.parse_reserved: |
; Ignore reserved items, except that tag 0xFE means long item |
; with first data byte = length of additional data, |
; second data byte = long item tag. No long items are defined yet, |
; so just skip them. |
cmp eax, 0xF |
jnz .item_parsed |
cmp [cur_item_size], 2 |
jnz .item_parsed |
movzx ecx, bl |
add esi, ecx |
cmp esi, [length] |
ja .invalid_report |
.item_parsed: |
cmp esi, [length] |
jb .fetch_next_item |
.parse_end: |
;-------------------------------- End of parsing ------------------------------ |
; If there are opened collections, it is invalid report. |
cmp [cur_collection], 0 |
jnz .invalid_report |
; There must be at least one input field. |
mov eax, [calldata] |
add eax, hid_data.input.first_report |
cmp [last_reports+0*4], eax |
jz .invalid_report |
; Everything is ok. |
inc [report_ok] |
jmp .end |
.memory_error: |
mov esi, nomemory_msg |
.end_str: |
call SysMsgBoardStr |
.end: |
; Free all global_items structures. |
test edi, edi |
jz @f |
push [edi+global_items.next] |
xchg eax, edi |
call Kfree |
pop edi |
jmp .end |
@@: |
; Free the last Usage list, if any. |
mov eax, [usage_list] |
@@: |
test eax, eax |
jz @f |
push [eax+usage_list_item.next] |
call Kfree |
pop eax |
jmp @b |
@@: |
} |
; Assign drivers to top-level HID collections. |
; The caller should provide ebx = pointer to hid_data and a local variable |
; [has_driver], it will be initialized with 0 if no driver is present. |
macro postprocess_descr |
{ |
postprocess_report_label: |
; Assign drivers to top-level collections. |
; Use mouse driver for Usage(GenericDesktop:Mouse), |
; use keyboard driver for Usage(GenericDesktop:Keyboard) |
; and Usage(GenericDesktop:Keypad) |
; 1. Prepare for the loop: get the pointer to the first collection, |
; store that no drivers were assigned yet. |
mov edi, [ebx+hid_data.first_collection] |
if ~HID_DUMP_UNCLAIMED |
mov [has_driver], 0 |
end if |
.next_collection: |
; 2. Test whether there is a collection to test; if no, break from the loop. |
test edi, edi |
jz .postprocess_done |
; 3. Get pointer to driver callbacks depending on [collection.usage]. |
; If [collection.usage] is unknown, use default driver if HID_DUMP_UNCLAIMED |
; and do not assign a driver otherwise. |
mov esi, mouse_driver |
cmp [edi+collection.usage], USAGE_GD_MOUSE |
jz .has_driver |
mov esi, keyboard_driver |
cmp [edi+collection.usage], USAGE_GD_KEYBOARD |
jz .has_driver |
cmp [edi+collection.usage], USAGE_GD_KEYPAD |
jz .has_driver |
if HID_DUMP_UNCLAIMED |
mov esi, default_driver |
else |
xor esi, esi |
end if |
; 4. If no driver is assigned (possible only if not HID_DUMP_UNCLAIMED), |
; go to 7 with driver data = 0; |
; other code uses this as a sign that driver callbacks should not be called. |
.has_driver: |
xor eax, eax |
if ~HID_DUMP_UNCLAIMED |
test esi, esi |
jz .set_driver |
end if |
; 5. Notify the driver about new device. |
call [esi+hid_driver_callbacks.add_device] |
; 6. If the driver has returned non-zero driver data, |
; store that is an assigned driver. |
; Otherwise, if HID_DUMP_UNCLAIMED, try to assign the default driver. |
if HID_DUMP_UNCLAIMED |
test eax, eax |
jnz .set_driver |
mov esi, default_driver |
call [esi+hid_driver_callbacks.add_device] |
else |
test eax, eax |
jz @f |
mov [has_driver], 1 |
jmp .set_driver |
@@: |
xor esi, esi |
end if |
.set_driver: |
; 7. Store driver data. If a driver is assigned, copy driver callbacks. |
mov [edi+collection.driver_data], eax |
test esi, esi |
jz @f |
push edi |
lodsd ; skip hid_driver_callbacks.add_device |
add edi, collection.callbacks |
repeat sizeof.hid_driver_active_callbacks / 4 |
movsd |
end repeat |
pop edi |
@@: |
; 8. Store pointer to the collection in all input reports belonging to it. |
; Note that the HID spec requires that reports should not cross top-level collections. |
mov eax, [edi+collection.input.first_report] |
test eax, eax |
jz .reports_processed |
.next_report: |
mov [eax+report.top_level_collection], edi |
cmp eax, [edi+collection.input.last_report] |
mov eax, [eax+report.next] |
jnz .next_report |
.reports_processed: |
mov edi, [edi+collection.next] |
jmp .next_collection |
.postprocess_done: |
} |
; Cleanup all resources allocated during parse_descr and postprocess_descr. |
; Called when the corresponding device is disconnected |
; with ebx = pointer to hid_data. |
macro hid_cleanup |
{ |
; 1. Notify all assigned drivers about disconnect. |
; Loop over all top-level collections and call callbacks.disconnect, |
; if a driver is assigned. |
mov esi, [ebx+hid_data.first_collection] |
.notify_drivers: |
test esi, esi |
jz .notify_drivers_done |
mov edi, [esi+collection.driver_data] |
test edi, edi |
jz @f |
call [esi+collection.callbacks.disconnect] |
@@: |
mov esi, [esi+collection.next] |
jmp .notify_drivers |
.notify_drivers_done: |
; 2. Free all collections. |
mov esi, [ebx+hid_data.first_collection] |
.free_collections: |
test esi, esi |
jz .collections_done |
; If a collection has childen, make it forget about them, |
; kill all children; after last child is killed, return to |
; the collection as a parent; this time, it will appear |
; as childless, so it will be killed after children. |
mov eax, [esi+collection.first_child] |
test eax, eax |
jz .no_children |
and [esi+collection.first_child], 0 |
xchg esi, eax |
jmp .free_collections |
.no_children: |
; If a collection has no children (maybe there were no children at all, |
; maybe all children were already killed), kill it and proceed either to |
; next sibling (if any) or to the parent. |
mov eax, [esi+collection.next] |
test eax, eax |
jnz @f |
mov eax, [esi+collection.parent] |
@@: |
xchg eax, esi |
call Kfree |
jmp .free_collections |
.collections_done: |
; 3. Free all three report sets. |
push 3 |
lea esi, [ebx+hid_data.input] |
; For every report set, loop over all reports, |
; for every report free all field groups, then free report itself. |
; When all reports in one set have been freed, free also report list table, |
; if there is one (reports are numbered). |
.report_set_loop: |
mov edi, [esi+report_set.first_report] |
.report_loop: |
test edi, edi |
jz .report_done |
mov eax, [edi+report.first_field] |
.field_loop: |
test eax, eax |
jz .field_done |
push [eax+report_field_group.next] |
call Kfree |
pop eax |
jmp .field_loop |
.field_done: |
mov eax, [edi+report.next] |
xchg eax, edi |
call Kfree |
jmp .report_loop |
.report_done: |
cmp [esi+report_set.numbered], 0 |
jz @f |
mov eax, [esi+report_set.data] |
call Kfree |
@@: |
add esi, sizeof.report_set |
dec dword [esp] |
jnz .report_set_loop |
pop eax |
} |
; Helper for parse_input. Extracts value of one field. |
; in: esi -> report_field_group |
; in: eax = offset in bits from report start |
; in: report -> report data |
; out: edx = value |
; Note: it can read one dword past report data. |
macro extract_field_value report |
{ |
mov ecx, eax |
shr eax, 5 |
shl eax, 2 |
add eax, report |
and ecx, 31 |
mov edx, [eax] |
mov eax, [eax+4] |
shrd edx, eax, cl |
mov ecx, [esi+report_field_group.sign_mask] |
and ecx, edx |
and edx, [esi+report_field_group.mask] |
sub edx, ecx |
} |
; Local variables for parse_input. |
macro parse_input_locals |
{ |
count_inside_group dd ? |
; Number of fields left in the current field. |
field_offset dd ? |
; Offset of the current field from report start, in bits. |
field_range_size dd ? |
; Size of range with valid values, Logical Maximum - Logical Minimum + 1. |
cur_usage dd ? |
; Pointer to current usage for Variable field groups. |
num_values dd ? |
; Number of values in the current instantiation of Array field group. |
values_base dd ? |
; Pointer to memory allocated for array with current values. |
values_prev dd ? |
; Pointer to memory allocated for array with previous values. |
values_cur_ptr dd ? |
; Pointer to the next value in [values_base] array. |
values_end dd ? |
; End of data in array with current values. |
values_prev_ptr dd ? |
; Pointer to the next value in [values_prev_ptr] array. |
values_prev_end dd ? |
; End of data in array with previous values. |
} |
; Parse input report. The caller should provide esi = pointer to report, |
; local variables parse_input_locals and [buffer] = report data. |
macro parse_input |
{ |
; 1. Ignore the report if there is no driver for it. |
mov ebx, [esi+report.top_level_collection] |
mov edi, [ebx+collection.driver_data] |
test edi, edi |
jz .done |
; 2. Notify the driver that a new packet arrived. |
call [ebx+collection.callbacks.begin_packet] |
; Loop over all field groups. |
; Report without fields is meaningless, but theoretically possible: |
; parse_descr does not create reports of zero size, but |
; a report can consist of "padding" fields without usages and have |
; no real fields. |
mov esi, [esi+report.first_field] |
test esi, esi |
jz .packet_processed |
.field_loop: |
; 3. Prepare for group handling: initialize field offset, fields count |
; and size of range for valid values. |
mov eax, [esi+report_field_group.offset] |
mov [field_offset], eax |
mov ecx, [esi+report_field_group.count] |
mov [count_inside_group], ecx |
mov eax, [esi+report_field_group.logical_maximum] |
inc eax |
sub eax, [esi+report_field_group.logical_minimum] |
mov [field_range_size], eax |
; 4. Select handler. Variable and Array groups are handled entirely differently; |
; for Variable groups, advance to 5, for Array groups, go to 6. |
test byte [esi+report_field_group.flags], HID_FIELD_VARIABLE |
jz .array_field |
; 5. Variable groups. They are simple. Loop over all .count fields, |
; for every field extract the value and get the next usage, |
; if the value is within valid range, call the driver. |
lea eax, [esi+report_field_group.common_sizeof] |
mov [cur_usage], eax |
.variable_data_loop: |
mov eax, [field_offset] |
extract_field_value [buffer] ; -> edx |
mov ecx, [cur_usage] |
mov ecx, [ecx] |
call [ebx+collection.callbacks.input_field] |
add [cur_usage], 4 |
mov eax, [esi+report_field_group.size] |
add [field_offset], eax |
dec [count_inside_group] |
jnz .variable_data_loop |
; Variable group is processed; go to 12. |
jmp .field_done |
.array_field: |
; Array groups. They are complicated. |
; 6. Array group: extract all values in one array. |
; memory was allocated during group creation, use it |
; 6a. Prepare: get data pointer, initialize num_values with zero. |
mov eax, [esi+report_field_group.num_usage_ranges] |
lea edx, [esi+report_field_group.usages+eax*sizeof.usage_range+4] |
mov eax, [esi+report_field_group.count] |
mov [values_cur_ptr], edx |
mov [values_base], edx |
lea edx, [edx+ecx*4] |
mov [values_prev], edx |
mov [values_prev_ptr], edx |
mov [num_values], 0 |
; 6b. Start loop for every field. Note that there must be at least one field, |
; parse_descr does not allow .count == 0. |
.array_getval_loop: |
; 6c. Extract the value of the current field. |
mov eax, [field_offset] |
extract_field_value [buffer] ; -> edx |
; 6d. Transform the value to the usage with binary search in array of |
; usage_ranges. started at [esi+report_field_group.usages] |
; having [esi+report_field_group.num_usage_ranges] items. |
; Ignore items outside of valid range. |
sub edx, [esi+report_field_group.logical_minimum] |
cmp edx, [field_range_size] |
jae .array_skip_item |
; If there are too few usages, use last of them. |
mov ecx, [esi+report_field_group.num_usage_ranges] ; upper bound |
xor eax, eax ; lower bound |
cmp edx, [esi+report_field_group.usages+ecx*sizeof.usage_range+usage_range.offset] |
jae .array_last_usage |
; loop invariant: usages[eax].offset <= edx < usages[ecx].offset |
.array_find_usage: |
lea edi, [eax+ecx] |
shr edi, 1 |
cmp edi, eax |
jz .array_found_usage_range |
cmp edx, [esi+report_field_group.usages+edi*sizeof.usage_range+usage_range.offset] |
jae .update_low |
mov ecx, edi |
jmp .array_find_usage |
.update_low: |
mov eax, edi |
jmp .array_find_usage |
.array_last_usage: |
lea eax, [ecx-1] |
mov edx, [esi+report_field_group.usages+ecx*sizeof.usage_range+usage_range.offset] |
dec edx |
.array_found_usage_range: |
sub edx, [esi+report_field_group.usages+eax*sizeof.usage_range+usage_range.offset] |
add edx, [esi+report_field_group.usages+eax*sizeof.usage_range+usage_range.first_usage] |
; 6e. Store the usage, advance data pointer, continue loop started at 6b. |
mov eax, [values_cur_ptr] |
mov [eax], edx |
add [values_cur_ptr], 4 |
inc [num_values] |
.array_skip_item: |
mov eax, [esi+report_field_group.size] |
add [field_offset], eax |
dec [count_inside_group] |
jnz .array_getval_loop |
; 7. Array group: ask driver about array overflow. |
; If driver says that the array is invalid, stop processing this group |
; (in particular, do not update previous values). |
mov ecx, [num_values] |
test ecx, ecx |
jz .duplicates_removed |
mov edx, [values_base] |
mov edi, [ebx+collection.driver_data] |
call [ebx+collection.callbacks.array_overflow?] |
jnc .field_done |
; 8. Array group: sort the array with current values. |
push esi |
mov ecx, [num_values] |
mov edx, [values_base] |
call sort |
pop esi |
; 9. Array group: remove duplicates. |
cmp [num_values], 1 |
jbe .duplicates_removed |
mov eax, [values_base] |
mov edx, [eax] |
add eax, 4 |
mov ecx, eax |
.duplicates_loop: |
cmp edx, [eax] |
jz @f |
mov edx, [eax] |
mov [ecx], edx |
add ecx, 4 |
@@: |
add eax, 4 |
cmp eax, [values_cur_ptr] |
jb .duplicates_loop |
mov [values_cur_ptr], ecx |
sub ecx, [values_base] |
shr ecx, 2 |
mov [num_values], ecx |
.duplicates_removed: |
; 10. Array group: compare current and previous values, |
; call driver for differences. |
mov edi, [ebx+collection.driver_data] |
mov eax, [values_cur_ptr] |
mov [values_end], eax |
mov eax, [values_base] |
mov [values_cur_ptr], eax |
mov eax, [esi+report_field_group.num_values_prev] |
shl eax, 2 |
add eax, [values_prev] |
mov [values_prev_end], eax |
.find_common: |
mov eax, [values_cur_ptr] |
cmp eax, [values_end] |
jae .cur_done |
mov ecx, [eax] |
mov eax, [values_prev_ptr] |
cmp eax, [values_prev_end] |
jae .prev_done |
mov edx, [eax] |
cmp ecx, edx |
jb .advance_cur |
ja .advance_prev |
; common item in both arrays; ignore |
add [values_cur_ptr], 4 |
add [values_prev_ptr], 4 |
jmp .find_common |
.advance_cur: |
; item is present in current array but not in previous; |
; call the driver with value = 1 |
add [values_cur_ptr], 4 |
mov edx, 1 |
call [ebx+collection.callbacks.input_field] |
jmp .find_common |
.advance_prev: |
; item is present in previous array but not in current; |
; call the driver with value = 0 |
add [values_prev_ptr], 4 |
mov ecx, edx |
xor edx, edx |
call [ebx+collection.callbacks.input_field] |
jmp .find_common |
.prev_done: |
; for all items which are left in current array |
; call the driver with value = 1 |
mov eax, [values_cur_ptr] |
@@: |
add [values_cur_ptr], 4 |
mov ecx, [eax] |
mov edx, 1 |
call [ebx+collection.callbacks.input_field] |
mov eax, [values_cur_ptr] |
cmp eax, [values_end] |
jb @b |
jmp .copy_array |
.cur_done: |
; for all items which are left in previous array |
; call the driver with value = 0 |
mov eax, [values_prev_ptr] |
add [values_prev_ptr], 4 |
cmp eax, [values_prev_end] |
jae @f |
mov ecx, [eax] |
xor edx, edx |
call [ebx+collection.callbacks.input_field] |
jmp .cur_done |
@@: |
.copy_array: |
; 11. Array group: copy current values to previous values. |
push esi edi |
mov ecx, [num_values] |
mov [esi+report_field_group.num_values_prev], ecx |
mov esi, [values_base] |
mov edi, [values_prev] |
rep movsd |
pop edi esi |
; 12. Field group is processed. Repeat with the next group, if any. |
.field_done: |
mov esi, [esi+report_field_group.next] |
test esi, esi |
jnz .field_loop |
.packet_processed: |
; 13. Packet is processed, notify the driver. |
call [ebx+collection.callbacks.end_packet] |
} |
/kernel/branches/Kolibri-acpi/drivers/usbhid/sort.inc |
---|
0,0 → 1,60 |
; Sort array of unsigned dwords in non-decreasing order. |
; ecx = array size, edx = array pointer. |
; Destroys eax, ecx, esi, edi. |
sort: |
test ecx, ecx |
jz .done |
mov eax, ecx |
@@: |
push eax |
call .restore |
pop eax |
dec eax |
jnz @b |
@@: |
cmp ecx, 1 |
jz .done |
mov esi, 1 |
mov edi, ecx |
call .exchange |
dec ecx |
mov eax, 1 |
call .restore |
jmp @b |
.done: |
ret |
.exchange: |
push eax ecx |
mov eax, [edx+esi*4-4] |
mov ecx, [edx+edi*4-4] |
mov [edx+esi*4-4], ecx |
mov [edx+edi*4-4], eax |
pop ecx eax |
ret |
.restore: |
lea esi, [eax+eax] |
cmp esi, ecx |
ja .doner |
mov edi, [edx+eax*4-4] |
cmp [edx+esi*4-4], edi |
ja .need_xchg |
cmp esi, ecx |
jae .doner |
mov edi, [edx+eax*4-4] |
cmp [edx+esi*4], edi |
jbe .doner |
.need_xchg: |
cmp esi, ecx |
jz .do_xchg |
mov edi, [edx+esi*4-4] |
cmp [edx+esi*4], edi |
sbb esi, -1 |
.do_xchg: |
mov edi, eax |
call .exchange |
mov eax, esi |
jmp .restore |
.doner: |
ret |
/kernel/branches/Kolibri-acpi/drivers/usbhid/unclaimed.inc |
---|
0,0 → 1,60 |
; HID default driver, part of USBHID driver. |
; Present only if compile-time setting HID_DUMP_UNCLAIMED is on. |
; Active for those devices when we do not have a specialized driver. |
; Just dumps everything to the debug board. |
if HID_DUMP_UNCLAIMED |
; Global constants. |
; They are assembled in a macro to separate code and data; |
; the code is located at the point of "include 'unclaimed.inc'", |
; the data are collected when workers_globals is instantiated. |
macro workers_globals |
{ |
; include global constants from previous workers |
workers_globals |
align 4 |
; Callbacks for HID layer. |
default_driver: |
dd default_driver_add_device |
dd default_driver_disconnect |
dd default_driver_begin_packet |
dd default_driver_array_overflow? |
dd default_driver_input_field |
dd default_driver_end_packet |
} |
; This procedure is called when HID layer detects a new driverless device. |
; in: ebx -> usb_device_data, edi -> collection |
; out: eax = device-specific data or NULL on error |
default_driver_add_device: |
; just return something nonzero, no matter what |
xor eax, eax |
inc eax |
ret |
; This procedure is called when HID layer processes every non-empty array field group. |
; in: edi -> keyboard_device_data (pointer returned from keyboard_driver_add_device) |
; in: ecx = fields count (always nonzero), edx = pointer to fields values |
; in: esi -> report_field_group |
; out: CF set => group is ok, CF cleared => group should be ignored |
default_driver_array_overflow?: |
; parse everything |
stc |
ret |
; This procedure is called from HID layer for every field. |
; in: ecx = field usage, edx = value, esi -> report_field_group |
default_driver_input_field: |
; Do not dump zero values in Variable fields, |
; they are present even if the corresponding control is inactive. |
test edx, edx |
jnz @f |
test byte [esi+report_field_group.flags], HID_FIELD_VARIABLE |
jnz .nodump |
@@: |
DEBUGF 1,'K : unclaimed HID input: usage=%x, value=%x\n',ecx,edx |
.nodump: |
; pass through |
; Three nothing-to-do procedures. |
default_driver_disconnect: |
default_driver_begin_packet: |
default_driver_end_packet: |
ret |
end if |
/kernel/branches/Kolibri-acpi/drivers/usbhid/usbhid.asm |
---|
0,0 → 1,553 |
; standard driver stuff |
format MS COFF |
DEBUG = 1 |
; this is for DEBUGF macro from 'fdo.inc' |
__DEBUG__ = 1 |
__DEBUG_LEVEL__ = 1 |
include '../proc32.inc' |
include '../imports.inc' |
include '../fdo.inc' |
include '../../struct.inc' |
public START |
public version |
; Compile-time settings. |
; If set, the code will dump all descriptors as they are read to the debug board. |
USB_DUMP_DESCRIPTORS = 1 |
; If set, the code will dump any unclaimed input to the debug board. |
HID_DUMP_UNCLAIMED = 1 |
; USB constants |
DEVICE_DESCR_TYPE = 1 |
CONFIG_DESCR_TYPE = 2 |
STRING_DESCR_TYPE = 3 |
INTERFACE_DESCR_TYPE = 4 |
ENDPOINT_DESCR_TYPE = 5 |
DEVICE_QUALIFIER_DESCR_TYPE = 6 |
CONTROL_PIPE = 0 |
ISOCHRONOUS_PIPE = 1 |
BULK_PIPE = 2 |
INTERRUPT_PIPE = 3 |
; USB HID constants |
HID_DESCR_TYPE = 21h |
REPORT_DESCR_TYPE = 22h |
PHYSICAL_DESCR_TYPE = 23h |
; USB structures |
struct config_descr |
bLength db ? |
bDescriptorType db ? |
wTotalLength dw ? |
bNumInterfaces db ? |
bConfigurationValue db ? |
iConfiguration db ? |
bmAttributes db ? |
bMaxPower db ? |
ends |
struct interface_descr |
bLength db ? |
bDescriptorType db ? |
bInterfaceNumber db ? |
bAlternateSetting db ? |
bNumEndpoints db ? |
bInterfaceClass db ? |
bInterfaceSubClass db ? |
bInterfaceProtocol db ? |
iInterface db ? |
ends |
struct endpoint_descr |
bLength db ? |
bDescriptorType db ? |
bEndpointAddress db ? |
bmAttributes db ? |
wMaxPacketSize dw ? |
bInterval db ? |
ends |
; USB HID structures |
struct hid_descr |
bLength db ? |
bDescriptorType db ? |
bcdHID dw ? |
bCountryCode db ? |
bNumDescriptors db ? |
base_sizeof rb 0 |
; now two fields are repeated .bNumDescriptors times: |
subDescriptorType db ? |
subDescriptorLength dw ? |
ends |
; Include macro for parsing report descriptors/data. |
macro workers_globals |
{} |
include 'report.inc' |
; Driver data for all devices |
struct usb_device_data |
hid hid_data ; data of HID layer |
epdescr dd ? ; endpoint descriptor |
hiddescr dd ? ; HID descriptor |
interface_number dd ? ; copy of interface_descr.bInterfaceNumber |
configpipe dd ? ; config pipe handle |
intpipe dd ? ; interrupt pipe handle |
input_transfer_size dd ? ; input transfer size |
input_buffer dd ? ; buffer for input transfers |
control rb 8 ; control packet to device |
ends |
section '.flat' code readable align 16 |
; The start procedure. |
proc START |
virtual at esp |
dd ? ; return address |
.reason dd ? |
end virtual |
; 1. Test whether the procedure is called with the argument DRV_ENTRY. |
; If not, return 0. |
xor eax, eax ; initialize return value |
cmp [.reason], 1 ; compare the argument |
jnz .nothing |
; 2. Register self as a USB driver. |
; The name is my_driver = 'usbhid'; IOCTL interface is not supported; |
; usb_functions is an offset of a structure with callback functions. |
stdcall RegUSBDriver, my_driver, eax, usb_functions |
; 3. Return the returned value of RegUSBDriver. |
.nothing: |
ret 4 |
endp |
; This procedure is called when new HID device is detected. |
; It initializes the device. |
proc AddDevice |
push ebx esi edi ; save used registers to be stdcall |
virtual at esp |
rd 3 ; saved registers |
dd ? ; return address |
.config_pipe dd ? |
.config_descr dd ? |
.interface dd ? |
end virtual |
DEBUGF 1,'K : USB HID device detected\n' |
; 1. Allocate memory for device data. |
movi eax, sizeof.usb_device_data |
call Kmalloc |
test eax, eax |
jnz @f |
mov esi, nomemory_msg |
call SysMsgBoardStr |
jmp .return0 |
@@: |
; zero-initialize it |
mov edi, eax |
xchg eax, ebx |
xor eax, eax |
movi ecx, sizeof.usb_device_data / 4 |
rep stosd |
mov edx, [.interface] |
; HID devices use one IN interrupt endpoint for polling the device |
; and an optional OUT interrupt endpoint. We do not use the later, |
; but must locate the first. Look for the IN interrupt endpoint. |
; Also, look for the HID descriptor; according to HID spec, it must be |
; located before endpoint descriptors. |
; 2. Get the upper bound of all descriptors' data. |
mov eax, [.config_descr] |
movzx ecx, [eax+config_descr.wTotalLength] |
add eax, ecx |
; 3. Loop over all descriptors until |
; either end-of-data reached - this is fail |
; or interface descriptor found - this is fail, all further data |
; correspond to that interface |
; or endpoint descriptor for IN endpoint is found |
; (HID descriptor must be located before the endpoint descriptor). |
; 3a. Loop start: edx points to the interface descriptor. |
.lookep: |
; 3b. Get next descriptor. |
movzx ecx, byte [edx] ; the first byte of all descriptors is length |
test ecx, ecx |
jz .cfgerror |
add edx, ecx |
; 3c. Check that at least two bytes are readable. The opposite is an error. |
inc edx |
cmp edx, eax |
jae .cfgerror |
dec edx |
; 3d. Check that this descriptor is not interface descriptor. The opposite is |
; an error. |
cmp [edx+endpoint_descr.bDescriptorType], INTERFACE_DESCR_TYPE |
jz .cfgerror |
; 3e. For HID descriptor, proceed to 4. |
; For endpoint descriptor, go to 5. |
; For other descriptors, continue the loop. |
; Note: bDescriptorType is in the same place in all descriptors. |
cmp [edx+endpoint_descr.bDescriptorType], ENDPOINT_DESCR_TYPE |
jz .foundep |
cmp [edx+endpoint_descr.bDescriptorType], HID_DESCR_TYPE |
jnz .lookep |
; 4a. Check that the descriptor contains all required data and all data are |
; readable. The opposite is an error. |
movzx ecx, [edx+hid_descr.bLength] |
cmp ecx, hid_descr.base_sizeof + 3 |
jb .cfgerror |
add ecx, edx |
cmp ecx, eax |
ja .cfgerror |
; 4b. Store the pointer in usb_device_data structure for further references. |
mov [ebx+usb_device_data.hiddescr], edx |
; 4c. Continue the loop. |
jmp .lookep |
.foundep: |
; 5a. Check that the descriptor contains all required data and all data are |
; readable. The opposite is an error. |
cmp byte [edx+endpoint_descr.bLength], sizeof.endpoint_descr |
jb .cfgerror |
lea ecx, [edx+sizeof.endpoint_descr] |
cmp ecx, eax |
jbe @f |
; 6. An error occured during processing endpoint descriptor. |
.cfgerror: |
; 6a. Print a message. |
mov esi, invalid_config_descr_msg |
call SysMsgBoardStr |
; 6b. Free memory allocated for device data. |
.free: |
xchg eax, ebx |
call Kfree |
.return0: |
; 6c. Return an error. |
xor eax, eax |
.nothing: |
pop edi esi ebx ; restore used registers to be stdcall |
ret 12 |
@@: |
; 5b. If this is not IN interrupt endpoint, ignore it and continue the loop. |
test [edx+endpoint_descr.bEndpointAddress], 80h |
jz .lookep |
mov cl, [edx+endpoint_descr.bmAttributes] |
and cl, 3 |
cmp cl, INTERRUPT_PIPE |
jnz .lookep |
; 5c. Store the pointer in usb_device_data structure for futher references. |
mov [ebx+usb_device_data.epdescr], edx |
; 5d. Check that HID descriptor was found. If not, go to 6. |
cmp [ebx+usb_device_data.hiddescr], 0 |
jz .cfgerror |
.descriptors_found: |
; 6. Configuration descriptor seems to be ok. |
; Send SET_IDLE command disabling auto-repeat feature (it is quite useless) |
; and continue configuring in SET_IDLE callback. |
lea edx, [ebx+usb_device_data.control] |
mov eax, [.interface] |
mov dword [edx], 21h + \ ; Class-specific request to Interface |
(0Ah shl 8) + \ ; SET_IDLE |
(0 shl 16) + \ ; apply to all input reports |
(0 shl 24) ; disable auto-repeat |
movzx eax, [eax+interface_descr.bInterfaceNumber] |
mov [ebx+usb_device_data.interface_number], eax |
mov [edx+4], eax ; set interface number, zero length |
mov eax, [.config_pipe] |
mov [ebx+usb_device_data.configpipe], eax |
xor ecx, ecx |
stdcall USBControlTransferAsync, eax, edx, ecx, ecx, idle_set, ebx, ecx |
; 7. Return pointer to usb_device_data. |
xchg eax, ebx |
jmp .nothing |
endp |
; This procedure is called by USB stack when SET_IDLE request initiated by |
; AddDevice is completed, either successfully or unsuccessfully. |
proc idle_set |
push ebx esi ; save used registers to be stdcall |
virtual at esp |
rd 2 ; saved registers |
dd ? ; return address |
.pipe dd ? |
.status dd ? |
.buffer dd ? |
.length dd ? |
.calldata dd ? |
end virtual |
; Ignore status. Support for SET_IDLE is optional, so the device is free to |
; STALL the request; config pipe should remain functional without explicit cleanup. |
mov ebx, [.calldata] |
; 1. HID descriptor contains length of Report descriptor. Parse it. |
mov esi, [ebx+usb_device_data.hiddescr] |
movzx ecx, [esi+hid_descr.bNumDescriptors] |
lea eax, [hid_descr.base_sizeof+ecx*3] |
cmp eax, 100h |
jae .cfgerror |
cmp al, [esi+hid_descr.bLength] |
jb .cfgerror |
.look_report: |
dec ecx |
js .cfgerror |
cmp [esi+hid_descr.subDescriptorType], REPORT_DESCR_TYPE |
jz .found_report |
add esi, 3 |
jmp .look_report |
.cfgerror: |
mov esi, invalid_config_descr_msg |
.abort_with_msg: |
call SysMsgBoardStr |
jmp .nothing |
.found_report: |
; 2. Send request for the Report descriptor. |
; 2a. Allocate memory. |
movzx eax, [esi+hid_descr.subDescriptorLength] |
test eax, eax |
jz .cfgerror |
push eax |
call Kmalloc |
pop ecx |
; If failed, say a message and stop initialization. |
mov esi, nomemory_msg |
test eax, eax |
jz .abort_with_msg |
; 2b. Submit the request. |
xchg eax, esi |
lea edx, [ebx+usb_device_data.control] |
mov eax, [ebx+usb_device_data.interface_number] |
mov dword [edx], 81h + \ ; Standard request to Interface |
(6 shl 8) + \ ; GET_DESCRIPTOR |
(0 shl 16) + \ ; descriptor index: there is only one report descriptor |
(REPORT_DESCR_TYPE shl 24); descriptor type |
mov [edx+4], ax ; Interface number |
mov [edx+6], cx ; descriptor length |
stdcall USBControlTransferAsync, [ebx+usb_device_data.configpipe], \ |
edx, esi, ecx, got_report, ebx, 0 |
; 2c. If failed, free the buffer and stop initialization. |
test eax, eax |
jnz .nothing |
xchg eax, esi |
call Kfree |
.nothing: |
pop esi ebx ; restore used registers to be stdcall |
ret 20 |
endp |
; This procedure is called by USB stack when the report descriptor queried |
; by idle_set is completed, either successfully or unsuccessfully. |
proc got_report stdcall uses ebx esi edi, pipe, status, buffer, length, calldata |
locals |
parse_descr_locals |
if ~HID_DUMP_UNCLAIMED |
has_driver db ? |
rb 3 |
end if |
endl |
; 1. Check the status; if the request has failed, say something to the debug board |
; and stop initialization. |
cmp [status], 0 |
jnz .generic_fail |
; 2. Subtract size of setup packet from the total length; |
; the rest is length of the descriptor, and it must be nonzero. |
sub [length], 8 |
ja .has_something |
.generic_fail: |
push esi |
mov esi, reportfail |
call SysMsgBoardStr |
pop esi |
jmp .exit |
.has_something: |
; 3. Process descriptor. |
; 3a. Dump it to the debug board, if enabled in compile-time setting. |
if USB_DUMP_DESCRIPTORS |
mov eax, [buffer] |
mov ecx, [length] |
DEBUGF 1,'K : report descriptor:' |
@@: |
DEBUGF 1,' %x',[eax]:2 |
inc eax |
dec ecx |
jnz @b |
DEBUGF 1,'\n' |
end if |
; 3b. Call the HID layer. |
parse_descr |
cmp [report_ok], 0 |
jz got_report.exit |
mov ebx, [calldata] |
postprocess_descr |
; 4. Stop initialization if no driver is assigned. |
if ~HID_DUMP_UNCLAIMED |
cmp [has_driver], 0 |
jz got_report.exit |
end if |
; 5. Open interrupt IN pipe. If failed, stop initialization. |
mov edx, [ebx+usb_device_data.epdescr] |
movzx ecx, [edx+endpoint_descr.bEndpointAddress] |
movzx eax, [edx+endpoint_descr.bInterval] |
movzx edx, [edx+endpoint_descr.wMaxPacketSize] |
stdcall USBOpenPipe, [ebx+usb_device_data.configpipe], ecx, edx, INTERRUPT_PIPE, eax |
test eax, eax |
jz got_report.exit |
mov [ebx+usb_device_data.intpipe], eax |
; 6. Initialize buffer for input packet. |
; 6a. Find the length of input packet. |
; This is the maximal length of all input reports. |
mov edx, [ebx+usb_device_data.hid.input.first_report] |
xor eax, eax |
.find_input_size: |
test edx, edx |
jz .found_input_size |
cmp eax, [edx+report.size] |
jae @f |
mov eax, [edx+report.size] |
@@: |
mov edx, [edx+report.next] |
jmp .find_input_size |
.found_input_size: |
; report.size is in bits, transform it to bytes |
add eax, 7 |
shr eax, 3 |
; if reports are numbered, the first byte is report ID, include it |
cmp [ebx+usb_device_data.hid.input.numbered], 0 |
jz @f |
inc eax |
@@: |
mov [ebx+usb_device_data.input_transfer_size], eax |
; 6b. Allocate memory for input packet: dword-align and add additional dword |
; for extract_field_value. |
add eax, 4+3 |
and eax, not 3 |
call Kmalloc |
test eax, eax |
jnz @f |
mov esi, nomemory_msg |
call SysMsgBoardStr |
jmp got_report.exit |
@@: |
mov [ebx+usb_device_data.input_buffer], eax |
; 7. Submit a request for input packet and wait for input. |
call ask_for_input |
got_report.exit: |
mov eax, [buffer] |
call Kfree |
ret |
endp |
; Helper procedure for got_report and got_input. |
; Submits a request for the next input packet. |
proc ask_for_input |
; just call USBNormalTransferAsync with correct parameters, |
; allow short packets |
stdcall USBNormalTransferAsync, \ |
[ebx+usb_device_data.intpipe], \ |
[ebx+usb_device_data.input_buffer], \ |
[ebx+usb_device_data.input_transfer_size], \ |
got_input, ebx, \ |
1 |
ret |
endp |
; This procedure is called by USB stack when a HID device responds with input |
; data packet. |
proc got_input stdcall uses ebx esi edi, pipe, status, buffer, length, calldata |
locals |
parse_input_locals |
endl |
; 1. Validate parameters: fail on error, ignore zero-length transfers. |
mov ebx, [calldata] |
cmp [status], 0 |
jnz .fail |
cmp [length], 0 |
jz .done |
; 2. Get pointer to report in esi. |
; 2a. If there are no report IDs, use hid.input.data. |
mov eax, [buffer] |
mov esi, [ebx+usb_device_data.hid.input.data] |
cmp [ebx+usb_device_data.hid.input.numbered], 0 |
jz .report_found |
; 2b. Otherwise, the first byte of report is report ID; |
; locate the report by its ID, advance buffer+length to one byte. |
movzx eax, byte [eax] |
mov esi, [esi+eax*4] |
inc [buffer] |
dec [length] |
.report_found: |
; 3. Validate: ignore transfers with unregistered report IDs |
; and transfers which are too short for the corresponding report. |
test esi, esi |
jz .done |
mov eax, [esi+report.size] |
add eax, 7 |
shr eax, 3 |
cmp eax, [length] |
ja .done |
; 4. Pass everything to HID layer. |
parse_input |
.done: |
; 5. Query the next input. |
mov ebx, [calldata] |
call ask_for_input |
.nothing: |
ret |
.fail: |
mov esi, transfer_error_msg |
call SysMsgBoardStr |
jmp .nothing |
endp |
; This function is called by the USB subsystem when a device is disconnected. |
proc DeviceDisconnected |
push ebx esi edi ; save used registers to be stdcall |
virtual at esp |
rd 3 ; saved registers |
dd ? ; return address |
.device_data dd ? |
end virtual |
; 1. Say a message. |
mov ebx, [.device_data] |
mov esi, disconnectmsg |
stdcall SysMsgBoardStr |
; 2. Ask HID layer to release all HID-related resources. |
hid_cleanup |
; 3. Free the device data. |
xchg eax, ebx |
call Kfree |
; 4. Return. |
.nothing: |
pop edi esi ebx ; restore used registers to be stdcall |
ret 4 ; purge one dword argument to be stdcall |
endp |
include 'sort.inc' |
include 'unclaimed.inc' |
include 'mouse.inc' |
include 'keyboard.inc' |
; strings |
my_driver db 'usbhid',0 |
nomemory_msg db 'K : no memory',13,10,0 |
invalid_config_descr_msg db 'K : invalid config descriptor',13,10,0 |
reportfail db 'K : failed to read report descriptor',13,10,0 |
transfer_error_msg db 'K : USB transfer error, disabling HID device',13,10,0 |
disconnectmsg db 'K : USB HID device disconnected',13,10,0 |
invalid_report_msg db 'K : report descriptor is invalid',13,10,0 |
delimiter_note db 'K : note: alternate usage ignored',13,10,0 |
; Exported variable: kernel API version. |
align 4 |
version dd 50005h |
; Structure with callback functions. |
usb_functions: |
dd 12 |
dd AddDevice |
dd DeviceDisconnected |
; for DEBUGF macro |
include_debug_strings |
; Workers data |
workers_globals |
; for uninitialized data |
;section '.data' data readable writable align 16 |
/kernel/branches/Kolibri-acpi/drivers/usbhid |
---|
Property changes: |
Added: tsvn:logminsize |
+5 |
\ No newline at end of property |
/kernel/branches/Kolibri-acpi/drivers/vidrdc.asm |
---|
0,0 → 1,438 |
; Stub of videodriver for RDC Semiconductor Co. M2010/M2012 videocards (controller names: R3306/R3308). |
; It is used in SoC produced by DMP Electronics Inc.: |
; Vortex86MX (contains RDC M2010 graphics card, appears in eBox-3300MX) |
; Vortex86MX+ (contains RDC M2012 graphics card, appears in eBox-3310MX) |
; Link to manufacturers websites - |
; RDC Semiconductor Co.: http://www.rdc.com.tw |
; DM&P Electronics Inc.: http://www.dmp.com.tw and http://www.compactpc.com.tw |
; Code stolen from vidintel.asm driver (c) by CleverMouse and adapted for RDC. |
; When the start procedure gets control, |
; it tries to detect preferred resolution, |
; sets the detected resolution assuming 32-bpp VESA mode and exits |
; (without registering a service). |
; Detection can be overloaded with compile-time settings |
; use_predefined_mode/predefined_width/predefined_height. |
; set predefined resolution here |
use_predefined_mode = 0;1 |
predefined_width = 0;1366 |
predefined_height = 0;768 |
; standard driver stuff |
format MS COFF |
DEBUG = 1 |
include 'proc32.inc' |
include 'imports.inc' |
public START |
public version |
section '.flat' code readable align 16 |
; the start procedure (see the description above) |
START: |
; 1. Detect device. Abort if not found. |
push esi |
call DetectDevice |
test esi, esi |
jz .return0 |
;{START}yogev_ezra: temporary exit after detection |
pusha |
mov esi, exitmsg |
call SysMsgBoardStr |
popa |
jmp .return0 |
;{END}yogev_ezra: temporary exit after detection |
; 2. Detect optimal mode unless the mode is given explicitly. Abort if failed. |
if use_predefined_mode = 0 |
call DetectMode |
end if |
cmp [width], 0 |
jz .return0_cleanup |
; 3. Set the detected mode. |
call SetMode |
; 4. Cleanup and return. |
.return0_cleanup: |
stdcall FreeKernelSpace, esi |
.return0: |
pop esi |
xor eax, eax |
ret 4 |
; check that there is RDC videocard |
; if so, map MMIO registers and set internal variables |
; esi points to MMIO block; NULL means no device |
DetectDevice: |
; 1. Sanity check: check that we are dealing with RDC videocard. |
; Integrated video device for RDC is always at PCI:0:13:0 (bus:dev:fn=0:0d:0) |
xor esi, esi ; initialize return value to NULL |
; 1a. Get PCI VendorID and DeviceID. |
push esi ; in: reg=0 (register) -> register 00 means return DeviceID (bits 16-31) + VendorID (bits 0-15) |
push 68h ; in: devfn=13:0 | device:5bit (0Dh = 1101) + func:3bit (0 = 000) -> total:1byte (1101000b = 68h) |
push esi ; in: bus=0 |
call PciRead32 |
; 1b. loword(eax) = ax = VendorID, hiword(eax) = DeviceID. |
; Test whether we have RDC Semiconductor Co. chipset. |
cmp ax, 17F3h ;VendorID 0x17F3, 'RDC Semiconductor Co.' |
jnz .return |
; 1c. Say hi including DeviceID. |
shr eax, 10h ; now, ax = HIWORD(eax) = PCI DeviceID |
push edi |
pusha |
mov edi, pciid_text ; edi='0000' |
call WriteWord |
mov esi, hellomsg |
call SysMsgBoardStr |
popa |
; 1d. Test whether we know this DeviceID. |
; If this is the case, remember the position of the device in line of RDC cards; |
; this knowledge will be useful later. |
; Tested on devices with id: 17F3:2010, 17F3:2012. |
mov ecx, pciids_num |
mov edi, pciids |
repnz scasw |
pop edi |
jnz .return_unknown_pciid |
sub ecx, pciids_num - 1 |
neg ecx |
mov [deviceType], ecx |
; 1e. Continue saying hi with positive intonation. |
pusha |
mov esi, knownmsg |
call SysMsgBoardStr |
popa |
; 2. Prepare MMIO region to control the card. |
; 2a. Read MMIO physical address from PCI config space. |
; According to RDC M2010/M2012 registers manual, their memory-mapped I/O space is located at Base address #1 |
push 14h ; in: reg=14h (register) -> register 14h means Base address #1 (BAR1) in PCI configuration space |
push 68h ; in: devfn=13:0 | device:5bit (0Dh = 1101) + func:3bit (0 = 000) -> total:1byte (1101000b = 68h) |
push esi ; in: bus=0 |
call PciRead32 |
; 2b. Mask out PCI region type, lower 4 bits. |
and al, not 0xF |
; 2c. Create virtual mapping of the physical memory. |
push 1Bh |
push 100000h |
push eax |
call MapIoMem |
; 3. Return. |
xchg esi, eax |
.return: |
ret |
; 1f. If we do not know DeviceID, continue saying hi with negative intonation. |
.return_unknown_pciid: |
pusha |
mov esi, unknownmsg |
call SysMsgBoardStr |
popa |
ret |
; Convert word in ax to hexadecimal text in edi, advance edi. |
WriteWord: |
; 1. Convert high byte. |
push eax |
mov al, ah |
call WriteByte |
pop eax |
; 2. Convert low byte. |
; Fall through to WriteByte; ret from WriteByte is ret from WriteWord too. |
; Convert byte in al to hexadecimal text in edi, advance edi. |
WriteByte: |
; 1. Convert high nibble. |
push eax |
shr al, 4 |
call WriteNibble |
pop eax |
; 2. Convert low nibble. |
and al, 0xF |
; Fall through to WriteNibble; ret from WriteNibble is ret from WriteByte too. |
; Convert nibble in al to hexadecimal text in edi, advance edi. |
WriteNibble: |
; Obvious, isn't it? |
cmp al, 10 |
sbb al, 69h |
das |
stosb ; This instruction uses EDI implicitly |
ret |
if use_predefined_mode = 0 |
; detect resolution of the flat panel |
DetectMode: |
push esi edi |
; 1. Get the location of block of GMBUS* registers. |
; Starting with Ironlake, GMBUS* registers were moved. |
add esi, 5100h |
cmp [deviceType], pciids_num ;ironlake_start |
jb @f |
add esi, 0xC0000 |
@@: |
; 2. Initialize GMBUS engine. |
mov edi, edid |
mov ecx, 0x10000 |
@@: |
test byte [esi+8+1], 80h |
loopnz @b |
jnz .fail |
mov dword [esi], 3 |
test byte [esi+8+1], 4 |
jz .noreset |
call ResetGMBus |
jnz .fail |
.noreset: |
; 3. Send read command. |
and dword [esi+20h], 0 |
mov dword [esi+4], 4E8000A1h |
; 4. Wait for data, writing to the buffer as data arrive. |
.getdata: |
mov ecx, 0x10000 |
@@: |
test byte [esi+8+1], 8 |
loopz @b |
test byte [esi+8+1], 4 |
jz .dataok |
call ResetGMBus |
jmp .fail |
.dataok: |
mov eax, [esi+0Ch] |
stosd |
cmp edi, edid+80h |
jb .getdata |
; 5. Wait for bus idle. |
mov ecx, 0x10000 |
@@: |
test byte [esi+8+1], 2 |
loopnz @b |
; 6. We got EDID; dump it if DEBUG. |
if DEBUG |
pusha |
xor ecx, ecx |
mov esi, edid |
mov edi, edid_text |
.dumploop: |
lodsb |
call WriteByte |
mov al, ' ' |
stosb |
inc cl |
test cl, 15 |
jnz @f |
mov byte [edi-1], 13 |
mov al, 10 |
stosb |
@@: |
test cl, cl |
jns .dumploop |
mov esi, edidmsg |
call SysMsgBoardStr |
popa |
end if |
; 7. Test whether EDID is good. |
; 7a. Signature: 00 FF FF FF FF FF FF 00. |
mov esi, edid |
cmp dword [esi], 0xFFFFFF00 |
jnz .fail |
cmp dword [esi+4], 0x00FFFFFF |
jnz .fail |
; 7b. Checksum must be zero. |
xor edx, edx |
mov ecx, 80h |
@@: |
lodsb |
add dl, al |
loop @b |
jnz .fail |
; 8. Get width and height from EDID. |
xor eax, eax |
mov ah, [esi-80h+3Ah] |
shr ah, 4 |
mov al, [esi-80h+38h] |
mov [width], eax |
mov ah, [esi-80h+3Dh] |
shr ah, 4 |
mov al, [esi-80h+3Bh] |
mov [height], eax |
; 9. Return. |
.fail: |
pop edi esi |
ret |
; reset bus, clear all errors |
ResetGMBus: |
; look into the PRM |
mov dword [esi+4], 80000000h |
mov dword [esi+4], 0 |
mov ecx, 0x10000 |
@@: |
test byte [esi+8+1], 2 |
loopnz @b |
ret |
end if |
; set resolution [width]*[height] |
SetMode: |
; 1. Program the registers of videocard. |
; look into the PRM |
cli |
; or byte [esi+7000Ah], 0Ch ; PIPEACONF: disable Display+Cursor Planes |
; or byte [esi+7100Ah], 0Ch ; PIPEBCONF: disable Display+Cursor Planes |
xor eax, eax |
xor edx, edx |
cmp [deviceType], pciids_num ;i965_start |
jb @f |
mov dl, 9Ch - 84h |
@@: |
; or byte [esi+71403h], 80h ; VGACNTRL: VGA Display Disable |
and byte [esi+70080h], not 27h ; CURACNTR: disable cursor A |
mov dword [esi+70084h], eax ; CURABASE: force write to CURA* regs |
and byte [esi+700C0h], not 27h ; CURBCNTR: disable cursor B |
mov dword [esi+700C4h], eax ; CURBBASE: force write to CURB* regs |
and byte [esi+70183h], not 80h ; DSPACNTR: disable Primary A Plane |
mov dword [esi+edx+70184h], eax ; DSPALINOFF/DSPASURF: force write to DSPA* regs |
and byte [esi+71183h], not 80h ; DSPBCNTR: disable Primary B Plane |
mov dword [esi+edx+71184h], eax ; DSPBLINOFF/DSPBSURF: force write to DSPB* regs |
if 1 |
cmp [deviceType], pciids_num ;ironlake_start |
jae .disable_pipes |
mov edx, 10000h |
or byte [esi+70024h], 2 ; PIPEASTAT: clear VBLANK status |
or byte [esi+71024h], 2 ; PIPEBSTAT: clear VBLANK status |
.wait_vblank_preironlake1: |
mov ecx, 1000h |
loop $ |
test byte [esi+7000Bh], 80h ; PIPEACONF: pipe A active? |
jz @f |
test byte [esi+70024h], 2 ; PIPEASTAT: got VBLANK? |
jz .wait_vblank_preironlake2 |
@@: |
test byte [esi+7100Bh], 80h ; PIPEBCONF: pipe B active? |
jz .disable_pipes |
test byte [esi+71024h], 2 ; PIPEBSTAT: got VBLANK? |
jnz .disable_pipes |
.wait_vblank_preironlake2: |
dec edx |
jnz .wait_vblank_preironlake1 |
jmp .not_disabled |
.disable_pipes: |
end if |
and byte [esi+7000Bh], not 80h ; PIPEACONF: disable pipe |
and byte [esi+7100Bh], not 80h ; PIPEBCONF: disable pipe |
cmp [deviceType], pciids_num ;gen4_start |
jb .wait_watching_scanline |
; g45 and later: use special flag from PIPE*CONF |
mov edx, 10000h |
@@: |
mov ecx, 1000h |
loop $ |
test byte [esi+7000Bh], 40h ; PIPEACONF: wait until pipe disabled |
jz @f |
dec edx |
jnz @b |
jmp .not_disabled |
@@: |
test byte [esi+7100Bh], 40h ; PIPEBCONF: wait until pipe disabled |
jz .disabled |
mov ecx, 1000h |
loop $ |
dec edx |
jnz @b |
jmp .not_disabled |
; pineview and before: wait while scanline still changes |
.wait_watching_scanline: |
mov edx, 1000h |
.dis1: |
push dword [esi+71000h] |
push dword [esi+70000h] |
mov ecx, 10000h |
loop $ |
pop eax |
xor eax, [esi+70000h] |
and eax, 1FFFh |
pop eax |
jnz .notdis1 |
xor eax, [esi+71000h] |
and eax, 1FFFh |
jz .disabled |
.notdis1: |
dec edx |
jnz .dis1 |
.not_disabled: |
sti |
jmp .return |
.disabled: |
lea eax, [esi+61183h] |
cmp [deviceType], pciids_num ;ironlake_start |
jb @f |
add eax, 0xE0000 - 0x60000 |
@@: |
lea edx, [esi+60000h] |
test byte [eax], 40h |
jz @f |
add edx, 1000h |
@@: |
mov eax, [width] |
dec eax |
shl eax, 16 |
mov ax, word [height] |
dec eax |
mov dword [edx+1Ch], eax ; PIPEASRC: set source image size |
ror eax, 16 |
mov dword [edx+10190h], eax ; for old cards |
mov ecx, [width] |
add ecx, 15 |
and ecx, not 15 |
shl ecx, 2 |
mov dword [edx+10188h], ecx ; DSPASTRIDE: set scanline length |
mov dword [edx+10184h], 0 ; DSPALINOFF: force write to DSPA* registers |
and byte [esi+61233h], not 80h ; PFIT_CONTROL: disable panel fitting |
or byte [edx+1000Bh], 80h ; PIPEACONF: enable pipe |
; and byte [edx+1000Ah], not 0Ch ; PIPEACONF: enable Display+Cursor Planes |
or byte [edx+10183h], 80h ; DSPACNTR: enable Display Plane A |
sti |
; 2. Notify the kernel that resolution has changed. |
call GetDisplay |
mov edx, [width] |
mov dword [eax+8], edx |
mov edx, [height] |
mov dword [eax+0Ch], edx |
mov [eax+18h], ecx |
mov eax, [width] |
dec eax |
dec edx |
call SetScreen |
.return: |
ret |
align 4 |
hellomsg db 'RDC videocard detected, PciId=17F3:' ;VendorID 0x17F3, 'RDC Semiconductor Co.' |
pciid_text db '0000' |
db ', which is ', 0 |
knownmsg db 'known',13,10,0 |
unknownmsg db 'unknown',13,10,0 |
exitmsg db 'Card detected successfully, exiting driver...',13,10,0 |
if DEBUG |
edidmsg db 'EDID successfully read:',13,10 |
edid_text rb 8*(16*3+1) |
db 0 |
end if |
version: |
dd 0x50005 |
width dd predefined_width |
height dd predefined_height |
pciids: |
dw 0x2010 ; M2010 - appears in eBox-3300MX (Vortex86MX SoC) |
dw 0x2012 ; M2012 - appears in eBox-3310MX (Vortex86MX+ SoC) |
pciids_num = ($ - pciids) / 2 |
align 4 |
deviceType dd ? |
edid rb 0x80 |