Hello, Samuel Thibault, le Fri 11 Jan 2008 00:23:12 +0000, a écrit : > I would like to implement support for braille devices, and for this I'd > need to first implement a USB serial device (FTDI chip). Has anybody > worked on that already?
Ok, was easier than expected, Here is a patch. The serial support is incomplete however because qemu still lacks support for flow control and modem lines. You will notice in tty_serial_init that I made the baud values more relaxed. This is because with divisor/baud conversions, things never get exact, so we need to be laxist with the value. For instance here with FTDI, the base divisor is 48000000/2, so for 57600 bps the guest needs to choose between divisors 416 and 417, which bring to either 57692bps or 57553bps but not exactly 57600bps. It happens that Linux chooses divisor 416, hence 57692bps. Of course, the higher the speed, the worse things get. The 1.1 factor is the smallest factor I could find between usual bps values, notably B110, B134 and B150. Samuel
Index: Makefile =================================================================== RCS file: /sources/qemu/qemu/Makefile,v retrieving revision 1.140 diff -u -p -r1.140 Makefile --- Makefile 6 Jan 2008 18:27:12 -0000 1.140 +++ Makefile 11 Jan 2008 10:59:50 -0000 @@ -57,7 +57,7 @@ OBJS+=i2c.o smbus.o smbus_eeprom.o max73 OBJS+=ssd0303.o ssd0323.o ads7846.o stellaris_input.o OBJS+=scsi-disk.o cdrom.o OBJS+=scsi-generic.o -OBJS+=usb.o usb-hub.o usb-linux.o usb-hid.o usb-msd.o usb-wacom.o +OBJS+=usb.o usb-hub.o usb-linux.o usb-hid.o usb-msd.o usb-wacom.o usb-serial.o OBJS+=sd.o ssi-sd.o ifdef CONFIG_WIN32 Index: vl.c =================================================================== RCS file: /sources/qemu/qemu/vl.c,v retrieving revision 1.395 diff -u -p -r1.395 vl.c --- vl.c 8 Jan 2008 19:32:16 -0000 1.395 +++ vl.c 11 Jan 2008 10:59:52 -0000 @@ -2237,45 +2237,33 @@ static void tty_serial_init(int fd, int #endif tcgetattr (fd, &tty); - switch(speed) { - case 50: +#define MARGIN 1.1 + if (speed <= 50 * MARGIN) spd = B50; - break; - case 75: + else if (speed <= 75 * MARGIN) spd = B75; - break; - case 300: + else if (speed <= 300 * MARGIN) spd = B300; - break; - case 600: + else if (speed <= 600 * MARGIN) spd = B600; - break; - case 1200: + else if (speed <= 1200 * MARGIN) spd = B1200; - break; - case 2400: + else if (speed <= 2400 * MARGIN) spd = B2400; - break; - case 4800: + else if (speed <= 4800 * MARGIN) spd = B4800; - break; - case 9600: + else if (speed <= 9600 * MARGIN) spd = B9600; - break; - case 19200: + else if (speed <= 19200 * MARGIN) spd = B19200; - break; - case 38400: + else if (speed <= 38400 * MARGIN) spd = B38400; - break; - case 57600: + else if (speed <= 57600 * MARGIN) spd = B57600; - break; - default: - case 115200: + else if (speed <= 115200 * MARGIN) + spd = B115200; + else spd = B115200; - break; - } cfsetispeed(&tty, spd); cfsetospeed(&tty, spd); @@ -5196,6 +5184,8 @@ static int usb_device_add(const char *de dev = usb_msd_init(p); } else if (!strcmp(devname, "wacom-tablet")) { dev = usb_wacom_init(); + } else if (strstart(devname, "serial:", &p)) { + dev = usb_serial_init(p); } else { return -1; } Index: hw/usb-serial.c =================================================================== RCS file: hw/usb-serial.c diff -N hw/usb-serial.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ hw/usb-serial.c 11 Jan 2008 10:59:52 -0000 @@ -0,0 +1,514 @@ +/* + * FTDI FT232BM Device emulation + * + * Copyright (c) 2006 CodeSourcery. + * Copyright (c) 2008 Samuel Thibault <[EMAIL PROTECTED]> + * Written by Paul Brook, reused for FTDI by Samuel Thibault + * + * This code is licenced under the LGPL. + */ + +#include "qemu-common.h" +#include "usb.h" +#include "qemu-char.h" + +//#define DEBUG_Serial + +#ifdef DEBUG_Serial +#define DPRINTF(fmt, args...) \ +do { printf("usb-serial: " fmt , ##args); } while (0) +#else +#define DPRINTF(fmt, args...) do {} while(0) +#endif + +#define RECV_BUF 384 +#define SEND_BUF 128 // Not used for now + +/* 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 + +#define DeviceVendor ((USB_DIR_OUT|USB_TYPE_VENDOR|USB_RECIP_DEVICE)<<8) + +/* RESET */ + +#define FTDI_RESET_SIO 0 +#define FTDI_RESET_RX 1 +#define FTDI_RESET_TX 2 + +/* SET_MDM_CTRL */ + +#define FTDI_MDM_CTRL 3 +#define FTDI_DTR 1 +#define FTDI_RTS 2 + +/* SET_FLOW_CTRL */ + +#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 + +typedef struct { + USBDevice dev; + uint8_t recv_buf[RECV_BUF]; + uint8_t recv_ptr; + uint8_t recv_used; + uint8_t send_buf[SEND_BUF]; + uint8_t event_chr; + uint8_t error_chr; + uint8_t event_trigger; + uint8_t lines; + QEMUSerialSetParams params; + int latency; /* ms */ + CharDriverState *cs; +} USBSerialState; + +static const uint8_t qemu_serial_dev_descriptor[] = { + 0x12, /* u8 bLength; */ + 0x01, /* u8 bDescriptorType; Device */ + 0x00, 0x02, /* u16 bcdUSB; v2.0 */ + + 0x00, /* u8 bDeviceClass; */ + 0x00, /* u8 bDeviceSubClass; */ + 0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x08, /* u8 bMaxPacketSize0; 8 Bytes */ + + /* Vendor and product id are arbitrary. */ + 0x03, 0x04, /* u16 idVendor; */ + 0x71, 0xFE, /* u16 idProduct; */ + //0x00, 0xFF, /* u16 idProduct; */ + 0x00, 0x04, /* u16 bcdDevice */ + + 0x01, /* u8 iManufacturer; */ + 0x02, /* u8 iProduct; */ + 0x03, /* u8 iSerialNumber; */ + 0x01 /* u8 bNumConfigurations; */ +}; + +static const uint8_t qemu_serial_config_descriptor[] = { + + /* one configuration */ + 0x09, /* u8 bLength; */ + 0x02, /* u8 bDescriptorType; Configuration */ + 0x20, 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; */ + + /* one interface */ + 0x09, /* u8 if_bLength; */ + 0x04, /* u8 if_bDescriptorType; Interface */ + 0x00, /* u8 if_bInterfaceNumber; */ + 0x00, /* u8 if_bAlternateSetting; */ + 0x02, /* u8 if_bNumEndpoints; */ + 0xff, /* u8 if_bInterfaceClass; Vendor Specific */ + 0xff, /* u8 if_bInterfaceSubClass; Vendor Specific */ + 0xff, /* u8 if_bInterfaceProtocol; Vendor Specific */ + 0x02, /* u8 if_iInterface; */ + + /* Bulk-In endpoint */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x81, /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x00, /* u8 ep_bInterval; */ + + /* Bulk-Out endpoint */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x02, /* u8 ep_bEndpointAddress; OUT Endpoint 2 */ + 0x02, /* u8 ep_bmAttributes; Bulk */ + 0x40, 0x00, /* u16 ep_wMaxPacketSize; */ + 0x00 /* u8 ep_bInterval; */ +}; + +static void usb_serial_reset(USBSerialState *s) +{ + /* TODO: Set flow control to none */ + s->event_chr = 0x0d; + s->event_trigger = 0; + s->recv_ptr = 0; + s->recv_used = 0; + /* TODO: purge in char driver */ + s->lines &= ~(FTDI_DTR|FTDI_RTS); +} + +static void usb_serial_handle_reset(USBDevice *dev) +{ + USBSerialState *s = (USBSerialState *)dev; + + DPRINTF("Reset\n"); + + usb_serial_reset(s); + /* TODO: Reset virtual device, send BREAK? */ +} + +static int usb_serial_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + USBSerialState *s = (USBSerialState *)dev; + int ret = 0; + + DPRINTF("got control %x, value %x\n",request, value); + 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, qemu_serial_dev_descriptor, + sizeof(qemu_serial_dev_descriptor)); + ret = sizeof(qemu_serial_dev_descriptor); + break; + case USB_DT_CONFIG: + memcpy(data, qemu_serial_config_descriptor, + sizeof(qemu_serial_config_descriptor)); + ret = sizeof(qemu_serial_config_descriptor); + break; + case USB_DT_STRING: + switch(value & 0xff) { + case 0: + /* language ids */ + data[0] = 4; + data[1] = 3; + data[2] = 0x09; + data[3] = 0x04; + ret = 4; + break; + case 1: + /* vendor description */ + ret = set_usb_string(data, "QEMU " QEMU_VERSION); + break; + case 2: + /* product description */ + ret = set_usb_string(data, "QEMU USB SERIAL"); + break; + case 3: + /* serial number */ + ret = set_usb_string(data, "1"); + 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 DeviceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == 0 && index != 0x81) { /* clear ep halt */ + goto fail; + } + ret = 0; + break; + + /* Class specific requests. */ + case DeviceVendor | 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 DeviceVendor | FTDI_SET_MDM_CTRL: + s->lines = value & FTDI_MDM_CTRL; + break; + case DeviceVendor | FTDI_SET_FLOW_CTRL: + /* TODO: ioctl */ + break; + case DeviceVendor | 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); + qemu_chr_ioctl(s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params); + break; + } + case DeviceVendor | FTDI_SET_DATA: + 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: + printf("unsupported parity %d\n", value & FTDI_PARITY); + break; + } + switch (value & FTDI_STOP) { + case FTDI_STOP1: + s->params.stop_bits = 1; + break; + case FTDI_STOP2: + s->params.stop_bits = 2; + break; + default: + printf("unsupported stop bits %d\n", value & FTDI_STOP); + break; + } + qemu_chr_ioctl(s->cs, CHR_IOCTL_SERIAL_SET_PARAMS, &s->params); + /* TODO: TX ON/OFF */ + break; + case DeviceVendor | FTDI_GET_MDM_ST: + /* TODO: return modem status */ + data[0] = 0; + ret = 1; + break; + case DeviceVendor | FTDI_SET_EVENT_CHR: + /* TODO: handle it */ + s->event_chr = value; + break; + case DeviceVendor | FTDI_SET_ERROR_CHR: + /* TODO: handle it */ + s->error_chr = value; + break; + case DeviceVendor | FTDI_SET_LATENCY: + s->latency = value; + break; + case DeviceVendor | FTDI_GET_LATENCY: + data[0] = s->latency; + ret = 1; + break; + default: + fail: + printf("got unsupported control %x, value %x\n",request, value); + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_serial_handle_data(USBDevice *dev, USBPacket *p) +{ + USBSerialState *s = (USBSerialState *)dev; + int ret = 0; + uint8_t devep = p->devep; + uint8_t *data = p->data; + int len = p->len; + int first_len; + + switch (p->pid) { + case USB_TOKEN_OUT: + if (devep != 2) + goto fail; + qemu_chr_write(s->cs, data, len); + break; + + case USB_TOKEN_IN: + if (devep != 1) + goto fail; + first_len = RECV_BUF - s->recv_ptr; + if (len <= 2) { + ret = USB_RET_NAK; + break; + } + /* TODO: Report serial line status */ + *data++ = 0; + *data++ = 0; + len -= 2; + if (len > s->recv_used) + len = s->recv_used; + if (!len) { + ret = USB_RET_NAK; + break; + } + if (first_len > len) + first_len = len; + memcpy(data, s->recv_buf + s->recv_ptr, first_len); + if (len > first_len) + memcpy(data + first_len, s->recv_buf, len - first_len); + s->recv_used -= len; + s->recv_ptr = (s->recv_ptr + len) % RECV_BUF; + ret = len + 2; + break; + + default: + DPRINTF("Bad token\n"); + fail: + ret = USB_RET_STALL; + break; + } + + return ret; +} + +static void usb_serial_handle_destroy(USBDevice *dev) +{ + USBSerialState *s = (USBSerialState *)dev; + + qemu_chr_close(s->cs); + qemu_free(s); +} + +int usb_serial_can_read(void *opaque) +{ + USBSerialState *s = opaque; + return RECV_BUF - s->recv_used; +} + +void usb_serial_read(void *opaque, const uint8_t *buf, int size) +{ + USBSerialState *s = opaque; + int first_size = RECV_BUF - s->recv_ptr; + if (first_size > size) + first_size = size; + memcpy(s->recv_buf + s->recv_ptr, buf, first_size); + if (size > first_size) + memcpy(s->recv_buf, buf + first_size, size - first_size); + s->recv_used += size; +} + +void usb_serial_event(void *opaque, int event) +{ + USBSerialState *s = opaque; + + switch (event) { + case CHR_EVENT_BREAK: + /* TODO: Send Break to USB */ + break; + case CHR_EVENT_FOCUS: + break; + case CHR_EVENT_RESET: + usb_serial_reset(s); + /* TODO: Reset USB port */ + break; + } +} + +USBDevice *usb_serial_init(const char *filename) +{ + USBSerialState *s; + CharDriverState *cdrv; + + s = qemu_mallocz(sizeof(USBSerialState)); + if (!s) + return NULL; + + cdrv = qemu_chr_open(filename); + if (!cdrv) + goto fail; + s->cs = cdrv; + + qemu_chr_add_handlers(cdrv, usb_serial_can_read, usb_serial_read, usb_serial_event, s); + + s->dev.speed = USB_SPEED_FULL; + s->dev.handle_packet = usb_generic_handle_packet; + + s->dev.handle_reset = usb_serial_handle_reset; + s->dev.handle_control = usb_serial_handle_control; + s->dev.handle_data = usb_serial_handle_data; + s->dev.handle_destroy = usb_serial_handle_destroy; + + snprintf(s->dev.devname, sizeof(s->dev.devname), "QEMU USB Serial(%.16s)", + filename); + + usb_serial_handle_reset((USBDevice *)s); + return (USBDevice *)s; + fail: + qemu_free(s); + return NULL; +} Index: hw/usb.h =================================================================== RCS file: /sources/qemu/qemu/hw/usb.h,v retrieving revision 1.19 diff -u -p -r1.19 usb.h --- hw/usb.h 17 Nov 2007 17:14:50 -0000 1.19 +++ hw/usb.h 11 Jan 2008 10:59:52 -0000 @@ -217,6 +217,9 @@ USBDevice *usb_msd_init(const char *file /* usb-wacom.c */ USBDevice *usb_wacom_init(void); +/* usb-serial.c */ +USBDevice *usb_serial_init(const char *filename); + /* usb ports of the VM */ void qemu_register_usb_port(USBPort *port, void *opaque, int index,