/* * Logitech Unifying receiver emulation (HID++ protocol). * * Copyright (c) 2014 Peter Wu * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "hw/hw.h" #include "ui/console.h" #include "hw/usb.h" #include "dump.h" #include "hw/input/hid.h" #include "hw/usb/hid-logitech-dj.h" /* Report IDs */ enum { HIDPP_SHORT = 0x10, /* 7 bytes */ HIDPP_LONG = 0x11, /* 20 bytes */ DJ_SHORT = 0x20, /* 15 bytes */ DJ_LONG = 0x21, /* 32 bytes */ }; /* returns the expected length of the report or 0 if invalid */ static unsigned msg_report_length(HidppMsg *msg) { switch (msg->report_id) { case HIDPP_SHORT: return 7; case HIDPP_LONG: return 20; case DJ_SHORT: return 15; case DJ_LONG: return 32; default: return 0; } } static void hidpp_queue_error(USBLtunifyState *s, HidppMsg *msg, uint8_t err) { HidppMsgShort *report; unsigned slot; if (s->error_queue.n < LQUEUE_SIZE(s->error_queue)) { slot = LQUEUE_WRAP(s->error_queue, s->error_queue.head + s->error_queue.n); s->error_queue.n++; report = &s->error_queue.reports[slot]; report->report_id = HIDPP_SHORT; report->device_index = msg->device_index; report->sub_id = msg->hidpp_s.sub_id; report->address = msg->hidpp_s.address; report->value[0] = err; report->value[1] = report->value[2] = 0; } } static void hidpp_queue_output_report(USBLtunifyState *s, HidppMsg *msg) { unsigned slot; assert(msg_report_length(msg) != 0); if (s->output_queue.n < LQUEUE_SIZE(s->output_queue)) { slot = LQUEUE_WRAP(s->output_queue, s->output_queue.head + s->output_queue.n); s->output_queue.n++; s->output_queue.reports[slot] = *msg; } } /* process a received report */ static void hidpp_set_report(USBDevice *dev, USBPacket *p, uint8_t *data, size_t len) { USBLtunifyState *s = DO_UPCAST(USBLtunifyState, dev, dev); HidppMsg *msg = (HidppMsg *) data; int report_len; if (len < sizeof(HidppMsgShort)) { goto fail; } report_len = msg_report_length(msg); if (report_len > 0 && len >= report_len) { if (s->input_queue.n < LQUEUE_SIZE(s->input_queue)) { unsigned slot = LQUEUE_WRAP(s->input_queue, s->input_queue.head + s->input_queue.n); s->input_queue.n++; memcpy((uint8_t *) &s->input_queue.reports[slot], data, report_len); } else { /* queue full, cannot accept this report */ hidpp_queue_error(s, msg, HIDPP_ERR_BUSY); } } else { fail: p->status = USB_RET_STALL; } } void usb_ltunify_handle_control_hidpp(USBDevice *dev, USBPacket *p, int request, int value, int index, int length, uint8_t *data) { //USBLtunifyState *s = (USBLtunifyState *) dev; switch (request) { case HID_GET_REPORT: /* FIXME */ break; case HID_SET_REPORT: hidpp_set_report(dev, p, data, length); break; default: p->status = USB_RET_STALL; break; } } void usb_ltunify_handle_datain_hidpp(USBDevice *dev, USBPacket *p) { USBLtunifyState *s = (USBLtunifyState *) dev; int slot; int len = 0; HidppMsg *msg = NULL; assert(p->pid == USB_TOKEN_IN); assert(p->ep->nr == 3); /* process input reports */ if (s->input_queue.n > 0 && 0) { slot = s->input_queue.head; LQUEUE_INCR(s->input_queue, s->input_queue.head); s->input_queue.n--; } /* try to send a reply to a previously sent request if possible. */ if (s->error_queue.n > 0) { slot = s->error_queue.head; LQUEUE_INCR(s->error_queue, s->error_queue.head); s->error_queue.n--; msg = (HidppMsg *) &s->error_queue.reports[slot]; } else if (s->output_queue.n > 0) { slot = s->output_queue.head; LQUEUE_INCR(s->output_queue, s->output_queue.head); s->output_queue.n--; msg = &s->output_queue.reports[slot]; } if (msg != NULL) { len = msg_report_length(msg); assert(len > 0); usb_dump_complete_data(s->usb_dump_state, p, (uint8_t *) msg, len); usb_packet_copy(p, (uint8_t *) msg, len); usb_dump_submit(s->usb_dump_state, p); } else { p->status = USB_RET_NAK; } }