7dce54fc55
git-svn-id: svn://kolibrios.org@5051 a494cfbc-eb01-0410-851d-a64ba20cac60
1445 lines
54 KiB
PHP
1445 lines
54 KiB
PHP
; 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
|
|
invoke 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]
|
|
invoke 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
|
|
invoke 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
|
|
invoke 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]
|
|
@@:
|
|
invoke 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
|
|
invoke 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
|
|
invoke 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
|
|
invoke 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
|
|
invoke 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
|
|
invoke 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:
|
|
invoke SysMsgBoardStr
|
|
.end:
|
|
; Free all global_items structures.
|
|
test edi, edi
|
|
jz @f
|
|
push [edi+global_items.next]
|
|
xchg eax, edi
|
|
invoke 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]
|
|
invoke 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_POINTER
|
|
jz .has_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
|
|
invoke 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]
|
|
invoke Kfree
|
|
pop eax
|
|
jmp .field_loop
|
|
.field_done:
|
|
mov eax, [edi+report.next]
|
|
xchg eax, edi
|
|
invoke Kfree
|
|
jmp .report_loop
|
|
.report_done:
|
|
cmp [esi+report_set.numbered], 0
|
|
jz @f
|
|
mov eax, [esi+report_set.data]
|
|
invoke 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]
|
|
}
|