@@ -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
new file mode 100644
@@ -0,0 +1,762 @@
+/*
+ * Vernier Go!Temp USB thermometer emulation
+ * see: http://www.vernier.com/go/gotemp.html
+ *
+ * Copyright (c) 2009 Scott Tsai <scottt.tw@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw.h"
+#include "usb.h"
+#include "sensor.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 */
+
+#define TEMPERATURE_AUTO_INCREMENT_MIN (celsius_to_internal_temperature_unit(25))
+#define TEMPERATURE_AUTO_INCREMENT_MAX (celsius_to_internal_temperature_unit(40))
+
+typedef struct {
+ USBDevice dev;
+ ThermTempEntry *list_entry; /* node in qemu_therm_temp_head list */
+ 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_clock(rt_clock) */
+ Queue response_queue; /* queue of response packets */
+ uint8_t rolling_counter;
+ int16_t temperature; /* unit: 1/128 Celsius */
+ uint8_t temperature_controlled_by_monitor; /* if true, 'temperature' is set by monitor command
+ if false, 'temperature' is incremented on every report */
+ 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_WEEK_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_WEEK_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 = TEMPERATURE_AUTO_INCREMENT_MIN;
+}
+
+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;
+
+ if (!s->temperature_controlled_by_monitor) {
+ s->temperature++;
+ if (s->temperature > TEMPERATURE_AUTO_INCREMENT_MAX)
+ s->temperature = TEMPERATURE_AUTO_INCREMENT_MIN;
+ }
+ 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_therm_temp(void *opaque, double temperature_celsius)
+{
+ GoTempState *s = opaque;
+ DPRINTF("%s(%p, %lf)\n", __func__, opaque, temperature_celsius);
+ s->temperature_controlled_by_monitor = 1;
+ s->temperature = celsius_to_internal_temperature_unit(temperature_celsius);
+}
+
+static void gotemp_handle_destroy(USBDevice *dev)
+{
+ GoTempState *s = DO_UPCAST(GoTempState, dev, dev);
+ qemu_del_therm_temp_handler(s->list_entry);
+}
+
+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);
+ s->list_entry = qemu_add_therm_temp_handler(gotemp_handle_therm_temp, s,
+ "QEMU USB Thermometer");
+ return 0;
+}
+
+static USBDevice *gotemp_init(const char *params)
+{
+ USBDevice *dev;
+ uint8_t temperature_controlled_by_monitor;
+ DPRINTF("%s(\"%s\") called\n", __func__, params);
+
+ if (params) {
+ if (strcmp(params, "controlled_by_monitor") != 0) {
+ qemu_error("bad thermometer option: \"%s\"\n", params);
+ return NULL;
+ }
+ temperature_controlled_by_monitor = 1;
+ } else {
+ temperature_controlled_by_monitor = 0;
+ }
+ dev = usb_create(NULL /* FIXME */, "QEMU USB Thermometer");
+ qdev_prop_set_uint8(&dev->qdev, "temperature_controlled_by_monitor", temperature_controlled_by_monitor);
+ DPRINTF("%s: DO_UPCAST(GoTempState, dev, dev)->temperature_controlled_by_monitor: %d\n",
+ __func__, DO_UPCAST(GoTempState, dev, dev)->temperature_controlled_by_monitor);
+ qdev_init(&dev->qdev);
+ return dev;
+}
+
+static struct USBDeviceInfo gotemp_info = {
+ .qdev.name = "QEMU USB Thermometer",
+ .qdev.alias = "usb-gotemp",
+ .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,
+ .usbdevice_name = "thermometer",
+ .usbdevice_init = gotemp_init,
+ .qdev.props = (Property[]) {
+ DEFINE_PROP_UINT8("temperature_controlled_by_monitor", GoTempState, temperature_controlled_by_monitor, 0),
+ DEFINE_PROP_END_OF_LIST(),
+ },
+};
+
+static void gotemp_register_devices(void)
+{
+ usb_qdev_register(&gotemp_info);
+}
+device_init(gotemp_register_devices)
@@ -42,6 +42,7 @@
#include "disas.h"
#include "balloon.h"
#include "qemu-timer.h"
+#include "sensor.h"
#include "migration.h"
#include "kvm.h"
#include "acl.h"
@@ -1945,6 +1946,96 @@ int monitor_get_fd(Monitor *mon, const char *fdname)
return -1;
}
+static QCIRCLEQ_HEAD(therm_temp_head, ThermTempEntry) qemu_therm_temp_head =
+QCIRCLEQ_HEAD_INITIALIZER(qemu_therm_temp_head);
+static ThermTempEntry *qemu_therm_temp_current;
+
+ThermTempEntry *qemu_add_therm_temp_handler(ThermTempHandler *cb, void *opaque, const char *name)
+{
+ ThermTempEntry *e;
+
+ e = qemu_mallocz(sizeof (*e));
+
+ e->cb = cb;
+ e->opaque = opaque;
+ e->name = strdup(name);
+ if (QCIRCLEQ_EMPTY(&qemu_therm_temp_head))
+ qemu_therm_temp_current = e;
+ QCIRCLEQ_INSERT_TAIL(&qemu_therm_temp_head, e, entries);
+ return e;
+}
+
+void qemu_del_therm_temp_handler(ThermTempEntry *e)
+{
+ if (qemu_therm_temp_current == e)
+ qemu_therm_temp_current = QCIRCLEQ_PREV(e, entries);
+ QCIRCLEQ_REMOVE(&qemu_therm_temp_head, e, entries);
+ qemu_free(e);
+}
+
+static void do_info_thermometers(Monitor *mon)
+{
+ ThermTempEntry *e;
+ int i = 0;
+
+ if (QCIRCLEQ_EMPTY(&qemu_therm_temp_head)) {
+ monitor_printf(mon, "No thermometer connected\n");
+ return;
+ }
+
+ monitor_printf(mon, "Thermometers available:\n");
+ QCIRCLEQ_FOREACH(e, &qemu_therm_temp_head, entries) {
+ monitor_printf(mon, "%c Thermometer #%d: %s\n",
+ (e == qemu_therm_temp_current ? '*' : ' '),
+ i, e->name);
+ i++;
+ }
+}
+
+static void do_therm_set(Monitor *mon, const QDict *qdict, QObject **ret_data)
+{
+ int i = 0;
+ int index = qdict_get_int(qdict, "index");
+ ThermTempEntry *e;
+
+ QCIRCLEQ_FOREACH(e, &qemu_therm_temp_head, entries) {
+ if (i++ == index)
+ break;
+ }
+ if (i == index + 1)
+ qemu_therm_temp_current = e;
+ else
+ monitor_printf(mon, "Thermometer at given index not found\n");
+}
+
+static void do_therm_temp(Monitor *mon, const QDict *qdict, QObject **ret_data)
+{
+ double temp_celsius;
+ const char *t = qdict_get_str(qdict, "temperature_str");
+ int l = strlen(t);
+ if (!l)
+ return;
+
+ switch (t[l-1]) {
+ case 'c':
+ case 'C':
+ temp_celsius = strtod(t, NULL);
+ break;
+ case 'f':
+ case 'F':
+ temp_celsius = (5.0/9.0) * (strtod(t, NULL) - 32.0);
+ break;
+ default:
+ monitor_printf(mon, "temperature value must end with 'c', 'C', 'f' or 'F'\n");
+ return;
+ }
+ if (QCIRCLEQ_EMPTY(&qemu_therm_temp_head)) {
+ monitor_printf(mon, "No thermometer connected\n");
+ return;
+ }
+ qemu_therm_temp_current->cb(qemu_therm_temp_current->opaque, temp_celsius);
+}
+
static const mon_cmd_t mon_cmds[] = {
#include "qemu-monitor.h"
{ NULL, NULL, },
@@ -2209,6 +2300,13 @@ static const mon_cmd_t info_cmds[] = {
.mhandler.info = do_info_roms,
},
{
+ .name = "thermometers",
+ .args_type = "",
+ .params = "",
+ .help = "show thermometers",
+ .mhandler.info = do_info_thermometers,
+ },
+ {
.name = NULL,
},
};
@@ -111,6 +111,8 @@ show migration status
show balloon information
@item info qtree
show device tree
+@item info thermometers
+show available thermometers
@end table
ETEXI
@@ -1040,6 +1042,39 @@ Close the file descriptor previously assigned to @var{fdname} using the
used by another monitor command.
ETEXI
+ {
+ .name = "therm_set",
+ .args_type = "index:i",
+ .params = "index",
+ .help = "set which thermometer device is affected by therm_temp",
+ .mhandler.cmd_new = do_therm_set,
+ },
+
+STEXI
+@item therm_set @var{index}
+Set which thermometer device is affected by @code{therm_temp}, index
+can be obtained with
+@example
+info thermometer
+@end example
+ETEXI
+
+ {
+ .name = "therm_temp",
+ .args_type = "temperature_str:s",
+ .params = "temperature",
+ .help = "set thermometer temperature",
+ .mhandler.cmd_new = do_therm_temp,
+ },
+
+STEXI
+@item therm_temp @var{temperature}
+Set the temperature of the thermometer selected with @code{therm_set}.
+@example
+therm_temp 0F
+therm_temp -17.8c
+@end example
+ETEXI
STEXI
@end table
ETEXI
new file mode 100644
@@ -0,0 +1,20 @@
+#ifndef SENSOR_H
+#define SENSOR_H
+
+#include "qemu-queue.h"
+
+/* thermometers */
+typedef void (ThermTempHandler)(void *opaque, double temperature_celsius);
+
+struct ThermTempEntry {
+ ThermTempHandler *cb;
+ void *opaque;
+ char *name;
+ QCIRCLEQ_ENTRY(ThermTempEntry) entries;
+};
+typedef struct ThermTempEntry ThermTempEntry;
+
+ThermTempEntry *qemu_add_therm_temp_handler(ThermTempHandler *cb, void *opaque, const char *name);
+void qemu_del_therm_temp_handler(ThermTempEntry *e);
+
+#endif
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. If the qdev property 'controlled_by_monitor' is _NOT_ set on the thermometer, such as through the command line option '-usbdevice thermometer:controlled_by_monitor', the temperature would increment on each report but is bound between 25C ~ 40C. Added new monitor commands: info thermometers therm_set INDEX therm_temp TEMPERATURE modeled after 'info mice', 'mouse_set' and 'mouse_move'. Signed-off-by: Scott Tsai <scottt.tw@gmail.com> --- Makefile | 2 +- hw/usb-gotemp.c | 762 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ monitor.c | 98 +++++++ qemu-monitor.hx | 35 +++ sensor.h | 20 ++ 5 files changed, 916 insertions(+), 1 deletions(-) create mode 100644 hw/usb-gotemp.c create mode 100644 sensor.h