|  | /* | 
|  | * 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) |