qemu/hw/usb/dev-serial.c
Mark Cave-Ayland 963a7bed57 dev-serial: store flow control and xon/xoff characters
Note that whilst the device does not do anything with these values, they are
logged with trace events and stored to allow future implementation.

The default flow control is set to none at reset as documented in the Linux
ftdi_sio.h header file.

Signed-off-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk>
Reviewed-by: Samuel Thibault <samuel.thibault@ens-lyon.org>
Message-id: 20201027150456.24606-9-mark.cave-ayland@ilande.co.uk
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2020-11-04 07:22:37 +01:00

709 lines
19 KiB
C

/*
* 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/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(const char *unused)
{
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)