Changeset 59e01689 in mainline
- Timestamp:
- 2011-03-19T18:30:20Z (14 years ago)
- Branches:
- lfn, master, serial, ticket/834-toolchain-update, topic/msim-upgrade, topic/simplify-dev-export
- Children:
- 09daa8b
- Parents:
- e93e319 (diff), 3746bfe (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the(diff)
links above to see all the changes relative to each parent. - Location:
- uspace
- Files:
-
- 1 added
- 10 edited
Legend:
- Unmodified
- Added
- Removed
-
uspace/drv/usbhid/hiddev.c
re93e319 r59e01689 158 158 } 159 159 160 hid_dev->report_desc_size = length; 161 160 162 usb_log_debug("Done.\n"); 161 163 … … 262 264 263 265 if (rc != EOK) { 264 usb_log_warning("Problem with parsing Report descriptor: %s.\n", 265 str_error(rc)); 266 return rc; 267 } 266 usb_log_warning("Problem with getting Report descriptor: %s.\n", 267 str_error(rc)); 268 return rc; 269 } 270 271 rc = usb_hid_parse_report_descriptor(hid_dev->parser, 272 hid_dev->report_desc, hid_dev->report_desc_size); 273 if (rc != EOK) { 274 usb_log_warning("Problem parsing Report descriptor: %s.\n", 275 str_error(rc)); 276 return rc; 277 } 278 279 usb_hid_descriptor_print(hid_dev->parser); 268 280 269 281 return EOK; … … 289 301 290 302 memset(dev, 0, sizeof(usbhid_dev_t)); 303 304 dev->parser = (usb_hid_report_parser_t *)(malloc(sizeof( 305 usb_hid_report_parser_t))); 306 if (dev->parser == NULL) { 307 usb_log_fatal("No memory!\n"); 308 free(dev); 309 return NULL; 310 } 291 311 292 312 dev->initialized = 0; … … 399 419 400 420 /* 421 * Initialize the report parser. 422 */ 423 rc = usb_hid_parser_init(hid_dev->parser); 424 if (rc != EOK) { 425 usb_log_error("Failed to initialize report parser.\n"); 426 return rc; 427 } 428 429 /* 401 430 * Get descriptors, parse descriptors and save endpoints. 402 431 */ … … 411 440 if (rc != EOK) { 412 441 /* TODO: end session?? */ 442 usb_endpoint_pipe_end_session(&hid_dev->ctrl_pipe); 413 443 usb_log_error("Failed to process descriptors: %s.\n", 414 444 str_error(rc)); -
uspace/drv/usbhid/hiddev.h
re93e319 r59e01689 32 32 /** @file 33 33 * Generic USB HID device structure and API. 34 * 35 * @todo Add function for parsing report - this is generic HID function, not 36 * keyboard-specific, as the report parser is also generic. 37 * @todo Add function for polling as that is also a generic HID process. 38 * @todo Add interrupt in pipe to the structure. 34 39 */ 35 40 … … 75 80 /** Report descriptor. */ 76 81 uint8_t *report_desc; 82 83 size_t report_desc_size; 84 77 85 /** HID Report parser. */ 78 86 usb_hid_report_parser_t *parser; -
uspace/drv/usbhid/kbddev.c
re93e319 r59e01689 51 51 #include <usb/classes/hidparser.h> 52 52 #include <usb/classes/classes.h> 53 #include <usb/classes/hidut.h> 53 54 54 55 #include "kbddev.h" … … 93 94 .flags = 0 94 95 }; 96 97 typedef enum usbhid_kbd_flags { 98 USBHID_KBD_STATUS_UNINITIALIZED = 0, 99 USBHID_KBD_STATUS_INITIALIZED = 1, 100 USBHID_KBD_STATUS_TO_DESTROY = -1 101 } usbhid_kbd_flags; 95 102 96 103 /*----------------------------------------------------------------------------*/ … … 124 131 }; 125 132 133 typedef enum usbhid_lock_code { 134 USBHID_LOCK_NUM = 0x53, 135 USBHID_LOCK_CAPS = 0x39, 136 USBHID_LOCK_SCROLL = 0x47, 137 USBHID_LOCK_COUNT = 3 138 } usbhid_lock_code; 139 140 static const usbhid_lock_code usbhid_lock_codes[USBHID_LOCK_COUNT] = { 141 USBHID_LOCK_NUM, 142 USBHID_LOCK_CAPS, 143 USBHID_LOCK_SCROLL 144 }; 145 126 146 /*----------------------------------------------------------------------------*/ 127 147 /* IPC method handler */ … … 218 238 219 239 assert(kbd_dev->hid_dev != NULL); 220 assert(kbd_dev->hid_dev->initialized );240 assert(kbd_dev->hid_dev->initialized == USBHID_KBD_STATUS_INITIALIZED); 221 241 usbhid_req_set_report(kbd_dev->hid_dev, USB_HID_REPORT_TYPE_OUTPUT, 222 242 buffer, BOOTP_BUFFER_OUT_SIZE); … … 346 366 * @sa usbhid_kbd_push_ev() 347 367 */ 348 static void usbhid_kbd_check_modifier_changes(usbhid_kbd_t *kbd_dev, 349 uint8_t modifiers) 350 { 351 /* 352 * TODO: why the USB keyboard has NUM_, SCROLL_ and CAPS_LOCK 353 * both as modifiers and as keys with their own scancodes??? 354 * 355 * modifiers should be sent as normal keys to usbhid_parse_scancode()!! 356 * so maybe it would be better if I received it from report parser in 357 * that way 358 */ 359 360 int i; 361 for (i = 0; i < USB_HID_MOD_COUNT; ++i) { 362 if ((modifiers & usb_hid_modifiers_consts[i]) && 363 !(kbd_dev->modifiers & usb_hid_modifiers_consts[i])) { 364 // modifier pressed 365 if (usbhid_modifiers_keycodes[i] != 0) { 366 usbhid_kbd_push_ev(kbd_dev, KEY_PRESS, 367 usbhid_modifiers_keycodes[i]); 368 } 369 } else if (!(modifiers & usb_hid_modifiers_consts[i]) && 370 (kbd_dev->modifiers & usb_hid_modifiers_consts[i])) { 371 // modifier released 372 if (usbhid_modifiers_keycodes[i] != 0) { 373 usbhid_kbd_push_ev(kbd_dev, KEY_RELEASE, 374 usbhid_modifiers_keycodes[i]); 375 } 376 } // no change 377 } 378 379 kbd_dev->modifiers = modifiers; 368 //static void usbhid_kbd_check_modifier_changes(usbhid_kbd_t *kbd_dev, 369 // const uint8_t *key_codes, size_t count) 370 //{ 371 // /* 372 // * TODO: why the USB keyboard has NUM_, SCROLL_ and CAPS_LOCK 373 // * both as modifiers and as keyUSB_HID_LOCK_COUNTs with their own scancodes??? 374 // * 375 // * modifiers should be sent as normal keys to usbhid_parse_scancode()!! 376 // * so maybe it would be better if I received it from report parser in 377 // * that way 378 // */ 379 380 // int i; 381 // for (i = 0; i < count; ++i) { 382 // if ((modifiers & usb_hid_modifiers_consts[i]) && 383 // !(kbd_dev->modifiers & usb_hid_modifiers_consts[i])) { 384 // // modifier pressed 385 // if (usbhid_modifiers_keycodes[i] != 0) { 386 // usbhid_kbd_push_ev(kbd_dev, KEY_PRESS, 387 // usbhid_modifiers_keycodes[i]); 388 // } 389 // } else if (!(modifiers & usb_hid_modifiers_consts[i]) && 390 // (kbd_dev->modifiers & usb_hid_modifiers_consts[i])) { 391 // // modifier released 392 // if (usbhid_modifiers_keycodes[i] != 0) { 393 // usbhid_kbd_push_ev(kbd_dev, KEY_RELEASE, 394 // usbhid_modifiers_keycodes[i]); 395 // } 396 // } // no change 397 // } 398 399 // kbd_dev->modifiers = modifiers; 400 //} 401 402 /*----------------------------------------------------------------------------*/ 403 404 static inline int usbhid_kbd_is_lock(unsigned int key_code) 405 { 406 return (key_code == KC_NUM_LOCK 407 || key_code == KC_SCROLL_LOCK 408 || key_code == KC_CAPS_LOCK); 380 409 } 381 410 … … 404 433 /* 405 434 * First of all, check if the kbd have reported phantom state. 435 * 436 * TODO: this must be changed as we don't know which keys are modifiers 437 * and which are regular keys. 406 438 */ 407 439 i = 0; … … 434 466 // not found, i.e. the key was released 435 467 key = usbhid_parse_scancode(kbd_dev->keys[j]); 436 usbhid_kbd_repeat_stop(kbd_dev, key); 468 if (!usbhid_kbd_is_lock(key)) { 469 usbhid_kbd_repeat_stop(kbd_dev, key); 470 } 437 471 usbhid_kbd_push_ev(kbd_dev, KEY_RELEASE, key); 438 472 usb_log_debug2("Key released: %d\n", key); … … 458 492 key_codes[i]); 459 493 usbhid_kbd_push_ev(kbd_dev, KEY_PRESS, key); 460 usbhid_kbd_repeat_start(kbd_dev, key); 494 if (!usbhid_kbd_is_lock(key)) { 495 usbhid_kbd_repeat_start(kbd_dev, key); 496 } 461 497 } else { 462 498 // found, nothing happens … … 502 538 503 539 usb_log_debug("Got keys from parser: %s\n", 504 usb_debug_str_buffer(key_codes, kbd_dev->key_count, 0));540 usb_debug_str_buffer(key_codes, count, 0)); 505 541 506 542 if (count != kbd_dev->key_count) { … … 510 546 } 511 547 512 usbhid_kbd_check_modifier_changes(kbd_dev, modifiers);548 ///usbhid_kbd_check_modifier_changes(kbd_dev, key_codes, count); 513 549 usbhid_kbd_check_key_changes(kbd_dev, key_codes, count); 514 550 } … … 535 571 uint8_t *buffer, size_t actual_size) 536 572 { 573 assert(kbd_dev->initialized == USBHID_KBD_STATUS_INITIALIZED); 574 assert(kbd_dev->hid_dev->parser != NULL); 575 537 576 usb_hid_report_in_callbacks_t *callbacks = 538 577 (usb_hid_report_in_callbacks_t *)malloc( … … 541 580 callbacks->keyboard = usbhid_kbd_process_keycodes; 542 581 543 usb_log_debug("Calling usb_hid_ boot_keyboard_input_report() with "582 usb_log_debug("Calling usb_hid_parse_report() with " 544 583 "buffer %s\n", usb_debug_str_buffer(buffer, actual_size, 0)); 545 584 546 int rc = usb_hid_boot_keyboard_input_report(buffer, actual_size, 547 callbacks, kbd_dev); 585 // int rc = usb_hid_boot_keyboard_input_report(buffer, actual_size, 586 // callbacks, kbd_dev); 587 int rc = usb_hid_parse_report(kbd_dev->hid_dev->parser, buffer, 588 actual_size, callbacks, kbd_dev); 548 589 549 590 if (rc != EOK) { … … 584 625 585 626 kbd_dev->console_phone = -1; 586 kbd_dev->initialized = 0;627 kbd_dev->initialized = USBHID_KBD_STATUS_UNINITIALIZED; 587 628 588 629 return kbd_dev; … … 590 631 591 632 /*----------------------------------------------------------------------------*/ 592 /** 593 * Properly destroys the USB/HID keyboard structure. 594 * 595 * @param kbd_dev Pointer to the structure to be destroyed. 596 */ 597 static void usbhid_kbd_free(usbhid_kbd_t **kbd_dev) 598 { 599 if (kbd_dev == NULL || *kbd_dev == NULL) { 600 return; 601 } 602 603 // hangup phone to the console 604 async_hangup((*kbd_dev)->console_phone); 605 606 if ((*kbd_dev)->hid_dev != NULL) { 607 usbhid_dev_free(&(*kbd_dev)->hid_dev); 608 assert((*kbd_dev)->hid_dev == NULL); 609 } 610 611 if ((*kbd_dev)->repeat_mtx != NULL) { 612 /* TODO: replace by some check and wait */ 613 assert(!fibril_mutex_is_locked((*kbd_dev)->repeat_mtx)); 614 free((*kbd_dev)->repeat_mtx); 615 } 616 617 free(*kbd_dev); 618 *kbd_dev = NULL; 633 634 static void usbhid_kbd_mark_unusable(usbhid_kbd_t *kbd_dev) 635 { 636 kbd_dev->initialized = USBHID_KBD_STATUS_TO_DESTROY; 619 637 } 620 638 … … 658 676 } 659 677 660 if (kbd_dev->initialized ) {678 if (kbd_dev->initialized == USBHID_KBD_STATUS_INITIALIZED) { 661 679 usb_log_warning("Keyboard structure already initialized.\n"); 662 680 return EINVAL; … … 671 689 } 672 690 673 assert(kbd_dev->hid_dev->initialized );691 assert(kbd_dev->hid_dev->initialized == USBHID_KBD_STATUS_INITIALIZED); 674 692 675 693 // save the size of the report (boot protocol report by default) 676 kbd_dev->key_count = BOOTP_REPORT_SIZE; 694 // kbd_dev->key_count = BOOTP_REPORT_SIZE; 695 696 usb_hid_report_path_t path; 697 path.usage_page = USB_HIDUT_PAGE_KEYBOARD; 698 kbd_dev->key_count = usb_hid_report_input_length( 699 kbd_dev->hid_dev->parser, &path); 700 701 usb_log_debug("Size of the input report: %zu\n", kbd_dev->key_count); 702 677 703 kbd_dev->keys = (uint8_t *)calloc( 678 704 kbd_dev->key_count, sizeof(uint8_t)); … … 709 735 assert(kbd_dev->hid_dev != NULL); 710 736 assert(kbd_dev->hid_dev->initialized); 711 usbhid_req_set_protocol(kbd_dev->hid_dev, USB_HID_PROTOCOL_BOOT);737 //usbhid_req_set_protocol(kbd_dev->hid_dev, USB_HID_PROTOCOL_BOOT); 712 738 713 739 usbhid_kbd_set_led(kbd_dev); … … 715 741 usbhid_req_set_idle(kbd_dev->hid_dev, IDLE_RATE); 716 742 717 kbd_dev->initialized = 1;743 kbd_dev->initialized = USBHID_KBD_STATUS_INITIALIZED; 718 744 usb_log_info("HID/KBD device structure initialized.\n"); 719 745 … … 829 855 usbhid_kbd_poll(kbd_dev); 830 856 857 // as there is another fibril using this device, so we must leave the 858 // structure to it, but mark it for destroying. 859 usbhid_kbd_mark_unusable(kbd_dev); 831 860 // at the end, properly destroy the KBD structure 832 usbhid_kbd_free(&kbd_dev);833 assert(kbd_dev == NULL);861 // usbhid_kbd_free(&kbd_dev); 862 // assert(kbd_dev == NULL); 834 863 835 864 return EOK; … … 953 982 } 954 983 984 /*----------------------------------------------------------------------------*/ 985 986 int usbhid_kbd_is_usable(const usbhid_kbd_t *kbd_dev) 987 { 988 return (kbd_dev->initialized == USBHID_KBD_STATUS_INITIALIZED); 989 } 990 991 /*----------------------------------------------------------------------------*/ 992 /** 993 * Properly destroys the USB/HID keyboard structure. 994 * 995 * @param kbd_dev Pointer to the structure to be destroyed. 996 */ 997 void usbhid_kbd_free(usbhid_kbd_t **kbd_dev) 998 { 999 if (kbd_dev == NULL || *kbd_dev == NULL) { 1000 return; 1001 } 1002 1003 // hangup phone to the console 1004 async_hangup((*kbd_dev)->console_phone); 1005 1006 if ((*kbd_dev)->hid_dev != NULL) { 1007 usbhid_dev_free(&(*kbd_dev)->hid_dev); 1008 assert((*kbd_dev)->hid_dev == NULL); 1009 } 1010 1011 if ((*kbd_dev)->repeat_mtx != NULL) { 1012 /* TODO: replace by some check and wait */ 1013 assert(!fibril_mutex_is_locked((*kbd_dev)->repeat_mtx)); 1014 free((*kbd_dev)->repeat_mtx); 1015 } 1016 1017 free(*kbd_dev); 1018 *kbd_dev = NULL; 1019 } 1020 955 1021 /** 956 1022 * @} -
uspace/drv/usbhid/kbddev.h
re93e319 r59e01689 42 42 43 43 #include <usb/classes/hid.h> 44 #include <usb/classes/hidparser.h> 44 45 #include <ddf/driver.h> 45 46 #include <usb/pipes.h> … … 100 101 fibril_mutex_t *repeat_mtx; 101 102 102 /** State of the structure (for checking before use). */ 103 /** State of the structure (for checking before use). 104 * 105 * 0 - not initialized 106 * 1 - initialized 107 * -1 - ready for destroying 108 */ 103 109 int initialized; 104 110 } usbhid_kbd_t; … … 107 113 108 114 int usbhid_kbd_try_add_device(ddf_dev_t *dev); 115 116 int usbhid_kbd_is_usable(const usbhid_kbd_t *kbd_dev); 117 118 void usbhid_kbd_free(usbhid_kbd_t **kbd_dev); 109 119 110 120 void usbhid_kbd_push_ev(usbhid_kbd_t *kbd_dev, int type, unsigned int key); -
uspace/drv/usbhid/kbdrepeat.c
re93e319 r59e01689 77 77 78 78 while (true) { 79 // check if the kbd structure is usable 80 if (!usbhid_kbd_is_usable(kbd)) { 81 usbhid_kbd_free(&kbd); 82 assert(kbd == NULL); 83 return; 84 } 85 79 86 fibril_mutex_lock(kbd->repeat_mtx); 80 87 -
uspace/drv/usbmouse/init.c
re93e319 r59e01689 124 124 goto leave; 125 125 } 126 127 /* Open the control pipe. */ 128 rc = usb_endpoint_pipe_start_session(&dev->ctrl_pipe); 129 if (rc != EOK) { 130 goto leave; 131 } 132 133 /* Set the boot protocol. */ 134 rc = usb_control_request_set(&dev->ctrl_pipe, 135 USB_REQUEST_TYPE_CLASS, USB_REQUEST_RECIPIENT_INTERFACE, 136 USB_HIDREQ_SET_PROTOCOL, USB_HID_PROTOCOL_BOOT, dev->interface_no, 137 NULL, 0); 138 if (rc != EOK) { 139 goto leave; 140 } 141 142 /* Close the control pipe (ignore errors). */ 143 usb_endpoint_pipe_end_session(&dev->ctrl_pipe); 144 126 145 127 146 /* Everything allright. */ -
uspace/lib/usb/include/usb/classes/hidparser.h
re93e319 r59e01689 37 37 38 38 #include <stdint.h> 39 #include <adt/list.h> 40 #include <usb/classes/hid_report_items.h> 41 42 /** 43 * Item prefix 44 */ 45 #define USB_HID_ITEM_SIZE(data) ((uint8_t)(data & 0x3)) 46 #define USB_HID_ITEM_TAG(data) ((uint8_t)((data & 0xF0) >> 4)) 47 #define USB_HID_ITEM_TAG_CLASS(data) ((uint8_t)((data & 0xC) >> 2)) 48 #define USB_HID_ITEM_IS_LONG(data) (data == 0xFE) 49 50 51 /** 52 * Input/Output/Feature Item flags 53 */ 54 /** Constant (1) / Variable (0) */ 55 #define USB_HID_ITEM_FLAG_CONSTANT(flags) ((flags & 0x1) == 0x1) 56 /** Variable (1) / Array (0) */ 57 #define USB_HID_ITEM_FLAG_VARIABLE(flags) ((flags & 0x2) == 0x2) 58 /** Absolute / Relative*/ 59 #define USB_HID_ITEM_FLAG_RELATIVE(flags) ((flags & 0x4) == 0x4) 60 /** Wrap / No Wrap */ 61 #define USB_HID_ITEM_FLAG_WRAP(flags) ((flags & 0x8) == 0x8) 62 #define USB_HID_ITEM_FLAG_LINEAR(flags) ((flags & 0x10) == 0x10) 63 #define USB_HID_ITEM_FLAG_PREFERRED(flags) ((flags & 0x20) == 0x20) 64 #define USB_HID_ITEM_FLAG_POSITION(flags) ((flags & 0x40) == 0x40) 65 #define USB_HID_ITEM_FLAG_VOLATILE(flags) ((flags & 0x80) == 0x80) 66 #define USB_HID_ITEM_FLAG_BUFFERED(flags) ((flags & 0x100) == 0x100) 67 68 69 /** 70 * Description of path of usage pages and usages in report descriptor 71 */ 72 typedef struct { 73 int32_t usage_page; 74 } usb_hid_report_path_t; 39 75 40 76 /** … … 42 78 */ 43 79 typedef struct { 44 45 uint8_t usage_min; 46 uint8_t usage_max; 47 uint8_t logical_min; 48 uint8_t logical_max; 49 uint8_t size; 50 uint8_t count; 51 uint8_t offset; 52 80 int32_t id; 81 int32_t usage_page; 82 int32_t usage; 83 int32_t usage_minimum; 84 int32_t usage_maximum; 85 int32_t logical_minimum; 86 int32_t logical_maximum; 87 int32_t size; 88 int32_t count; 89 size_t offset; 90 int32_t delimiter; 91 92 int32_t unit_exponent; 93 int32_t unit; 94 95 /* 96 * some not yet used fields 97 */ 98 int32_t string_index; 99 int32_t string_minimum; 100 int32_t string_maximum; 101 int32_t designator_index; 102 int32_t designator_minimum; 103 int32_t designator_maximum; 104 int32_t physical_minimum; 105 int32_t physical_maximum; 106 107 uint8_t item_flags; 108 109 link_t link; 53 110 } usb_hid_report_item_t; 54 111 55 112 56 113 /** HID report parser structure. */ 57 typedef struct { 58 } usb_hid_report_parser_t; 114 typedef struct { 115 link_t input; 116 link_t output; 117 link_t feature; 118 } usb_hid_report_parser_t; 119 59 120 60 121 … … 127 188 int usb_hid_boot_keyboard_output_report(uint8_t leds, uint8_t *data, size_t size); 128 189 190 int usb_hid_parser_init(usb_hid_report_parser_t *parser); 129 191 int usb_hid_parse_report_descriptor(usb_hid_report_parser_t *parser, 130 192 const uint8_t *data, size_t size); … … 134 196 const usb_hid_report_in_callbacks_t *callbacks, void *arg); 135 197 136 137 int usb_hid_free_report_parser(usb_hid_report_parser_t *parser); 198 int usb_hid_report_input_length(const usb_hid_report_parser_t *parser, 199 const usb_hid_report_path_t *path); 200 201 202 void usb_hid_free_report_parser(usb_hid_report_parser_t *parser); 203 204 void usb_hid_descriptor_print(usb_hid_report_parser_t *parser); 138 205 139 206 #endif -
uspace/lib/usb/include/usb/devdrv.h
re93e319 r59e01689 47 47 */ 48 48 usb_endpoint_mapping_t *pipes; 49 /** Current interface. 50 * Usually, drivers operate on single interface only. 51 * This item contains the value of the interface or -1 for any. 52 */ 53 int interface_no; 49 54 /** Generic DDF device backing this one. */ 50 55 ddf_dev_t *ddf_dev; -
uspace/lib/usb/src/devdrv.c
re93e319 r59e01689 109 109 { 110 110 int rc; 111 int my_interface= usb_device_get_assigned_interface(dev->ddf_dev);111 dev->interface_no = usb_device_get_assigned_interface(dev->ddf_dev); 112 112 113 113 size_t pipe_count = count_other_pipes(drv); … … 134 134 135 135 dev->pipes[i].description = drv->endpoints[i]; 136 dev->pipes[i].interface_no = my_interface;136 dev->pipes[i].interface_no = dev->interface_no; 137 137 } 138 138 -
uspace/lib/usb/src/hidparser.c
re93e319 r59e01689 36 36 #include <errno.h> 37 37 #include <stdio.h> 38 #include <malloc.h> 39 #include <mem.h> 40 #include <usb/debug.h> 41 42 #define USB_HID_NEW_REPORT_ITEM 1 43 #define USB_HID_NO_ACTION 2 44 #define USB_HID_UNKNOWN_TAG -99 45 46 #define BAD_HACK_USAGE_PAGE 0x07 47 48 int usb_hid_report_parse_tag(uint8_t tag, uint8_t class, const uint8_t *data, size_t item_size, 49 usb_hid_report_item_t *report_item); 50 int usb_hid_report_parse_main_tag(uint8_t tag, const uint8_t *data, size_t item_size, 51 usb_hid_report_item_t *report_item); 52 int usb_hid_report_parse_global_tag(uint8_t tag, const uint8_t *data, size_t item_size, 53 usb_hid_report_item_t *report_item); 54 int usb_hid_report_parse_local_tag(uint8_t tag, const uint8_t *data, size_t item_size, 55 usb_hid_report_item_t *report_item); 56 57 void usb_hid_descriptor_print_list(link_t *head); 58 int usb_hid_report_reset_local_items(); 59 void usb_hid_free_report_list(link_t *head); 60 int32_t usb_hid_report_tag_data_int32(const uint8_t *data, size_t size); 61 inline size_t usb_hid_count_item_offset(usb_hid_report_item_t * report_item, size_t offset); 62 int usb_hid_translate_data(usb_hid_report_item_t *item, const uint8_t *data, size_t j); 63 int usb_pow(int a, int b); 64 65 int usb_pow(int a, int b) 66 { 67 switch(b) { 68 case 0: 69 return 1; 70 break; 71 case 1: 72 return a; 73 break; 74 default: 75 return a * usb_pow(a, b-1); 76 break; 77 } 78 } 79 80 /** 81 * 82 */ 83 int usb_hid_parser_init(usb_hid_report_parser_t *parser) 84 { 85 if(parser == NULL) { 86 return -1; 87 } 88 89 list_initialize(&(parser->input)); 90 list_initialize(&(parser->output)); 91 list_initialize(&(parser->feature)); 92 93 return EOK; 94 } 95 38 96 39 97 /** Parse HID report descriptor. … … 46 104 const uint8_t *data, size_t size) 47 105 { 48 return ENOTSUP; 106 size_t i=0; 107 uint8_t tag=0; 108 uint8_t item_size=0; 109 int class=0; 110 int ret; 111 usb_hid_report_item_t *report_item=0; 112 usb_hid_report_item_t *new_report_item; 113 114 size_t offset_input=0; 115 size_t offset_output=0; 116 size_t offset_feature=0; 117 118 119 if(!(report_item=malloc(sizeof(usb_hid_report_item_t)))){ 120 return ENOMEM; 121 } 122 memset(report_item, 0, sizeof(usb_hid_report_item_t)); 123 124 link_initialize(&(report_item->link)); 125 126 while(i<size){ 127 if(!USB_HID_ITEM_IS_LONG(data[i])){ 128 129 if((i+USB_HID_ITEM_SIZE(data[i]))>= size){ 130 return -1; // TODO ERROR CODE 131 } 132 133 tag = USB_HID_ITEM_TAG(data[i]); 134 item_size = USB_HID_ITEM_SIZE(data[i]); 135 class = USB_HID_ITEM_TAG_CLASS(data[i]); 136 137 usb_log_debug2( 138 "i(%u) data(%X) value(%X): TAG %u, class %u, size %u - ", i, 139 data[i], usb_hid_report_tag_data_int32(data+i+1,item_size), 140 tag, class, item_size); 141 142 ret = usb_hid_report_parse_tag(tag,class,data+i+1, 143 item_size,report_item); 144 usb_log_debug2("ret: %u\n", ret); 145 switch(ret){ 146 case USB_HID_NEW_REPORT_ITEM: 147 // store report item to report and create the new one 148 usb_log_debug("\nNEW REPORT ITEM: %X",tag); 149 150 switch(tag) { 151 case USB_HID_REPORT_TAG_INPUT: 152 report_item->offset = offset_input; 153 offset_input += report_item->count * report_item->size; 154 usb_log_debug(" - INPUT\n"); 155 list_append(&(report_item->link), &(parser->input)); 156 break; 157 case USB_HID_REPORT_TAG_OUTPUT: 158 report_item->offset = offset_output; 159 offset_output += report_item->count * report_item->size; 160 usb_log_debug(" - OUTPUT\n"); 161 list_append(&(report_item->link), &(parser->output)); 162 163 break; 164 case USB_HID_REPORT_TAG_FEATURE: 165 report_item->offset = offset_feature; 166 offset_feature += report_item->count * report_item->size; 167 usb_log_debug(" - FEATURE\n"); 168 list_append(&(report_item->link), &(parser->feature)); 169 break; 170 default: 171 usb_log_debug("\tjump over - tag %X\n", tag); 172 break; 173 } 174 175 /* clone current state table to the new item */ 176 if(!(new_report_item = malloc(sizeof(usb_hid_report_item_t)))) { 177 return ENOMEM; 178 } 179 memcpy(new_report_item,report_item, sizeof(usb_hid_report_item_t)); 180 /* reset local items */ 181 new_report_item->usage_minimum = 0; 182 new_report_item->usage_maximum = 0; 183 184 link_initialize(&(new_report_item->link)); 185 report_item = new_report_item; 186 187 break; 188 case USB_HID_REPORT_TAG_PUSH: 189 // push current state to stack 190 // not yet implemented 191 break; 192 case USB_HID_REPORT_TAG_POP: 193 // restore current state from stack 194 // not yet implemented 195 break; 196 197 default: 198 // nothing special to do 199 break; 200 } 201 202 /* jump over the processed block */ 203 i += 1 + USB_HID_ITEM_SIZE(data[i]); 204 } 205 else{ 206 // TBD 207 i += 3 + USB_HID_ITEM_SIZE(data[i+1]); 208 } 209 210 211 } 212 213 return EOK; 214 } 215 216 217 /** 218 * Parse input report. 219 * 220 * @param data Data for report 221 * @param size Size of report 222 * @param callbacks Callbacks for report actions 223 * @param arg Custom arguments 224 * 225 * @return Error code 226 */ 227 int usb_hid_boot_keyboard_input_report(const uint8_t *data, size_t size, 228 const usb_hid_report_in_callbacks_t *callbacks, void *arg) 229 { 230 int i; 231 usb_hid_report_item_t item; 232 233 /* fill item due to the boot protocol report descriptor */ 234 // modifier keys are in the first byte 235 uint8_t modifiers = data[0]; 236 237 item.offset = 2; /* second byte is reserved */ 238 item.size = 8; 239 item.count = 6; 240 item.usage_minimum = 0; 241 item.usage_maximum = 255; 242 item.logical_minimum = 0; 243 item.logical_maximum = 255; 244 245 if (size != 8) { 246 return -1; //ERANGE; 247 } 248 249 uint8_t keys[6]; 250 for (i = 0; i < item.count; i++) { 251 keys[i] = data[i + item.offset]; 252 } 253 254 callbacks->keyboard(keys, 6, modifiers, arg); 255 return EOK; 256 } 257 258 /** 259 * Makes output report for keyboard boot protocol 260 * 261 * @param leds 262 * @param output Output report data buffer 263 * @param size Size of the output buffer 264 * @return Error code 265 */ 266 int usb_hid_boot_keyboard_output_report(uint8_t leds, uint8_t *data, size_t size) 267 { 268 if(size != 1){ 269 return -1; 270 } 271 272 /* used only first five bits, others are only padding*/ 273 *data = leds; 274 return EOK; 275 } 276 277 /** 278 * 279 * @param Tag to parse 280 * @param Report descriptor buffer 281 * @param Size of data belongs to this tag 282 * @param Current report item structe 283 * @return Code of action to be done next 284 */ 285 int usb_hid_report_parse_tag(uint8_t tag, uint8_t class, const uint8_t *data, size_t item_size, 286 usb_hid_report_item_t *report_item) 287 { 288 int ret; 289 290 switch(class){ 291 case USB_HID_TAG_CLASS_MAIN: 292 293 if((ret=usb_hid_report_parse_main_tag(tag,data,item_size,report_item)) == EOK) { 294 return USB_HID_NEW_REPORT_ITEM; 295 } 296 else { 297 /*TODO process the error */ 298 return ret; 299 } 300 break; 301 302 case USB_HID_TAG_CLASS_GLOBAL: 303 return usb_hid_report_parse_global_tag(tag,data,item_size,report_item); 304 break; 305 306 case USB_HID_TAG_CLASS_LOCAL: 307 return usb_hid_report_parse_local_tag(tag,data,item_size,report_item); 308 break; 309 default: 310 return USB_HID_NO_ACTION; 311 } 312 } 313 314 /** 315 * Parse main tags of report descriptor 316 * 317 * @param Tag identifier 318 * @param Data buffer 319 * @param Length of data buffer 320 * @param Current state table 321 * @return Error code 322 */ 323 324 int usb_hid_report_parse_main_tag(uint8_t tag, const uint8_t *data, size_t item_size, 325 usb_hid_report_item_t *report_item) 326 { 327 switch(tag) 328 { 329 case USB_HID_REPORT_TAG_INPUT: 330 case USB_HID_REPORT_TAG_OUTPUT: 331 case USB_HID_REPORT_TAG_FEATURE: 332 report_item->item_flags = *data; 333 return EOK; 334 break; 335 336 case USB_HID_REPORT_TAG_COLLECTION: 337 // TODO 338 return USB_HID_NO_ACTION; 339 break; 340 341 case USB_HID_REPORT_TAG_END_COLLECTION: 342 /* should be ignored */ 343 return USB_HID_NO_ACTION; 344 break; 345 default: 346 return USB_HID_NO_ACTION; 347 } 348 349 return EOK; 350 } 351 352 /** 353 * Parse global tags of report descriptor 354 * 355 * @param Tag identifier 356 * @param Data buffer 357 * @param Length of data buffer 358 * @param Current state table 359 * @return Error code 360 */ 361 362 int usb_hid_report_parse_global_tag(uint8_t tag, const uint8_t *data, size_t item_size, 363 usb_hid_report_item_t *report_item) 364 { 365 // TODO take care about the bit length of data 366 switch(tag) 367 { 368 case USB_HID_REPORT_TAG_USAGE_PAGE: 369 report_item->usage_page = usb_hid_report_tag_data_int32(data,item_size); 370 break; 371 case USB_HID_REPORT_TAG_LOGICAL_MINIMUM: 372 report_item->logical_minimum = usb_hid_report_tag_data_int32(data,item_size); 373 break; 374 case USB_HID_REPORT_TAG_LOGICAL_MAXIMUM: 375 report_item->logical_maximum = usb_hid_report_tag_data_int32(data,item_size); 376 break; 377 case USB_HID_REPORT_TAG_PHYSICAL_MINIMUM: 378 report_item->physical_minimum = usb_hid_report_tag_data_int32(data,item_size); 379 break; 380 case USB_HID_REPORT_TAG_PHYSICAL_MAXIMUM: 381 report_item->physical_maximum = usb_hid_report_tag_data_int32(data,item_size); 382 break; 383 case USB_HID_REPORT_TAG_UNIT_EXPONENT: 384 report_item->unit_exponent = usb_hid_report_tag_data_int32(data,item_size); 385 break; 386 case USB_HID_REPORT_TAG_UNIT: 387 report_item->unit = usb_hid_report_tag_data_int32(data,item_size); 388 break; 389 case USB_HID_REPORT_TAG_REPORT_SIZE: 390 report_item->size = usb_hid_report_tag_data_int32(data,item_size); 391 break; 392 case USB_HID_REPORT_TAG_REPORT_COUNT: 393 report_item->count = usb_hid_report_tag_data_int32(data,item_size); 394 break; 395 case USB_HID_REPORT_TAG_REPORT_ID: 396 report_item->id = usb_hid_report_tag_data_int32(data,item_size); 397 break; 398 case USB_HID_REPORT_TAG_PUSH: 399 case USB_HID_REPORT_TAG_POP: 400 return tag; 401 break; 402 403 default: 404 return USB_HID_NO_ACTION; 405 } 406 407 return EOK; 408 } 409 410 /** 411 * Parse local tags of report descriptor 412 * 413 * @param Tag identifier 414 * @param Data buffer 415 * @param Length of data buffer 416 * @param Current state table 417 * @return Error code 418 */ 419 int usb_hid_report_parse_local_tag(uint8_t tag, const uint8_t *data, size_t item_size, 420 usb_hid_report_item_t *report_item) 421 { 422 switch(tag) 423 { 424 case USB_HID_REPORT_TAG_USAGE: 425 report_item->usage = usb_hid_report_tag_data_int32(data,item_size); 426 break; 427 case USB_HID_REPORT_TAG_USAGE_MINIMUM: 428 report_item->usage_minimum = usb_hid_report_tag_data_int32(data,item_size); 429 break; 430 case USB_HID_REPORT_TAG_USAGE_MAXIMUM: 431 report_item->usage_maximum = usb_hid_report_tag_data_int32(data,item_size); 432 break; 433 case USB_HID_REPORT_TAG_DESIGNATOR_INDEX: 434 report_item->designator_index = usb_hid_report_tag_data_int32(data,item_size); 435 break; 436 case USB_HID_REPORT_TAG_DESIGNATOR_MINIMUM: 437 report_item->designator_minimum = usb_hid_report_tag_data_int32(data,item_size); 438 break; 439 case USB_HID_REPORT_TAG_DESIGNATOR_MAXIMUM: 440 report_item->designator_maximum = usb_hid_report_tag_data_int32(data,item_size); 441 break; 442 case USB_HID_REPORT_TAG_STRING_INDEX: 443 report_item->string_index = usb_hid_report_tag_data_int32(data,item_size); 444 break; 445 case USB_HID_REPORT_TAG_STRING_MINIMUM: 446 report_item->string_minimum = usb_hid_report_tag_data_int32(data,item_size); 447 break; 448 case USB_HID_REPORT_TAG_STRING_MAXIMUM: 449 report_item->string_maximum = usb_hid_report_tag_data_int32(data,item_size); 450 break; 451 case USB_HID_REPORT_TAG_DELIMITER: 452 report_item->delimiter = usb_hid_report_tag_data_int32(data,item_size); 453 break; 454 455 default: 456 return USB_HID_NO_ACTION; 457 } 458 459 return EOK; 460 } 461 462 /** 463 * Converts raw data to int32 (thats the maximum length of short item data) 464 * 465 * @param Data buffer 466 * @param Size of buffer 467 * @return Converted int32 number 468 */ 469 int32_t usb_hid_report_tag_data_int32(const uint8_t *data, size_t size) 470 { 471 unsigned int i; 472 int32_t result; 473 474 result = 0; 475 for(i=0; i<size; i++) { 476 result = (result | (data[i]) << (i*8)); 477 } 478 479 return result; 480 } 481 482 483 484 /** 485 * Prints content of given list of report items. 486 * 487 * @param List of report items 488 * @return void 489 */ 490 void usb_hid_descriptor_print_list(link_t *head) 491 { 492 usb_hid_report_item_t *report_item; 493 link_t *item; 494 495 if(head == NULL || list_empty(head)) { 496 usb_log_debug("\tempty\n"); 497 return; 498 } 499 500 for(item = head->next; item != head; item = item->next) { 501 502 report_item = list_get_instance(item, usb_hid_report_item_t, link); 503 504 usb_log_debug("\tOFFSET: %X\n", report_item->offset); 505 usb_log_debug("\tCOUNT: %X\n", report_item->count); 506 usb_log_debug("\tSIZE: %X\n", report_item->size); 507 usb_log_debug("\tCONSTANT/VAR: %X\n", USB_HID_ITEM_FLAG_CONSTANT(report_item->item_flags)); 508 usb_log_debug("\tVARIABLE/ARRAY: %X\n", USB_HID_ITEM_FLAG_VARIABLE(report_item->item_flags)); 509 usb_log_debug("\tUSAGE: %X\n", report_item->usage); 510 usb_log_debug("\tUSAGE PAGE: %X\n", report_item->usage_page); 511 usb_log_debug("\tLOGMIN: %X\n", report_item->logical_minimum); 512 usb_log_debug("\tLOGMAX: %X\n", report_item->logical_maximum); 513 usb_log_debug("\tPHYMIN: %X\n", report_item->physical_minimum); 514 usb_log_debug("\tPHYMAX: %X\n", report_item->physical_maximum); 515 usb_log_debug("\tUSAGEMIN: %X\n", report_item->usage_minimum); 516 usb_log_debug("\tUSAGEMAX: %X\n", report_item->usage_maximum); 517 518 usb_log_debug("\n"); 519 520 } 521 522 523 } 524 /** 525 * Prints content of given descriptor in human readable format. 526 * 527 * @param Parsed descriptor to print 528 * @return void 529 */ 530 void usb_hid_descriptor_print(usb_hid_report_parser_t *parser) 531 { 532 usb_log_debug("INPUT:\n"); 533 usb_hid_descriptor_print_list(&parser->input); 534 535 usb_log_debug("OUTPUT: \n"); 536 usb_hid_descriptor_print_list(&parser->output); 537 538 usb_log_debug("FEATURE:\n"); 539 usb_hid_descriptor_print_list(&parser->feature); 540 541 } 542 543 /** 544 * Releases whole linked list of report items 545 * 546 * 547 */ 548 void usb_hid_free_report_list(link_t *head) 549 { 550 return; 551 552 usb_hid_report_item_t *report_item; 553 link_t *next; 554 555 if(head == NULL || list_empty(head)) { 556 return; 557 } 558 559 next = head->next; 560 while(next != head) { 561 562 report_item = list_get_instance(next, usb_hid_report_item_t, link); 563 next = next->next; 564 565 free(report_item); 566 } 567 568 return; 569 570 } 571 572 /** Free the HID report parser structure 573 * 574 * @param parser Opaque HID report parser structure 575 * @return Error code 576 */ 577 void usb_hid_free_report_parser(usb_hid_report_parser_t *parser) 578 { 579 if(parser == NULL){ 580 return; 581 } 582 583 usb_hid_free_report_list(&parser->input); 584 usb_hid_free_report_list(&parser->output); 585 usb_hid_free_report_list(&parser->feature); 586 587 return; 49 588 } 50 589 … … 58 597 * @param arg Custom argument (passed through to the callbacks). 59 598 * @return Error code. 60 */ 599 */ 61 600 int usb_hid_parse_report(const usb_hid_report_parser_t *parser, 62 601 const uint8_t *data, size_t size, 63 602 const usb_hid_report_in_callbacks_t *callbacks, void *arg) 64 603 { 65 int i; 66 67 /* main parsing loop */ 68 while(0){ 69 } 70 71 72 uint8_t keys[6]; 73 74 for (i = 0; i < 6; ++i) { 75 keys[i] = data[i]; 76 } 77 78 callbacks->keyboard(keys, 6, 0, arg); 79 604 /* 605 * 606 * only key codes (usage page 0x07) will be processed 607 * other usages will be ignored 608 */ 609 link_t *list_item; 610 usb_hid_report_item_t *item; 611 uint8_t *keys; 612 uint8_t item_value; 613 size_t key_count=0; 614 size_t i=0; 615 size_t j=0; 616 617 // get the size of result keycodes array 618 usb_hid_report_path_t path; 619 path.usage_page = BAD_HACK_USAGE_PAGE; 620 key_count = usb_hid_report_input_length(parser, &path); 621 622 if(!(keys = malloc(sizeof(uint8_t) * key_count))){ 623 return ENOMEM; 624 } 625 626 // read data 627 list_item = parser->input.next; 628 while(list_item != &(parser->input)) { 629 630 item = list_get_instance(list_item, usb_hid_report_item_t, link); 631 if(!USB_HID_ITEM_FLAG_CONSTANT(item->item_flags) && 632 (item->usage_page == path.usage_page)) { 633 for(j=0; j<(size_t)(item->count); j++) { 634 if((USB_HID_ITEM_FLAG_VARIABLE(item->item_flags) == 0) || 635 ((item->usage_minimum == 0) && (item->usage_maximum == 0))) { 636 // variable item 637 keys[i++] = usb_hid_translate_data(item, data,j); 638 } 639 else { 640 // bitmapa 641 if((item_value = usb_hid_translate_data(item, data, j)) != 0) { 642 keys[i++] = j + item->usage_minimum; 643 } 644 else { 645 keys[i++] = 0; 646 } 647 } 648 } 649 } 650 list_item = list_item->next; 651 } 652 653 callbacks->keyboard(keys, key_count, 0, arg); 654 655 free(keys); 80 656 return EOK; 81 } 82 83 /** Free the HID report parser structure 84 * 85 * @param parser Opaque HID report parser structure 86 * @return Error code 87 */ 88 int usb_hid_free_report_parser(usb_hid_report_parser_t *parser) 89 { 90 91 return EOK; 92 } 93 94 95 /** 96 * Parse input report. 97 * 98 * @param data Data for report 99 * @param size Size of report 100 * @param callbacks Callbacks for report actions 101 * @param arg Custom arguments 102 * 103 * @return Error code 104 */ 105 int usb_hid_boot_keyboard_input_report(const uint8_t *data, size_t size, 106 const usb_hid_report_in_callbacks_t *callbacks, void *arg) 107 { 108 int i; 109 usb_hid_report_item_t item; 110 111 /* fill item due to the boot protocol report descriptor */ 112 // modifier keys are in the first byte 113 uint8_t modifiers = data[0]; 114 115 item.offset = 2; /* second byte is reserved */ 116 item.size = 8; 117 item.count = 6; 118 item.usage_min = 0; 119 item.usage_max = 255; 120 item.logical_min = 0; 121 item.logical_max = 255; 122 123 if (size != 8) { 124 return ERANGE; 125 } 126 127 uint8_t keys[6]; 128 for (i = 0; i < item.count; i++) { 129 keys[i] = data[i + item.offset]; 130 } 131 132 callbacks->keyboard(keys, 6, modifiers, arg); 133 return EOK; 134 } 135 136 /** 137 * Makes output report for keyboard boot protocol 138 * 139 * @param leds 140 * @param output Output report data buffer 141 * @param size Size of the output buffer 142 * @return Error code 143 */ 144 int usb_hid_boot_keyboard_output_report(uint8_t leds, uint8_t *data, size_t size) 145 { 146 if (size < 1){ 147 return -1; 148 } 149 150 data[0] = leds; 151 return EOK; 152 } 657 658 } 659 660 661 int usb_hid_translate_data(usb_hid_report_item_t *item, const uint8_t *data, size_t j) 662 { 663 int resolution; 664 int offset; 665 int part_size; 666 667 int32_t value; 668 int32_t mask; 669 const uint8_t *foo; 670 671 // now only common numbers llowed 672 if(item->size > 32) { 673 return 0; 674 } 675 676 if((item->physical_minimum == 0) && (item->physical_maximum == 0)) { 677 item->physical_minimum = item->logical_minimum; 678 item->physical_maximum = item->logical_maximum; 679 } 680 681 if(item->physical_maximum == item->physical_minimum){ 682 resolution = 1; 683 } 684 else { 685 resolution = (item->logical_maximum - item->logical_minimum) / 686 ((item->physical_maximum - item->physical_minimum) * 687 (usb_pow(10,(item->unit_exponent)))); 688 } 689 offset = item->offset + (j * item->size); 690 691 // FIXME 692 if((offset/8) != ((offset+item->size)/8)) { 693 usb_log_debug2("offset %d\n", offset); 694 695 part_size = ((offset+item->size)%8); 696 usb_log_debug2("part size %d\n",part_size); 697 698 // the higher one 699 foo = data+(offset/8); 700 mask = ((1 << (item->size-part_size))-1); 701 value = (*foo & mask) << part_size; 702 703 usb_log_debug2("hfoo %x\n", *foo); 704 usb_log_debug2("hmaska %x\n", mask); 705 usb_log_debug2("hval %d\n", value); 706 707 // the lower one 708 foo = data+((offset+item->size)/8); 709 mask = ((1 << part_size)-1) << (8-part_size); 710 value += ((*foo & mask) >> (8-part_size)); 711 712 usb_log_debug2("lfoo %x\n", *foo); 713 usb_log_debug2("lmaska %x\n", mask); 714 usb_log_debug2("lval %d\n", ((*foo & mask) >> (8-(item->size-part_size)))); 715 usb_log_debug2("val %d\n", value); 716 717 718 } 719 else { 720 foo = data+(offset/8); 721 mask = ((1 << item->size)-1) << (8-((offset%8)+item->size)); 722 value = (*foo & mask) >> (8-((offset%8)+item->size)); 723 724 usb_log_debug2("offset %d\n", offset); 725 726 usb_log_debug2("foo %x\n", *foo); 727 usb_log_debug2("maska %x\n", mask); 728 usb_log_debug2("val %d\n", value); 729 } 730 731 usb_log_debug2("---\n\n"); 732 733 return (int)(((value - item->logical_minimum) / resolution) + item->physical_minimum); 734 735 } 736 737 int usb_hid_report_input_length(const usb_hid_report_parser_t *parser, 738 const usb_hid_report_path_t *path) 739 { 740 int ret = 0; 741 link_t *item; 742 usb_hid_report_item_t *report_item; 743 744 item = (&parser->input)->next; 745 while(&parser->input != item) { 746 report_item = list_get_instance(item, usb_hid_report_item_t, link); 747 if(!USB_HID_ITEM_FLAG_CONSTANT(report_item->item_flags) && 748 (report_item->usage_page == path->usage_page)) { 749 ret += report_item->count; 750 } 751 752 item = item->next; 753 } 754 755 return ret; 756 } 757 758 153 759 154 760 /**
Note:
See TracChangeset
for help on using the changeset viewer.