/* * I2C tiny usb device emulation * * Copyright (c) 2015 Tim Sander * * Loosly based on usb dev-serial.c: * Copyright (c) 2006 CodeSourcery. * Copyright (c) 2008 Samuel Thibault * Written by Paul Brook, reused for FTDI by Samuel Thibault * * This code is licensed under the LGPL. * */ #include "qemu-common.h" #include "qemu/error-report.h" #include "hw/usb.h" #include "hw/usb/desc.h" #include "sysemu/char.h" #include "endian.h" #define DEBUG_USBI2C #ifdef DEBUG_USBI2C #define DPRINTF(fmt, ...) \ do { printf("usb-i2c-tiny: " fmt , ## __VA_ARGS__); } while (0) #else #define DPRINTF(fmt, ...) do {} while (0) #endif /* commands from USB, must e.g. match command ids in kernel driver */ #define CMD_ECHO 0 #define CMD_GET_FUNC 1 #define CMD_SET_DELAY 2 #define CMD_GET_STATUS 3 /* To determine what functionality is present */ #define I2C_FUNC_I2C 0x00000001 #define I2C_FUNC_10BIT_ADDR 0x00000002 #define I2C_FUNC_PROTOCOL_MANGLING 0x00000004 #define I2C_FUNC_SMBUS_HWPEC_CALC 0x00000008 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_READ_WORD_DATA_PEC 0x00000800 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_WRITE_WORD_DATA_PEC 0x00001000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_PROC_CALL_PEC 0x00002000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_BLOCK_PROC_CALL_PEC 0x00004000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_BLOCK_PROC_CALL 0x00008000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_QUICK 0x00010000 #define I2C_FUNC_SMBUS_READ_BYTE 0x00020000 #define I2C_FUNC_SMBUS_WRITE_BYTE 0x00040000 #define I2C_FUNC_SMBUS_READ_BYTE_DATA 0x00080000 #define I2C_FUNC_SMBUS_WRITE_BYTE_DATA 0x00100000 #define I2C_FUNC_SMBUS_READ_WORD_DATA 0x00200000 #define I2C_FUNC_SMBUS_WRITE_WORD_DATA 0x00400000 #define I2C_FUNC_SMBUS_PROC_CALL 0x00800000 #define I2C_FUNC_SMBUS_READ_BLOCK_DATA 0x01000000 #define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000 #define I2C_FUNC_SMBUS_READ_I2C_BLOCK 0x04000000 /*I2C-like blk-xfr */ #define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 0x08000000 /*1-byte reg. addr.*/ #define I2C_FUNC_SMBUS_READ_I2C_BLOCK_2 0x10000000 /*I2C-like blk-xfer*/ #define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK_2 0x20000000 /* w/ 2-byte regadr*/ #define I2C_FUNC_SMBUS_READ_BLOCK_DATA_PEC 0x40000000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA_PEC 0x80000000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_BYTE (I2C_FUNC_SMBUS_READ_BYTE | \ I2C_FUNC_SMBUS_WRITE_BYTE) #define I2C_FUNC_SMBUS_BYTE_DATA (I2C_FUNC_SMBUS_READ_BYTE_DATA | \ I2C_FUNC_SMBUS_WRITE_BYTE_DATA) #define I2C_FUNC_SMBUS_WORD_DATA (I2C_FUNC_SMBUS_READ_WORD_DATA | \ I2C_FUNC_SMBUS_WRITE_WORD_DATA) #define I2C_FUNC_SMBUS_BLOCK_DATA (I2C_FUNC_SMBUS_READ_BLOCK_DATA | \ I2C_FUNC_SMBUS_WRITE_BLOCK_DATA) #define I2C_FUNC_SMBUS_I2C_BLOCK (I2C_FUNC_SMBUS_READ_I2C_BLOCK | \ I2C_FUNC_SMBUS_WRITE_I2C_BLOCK) #define I2C_FUNC_SMBUS_EMUL (I2C_FUNC_SMBUS_QUICK | \ I2C_FUNC_SMBUS_BYTE | \ I2C_FUNC_SMBUS_BYTE_DATA | \ I2C_FUNC_SMBUS_WORD_DATA | \ I2C_FUNC_SMBUS_PROC_CALL | \ I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | \ I2C_FUNC_SMBUS_WRITE_BLOCK_DATA_PEC | \ I2C_FUNC_SMBUS_I2C_BLOCK) #define DeviceOutVendor ((USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_DEVICE)<<8) #define DeviceInVendor ((USB_DIR_IN|USB_TYPE_VENDOR|USB_RECIP_DEVICE)<<8) typedef struct { USBDevice dev; } UsbI2cTinyState; #define TYPE_USB_I2C_TINY "usb-i2c-dev" #define USB_I2C_TINY_DEV(obj) OBJECT_CHECK(UsbI2cTinyState, \ (obj), TYPE_USB_I2C_TINY) enum { STR_MANUFACTURER = 1, STR_PRODUCT_SERIAL, STR_SERIALNUMBER, }; static const USBDescStrings desc_strings = { [STR_MANUFACTURER] = "QEMU", [STR_PRODUCT_SERIAL] = "QEMU USB I2C Tiny", [STR_SERIALNUMBER] = "1", }; static const USBDescIface desc_iface0 = { .bInterfaceNumber = 1, .bNumEndpoints = 0, .bInterfaceClass = 0xff, .bInterfaceSubClass = 0xff, .bInterfaceProtocol = 0xff, .eps = (USBDescEndpoint[]) { } }; static const USBDescDevice desc_device = { .bcdUSB = 0x0110, .bMaxPacketSize0 = 8, .bNumConfigurations = 1, .confs = (USBDescConfig[]) { { .bNumInterfaces = 1, .bConfigurationValue = 1, .bmAttributes = USB_CFG_ATT_ONE, .bMaxPower = 50, .nif = 1, .ifs = &desc_iface0, }, }, }; static const USBDesc desc_usb_i2c = { .id = { .idVendor = 0x0403, .idProduct = 0xc631, .bcdDevice = 0x0205, .iManufacturer = STR_MANUFACTURER, .iProduct = STR_PRODUCT_SERIAL, .iSerialNumber = STR_SERIALNUMBER, }, .full = &desc_device, .str = desc_strings, }; static void usb_i2c_handle_reset(USBDevice *dev) { DPRINTF("reset\n"); } static void usb_i2c_handle_control(USBDevice *dev, USBPacket *p, int request, int value, int index, int length, uint8_t *data) { int ret; DPRINTF("got control %x, value %x\n", request, value); DPRINTF("iov_base:%lx pid:%x stream:%x param:%lx status:%x len:%x\n", (uint64_t)(p->iov).iov->iov_base, p->pid, p->stream, p->parameter, p->status, p->actual_length); ret = usb_desc_handle_control(dev, p, request, value, index, length, data); DPRINTF("usb_desc_handle_control return value: %i status: %x\n", ret, p->status); if (ret >= 0) { return; } switch (request) { case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: break; case 0x4102: /* set clock, ignore */ break; case 0x4105: DPRINTF("unknown access %x\n", data[0]); break; case 0x4107: DPRINTF("write %x\n", data[0]); break; case 0xc101: { /* thats what the real thing reports, FIXME: can we do better here? */ uint32_t func = htole32(I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL); DPRINTF("got functionality read %x, value %x\n", request, value); memcpy(data, &func, sizeof(func)); p->actual_length = sizeof(func); } break; case 0xc103: DPRINTF("unknown call:\n"); DPRINTF("%x %x %x %x %x %x %x %x %x %x %x\n", value, request, length, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); data[0] = 0x09; p->actual_length = 1; break; case 0xc107: DPRINTF("read access "); DPRINTF("%x %x %x %x %x %x %x %x %x %x %x\n", value, request, length, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); data[0] = 0x08; p->actual_length = 1; break; default: DPRINTF("got unsupported/bogus control %x, value %x\n", request, value); p->status = USB_RET_STALL; break; } } static void usb_i2c_handle_data(USBDevice *dev, USBPacket *p) { DPRINTF("unexpected call to usb_i2c_handle_data\n"); } static void usb_i2c_realize(USBDevice *dev, Error **errp) { Error *local_err = NULL; usb_desc_create_serial(dev); usb_desc_init(dev); usb_check_attach(dev, &local_err); if (local_err) { error_propagate(errp, local_err); return; } usb_i2c_handle_reset(dev); } static USBDevice *usb_i2c_init(USBBus *bus, const char *filename) { USBDevice *dev; CharDriverState *cdrv; uint32_t vendorid = 0, productid = 0; char label[32]; static int index; while (*filename && *filename != ':') { const char *p; char *e; if (strstart(filename, "vendorid=", &p)) { vendorid = strtol(p, &e, 16); if (e == p || (*e && *e != ',' && *e != ':')) { error_report("bogus vendor ID %s", p); return NULL; } filename = e; } else if (strstart(filename, "productid=", &p)) { productid = strtol(p, &e, 16); if (e == p || (*e && *e != ',' && *e != ':')) { error_report("bogus product ID %s", p); return NULL; } filename = e; } else { error_report("unrecognized usbi2c USB option %s", filename); return NULL; } while (*filename == ',') { filename++; } } if (!*filename) { error_report("character device specification needed"); return NULL; } filename++; snprintf(label, sizeof(label), "usbusbi2c%d", index++); cdrv = qemu_chr_new(label, filename, NULL); if (!cdrv) { return NULL; } dev = usb_create(bus, "usb-usbi2c"); qdev_prop_set_chr(&dev->qdev, "chardev", cdrv); if (vendorid) { qdev_prop_set_uint16(&dev->qdev, "vendorid", vendorid); } if (productid) { qdev_prop_set_uint16(&dev->qdev, "productid", productid); } return dev; } static const VMStateDescription vmstate_usb_i2c = { .name = "usb-i2c-tiny", .unmigratable = 1, }; static Property usbi2c_properties[] = { DEFINE_PROP_END_OF_LIST(), }; static void usb_i2c_dev_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); USBDeviceClass *uc = USB_DEVICE_CLASS(klass); uc->realize = usb_i2c_realize; uc->handle_reset = usb_i2c_handle_reset; uc->handle_control = usb_i2c_handle_control; uc->handle_data = usb_i2c_handle_data; dc->vmsd = &vmstate_usb_i2c; set_bit(DEVICE_CATEGORY_INPUT, dc->categories); } static const TypeInfo usb_i2c_dev_type_info = { .name = TYPE_USB_I2C_TINY, .parent = TYPE_USB_DEVICE, .instance_size = sizeof(UsbI2cTinyState), .abstract = true, .class_init = usb_i2c_dev_class_init, }; static void usb_i2c_class_initfn(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); USBDeviceClass *uc = USB_DEVICE_CLASS(klass); uc->product_desc = "QEMU USB I2C Tiny"; uc->usb_desc = &desc_usb_i2c; dc->props = usbi2c_properties; } static const TypeInfo usbi2c_info = { .name = "usb-i2c-tiny", .parent = TYPE_USB_I2C_TINY, .class_init = usb_i2c_class_initfn, }; static void usb_i2c_register_types(void) { type_register_static(&usb_i2c_dev_type_info); type_register_static(&usbi2c_info); usb_legacy_register("usb-i2c-tiny", "i2c-bus-tiny", usb_i2c_init); } type_init(usb_i2c_register_types)