diff options
author | 2023-10-10 11:40:56 +0000 | |
---|---|---|
committer | 2023-10-10 11:40:56 +0000 | |
commit | e02cda008591317b1625707ff8e115a4841aa889 (patch) | |
tree | aee302e3cf8b59ec2d32ec481be3d1afddfc8968 /hw/usb/dev-serial.c | |
parent | cc668e6b7e0ffd8c9d130513d12053cf5eda1d3b (diff) |
Introduce Virtio-loopback epsilon release:
Epsilon release introduces a new compatibility layer which make virtio-loopback
design to work with QEMU and rust-vmm vhost-user backend without require any
changes.
Signed-off-by: Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>
Change-Id: I52e57563e08a7d0bdc002f8e928ee61ba0c53dd9
Diffstat (limited to 'hw/usb/dev-serial.c')
-rw-r--r-- | hw/usb/dev-serial.c | 709 |
1 files changed, 709 insertions, 0 deletions
diff --git a/hw/usb/dev-serial.c b/hw/usb/dev-serial.c new file mode 100644 index 000000000..63047d79c --- /dev/null +++ b/hw/usb/dev-serial.c @@ -0,0 +1,709 @@ +/* + * FTDI FT232BM Device emulation + * + * Copyright (c) 2006 CodeSourcery. + * Copyright (c) 2008 Samuel Thibault <samuel.thibault@ens-lyon.org> + * Written by Paul Brook, reused for FTDI by Samuel Thibault + * + * This code is licensed under the LGPL. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/cutils.h" +#include "qemu/error-report.h" +#include "qemu/module.h" +#include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" +#include "hw/usb.h" +#include "migration/vmstate.h" +#include "desc.h" +#include "chardev/char-serial.h" +#include "chardev/char-fe.h" +#include "qom/object.h" +#include "trace.h" + + +#define RECV_BUF (512 - (2 * 8)) + +/* Commands */ +#define FTDI_RESET 0 +#define FTDI_SET_MDM_CTRL 1 +#define FTDI_SET_FLOW_CTRL 2 +#define FTDI_SET_BAUD 3 +#define FTDI_SET_DATA 4 +#define FTDI_GET_MDM_ST 5 +#define FTDI_SET_EVENT_CHR 6 +#define FTDI_SET_ERROR_CHR 7 +#define FTDI_SET_LATENCY 9 +#define FTDI_GET_LATENCY 10 + +/* RESET */ + +#define FTDI_RESET_SIO 0 +#define FTDI_RESET_RX 1 +#define FTDI_RESET_TX 2 + +/* SET_MDM_CTRL */ + +#define FTDI_DTR 1 +#define FTDI_SET_DTR (FTDI_DTR << 8) +#define FTDI_RTS 2 +#define FTDI_SET_RTS (FTDI_RTS << 8) + +/* SET_FLOW_CTRL */ + +#define FTDI_NO_HS 0 +#define FTDI_RTS_CTS_HS 1 +#define FTDI_DTR_DSR_HS 2 +#define FTDI_XON_XOFF_HS 4 + +/* SET_DATA */ + +#define FTDI_PARITY (0x7 << 8) +#define FTDI_ODD (0x1 << 8) +#define FTDI_EVEN (0x2 << 8) +#define FTDI_MARK (0x3 << 8) +#define FTDI_SPACE (0x4 << 8) + +#define FTDI_STOP (0x3 << 11) +#define FTDI_STOP1 (0x0 << 11) +#define FTDI_STOP15 (0x1 << 11) +#define FTDI_STOP2 (0x2 << 11) + +/* GET_MDM_ST */ +/* TODO: should be sent every 40ms */ +#define FTDI_CTS (1 << 4) /* CTS line status */ +#define FTDI_DSR (1 << 5) /* DSR line status */ +#define FTDI_RI (1 << 6) /* RI line status */ +#define FTDI_RLSD (1 << 7) /* Receive Line Signal Detect */ + +/* Status */ + +#define FTDI_DR (1 << 0) /* Data Ready */ +#define FTDI_OE (1 << 1) /* Overrun Err */ +#define FTDI_PE (1 << 2) /* Parity Err */ +#define FTDI_FE (1 << 3) /* Framing Err */ +#define FTDI_BI (1 << 4) /* Break Interrupt */ +#define FTDI_THRE (1 << 5) /* Transmitter Holding Register */ +#define FTDI_TEMT (1 << 6) /* Transmitter Empty */ +#define FTDI_FIFO (1 << 7) /* Error in FIFO */ + +struct USBSerialState { + USBDevice dev; + + USBEndpoint *intr; + uint8_t recv_buf[RECV_BUF]; + uint16_t recv_ptr; + uint16_t recv_used; + uint8_t event_chr; + uint8_t error_chr; + uint8_t event_trigger; + bool always_plugged; + uint8_t flow_control; + uint8_t xon; + uint8_t xoff; + QEMUSerialSetParams params; + int latency; /* ms */ + CharBackend cs; +}; + +#define TYPE_USB_SERIAL "usb-serial-dev" +OBJECT_DECLARE_SIMPLE_TYPE(USBSerialState, USB_SERIAL) + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT_SERIAL, + STR_PRODUCT_BRAILLE, + STR_SERIALNUMBER, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU", + [STR_PRODUCT_SERIAL] = "QEMU USB SERIAL", + [STR_PRODUCT_BRAILLE] = "QEMU USB BAUM BRAILLE", + [STR_SERIALNUMBER] = "1", +}; + +static const USBDescIface desc_iface0 = { + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0xff, + .bInterfaceSubClass = 0xff, + .bInterfaceProtocol = 0xff, + .eps = (USBDescEndpoint[]) { + { + .bEndpointAddress = USB_DIR_IN | 0x01, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 64, + },{ + .bEndpointAddress = USB_DIR_OUT | 0x02, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = 64, + }, + } +}; + +static const USBDescDevice desc_device = { + .bcdUSB = 0x0200, + .bMaxPacketSize0 = 8, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_WAKEUP, + .bMaxPower = 50, + .nif = 1, + .ifs = &desc_iface0, + }, + }, +}; + +static const USBDesc desc_serial = { + .id = { + .idVendor = 0x0403, + .idProduct = 0x6001, + .bcdDevice = 0x0400, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_SERIAL, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device, + .str = desc_strings, +}; + +static const USBDesc desc_braille = { + .id = { + .idVendor = 0x0403, + .idProduct = 0xfe72, + .bcdDevice = 0x0400, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT_BRAILLE, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device, + .str = desc_strings, +}; + +static void usb_serial_set_flow_control(USBSerialState *s, + uint8_t flow_control) +{ + USBDevice *dev = USB_DEVICE(s); + USBBus *bus = usb_bus_from_device(dev); + + /* TODO: ioctl */ + s->flow_control = flow_control; + trace_usb_serial_set_flow_control(bus->busnr, dev->addr, flow_control); +} + +static void usb_serial_set_xonxoff(USBSerialState *s, int xonxoff) +{ + USBDevice *dev = USB_DEVICE(s); + USBBus *bus = usb_bus_from_device(dev); + + s->xon = xonxoff & 0xff; + s->xoff = (xonxoff >> 8) & 0xff; + + trace_usb_serial_set_xonxoff(bus->busnr, dev->addr, s->xon, s->xoff); +} + +static void usb_serial_reset(USBSerialState *s) +{ + s->event_chr = 0x0d; + s->event_trigger = 0; + s->recv_ptr = 0; + s->recv_used = 0; + /* TODO: purge in char driver */ + usb_serial_set_flow_control(s, FTDI_NO_HS); +} + +static void usb_serial_handle_reset(USBDevice *dev) +{ + USBSerialState *s = USB_SERIAL(dev); + USBBus *bus = usb_bus_from_device(dev); + + trace_usb_serial_reset(bus->busnr, dev->addr); + + usb_serial_reset(s); + /* TODO: Reset char device, send BREAK? */ +} + +static uint8_t usb_get_modem_lines(USBSerialState *s) +{ + int flags; + uint8_t ret; + + if (qemu_chr_fe_ioctl(&s->cs, + CHR_IOCTL_SERIAL_GET_TIOCM, &flags) == -ENOTSUP) { + return FTDI_CTS | FTDI_DSR | FTDI_RLSD; + } + + ret = 0; + if (flags & CHR_TIOCM_CTS) { + ret |= FTDI_CTS; + } + if (flags & CHR_TIOCM_DSR) { + ret |= FTDI_DSR; + } + if (flags & CHR_TIOCM_RI) { + ret |= FTDI_RI; + } + if (flags & CHR_TIOCM_CAR) { + ret |= FTDI_RLSD; + } + + return ret; +} + +static void usb_serial_handle_control(USBDevice *dev, USBPacket *p, + int request, int value, int index, + int length, uint8_t *data) +{ + USBSerialState *s = USB_SERIAL(dev); + USBBus *bus = usb_bus_from_device(dev); + int ret; + + trace_usb_serial_handle_control(bus->busnr, dev->addr, request, value); + + ret = usb_desc_handle_control(dev, p, request, value, index, length, data); + if (ret >= 0) { + return; + } + + switch (request) { + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + break; + + /* Class specific requests. */ + case VendorDeviceOutRequest | FTDI_RESET: + switch (value) { + case FTDI_RESET_SIO: + usb_serial_reset(s); + break; + case FTDI_RESET_RX: + s->recv_ptr = 0; + s->recv_used = 0; + /* TODO: purge from char device */ + break; + case FTDI_RESET_TX: + /* TODO: purge from char device */ + break; + } + break; + case VendorDeviceOutRequest | FTDI_SET_MDM_CTRL: + { + static int flags; + qemu_chr_fe_ioctl(&s->cs, CHR_IOCTL_SERIAL_GET_TIOCM, &flags); + if (value & FTDI_SET_RTS) { + if (value & FTDI_RTS) { + flags |= CHR_TIOCM_RTS; + } else { + flags &= ~CHR_TIOCM_RTS; + } + } + if (value & FTDI_SET_DTR) { + if (value & FTDI_DTR) { + flags |= CHR_TIOCM_DTR; + } else { + flags &= ~CHR_TIOCM_DTR; + } + } + qemu_chr_fe_ioctl(&s->cs, CHR_IOCTL_SERIAL_SET_TIOCM, &flags); + break; + } + case VendorDeviceOutRequest | FTDI_SET_FLOW_CTRL: { + uint8_t flow_control = index >> 8; + + usb_serial_set_flow_control(s, flow_control); + if (flow_control & FTDI_XON_XOFF_HS) { + usb_serial_set_xonxoff(s, value); + } + break; + } + case VendorDeviceOutRequest | FTDI_SET_BAUD: { + static const int subdivisors8[8] = { 0, 4, 2, 1, 3, 5, 6, 7 }; + int subdivisor8 = subdivisors8[((value & 0xc000) >> 14) + | ((index & 1) << 2)]; + int divisor = value & 0x3fff; + + /* chip special cases */ + if (divisor == 1 && subdivisor8 == 0) { + subdivisor8 = 4; + } + if (divisor == 0 && subdivisor8 == 0) { + divisor = 1; + } + + s->params.speed = (48000000 / 2) / (8 * divisor + subdivisor8); + trace_usb_serial_set_baud(bus->busnr, dev->addr, s->params.speed); + qemu_chr_fe_ioctl(&s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params); + break; + } + case VendorDeviceOutRequest | FTDI_SET_DATA: + switch (value & 0xff) { + case 7: + s->params.data_bits = 7; + break; + case 8: + s->params.data_bits = 8; + break; + default: + /* + * According to a comment in Linux's ftdi_sio.c original FTDI + * chips fall back to 8 data bits for unsupported data_bits + */ + trace_usb_serial_unsupported_data_bits(bus->busnr, dev->addr, + value & 0xff); + s->params.data_bits = 8; + } + + switch (value & FTDI_PARITY) { + case 0: + s->params.parity = 'N'; + break; + case FTDI_ODD: + s->params.parity = 'O'; + break; + case FTDI_EVEN: + s->params.parity = 'E'; + break; + default: + trace_usb_serial_unsupported_parity(bus->busnr, dev->addr, + value & FTDI_PARITY); + goto fail; + } + + switch (value & FTDI_STOP) { + case FTDI_STOP1: + s->params.stop_bits = 1; + break; + case FTDI_STOP2: + s->params.stop_bits = 2; + break; + default: + trace_usb_serial_unsupported_stopbits(bus->busnr, dev->addr, + value & FTDI_STOP); + goto fail; + } + + trace_usb_serial_set_data(bus->busnr, dev->addr, s->params.parity, + s->params.data_bits, s->params.stop_bits); + qemu_chr_fe_ioctl(&s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params); + /* TODO: TX ON/OFF */ + break; + case VendorDeviceRequest | FTDI_GET_MDM_ST: + data[0] = usb_get_modem_lines(s) | 1; + data[1] = FTDI_THRE | FTDI_TEMT; + p->actual_length = 2; + break; + case VendorDeviceOutRequest | FTDI_SET_EVENT_CHR: + /* TODO: handle it */ + s->event_chr = value; + break; + case VendorDeviceOutRequest | FTDI_SET_ERROR_CHR: + /* TODO: handle it */ + s->error_chr = value; + break; + case VendorDeviceOutRequest | FTDI_SET_LATENCY: + s->latency = value; + break; + case VendorDeviceRequest | FTDI_GET_LATENCY: + data[0] = s->latency; + p->actual_length = 1; + break; + default: + fail: + trace_usb_serial_unsupported_control(bus->busnr, dev->addr, request, + value); + p->status = USB_RET_STALL; + break; + } +} + +static void usb_serial_token_in(USBSerialState *s, USBPacket *p) +{ + const int max_packet_size = desc_iface0.eps[0].wMaxPacketSize; + int packet_len; + uint8_t header[2]; + + packet_len = p->iov.size; + if (packet_len <= 2) { + p->status = USB_RET_NAK; + return; + } + + header[0] = usb_get_modem_lines(s) | 1; + /* We do not have the uart details */ + /* handle serial break */ + if (s->event_trigger && s->event_trigger & FTDI_BI) { + s->event_trigger &= ~FTDI_BI; + header[1] = FTDI_BI; + usb_packet_copy(p, header, 2); + return; + } else { + header[1] = 0; + } + + if (!s->recv_used) { + p->status = USB_RET_NAK; + return; + } + + while (s->recv_used && packet_len > 2) { + int first_len, len; + + len = MIN(packet_len, max_packet_size); + len -= 2; + if (len > s->recv_used) { + len = s->recv_used; + } + + first_len = RECV_BUF - s->recv_ptr; + if (first_len > len) { + first_len = len; + } + usb_packet_copy(p, header, 2); + usb_packet_copy(p, s->recv_buf + s->recv_ptr, first_len); + if (len > first_len) { + usb_packet_copy(p, s->recv_buf, len - first_len); + } + s->recv_used -= len; + s->recv_ptr = (s->recv_ptr + len) % RECV_BUF; + packet_len -= len + 2; + } + + return; +} + +static void usb_serial_handle_data(USBDevice *dev, USBPacket *p) +{ + USBSerialState *s = USB_SERIAL(dev); + USBBus *bus = usb_bus_from_device(dev); + uint8_t devep = p->ep->nr; + struct iovec *iov; + int i; + + switch (p->pid) { + case USB_TOKEN_OUT: + if (devep != 2) { + goto fail; + } + for (i = 0; i < p->iov.niov; i++) { + iov = p->iov.iov + i; + /* + * XXX this blocks entire thread. Rewrite to use + * qemu_chr_fe_write and background I/O callbacks + */ + qemu_chr_fe_write_all(&s->cs, iov->iov_base, iov->iov_len); + } + p->actual_length = p->iov.size; + break; + + case USB_TOKEN_IN: + if (devep != 1) { + goto fail; + } + usb_serial_token_in(s, p); + break; + + default: + trace_usb_serial_bad_token(bus->busnr, dev->addr); + fail: + p->status = USB_RET_STALL; + break; + } +} + +static int usb_serial_can_read(void *opaque) +{ + USBSerialState *s = opaque; + + if (!s->dev.attached) { + return 0; + } + return RECV_BUF - s->recv_used; +} + +static void usb_serial_read(void *opaque, const uint8_t *buf, int size) +{ + USBSerialState *s = opaque; + int first_size, start; + + /* room in the buffer? */ + if (size > (RECV_BUF - s->recv_used)) { + size = RECV_BUF - s->recv_used; + } + + start = s->recv_ptr + s->recv_used; + if (start < RECV_BUF) { + /* copy data to end of buffer */ + first_size = RECV_BUF - start; + if (first_size > size) { + first_size = size; + } + + memcpy(s->recv_buf + start, buf, first_size); + + /* wrap around to front if needed */ + if (size > first_size) { + memcpy(s->recv_buf, buf + first_size, size - first_size); + } + } else { + start -= RECV_BUF; + memcpy(s->recv_buf + start, buf, size); + } + s->recv_used += size; + + usb_wakeup(s->intr, 0); +} + +static void usb_serial_event(void *opaque, QEMUChrEvent event) +{ + USBSerialState *s = opaque; + + switch (event) { + case CHR_EVENT_BREAK: + s->event_trigger |= FTDI_BI; + break; + case CHR_EVENT_OPENED: + if (!s->always_plugged && !s->dev.attached) { + usb_device_attach(&s->dev, &error_abort); + } + break; + case CHR_EVENT_CLOSED: + if (!s->always_plugged && s->dev.attached) { + usb_device_detach(&s->dev); + } + break; + case CHR_EVENT_MUX_IN: + case CHR_EVENT_MUX_OUT: + /* Ignore */ + break; + } +} + +static void usb_serial_realize(USBDevice *dev, Error **errp) +{ + USBSerialState *s = USB_SERIAL(dev); + Error *local_err = NULL; + + usb_desc_create_serial(dev); + usb_desc_init(dev); + dev->auto_attach = 0; + + if (!qemu_chr_fe_backend_connected(&s->cs)) { + error_setg(errp, "Property chardev is required"); + return; + } + + usb_check_attach(dev, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + + qemu_chr_fe_set_handlers(&s->cs, usb_serial_can_read, usb_serial_read, + usb_serial_event, NULL, s, NULL, true); + usb_serial_handle_reset(dev); + + if ((s->always_plugged || qemu_chr_fe_backend_open(&s->cs)) && + !dev->attached) { + usb_device_attach(dev, &error_abort); + } + s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1); +} + +static USBDevice *usb_braille_init(void) +{ + USBDevice *dev; + Chardev *cdrv; + + cdrv = qemu_chr_new("braille", "braille", NULL); + if (!cdrv) { + return NULL; + } + + dev = usb_new("usb-braille"); + qdev_prop_set_chr(&dev->qdev, "chardev", cdrv); + return dev; +} + +static const VMStateDescription vmstate_usb_serial = { + .name = "usb-serial", + .unmigratable = 1, +}; + +static Property serial_properties[] = { + DEFINE_PROP_CHR("chardev", USBSerialState, cs), + DEFINE_PROP_BOOL("always-plugged", USBSerialState, always_plugged, false), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_serial_dev_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->realize = usb_serial_realize; + uc->handle_reset = usb_serial_handle_reset; + uc->handle_control = usb_serial_handle_control; + uc->handle_data = usb_serial_handle_data; + dc->vmsd = &vmstate_usb_serial; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +} + +static const TypeInfo usb_serial_dev_type_info = { + .name = TYPE_USB_SERIAL, + .parent = TYPE_USB_DEVICE, + .instance_size = sizeof(USBSerialState), + .abstract = true, + .class_init = usb_serial_dev_class_init, +}; + +static void usb_serial_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->product_desc = "QEMU USB Serial"; + uc->usb_desc = &desc_serial; + device_class_set_props(dc, serial_properties); +} + +static const TypeInfo serial_info = { + .name = "usb-serial", + .parent = TYPE_USB_SERIAL, + .class_init = usb_serial_class_initfn, +}; + +static Property braille_properties[] = { + DEFINE_PROP_CHR("chardev", USBSerialState, cs), + DEFINE_PROP_END_OF_LIST(), +}; + +static void usb_braille_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + USBDeviceClass *uc = USB_DEVICE_CLASS(klass); + + uc->product_desc = "QEMU USB Braille"; + uc->usb_desc = &desc_braille; + device_class_set_props(dc, braille_properties); +} + +static const TypeInfo braille_info = { + .name = "usb-braille", + .parent = TYPE_USB_SERIAL, + .class_init = usb_braille_class_initfn, +}; + +static void usb_serial_register_types(void) +{ + type_register_static(&usb_serial_dev_type_info); + type_register_static(&serial_info); + type_register_static(&braille_info); + usb_legacy_register("usb-braille", "braille", usb_braille_init); +} + +type_init(usb_serial_register_types) |