diff options
-rw-r--r-- | drivers/hid/usbhid/hid-quirks.c | 163 | ||||
-rw-r--r-- | include/linux/hid.h | 1 |
2 files changed, 150 insertions, 14 deletions
diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c index c4fdccdda858..a5fc8b5144ef 100644 --- a/drivers/hid/usbhid/hid-quirks.c +++ b/drivers/hid/usbhid/hid-quirks.c | |||
@@ -447,22 +447,152 @@ static const struct hid_blacklist { | |||
447 | 447 | ||
448 | { 0, 0 } | 448 | { 0, 0 } |
449 | }; | 449 | }; |
450 | |||
451 | /* Dynamic HID quirks list - specified at runtime */ | ||
452 | struct quirks_list_struct { | ||
453 | struct hid_blacklist hid_bl_item; | ||
454 | struct list_head node; | ||
455 | }; | ||
456 | |||
457 | static LIST_HEAD(dquirks_list); | ||
458 | static DECLARE_RWSEM(dquirks_rwsem); | ||
459 | |||
460 | /* Runtime ("dynamic") quirks manipulation functions */ | ||
461 | |||
450 | /** | 462 | /** |
451 | * usbhid_exists_squirk: return any static quirks for a USB HID device | 463 | * usbhid_exists_dquirk: find any dynamic quirks for a USB HID device |
452 | * @idVendor: the 16-bit USB vendor ID, in native byteorder | 464 | * @idVendor: the 16-bit USB vendor ID, in native byteorder |
453 | * @idProduct: the 16-bit USB product ID, in native byteorder | 465 | * @idProduct: the 16-bit USB product ID, in native byteorder |
454 | * | 466 | * |
455 | * Description: | 467 | * Description: |
456 | * Given a USB vendor ID and product ID, return a pointer to | 468 | * Scans dquirks_list for a matching dynamic quirk and returns |
457 | * the hid_blacklist entry associated with that device. | 469 | * the pointer to the relevant struct hid_blacklist if found. |
458 | * | 470 | * Must be called with a read lock held on dquirks_rwsem. |
459 | * Returns: pointer if quirk found, or NULL if no quirks found. | 471 | * |
460 | */ | 472 | * Returns: NULL if no quirk found, struct hid_blacklist * if found. |
473 | */ | ||
474 | static struct hid_blacklist *usbhid_exists_dquirk(const u16 idVendor, | ||
475 | const u16 idProduct) | ||
476 | { | ||
477 | struct quirks_list_struct *q; | ||
478 | struct hid_blacklist *bl_entry = NULL; | ||
479 | |||
480 | WARN_ON(idVendor == 0); | ||
481 | |||
482 | list_for_each_entry(q, &dquirks_list, node) { | ||
483 | if (q->hid_bl_item.idVendor == idVendor && | ||
484 | q->hid_bl_item.idProduct == idProduct) { | ||
485 | bl_entry = &q->hid_bl_item; | ||
486 | break; | ||
487 | } | ||
488 | } | ||
489 | |||
490 | if (bl_entry != NULL) | ||
491 | dbg("Found dynamic quirk 0x%x for USB HID vendor 0x%hx prod 0x%hx\n", | ||
492 | bl_entry->quirks, bl_entry->idVendor, | ||
493 | bl_entry->idProduct); | ||
494 | |||
495 | return bl_entry; | ||
496 | } | ||
497 | |||
498 | |||
499 | /** | ||
500 | * usbhid_modify_dquirk: add/replace a HID quirk | ||
501 | * @idVendor: the 16-bit USB vendor ID, in native byteorder | ||
502 | * @idProduct: the 16-bit USB product ID, in native byteorder | ||
503 | * @quirks: the u32 quirks value to add/replace | ||
504 | * | ||
505 | * Description: | ||
506 | * If an dynamic quirk exists in memory for this (idVendor, | ||
507 | * idProduct) pair, replace its quirks value with what was | ||
508 | * provided. Otherwise, add the quirk to the dynamic quirks list. | ||
509 | * | ||
510 | * Returns: 0 OK, -error on failure. | ||
511 | */ | ||
512 | int usbhid_modify_dquirk(const u16 idVendor, const u16 idProduct, | ||
513 | const u32 quirks) | ||
514 | { | ||
515 | struct quirks_list_struct *q_new, *q; | ||
516 | int list_edited = 0; | ||
517 | |||
518 | if (!idVendor) { | ||
519 | dbg("Cannot add a quirk with idVendor = 0"); | ||
520 | return -EINVAL; | ||
521 | } | ||
522 | |||
523 | q_new = kmalloc(sizeof(struct quirks_list_struct), GFP_KERNEL); | ||
524 | if (!q_new) { | ||
525 | dbg("Could not allocate quirks_list_struct"); | ||
526 | return -ENOMEM; | ||
527 | } | ||
528 | |||
529 | q_new->hid_bl_item.idVendor = idVendor; | ||
530 | q_new->hid_bl_item.idProduct = idProduct; | ||
531 | q_new->hid_bl_item.quirks = quirks; | ||
532 | |||
533 | down_write(&dquirks_rwsem); | ||
534 | |||
535 | list_for_each_entry(q, &dquirks_list, node) { | ||
536 | |||
537 | if (q->hid_bl_item.idVendor == idVendor && | ||
538 | q->hid_bl_item.idProduct == idProduct) { | ||
539 | |||
540 | list_replace(&q->node, &q_new->node); | ||
541 | kfree(q); | ||
542 | list_edited = 1; | ||
543 | break; | ||
544 | |||
545 | } | ||
546 | |||
547 | } | ||
548 | |||
549 | if (!list_edited) | ||
550 | list_add_tail(&q_new->node, &dquirks_list); | ||
551 | |||
552 | up_write(&dquirks_rwsem); | ||
553 | |||
554 | return 0; | ||
555 | } | ||
556 | |||
557 | |||
558 | /** | ||
559 | * usbhid_remove_all_dquirks: remove all runtime HID quirks from memory | ||
560 | * | ||
561 | * Description: | ||
562 | * Free all memory associated with dynamic quirks - called before | ||
563 | * module unload. | ||
564 | * | ||
565 | */ | ||
566 | static void usbhid_remove_all_dquirks(void) | ||
567 | { | ||
568 | struct quirks_list_struct *q, *temp; | ||
569 | |||
570 | down_write(&dquirks_rwsem); | ||
571 | list_for_each_entry_safe(q, temp, &dquirks_list, node) { | ||
572 | list_del(&q->node); | ||
573 | kfree(q); | ||
574 | } | ||
575 | up_write(&dquirks_rwsem); | ||
576 | |||
577 | } | ||
578 | |||
579 | |||
580 | /** | ||
581 | * usbhid_exists_squirk: return any static quirks for a USB HID device | ||
582 | * @idVendor: the 16-bit USB vendor ID, in native byteorder | ||
583 | * @idProduct: the 16-bit USB product ID, in native byteorder | ||
584 | * | ||
585 | * Description: | ||
586 | * Given a USB vendor ID and product ID, return a pointer to | ||
587 | * the hid_blacklist entry associated with that device. | ||
588 | * | ||
589 | * Returns: pointer if quirk found, or NULL if no quirks found. | ||
590 | */ | ||
461 | static const struct hid_blacklist *usbhid_exists_squirk(const u16 idVendor, | 591 | static const struct hid_blacklist *usbhid_exists_squirk(const u16 idVendor, |
462 | const u16 idProduct) | 592 | const u16 idProduct) |
463 | { | 593 | { |
464 | const struct hid_blacklist *bl_entry = NULL; | 594 | const struct hid_blacklist *bl_entry = NULL; |
465 | int n = 0; | 595 | int n = 0; |
466 | 596 | ||
467 | for (; hid_blacklist[n].idVendor; n++) | 597 | for (; hid_blacklist[n].idVendor; n++) |
468 | if (hid_blacklist[n].idVendor == idVendor && | 598 | if (hid_blacklist[n].idVendor == idVendor && |
@@ -502,9 +632,14 @@ u32 usbhid_lookup_quirk(const u16 idVendor, const u16 idProduct) | |||
502 | idProduct <= USB_DEVICE_ID_CODEMERCS_IOW_LAST) | 632 | idProduct <= USB_DEVICE_ID_CODEMERCS_IOW_LAST) |
503 | return HID_QUIRK_IGNORE; | 633 | return HID_QUIRK_IGNORE; |
504 | 634 | ||
505 | bl_entry = usbhid_exists_squirk(idVendor, idProduct); | 635 | down_read(&dquirks_rwsem); |
636 | bl_entry = usbhid_exists_dquirk(idVendor, idProduct); | ||
637 | if (!bl_entry) | ||
638 | bl_entry = usbhid_exists_squirk(idVendor, idProduct); | ||
506 | if (bl_entry) | 639 | if (bl_entry) |
507 | quirks = bl_entry->quirks; | 640 | quirks = bl_entry->quirks; |
641 | up_read(&dquirks_rwsem); | ||
642 | |||
508 | return quirks; | 643 | return quirks; |
509 | } | 644 | } |
510 | 645 | ||
diff --git a/include/linux/hid.h b/include/linux/hid.h index 23e0dcf8ec41..4ba456d71f6b 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h | |||
@@ -496,6 +496,7 @@ void hid_free_device(struct hid_device *device); | |||
496 | struct hid_device *hid_parse_report(__u8 *start, unsigned size); | 496 | struct hid_device *hid_parse_report(__u8 *start, unsigned size); |
497 | 497 | ||
498 | u32 usbhid_lookup_quirk(const u16 idVendor, const u16 idProduct); | 498 | u32 usbhid_lookup_quirk(const u16 idVendor, const u16 idProduct); |
499 | int usbhid_modify_dquirk(const u16 idVendor, const u16 idProduct, const u32 quirks); | ||
499 | 500 | ||
500 | #ifdef CONFIG_HID_FF | 501 | #ifdef CONFIG_HID_FF |
501 | int hid_ff_init(struct hid_device *hid); | 502 | int hid_ff_init(struct hid_device *hid); |