From patchwork Tue Nov 10 09:37:29 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Scott Tsai X-Patchwork-Id: 38055 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 51683B7B96 for ; Tue, 10 Nov 2009 20:44:39 +1100 (EST) Received: from localhost ([127.0.0.1]:59219 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1N7nH8-0007UY-EW for incoming@patchwork.ozlabs.org; Tue, 10 Nov 2009 04:44:34 -0500 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1N7nDP-00061B-OT for qemu-devel@nongnu.org; Tue, 10 Nov 2009 04:40:43 -0500 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1N7nDL-0005y7-Hi for qemu-devel@nongnu.org; Tue, 10 Nov 2009 04:40:43 -0500 Received: from [199.232.76.173] (port=60872 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1N7nDL-0005xw-9g for qemu-devel@nongnu.org; Tue, 10 Nov 2009 04:40:39 -0500 Received: from mail-yx0-f188.google.com ([209.85.210.188]:46927) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1N7nDK-00077v-8G for qemu-devel@nongnu.org; Tue, 10 Nov 2009 04:40:38 -0500 Received: by mail-yx0-f188.google.com with SMTP id 26so3289048yxe.4 for ; Tue, 10 Nov 2009 01:40:38 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:received:received:from:to:cc:subject:date :message-id:x-mailer:in-reply-to:references; bh=GqHcVbq/RVT/qPFg6EUhCmED66h00I0+k9YTFFL55zU=; b=hWhP00mr6htjEO1wbHPvJ1X3SA/lcVBE1TMputBYu1mYTJY9lkZYM4WUJyhIT2CYXC vHz2IF9he/JodC4T7cmHLzF9osyJlI/lXhT/VC6fGDI/SZhBh8vaZsyE0tZp7VTHCbeA zsuptkTW0T9sYVTBt/noyNB2GMclMaoE66uzY= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references; b=wY3VilUPZHY6l6INeS+UL5pxyrPXZJ7aQIn1kpDBAfm235qteXUT05ft6ZYRMBUnQ6 BUEieE4cKwQrQnHx/K2IffyvXI+AjzYcfHYhQ80qrgGvo4vrcTtVWXhAZryxBGpahptJ mfgeTk+ADE1XSa8r1/rXtlpzmnVPFHgjNL8UE= Received: by 10.90.16.40 with SMTP id 40mr1144840agp.7.1257846037718; Tue, 10 Nov 2009 01:40:37 -0800 (PST) Received: from localhost (220-136-183-174.dynamic.hinet.net [220.136.183.174]) by mx.google.com with ESMTPS id 23sm282076yxe.0.2009.11.10.01.40.35 (version=TLSv1/SSLv3 cipher=RC4-MD5); Tue, 10 Nov 2009 01:40:36 -0800 (PST) From: Scott Tsai To: qemu-devel@nongnu.org Date: Tue, 10 Nov 2009 17:37:29 +0800 Message-Id: <1257845850-4660-3-git-send-email-scottt.tw@gmail.com> X-Mailer: git-send-email 1.6.5.2 In-Reply-To: <1257845850-4660-2-git-send-email-scottt.tw@gmail.com> References: <1257845850-4660-1-git-send-email-scottt.tw@gmail.com> <1257845850-4660-2-git-send-email-scottt.tw@gmail.com> X-detected-operating-system: by monty-python.gnu.org: GNU/Linux 2.6 (newer, 2) Cc: Scott Tsai Subject: [Qemu-devel] [PATCH V2 2/3] usb-gotemp: new module emulating a USB thermometer X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Emulate the Vernier Go!Temp USB thermometer (see: http://www.vernier.com/go/gotemp.html) used in Greg Kroah-Hartman's "Write a Real, Working, Linux Driver" talk. The emulation is complete enough for gregkh's sample driver and using the vendor supplied SDK through the in-kernel 'ldusb' module under Linux. No testing have yet been done with the vendor's fancier Windows software. Signed-off-by: Scott Tsai --- Makefile | 2 +- hw/usb-gotemp.c | 710 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 711 insertions(+), 1 deletions(-) create mode 100644 hw/usb-gotemp.c diff --git a/Makefile b/Makefile index 30f1c9d..54b8968 100644 --- a/Makefile +++ b/Makefile @@ -124,7 +124,7 @@ obj-y += i2c.o smbus.o smbus_eeprom.o obj-y += eeprom93xx.o obj-y += scsi-disk.o cdrom.o obj-y += scsi-generic.o scsi-bus.o -obj-y += usb.o usb-hub.o usb-$(HOST_USB).o usb-hid.o usb-msd.o usb-wacom.o +obj-y += usb.o usb-hub.o usb-$(HOST_USB).o usb-hid.o usb-msd.o usb-wacom.o usb-gotemp.o obj-y += usb-serial.o usb-net.o usb-bus.o obj-$(CONFIG_SSI) += ssi.o obj-$(CONFIG_SSI_SD) += ssi-sd.o diff --git a/hw/usb-gotemp.c b/hw/usb-gotemp.c new file mode 100644 index 0000000..1db6c62 --- /dev/null +++ b/hw/usb-gotemp.c @@ -0,0 +1,710 @@ +/* + * Vernier Go!Temp USB thermometer emulation + * see: http://www.vernier.com/go/gotemp.html + * + * Copyright (c) 2009 Scott Tsai + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "hw.h" +#include "usb.h" + +//#define DEBUG_GOTEMP + +#ifdef DEBUG_GOTEMP +#define DPRINTF(fmt, ...) \ + do { fprintf(stderr, "usb-gotemp: " fmt , ## __VA_ARGS__); } while (0) +static void DHEXDUMP(uint8_t *buf, int len) +{ + int i; + if (!buf || !len) { + fprintf(stderr, "(null)\n"); + return; + } + for (i = 0; i < len - 1; i++) + fprintf(stderr, "0x%02x ", buf[i]); + fprintf(stderr, "0x%02x\n", buf[i]); +} +#else +#define DPRINTF(fmt, ...) do {} while(0) +static void DHEXDUMP(uint8_t *buf, int len) { } +#endif + +/* + * This device has three logical packet streams: + * 1. Commands in HID SET_REPORT requests to endpoint 0 + * 2. Command responses in USB interrupt transfers from endpoint 1 + * 3. Measurements in USB interrupt transfers also from endpoint 1 + * + * All command, response and measurement packets are 8 bytes long. + */ + +#define PACKET_SIZE 8 +#define QUEUE_SIZE 4 /* arbitrary */ + +typedef struct { + uint8_t buf[QUEUE_SIZE][PACKET_SIZE]; + int wp, rp; +} Queue; + +static int queue_empty(Queue *q) +{ + return q->wp == q->rp; +} + +static void queue_put(Queue *q, uint8_t *pkt) +{ + int next = (q->wp + 1) % QUEUE_SIZE; + if (next == q->rp) + return; + q->wp = next; + memcpy(q->buf[next], pkt, PACKET_SIZE); +} + +static void queue_get(Queue *q, uint8_t *pkt) +{ + q->rp = (q->rp + 1) % QUEUE_SIZE; + memcpy(pkt, q->buf[q->rp], PACKET_SIZE); +} + +#define LED_COLOR_RED 0x40 +#define LED_COLOR_GREEN 0x80 +#define LED_COLOR_RED_GREEN 0x00 +#define LED_BRIGHTNESS_MIN 0x00 +#define LED_BRIGHTNESS_MAX 0x10 +#define LED_BRIGHTNESS_DEFAULT 0x04 + +/* Vernier product code names: + * Go!Link is also known as Skip. + * Go!Temp is also known as Jonah and is the device emulated here. + * Go!Motion is also known as Cyclops + */ + +#define MEASUREMENT_TICK_IN_SECONDS 0.000128 +#define MEASUREMENT_PERIOD_DEFAULT_JONAH 0x0f82 /* unit: 0.000128 seconds, about 0.5 seconds */ + +typedef struct { + USBDevice dev; + int status; /* as reported by CMD_ID_GET_STATUS */ + int measuring; /* whether measurement packets should be sent */ + uint32_t measurement_period; /* unit: 0.000128 seconds */ + int64_t last_measure_time; /* unit: milliseconds in qemu_get_lock(rt_clock) */ + Queue response_queue; /* queue of response packets */ + uint8_t rolling_counter; + int16_t temperature; /* unit: 1/128 Celsius */ + uint8_t red_led_brightness; + uint8_t green_led_brightness; +} GoTempState; + +#define MANUFACTURER_STRING "Vernier Software & Technology" +#define MANUFACTURER_STRING_INDEX 1 +#define PRODUCT_STRING "Go! Temp ver 1.53" +#define PRODUCT_STRING_INDEX 2 + +/* MASTER_CPU_VERSION: reported in USB device descriptor and the CMD_ID_GET_STATUS command */ +#define MASTER_CPU_VERSION_MAJOR 0x01 +#define MASTER_CPU_VERSION_MINOR 0x53 + +static const uint8_t gotemp_dev_descriptor[] = { + 0x12, /* u8 bLength; */ + 0x01, /* u8 bDescriptorType; Device */ + 0x10, 0x01, /* u16 bcdUSB; v1.1 */ + + 0x00, /* u8 bDeviceClass; */ + 0x00, /* u8 bDeviceSubClass; */ + 0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x08, /* u8 bMaxPacketSize0; 8 Bytes */ + + 0xf7, 0x08, /* u16 idVendor; */ + 0x02, 0x00, /* u16 idProduct; */ + MASTER_CPU_VERSION_MINOR, MASTER_CPU_VERSION_MAJOR, /* u16 bcdDevice, "ver 1.53", also included in product string */ + + MANUFACTURER_STRING_INDEX, /* u8 iManufacturer; */ + PRODUCT_STRING_INDEX, /* u8 iProduct; */ + 0x00, /* u8 iSerialNumber; */ + 0x01 /* u8 bNumConfigurations; */ +}; + +static const uint8_t gotemp_config_descriptor[] = { + /* one configuration */ + 0x09, /* u8 bLength; */ + 0x02, /* u8 bDescriptorType; Configuration */ + 0x22, 0x00, /* u16 wTotalLength; */ + 0x01, /* u8 bNumInterfaces; (1) */ + 0x01, /* u8 bConfigurationValue; */ + 0x00, /* u8 iConfiguration; */ + 0x80, /* u8 bmAttributes; + Bit 7: must be set, +6: Self-powered, +5: Remote wakeup, +4..0: resvd */ + 100/2, /* u8 MaxPower; 100mA */ + + /* one interface */ + 0x09, /* u8 if_bLength; */ + 0x04, /* u8 if_bDescriptorType; Interface */ + 0x00, /* u8 if_bInterfaceNumber; */ + 0x00, /* u8 if_bAlternateSetting; */ + 0x01, /* u8 if_bNumEndpoints; */ + 0x03, /* u8 if_bInterfaceClass; HID */ + 0x00, /* u8 if_bInterfaceSubClass; */ + 0x00, /* u8 if_bInterfaceProtocol; */ + 0x00, /* u8 if_iInterface; */ + + /* HID descriptor */ + 0x09, /* u8 bLength; */ + 0x21, /* u8 bDescriptorType; HID */ + 0x10, 0x01, /* u16 bcdHID; HCD specification release number */ + 0x00, /* u8 bCountryCode; */ + 0x01, /* u8 bNumDescriptors; */ + 0x22, /* u8 bDescriptorType; report descriptor */ + 0x32, 0x00, /* u16 wDescriptorLength; length of report descriptor above */ + + /* one endpoint (status change endpoint) */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x81, /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* u8 ep_bmAttributes; Interrupt */ + 0x08, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x0a /* u8 ep_bInterval; 10 milliseconds (low-speed) */ +}; + +static const uint8_t gotemp_hid_report_descriptor[] = { + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined) */ + 0x09, 0x01, /* Usage (Vendor Defined) */ + 0xa1, 0x01, /* Collection (Application) */ + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x46, /* Usage (Vector) */ + 0x15, 0x80, /* Logical Minimum */ + 0x25, 0x7f, /* Logical Maximum */ + 0x95, 0x08, /* Report Count */ + 0x75, 0x08, /* Report Size */ + 0x81, 0x06, /* Input (Data, Variable, Relative) */ + 0x05, 0x01, /* Usage Page (Generic Desktop) */ + 0x09, 0x46, /* Usage (Vector) */ + 0x15, 0x80, /* Logical Minimum */ + 0x25, 0x7f, /* Logical Maximum */ + 0x95, 0x08, /* Report Count */ + 0x75, 0x08, /* Report Size */ + 0xb1, 0x06, /* Feature (Data, Variable, Relative) */ + 0x05, 0x08, /* Usage Page (LEDs) */ + 0x09, 0x2d, /* Usage (Ready) */ + 0x15, 0x80, /* Logical Minimum */ + 0x25, 0x7f, /* Logical Maximum */ + 0x95, 0x08, /* Report Count */ + 0x75, 0x08, /* Report Size */ + 0x91, 0x06, /* Output (Data, Variable, Relative) */ + 0xc0 /* End Collection */ +}; + +/* gotemp_nv_mem: writable on real hardware */ +static const uint8_t gotemp_nv_mem[] = { + 0x01, 0x3c, 0x21, 0x00, 0x00, 0x07, + 0x51, 0x05, 0x54, 0x65, 0x6d, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x54, 0x65, 0x6d, 0x70, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x34, 0x0f, 0x01, 0x00, 0x00, 0x00, 0x3f, + 0x00, 0x00, 0x80, 0x3f, 0xb4, 0x00, 0x00, + 0x00, 0x01, 0x0e, 0x01, 0x00, 0x00, 0xc8, + 0xc1, 0x00, 0x00, 0xfa, 0x42, 0x04, 0x02, + 0x00, 0x20, 0xd0, 0x7f, 0xc3, 0xcd, 0xcc, + 0xcc, 0x42, 0x00, 0x00, 0x00, 0x00, 0x28, + 0x43, 0x29, 0x00, 0x00, 0x00, 0x00, 0x50, + 0x3b, 0xd6, 0xc3, 0xec, 0x51, 0x38, 0x43, + 0x00, 0x00, 0x00, 0x00, 0x28, 0x46, 0x29, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x89, + 0x41, 0xcd, 0xcc, 0xcc, 0x42, 0x00, 0x00, + 0x00, 0x00, 0x28, 0x4b, 0x29, 0x00, 0x00, + 0x00, 0x00, 0x94, +}; + +#define MANUFACTURE_DATE_WEEEK_IN_YEAR_IN_BCD 0xff; +#define MANUFACTURE_DATE_YEAR_LAST_TWO_DIGITS_IN_BCD 0x00; +#define SERIAL_NUMBER 0x0dca1000 + +#define CMD_ID_GET_STATUS 0x10 +#define CMD_ID_READ_LOCAL_NV_MEM 0x17 +#define CMD_ID_START_MEASUREMENTS 0x18 +#define CMD_ID_STOP_MEASUREMENTS 0x19 +#define CMD_ID_INIT 0x1a +#define CMD_ID_SET_MEASUREMENT_PERIOD 0x1b +#define CMD_ID_GET_MEASUREMENT_PERIOD 0x1c +#define CMD_ID_SET_LED_STATE 0x1d +#define CMD_ID_GET_LED_STATE 0x1e +#define CMD_ID_GET_SERIAL_NUMBER 0x20 +#define CMD_ID_READ_REMOTE_NV_MEM 0x27 + +#define RESPONSE_HEADER_NV_MEM_READ 0x49 +#define RESPONSE_HEADER_CMD_SUCCESS 0x5a +#define RESPONSE_HEADER_GET_LED_STATUS 0x5b +#define RESPONSE_HEADER_GET_STATUS_JONAH 0x5c +#define RESPONSE_HEADER_GET_MREASUREMENT_PERIOD 0x5d +#define RESPONSE_HEADER_GET_SERIAL_NUMBER 0x5f +#define RESPONSE_HEADER_CMD_ERROR (RESPONSE_HEADER_CMD_SUCCESS | 0x20) +#define RESPONSE_HEADER_INIT_SUCCESS 0x9a + +#define RESPONSE_STATUS_SUCCESS 0x00 +#define RESPONSE_STATUS_NOT_READY_FOR_NEW_CMD 0x30 +#define RESPONSE_STATUS_CMD_NOT_SUPPORTED 0x31 +#define RESPONSE_STATUS_INTERNAL_ERROR1 0x32 +#define RESPONSE_STATUS_INTERNAL_ERROR2 0x33 +#define RESPONSE_STATUS_ERROR_CANNOT_CHANGE_PERIOD_WHILE_COLLECTING 0x34 +#define RESPONSE_STATUS_ERROR_CANNOT_READ_NV_MEM_BLK_WHILE_COLLECTING_FAST 0x35 +#define RESPONSE_STATUS_ERROR_INVALID_PARAMETER 0x36 +#define RESPONSE_STATUS_ERROR_CANNOT_WRITE_FLASH_WHILE_COLLECTING 0x37 +#define RESPONSE_STATUS_ERROR_CANNOT_WRITE_FLASH_WHILE_HOST_FIFO_BUSY 0x38 +#define RESPONSE_STATUS_ERROR_OP_BLOCKED_WHILE_COLLECTING 0x39 +#define RESPONSE_STATUS_ERROR_CALCULATOR_CANNOT_MEASURE_WITH_NO_BATTERIES 0x3A +#define RESPONSE_STATUS_ERROR_SLAVE_POWERUP_INIT 0x40 +#define RESPONSE_STATUS_ERROR_SLAVE_POWERRESTORE_INIT 0x41 + +static void gotemp_get_status(GoTempState *s, uint8_t *pkt) +{ + pkt[0] = RESPONSE_HEADER_GET_STATUS_JONAH; + pkt[1] = CMD_ID_GET_STATUS; + pkt[2] = s->status; + pkt[3] = MASTER_CPU_VERSION_MINOR; + pkt[4] = MASTER_CPU_VERSION_MAJOR; +} + +static int16_t celsius_to_internal_temperature_unit(int v) +{ + return v * 128; +} + +static void gotemp_fill_success_response(GoTempState *s, uint8_t cmd, uint8_t *pkt) +{ + /* Response format for most commands: + * pkt[0]: header + * pkt[1]: cmd + * pkt[2]: status + */ + + memset(pkt, 0, PACKET_SIZE); + if (cmd == CMD_ID_INIT) + pkt[0] = RESPONSE_HEADER_INIT_SUCCESS; + else + pkt[0] = RESPONSE_HEADER_CMD_SUCCESS; + pkt[1] = cmd; + pkt[2] = RESPONSE_STATUS_SUCCESS; +} + +static void gotemp_queue_response(GoTempState *s, uint8_t *pkt) +{ + queue_put(&s->response_queue, pkt); +} + +static int gotemp_respond(GoTempState *s, uint8_t *buf, int len) +{ + /* All Go!Temp response packets are 8 bytes */ + uint8_t pkt[PACKET_SIZE]; + int l; + queue_get(&s->response_queue, pkt); + + l = len < PACKET_SIZE ? len : PACKET_SIZE; + if (pkt[0] == RESPONSE_HEADER_NV_MEM_READ) { + uint8_t cmd = pkt[1], addr = pkt[2], len = pkt[3], offset = pkt[4]; + int t = len - offset; + if (offset == 0) { + if (t > 6) { + t = 6; + buf[0] = 0x49 + 0x06; + } else { + buf[0] = 0x49 + t + 0x10; /* first packet is also the last packet in NVRAM read */ + } + buf[1] = cmd; + memcpy(buf + 2, gotemp_nv_mem + addr + offset, t); + } else { + if (t > 7) { + t = 7; + buf[0] = 0x40 + 0x07; + } else { + buf[0] = 0x40 + t + 0x10; /* last packet in NVRAM read */ + } + memcpy(buf + 1, gotemp_nv_mem + addr + offset, t); + } + if (!(buf[0] & 0x10)) { /* not last packet, queue next transfer */ + pkt[4] += t; + gotemp_queue_response(s, pkt); + } + } else { + memcpy(buf, pkt, l); + } + return l; +} + +static void gotemp_read_nv_mem(GoTempState *s, uint8_t gotemp_cmd, uint8_t addr, uint8_t len, uint8_t *pkt) +{ + /* Need to send 'len' bytes in 6 (first packet) or 7 byte chunks. + * The responses to CMD_ID_*_NVRAM_READ are special cased in gotemp_respond and we're just filling + * an internal book keeping record here, not the real packet that gets sent over the wire. + * */ + pkt[0] = RESPONSE_HEADER_NV_MEM_READ; + pkt[1] = gotemp_cmd; + pkt[2] = addr; /* requestes address */ + pkt[3] = len; /* requested length */ + pkt[4] = 0x00; /* current transfer offset from address */ +} + +static void gotemp_set_led(GoTempState *s, uint8_t color, uint8_t brightness) +{ + if (brightness > LED_BRIGHTNESS_MAX) + brightness = LED_BRIGHTNESS_MAX; + + switch (color) { + case LED_COLOR_RED: + s->red_led_brightness = brightness; + break; + case LED_COLOR_GREEN: + s->green_led_brightness = brightness; + break; + case LED_COLOR_RED_GREEN: + s->red_led_brightness = brightness; + s->green_led_brightness = brightness; + break; + } +} + +static void gotemp_get_led(GoTempState *s, uint8_t *pkt) +{ + pkt[0] = RESPONSE_HEADER_GET_LED_STATUS; + pkt[1] = CMD_ID_GET_LED_STATE; + if (s->red_led_brightness && s->green_led_brightness) { + pkt[2] = LED_COLOR_RED_GREEN; + pkt[3] = s->red_led_brightness; + } else if (s->red_led_brightness) { + pkt[2] = LED_COLOR_RED; + pkt[3] = s->red_led_brightness; + } else if (s->green_led_brightness) { + pkt[2] = LED_COLOR_GREEN; + pkt[3] = s->green_led_brightness; + } else { + pkt[2] = 0x00; + pkt[3] = 0x00; + } +} + +static uint32_t le32_unpack(uint8_t *buf) +{ + return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; +} + +static void le32_pack(uint8_t *buf, uint32_t v) +{ + buf[0] = v & 0xff; + buf[1] = (v >> 8) & 0xff; + buf[2] = (v >> 16) & 0xff; + buf[3] = (v >> 24) & 0xff; +} + +static void gotemp_get_serial(GoTempState *s, uint8_t *pkt) +{ + pkt[0] = RESPONSE_HEADER_GET_SERIAL_NUMBER; + pkt[1] = CMD_ID_GET_SERIAL_NUMBER; + pkt[2] = MANUFACTURE_DATE_WEEEK_IN_YEAR_IN_BCD; + pkt[3] = MANUFACTURE_DATE_YEAR_LAST_TWO_DIGITS_IN_BCD; + le32_pack(pkt + 4, SERIAL_NUMBER); +} + +static int64_t measurement_ticks_to_ms(uint32_t ticks) +{ + return ticks * MEASUREMENT_TICK_IN_SECONDS * 1000; +} + +static void gotemp_get_measurement_period(GoTempState *s, uint8_t *pkt) +{ + pkt[0] = RESPONSE_HEADER_GET_MREASUREMENT_PERIOD; + pkt[1] = CMD_ID_GET_MEASUREMENT_PERIOD; + le32_pack(pkt + 2, s->measurement_period); +} + +static void gotemp_reset(GoTempState *s) +{ + s->measuring = 1; /* device is measuring upon reset, CMD_ID_INIT stops the measuring */ + s->measurement_period = MEASUREMENT_PERIOD_DEFAULT_JONAH; + s->last_measure_time = qemu_get_clock(rt_clock); + gotemp_set_led(s, LED_COLOR_RED_GREEN, LED_BRIGHTNESS_DEFAULT); + s->response_queue.wp = s-> response_queue.rp = 0; + s->rolling_counter = 0; + s->status = RESPONSE_STATUS_SUCCESS; + s->temperature = celsius_to_internal_temperature_unit(25); +} + +static void gotemp_handle_reset(USBDevice *dev) +{ + GoTempState *s = (GoTempState*)dev; + DPRINTF("%s\n", __func__); + gotemp_reset(s); +} + +static int gotemp_handle_hid_set_report(GoTempState *s, uint8_t *data) +{ + uint8_t pkt[PACKET_SIZE]; + uint8_t gotemp_cmd = data[0]; + switch (gotemp_cmd) { + case CMD_ID_GET_STATUS: + gotemp_get_status(s, pkt); + break; + case CMD_ID_INIT: + s->measuring = 0; + s->measurement_period = MEASUREMENT_PERIOD_DEFAULT_JONAH; + gotemp_set_led(s, LED_COLOR_RED_GREEN, LED_BRIGHTNESS_DEFAULT); + s->response_queue.wp = s->response_queue.rp = 0; + s->status = RESPONSE_STATUS_SUCCESS; + gotemp_fill_success_response(s, gotemp_cmd, pkt); + break; + case CMD_ID_START_MEASUREMENTS: + s->measuring = 1; + s->last_measure_time = qemu_get_clock(rt_clock); + gotemp_fill_success_response(s, gotemp_cmd, pkt); + break; + case CMD_ID_STOP_MEASUREMENTS: + s->measuring = 0; + gotemp_fill_success_response(s, gotemp_cmd, pkt); + break; + case CMD_ID_READ_LOCAL_NV_MEM: + case CMD_ID_READ_REMOTE_NV_MEM: + gotemp_read_nv_mem(s, gotemp_cmd, data[1], data[2], pkt); + break; + case CMD_ID_SET_MEASUREMENT_PERIOD: + s->measurement_period = le32_unpack(data + 1); + gotemp_fill_success_response(s, gotemp_cmd, pkt); + break; + case CMD_ID_GET_MEASUREMENT_PERIOD: + gotemp_get_measurement_period(s, pkt); + break; + case CMD_ID_SET_LED_STATE: + gotemp_set_led(s, data[1], data[2]); + gotemp_fill_success_response(s, gotemp_cmd, pkt); + break; + case CMD_ID_GET_LED_STATE: + gotemp_get_led(s, pkt); + break; + case CMD_ID_GET_SERIAL_NUMBER: + gotemp_get_serial(s, pkt); + break; + default: + DPRINTF("%s: unsupported gotemp command: 0x%02x\n", __func__, gotemp_cmd); + pkt[0] = RESPONSE_HEADER_CMD_ERROR; + pkt[1] = gotemp_cmd; + pkt[2] = RESPONSE_STATUS_CMD_NOT_SUPPORTED; + break; + } + gotemp_queue_response(s, pkt); + return 0; +} + +static int gotemp_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + GoTempState *s = (GoTempState*)dev; + int ret = 0; + + DPRINTF("%s(request: 0x%04x, value: 0x%04x, index: 0x%04x)\n", __func__, request, value, index); + DPRINTF("\tdata: "); + DHEXDUMP(data, length > 32 ? 32 : length); + switch (request) { + case DeviceRequest | USB_REQ_GET_STATUS: + data[0] = (0 << USB_DEVICE_SELF_POWERED) | + (dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP); + data[1] = 0x00; + ret = 2; + break; + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 0; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_FEATURE: + if (value == USB_DEVICE_REMOTE_WAKEUP) { + dev->remote_wakeup = 1; + } else { + goto fail; + } + ret = 0; + break; + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + dev->addr = value; + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + switch(value >> 8) { + case USB_DT_DEVICE: + memcpy(data, gotemp_dev_descriptor, + sizeof(gotemp_dev_descriptor)); + ret = sizeof(gotemp_dev_descriptor); + break; + case USB_DT_CONFIG: + memcpy(data, gotemp_config_descriptor, + sizeof(gotemp_config_descriptor)); + ret = sizeof(gotemp_config_descriptor); + break; + case USB_DT_STRING: + switch(value & 0xff) { + case 0: + /* language ids */ + data[0] = 4; + data[1] = 3; + data[2] = 0x09; /* little endian 0x0409: en_US */ + data[3] = 0x04; + ret = 4; + break; + case MANUFACTURER_STRING_INDEX: + ret = set_usb_string(data, MANUFACTURER_STRING); + break; + case PRODUCT_STRING_INDEX: + ret = set_usb_string(data, PRODUCT_STRING); + break; + default: + goto fail; + } + break; + default: + goto fail; + } + break; + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + data[0] = 1; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_INTERFACE: + data[0] = 0; + ret = 1; + break; + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + /* HID specific requests */ + case InterfaceRequest | USB_REQ_GET_DESCRIPTOR: + if (value >> 8 != 0x22) + goto fail; + memcpy(data, gotemp_hid_report_descriptor, sizeof(gotemp_hid_report_descriptor)); + ret = sizeof(gotemp_hid_report_descriptor); + break; + case InterfaceRequest | USB_REQ_SET_CONFIGURATION: + break; + case USB_REQ_HID_GET_REPORT: + /* FIXME: mandatory for HID devices, verify behavior on real hardware */ + break; + case USB_REQ_HID_SET_REPORT: + if (length < PACKET_SIZE) + goto fail; + ret = gotemp_handle_hid_set_report(s, data); + break; + default: +fail: + DPRINTF("%s: unsupported request: 0x%04x, value: 0x%04x, index: 0x%04x\n", __func__, request, value, index); + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int gotemp_poll(GoTempState *s, uint8_t *buf, int len) +{ + int l; + int64_t now; + if (!s->measuring) + return USB_RET_NAK; + + now = qemu_get_clock(rt_clock); + if ((now - s->last_measure_time) < measurement_ticks_to_ms(s->measurement_period)) + return USB_RET_NAK; + + s->last_measure_time = now; + l = 0; + if (len > l) + buf[l++] = 1; /* measurements in packet */ + if (len > l) + buf[l++] = s->rolling_counter++; + if (len > l) + buf[l++] = s->temperature & 0xff; + if (len > l) + buf[l++] = s->temperature >> 8; + if (len > l) + buf[l++] = 0x00; + if (len > l) + buf[l++] = 0x00; + if (len > l) + buf[l++] = 0x00; + if (len > l) + buf[l++] = 0x00; + s->temperature++; + return l; +} + +static int gotemp_handle_data(USBDevice *dev, USBPacket *p) +{ + GoTempState *s = (GoTempState *)dev; + int ret = 0; + + // DPRINTF("%s: p: {pid: 0x%02x, devep: %d}\n", __func__, p->pid, p->devep); + switch(p->pid) { + case USB_TOKEN_IN: + if (p->devep != 1) + goto fail; + if (!queue_empty(&s->response_queue)) + ret = gotemp_respond(s, p->data, p->len); + else + ret = gotemp_poll(s, p->data, p->len); + break; + case USB_TOKEN_OUT: + default: +fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static void gotemp_handle_destroy(USBDevice *dev) +{ +} + +static int gotemp_initfn(USBDevice *dev) +{ + DPRINTF("%s called\n", __func__); + GoTempState *s = DO_UPCAST(GoTempState, dev, dev); + s->dev.speed = USB_SPEED_LOW; + gotemp_reset(s); + return 0; +} + +static struct USBDeviceInfo gotemp_info = { + .qdev.name = "QEMU USB Thermometer", + .qdev.alias = "usb-gotemp", + .usbdevice_name = "thermometer", + .qdev.size = sizeof(GoTempState), + .init = gotemp_initfn, + .handle_packet = usb_generic_handle_packet, + .handle_reset = gotemp_handle_reset, + .handle_control = gotemp_handle_control, + .handle_data = gotemp_handle_data, + .handle_destroy = gotemp_handle_destroy, +}; + +static void gotemp_register_devices(void) +{ + usb_qdev_register(&gotemp_info); +} +device_init(gotemp_register_devices)